mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 16:59:27 +08:00
1 parent
af8b64e748
commit
f76a1ecd1a
2 files changed
+57
-17
No files matched your search
+19
-6
@@ -705,7 +705,7 @@ el.editorTools.addEventListener("scroll", () => fontPicker.position());
|
|||||||
const canvasEditor = new CanvasEditor(el.viewerPanel, {
|
const canvasEditor = new CanvasEditor(el.viewerPanel, {
|
||||||
textControls: [el.textFont, el.textFontToggle, el.textFontAdd, el.textFontList, el.textSize, el.textColor, textBackground, ...textFormatButtons, el.undoButton, el.redoButton, el.saveButton, el.saveAsButton, el.fontInput, document.querySelector("#textReflow")],
|
textControls: [el.textFont, el.textFontToggle, el.textFontAdd, el.textFontList, el.textSize, el.textColor, textBackground, ...textFormatButtons, el.undoButton, el.redoButton, el.saveButton, el.saveAsButton, el.fontInput, document.querySelector("#textReflow")],
|
||||||
keepTextFocus: () => fontPicker.open || el.closePanel.open || canvasEditor.input?.fontDialog,
|
keepTextFocus: () => fontPicker.open || el.closePanel.open || canvasEditor.input?.fontDialog,
|
||||||
textClipboardScope: () => `${documentTab().id}:${state.wasmSeq}:${state.fileName}:${state.doc?.documents?.[state.doc.documentIndex || 0]?.root}:${state.doc?.documentIndex || 0}:${state.doc?.documentVersion || ""}`,
|
textClipboardScope: () => `${documentTab().id}:${state.wasmSeq}:${state.fileName}:${state.doc?.documents?.[state.doc.documentIndex || 0]?.root}:${state.doc?.documentIndex || 0}:${state.doc?.documentVersion || ""}:${state.editorInfo?.revision ?? ""}`,
|
||||||
getTextClipboard: () => workspace.textClipboard,
|
getTextClipboard: () => workspace.textClipboard,
|
||||||
setTextClipboard: clipboard => { workspace.textClipboard = clipboard; },
|
setTextClipboard: clipboard => { workspace.textClipboard = clipboard; },
|
||||||
scrollBy: (x, y) => { el.viewerPanel.scrollLeft += x; pageView.scrollViewerBy(y); },
|
scrollBy: (x, y) => { el.viewerPanel.scrollLeft += x; pageView.scrollViewerBy(y); },
|
||||||
@@ -10848,11 +10848,19 @@ function resetSearch(clearInput = true) {
|
|||||||
async function refreshSearchPages(pages, openSeq) {
|
async function refreshSearchPages(pages, openSeq) {
|
||||||
const tab = documentTab(), target = tab.state, query = target.searchQuery;
|
const tab = documentTab(), target = tab.state, query = target.searchQuery;
|
||||||
if (!query || target.searchRunning || target.searchTruncated) { resetSearch(false); return; }
|
if (!query || target.searchRunning || target.searchTruncated) { resetSearch(false); return; }
|
||||||
const seq = ++target.searchSeq;
|
pages = pages.filter(page => page < target.searchNext);
|
||||||
|
if (!pages.length) return;
|
||||||
|
const seq = ++target.searchSeq, next = target.searchNext;
|
||||||
target.searchRunning = true;
|
target.searchRunning = true;
|
||||||
const selected = target.searchMatches[target.searchIndex];
|
const previous = target.searchMatches, selected = previous[target.searchIndex];
|
||||||
|
target.searchNext = Math.min(...pages);
|
||||||
|
target.searchMatches = previous.filter(match => match.page < target.searchNext);
|
||||||
|
target.searchIndex = target.searchMatches.indexOf(selected);
|
||||||
|
el.searchResults.replaceChildren();
|
||||||
|
clearSearchHighlights();
|
||||||
|
renderSearchResults();
|
||||||
try {
|
try {
|
||||||
const matches = target.searchMatches.filter(match => !pages.includes(match.page));
|
const matches = previous.filter(match => !pages.includes(match.page));
|
||||||
for (const page of pages) {
|
for (const page of pages) {
|
||||||
const result = await callTabWASM(tab, "ofdgoSearchPage", page, query, SEARCH_MATCH_LIMIT - matches.length + 1);
|
const result = await callTabWASM(tab, "ofdgoSearchPage", page, query, SEARCH_MATCH_LIMIT - matches.length + 1);
|
||||||
if (tab.closed || target.openSeq !== openSeq || target.searchSeq !== seq) return;
|
if (tab.closed || target.openSeq !== openSeq || target.searchSeq !== seq) return;
|
||||||
@@ -10864,6 +10872,8 @@ async function refreshSearchPages(pages, openSeq) {
|
|||||||
}
|
}
|
||||||
matches.sort((a,b) => a.page - b.page);
|
matches.sort((a,b) => a.page - b.page);
|
||||||
target.searchMatches = matches;
|
target.searchMatches = matches;
|
||||||
|
target.searchNext = next;
|
||||||
|
target.searchPaused = false;
|
||||||
target.searchIndex = selected ? matches.indexOf(selected) : -1;
|
target.searchIndex = selected ? matches.indexOf(selected) : -1;
|
||||||
if (isForegroundDocument(tab)) {
|
if (isForegroundDocument(tab)) {
|
||||||
el.searchResults.replaceChildren();
|
el.searchResults.replaceChildren();
|
||||||
@@ -10874,8 +10884,7 @@ async function refreshSearchPages(pages, openSeq) {
|
|||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (target.openSeq === openSeq && target.searchSeq === seq) {
|
if (target.openSeq === openSeq && target.searchSeq === seq) {
|
||||||
target.searchQuery = "";
|
if (isForegroundDocument(tab)) { el.searchStatus.textContent = "搜索失败"; showError(err, false); }
|
||||||
if (isForegroundDocument(tab)) { resetSearch(false); showError(err, false); }
|
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
if (target.searchSeq === seq) target.searchRunning = false;
|
if (target.searchSeq === seq) target.searchRunning = false;
|
||||||
@@ -12616,6 +12625,7 @@ function showError(err, empty = !state.doc) {
|
|||||||
|
|
||||||
function setBusy(busy, text = "", percent = 0, status = "", tab = documentTab()) {
|
function setBusy(busy, text = "", percent = 0, status = "", tab = documentTab()) {
|
||||||
busy ||= Boolean(tab.editTask || operationRequest(tab));
|
busy ||= Boolean(tab.editTask || operationRequest(tab));
|
||||||
|
const resumeSearch = tab.busy && !busy && tab.state.searchPaused;
|
||||||
tab.busy = busy;
|
tab.busy = busy;
|
||||||
if (text) tab.progress = {text, percent};
|
if (text) tab.progress = {text, percent};
|
||||||
if (status) tab.status = status;
|
if (status) tab.status = status;
|
||||||
@@ -12657,6 +12667,9 @@ function setBusy(busy, text = "", percent = 0, status = "", tab = documentTab())
|
|||||||
el.fontList.inert = busy;
|
el.fontList.inert = busy;
|
||||||
el.docFontList.inert = busy;
|
el.docFontList.inert = busy;
|
||||||
if (!busy) {
|
if (!busy) {
|
||||||
|
if (resumeSearch) queueMicrotask(() => {
|
||||||
|
if (isForegroundDocument(tab) && !tab.busy && !tab.refreshPending && tab.state.searchPaused) searchDocument();
|
||||||
|
});
|
||||||
el.progressPanel.hidden = !state.saving;
|
el.progressPanel.hidden = !state.saving;
|
||||||
if (state.saving) {
|
if (state.saving) {
|
||||||
el.cancelExportButton.hidden = false;
|
el.cancelExportButton.hidden = false;
|
||||||
|
|||||||
+38
-11
@@ -71,6 +71,32 @@ function sliceRuns(runs, start, end) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function textChange(before, after) {
|
||||||
|
const sameFormat = (a, b) => a && b && FORMAT_KEYS.every(key => a.format[key] === b.format[key]);
|
||||||
|
let first = 0, lastBefore = before.length - 1, lastAfter = after.length - 1, start = 0;
|
||||||
|
let endBefore = before.reduce((length, run) => length + run.text.length, 0);
|
||||||
|
let endAfter = after.reduce((length, run) => length + run.text.length, 0);
|
||||||
|
while (sameFormat(before[first], after[first]) && before[first].text === after[first].text) start += before[first++].text.length;
|
||||||
|
while (lastBefore >= first && lastAfter >= first && sameFormat(before[lastBefore], after[lastAfter]) && before[lastBefore].text === after[lastAfter].text) {
|
||||||
|
endBefore -= before[lastBefore--].text.length;
|
||||||
|
endAfter -= after[lastAfter--].text.length;
|
||||||
|
}
|
||||||
|
if (first <= lastBefore && first <= lastAfter && sameFormat(before[first], after[first])) {
|
||||||
|
const left = before[first].text, right = after[first].text;
|
||||||
|
let count = 0;
|
||||||
|
while (count < left.length && count < right.length && left[count] === right[count]) count++;
|
||||||
|
start += count;
|
||||||
|
}
|
||||||
|
if (first <= lastBefore && first <= lastAfter && sameFormat(before[lastBefore], after[lastAfter])) {
|
||||||
|
const left = before[lastBefore].text, right = after[lastAfter].text;
|
||||||
|
let count = 0;
|
||||||
|
while (count < Math.min(left.length, right.length, endBefore - start, endAfter - start) && left[left.length - count - 1] === right[right.length - count - 1]) count++;
|
||||||
|
endBefore -= count;
|
||||||
|
endAfter -= count;
|
||||||
|
}
|
||||||
|
return { start, before: sliceRuns(before, start, endBefore), after: sliceRuns(after, start, endAfter) };
|
||||||
|
}
|
||||||
|
|
||||||
export class RichTextSession {
|
export class RichTextSession {
|
||||||
constructor(editor, model, faces = new Map(), imports = [], resources = new Map()) {
|
constructor(editor, model, faces = new Map(), imports = [], resources = new Map()) {
|
||||||
this.editor = editor;
|
this.editor = editor;
|
||||||
@@ -127,9 +153,10 @@ export class RichTextSession {
|
|||||||
this.selection();
|
this.selection();
|
||||||
this.entries[this.index].caret = [...this.caret];
|
this.entries[this.index].caret = [...this.caret];
|
||||||
}
|
}
|
||||||
record(change) {
|
record(before) {
|
||||||
|
const change = textChange(before, this.model.runs);
|
||||||
const entry = { change, typing: { ...this.typing }, caret: [...this.caret] }, old = this.entries[this.index];
|
const entry = { change, typing: { ...this.typing }, caret: [...this.caret] }, old = this.entries[this.index];
|
||||||
const changed = change && JSON.stringify(change.before) !== JSON.stringify(change.after);
|
const changed = change.before.length || change.after.length;
|
||||||
if (!changed && JSON.stringify(entry.typing) === JSON.stringify(old.typing)) return;
|
if (!changed && JSON.stringify(entry.typing) === JSON.stringify(old.typing)) return;
|
||||||
if (!changed) entry.change = null;
|
if (!changed) entry.change = null;
|
||||||
this.entries.splice(++this.index, Infinity, entry);
|
this.entries.splice(++this.index, Infinity, entry);
|
||||||
@@ -174,7 +201,7 @@ export class RichTextSession {
|
|||||||
if (this.editing.composing || this.editing.saving) return;
|
if (this.editing.composing || this.editing.saving) return;
|
||||||
this.remember();
|
this.remember();
|
||||||
const [start, end] = this.selection();
|
const [start, end] = this.selection();
|
||||||
const before = sliceRuns(this.model.runs, start, end);
|
const before = this.model.runs;
|
||||||
this.caret = this.caret[0] > this.caret[1] ? [end, start] : [start, end];
|
this.caret = this.caret[0] > this.caret[1] ? [end, start] : [start, end];
|
||||||
if (start !== end) this.model.runs = normalizeRuns([
|
if (start !== end) this.model.runs = normalizeRuns([
|
||||||
...sliceRuns(this.model.runs, 0, start),
|
...sliceRuns(this.model.runs, 0, start),
|
||||||
@@ -182,19 +209,19 @@ export class RichTextSession {
|
|||||||
...sliceRuns(this.model.runs, end, this.text.length),
|
...sliceRuns(this.model.runs, end, this.text.length),
|
||||||
]);
|
]);
|
||||||
this.typing = { ...this.typing, ...values };
|
this.typing = { ...this.typing, ...values };
|
||||||
this.record({ start, before, after: sliceRuns(this.model.runs, start, end) });
|
this.record(before);
|
||||||
this.render();
|
this.render();
|
||||||
this.editor.options.onTextChange();
|
this.editor.options.onTextChange();
|
||||||
}
|
}
|
||||||
replace(runs) {
|
replace(runs) {
|
||||||
this.remember();
|
this.remember();
|
||||||
const [start, end] = this.selection();
|
const [start, end] = this.selection();
|
||||||
const before = sliceRuns(this.model.runs, start, end);
|
const before = this.model.runs;
|
||||||
this.model.runs = normalizeRuns([...sliceRuns(this.model.runs, 0, start), ...runs, ...sliceRuns(this.model.runs, end, this.text.length)]);
|
this.model.runs = normalizeRuns([...sliceRuns(this.model.runs, 0, start), ...runs, ...sliceRuns(this.model.runs, end, this.text.length)]);
|
||||||
this.caret = [start + runs.reduce((length, run) => length + run.text.length, 0)];
|
this.caret = [start + runs.reduce((length, run) => length + run.text.length, 0)];
|
||||||
this.caret.push(this.caret[0]);
|
this.caret.push(this.caret[0]);
|
||||||
if (runs.length) this.typing = { ...runs.at(-1).format };
|
if (runs.length) this.typing = { ...runs.at(-1).format };
|
||||||
this.record({ start, before, after: sliceRuns(this.model.runs, start, this.caret[0]) });
|
this.record(before);
|
||||||
this.render();
|
this.render();
|
||||||
this.editor.options.onTextChange();
|
this.editor.options.onTextChange();
|
||||||
}
|
}
|
||||||
@@ -214,12 +241,12 @@ export class RichTextSession {
|
|||||||
while (end > start && nextEnd > start && previous[end - 1] === value[nextEnd - 1]) { end--; nextEnd--; }
|
while (end > start && nextEnd > start && previous[end - 1] === value[nextEnd - 1]) { end--; nextEnd--; }
|
||||||
if (end < previous.length && /[\uDC00-\uDFFF]/.test(previous[end])) { end++; nextEnd++; }
|
if (end < previous.length && /[\uDC00-\uDFFF]/.test(previous[end])) { end++; nextEnd++; }
|
||||||
}
|
}
|
||||||
const removed = sliceRuns(this.model.runs, start, end);
|
const runs = this.model.runs;
|
||||||
this.model.runs = normalizeRuns([...sliceRuns(this.model.runs, 0, start),
|
this.model.runs = normalizeRuns([...sliceRuns(this.model.runs, 0, start),
|
||||||
{ text: value.slice(start, nextEnd), format: { ...this.typing } }, ...sliceRuns(this.model.runs, end, previous.length)]);
|
{ text: value.slice(start, nextEnd), format: { ...this.typing } }, ...sliceRuns(this.model.runs, end, previous.length)]);
|
||||||
this.caret = caret;
|
this.caret = caret;
|
||||||
this.beforeCaret = null;
|
this.beforeCaret = null;
|
||||||
this.record({ start, before: removed, after: sliceRuns(this.model.runs, start, nextEnd) });
|
this.record(runs);
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
copy(event, cut = false) {
|
copy(event, cut = false) {
|
||||||
@@ -257,7 +284,7 @@ export class RichTextSession {
|
|||||||
if (this.editor.input !== this.editing || this.editing.composing || this.editing.saving || this.model.runs !== original || this.selection().join() !== caret.join()) return;
|
if (this.editor.input !== this.editing || this.editing.composing || this.editing.saving || this.model.runs !== original || this.selection().join() !== caret.join()) return;
|
||||||
}
|
}
|
||||||
for (const [key, data] of copy.fonts) this.imports.set(keys.get(key), data);
|
for (const [key, data] of copy.fonts) this.imports.set(keys.get(key), data);
|
||||||
for (const [key, face] of faces) { this.faces.set(keys.get(key), face); document.fonts.add(face); }
|
for (const [key, face] of faces) if (!this.faces.has(keys.get(key))) { this.faces.set(keys.get(key), face); document.fonts.add(face); }
|
||||||
for (const [key, choice] of copy.choices) this.choices.set(keys.get(key), choice);
|
for (const [key, choice] of copy.choices) this.choices.set(keys.get(key), choice);
|
||||||
runs = copy.runs.map(run => ({ text: run.text, format: { ...run.format, font: keys.get(run.format.font) } }));
|
runs = copy.runs.map(run => ({ text: run.text, format: { ...run.format, font: keys.get(run.format.font) } }));
|
||||||
}
|
}
|
||||||
@@ -1834,12 +1861,12 @@ export class CanvasEditor {
|
|||||||
session.model.runs = normalizeRuns([{ text: entry.value, format }]);
|
session.model.runs = normalizeRuns([{ text: entry.value, format }]);
|
||||||
session.typing = { ...format };
|
session.typing = { ...format };
|
||||||
session.caret = entry.caret || caret;
|
session.caret = entry.caret || caret;
|
||||||
session.record({ start: 0, before, after: session.model.runs });
|
session.record(before);
|
||||||
}
|
}
|
||||||
const before = session.model.runs;
|
const before = session.model.runs;
|
||||||
session.model.runs = normalizeRuns([{ text: value, format }]);
|
session.model.runs = normalizeRuns([{ text: value, format }]);
|
||||||
session.caret = caret;
|
session.caret = caret;
|
||||||
session.record({ start: 0, before, after: session.model.runs });
|
session.record(before);
|
||||||
editing.rich = session;
|
editing.rich = session;
|
||||||
editing.source = false;
|
editing.source = false;
|
||||||
document.removeEventListener("selectionchange", editing.selectionListener);
|
document.removeEventListener("selectionchange", editing.selectionListener);
|
||||||
|
|||||||
Reference in new issue
Block a user