v0.2.87
This commit is contained in:
1 parent
acfe926066
commit
df403c0aa7
26 files changed
+1502
-144
No files matched your search
@@ -17,10 +17,12 @@ import { CollaborationRevisionGapError, CollaborationSession } from "@rustpad/co
|
||||
import { copyText } from "@rustpad/clipboard";
|
||||
import { lineFromHash, lineLink, lineStartOffset } from "@rustpad/line-links";
|
||||
import { applyFormat, bindFormatShortcuts, bindIndentationShortcuts } from "@rustpad/editor-format";
|
||||
import { createMentionAutocomplete } from "@rustpad/mention-autocomplete";
|
||||
import { createMentionNavigator } from "@rustpad/mention-navigation";
|
||||
import { updateImageAliasInLineBySource } from "@rustpad/image-alias";
|
||||
import { previewEditingHost } from "@rustpad/preview-edit";
|
||||
import { createRenderQueue } from "@rustpad/render-queue";
|
||||
import { alignPreviewLineNumbers, renderMarkdown, setMarkdownFiles, unresolvedMarkdownFileAliases } from "@rustpad/markdown";
|
||||
import { alignPreviewLineNumbers, renderMarkdown, setMarkdownFiles, setMarkdownMentions, unresolvedMarkdownFileAliases } from "@rustpad/markdown";
|
||||
import { getNickname, getGuestId, getAuthToken, getAccessToken, setAccessToken } from "@rustpad/session";
|
||||
import { bindIdentityDialog, validateCurrentSession } from "@rustpad/auth-ui";
|
||||
import { bindNoteFiles } from "@rustpad/note-files";
|
||||
@@ -49,7 +51,7 @@ export function startNoteEditor(adapter) {
|
||||
? crypto.randomUUID().replaceAll("-", "")
|
||||
: `${Date.now().toString(36)}${Math.random().toString(36).slice(2)}`;
|
||||
const collaboration = new CollaborationSession(collaborationClientId);
|
||||
let accessToken = shareToken || getAccessToken(adapter.access.kind, adapter.access.key), password = "", nickname = getNickname(), info, socket, saveTimer, applyingRemote = false, applyingHistory = false, resourceUnlocked = false, uiState = readEditorState(), authorship = parseAuthorship("", "[]"), previousContent = "", globalColor = "", noteColor = "", presenceUsers = [], authorshipMode = "simple", authorshipColorsEnabled = true, lastRevealedLineHash = "", flushRequested = false, accountSession = null;
|
||||
let accessToken = shareToken || getAccessToken(adapter.access.kind, adapter.access.key), password = "", nickname = getNickname(), info, socket, saveTimer, applyingRemote = false, applyingHistory = false, resourceUnlocked = false, uiState = readEditorState(), authorship = parseAuthorship("", "[]"), previousContent = "", globalColor = "", noteColor = "", presenceUsers = [], authorshipMode = "simple", authorshipColorsEnabled = true, lastRevealedLineHash = "", lastRevealedHeadingHash = "", flushRequested = false, accountSession = null;
|
||||
let editorSettingsSaveTimer, editorSettingsSaveInFlight = false, pendingPersonalSettingsSave = false, pendingAuthorshipSettingsSave = false, connectionNoticeTimer = 0, connectionWasInterrupted = false;
|
||||
let pendingPreviewViewport = null;
|
||||
const editHistory = {
|
||||
@@ -192,6 +194,7 @@ export function startNoteEditor(adapter) {
|
||||
editor.readOnly = readOnly;
|
||||
document.body.classList.toggle("document-read-only", readOnly);
|
||||
if (!readOnly) return;
|
||||
mentionAutocomplete.close();
|
||||
clearTimeout(saveTimer);
|
||||
saveState.textContent = label;
|
||||
const activePreviewEdit = preview.querySelector('.preview-editable[contenteditable="true"]');
|
||||
@@ -260,6 +263,37 @@ export function startNoteEditor(adapter) {
|
||||
renderFavoriteButton(false);
|
||||
}
|
||||
}
|
||||
const mentionAutocomplete = createMentionAutocomplete(editor);
|
||||
const mentionNavigator = createMentionNavigator({
|
||||
container: document.querySelector("#mentioned-users"),
|
||||
list: document.querySelector("#mentioned-users-list"),
|
||||
previewContainer: document.querySelector("#mentioned-users-preview"),
|
||||
previewList: document.querySelector("#mentioned-users-preview-list"),
|
||||
editor, preview, getView: () => activeView(), onEditorScroll: () => syncEditorLayers(),
|
||||
});
|
||||
let mentionLoadId = 0;
|
||||
async function refreshMentionUsers() {
|
||||
const requestId = ++mentionLoadId;
|
||||
setMarkdownMentions([]);
|
||||
mentionAutocomplete.setUsers([]);
|
||||
mentionNavigator.setUsers([]);
|
||||
if (!accountSession) { render(); return; }
|
||||
try {
|
||||
const params = new URLSearchParams({ kind: adapter.access.kind, slug: adapter.access.key });
|
||||
const response = await api(`/api/auth/resources/mentions?${params}`);
|
||||
if (requestId !== mentionLoadId) return;
|
||||
setMarkdownMentions(response.users || []);
|
||||
mentionAutocomplete.setUsers(response.users || []);
|
||||
mentionNavigator.setUsers(response.users || []);
|
||||
} catch {
|
||||
if (requestId !== mentionLoadId) return;
|
||||
setMarkdownMentions([]);
|
||||
mentionAutocomplete.setUsers([]);
|
||||
mentionNavigator.setUsers([]);
|
||||
}
|
||||
render();
|
||||
}
|
||||
|
||||
async function loadNoteInfo() {
|
||||
info = await adapter.loadInfo(sessionHeaders());
|
||||
const documentTitle = document.querySelector("#document-title");
|
||||
@@ -270,6 +304,7 @@ export function startNoteEditor(adapter) {
|
||||
globalColor = info.global_color || ""; noteColor = info.note_color || "";
|
||||
if (!getAuthToken()) noteColor = readGuestColor();
|
||||
updateMarkdownFiles(info.files || []);
|
||||
void refreshMentionUsers();
|
||||
if (info.personal_editor_settings) {
|
||||
compactToggle.checked = info.compact_view !== false;
|
||||
lineToggle.checked = info.editor_line_numbers !== false;
|
||||
@@ -315,11 +350,11 @@ export function startNoteEditor(adapter) {
|
||||
setDiagnosticField("quality", qualityLabel);
|
||||
setDiagnosticField("latency", Number.isFinite(latency.current)
|
||||
? t("diagnostics.latency", {
|
||||
current: formatNumber(latency.current),
|
||||
average: formatNumber(latency.average),
|
||||
minimum: formatNumber(latency.minimum),
|
||||
maximum: formatNumber(latency.maximum),
|
||||
}, `${latency.current} ms · avg ${latency.average} ms · ${latency.minimum}–${latency.maximum} ms`)
|
||||
current: formatNumber(latency.current),
|
||||
average: formatNumber(latency.average),
|
||||
minimum: formatNumber(latency.minimum),
|
||||
maximum: formatNumber(latency.maximum),
|
||||
}, `${latency.current} ms · avg ${latency.average} ms · ${latency.minimum}–${latency.maximum} ms`)
|
||||
: t("diagnostics.waitHeartbeat", {}, "Waiting for heartbeat"));
|
||||
setDiagnosticField("jitter", Number.isFinite(latency.jitter) ? `${formatNumber(latency.jitter)} ms` : "—");
|
||||
setDiagnosticField("uptime", runtime.authenticated_at
|
||||
@@ -513,6 +548,29 @@ export function startNoteEditor(adapter) {
|
||||
syncEditorLayers();
|
||||
}
|
||||
|
||||
function revealHeadingAnchor() {
|
||||
if (!location.hash || location.hash === lastRevealedHeadingHash || uiState.mode !== "markdown") return;
|
||||
let id;
|
||||
try { id = decodeURIComponent(location.hash.slice(1)); } catch { return; }
|
||||
if (!id || /^L[1-9]\d*$/i.test(id)) return;
|
||||
const heading = [...preview.querySelectorAll("h1[id], h2[id], h3[id], h4[id], h5[id], h6[id]")]
|
||||
.find(element => element.id === id);
|
||||
if (!heading) return;
|
||||
for (let parent = heading.parentElement; parent && parent !== preview; parent = parent.parentElement) {
|
||||
if (parent.matches("details.markdown-details")) parent.open = true;
|
||||
}
|
||||
if (renderedView !== "edit") {
|
||||
const headingBounds = heading.getBoundingClientRect();
|
||||
const previewBounds = preview.getBoundingClientRect();
|
||||
preview.scrollTop += headingBounds.top - previewBounds.top - 16;
|
||||
}
|
||||
if (renderedView !== "preview") {
|
||||
const sourceLine = Number(heading.dataset.sourceLine);
|
||||
if (Number.isFinite(sourceLine)) scrollEditorToAnchor({ sourceLine });
|
||||
}
|
||||
lastRevealedHeadingHash = location.hash;
|
||||
}
|
||||
|
||||
function revealLinkedLine() {
|
||||
if (!location.hash || location.hash === lastRevealedLineHash) return;
|
||||
const lineCount = Math.max(1, (editor.value.match(/\n/g) || []).length + 1);
|
||||
@@ -540,6 +598,7 @@ export function startNoteEditor(adapter) {
|
||||
function previewNodeMarkdownParts(current) {
|
||||
if (current.nodeType !== Node.ELEMENT_NODE) return { open: "", close: "", atomic: null };
|
||||
const tag = current.tagName.toLowerCase();
|
||||
if (current.classList.contains("heading-permalink")) return { open: "", close: "", atomic: "" };
|
||||
if (tag === "strong" || tag === "b") return { open: "**", close: "**", atomic: null };
|
||||
if (tag === "em" || tag === "i") return { open: "*", close: "*", atomic: null };
|
||||
if (tag === "s" || tag === "del") return { open: "~~", close: "~~", atomic: null };
|
||||
@@ -1176,6 +1235,7 @@ export function startNoteEditor(adapter) {
|
||||
preview.innerHTML = editor.value.split("\n").map((line, index) => `<div class="preview-source-line preview-editable" data-source-line="${index + 1}">${escapeHtml(line) || "<br>"}</div>`).join("");
|
||||
document.querySelector("#preview-label").textContent = "Text preview";
|
||||
}
|
||||
mentionNavigator.render({ markdown: uiState.mode === "markdown" });
|
||||
alignPreviewLineNumbers(preview);
|
||||
document.querySelector("#characters").textContent = `${editor.value.length} characters`;
|
||||
document.querySelector("#words").textContent = `${editor.value.trim() ? editor.value.trim().split(/\s+/).length : 0} words`;
|
||||
@@ -1183,6 +1243,7 @@ export function startNoteEditor(adapter) {
|
||||
requestAnimationFrame(() => {
|
||||
if (previewViewport) restorePreviewViewport(previewViewport);
|
||||
else syncPreviewScroll();
|
||||
revealHeadingAnchor();
|
||||
});
|
||||
}
|
||||
const render = createRenderQueue(renderNow);
|
||||
@@ -1252,6 +1313,7 @@ export function startNoteEditor(adapter) {
|
||||
|
||||
function restoreHistorySnapshot(snapshot) {
|
||||
if (!snapshot || !canEditDocument()) return;
|
||||
mentionAutocomplete.close();
|
||||
const previous = editor.value;
|
||||
const viewportScrollTop = editor.scrollTop;
|
||||
const viewportScrollLeft = editor.scrollLeft;
|
||||
@@ -1340,6 +1402,7 @@ export function startNoteEditor(adapter) {
|
||||
}
|
||||
|
||||
function applyEditorDocument(content, ownerMap, { resetHistory = true } = {}) {
|
||||
if (content !== editor.value) mentionAutocomplete.close();
|
||||
if (content === editor.value) {
|
||||
if (ownerMap != null) authorship = adoptCurrentOwnerAliases(parseAuthorship(content, ownerMap), content.length);
|
||||
previousContent = content;
|
||||
@@ -1966,8 +2029,20 @@ export function startNoteEditor(adapter) {
|
||||
}
|
||||
}
|
||||
}
|
||||
window.addEventListener("popstate", () => { const anchor = activeScrollAnchor(); lastRevealedLineHash = ""; uiState = readEditorState(); applyUi(); restoreScrollAnchor(anchor); requestAnimationFrame(revealLinkedLine); });
|
||||
window.addEventListener("hashchange", () => { lastRevealedLineHash = ""; renderGutter(); requestAnimationFrame(revealLinkedLine); });
|
||||
window.addEventListener("popstate", () => { const anchor = activeScrollAnchor(); lastRevealedLineHash = ""; lastRevealedHeadingHash = ""; uiState = readEditorState(); applyUi(); restoreScrollAnchor(anchor); requestAnimationFrame(() => { revealLinkedLine(); revealHeadingAnchor(); }); });
|
||||
window.addEventListener("hashchange", () => { lastRevealedLineHash = ""; lastRevealedHeadingHash = ""; renderGutter(); requestAnimationFrame(() => { revealLinkedLine(); revealHeadingAnchor(); }); });
|
||||
preview.addEventListener("click", event => {
|
||||
const link = event.target instanceof Element ? event.target.closest('a[href^="#"]') : null;
|
||||
if (!link) return;
|
||||
const targetHash = link.getAttribute("href");
|
||||
let headingId;
|
||||
try { headingId = decodeURIComponent(targetHash.slice(1)); } catch { return; }
|
||||
if (![...preview.querySelectorAll("h1[id], h2[id], h3[id], h4[id], h5[id], h6[id]")].some(node => node.id === headingId)) return;
|
||||
event.preventDefault();
|
||||
if (location.hash !== targetHash) history.pushState(null, "", targetHash);
|
||||
lastRevealedHeadingHash = "";
|
||||
revealHeadingAnchor();
|
||||
});
|
||||
window.addEventListener("rustpad:urlchange", updateAddressLabel);
|
||||
gutter.addEventListener("click", async event => {
|
||||
const button = event.target.closest(".line-number-button[data-line]");
|
||||
@@ -2020,10 +2095,12 @@ export function startNoteEditor(adapter) {
|
||||
});
|
||||
window.addEventListener("rustpad:session-change", event => {
|
||||
accountSession = event.detail?.session || null;
|
||||
void refreshMentionUsers();
|
||||
syncFavoriteButton();
|
||||
});
|
||||
window.addEventListener("rustpad:session-expired", () => {
|
||||
accountSession = null;
|
||||
void refreshMentionUsers();
|
||||
renderFavoriteButton(false);
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user