This commit is contained in:
Mateusz Gruszczyński committed 2026-10-14 13:38:59 +02:00
1 parent df403c0aa7
commit 9e133628e5
17 files changed
+270 -34

No files matched your search

+10
View File
@@ -4099,6 +4099,16 @@ dialog::backdrop {
line-height: inherit;
}
.markdown-body .task-list-item--cancelled>.list-item-content {
text-decoration: line-through;
color: var(--text-subtle);
}
.markdown-body .task-list-item--cancelled>.task-checkbox {
cursor: not-allowed;
opacity: .6;
}
.markdown-body .task-list-item>ul,
.markdown-body .task-list-item>ol {
min-width: 0;
+5 -4
View File
@@ -274,7 +274,7 @@ function renderStandaloneMedia(line, sourceLine) {
}
function listLine(line) {
const match = line.match(/^(\s*)([-*+]|(\d+)\.)\s+(?:\[([ xX])\]\s+)?(.+)$/);
const match = line.match(/^(\s*)([-*+]|(\d+)\.)\s+(?:\[([ xX~])\]\s+)?(.+)$/);
if (!match) return null;
const indent = match[1].replace(/\t/g, " ").length;
return {
@@ -283,6 +283,7 @@ function listLine(line) {
type: match[3] ? "ol" : "ul",
number: match[3] ? Number(match[3]) : null,
checked: match[4] == null ? null : match[4].toLowerCase() === "x",
cancelled: match[4] === "~",
text: match[5],
marker: match[3] ? `${match[3]}. ` : `${match[2]} `
};
@@ -302,14 +303,14 @@ function renderList(lines, start, lineOffset = 0, baseIndent = null, forcedType
if (!item || item.indent < indent || item.indent !== indent || item.type !== type) break;
const sourceLine = index + lineOffset + 1;
const prefix = `${item.whitespace}${item.marker}${item.checked == null ? "" : `[${item.checked ? "x" : " "}] `}`;
const prefix = `${item.whitespace}${item.marker}${item.checked == null ? "" : `[${item.cancelled ? "~" : item.checked ? "x" : " "}] `}`;
const valueAttr = "";
const taskClass = item.checked == null ? "" : " task-list-item";
const taskClass = item.checked == null ? "" : ` task-list-item${item.cancelled ? " task-list-item--cancelled" : ""}`;
hasTask ||= item.checked != null;
const gutterOffset = `${(depth + 1) * 1.75}em`;
body += `<li class="preview-source-line list-source-line${taskClass}" data-source-line="${sourceLine}"${valueAttr} style="--list-gutter-offset:${gutterOffset}">`;
if (item.checked != null) {
body += `<input type="checkbox" class="task-checkbox" data-source-line="${sourceLine}"${item.checked ? " checked" : ""}>`;
body += `<input type="checkbox" class="task-checkbox" data-source-line="${sourceLine}"${item.checked ? " checked" : ""}${item.cancelled ? " disabled aria-label=\"Cancelled task\"" : ""}>`;
}
body += `<span class="preview-editable list-item-content" data-source-line="${sourceLine}" data-source-prefix="${escapeHtml(prefix)}">${inline(item.text)}</span>`;
index++;
+19 -4
View File
@@ -1247,6 +1247,21 @@ export function startNoteEditor(adapter) {
});
}
const render = createRenderQueue(renderNow);
// Collaborative edits can arrive many times per second. Keep typing and
// WebSocket processing responsive by batching expensive Markdown/DOM work.
let deferredRenderTimer = null;
let firstDeferredRenderAt = 0;
function scheduleDocumentRender() {
const now = performance.now();
if (deferredRenderTimer === null) firstDeferredRenderAt = now;
else clearTimeout(deferredRenderTimer);
const delay = Math.min(180, Math.max(0, 750 - (now - firstDeferredRenderAt)));
deferredRenderTimer = setTimeout(() => {
deferredRenderTimer = null;
firstDeferredRenderAt = 0;
render();
}, delay);
}
function queueCollaborativeOperation(operation, ownerReplacements = []) {
if (!collaboration.ready || !canEditDocument()) return false;
try {
@@ -1407,7 +1422,7 @@ export function startNoteEditor(adapter) {
if (ownerMap != null) authorship = adoptCurrentOwnerAliases(parseAuthorship(content, ownerMap), content.length);
previousContent = content;
editHistory.syncCurrent();
renderGutter();
scheduleDocumentRender();
requestAnimationFrame(revealLinkedLine);
return;
}
@@ -1427,7 +1442,7 @@ export function startNoteEditor(adapter) {
applyingRemote = false;
if (resetHistory) editHistory.reset();
else editHistory.syncCurrent();
render();
scheduleDocumentRender();
editor.scrollTop = initialLoad ? 0 : scrollTop;
editor.scrollLeft = scrollLeft;
syncEditorLayers();
@@ -2238,7 +2253,7 @@ export function startNoteEditor(adapter) {
});
preview.addEventListener("change", event => {
const checkbox = event.target.closest(".task-checkbox");
if (!checkbox) return;
if (!checkbox || checkbox.disabled) return;
if (!canEditDocument()) {
checkbox.checked = !checkbox.checked;
saveState.textContent = "Read only";
@@ -2488,7 +2503,7 @@ export function startNoteEditor(adapter) {
authorship = replaceAuthorshipOwner(authorship, owner => ownerName(owner) === nickname, currentOwner(), previous.length);
authorship = applyAuthorshipEdit(authorship, previous, nextContent, currentOwner());
previousContent = nextContent;
render();
scheduleDocumentRender();
if (applyingRemote || applyingHistory) return;
queueCollaborativeOperation(operationFromEdit(previous, nextContent, authorship));
editHistory.record(event.inputType || "");
+1 -1
View File
@@ -60,7 +60,7 @@ async function renderMediaPlayers() { const nodes = content.querySelectorAll("[d
function lockPublicContent(allowTaskUpdates) {
content.querySelectorAll('[contenteditable]').forEach(node => node.removeAttribute('contenteditable'));
content.querySelectorAll('.preview-editable').forEach(node => node.classList.remove('preview-editable'));
content.querySelectorAll('.task-checkbox').forEach(box => { box.disabled = !allowTaskUpdates; box.title = allowTaskUpdates ? t("public.task.update", {}, "Update this task") : t("public.task.disabled", {}, "Task updates are disabled by the owner"); });
content.querySelectorAll('.task-checkbox').forEach(box => { box.disabled = box.disabled || !allowTaskUpdates; box.title = box.disabled ? t("public.task.update", {}, "Update this task") : t("public.task.disabled", {}, "Task updates are disabled by the owner"); });
}
function publicAnchorTarget(hash) {
const rawId = String(hash || "").replace(/^#/, "");