mirror of
https://github.com/agent0ai/agent-zero.git
synced 2026-07-18 05:13:30 +00:00
Show full documents in Editor
Remove heading-based Markdown pagination so source and preview operate on the complete file. Add support for keyboard undo/redo ops. Give plain text documents the same preview, formatting, save, and file-action toolbar as Markdown, with regression coverage for both behaviors.
This commit is contained in:
parent
77349bd9a5
commit
dbdb77b057
5 changed files with 132 additions and 167 deletions
|
|
@ -20,6 +20,7 @@
|
|||
- Keep Editor Open wired through the File Browser text picker so users can open one or more Markdown or plain text files with an obvious confirmation action.
|
||||
- Keep Download in the Editor file-actions menu and save dirty text before downloading it.
|
||||
- Keep Save As distinct from Rename: Save As writes the current editor text to a chosen `.md` or `.txt` path and retargets the active session without removing the original file.
|
||||
- Keep Markdown and plain text on the same toolbar, with full-document source and preview modes plus shared Undo/Redo buttons and keyboard shortcuts.
|
||||
- Preserve source chat context ids when opening Markdown files from tool-result canvas handoffs.
|
||||
|
||||
## Work Guidance
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
<div x-data>
|
||||
<template x-if="$store.editor">
|
||||
<div class="editor-panel" x-create="$store.editor.onMount($el, xAttrs($el) || {})" x-destroy="$store.editor.cleanup()">
|
||||
<div class="editor-shell" @keydown="$store.editor.handleEditorKeydown($event)">
|
||||
<div class="editor-shell" @keydown.capture="$store.editor.handleEditorKeydown($event)">
|
||||
<div class="editor-tabs" x-show="$store.editor.visibleTabs().length > 0" style="display: none;" role="tablist" aria-label="Open text files">
|
||||
<template x-for="tab in $store.editor.visibleTabs()" :key="tab.tab_id">
|
||||
<div
|
||||
|
|
@ -82,7 +82,7 @@
|
|||
<button
|
||||
type="button"
|
||||
class="editor-icon-button editor-mode-toggle"
|
||||
x-show="$store.editor.isMarkdown()"
|
||||
x-show="$store.editor.isTextDocument()"
|
||||
style="display: none;"
|
||||
:title="$store.editor.viewModeTitle()"
|
||||
:aria-label="$store.editor.viewModeTitle()"
|
||||
|
|
@ -91,44 +91,40 @@
|
|||
<span class="material-symbols-outlined" aria-hidden="true" x-text="$store.editor.viewModeIcon()"></span>
|
||||
</button>
|
||||
|
||||
<div class="editor-tool-group editor-source-tools" x-show="$store.editor.isTextDocument() && $store.editor.isSourceMode()" style="display: none;">
|
||||
<button type="button" class="editor-icon-button" title="Undo" aria-label="Undo" :disabled="!$store.editor.canUndo()" @click="$store.editor.undo()">
|
||||
<div class="editor-tool-group editor-history-tools" x-show="$store.editor.isTextDocument()" style="display: none;">
|
||||
<button type="button" class="editor-icon-button" title="Undo" aria-label="Undo" :disabled="$store.editor.previewEditing || !$store.editor.canUndo()" @click="$store.editor.undo()">
|
||||
<span class="material-symbols-outlined">undo</span>
|
||||
</button>
|
||||
<button type="button" class="editor-icon-button" title="Redo" aria-label="Redo" :disabled="!$store.editor.canRedo()" @click="$store.editor.redo()">
|
||||
<button type="button" class="editor-icon-button" title="Redo" aria-label="Redo" :disabled="$store.editor.previewEditing || !$store.editor.canRedo()" @click="$store.editor.redo()">
|
||||
<span class="material-symbols-outlined">redo</span>
|
||||
</button>
|
||||
<button type="button" class="editor-icon-button" title="Bold" aria-label="Bold" x-show="$store.editor.isMarkdown()" @click="$store.editor.format('bold')">
|
||||
</div>
|
||||
|
||||
<div class="editor-tool-group editor-source-tools" x-show="$store.editor.isTextDocument() && $store.editor.isSourceMode()" style="display: none;">
|
||||
<button type="button" class="editor-icon-button" title="Bold" aria-label="Bold" @click="$store.editor.format('bold')">
|
||||
<span class="material-symbols-outlined">format_bold</span>
|
||||
</button>
|
||||
<button type="button" class="editor-icon-button" title="Italic" aria-label="Italic" x-show="$store.editor.isMarkdown()" @click="$store.editor.format('italic')">
|
||||
<button type="button" class="editor-icon-button" title="Italic" aria-label="Italic" @click="$store.editor.format('italic')">
|
||||
<span class="material-symbols-outlined">format_italic</span>
|
||||
</button>
|
||||
<button type="button" class="editor-icon-button" title="List" aria-label="List" x-show="$store.editor.isMarkdown()" @click="$store.editor.format('list')">
|
||||
<button type="button" class="editor-icon-button" title="List" aria-label="List" @click="$store.editor.format('list')">
|
||||
<span class="material-symbols-outlined">format_list_bulleted</span>
|
||||
</button>
|
||||
<button type="button" class="editor-icon-button" title="Numbered list" aria-label="Numbered list" x-show="$store.editor.isMarkdown()" @click="$store.editor.format('numbered')">
|
||||
<button type="button" class="editor-icon-button" title="Numbered list" aria-label="Numbered list" @click="$store.editor.format('numbered')">
|
||||
<span class="material-symbols-outlined">format_list_numbered</span>
|
||||
</button>
|
||||
<button type="button" class="editor-icon-button" title="Table" aria-label="Table" x-show="$store.editor.isMarkdown()" @click="$store.editor.format('table')">
|
||||
<button type="button" class="editor-icon-button" title="Table" aria-label="Table" @click="$store.editor.format('table')">
|
||||
<span class="material-symbols-outlined">table</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="editor-tool-group editor-preview-tools" x-show="$store.editor.isMarkdown() && $store.editor.isPreviewMode()" style="display: none;">
|
||||
<button type="button" class="editor-icon-button" title="Previous page" aria-label="Previous page" :disabled="$store.editor.previewEditing || $store.editor.activePageIndex <= 0" @click="$store.editor.previousPage()">
|
||||
<span class="material-symbols-outlined">chevron_left</span>
|
||||
</button>
|
||||
<span class="editor-page-count" x-text="$store.editor.pagePositionLabel()"></span>
|
||||
<button type="button" class="editor-icon-button" title="Next page" aria-label="Next page" :disabled="$store.editor.previewEditing || $store.editor.activePageIndex >= $store.editor.pages().length - 1" @click="$store.editor.nextPage()">
|
||||
<span class="material-symbols-outlined">chevron_right</span>
|
||||
</button>
|
||||
<button type="button" class="editor-icon-button" title="Edit page" aria-label="Edit page" x-show="!$store.editor.previewEditing" @click="$store.editor.startPreviewEdit()">
|
||||
<div class="editor-tool-group editor-preview-tools" x-show="$store.editor.isTextDocument() && $store.editor.isPreviewMode()" style="display: none;">
|
||||
<button type="button" class="editor-icon-button" title="Edit document" aria-label="Edit document" x-show="!$store.editor.previewEditing" @click="$store.editor.startPreviewEdit()">
|
||||
<span class="material-symbols-outlined">edit_note</span>
|
||||
</button>
|
||||
<button type="button" class="editor-icon-button is-primary" title="Apply page edit" aria-label="Apply page edit" x-show="$store.editor.previewEditing" @click="$store.editor.applyPreviewEdit()">
|
||||
<button type="button" class="editor-icon-button is-primary" title="Apply document edit" aria-label="Apply document edit" x-show="$store.editor.previewEditing" @click="$store.editor.applyPreviewEdit()">
|
||||
<span class="material-symbols-outlined">check</span>
|
||||
</button>
|
||||
<button type="button" class="editor-icon-button" title="Cancel page edit" aria-label="Cancel page edit" x-show="$store.editor.previewEditing" @click="$store.editor.cancelPreviewEdit()">
|
||||
<button type="button" class="editor-icon-button" title="Cancel document edit" aria-label="Cancel document edit" x-show="$store.editor.previewEditing" @click="$store.editor.cancelPreviewEdit()">
|
||||
<span class="material-symbols-outlined">close</span>
|
||||
</button>
|
||||
<button type="button" class="editor-icon-button" title="Search" aria-label="Search" :disabled="$store.editor.previewEditing" @click="$store.editor.openSearch()">
|
||||
|
|
@ -241,7 +237,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="editor-preview-shell" x-show="$store.editor.session && $store.editor.isMarkdown() && $store.editor.isPreviewMode()" style="display: none;">
|
||||
<div class="editor-preview-shell" x-show="$store.editor.session && $store.editor.isTextDocument() && $store.editor.isPreviewMode()" style="display: none;">
|
||||
<div class="editor-preview-title">
|
||||
<h1 x-text="$store.editor.pageTitle()"></h1>
|
||||
</div>
|
||||
|
|
@ -249,7 +245,7 @@
|
|||
<textarea
|
||||
class="editor-preview-page-editor"
|
||||
data-editor-preview-source
|
||||
aria-label="Markdown page source"
|
||||
aria-label="Document source"
|
||||
x-model="$store.editor.previewEditText"
|
||||
@input="$store.editor.onPreviewEditInput()"
|
||||
@keydown.meta.enter.prevent="$store.editor.applyPreviewEdit()"
|
||||
|
|
@ -263,7 +259,7 @@
|
|||
data-editor-preview
|
||||
x-show="!$store.editor.previewEditing"
|
||||
x-html="$store.editor.previewHtml()"
|
||||
x-effect="$store.editor.activePageIndex; $store.editor.editorText; $store.editor.searchQuery; $store.editor.searchIndex; $store.editor.schedulePreviewEnhance()"
|
||||
x-effect="$store.editor.editorText; $store.editor.searchQuery; $store.editor.searchIndex; $store.editor.schedulePreviewEnhance()"
|
||||
@click="$store.editor.handlePreviewClick($event)"
|
||||
></div>
|
||||
</div>
|
||||
|
|
@ -597,7 +593,6 @@
|
|||
min-width: 8px;
|
||||
}
|
||||
|
||||
.editor-page-count,
|
||||
.editor-search-count {
|
||||
min-width: 54px;
|
||||
color: var(--color-text-secondary);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import { renderSafeMarkdown } from "/js/safe-markdown.js";
|
||||
|
||||
const PAGE_HEADING_RE = /^(#{1,2})\s+(.+?)\s*#*\s*$/;
|
||||
const FOOTNOTE_DEF_RE = /^\[\^([^\]]+)\]:\s*(.*)$/;
|
||||
|
||||
export function renderEditorPreviewMarkdown(markdown = "", fullMarkdown = markdown) {
|
||||
|
|
@ -13,68 +12,15 @@ export function renderEditorPreviewMarkdown(markdown = "", fullMarkdown = markdo
|
|||
|
||||
export function buildMarkdownPages(markdown = "", fallbackTitle = "Markdown") {
|
||||
const source = String(markdown || "");
|
||||
const lines = source.split("\n");
|
||||
const pages = [];
|
||||
let current = null;
|
||||
let intro = [];
|
||||
let introStart = 0;
|
||||
let fenced = false;
|
||||
let offset = 0;
|
||||
|
||||
const startPage = (title, level, line, start) => {
|
||||
if (current) {
|
||||
current.end = start;
|
||||
pages.push(finalizePage(current, pages.length, source));
|
||||
}
|
||||
current = {
|
||||
title: cleanHeadingText(title) || fallbackTitle,
|
||||
level,
|
||||
lines: [line],
|
||||
start,
|
||||
end: source.length,
|
||||
};
|
||||
};
|
||||
|
||||
for (const [index, line] of lines.entries()) {
|
||||
const lineStart = offset;
|
||||
const lineEnd = lineStart + line.length + (index < lines.length - 1 ? 1 : 0);
|
||||
if (/^\s*```/.test(line)) fenced = !fenced;
|
||||
const match = !fenced ? line.match(PAGE_HEADING_RE) : null;
|
||||
if (match) {
|
||||
if (!current && intro.join("\n").trim()) {
|
||||
pages.push(finalizePage({
|
||||
title: fallbackTitle,
|
||||
level: 0,
|
||||
lines: intro,
|
||||
start: introStart,
|
||||
end: lineStart,
|
||||
}, pages.length, source));
|
||||
intro = [];
|
||||
}
|
||||
startPage(match[2], match[1].length, line, lineStart);
|
||||
offset = lineEnd;
|
||||
continue;
|
||||
}
|
||||
if (current) current.lines.push(line);
|
||||
else intro.push(line);
|
||||
offset = lineEnd;
|
||||
}
|
||||
|
||||
if (current) {
|
||||
current.end = source.length;
|
||||
pages.push(finalizePage(current, pages.length, source));
|
||||
}
|
||||
else if (intro.join("\n").trim() || !pages.length) {
|
||||
pages.push(finalizePage({
|
||||
title: fallbackTitle,
|
||||
level: 0,
|
||||
lines: intro,
|
||||
start: introStart,
|
||||
end: source.length,
|
||||
}, pages.length, source));
|
||||
}
|
||||
|
||||
return pages;
|
||||
return [{
|
||||
index: 0,
|
||||
title: fallbackTitle,
|
||||
level: 0,
|
||||
anchor: slugifyHeading(fallbackTitle),
|
||||
start: 0,
|
||||
end: source.length,
|
||||
markdown: source,
|
||||
}];
|
||||
}
|
||||
|
||||
export function slugifyHeading(text = "", used = new Map()) {
|
||||
|
|
@ -119,28 +65,6 @@ export function isMarkdownPath(path = "") {
|
|||
return /\.md(?:own)?$/i.test(String(path || "").split(/[?#]/, 1)[0]);
|
||||
}
|
||||
|
||||
function finalizePage(page, index, source = "") {
|
||||
const start = Math.max(0, Number(page.start || 0));
|
||||
const end = Math.max(start, Number(page.end ?? String(source || "").length));
|
||||
const markdown = String(source || "").slice(start, end) || page.lines.join("\n");
|
||||
return {
|
||||
index,
|
||||
title: page.title,
|
||||
level: page.level,
|
||||
anchor: slugifyHeading(page.title),
|
||||
start,
|
||||
end,
|
||||
markdown,
|
||||
};
|
||||
}
|
||||
|
||||
function cleanHeadingText(text = "") {
|
||||
return String(text || "")
|
||||
.replace(/\\([\\`*_[\]{}()#+.!-])/g, "$1")
|
||||
.replace(/[*_`~]/g, "")
|
||||
.trim();
|
||||
}
|
||||
|
||||
function prepareFootnotes(markdown = "", fullMarkdown = markdown) {
|
||||
const definitions = [];
|
||||
const body = [];
|
||||
|
|
|
|||
|
|
@ -251,6 +251,7 @@ const model = {
|
|||
_inputTimer: null,
|
||||
_history: [],
|
||||
_historyIndex: -1,
|
||||
_historyPushedAt: 0,
|
||||
_pendingFocus: false,
|
||||
_pendingFocusEnd: true,
|
||||
_focusAttempts: 0,
|
||||
|
|
@ -336,7 +337,7 @@ const model = {
|
|||
},
|
||||
|
||||
async setViewMode(mode) {
|
||||
const next = mode === PREVIEW_MODE && this.isMarkdown() ? PREVIEW_MODE : SOURCE_MODE;
|
||||
const next = mode === PREVIEW_MODE && this.isTextDocument() ? PREVIEW_MODE : SOURCE_MODE;
|
||||
if (this.viewMode === next) return;
|
||||
this.applyPreviewEdit({ silent: true });
|
||||
this.syncEditorText();
|
||||
|
|
@ -354,7 +355,7 @@ const model = {
|
|||
},
|
||||
|
||||
async toggleViewMode() {
|
||||
if (!this.isMarkdown()) return;
|
||||
if (!this.isTextDocument()) return;
|
||||
await this.setViewMode(this.isPreviewMode() ? SOURCE_MODE : PREVIEW_MODE);
|
||||
},
|
||||
|
||||
|
|
@ -367,7 +368,7 @@ const model = {
|
|||
},
|
||||
|
||||
pages() {
|
||||
if (!this.isMarkdown()) return [];
|
||||
if (!this.isTextDocument()) return [];
|
||||
return buildMarkdownPages(this.editorText, this.tabTitle(this.session || {}));
|
||||
},
|
||||
|
||||
|
|
@ -381,35 +382,13 @@ const model = {
|
|||
return this.currentPage().title || this.tabTitle(this.session || {});
|
||||
},
|
||||
|
||||
pagePositionLabel() {
|
||||
const pages = this.pages();
|
||||
if (!pages.length) return "";
|
||||
return `${Math.min(this.activePageIndex + 1, pages.length)} of ${pages.length}`;
|
||||
},
|
||||
|
||||
previewHtml() {
|
||||
if (!this.isMarkdown()) return "";
|
||||
if (!this.isTextDocument()) return "";
|
||||
return renderEditorPreviewMarkdown(this.currentPage().markdown || "", this.editorText);
|
||||
},
|
||||
|
||||
selectPage(index) {
|
||||
if (this.previewEditing) return;
|
||||
const pages = this.pages();
|
||||
if (!pages.length) return;
|
||||
this.activePageIndex = Math.max(0, Math.min(Number(index) || 0, pages.length - 1));
|
||||
this.schedulePreviewEnhance();
|
||||
},
|
||||
|
||||
nextPage() {
|
||||
this.selectPage(this.activePageIndex + 1);
|
||||
},
|
||||
|
||||
previousPage() {
|
||||
this.selectPage(this.activePageIndex - 1);
|
||||
},
|
||||
|
||||
startPreviewEdit() {
|
||||
if (!this.session || !this.isMarkdown() || !this.isPreviewMode()) return;
|
||||
if (!this.session || !this.isTextDocument() || !this.isPreviewMode()) return;
|
||||
const page = this.currentPage();
|
||||
this.previewEditing = true;
|
||||
this.previewEditDirty = false;
|
||||
|
|
@ -457,7 +436,7 @@ const model = {
|
|||
this.previewEditPageIndex = -1;
|
||||
|
||||
return this.replacePageMarkdown(page, replacement, {
|
||||
message: "Page updated",
|
||||
message: "Document updated",
|
||||
silent: options.silent,
|
||||
});
|
||||
},
|
||||
|
|
@ -493,7 +472,7 @@ const model = {
|
|||
},
|
||||
|
||||
togglePreviewTask(taskIndex, checked) {
|
||||
if (!this.session || !this.isMarkdown() || !this.isPreviewMode() || this.previewEditing) return false;
|
||||
if (!this.session || !this.isTextDocument() || !this.isPreviewMode() || this.previewEditing) return false;
|
||||
const page = this.currentPage();
|
||||
const lines = String(page.markdown || "").split("\n");
|
||||
const indexes = taskLineIndexes(page.markdown || "");
|
||||
|
|
@ -514,7 +493,7 @@ const model = {
|
|||
},
|
||||
|
||||
schedulePreviewEnhance() {
|
||||
if (!this.isMarkdown() || !this.isPreviewMode()) return;
|
||||
if (!this.isTextDocument() || !this.isPreviewMode()) return;
|
||||
if (this._previewEnhanceTimer) globalThis.clearTimeout(this._previewEnhanceTimer);
|
||||
this._previewEnhanceTimer = globalThis.setTimeout(() => {
|
||||
this._previewEnhanceTimer = null;
|
||||
|
|
@ -707,7 +686,7 @@ const model = {
|
|||
},
|
||||
|
||||
openSearch() {
|
||||
if (!this.isMarkdown()) return;
|
||||
if (!this.isTextDocument()) return;
|
||||
if (!this.isPreviewMode()) {
|
||||
this.setViewMode(PREVIEW_MODE);
|
||||
}
|
||||
|
|
@ -870,8 +849,19 @@ const model = {
|
|||
},
|
||||
|
||||
handleEditorKeydown(event) {
|
||||
if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "f") {
|
||||
if (!this.isMarkdown()) return;
|
||||
if (!(event.metaKey || event.ctrlKey) || !this.isTextDocument()) return;
|
||||
const key = event.key.toLowerCase();
|
||||
const historyAction = key === "y" || (key === "z" && event.shiftKey) ? "redo" : key === "z" ? "undo" : "";
|
||||
const nativeEditing = event.target?.matches?.("input, textarea, [contenteditable='true']")
|
||||
&& !event.target.closest?.("[data-editor-ace], [data-editor-source]");
|
||||
if (nativeEditing) return;
|
||||
if (historyAction && !this.previewEditing) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this[historyAction]();
|
||||
return;
|
||||
}
|
||||
if (key === "f") {
|
||||
event.preventDefault();
|
||||
this.openSearch();
|
||||
}
|
||||
|
|
@ -982,11 +972,6 @@ const model = {
|
|||
this.activeTabId = tab?.tab_id || "";
|
||||
this.editorText = String(tab?.text || "");
|
||||
this.dirty = Boolean(tab?.dirty);
|
||||
if (!this.isMarkdown(tab) && this.isPreviewMode()) {
|
||||
this.viewMode = SOURCE_MODE;
|
||||
this.searchOpen = false;
|
||||
this.searchQuery = "";
|
||||
}
|
||||
if (this.previewEditing) this.cancelPreviewEdit();
|
||||
if (!options.preservePage) {
|
||||
this.activePageIndex = 0;
|
||||
|
|
@ -1500,7 +1485,6 @@ const model = {
|
|||
const wasActive = this.activeTabId === (previous?.tab_id || next.tab_id);
|
||||
if (wasActive) {
|
||||
this.session = next;
|
||||
if (!this.isMarkdown(next) && this.isPreviewMode()) this.viewMode = SOURCE_MODE;
|
||||
this.updateSourceEditorMode(next);
|
||||
}
|
||||
const index = this.tabs.findIndex((tab) => tab.tab_id === (previous?.tab_id || next.tab_id));
|
||||
|
|
@ -1519,37 +1503,41 @@ const model = {
|
|||
resetHistory(text) {
|
||||
this._history = [String(text || "")];
|
||||
this._historyIndex = 0;
|
||||
this._historyPushedAt = 0;
|
||||
},
|
||||
|
||||
pushHistory(text) {
|
||||
pushHistory(text, coalesce = false) {
|
||||
const value = String(text || "");
|
||||
if (this._history[this._historyIndex] === value) return;
|
||||
const now = Date.now();
|
||||
if (
|
||||
coalesce
|
||||
&& this._historyPushedAt
|
||||
&& now - this._historyPushedAt <= INPUT_PUSH_DELAY_MS
|
||||
&& this._historyIndex === this._history.length - 1
|
||||
&& this._historyIndex > 0
|
||||
) {
|
||||
this._history[this._historyIndex] = value;
|
||||
this._historyPushedAt = now;
|
||||
return;
|
||||
}
|
||||
this._history = this._history.slice(0, this._historyIndex + 1);
|
||||
this._history.push(value);
|
||||
if (this._history.length > MAX_HISTORY) this._history.shift();
|
||||
this._historyIndex = this._history.length - 1;
|
||||
this._historyPushedAt = coalesce ? now : 0;
|
||||
},
|
||||
|
||||
undo() {
|
||||
if (this.sourceEditor && this.isSourceMode()) {
|
||||
this.sourceEditor.undo();
|
||||
this.editorText = this.sourceEditor.getValue();
|
||||
this.syncEditorText();
|
||||
return;
|
||||
}
|
||||
if (this._historyIndex <= 0) return;
|
||||
this._historyPushedAt = 0;
|
||||
this._historyIndex -= 1;
|
||||
this.applyEditorText(this._history[this._historyIndex], true);
|
||||
},
|
||||
|
||||
redo() {
|
||||
if (this.sourceEditor && this.isSourceMode()) {
|
||||
this.sourceEditor.redo();
|
||||
this.editorText = this.sourceEditor.getValue();
|
||||
this.syncEditorText();
|
||||
return;
|
||||
}
|
||||
if (this._historyIndex >= this._history.length - 1) return;
|
||||
this._historyPushedAt = 0;
|
||||
this._historyIndex += 1;
|
||||
this.applyEditorText(this._history[this._historyIndex], true);
|
||||
},
|
||||
|
|
@ -1580,7 +1568,7 @@ const model = {
|
|||
|
||||
onSourceInput() {
|
||||
this.markDirty();
|
||||
this.pushHistory(this.editorText);
|
||||
this.pushHistory(this.editorText, true);
|
||||
this.scheduleInputPush();
|
||||
},
|
||||
|
||||
|
|
@ -1613,7 +1601,7 @@ const model = {
|
|||
},
|
||||
|
||||
format(command) {
|
||||
if (!this.session || !this.isMarkdown()) return;
|
||||
if (!this.session || !this.isTextDocument()) return;
|
||||
if (this.sourceEditor && this.isSourceMode()) {
|
||||
const selected = this.sourceEditor.getSelectedText();
|
||||
const replacement = this.formatReplacement(command, selected);
|
||||
|
|
|
|||
|
|
@ -156,6 +156,8 @@ def test_right_canvas_uses_desktop_surface_id_and_migrates_legacy_office_state()
|
|||
assert 'input[type="checkbox"]' in editor_store
|
||||
assert "renderEditorPreviewMarkdown" in editor_store
|
||||
assert "buildMarkdownPages" in editor_store
|
||||
assert "PAGE_HEADING_RE" not in editor_preview
|
||||
assert "markdown: source" in editor_preview
|
||||
assert "hydrateActiveSession" in editor_store
|
||||
assert "refreshSourceEditorLayout" in editor_store
|
||||
assert "editor.resize?.(true)" in editor_store
|
||||
|
|
@ -170,7 +172,6 @@ def test_right_canvas_uses_desktop_surface_id_and_migrates_legacy_office_state()
|
|||
assert "renderSafeMarkdown" in editor_preview
|
||||
assert "prepareFootnotes" in editor_preview
|
||||
assert "resolveDocumentRelativePath" in editor_preview
|
||||
assert "slice(start, end)" in editor_preview
|
||||
assert "allowDataImages: true" in editor_preview
|
||||
assert "allowLatex: true" in editor_preview
|
||||
assert "html = sanitizeHtml(html, options);" in safe_markdown
|
||||
|
|
@ -564,6 +565,7 @@ def test_editor_toolbar_places_preview_toggle_left_and_save_on_right():
|
|||
toolbar = editor_panel[toolbar_start:toolbar_end]
|
||||
|
||||
mode_toggle = toolbar.index("editor-mode-toggle")
|
||||
history_tools = toolbar.index("editor-history-tools")
|
||||
source_tools = toolbar.index("editor-source-tools")
|
||||
preview_tools = toolbar.index("editor-preview-tools")
|
||||
spacer = toolbar.index("editor-toolbar-spacer")
|
||||
|
|
@ -572,8 +574,8 @@ def test_editor_toolbar_places_preview_toggle_left_and_save_on_right():
|
|||
file_actions = toolbar.index("editor-file-actions")
|
||||
file_menu = toolbar.index("editor-file-menu")
|
||||
|
||||
assert mode_toggle < source_tools
|
||||
assert mode_toggle < preview_tools
|
||||
assert mode_toggle < history_tools < source_tools
|
||||
assert history_tools < preview_tools
|
||||
assert spacer < save_button < save_as_button < file_actions < file_menu
|
||||
assert "@click=\"$store.editor.save()\"" in toolbar
|
||||
assert "@click=\"$store.editor.saveAs()\"" in toolbar
|
||||
|
|
@ -589,6 +591,61 @@ def test_editor_toolbar_places_preview_toggle_left_and_save_on_right():
|
|||
assert "<span>Close File</span>" in file_menu_markup
|
||||
|
||||
|
||||
def test_editor_uses_full_document_preview_and_matching_markdown_text_tools():
|
||||
editor_panel = read("plugins", "_editor", "webui", "editor-panel.html")
|
||||
editor_store = read("plugins", "_editor", "webui", "editor-store.js")
|
||||
|
||||
assert "Previous page" not in editor_panel
|
||||
assert "Next page" not in editor_panel
|
||||
assert "editor-page-count" not in editor_panel
|
||||
assert "pagePositionLabel" not in editor_store
|
||||
assert "nextPage()" not in editor_store
|
||||
assert "previousPage()" not in editor_store
|
||||
assert 'x-show="$store.editor.isTextDocument()"' in editor_panel
|
||||
assert '$store.editor.isTextDocument() && $store.editor.isPreviewMode()' in editor_panel
|
||||
assert 'mode === PREVIEW_MODE && this.isTextDocument()' in editor_store
|
||||
assert "if (!this.session || !this.isTextDocument()) return;" in editor_store
|
||||
|
||||
source_tools_start = editor_panel.index('class="editor-tool-group editor-source-tools"')
|
||||
preview_tools_start = editor_panel.index('class="editor-tool-group editor-preview-tools"')
|
||||
source_tools = editor_panel[source_tools_start:preview_tools_start]
|
||||
for action in ("Bold", "Italic", "List", "Numbered list", "Table"):
|
||||
assert f'title="{action}"' in source_tools
|
||||
assert "isMarkdown()" not in source_tools
|
||||
|
||||
|
||||
def test_editor_history_shortcuts_and_toolbar_controls_cover_markdown_and_text():
|
||||
editor_panel = read("plugins", "_editor", "webui", "editor-panel.html")
|
||||
editor_store = read("plugins", "_editor", "webui", "editor-store.js")
|
||||
|
||||
assert '@keydown.capture="$store.editor.handleEditorKeydown($event)"' in editor_panel
|
||||
history_start = editor_panel.index('class="editor-tool-group editor-history-tools"')
|
||||
source_start = editor_panel.index('class="editor-tool-group editor-source-tools"')
|
||||
history_tools = editor_panel[history_start:source_start]
|
||||
assert 'x-show="$store.editor.isTextDocument()"' in history_tools
|
||||
assert 'title="Undo"' in history_tools
|
||||
assert 'title="Redo"' in history_tools
|
||||
assert "$store.editor.previewEditing || !$store.editor.canUndo()" in history_tools
|
||||
assert "$store.editor.previewEditing || !$store.editor.canRedo()" in history_tools
|
||||
|
||||
keydown_start = editor_store.index("handleEditorKeydown(event)")
|
||||
create_start = editor_store.index("\n async create", keydown_start)
|
||||
keydown = editor_store[keydown_start:create_start]
|
||||
assert 'key === "z"' in keydown
|
||||
assert 'key === "y"' in keydown
|
||||
assert "event.stopPropagation()" in keydown
|
||||
assert "this[historyAction]()" in keydown
|
||||
assert "nativeEditing" in keydown
|
||||
|
||||
undo_start = editor_store.index("\n undo()")
|
||||
can_undo_start = editor_store.index("\n canUndo()", undo_start)
|
||||
history_actions = editor_store[undo_start:can_undo_start]
|
||||
assert "sourceEditor.undo" not in history_actions
|
||||
assert "sourceEditor.redo" not in history_actions
|
||||
assert "pushHistory(text, coalesce = false)" in editor_store
|
||||
assert "this.pushHistory(this.editorText, true)" in editor_store
|
||||
|
||||
|
||||
def test_desktop_text_open_with_routes_to_editor_surface():
|
||||
desktop_session = read("plugins", "_desktop", "helpers", "desktop_session.py")
|
||||
desktop_store = read("plugins", "_desktop", "webui", "desktop-store.js")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue