page style
This commit is contained in:
Generated
+1
-1
@@ -2581,7 +2581,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustpad"
|
name = "rustpad"
|
||||||
version = "0.2.9"
|
version = "0.2.10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"argon2",
|
"argon2",
|
||||||
"aws-config",
|
"aws-config",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "rustpad"
|
name = "rustpad"
|
||||||
version = "0.2.9"
|
version = "0.2.10"
|
||||||
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"
|
||||||
|
|||||||
+78
-9
@@ -4861,7 +4861,7 @@ dialog::backdrop {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 36px;
|
min-height: 36px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-start;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pad-page .header-actions .page-settings-menu {
|
.pad-page .header-actions .page-settings-menu {
|
||||||
@@ -5453,12 +5453,16 @@ dialog::backdrop {
|
|||||||
min-height: 0;
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Compact Page settings dropdown. */
|
/* Published page action and settings in one dropdown. */
|
||||||
.page-settings {
|
.page-settings {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-settings>summary {
|
.page-settings>summary {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 9px;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
@@ -5467,6 +5471,35 @@ dialog::backdrop {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.page-settings__label {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-settings__status {
|
||||||
|
width: 7px;
|
||||||
|
height: 7px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--muted);
|
||||||
|
box-shadow: 0 0 0 2px color-mix(in srgb, var(--muted) 18%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-settings.is-enabled .page-settings__status {
|
||||||
|
background: var(--accent);
|
||||||
|
box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 22%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-settings__chevron {
|
||||||
|
font-size: .72em;
|
||||||
|
line-height: 1;
|
||||||
|
transition: transform .16s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-settings[open] .page-settings__chevron {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
.page-settings-menu {
|
.page-settings-menu {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 30;
|
z-index: 30;
|
||||||
@@ -5474,7 +5507,7 @@ dialog::backdrop {
|
|||||||
right: 0;
|
right: 0;
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
min-width: 220px;
|
min-width: 260px;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
border-radius: 9px;
|
border-radius: 9px;
|
||||||
@@ -5482,6 +5515,46 @@ dialog::backdrop {
|
|||||||
box-shadow: 0 12px 30px var(--shadow-28);
|
box-shadow: 0 12px 30px var(--shadow-28);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.page-settings-action {
|
||||||
|
display: grid;
|
||||||
|
gap: 2px;
|
||||||
|
width: 100%;
|
||||||
|
min-height: 46px;
|
||||||
|
padding: 7px 9px;
|
||||||
|
border: 1px solid color-mix(in srgb, var(--accent) 42%, var(--border));
|
||||||
|
border-radius: 7px;
|
||||||
|
background: var(--note-action-accent-bg);
|
||||||
|
color: var(--text);
|
||||||
|
text-align: left;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-settings-action span {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-settings-action small {
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: .72rem;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-settings-action:hover:not(:disabled) {
|
||||||
|
border-color: color-mix(in srgb, var(--accent) 68%, var(--border));
|
||||||
|
background: var(--note-action-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-settings-action:disabled {
|
||||||
|
opacity: .45;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-settings-divider {
|
||||||
|
height: 1px;
|
||||||
|
margin: 3px 1px;
|
||||||
|
background: var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
.page-settings-menu .public-task-toggle,
|
.page-settings-menu .public-task-toggle,
|
||||||
.mobile-editor-options__panel .mobile-option-check {
|
.mobile-editor-options__panel .mobile-option-check {
|
||||||
min-height: 30px;
|
min-height: 30px;
|
||||||
@@ -5495,11 +5568,6 @@ dialog::backdrop {
|
|||||||
background: var(--surface-hover);
|
background: var(--surface-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
#publish-page:disabled {
|
|
||||||
opacity: .45;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Keep the Simple/Full switch inside the editor label frame. */
|
/* Keep the Simple/Full switch inside the editor label frame. */
|
||||||
.authorship-mode-control {
|
.authorship-mode-control {
|
||||||
padding: 1px;
|
padding: 1px;
|
||||||
@@ -6007,7 +6075,7 @@ dialog::backdrop {
|
|||||||
transform: translateY(0);
|
transform: translateY(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
.pad-page #publish-page:not(:disabled) {
|
.pad-page .page-settings-action:not(:disabled) {
|
||||||
border-color: color-mix(in srgb, var(--accent) 55%, var(--note-action-border));
|
border-color: color-mix(in srgb, var(--accent) 55%, var(--note-action-border));
|
||||||
background: var(--note-action-accent-bg);
|
background: var(--note-action-accent-bg);
|
||||||
color: var(--accent-text-strong);
|
color: var(--accent-text-strong);
|
||||||
@@ -6015,6 +6083,7 @@ dialog::backdrop {
|
|||||||
|
|
||||||
.pad-page .header-actions>.secondary-button:focus-visible,
|
.pad-page .header-actions>.secondary-button:focus-visible,
|
||||||
.pad-page .page-settings>summary.secondary-button:focus-visible,
|
.pad-page .page-settings>summary.secondary-button:focus-visible,
|
||||||
|
.pad-page .page-settings-action:focus-visible,
|
||||||
.pad-page .editor-toolbar :is(button, summary, select):focus-visible {
|
.pad-page .editor-toolbar :is(button, summary, select):focus-visible {
|
||||||
outline: 2px solid var(--focus);
|
outline: 2px solid var(--focus);
|
||||||
outline-offset: 2px;
|
outline-offset: 2px;
|
||||||
|
|||||||
+14
-6
@@ -34,17 +34,23 @@
|
|||||||
<span aria-hidden="true"></span><span aria-hidden="true"></span><span aria-hidden="true"></span>
|
<span aria-hidden="true"></span><span aria-hidden="true"></span><span aria-hidden="true"></span>
|
||||||
</button>
|
</button>
|
||||||
<div id="header-actions" class="header-actions"><button id="copy-link" class="secondary-button">Copy
|
<div id="header-actions" class="header-actions"><button id="copy-link" class="secondary-button">Copy
|
||||||
link</button><button id="publish-page" class="secondary-button">Page</button>
|
link</button>
|
||||||
<details class="page-settings">
|
<details class="page-settings">
|
||||||
<summary class="secondary-button">Page settings</summary>
|
<summary class="secondary-button" aria-label="Page options"><span class="page-settings__label"><span
|
||||||
<div class="page-settings-menu"><label class="public-task-toggle"
|
class="page-settings__status" aria-hidden="true"></span>Page</span><span
|
||||||
|
class="page-settings__chevron" aria-hidden="true">▾</span></summary>
|
||||||
|
<div class="page-settings-menu"><button id="publish-page" class="page-settings-action"
|
||||||
|
type="button"><span>Open page</span><small>Copy its link and open it in a new
|
||||||
|
tab</small></button>
|
||||||
|
<div class="page-settings-divider" role="separator"></div><label class="public-task-toggle"
|
||||||
title="Enable or disable the published page"><input id="public-page-enabled"
|
title="Enable or disable the published page"><input id="public-page-enabled"
|
||||||
type="checkbox"> Enable Page</label><label class="public-task-toggle"
|
type="checkbox"> Enable Page</label><label class="public-task-toggle"
|
||||||
title="Allow visitors to update task checkboxes on the published page"><input
|
title="Allow visitors to update task checkboxes on the published page"><input
|
||||||
id="public-task-updates" type="checkbox"> Editable tasks</label><label
|
id="public-task-updates" type="checkbox"> Editable tasks</label><label
|
||||||
class="public-task-toggle"
|
class="public-task-toggle"
|
||||||
title="Allow the published page to open without the resource password or private access"><input
|
title="Allow the published page to open without the resource password or private access"><input
|
||||||
id="unprotect-public-page" type="checkbox"> Unprotect Page</label></div>
|
id="unprotect-public-page" type="checkbox"> Unprotect Page</label>
|
||||||
|
</div>
|
||||||
</details><button id="files-button" class="secondary-button">Files</button><button id="delete-note"
|
</details><button id="files-button" class="secondary-button">Files</button><button id="delete-note"
|
||||||
class="secondary-button danger-button" hidden>Delete</button><button id="history-button"
|
class="secondary-button danger-button" hidden>Delete</button><button id="history-button"
|
||||||
class="secondary-button">History</button>
|
class="secondary-button">History</button>
|
||||||
@@ -118,7 +124,8 @@
|
|||||||
class="active">Split</button><button data-view="preview">Preview</button></div>
|
class="active">Split</button><button data-view="preview">Preview</button></div>
|
||||||
</div>
|
</div>
|
||||||
<div id="connection-notice" class="connection-notice" role="status" aria-live="polite" hidden>
|
<div id="connection-notice" class="connection-notice" role="status" aria-live="polite" hidden>
|
||||||
<span class="connection-notice__signal" aria-hidden="true"><span></span><span></span><span></span></span>
|
<span class="connection-notice__signal"
|
||||||
|
aria-hidden="true"><span></span><span></span><span></span></span>
|
||||||
<span class="connection-notice__content"><strong id="connection-notice-title">Connection
|
<span class="connection-notice__content"><strong id="connection-notice-title">Connection
|
||||||
interrupted</strong><span id="connection-notice-message">Trying to reconnect
|
interrupted</strong><span id="connection-notice-message">Trying to reconnect
|
||||||
automatically.</span></span>
|
automatically.</span></span>
|
||||||
@@ -199,7 +206,8 @@
|
|||||||
</div><button id="close-shortcuts" class="icon-button" type="button">×</button>
|
</div><button id="close-shortcuts" class="icon-button" type="button">×</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="shortcut-grid">
|
<div class="shortcut-grid">
|
||||||
<kbd>Ctrl/Cmd+Z</kbd><span>Undo</span><kbd>Ctrl/Cmd+B</kbd><span>Bold</span><kbd>Ctrl/Cmd+I</kbd><span>Italic</span><kbd>Ctrl/Cmd+Shift+X</kbd><span>Strikethrough</span><kbd>Ctrl/Cmd+K</kbd><span>Link</span><kbd>Ctrl/Cmd+Shift+7</kbd><span>Numbered
|
<kbd>Ctrl/Cmd+Z</kbd><span>Undo last
|
||||||
|
change</span><kbd>Ctrl/Cmd+Shift+Z</kbd><span>Redo</span><kbd>Ctrl/Cmd+B</kbd><span>Bold</span><kbd>Ctrl/Cmd+I</kbd><span>Italic</span><kbd>Ctrl/Cmd+Shift+X</kbd><span>Strikethrough</span><kbd>Ctrl/Cmd+K</kbd><span>Link</span><kbd>Ctrl/Cmd+Shift+7</kbd><span>Numbered
|
||||||
list</span><kbd>Ctrl/Cmd+Shift+8</kbd><span>Bullet list</span><kbd>Ctrl/Cmd+Shift+9</kbd><span>Task
|
list</span><kbd>Ctrl/Cmd+Shift+8</kbd><span>Bullet list</span><kbd>Ctrl/Cmd+Shift+9</kbd><span>Task
|
||||||
list</span><kbd>Alt+1…4</kbd><span>Headings H1–H4</span>__EXTRA_SHORTCUTS__
|
list</span><kbd>Alt+1…4</kbd><span>Headings H1–H4</span>__EXTRA_SHORTCUTS__
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+134
-6
@@ -32,8 +32,80 @@ export function startNoteEditor(adapter) {
|
|||||||
const mobileFontFamily = document.querySelector("#mobile-font-family"), mobileFontSize = document.querySelector("#mobile-font-size"), mobileLineToggle = document.querySelector("#mobile-line-numbers-toggle"), mobilePreviewLineToggle = document.querySelector("#mobile-preview-line-numbers-toggle"), mobileCompactToggle = document.querySelector("#mobile-compact-toggle"), mobileLineLinksToggle = document.querySelector("#mobile-line-links-toggle");
|
const mobileFontFamily = document.querySelector("#mobile-font-family"), mobileFontSize = document.querySelector("#mobile-font-size"), mobileLineToggle = document.querySelector("#mobile-line-numbers-toggle"), mobilePreviewLineToggle = document.querySelector("#mobile-preview-line-numbers-toggle"), mobileCompactToggle = document.querySelector("#mobile-compact-toggle"), mobileLineLinksToggle = document.querySelector("#mobile-line-links-toggle");
|
||||||
const shareToken = new URLSearchParams(location.search).get("share");
|
const shareToken = new URLSearchParams(location.search).get("share");
|
||||||
const notePreferenceKey = name => `rustpad:${name}:${location.pathname}`;
|
const notePreferenceKey = name => `rustpad:${name}:${location.pathname}`;
|
||||||
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 = "";
|
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 = "";
|
||||||
let editorSettingsSaveTimer, editorSettingsSaveInFlight = false, pendingPersonalSettingsSave = false, pendingAuthorshipSettingsSave = false, connectionNoticeTimer = 0, connectionWasInterrupted = false;
|
let editorSettingsSaveTimer, editorSettingsSaveInFlight = false, pendingPersonalSettingsSave = false, pendingAuthorshipSettingsSave = false, connectionNoticeTimer = 0, connectionWasInterrupted = false;
|
||||||
|
const editHistory = {
|
||||||
|
entries: [],
|
||||||
|
index: -1,
|
||||||
|
lastKind: "",
|
||||||
|
lastRecordedAt: 0,
|
||||||
|
snapshot() {
|
||||||
|
return {
|
||||||
|
content: editor.value,
|
||||||
|
ownerMap: serializeAuthorship(authorship, editor.value.length),
|
||||||
|
selectionStart: editor.selectionStart,
|
||||||
|
selectionEnd: editor.selectionEnd,
|
||||||
|
selectionDirection: editor.selectionDirection,
|
||||||
|
scrollTop: editor.scrollTop,
|
||||||
|
scrollLeft: editor.scrollLeft,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
reset() {
|
||||||
|
this.entries = [this.snapshot()];
|
||||||
|
this.index = 0;
|
||||||
|
this.lastKind = "";
|
||||||
|
this.lastRecordedAt = 0;
|
||||||
|
},
|
||||||
|
syncCurrent() {
|
||||||
|
if (this.index < 0) {
|
||||||
|
this.reset();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.entries[this.index] = this.snapshot();
|
||||||
|
},
|
||||||
|
record(inputType = "") {
|
||||||
|
const snapshot = this.snapshot();
|
||||||
|
if (this.index < 0) {
|
||||||
|
this.entries = [snapshot];
|
||||||
|
this.index = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.entries[this.index]?.content === snapshot.content) {
|
||||||
|
this.entries[this.index] = snapshot;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.index < this.entries.length - 1) this.entries.splice(this.index + 1);
|
||||||
|
const kind = inputType === "insertText" || inputType === "insertCompositionText"
|
||||||
|
? "typing"
|
||||||
|
: inputType === "deleteContentBackward" || inputType === "deleteContentForward"
|
||||||
|
? "deleting"
|
||||||
|
: "action";
|
||||||
|
const now = Date.now();
|
||||||
|
const merge = kind !== "action" && kind === this.lastKind && now - this.lastRecordedAt < 900 && this.index > 0;
|
||||||
|
if (merge) this.entries[this.index] = snapshot;
|
||||||
|
else {
|
||||||
|
this.entries.push(snapshot);
|
||||||
|
this.index += 1;
|
||||||
|
if (this.entries.length > 100) {
|
||||||
|
this.entries.shift();
|
||||||
|
this.index -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.lastKind = kind;
|
||||||
|
this.lastRecordedAt = now;
|
||||||
|
},
|
||||||
|
move(offset) {
|
||||||
|
const nextIndex = this.index + offset;
|
||||||
|
if (nextIndex < 0 || nextIndex >= this.entries.length) return false;
|
||||||
|
this.index = nextIndex;
|
||||||
|
this.lastKind = "";
|
||||||
|
this.lastRecordedAt = 0;
|
||||||
|
restoreHistorySnapshot(this.entries[this.index]);
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
undo() { return this.move(-1); },
|
||||||
|
redo() { return this.move(1); },
|
||||||
|
};
|
||||||
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";
|
||||||
@@ -577,6 +649,29 @@ export function startNoteEditor(adapter) {
|
|||||||
preview.scrollTop = ratio * previewRange;
|
preview.scrollTop = ratio * previewRange;
|
||||||
}
|
}
|
||||||
function render() { if (uiState.mode === "markdown") { preview.classList.remove("preview--raw"); preview.innerHTML = renderMarkdown(editor.value); scheduleAliasFileRefresh(editor.value); document.querySelector("#preview-label").textContent = "Preview (mermaid / markdown)"; renderMermaid(); renderCodeHighlight(); } else { preview.classList.add("preview--raw"); 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 · editable"; } 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`; renderGutter(); requestAnimationFrame(syncPreviewScroll); }
|
function render() { if (uiState.mode === "markdown") { preview.classList.remove("preview--raw"); preview.innerHTML = renderMarkdown(editor.value); scheduleAliasFileRefresh(editor.value); document.querySelector("#preview-label").textContent = "Preview (mermaid / markdown)"; renderMermaid(); renderCodeHighlight(); } else { preview.classList.add("preview--raw"); 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 · editable"; } 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`; renderGutter(); requestAnimationFrame(syncPreviewScroll); }
|
||||||
|
function scheduleDocumentSave() {
|
||||||
|
clearTimeout(saveTimer);
|
||||||
|
document.querySelector("#save-state").textContent = "Saving…";
|
||||||
|
saveTimer = setTimeout(() => socket?.update(editor.value, serializeAuthorship(authorship, editor.value.length)), 250);
|
||||||
|
}
|
||||||
|
function restoreHistorySnapshot(snapshot) {
|
||||||
|
if (!snapshot) return;
|
||||||
|
const maxOffset = snapshot.content.length;
|
||||||
|
const selectionStart = Math.min(snapshot.selectionStart ?? maxOffset, maxOffset);
|
||||||
|
const selectionEnd = Math.min(snapshot.selectionEnd ?? selectionStart, maxOffset);
|
||||||
|
applyingHistory = true;
|
||||||
|
editor.value = snapshot.content;
|
||||||
|
authorship = parseAuthorship(snapshot.content, snapshot.ownerMap);
|
||||||
|
previousContent = snapshot.content;
|
||||||
|
editor.setSelectionRange(selectionStart, selectionEnd, snapshot.selectionDirection || "none");
|
||||||
|
render();
|
||||||
|
editor.scrollTop = snapshot.scrollTop || 0;
|
||||||
|
editor.scrollLeft = snapshot.scrollLeft || 0;
|
||||||
|
syncEditorLayers();
|
||||||
|
applyingHistory = false;
|
||||||
|
editor.focus({ preventScroll: true });
|
||||||
|
scheduleDocumentSave();
|
||||||
|
}
|
||||||
function activeView() {
|
function activeView() {
|
||||||
return singlePaneQuery.matches ? compactView : uiState.view;
|
return singlePaneQuery.matches ? compactView : uiState.view;
|
||||||
}
|
}
|
||||||
@@ -621,6 +716,7 @@ export function startNoteEditor(adapter) {
|
|||||||
if (content === editor.value) {
|
if (content === editor.value) {
|
||||||
if (ownerMap != null) authorship = adoptCurrentOwnerAliases(parseAuthorship(content, ownerMap), content.length);
|
if (ownerMap != null) authorship = adoptCurrentOwnerAliases(parseAuthorship(content, ownerMap), content.length);
|
||||||
previousContent = content;
|
previousContent = content;
|
||||||
|
editHistory.syncCurrent();
|
||||||
renderGutter();
|
renderGutter();
|
||||||
requestAnimationFrame(revealLinkedLine);
|
requestAnimationFrame(revealLinkedLine);
|
||||||
return;
|
return;
|
||||||
@@ -636,6 +732,7 @@ export function startNoteEditor(adapter) {
|
|||||||
editor.scrollTop = scrollTop;
|
editor.scrollTop = scrollTop;
|
||||||
editor.scrollLeft = scrollLeft;
|
editor.scrollLeft = scrollLeft;
|
||||||
applyingRemote = false;
|
applyingRemote = false;
|
||||||
|
editHistory.reset();
|
||||||
render();
|
render();
|
||||||
editor.scrollTop = scrollTop;
|
editor.scrollTop = scrollTop;
|
||||||
editor.scrollLeft = scrollLeft;
|
editor.scrollLeft = scrollLeft;
|
||||||
@@ -1062,6 +1159,29 @@ export function startNoteEditor(adapter) {
|
|||||||
button.closest("details")?.removeAttribute("open");
|
button.closest("details")?.removeAttribute("open");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
function handleHistoryShortcut(event) {
|
||||||
|
const primary = event.ctrlKey || event.metaKey;
|
||||||
|
if (!primary || event.altKey) return;
|
||||||
|
const key = event.key.toLowerCase();
|
||||||
|
const undo = key === "z" && !event.shiftKey;
|
||||||
|
const redo = (key === "z" && event.shiftKey) || (key === "y" && !event.shiftKey);
|
||||||
|
if (!undo && !redo) return;
|
||||||
|
const target = event.target instanceof Element ? event.target : null;
|
||||||
|
if (target?.closest('.preview-editable[contenteditable="true"]')) return;
|
||||||
|
if (target && target !== editor && target.matches("input, textarea, select, [contenteditable='true']")) return;
|
||||||
|
if (document.querySelector("dialog[open]") && target !== editor) return;
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
if (redo) editHistory.redo();
|
||||||
|
else editHistory.undo();
|
||||||
|
}
|
||||||
|
document.addEventListener("keydown", handleHistoryShortcut, true);
|
||||||
|
editor.addEventListener("beforeinput", event => {
|
||||||
|
if (event.inputType !== "historyUndo" && event.inputType !== "historyRedo") return;
|
||||||
|
event.preventDefault();
|
||||||
|
if (event.inputType === "historyRedo") editHistory.redo();
|
||||||
|
else editHistory.undo();
|
||||||
|
});
|
||||||
bindFormatShortcuts(editor);
|
bindFormatShortcuts(editor);
|
||||||
bindEmojiPicker({ editor, details: document.querySelector("#emoji-picker"), search: document.querySelector("#emoji-search"), categories: document.querySelector("#emoji-categories"), grid: document.querySelector("#emoji-grid"), empty: document.querySelector("#emoji-empty") });
|
bindEmojiPicker({ editor, details: document.querySelector("#emoji-picker"), search: document.querySelector("#emoji-search"), categories: document.querySelector("#emoji-categories"), grid: document.querySelector("#emoji-grid"), empty: document.querySelector("#emoji-empty") });
|
||||||
document.querySelector("#shortcuts-button").addEventListener("click", () => document.querySelector("#shortcuts-dialog").showModal());
|
document.querySelector("#shortcuts-button").addEventListener("click", () => document.querySelector("#shortcuts-dialog").showModal());
|
||||||
@@ -1151,12 +1271,21 @@ export function startNoteEditor(adapter) {
|
|||||||
deletePreviewSelection();
|
deletePreviewSelection();
|
||||||
});
|
});
|
||||||
const publishPageButton = document.querySelector("#publish-page");
|
const publishPageButton = document.querySelector("#publish-page");
|
||||||
|
const pageSettings = document.querySelector(".page-settings");
|
||||||
function updatePageControls() {
|
function updatePageControls() {
|
||||||
const enabled = publicPageEnabled.checked;
|
const enabled = publicPageEnabled.checked;
|
||||||
publishPageButton.disabled = !enabled;
|
publishPageButton.disabled = !enabled;
|
||||||
publicTaskUpdates.disabled = !enabled;
|
publicTaskUpdates.disabled = !enabled;
|
||||||
unprotectPublicPage.disabled = !enabled;
|
unprotectPublicPage.disabled = !enabled;
|
||||||
|
pageSettings?.classList.toggle("is-enabled", enabled);
|
||||||
|
pageSettings?.querySelector("summary")?.setAttribute("title", enabled ? "Published page enabled" : "Published page disabled");
|
||||||
}
|
}
|
||||||
|
document.addEventListener("pointerdown", event => {
|
||||||
|
if (pageSettings?.open && !event.target.closest(".page-settings")) pageSettings.open = false;
|
||||||
|
}, { passive: true });
|
||||||
|
document.addEventListener("keydown", event => {
|
||||||
|
if (event.key === "Escape" && pageSettings?.open) pageSettings.open = false;
|
||||||
|
});
|
||||||
const savePublicOptions = async () => adapter.publish(accessToken, publicTaskUpdates.checked, unprotectPublicPage.checked, publicPageEnabled.checked);
|
const savePublicOptions = async () => adapter.publish(accessToken, publicTaskUpdates.checked, unprotectPublicPage.checked, publicPageEnabled.checked);
|
||||||
publicPageEnabled.addEventListener("change", async () => {
|
publicPageEnabled.addEventListener("change", async () => {
|
||||||
const previous = !publicPageEnabled.checked;
|
const previous = !publicPageEnabled.checked;
|
||||||
@@ -1211,17 +1340,16 @@ export function startNoteEditor(adapter) {
|
|||||||
syncEditorLayers();
|
syncEditorLayers();
|
||||||
syncPreviewScroll();
|
syncPreviewScroll();
|
||||||
});
|
});
|
||||||
editor.addEventListener("input", () => {
|
editor.addEventListener("input", event => {
|
||||||
const nextContent = editor.value;
|
const nextContent = editor.value;
|
||||||
authorship = adoptCurrentOwnerAliases(authorship, previousContent.length);
|
authorship = adoptCurrentOwnerAliases(authorship, previousContent.length);
|
||||||
authorship = replaceAuthorshipOwner(authorship, owner => ownerName(owner) === nickname, currentOwner(), previousContent.length);
|
authorship = replaceAuthorshipOwner(authorship, owner => ownerName(owner) === nickname, currentOwner(), previousContent.length);
|
||||||
authorship = applyAuthorshipEdit(authorship, previousContent, nextContent, currentOwner());
|
authorship = applyAuthorshipEdit(authorship, previousContent, nextContent, currentOwner());
|
||||||
previousContent = nextContent;
|
previousContent = nextContent;
|
||||||
render();
|
render();
|
||||||
if (applyingRemote) return;
|
if (applyingRemote || applyingHistory) return;
|
||||||
clearTimeout(saveTimer);
|
editHistory.record(event.inputType || "");
|
||||||
document.querySelector("#save-state").textContent = "Saving…";
|
scheduleDocumentSave();
|
||||||
saveTimer = setTimeout(() => socket?.update(editor.value, serializeAuthorship(authorship, editor.value.length)), 250);
|
|
||||||
});
|
});
|
||||||
passwordDialog.addEventListener("cancel", event => {
|
passwordDialog.addEventListener("cancel", event => {
|
||||||
if (info?.protected && !resourceUnlocked) {
|
if (info?.protected && !resourceUnlocked) {
|
||||||
|
|||||||
Reference in New Issue
Block a user