line links
This commit is contained in:
Generated
+1
-1
@@ -2581,7 +2581,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustpad"
|
name = "rustpad"
|
||||||
version = "0.2.0"
|
version = "0.2.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"argon2",
|
"argon2",
|
||||||
"aws-config",
|
"aws-config",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "rustpad"
|
name = "rustpad"
|
||||||
version = "0.2.0"
|
version = "0.2.1"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
rust-version = "1.94"
|
rust-version = "1.94"
|
||||||
description = "Collaborative Markdown notepad built with Axum, WebSockets and SQLite, PostgreSQL and MySQL"
|
description = "Collaborative Markdown notepad built with Axum, WebSockets and SQLite, PostgreSQL and MySQL"
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ const MODULES: &[&str] = &[
|
|||||||
"emoji-picker",
|
"emoji-picker",
|
||||||
"image-upload",
|
"image-upload",
|
||||||
"logger",
|
"logger",
|
||||||
|
"line-links",
|
||||||
"markdown",
|
"markdown",
|
||||||
"modal",
|
"modal",
|
||||||
"note-api",
|
"note-api",
|
||||||
|
|||||||
+38
-1
@@ -5298,4 +5298,41 @@ dialog::backdrop {
|
|||||||
left: 10px;
|
left: 10px;
|
||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/* Optional links to exact editor lines. */
|
||||||
|
.line-number-button {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: transparent;
|
||||||
|
color: inherit;
|
||||||
|
font: inherit;
|
||||||
|
text-align: right;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line-links-enabled .line-number-button {
|
||||||
|
cursor: copy;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line-links-enabled .line-number-button:hover,
|
||||||
|
.line-links-enabled .line-number-button:focus-visible {
|
||||||
|
background: color-mix(in srgb, var(--accent) 14%, transparent);
|
||||||
|
color: var(--text);
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line-number-button.is-linked {
|
||||||
|
background: color-mix(in srgb, var(--accent) 20%, transparent);
|
||||||
|
color: var(--text);
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line-number-button.is-copied {
|
||||||
|
background: color-mix(in srgb, var(--success) 18%, transparent);
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2026 Mateusz Gruszczyński @linuxiarz.pl
|
||||||
|
* Source-Available Code / Dual-Licensed.
|
||||||
|
*
|
||||||
|
* Free for non-commercial and evaluation use under terms of BSL/GPLv3.
|
||||||
|
* Commercial or production use requires a valid paid license.
|
||||||
|
* See LICENSE file in repository root for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const LINE_HASH = /^#L([1-9]\d*)$/i;
|
||||||
|
|
||||||
|
export function lineFromHash(hash, lineCount = Number.POSITIVE_INFINITY) {
|
||||||
|
const match = LINE_HASH.exec(String(hash || ""));
|
||||||
|
if (!match) return null;
|
||||||
|
const line = Number(match[1]);
|
||||||
|
if (!Number.isSafeInteger(line) || line > lineCount) return null;
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function lineStartOffset(text, line) {
|
||||||
|
const target = Number(line);
|
||||||
|
if (!Number.isSafeInteger(target) || target < 1) return null;
|
||||||
|
if (target === 1) return 0;
|
||||||
|
|
||||||
|
let currentLine = 1;
|
||||||
|
for (let index = 0; index < text.length; index += 1) {
|
||||||
|
if (text.charCodeAt(index) !== 10) continue;
|
||||||
|
currentLine += 1;
|
||||||
|
if (currentLine === target) return index + 1;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function lineLink(href, line) {
|
||||||
|
const target = Number(line);
|
||||||
|
if (!Number.isSafeInteger(target) || target < 1) throw new TypeError("Invalid line number");
|
||||||
|
const url = new URL(href);
|
||||||
|
url.hash = `L${target}`;
|
||||||
|
return url.href;
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ installGlobalDiagnostics();
|
|||||||
|
|
||||||
import { applyAuthorshipEdit, authorshipOwners, lineAuthors, mapSelectionThroughEdit, parseAuthorship, renderAuthorshipLayer, replaceAuthorshipOwner, serializeAuthorship, syncAuthorshipLayer } from "@rustpad/authorship";
|
import { applyAuthorshipEdit, authorshipOwners, lineAuthors, mapSelectionThroughEdit, parseAuthorship, renderAuthorshipLayer, replaceAuthorshipOwner, serializeAuthorship, syncAuthorshipLayer } from "@rustpad/authorship";
|
||||||
import { copyText } from "@rustpad/clipboard";
|
import { copyText } from "@rustpad/clipboard";
|
||||||
|
import { lineFromHash, lineLink, lineStartOffset } from "@rustpad/line-links";
|
||||||
import { applyFormat, bindFormatShortcuts } from "@rustpad/editor-format";
|
import { applyFormat, bindFormatShortcuts } from "@rustpad/editor-format";
|
||||||
import { bindEmojiPicker } from "@rustpad/emoji-picker";
|
import { bindEmojiPicker } from "@rustpad/emoji-picker";
|
||||||
import { alignPreviewLineNumbers, renderMarkdown } from "@rustpad/markdown";
|
import { alignPreviewLineNumbers, renderMarkdown } from "@rustpad/markdown";
|
||||||
@@ -26,16 +27,17 @@ export function startNoteEditor(adapter) {
|
|||||||
const modeToggle = document.querySelector("#mode-toggle"), passwordDialog = document.querySelector("#password-dialog"), identityDialog = document.querySelector("#identity-dialog");
|
const modeToggle = document.querySelector("#mode-toggle"), passwordDialog = document.querySelector("#password-dialog"), identityDialog = document.querySelector("#identity-dialog");
|
||||||
const accessLevel = document.querySelector("#access-level"), roomDetails = document.querySelector("#room-details"), roomUsers = document.querySelector("#room-users"), roomCount = document.querySelector("#room-count"), socketLatency = document.querySelector("#socket-latency"), chatMessages = document.querySelector("#chat-messages"), chatForm = document.querySelector("#chat-form"), chatInput = document.querySelector("#chat-input"), chatUnread = document.querySelector("#chat-unread"), mobileChatUnread = document.querySelector("#mobile-chat-unread");
|
const accessLevel = document.querySelector("#access-level"), roomDetails = document.querySelector("#room-details"), roomUsers = document.querySelector("#room-users"), roomCount = document.querySelector("#room-count"), socketLatency = document.querySelector("#socket-latency"), chatMessages = document.querySelector("#chat-messages"), chatForm = document.querySelector("#chat-form"), chatInput = document.querySelector("#chat-input"), chatUnread = document.querySelector("#chat-unread"), mobileChatUnread = document.querySelector("#mobile-chat-unread");
|
||||||
let unreadChat = 0;
|
let unreadChat = 0;
|
||||||
const compactToggle = document.querySelector("#compact-toggle"), authorshipColorsToggle = document.querySelector("#authorship-colors-toggle"), authorshipColorsLabel = document.querySelector("#authorship-colors-label"), saveEditorSettingsButton = document.querySelector("#save-editor-settings"), publicPageEnabled = document.querySelector("#public-page-enabled"), publicTaskUpdates = document.querySelector("#public-task-updates"), unprotectPublicPage = document.querySelector("#unprotect-public-page"), participantBadges = document.querySelector("#participant-badges"), fontFamily = document.querySelector("#font-family"), fontSize = document.querySelector("#font-size"), currentUser = document.querySelector("#current-user"), userColorPicker = document.querySelector("#user-color-picker"), useGlobalColorButton = document.querySelector("#use-global-color");
|
const compactToggle = document.querySelector("#compact-toggle"), lineLinksToggle = document.querySelector("#line-links-toggle"), authorshipColorsToggle = document.querySelector("#authorship-colors-toggle"), authorshipColorsLabel = document.querySelector("#authorship-colors-label"), saveEditorSettingsButton = document.querySelector("#save-editor-settings"), publicPageEnabled = document.querySelector("#public-page-enabled"), publicTaskUpdates = document.querySelector("#public-task-updates"), unprotectPublicPage = document.querySelector("#unprotect-public-page"), participantBadges = document.querySelector("#participant-badges"), fontFamily = document.querySelector("#font-family"), fontSize = document.querySelector("#font-size"), currentUser = document.querySelector("#current-user"), userColorPicker = document.querySelector("#user-color-picker"), useGlobalColorButton = document.querySelector("#use-global-color");
|
||||||
const shareToken = new URLSearchParams(location.search).get("share"); if (shareToken) setAccessToken(adapter.access.kind, adapter.access.key, shareToken);
|
const shareToken = new URLSearchParams(location.search).get("share"); if (shareToken) setAccessToken(adapter.access.kind, adapter.access.key, shareToken);
|
||||||
const notePreferenceKey = name => `rustpad:${name}:${adapter.access.kind}:${adapter.access.key}`;
|
const notePreferenceKey = name => `rustpad:${name}:${adapter.access.kind}:${adapter.access.key}`;
|
||||||
let accessToken = shareToken || getAccessToken(adapter.access.kind, adapter.access.key), password = "", nickname = getNickname(), info, socket, saveTimer, applyingRemote = false, resourceUnlocked = false, uiState = readEditorState(), authorship = parseAuthorship("", "[]"), previousContent = "", globalColor = "", noteColor = "", presenceUsers = [], authorshipMode = "simple", authorshipColorsEnabled = true;
|
let accessToken = shareToken || getAccessToken(adapter.access.kind, adapter.access.key), password = "", nickname = getNickname(), info, socket, saveTimer, applyingRemote = false, resourceUnlocked = false, uiState = readEditorState(), authorship = parseAuthorship("", "[]"), previousContent = "", globalColor = "", noteColor = "", presenceUsers = [], authorshipMode = "simple", authorshipColorsEnabled = true, lastRevealedLineHash = "";
|
||||||
const compactLayoutQuery = window.matchMedia("(max-width: 1499px)");
|
const compactLayoutQuery = window.matchMedia("(max-width: 1499px)");
|
||||||
const singlePaneQuery = window.matchMedia("(max-width: 760px)");
|
const singlePaneQuery = window.matchMedia("(max-width: 760px)");
|
||||||
let compactView = uiState.view === "preview" ? "preview" : "edit";
|
let compactView = uiState.view === "preview" ? "preview" : "edit";
|
||||||
const lineToggle = document.querySelector("#line-numbers-toggle"), previewLineToggle = document.querySelector("#preview-line-numbers-toggle"); lineToggle.checked = localStorage.getItem("rustpad:line-numbers") !== "off";
|
const lineToggle = document.querySelector("#line-numbers-toggle"), previewLineToggle = document.querySelector("#preview-line-numbers-toggle"); lineToggle.checked = localStorage.getItem("rustpad:line-numbers") !== "off";
|
||||||
previewLineToggle.checked = localStorage.getItem("rustpad:preview-line-numbers") === "on";
|
previewLineToggle.checked = localStorage.getItem("rustpad:preview-line-numbers") === "on";
|
||||||
compactToggle.checked = localStorage.getItem("rustpad:compact") !== "off";
|
compactToggle.checked = localStorage.getItem("rustpad:compact") !== "off";
|
||||||
|
lineLinksToggle.checked = localStorage.getItem("rustpad:line-links") === "on";
|
||||||
fontFamily.value = localStorage.getItem("rustpad:font-family") || "mono";
|
fontFamily.value = localStorage.getItem("rustpad:font-family") || "mono";
|
||||||
fontSize.value = localStorage.getItem("rustpad:font-size") || "14";
|
fontSize.value = localStorage.getItem("rustpad:font-size") || "14";
|
||||||
authorshipColorsToggle.checked = authorshipColorsEnabled;
|
authorshipColorsToggle.checked = authorshipColorsEnabled;
|
||||||
@@ -125,6 +127,11 @@ export function startNoteEditor(adapter) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function renderGutter() {
|
function renderGutter() {
|
||||||
|
document.body.classList.toggle("hide-editor-line-numbers", !lineToggle.checked);
|
||||||
|
document.body.classList.toggle("hide-preview-line-numbers", !previewLineToggle.checked);
|
||||||
|
document.body.classList.toggle("line-links-enabled", lineLinksToggle.checked);
|
||||||
|
gutter.setAttribute("aria-hidden", String(!lineLinksToggle.checked));
|
||||||
|
|
||||||
const lineCount = Math.max(1, (editor.value.match(/\n/g) || []).length + 1);
|
const lineCount = Math.max(1, (editor.value.match(/\n/g) || []).length + 1);
|
||||||
const lines = Array.from({ length: lineCount });
|
const lines = Array.from({ length: lineCount });
|
||||||
const owners = authorshipOwners(authorship);
|
const owners = authorshipOwners(authorship);
|
||||||
@@ -138,7 +145,7 @@ export function startNoteEditor(adapter) {
|
|||||||
editorWorkspace.dataset.authorshipMode = authorshipMode;
|
editorWorkspace.dataset.authorshipMode = authorshipMode;
|
||||||
const style = getComputedStyle(editor), lineHeight = parseFloat(style.lineHeight) || 29, paddingTop = parseFloat(style.paddingTop) || 24, paddingBottom = parseFloat(style.paddingBottom) || 24;
|
const style = getComputedStyle(editor), lineHeight = parseFloat(style.lineHeight) || 29, paddingTop = parseFloat(style.paddingTop) || 24, paddingBottom = parseFloat(style.paddingBottom) || 24;
|
||||||
gutter.style.paddingTop = `${paddingTop}px`; gutter.style.paddingBottom = `${paddingBottom}px`; gutter.style.lineHeight = `${lineHeight}px`;
|
gutter.style.paddingTop = `${paddingTop}px`; gutter.style.paddingBottom = `${paddingBottom}px`; gutter.style.lineHeight = `${lineHeight}px`;
|
||||||
gutter.innerHTML = lines.map((_, i) => `<div style="height:${lineHeight}px">${i + 1}</div>`).join("");
|
gutter.innerHTML = lines.map((_, i) => `<div class="line-number-row" style="height:${lineHeight}px"><button class="line-number-button" type="button" tabindex="-1" data-line="${i + 1}" aria-label="Copy link to line ${i + 1}">${i + 1}</button></div>`).join("");
|
||||||
ownerLabels.style.setProperty("--editor-line-height", `${lineHeight}px`);
|
ownerLabels.style.setProperty("--editor-line-height", `${lineHeight}px`);
|
||||||
if (full) {
|
if (full) {
|
||||||
let previousAuthorSignature = null;
|
let previousAuthorSignature = null;
|
||||||
@@ -155,9 +162,32 @@ export function startNoteEditor(adapter) {
|
|||||||
} else ownerLabels.replaceChildren();
|
} else ownerLabels.replaceChildren();
|
||||||
if (showAuthorship) renderAuthorshipLayer(authorshipLayer, editor, authorship, colorFor);
|
if (showAuthorship) renderAuthorshipLayer(authorshipLayer, editor, authorship, colorFor);
|
||||||
else authorshipLayer.replaceChildren();
|
else authorshipLayer.replaceChildren();
|
||||||
|
const linkedLine = lineFromHash(location.hash, lineCount);
|
||||||
|
gutter.querySelector(`[data-line="${linkedLine}"]`)?.classList.add("is-linked");
|
||||||
|
syncEditorLayers();
|
||||||
|
}
|
||||||
|
|
||||||
|
function revealLinkedLine() {
|
||||||
|
if (!location.hash || location.hash === lastRevealedLineHash) return;
|
||||||
|
const lineCount = Math.max(1, (editor.value.match(/\n/g) || []).length + 1);
|
||||||
|
const line = lineFromHash(location.hash, lineCount);
|
||||||
|
if (!line) return;
|
||||||
|
const offset = lineStartOffset(editor.value, line);
|
||||||
|
if (offset == null) return;
|
||||||
|
|
||||||
|
const style = getComputedStyle(editor);
|
||||||
|
const lineHeight = parseFloat(style.lineHeight) || 29;
|
||||||
|
const paddingTop = parseFloat(style.paddingTop) || 24;
|
||||||
|
const lineTop = paddingTop + (line - 1) * lineHeight;
|
||||||
|
const lineBottom = lineTop + lineHeight;
|
||||||
|
if (lineTop < editor.scrollTop || lineBottom > editor.scrollTop + editor.clientHeight) {
|
||||||
|
editor.scrollTop = Math.max(0, lineTop - Math.max(lineHeight, editor.clientHeight * 0.25));
|
||||||
|
}
|
||||||
|
editor.setSelectionRange(offset, offset);
|
||||||
|
lastRevealedLineHash = location.hash;
|
||||||
|
gutter.querySelectorAll(".line-number-button.is-linked").forEach(button => button.classList.remove("is-linked"));
|
||||||
|
gutter.querySelector(`[data-line="${line}"]`)?.classList.add("is-linked");
|
||||||
syncEditorLayers();
|
syncEditorLayers();
|
||||||
document.body.classList.toggle("hide-editor-line-numbers", !lineToggle.checked);
|
|
||||||
document.body.classList.toggle("hide-preview-line-numbers", !previewLineToggle.checked);
|
|
||||||
}
|
}
|
||||||
function escapeHtml(v) { return String(v).replace(/[&<>"']/g, c => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c])); } function formatDate(value) { const raw = String(value ?? "").trim(); let normalized = raw; if (/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(?:\.\d+)?[+-]\d{2}$/.test(normalized)) normalized = normalized.replace(" ", "T") + ":00"; else if (/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(?:\.\d+)?[+-]\d{2}:\d{2}$/.test(normalized)) normalized = normalized.replace(" ", "T"); else if (/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(?:\.\d+)?$/.test(normalized)) normalized = normalized.replace(" ", "T") + "Z"; const date = new Date(normalized); return Number.isNaN(date.getTime()) ? raw : date.toLocaleString("pl-PL", { year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit" }); }
|
function escapeHtml(v) { return String(v).replace(/[&<>"']/g, c => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c])); } function formatDate(value) { const raw = String(value ?? "").trim(); let normalized = raw; if (/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(?:\.\d+)?[+-]\d{2}$/.test(normalized)) normalized = normalized.replace(" ", "T") + ":00"; else if (/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(?:\.\d+)?[+-]\d{2}:\d{2}$/.test(normalized)) normalized = normalized.replace(" ", "T"); else if (/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(?:\.\d+)?$/.test(normalized)) normalized = normalized.replace(" ", "T") + "Z"; const date = new Date(normalized); return Number.isNaN(date.getTime()) ? raw : date.toLocaleString("pl-PL", { year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit" }); }
|
||||||
|
|
||||||
@@ -493,6 +523,7 @@ export function startNoteEditor(adapter) {
|
|||||||
if (ownerMap != null) authorship = adoptCurrentOwnerAliases(parseAuthorship(content, ownerMap), content.length);
|
if (ownerMap != null) authorship = adoptCurrentOwnerAliases(parseAuthorship(content, ownerMap), content.length);
|
||||||
previousContent = content;
|
previousContent = content;
|
||||||
renderGutter();
|
renderGutter();
|
||||||
|
requestAnimationFrame(revealLinkedLine);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const previewSnapshot = previewEditSnapshot();
|
const previewSnapshot = previewEditSnapshot();
|
||||||
@@ -511,6 +542,7 @@ export function startNoteEditor(adapter) {
|
|||||||
editor.scrollLeft = scrollLeft;
|
editor.scrollLeft = scrollLeft;
|
||||||
syncEditorLayers();
|
syncEditorLayers();
|
||||||
restorePreviewEdit(previewSnapshot);
|
restorePreviewEdit(previewSnapshot);
|
||||||
|
requestAnimationFrame(revealLinkedLine);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { loadFiles } = bindNoteFiles({
|
const { loadFiles } = bindNoteFiles({
|
||||||
@@ -598,7 +630,7 @@ export function startNoteEditor(adapter) {
|
|||||||
compactLayoutQuery.addEventListener("change", event => {
|
compactLayoutQuery.addEventListener("change", event => {
|
||||||
if (!event.matches) setHeaderMenuOpen(false);
|
if (!event.matches) setHeaderMenuOpen(false);
|
||||||
});
|
});
|
||||||
modeToggle.addEventListener("click", () => { uiState = { ...uiState, mode: uiState.mode === "markdown" ? "text" : "markdown" }; applyUi({ write: true }); }); lineToggle.addEventListener("change", () => { localStorage.setItem("rustpad:line-numbers", lineToggle.checked ? "on" : "off"); renderGutter(); }); previewLineToggle.addEventListener("change", () => { localStorage.setItem("rustpad:preview-line-numbers", previewLineToggle.checked ? "on" : "off"); renderGutter(); alignPreviewLineNumbers(preview); }); compactToggle.addEventListener("change", () => { localStorage.setItem("rustpad:compact", compactToggle.checked ? "on" : "off"); applyUi(); }); fontFamily.addEventListener("change", () => { localStorage.setItem("rustpad:font-family", fontFamily.value); applyUi(); }); fontSize.addEventListener("change", () => { localStorage.setItem("rustpad:font-size", fontSize.value); applyUi(); });
|
modeToggle.addEventListener("click", () => { uiState = { ...uiState, mode: uiState.mode === "markdown" ? "text" : "markdown" }; applyUi({ write: true }); }); lineToggle.addEventListener("change", () => { localStorage.setItem("rustpad:line-numbers", lineToggle.checked ? "on" : "off"); renderGutter(); }); previewLineToggle.addEventListener("change", () => { localStorage.setItem("rustpad:preview-line-numbers", previewLineToggle.checked ? "on" : "off"); renderGutter(); alignPreviewLineNumbers(preview); }); compactToggle.addEventListener("change", () => { localStorage.setItem("rustpad:compact", compactToggle.checked ? "on" : "off"); applyUi(); }); lineLinksToggle.addEventListener("change", () => { localStorage.setItem("rustpad:line-links", lineLinksToggle.checked ? "on" : "off"); renderGutter(); }); fontFamily.addEventListener("change", () => { localStorage.setItem("rustpad:font-family", fontFamily.value); applyUi(); }); fontSize.addEventListener("change", () => { localStorage.setItem("rustpad:font-size", fontSize.value); applyUi(); });
|
||||||
document.querySelector("#mobile-files-button")?.addEventListener("click", () => document.querySelector("#files-button")?.click());
|
document.querySelector("#mobile-files-button")?.addEventListener("click", () => document.querySelector("#files-button")?.click());
|
||||||
document.querySelector("#mobile-color-button")?.addEventListener("click", () => userColorPicker.click());
|
document.querySelector("#mobile-color-button")?.addEventListener("click", () => userColorPicker.click());
|
||||||
const compactBubbleQuery = matchMedia("(max-width: 1499px)");
|
const compactBubbleQuery = matchMedia("(max-width: 1499px)");
|
||||||
@@ -762,8 +794,23 @@ export function startNoteEditor(adapter) {
|
|||||||
saveEditorSettingsButton.disabled = !info?.can_save_editor_settings;
|
saveEditorSettingsButton.disabled = !info?.can_save_editor_settings;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
window.addEventListener("popstate", () => { uiState = readEditorState(); applyUi(); });
|
window.addEventListener("popstate", () => { lastRevealedLineHash = ""; uiState = readEditorState(); applyUi(); requestAnimationFrame(revealLinkedLine); });
|
||||||
|
window.addEventListener("hashchange", () => { lastRevealedLineHash = ""; renderGutter(); requestAnimationFrame(revealLinkedLine); });
|
||||||
window.addEventListener("rustpad:urlchange", updateAddressLabel);
|
window.addEventListener("rustpad:urlchange", updateAddressLabel);
|
||||||
|
gutter.addEventListener("click", async event => {
|
||||||
|
const button = event.target.closest(".line-number-button[data-line]");
|
||||||
|
if (!button || !lineLinksToggle.checked) return;
|
||||||
|
const line = Number(button.dataset.line);
|
||||||
|
try {
|
||||||
|
await copyText(lineLink(currentShareUrl(uiState), line));
|
||||||
|
gutter.querySelectorAll(".line-number-button.is-copied").forEach(item => item.classList.remove("is-copied"));
|
||||||
|
button.classList.add("is-copied");
|
||||||
|
setTimeout(() => button.classList.remove("is-copied"), 900);
|
||||||
|
toast(`Link to line ${line} copied`);
|
||||||
|
} catch (error) {
|
||||||
|
toast(error.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
document.querySelector("#copy-link").addEventListener("click", async () => {
|
document.querySelector("#copy-link").addEventListener("click", async () => {
|
||||||
try { await copyText(currentShareUrl(uiState)); toast("Link copied"); }
|
try { await copyText(currentShareUrl(uiState)); toast("Link copied"); }
|
||||||
catch (error) { toast(error.message); }
|
catch (error) { toast(error.message); }
|
||||||
|
|||||||
+2
-1
@@ -108,7 +108,8 @@
|
|||||||
class="line-toggle"><input id="line-numbers-toggle" type="checkbox" checked> Editor
|
class="line-toggle"><input id="line-numbers-toggle" type="checkbox" checked> Editor
|
||||||
lines</label><label class="line-toggle"><input id="preview-line-numbers-toggle" type="checkbox">
|
lines</label><label class="line-toggle"><input id="preview-line-numbers-toggle" type="checkbox">
|
||||||
Preview lines</label><label class="line-toggle"><input id="compact-toggle" type="checkbox" checked>
|
Preview lines</label><label class="line-toggle"><input id="compact-toggle" type="checkbox" checked>
|
||||||
Compact</label>
|
Compact</label><label class="line-toggle"><input id="line-links-toggle" type="checkbox">
|
||||||
|
Line links</label>
|
||||||
<div class="toolbar-fill"></div><button id="mode-toggle" class="markdown-toggle active"
|
<div class="toolbar-fill"></div><button id="mode-toggle" class="markdown-toggle active"
|
||||||
aria-pressed="true">Markdown</button>
|
aria-pressed="true">Markdown</button>
|
||||||
<div class="view-switch"><button data-view="edit">Edit</button><button data-view="split"
|
<div class="view-switch"><button data-view="edit">Edit</button><button data-view="split"
|
||||||
|
|||||||
+2
-1
@@ -107,7 +107,8 @@
|
|||||||
class="line-toggle"><input id="line-numbers-toggle" type="checkbox" checked> Editor
|
class="line-toggle"><input id="line-numbers-toggle" type="checkbox" checked> Editor
|
||||||
lines</label><label class="line-toggle"><input id="preview-line-numbers-toggle" type="checkbox">
|
lines</label><label class="line-toggle"><input id="preview-line-numbers-toggle" type="checkbox">
|
||||||
Preview lines</label><label class="line-toggle"><input id="compact-toggle" type="checkbox" checked>
|
Preview lines</label><label class="line-toggle"><input id="compact-toggle" type="checkbox" checked>
|
||||||
Compact</label>
|
Compact</label><label class="line-toggle"><input id="line-links-toggle" type="checkbox">
|
||||||
|
Line links</label>
|
||||||
<div class="toolbar-fill"></div><button id="mode-toggle" class="markdown-toggle active"
|
<div class="toolbar-fill"></div><button id="mode-toggle" class="markdown-toggle active"
|
||||||
aria-pressed="true">Markdown</button>
|
aria-pressed="true">Markdown</button>
|
||||||
<div class="view-switch"><button data-view="edit">Edit</button><button data-view="split"
|
<div class="view-switch"><button data-view="edit">Edit</button><button data-view="split"
|
||||||
|
|||||||
Reference in New Issue
Block a user