codemirror
This commit is contained in:
67
apps/server/internal/httpserver/static/codemirror.bundle.js
Normal file
67
apps/server/internal/httpserver/static/codemirror.bundle.js
Normal file
File diff suppressed because one or more lines are too long
@@ -11,15 +11,13 @@
|
|||||||
var conflictApply = document.querySelector("[data-conflict-apply]");
|
var conflictApply = document.querySelector("[data-conflict-apply]");
|
||||||
var conflictDismiss = document.querySelector("[data-conflict-dismiss]");
|
var conflictDismiss = document.querySelector("[data-conflict-dismiss]");
|
||||||
var conflictResolution = document.querySelector("[data-conflict-resolution]");
|
var conflictResolution = document.querySelector("[data-conflict-resolution]");
|
||||||
var conflictResolutionMount = document.querySelector("[data-conflict-monaco-mount]");
|
var conflictResolutionMount = document.querySelector("[data-conflict-codemirror-mount]");
|
||||||
var conflictUseServer = document.querySelector("[data-conflict-use='server']");
|
var conflictUseServer = document.querySelector("[data-conflict-use='server']");
|
||||||
var conflictUseLocal = document.querySelector("[data-conflict-use='local']");
|
var conflictUseLocal = document.querySelector("[data-conflict-use='local']");
|
||||||
var conflictUseBoth = document.querySelector("[data-conflict-use='both']");
|
var conflictUseBoth = document.querySelector("[data-conflict-use='both']");
|
||||||
var monacoContainer = document.querySelector("[data-monaco-mount]");
|
var codeMirrorContainer = document.querySelector("[data-codemirror-mount]");
|
||||||
var monacoLoaded = false;
|
var codeMirrorReadyPromise = null;
|
||||||
var monacoReadyPromise = null;
|
var codeMirrorModules = null;
|
||||||
var monacoDarkTheme = "cairnquire-dark";
|
|
||||||
var monacoLightTheme = "cairnquire-light";
|
|
||||||
|
|
||||||
if (!form || !textarea || statusNodes.length === 0) {
|
if (!form || !textarea || statusNodes.length === 0) {
|
||||||
return;
|
return;
|
||||||
@@ -36,6 +34,9 @@
|
|||||||
var conflictDiffView = null;
|
var conflictDiffView = null;
|
||||||
var resolutionEditor = null;
|
var resolutionEditor = null;
|
||||||
var suppressResolutionEvents = false;
|
var suppressResolutionEvents = false;
|
||||||
|
var doneLink = document.querySelector("[data-done-link]");
|
||||||
|
var isSaving = false;
|
||||||
|
var suppressBeforeUnload = false;
|
||||||
|
|
||||||
function setStatus(nextStatus) {
|
function setStatus(nextStatus) {
|
||||||
statusNodes.forEach(function (node) {
|
statusNodes.forEach(function (node) {
|
||||||
@@ -223,7 +224,7 @@
|
|||||||
var value = getEditorValue();
|
var value = getEditorValue();
|
||||||
if (value === lastSavedValue) {
|
if (value === lastSavedValue) {
|
||||||
setStatus("Saved");
|
setStatus("Saved");
|
||||||
return;
|
return "unchanged";
|
||||||
}
|
}
|
||||||
|
|
||||||
isSaving = true;
|
isSaving = true;
|
||||||
@@ -233,7 +234,7 @@
|
|||||||
if (!window.MDHubSync) {
|
if (!window.MDHubSync) {
|
||||||
isSaving = false;
|
isSaving = false;
|
||||||
setStatus("Queued");
|
setStatus("Queued");
|
||||||
return;
|
return "queued";
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -249,16 +250,17 @@
|
|||||||
}
|
}
|
||||||
setStatus("Saved");
|
setStatus("Saved");
|
||||||
isSaving = false;
|
isSaving = false;
|
||||||
return;
|
return "saved";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result.status === "conflict") {
|
if (result.status === "conflict") {
|
||||||
showConflict(result.conflict, value);
|
showConflict(result.conflict, value);
|
||||||
isSaving = false;
|
isSaving = false;
|
||||||
return;
|
return "conflict";
|
||||||
}
|
}
|
||||||
|
|
||||||
setStatus("Queued");
|
setStatus("Queued");
|
||||||
|
return "queued";
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setStatus("Queued");
|
setStatus("Queued");
|
||||||
throw err;
|
throw err;
|
||||||
@@ -328,138 +330,236 @@
|
|||||||
}, 2000);
|
}, 2000);
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadMonaco() {
|
function doneHref() {
|
||||||
if (monacoReadyPromise) {
|
return doneLink ? doneLink.getAttribute("href") : "/docs/" + path.replace(/\.md$/, "");
|
||||||
return monacoReadyPromise;
|
}
|
||||||
|
|
||||||
|
function clearScheduledSave() {
|
||||||
|
if (saveTimer) {
|
||||||
|
clearTimeout(saveTimer);
|
||||||
|
saveTimer = null;
|
||||||
}
|
}
|
||||||
if (monacoLoaded && window.monaco) {
|
|
||||||
monacoReadyPromise = Promise.resolve();
|
|
||||||
return monacoReadyPromise;
|
|
||||||
}
|
}
|
||||||
monacoLoaded = true;
|
|
||||||
|
|
||||||
var isDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
function leaveEditorNow() {
|
||||||
|
clearScheduledSave();
|
||||||
|
suppressBeforeUnload = true;
|
||||||
|
window.location.href = doneHref();
|
||||||
|
}
|
||||||
|
|
||||||
monacoReadyPromise = new Promise(function (resolve) {
|
function saveThenLeaveEditor() {
|
||||||
var script = document.createElement("script");
|
var href = doneHref();
|
||||||
script.src = "https://cdn.jsdelivr.net/npm/monaco-editor@0.52.2/min/vs/loader.js";
|
|
||||||
script.onload = function () {
|
if (currentConflict) {
|
||||||
require.config({
|
setStatus("Conflict");
|
||||||
paths: { vs: "https://cdn.jsdelivr.net/npm/monaco-editor@0.52.2/min/vs" },
|
if (conflictNotice) {
|
||||||
|
conflictNotice.scrollIntoView({ behavior: "smooth", block: "center" });
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearScheduledSave();
|
||||||
|
|
||||||
|
if (isSaving) {
|
||||||
|
waitForSaveThenNavigate(href);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setStatus("Saving");
|
||||||
|
saveNow()
|
||||||
|
.then(function (result) {
|
||||||
|
if (result === "saved" || result === "unchanged") {
|
||||||
|
suppressBeforeUnload = true;
|
||||||
|
window.location.href = href;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result === "conflict") {
|
||||||
|
setStatus("Conflict");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setStatus("Queued");
|
||||||
|
if (window.confirm("Could not save. Leave without saving?")) {
|
||||||
|
suppressBeforeUnload = true;
|
||||||
|
window.location.href = href;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(function () {
|
||||||
|
setStatus("Queued");
|
||||||
|
if (window.confirm("Could not save. Leave without saving?")) {
|
||||||
|
suppressBeforeUnload = true;
|
||||||
|
window.location.href = href;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
require(["vs/editor/editor.main"], function () {
|
}
|
||||||
monaco.editor.defineTheme(monacoDarkTheme, {
|
|
||||||
base: "vs-dark",
|
function loadCodeMirror() {
|
||||||
inherit: true,
|
if (codeMirrorReadyPromise) {
|
||||||
rules: [
|
return codeMirrorReadyPromise;
|
||||||
{ token: "comment", foreground: "8B9DAF", fontStyle: "italic" },
|
}
|
||||||
{ token: "keyword", foreground: "E8943A" },
|
|
||||||
{ token: "string", foreground: "C9A96E" },
|
codeMirrorReadyPromise = Promise.all([
|
||||||
{ token: "number", foreground: "C9A96E" },
|
import("/static/codemirror.bundle.js"),
|
||||||
{ token: "type", foreground: "E8943A" },
|
]).then(function (modules) {
|
||||||
{ token: "tag", foreground: "E8943A" },
|
var bundle = modules[0];
|
||||||
{ token: "attribute.name", foreground: "D4A05A" },
|
codeMirrorModules = {
|
||||||
{ token: "attribute.value", foreground: "C9A96E" },
|
cm: bundle,
|
||||||
{ token: "delimiter", foreground: "B0B8C4" },
|
markdown: bundle,
|
||||||
{ token: "variable", foreground: "D4D8DE" },
|
vim: bundle,
|
||||||
],
|
};
|
||||||
colors: {
|
registerVimCommands();
|
||||||
"editor.background": "#1E1E1E",
|
return codeMirrorModules;
|
||||||
"editor.foreground": "#E8ECF0",
|
|
||||||
"editor.lineHighlightBackground": "#282828",
|
|
||||||
"editor.selectionBackground": "#3A3A3A",
|
|
||||||
"editorLineNumber.foreground": "#555555",
|
|
||||||
"editorLineNumber.activeForeground": "#E8943A",
|
|
||||||
"editor.inactiveSelectionBackground": "#333333",
|
|
||||||
"editorCursor.foreground": "#E8943A",
|
|
||||||
"editorIndentGuide.background": "#2A2A2A",
|
|
||||||
"editorIndentGuide.activeBackground": "#3A3A3A",
|
|
||||||
"editorWhitespace.foreground": "#2A2A2A",
|
|
||||||
"editorGutter.background": "#1E1E1E",
|
|
||||||
"editorOverviewRuler.border": "#1E1E1E",
|
|
||||||
"scrollbarSlider.background": "#3A3A3A88",
|
|
||||||
"scrollbarSlider.hoverBackground": "#4A4A4A88",
|
|
||||||
"scrollbarSlider.activeBackground": "#5A5A5A88",
|
|
||||||
"minimap.background": "#1E1E1E",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
monaco.editor.defineTheme(monacoLightTheme, {
|
return codeMirrorReadyPromise;
|
||||||
base: "vs",
|
}
|
||||||
inherit: true,
|
|
||||||
rules: [
|
|
||||||
{ token: "comment", foreground: "6A7B8C", fontStyle: "italic" },
|
|
||||||
{ token: "keyword", foreground: "C67A2A" },
|
|
||||||
{ token: "string", foreground: "8A6D3B" },
|
|
||||||
{ token: "number", foreground: "8A6D3B" },
|
|
||||||
{ token: "type", foreground: "C67A2A" },
|
|
||||||
{ token: "tag", foreground: "C67A2A" },
|
|
||||||
{ token: "attribute.name", foreground: "A87D3A" },
|
|
||||||
{ token: "attribute.value", foreground: "8A6D3B" },
|
|
||||||
],
|
|
||||||
colors: {
|
|
||||||
"editor.background": "#FCFAF5",
|
|
||||||
"editor.foreground": "#1C2430",
|
|
||||||
"editor.lineHighlightBackground": "#F0EDE5",
|
|
||||||
"editor.selectionBackground": "#E8943A33",
|
|
||||||
"editorLineNumber.foreground": "#AAAAAA",
|
|
||||||
"editorLineNumber.activeForeground": "#C67A2A",
|
|
||||||
"editorCursor.foreground": "#C67A2A",
|
|
||||||
"editorIndentGuide.background": "#E8E5DD",
|
|
||||||
"editorGutter.background": "#FCFAF5",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (monacoContainer) {
|
function registerVimCommands() {
|
||||||
editor = monaco.editor.create(monacoContainer, {
|
var Vim = codeMirrorModules && codeMirrorModules.vim && codeMirrorModules.vim.Vim;
|
||||||
value: textarea.value,
|
if (!Vim) {
|
||||||
language: "markdown",
|
return;
|
||||||
theme: isDark ? monacoDarkTheme : monacoLightTheme,
|
}
|
||||||
fontFamily: "'Iosevka', 'JetBrains Mono', ui-monospace, SFMono-Regular, monospace",
|
|
||||||
fontSize: 14,
|
|
||||||
lineHeight: 22,
|
|
||||||
minimap: { enabled: false },
|
|
||||||
wordWrap: "on",
|
|
||||||
scrollBeyondLastLine: false,
|
|
||||||
padding: { top: 12 },
|
|
||||||
renderLineHighlight: "line",
|
|
||||||
smoothScrolling: true,
|
|
||||||
cursorBlinking: "smooth",
|
|
||||||
cursorSmoothCaretAnimation: "on",
|
|
||||||
bracketPairColorization: { enabled: true },
|
|
||||||
automaticLayout: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
textarea.hidden = true;
|
Vim.defineEx("write", "w", function () {
|
||||||
monacoContainer.classList.add("monaco-mounted");
|
|
||||||
|
|
||||||
editor.onDidChangeModelContent(function () {
|
|
||||||
textarea.value = editor.getValue();
|
|
||||||
scheduleSave();
|
|
||||||
});
|
|
||||||
|
|
||||||
if (window.MDHubSync) {
|
|
||||||
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, function () {
|
|
||||||
saveNow().catch(function () {
|
saveNow().catch(function () {
|
||||||
setStatus("Queued");
|
setStatus("Queued");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
Vim.defineEx("quit", "q", function () {
|
||||||
|
leaveEditorNow();
|
||||||
|
});
|
||||||
|
Vim.defineEx("wq", "wq", function () {
|
||||||
|
saveThenLeaveEditor();
|
||||||
|
});
|
||||||
|
Vim.defineEx("xit", "x", function () {
|
||||||
|
saveThenLeaveEditor();
|
||||||
|
});
|
||||||
|
Vim.map("<Esc>", ":q<CR>");
|
||||||
}
|
}
|
||||||
|
|
||||||
var darkMQ = window.matchMedia("(prefers-color-scheme: dark)");
|
function codeMirrorTheme() {
|
||||||
darkMQ.addEventListener("change", function (e) {
|
var EditorView = codeMirrorModules.cm.EditorView;
|
||||||
monaco.editor.setTheme(e.matches ? monacoDarkTheme : monacoLightTheme);
|
var isDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
||||||
});
|
|
||||||
|
|
||||||
editor.focus();
|
return EditorView.theme({
|
||||||
|
"&": {
|
||||||
|
height: "100%",
|
||||||
|
minHeight: "60vh",
|
||||||
|
backgroundColor: "var(--panel)",
|
||||||
|
color: "var(--text)",
|
||||||
|
fontSize: "14px",
|
||||||
|
},
|
||||||
|
"&.cm-focused": {
|
||||||
|
outline: "none",
|
||||||
|
},
|
||||||
|
".cm-scroller": {
|
||||||
|
fontFamily: "'Iosevka', 'JetBrains Mono', ui-monospace, SFMono-Regular, monospace",
|
||||||
|
lineHeight: "22px",
|
||||||
|
},
|
||||||
|
".cm-content": {
|
||||||
|
padding: "12px 0",
|
||||||
|
caretColor: "var(--accent)",
|
||||||
|
},
|
||||||
|
".cm-line": {
|
||||||
|
padding: "0 14px",
|
||||||
|
},
|
||||||
|
".cm-gutters": {
|
||||||
|
backgroundColor: "var(--panel-strong)",
|
||||||
|
borderRight: "1px solid var(--border)",
|
||||||
|
color: "var(--muted)",
|
||||||
|
},
|
||||||
|
".cm-activeLine, .cm-activeLineGutter": {
|
||||||
|
backgroundColor: isDark ? "oklch(0.24 0.012 55)" : "#f0ede5",
|
||||||
|
},
|
||||||
|
".cm-selectionBackground, &.cm-focused .cm-selectionBackground": {
|
||||||
|
backgroundColor: "color-mix(in srgb, var(--accent) 22%, transparent)",
|
||||||
|
},
|
||||||
|
".cm-cursor": {
|
||||||
|
borderLeftColor: "var(--accent)",
|
||||||
|
},
|
||||||
|
".cm-panel": {
|
||||||
|
backgroundColor: "var(--panel-strong)",
|
||||||
|
borderTop: "1px solid var(--border)",
|
||||||
|
color: "var(--text)",
|
||||||
|
fontFamily: "'Iosevka', 'JetBrains Mono', ui-monospace, SFMono-Regular, monospace",
|
||||||
|
},
|
||||||
|
".cm-vim-panel input": {
|
||||||
|
color: "var(--text)",
|
||||||
|
},
|
||||||
|
}, { dark: isDark });
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve();
|
function codeMirrorHighlighting() {
|
||||||
|
var cm = codeMirrorModules.cm;
|
||||||
|
var tags = codeMirrorModules.cm.tags;
|
||||||
|
var urlColor = window.matchMedia("(prefers-color-scheme: dark)").matches
|
||||||
|
? "oklch(0.78 0.14 190)"
|
||||||
|
: "#047c78";
|
||||||
|
|
||||||
|
return cm.syntaxHighlighting(cm.HighlightStyle.define([
|
||||||
|
{
|
||||||
|
tag: [tags.url, tags.link],
|
||||||
|
color: urlColor,
|
||||||
|
textDecoration: "underline",
|
||||||
|
textUnderlineOffset: "2px",
|
||||||
|
},
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
function createCodeMirrorEditor(mount, initialValue, options) {
|
||||||
|
var cm = codeMirrorModules.cm;
|
||||||
|
var markdownModule = codeMirrorModules.markdown;
|
||||||
|
var vimModule = codeMirrorModules.vim;
|
||||||
|
var onChange = options && options.onChange ? options.onChange : function () {};
|
||||||
|
var extensions = [
|
||||||
|
vimModule.vim({ status: true }),
|
||||||
|
cm.basicSetup,
|
||||||
|
markdownModule.markdown({
|
||||||
|
base: markdownModule.markdownLanguage,
|
||||||
|
}),
|
||||||
|
cm.EditorView.lineWrapping,
|
||||||
|
codeMirrorTheme(),
|
||||||
|
codeMirrorHighlighting(),
|
||||||
|
cm.EditorView.updateListener.of(function (update) {
|
||||||
|
if (update.docChanged) {
|
||||||
|
onChange(update.state.doc.toString());
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
mount.replaceChildren();
|
||||||
|
mount.classList.add("codemirror-mounted");
|
||||||
|
|
||||||
|
var view = new cm.EditorView({
|
||||||
|
doc: initialValue,
|
||||||
|
extensions: extensions,
|
||||||
|
parent: mount,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
view: view,
|
||||||
|
getValue: function () {
|
||||||
|
return view.state.doc.toString();
|
||||||
|
},
|
||||||
|
setValue: function (value) {
|
||||||
|
if (view.state.doc.toString() === value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
view.dispatch({
|
||||||
|
changes: { from: 0, to: view.state.doc.length, insert: value },
|
||||||
|
});
|
||||||
|
},
|
||||||
|
focus: function () {
|
||||||
|
view.focus();
|
||||||
|
},
|
||||||
|
hasTextFocus: function () {
|
||||||
|
return view.hasFocus;
|
||||||
|
},
|
||||||
|
destroy: function () {
|
||||||
|
view.destroy();
|
||||||
|
},
|
||||||
};
|
};
|
||||||
document.head.appendChild(script);
|
|
||||||
});
|
|
||||||
return monacoReadyPromise;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function ensureResolutionEditor() {
|
function ensureResolutionEditor() {
|
||||||
@@ -467,54 +567,42 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
loadMonaco().then(function () {
|
loadCodeMirror().then(function () {
|
||||||
if (!window.monaco || resolutionEditor) {
|
if (resolutionEditor) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var isDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
resolutionEditor = createCodeMirrorEditor(conflictResolutionMount, conflictResolution.value || resolvedConflictContent, {
|
||||||
resolutionEditor = monaco.editor.create(conflictResolutionMount, {
|
onChange: function (value) {
|
||||||
value: conflictResolution.value || resolvedConflictContent,
|
|
||||||
language: "markdown",
|
|
||||||
theme: isDark ? monacoDarkTheme : monacoLightTheme,
|
|
||||||
fontFamily: "'Iosevka', 'JetBrains Mono', ui-monospace, SFMono-Regular, monospace",
|
|
||||||
fontSize: 14,
|
|
||||||
lineHeight: 22,
|
|
||||||
minimap: { enabled: false },
|
|
||||||
wordWrap: "on",
|
|
||||||
scrollBeyondLastLine: false,
|
|
||||||
padding: { top: 12 },
|
|
||||||
renderLineHighlight: "line",
|
|
||||||
smoothScrolling: true,
|
|
||||||
cursorBlinking: "smooth",
|
|
||||||
cursorSmoothCaretAnimation: "on",
|
|
||||||
bracketPairColorization: { enabled: true },
|
|
||||||
automaticLayout: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
conflictResolution.hidden = true;
|
|
||||||
conflictResolutionMount.classList.add("monaco-mounted");
|
|
||||||
|
|
||||||
resolutionEditor.onDidChangeModelContent(function () {
|
|
||||||
if (suppressResolutionEvents) {
|
if (suppressResolutionEvents) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
resolvedConflictContent = resolutionEditor.getValue();
|
resolvedConflictContent = value;
|
||||||
conflictResolution.value = resolvedConflictContent;
|
conflictResolution.value = value;
|
||||||
if (conflictApply) {
|
if (conflictApply) {
|
||||||
conflictApply.disabled = false;
|
conflictApply.disabled = false;
|
||||||
}
|
}
|
||||||
setConflictMessage("Manual resolution updated. Apply it to save the resolved document.");
|
setConflictMessage("Manual resolution updated. Apply it to save the resolved document.");
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
conflictResolution.hidden = true;
|
||||||
resolutionEditor.focus();
|
resolutionEditor.focus();
|
||||||
}).catch(function () {
|
}).catch(function () {
|
||||||
conflictResolution.hidden = false;
|
conflictResolution.hidden = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (monacoContainer) {
|
if (codeMirrorContainer) {
|
||||||
loadMonaco().catch(function () {
|
loadCodeMirror().then(function () {
|
||||||
|
editor = createCodeMirrorEditor(codeMirrorContainer, textarea.value, {
|
||||||
|
onChange: function (value) {
|
||||||
|
textarea.value = value;
|
||||||
|
scheduleSave();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
textarea.hidden = true;
|
||||||
|
editor.focus();
|
||||||
|
}).catch(function () {
|
||||||
textarea.addEventListener("input", scheduleSave);
|
textarea.addEventListener("input", scheduleSave);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -582,59 +670,10 @@
|
|||||||
|
|
||||||
restorePendingConflict();
|
restorePendingConflict();
|
||||||
|
|
||||||
var doneLink = document.querySelector("[data-done-link]");
|
|
||||||
var isSaving = false;
|
|
||||||
|
|
||||||
if (doneLink) {
|
if (doneLink) {
|
||||||
doneLink.addEventListener("click", function (event) {
|
doneLink.addEventListener("click", function (event) {
|
||||||
var currentValue = getEditorValue();
|
|
||||||
var href = doneLink.getAttribute("href");
|
|
||||||
|
|
||||||
// If there's an unresolved conflict, warn the user
|
|
||||||
if (currentConflict) {
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
setStatus("Conflict");
|
saveThenLeaveEditor();
|
||||||
if (conflictNotice) {
|
|
||||||
conflictNotice.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If nothing changed, allow normal navigation
|
|
||||||
if (currentValue === lastSavedValue) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unsaved changes — cancel any pending timer and save immediately
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
if (saveTimer) {
|
|
||||||
clearTimeout(saveTimer);
|
|
||||||
saveTimer = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isSaving) {
|
|
||||||
// Already saving, just wait and then navigate
|
|
||||||
waitForSaveThenNavigate(href);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
isSaving = true;
|
|
||||||
setStatus("Saving");
|
|
||||||
|
|
||||||
saveNow()
|
|
||||||
.then(function () {
|
|
||||||
isSaving = false;
|
|
||||||
window.location.href = href;
|
|
||||||
})
|
|
||||||
.catch(function () {
|
|
||||||
isSaving = false;
|
|
||||||
setStatus("Queued");
|
|
||||||
// If save fails, ask user if they want to leave anyway
|
|
||||||
if (window.confirm("Could not save. Leave without saving?")) {
|
|
||||||
window.location.href = href;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -642,6 +681,14 @@
|
|||||||
var checkInterval = setInterval(function () {
|
var checkInterval = setInterval(function () {
|
||||||
if (!isSaving) {
|
if (!isSaving) {
|
||||||
clearInterval(checkInterval);
|
clearInterval(checkInterval);
|
||||||
|
if (currentConflict) {
|
||||||
|
setStatus("Conflict");
|
||||||
|
if (conflictNotice) {
|
||||||
|
conflictNotice.scrollIntoView({ behavior: "smooth", block: "center" });
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
suppressBeforeUnload = true;
|
||||||
window.location.href = href;
|
window.location.href = href;
|
||||||
}
|
}
|
||||||
}, 100);
|
}, 100);
|
||||||
@@ -653,15 +700,42 @@
|
|||||||
|
|
||||||
// Warn on browser unload if there are unsaved changes
|
// Warn on browser unload if there are unsaved changes
|
||||||
window.addEventListener("beforeunload", function (e) {
|
window.addEventListener("beforeunload", function (e) {
|
||||||
|
if (suppressBeforeUnload) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (getEditorValue() !== lastSavedValue || currentConflict) {
|
if (getEditorValue() !== lastSavedValue || currentConflict) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.returnValue = "";
|
e.returnValue = "";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function editorSurfaceHasFocus(target) {
|
||||||
|
if (!target) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (target === textarea || target.closest("[data-codemirror-mount], [data-conflict-codemirror-mount], [data-document-editor], .cm-editor")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return Boolean(editor && editor.hasTextFocus && editor.hasTextFocus());
|
||||||
|
}
|
||||||
|
|
||||||
document.addEventListener("keydown", function (e) {
|
document.addEventListener("keydown", function (e) {
|
||||||
if ((e.metaKey || e.ctrlKey) && e.key === "s") {
|
if ((e.metaKey || e.ctrlKey) && e.key === "s") {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
saveNow().catch(function () {
|
||||||
|
setStatus("Queued");
|
||||||
});
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.metaKey && e.key === "Enter") {
|
||||||
|
e.preventDefault();
|
||||||
|
saveThenLeaveEditor();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!editorSurfaceHasFocus(e.target)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}, true);
|
||||||
})();
|
})();
|
||||||
|
|||||||
471
apps/server/internal/httpserver/static/keyboard-nav.js
Normal file
471
apps/server/internal/httpserver/static/keyboard-nav.js
Normal file
@@ -0,0 +1,471 @@
|
|||||||
|
(function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
if (document.querySelector("[data-editor-form]")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var browser = document.querySelector(".miller-browser");
|
||||||
|
if (!browser) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var documentShell = document.querySelector(".document-shell[data-document-path]");
|
||||||
|
var markdownBody = document.querySelector(".markdown-body");
|
||||||
|
var mode = "browser";
|
||||||
|
var selectedColumnIndex = 0;
|
||||||
|
var selectedLink = null;
|
||||||
|
var selectedParagraphIndex = -1;
|
||||||
|
|
||||||
|
function getColumns() {
|
||||||
|
return Array.prototype.slice.call(browser.querySelectorAll(".miller-column"));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getColumnLinks(columnIndex) {
|
||||||
|
var columns = getColumns();
|
||||||
|
if (!columns[columnIndex]) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return Array.prototype.slice.call(columns[columnIndex].querySelectorAll("a[href]"));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getParagraphTargets() {
|
||||||
|
if (!markdownBody) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return Array.prototype.slice.call(
|
||||||
|
markdownBody.querySelectorAll(".commentable, p, h1, h2, h3, h4, h5, h6, li, blockquote, pre")
|
||||||
|
).filter(function (el) {
|
||||||
|
return (el.textContent || "").trim().length > 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function clamp(value, min, max) {
|
||||||
|
return Math.max(min, Math.min(max, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
function isEditableTarget(target) {
|
||||||
|
if (!target) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return Boolean(target.closest(
|
||||||
|
"input, textarea, select, [contenteditable='true'], [contenteditable=''], [role='textbox'], .monaco-editor"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
function isKeyboardNavigationTarget(target) {
|
||||||
|
if (!target) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return Boolean(target.closest(".miller-browser a[href], .markdown-body .is-keyboard-selected"));
|
||||||
|
}
|
||||||
|
|
||||||
|
function shouldIgnoreTarget(target) {
|
||||||
|
if (isEditableTarget(target)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (isKeyboardNavigationTarget(target)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return Boolean(target && target.closest("a[href], button, summary, details, [role='button'], [role='menuitem']"));
|
||||||
|
}
|
||||||
|
|
||||||
|
function showKeyboardUI() {
|
||||||
|
document.body.classList.add("keyboard-navigation-active");
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearBrowserSelection() {
|
||||||
|
browser.querySelectorAll(".is-keyboard-selected").forEach(function (node) {
|
||||||
|
node.classList.remove("is-keyboard-selected");
|
||||||
|
node.removeAttribute("aria-selected");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearParagraphSelection() {
|
||||||
|
getParagraphTargets().forEach(function (node) {
|
||||||
|
node.classList.remove("is-keyboard-selected");
|
||||||
|
node.removeAttribute("tabindex");
|
||||||
|
node.removeAttribute("aria-selected");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function scrollBrowserColumnIntoView(column) {
|
||||||
|
if (!column) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var leftEdge = column.offsetLeft;
|
||||||
|
var rightEdge = leftEdge + column.offsetWidth;
|
||||||
|
var targetLeft = browser.scrollLeft;
|
||||||
|
|
||||||
|
if (leftEdge < browser.scrollLeft) {
|
||||||
|
targetLeft = leftEdge - 8;
|
||||||
|
} else if (rightEdge > browser.scrollLeft + browser.clientWidth) {
|
||||||
|
targetLeft = rightEdge - browser.clientWidth + 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetLeft !== browser.scrollLeft) {
|
||||||
|
browser.scrollTo({ left: Math.max(0, targetLeft), behavior: "smooth" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function expandColumnForKeyboard(column) {
|
||||||
|
getColumns().forEach(function (candidate) {
|
||||||
|
if (candidate !== column) {
|
||||||
|
candidate.classList.remove("is-preview-expanded");
|
||||||
|
candidate.style.removeProperty("--expanded-column-width");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!column || !column.matches(".miller-column:not(:first-child):not(:last-child)")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var expandedWidth = Math.min(Math.max(column.scrollWidth + 12, 192), 384);
|
||||||
|
column.style.setProperty("--expanded-column-width", expandedWidth + "px");
|
||||||
|
column.classList.add("is-preview-expanded");
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateBrowserSelection(focusSelected) {
|
||||||
|
clearBrowserSelection();
|
||||||
|
var columns = getColumns();
|
||||||
|
var column = columns[selectedColumnIndex];
|
||||||
|
if (!column) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
expandColumnForKeyboard(column);
|
||||||
|
scrollBrowserColumnIntoView(column);
|
||||||
|
|
||||||
|
if (!selectedLink || !column.contains(selectedLink)) {
|
||||||
|
selectedLink = column.querySelector("a.is-active[href]") || column.querySelector("a[href]");
|
||||||
|
}
|
||||||
|
if (!selectedLink) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedLink.classList.add("is-keyboard-selected");
|
||||||
|
selectedLink.setAttribute("aria-selected", "true");
|
||||||
|
if (focusSelected) {
|
||||||
|
selectedLink.focus({ preventScroll: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectBrowserColumn(columnIndex, focusSelected) {
|
||||||
|
var columns = getColumns();
|
||||||
|
if (!columns.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mode = "browser";
|
||||||
|
selectedColumnIndex = clamp(columnIndex, 0, columns.length - 1);
|
||||||
|
var links = getColumnLinks(selectedColumnIndex);
|
||||||
|
selectedLink = links.find(function (link) {
|
||||||
|
return link.classList.contains("is-active");
|
||||||
|
}) || links[0] || null;
|
||||||
|
clearParagraphSelection();
|
||||||
|
updateBrowserSelection(focusSelected);
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveBrowserVertical(delta) {
|
||||||
|
var links = getColumnLinks(selectedColumnIndex);
|
||||||
|
if (!links.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var currentIndex = links.indexOf(selectedLink);
|
||||||
|
if (currentIndex < 0) {
|
||||||
|
currentIndex = links.findIndex(function (link) {
|
||||||
|
return link.classList.contains("is-active");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (currentIndex < 0) {
|
||||||
|
currentIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedLink = links[clamp(currentIndex + delta, 0, links.length - 1)];
|
||||||
|
updateBrowserSelection(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function navigateTo(link) {
|
||||||
|
if (!link || !link.href) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.location.href = link.href;
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveBrowserRight() {
|
||||||
|
var columns = getColumns();
|
||||||
|
if (!columns.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedColumnIndex < columns.length - 1 && selectedLink && selectedLink.classList.contains("is-active")) {
|
||||||
|
selectBrowserColumn(selectedColumnIndex + 1, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedColumnIndex < columns.length - 1 && !selectedLink) {
|
||||||
|
selectBrowserColumn(selectedColumnIndex + 1, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedLink && !selectedLink.classList.contains("is-active")) {
|
||||||
|
navigateTo(selectedLink);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (markdownBody && getParagraphTargets().length) {
|
||||||
|
enterParagraphMode(0);
|
||||||
|
} else if (selectedLink) {
|
||||||
|
navigateTo(selectedLink);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function nearestParagraphIndex() {
|
||||||
|
var targets = getParagraphTargets();
|
||||||
|
if (!targets.length) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (selectedParagraphIndex >= 0 && selectedParagraphIndex < targets.length) {
|
||||||
|
return selectedParagraphIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
var shellRect = documentShell ? documentShell.getBoundingClientRect() : null;
|
||||||
|
var targetY = shellRect ? shellRect.top + shellRect.height * 0.35 : window.innerHeight * 0.35;
|
||||||
|
var bestIndex = 0;
|
||||||
|
var bestDistance = Number.POSITIVE_INFINITY;
|
||||||
|
targets.forEach(function (el, index) {
|
||||||
|
var distance = Math.abs(el.getBoundingClientRect().top - targetY);
|
||||||
|
if (distance < bestDistance) {
|
||||||
|
bestDistance = distance;
|
||||||
|
bestIndex = index;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return bestIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectParagraph(index) {
|
||||||
|
var targets = getParagraphTargets();
|
||||||
|
if (!targets.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearBrowserSelection();
|
||||||
|
clearParagraphSelection();
|
||||||
|
mode = "paragraph";
|
||||||
|
selectedParagraphIndex = clamp(index, 0, targets.length - 1);
|
||||||
|
|
||||||
|
var target = targets[selectedParagraphIndex];
|
||||||
|
target.classList.add("is-keyboard-selected");
|
||||||
|
target.setAttribute("aria-selected", "true");
|
||||||
|
target.setAttribute("tabindex", "-1");
|
||||||
|
target.focus({ preventScroll: true });
|
||||||
|
target.scrollIntoView({ block: "nearest", behavior: "smooth" });
|
||||||
|
}
|
||||||
|
|
||||||
|
function enterParagraphMode(offset) {
|
||||||
|
var index = nearestParagraphIndex();
|
||||||
|
if (index < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
selectParagraph(index + offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveParagraphVertical(delta) {
|
||||||
|
var targets = getParagraphTargets();
|
||||||
|
if (!targets.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var currentIndex = selectedParagraphIndex;
|
||||||
|
if (currentIndex < 0 || currentIndex >= targets.length) {
|
||||||
|
currentIndex = nearestParagraphIndex();
|
||||||
|
}
|
||||||
|
selectParagraph(currentIndex + delta);
|
||||||
|
}
|
||||||
|
|
||||||
|
function openSelectedParagraphInEditor() {
|
||||||
|
var editLink = document.querySelector(".document-actions-dropdown__item[href$='/edit']");
|
||||||
|
if (editLink) {
|
||||||
|
navigateTo(editLink);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var path = documentShell ? documentShell.getAttribute("data-document-path") : "";
|
||||||
|
if (!path) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.location.href = "/docs/" + path.replace(/\.md$/, "") + "/edit";
|
||||||
|
}
|
||||||
|
|
||||||
|
function focusSearch() {
|
||||||
|
var search = document.querySelector(".site-search input[type='search'], .site-search input[name='q']");
|
||||||
|
if (!search) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
clearBrowserSelection();
|
||||||
|
clearParagraphSelection();
|
||||||
|
search.focus();
|
||||||
|
search.select();
|
||||||
|
}
|
||||||
|
|
||||||
|
function focusCommentsSidebar() {
|
||||||
|
var desktopToggle = document.querySelector("[data-comment-toggle]");
|
||||||
|
if (desktopToggle && desktopToggle.getAttribute("aria-pressed") === "false") {
|
||||||
|
desktopToggle.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
var mobileToggle = document.querySelector("[data-toggle-comments]");
|
||||||
|
if (mobileToggle && window.matchMedia("(max-width: 760px)").matches && mobileToggle.getAttribute("aria-expanded") === "false") {
|
||||||
|
mobileToggle.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
var focusTarget = document.querySelector("[data-comments-aside-list] .comments-aside-item") ||
|
||||||
|
document.querySelector("[data-comments-aside-list]") ||
|
||||||
|
document.querySelector("[data-comments-aside]");
|
||||||
|
if (!focusTarget) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearBrowserSelection();
|
||||||
|
clearParagraphSelection();
|
||||||
|
focusTarget.setAttribute("tabindex", "-1");
|
||||||
|
focusTarget.focus({ preventScroll: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
function focusDocumentActions() {
|
||||||
|
var dropdown = document.querySelector(".document-actions-dropdown");
|
||||||
|
if (!dropdown) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dropdown.open = true;
|
||||||
|
var focusTarget = dropdown.querySelector(".document-actions-dropdown__item[href], .document-actions-dropdown__item:not([disabled])") ||
|
||||||
|
dropdown.querySelector("summary");
|
||||||
|
if (!focusTarget) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearBrowserSelection();
|
||||||
|
clearParagraphSelection();
|
||||||
|
focusTarget.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeDocumentActions() {
|
||||||
|
var dropdown = document.querySelector(".document-actions-dropdown[open]");
|
||||||
|
if (dropdown) {
|
||||||
|
dropdown.open = false;
|
||||||
|
var summary = dropdown.querySelector("summary");
|
||||||
|
if (summary) {
|
||||||
|
summary.focus();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleEnter() {
|
||||||
|
if (mode === "paragraph") {
|
||||||
|
openSelectedParagraphInEditor();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
navigateTo(selectedLink);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleKeydown(event) {
|
||||||
|
if (event.defaultPrevented || event.ctrlKey || event.metaKey || event.altKey || shouldIgnoreTarget(event.target)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var key = event.key;
|
||||||
|
var normalized = key.length === 1 ? key.toLowerCase() : key;
|
||||||
|
|
||||||
|
if (event.code === "Backslash" || key === "\\") {
|
||||||
|
event.preventDefault();
|
||||||
|
focusSearch();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (normalized === "c") {
|
||||||
|
event.preventDefault();
|
||||||
|
focusCommentsSidebar();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (normalized === "e") {
|
||||||
|
event.preventDefault();
|
||||||
|
focusDocumentActions();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key === "Escape" && closeDocumentActions()) {
|
||||||
|
event.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key === "ArrowUp" || normalized === "k") {
|
||||||
|
event.preventDefault();
|
||||||
|
showKeyboardUI();
|
||||||
|
if (mode === "paragraph") {
|
||||||
|
moveParagraphVertical(-1);
|
||||||
|
} else {
|
||||||
|
moveBrowserVertical(-1);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key === "ArrowDown" || normalized === "j") {
|
||||||
|
event.preventDefault();
|
||||||
|
showKeyboardUI();
|
||||||
|
if (mode === "paragraph") {
|
||||||
|
moveParagraphVertical(1);
|
||||||
|
} else {
|
||||||
|
moveBrowserVertical(1);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key === "ArrowLeft" || normalized === "h") {
|
||||||
|
event.preventDefault();
|
||||||
|
showKeyboardUI();
|
||||||
|
if (mode === "paragraph") {
|
||||||
|
selectBrowserColumn(getColumns().length - 1, true);
|
||||||
|
} else {
|
||||||
|
selectBrowserColumn(selectedColumnIndex - 1, true);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key === "ArrowRight" || normalized === "l") {
|
||||||
|
event.preventDefault();
|
||||||
|
showKeyboardUI();
|
||||||
|
if (mode === "paragraph") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
moveBrowserRight();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key === "Enter") {
|
||||||
|
event.preventDefault();
|
||||||
|
showKeyboardUI();
|
||||||
|
handleEnter();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function initSelection() {
|
||||||
|
var columns = getColumns();
|
||||||
|
if (!columns.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var activeLinks = Array.prototype.slice.call(browser.querySelectorAll("a.is-active[href]"));
|
||||||
|
var activeLink = activeLinks[activeLinks.length - 1] || browser.querySelector("a[href]");
|
||||||
|
selectedLink = activeLink;
|
||||||
|
if (activeLink) {
|
||||||
|
var activeColumn = activeLink.closest(".miller-column");
|
||||||
|
selectedColumnIndex = Math.max(0, columns.indexOf(activeColumn));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
initSelection();
|
||||||
|
document.addEventListener("keydown", handleKeydown);
|
||||||
|
})();
|
||||||
@@ -1540,11 +1540,17 @@ code {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.miller-column a:hover,
|
.miller-column a:hover,
|
||||||
.miller-column a.is-active {
|
.miller-column a.is-active,
|
||||||
|
.keyboard-navigation-active .miller-column a.is-keyboard-selected {
|
||||||
background: var(--accent-soft);
|
background: var(--accent-soft);
|
||||||
color: var(--accent);
|
color: var(--accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.keyboard-navigation-active .miller-column a.is-keyboard-selected {
|
||||||
|
outline: 2px solid color-mix(in srgb, var(--accent) 70%, transparent);
|
||||||
|
outline-offset: -2px;
|
||||||
|
}
|
||||||
|
|
||||||
.browser-item-name {
|
.browser-item-name {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
@@ -2080,7 +2086,7 @@ code {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-monaco-mount] {
|
[data-codemirror-mount] {
|
||||||
display: none;
|
display: none;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 60vh;
|
min-height: 60vh;
|
||||||
@@ -2089,7 +2095,7 @@ code {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-monaco-mount].monaco-mounted {
|
[data-codemirror-mount].codemirror-mounted {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2105,26 +2111,31 @@ code {
|
|||||||
font: 400 0.98rem/1.6 ui-monospace, SFMono-Regular, monospace;
|
font: 400 0.98rem/1.6 ui-monospace, SFMono-Regular, monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-monaco-mount].monaco-mounted ~ textarea {
|
[data-codemirror-mount].codemirror-mounted ~ textarea {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes monaco-shimmer {
|
@keyframes codemirror-shimmer {
|
||||||
0% { background-position: -200% 0; }
|
0% { background-position: -200% 0; }
|
||||||
100% { background-position: 200% 0; }
|
100% { background-position: 200% 0; }
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-monaco-mount]:not(.monaco-mounted) {
|
[data-codemirror-mount]:not(.codemirror-mounted) {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
background: linear-gradient(90deg, var(--panel-strong) 25%, var(--panel) 50%, var(--panel-strong) 75%);
|
background: linear-gradient(90deg, var(--panel-strong) 25%, var(--panel) 50%, var(--panel-strong) 75%);
|
||||||
background-size: 200% 100%;
|
background-size: 200% 100%;
|
||||||
animation: monaco-shimmer 1.5s ease infinite;
|
animation: codemirror-shimmer 1.5s ease infinite;
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[data-codemirror-mount] .cm-editor,
|
||||||
|
[data-conflict-codemirror-mount] .cm-editor {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.editor-status {
|
.editor-status {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
min-width: 5.5rem;
|
min-width: 5.5rem;
|
||||||
@@ -2290,7 +2301,7 @@ code {
|
|||||||
padding: 0.75rem;
|
padding: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-conflict-monaco-mount] {
|
[data-conflict-codemirror-mount] {
|
||||||
display: none;
|
display: none;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 18rem;
|
min-height: 18rem;
|
||||||
@@ -2300,11 +2311,11 @@ code {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-conflict-monaco-mount].monaco-mounted {
|
[data-conflict-codemirror-mount].codemirror-mounted {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-conflict-monaco-mount].monaco-mounted + textarea {
|
[data-conflict-codemirror-mount].codemirror-mounted + textarea {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2833,7 +2844,7 @@ code {
|
|||||||
color: var(--text);
|
color: var(--text);
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-conflict-monaco-mount] {
|
[data-conflict-codemirror-mount] {
|
||||||
border-color: oklch(0.55 0.18 25 / 0.3);
|
border-color: oklch(0.55 0.18 25 / 0.3);
|
||||||
background: oklch(0.14 0.012 55);
|
background: oklch(0.14 0.012 55);
|
||||||
}
|
}
|
||||||
@@ -2913,10 +2924,17 @@ code {
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.commentable:hover {
|
.commentable:hover,
|
||||||
|
.keyboard-navigation-active .commentable.is-keyboard-selected,
|
||||||
|
.keyboard-navigation-active .markdown-body .is-keyboard-selected {
|
||||||
background: var(--accent-soft);
|
background: var(--accent-soft);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.keyboard-navigation-active .markdown-body .is-keyboard-selected {
|
||||||
|
outline: 2px solid color-mix(in srgb, var(--accent) 68%, transparent);
|
||||||
|
outline-offset: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
.comment-anchor-btn {
|
.comment-anchor-btn {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: -1.5rem;
|
right: -1.5rem;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
const STATIC_CACHE = "md-hub-static-v2";
|
const STATIC_CACHE = "md-hub-static-v6";
|
||||||
const DB_NAME = "md-hub-cache";
|
const DB_NAME = "md-hub-cache";
|
||||||
const DB_VERSION = 3;
|
const DB_VERSION = 3;
|
||||||
const STORE_PAGES = "pages";
|
const STORE_PAGES = "pages";
|
||||||
@@ -8,7 +8,9 @@ const STATIC_ASSETS = [
|
|||||||
"/static/sync.js",
|
"/static/sync.js",
|
||||||
"/static/realtime.js",
|
"/static/realtime.js",
|
||||||
"/static/editor.js",
|
"/static/editor.js",
|
||||||
|
"/static/codemirror.bundle.js",
|
||||||
"/static/render.js",
|
"/static/render.js",
|
||||||
|
"/static/keyboard-nav.js",
|
||||||
"/static/tags.js",
|
"/static/tags.js",
|
||||||
"/static/favicon.png",
|
"/static/favicon.png",
|
||||||
"/static/cairnquire%20logo%402x.webp",
|
"/static/cairnquire%20logo%402x.webp",
|
||||||
|
|||||||
@@ -111,6 +111,7 @@
|
|||||||
<script src="/static/auth.js" defer></script>
|
<script src="/static/auth.js" defer></script>
|
||||||
<script src="/static/render.js" defer></script>
|
<script src="/static/render.js" defer></script>
|
||||||
<script src="/static/comments.js" defer></script>
|
<script src="/static/comments.js" defer></script>
|
||||||
|
<script src="/static/keyboard-nav.js" defer></script>
|
||||||
<script src="/static/mobile.js" defer></script>
|
<script src="/static/mobile.js" defer></script>
|
||||||
<script src="/static/archive.js" defer></script>
|
<script src="/static/archive.js" defer></script>
|
||||||
<script src="/static/permissions.js" defer></script>
|
<script src="/static/permissions.js" defer></script>
|
||||||
|
|||||||
@@ -70,7 +70,7 @@
|
|||||||
<div class="editor-conflict__diff" data-conflict-diff></div>
|
<div class="editor-conflict__diff" data-conflict-diff></div>
|
||||||
<div class="editor-conflict__resolution">
|
<div class="editor-conflict__resolution">
|
||||||
<label for="conflict-resolution">Resolved document</label>
|
<label for="conflict-resolution">Resolved document</label>
|
||||||
<div data-conflict-monaco-mount></div>
|
<div data-conflict-codemirror-mount></div>
|
||||||
<textarea id="conflict-resolution" data-conflict-resolution spellcheck="false"></textarea>
|
<textarea id="conflict-resolution" data-conflict-resolution spellcheck="false"></textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -78,7 +78,7 @@
|
|||||||
|
|
||||||
<form class="editor-form" data-editor-form data-document-path="{{ .Path }}">
|
<form class="editor-form" data-editor-form data-document-path="{{ .Path }}">
|
||||||
<label class="sr-only" for="document-editor">Markdown editor</label>
|
<label class="sr-only" for="document-editor">Markdown editor</label>
|
||||||
<div data-monaco-mount></div>
|
<div data-codemirror-mount></div>
|
||||||
<textarea id="document-editor" name="content" data-document-editor spellcheck="false">{{ .Source }}</textarea>
|
<textarea id="document-editor" name="content" data-document-editor spellcheck="false">{{ .Source }}</textarea>
|
||||||
</form>
|
</form>
|
||||||
</article>
|
</article>
|
||||||
|
|||||||
Reference in New Issue
Block a user