mirror of
https://github.com/ankitects/anki.git
synced 2025-09-20 23:12:21 -04:00
Add a legacy switch to NoteEditor
This commit is contained in:
parent
a05f3dd38a
commit
63052e7fb9
11 changed files with 208 additions and 94 deletions
|
@ -146,7 +146,10 @@ fn build_css(build: &mut Build) -> Result<()> {
|
|||
},
|
||||
)?;
|
||||
}
|
||||
let other_ts_css = build.inputs_with_suffix(inputs![":ts:editor", ":ts:reviewer:reviewer.css"], ".css");
|
||||
let other_ts_css = build.inputs_with_suffix(
|
||||
inputs![":ts:editor", ":ts:editable", ":ts:reviewer:reviewer.css"],
|
||||
".css",
|
||||
);
|
||||
build.add_action(
|
||||
"qt:aqt:data:web:css",
|
||||
CopyFiles {
|
||||
|
@ -187,8 +190,15 @@ fn build_js(build: &mut Build) -> Result<()> {
|
|||
inputs: files,
|
||||
},
|
||||
)?;
|
||||
let files_from_ts =
|
||||
build.inputs_with_suffix(inputs![":ts:editor", ":ts:reviewer:reviewer.js", ":ts:mathjax"], ".js");
|
||||
let files_from_ts = build.inputs_with_suffix(
|
||||
inputs![
|
||||
":ts:editor",
|
||||
":ts:editable",
|
||||
":ts:reviewer:reviewer.js",
|
||||
":ts:mathjax"
|
||||
],
|
||||
".js",
|
||||
);
|
||||
build.add_action(
|
||||
"qt:aqt:data:web:js",
|
||||
CopyFiles {
|
||||
|
|
|
@ -203,16 +203,23 @@ fn build_and_check_pages(build: &mut Build) -> Result<()> {
|
|||
|
||||
Ok(())
|
||||
};
|
||||
// we use the generated .css file separately in the legacy editor
|
||||
build_page(
|
||||
"editable",
|
||||
false,
|
||||
inputs![
|
||||
":ts:lib",
|
||||
":ts:components",
|
||||
":ts:domlib",
|
||||
":ts:sveltelib",
|
||||
":sass",
|
||||
":sveltekit",
|
||||
],
|
||||
)?;
|
||||
build_page(
|
||||
"congrats",
|
||||
true,
|
||||
inputs![
|
||||
//
|
||||
":ts:lib",
|
||||
":ts:components",
|
||||
":sass",
|
||||
":sveltekit"
|
||||
],
|
||||
inputs![":ts:lib", ":ts:components", ":sass", ":sveltekit"],
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -91,4 +91,3 @@ class EditCurrent(QMainWindow):
|
|||
self.editor.call_after_note_saved(callback)
|
||||
|
||||
onReset = on_operation_did_execute
|
||||
onReset = on_operation_did_execute
|
||||
|
|
|
@ -187,7 +187,7 @@ class Editor:
|
|||
context=self,
|
||||
default_css=False,
|
||||
)
|
||||
self.web.eval(f"setupEditor('{mode}')")
|
||||
self.web.eval(f"setupEditor('{mode}', true)")
|
||||
self.web.show()
|
||||
|
||||
lefttopbtns: list[str] = []
|
||||
|
@ -563,9 +563,9 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
|
|||
if not self.note:
|
||||
return
|
||||
|
||||
data = [
|
||||
(fld, self.mw.col.media.escape_media_filenames(val))
|
||||
for fld, val in self.note.items()
|
||||
field_names = self.note.keys()
|
||||
field_values = [
|
||||
self.mw.col.media.escape_media_filenames(val) for val in self.note.values()
|
||||
]
|
||||
|
||||
note_type = self.note_type()
|
||||
|
@ -600,7 +600,7 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
|
|||
|
||||
js = f"""
|
||||
saveSession();
|
||||
setFields({json.dumps(data)});
|
||||
setFields({json.dumps(field_names)}, {json.dumps(field_values)});
|
||||
setIsImageOcclusion({json.dumps(self.current_notetype_is_image_occlusion())});
|
||||
setNotetypeMeta({json.dumps(notetype_meta)});
|
||||
setCollapsed({json.dumps(collapsed)});
|
||||
|
@ -1787,4 +1787,3 @@ gui_hooks.editor_will_use_font_for_field.append(fontMungeHack)
|
|||
gui_hooks.editor_will_munge_html.append(munge_html) # type: ignore
|
||||
gui_hooks.editor_will_munge_html.append(remove_null_bytes) # type: ignore
|
||||
gui_hooks.editor_will_munge_html.append(reverse_url_quoting) # type: ignore
|
||||
gui_hooks.editor_will_munge_html.append(reverse_url_quoting) # type: ignore
|
||||
|
|
|
@ -4,3 +4,6 @@
|
|||
import "./editable-base.scss";
|
||||
import "./content-editable.scss";
|
||||
import "./mathjax.scss";
|
||||
/* only imported for the CSS in the legacy editor */
|
||||
import "./ContentEditable.svelte";
|
||||
import "./Mathjax.svelte";
|
||||
|
|
|
@ -215,6 +215,12 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
}
|
||||
|
||||
async function toggleStickyAll() {
|
||||
if (isLegacy) {
|
||||
bridgeCommand(
|
||||
"toggleStickyAll",
|
||||
(values: boolean[]) => (stickies = values),
|
||||
);
|
||||
} else {
|
||||
const values: boolean[] = [];
|
||||
const notetype = await getNotetype({ ntid: notetypeMeta.id });
|
||||
const anySticky = notetype.fields.some((f) => f.config!.sticky);
|
||||
|
@ -228,6 +234,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
await updateEditorNotetype(notetype);
|
||||
setSticky(values);
|
||||
}
|
||||
}
|
||||
|
||||
let deregisterSticky: () => void;
|
||||
|
||||
|
@ -262,9 +269,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
await setMeta(tagsCollapsedMetaKey, collapsed);
|
||||
}
|
||||
|
||||
let note: Note;
|
||||
export function setNote(n: Note): void {
|
||||
note = n;
|
||||
function clearCodeMirrorHistory() {
|
||||
// TODO this is a hack, because it requires the NoteEditor to know implementation details of the PlainTextInput.
|
||||
// It should be refactored once we work on our own Undo stack
|
||||
for (const pi of plainTextInputs) {
|
||||
|
@ -272,6 +277,22 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
}
|
||||
}
|
||||
|
||||
let noteId: number | null = null;
|
||||
export function setNoteId(ntid: number): void {
|
||||
clearCodeMirrorHistory();
|
||||
noteId = ntid;
|
||||
}
|
||||
|
||||
function getNoteId(): number | null {
|
||||
return noteId;
|
||||
}
|
||||
|
||||
let note: Note;
|
||||
export function setNote(n: Note): void {
|
||||
note = n;
|
||||
clearCodeMirrorHistory();
|
||||
}
|
||||
|
||||
let notetypeMeta: NotetypeIdAndModTime;
|
||||
function setNotetypeMeta(notetype: Notetype): void {
|
||||
notetypeMeta = { id: notetype.id, modTime: notetype.mtimeSecs };
|
||||
|
@ -330,6 +351,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
|
||||
async function transformContentBeforeSave(content: string): Promise<string> {
|
||||
content = content.replace(/ data-editor-shrink="(true|false)"/g, "");
|
||||
if (!isLegacy) {
|
||||
// misbehaving apps may include a null byte in the text
|
||||
content = content.replaceAll("\0", "");
|
||||
// reverse the url quoting we added to get images to display
|
||||
|
@ -338,6 +360,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
if (["<br>", "<div><br></div>"].includes(content)) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
|
@ -352,10 +375,18 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
|
||||
async function updateField(index: number, content: string): Promise<void> {
|
||||
fieldSave.schedule(async () => {
|
||||
if (isLegacy) {
|
||||
bridgeCommand(
|
||||
`key:${index}:${getNoteId()}:${transformContentBeforeSave(
|
||||
content,
|
||||
)}`,
|
||||
);
|
||||
} else {
|
||||
bridgeCommand(`key:${index}`);
|
||||
note!.fields[index] = await transformContentBeforeSave(content);
|
||||
await updateCurrentNote();
|
||||
await updateDuplicateDisplay();
|
||||
}
|
||||
}, 600);
|
||||
}
|
||||
|
||||
|
@ -640,17 +671,25 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
|
||||
async function pickIOImage() {
|
||||
imageOcclusionMode = undefined;
|
||||
if (isLegacy) {
|
||||
bridgeCommand("addImageForOcclusion");
|
||||
} else {
|
||||
const filename = await openFilePickerForImageOcclusion();
|
||||
if (!filename) {
|
||||
return;
|
||||
}
|
||||
setupMaskEditor(filename);
|
||||
}
|
||||
}
|
||||
|
||||
async function pickIOImageFromClipboard() {
|
||||
imageOcclusionMode = undefined;
|
||||
if (isLegacy) {
|
||||
bridgeCommand("addImageForOcclusionFromClipboard");
|
||||
} else {
|
||||
await setupMaskEditorFromClipboard();
|
||||
}
|
||||
}
|
||||
|
||||
async function handlePickerDrop(event: DragEvent) {
|
||||
if ($editorState === EditorState.ImageOcclusionPicker) {
|
||||
|
@ -928,6 +967,19 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
await loadNote(note!.id, notetypeMeta.id, 0, null);
|
||||
}
|
||||
|
||||
function checkNonLegacy(value: any): any | undefined {
|
||||
if (isLegacy) {
|
||||
return value;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function preventDefaultIfNonLegacy(event: Event) {
|
||||
if (!isLegacy) {
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
$: signalEditorState($editorState);
|
||||
|
||||
$: $editorState = getEditorState(
|
||||
|
@ -976,6 +1028,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
saveNow,
|
||||
closeAddCards,
|
||||
focusIfField,
|
||||
getNoteId,
|
||||
setNoteId,
|
||||
setNotetypeMeta,
|
||||
wrap,
|
||||
setMathjaxEnabled,
|
||||
|
@ -1036,6 +1090,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
|
||||
export let uiResolve: (api: NoteEditorAPI) => void;
|
||||
export let mode: EditorMode;
|
||||
export let isLegacy: boolean;
|
||||
|
||||
$: if (noteEditor) {
|
||||
uiResolve(api as NoteEditorAPI);
|
||||
|
@ -1044,9 +1099,9 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
|
||||
<!-- Block Qt's default drag & drop behavior -->
|
||||
<svelte:body
|
||||
on:dragenter|preventDefault
|
||||
on:dragover|preventDefault
|
||||
on:drop|preventDefault
|
||||
on:dragenter={preventDefaultIfNonLegacy}
|
||||
on:dragover={preventDefaultIfNonLegacy}
|
||||
on:drop={preventDefaultIfNonLegacy}
|
||||
/>
|
||||
|
||||
<!--
|
||||
|
@ -1059,11 +1114,13 @@ components and functionality for general note editing.
|
|||
role="presentation"
|
||||
bind:this={noteEditor}
|
||||
on:contextmenu={(event) => {
|
||||
if (!isLegacy) {
|
||||
contextMenuInput = $focusedInput;
|
||||
onContextMenu(event, api, $focusedInput, contextMenu);
|
||||
}
|
||||
}}
|
||||
on:dragover|preventDefault
|
||||
on:drop={handlePickerDrop}
|
||||
on:dragover={preventDefaultIfNonLegacy}
|
||||
on:drop={checkNonLegacy(handlePickerDrop)}
|
||||
>
|
||||
<EditorToolbar {size} {wrap} api={toolbar}>
|
||||
<svelte:fragment slot="notetypeButtons">
|
||||
|
@ -1122,12 +1179,20 @@ components and functionality for general note editing.
|
|||
on:focusout={async () => {
|
||||
$focusedField = null;
|
||||
setAddonButtonsDisabled(true);
|
||||
if (isLegacy) {
|
||||
bridgeCommand(
|
||||
`blur:${index}:${getNoteId()}:${transformContentBeforeSave(
|
||||
get(content),
|
||||
)}`,
|
||||
);
|
||||
} else {
|
||||
bridgeCommand(`blur:${index}`);
|
||||
note!.fields[index] = await transformContentBeforeSave(
|
||||
get(content),
|
||||
);
|
||||
await updateCurrentNote();
|
||||
await updateDuplicateDisplay();
|
||||
}
|
||||
}}
|
||||
on:mouseenter={() => {
|
||||
$hoveredField = fields[index];
|
||||
|
@ -1160,6 +1225,7 @@ components and functionality for general note editing.
|
|||
bind:active={stickies[index]}
|
||||
{index}
|
||||
{note}
|
||||
{isLegacy}
|
||||
show={fields[index] === $hoveredField ||
|
||||
fields[index] === $focusedField}
|
||||
/>
|
||||
|
@ -1192,6 +1258,7 @@ components and functionality for general note editing.
|
|||
>
|
||||
<RichTextInput
|
||||
{hidden}
|
||||
{isLegacy}
|
||||
on:focusout={() => {
|
||||
saveFieldNow();
|
||||
$focusedInput = null;
|
||||
|
@ -1241,9 +1308,12 @@ components and functionality for general note editing.
|
|||
<Collapsible toggleDisplay collapse={$tagsCollapsed}>
|
||||
<TagEditor {tags} on:tagsupdate={saveTags} />
|
||||
</Collapsible>
|
||||
{#if !isLegacy}
|
||||
<ActionButtons {mode} {onClose} {onAdd} />
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
{#if !isLegacy}
|
||||
<ContextMenu bind:this={contextMenu}>
|
||||
{#each contextMenuItems as item}
|
||||
<Item
|
||||
|
@ -1256,6 +1326,7 @@ components and functionality for general note editing.
|
|||
</Item>
|
||||
{/each}
|
||||
</ContextMenu>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
|
|
|
@ -16,11 +16,13 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
import { context as editorFieldContext } from "./EditorField.svelte";
|
||||
import type { Note } from "@generated/anki/notes_pb";
|
||||
import { getNotetype, updateEditorNotetype } from "@generated/backend";
|
||||
import { bridgeCommand } from "@tslib/bridgecommand";
|
||||
|
||||
const animated = !document.body.classList.contains("reduce-motion");
|
||||
|
||||
export let active: boolean;
|
||||
export let show: boolean;
|
||||
export let isLegacy: boolean;
|
||||
|
||||
const editorField = editorFieldContext.get();
|
||||
const keyCombination = "F9";
|
||||
|
@ -29,11 +31,17 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
export let note: Note;
|
||||
|
||||
async function toggle() {
|
||||
if (isLegacy) {
|
||||
bridgeCommand(`toggleSticky:${index}`, (value: boolean) => {
|
||||
active = value;
|
||||
});
|
||||
} else {
|
||||
active = !active;
|
||||
const notetype = await getNotetype({ ntid: note.notetypeId });
|
||||
notetype.fields[index].config!.sticky = active;
|
||||
await updateEditorNotetype(notetype);
|
||||
}
|
||||
}
|
||||
|
||||
function shortcut(target: HTMLElement): () => void {
|
||||
return registerShortcut(toggle, keyCombination, { target });
|
||||
|
|
|
@ -53,11 +53,11 @@ export const components = {
|
|||
|
||||
export { editorToolbar } from "./editor-toolbar";
|
||||
|
||||
export async function setupEditor(mode: EditorMode) {
|
||||
export async function setupEditor(mode: EditorMode, isLegacy = false) {
|
||||
if (!["add", "browser", "current"].includes(mode)) {
|
||||
alert("unexpected editor type");
|
||||
return;
|
||||
}
|
||||
await setupI18n({ modules: editorModules });
|
||||
mount(NoteEditor, { target: document.body, props: { uiResolve, mode } });
|
||||
mount(NoteEditor, { target: document.body, props: { uiResolve, mode, isLegacy } });
|
||||
}
|
||||
|
|
|
@ -37,6 +37,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
Object.assign(api, {
|
||||
setColorButtons,
|
||||
});
|
||||
// For legacy editor
|
||||
Object.assign(globalThis, { setColorButtons });
|
||||
</script>
|
||||
|
||||
<DynamicallySlottable slotHost={Item} {api}>
|
||||
|
|
|
@ -84,6 +84,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
import { fragmentToStored, storedToFragment } from "./transform";
|
||||
|
||||
export let hidden = false;
|
||||
export let isLegacy = false;
|
||||
export const focusFlag = new Flag();
|
||||
export let isClozeField: boolean;
|
||||
|
||||
|
@ -231,6 +232,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
|
||||
<div class="rich-text-input" on:focusin={setFocus} on:focusout={removeFocus} {hidden}>
|
||||
<RichTextStyles
|
||||
{isLegacy}
|
||||
color={$pageTheme.isDark ? "white" : "black"}
|
||||
fontFamily={$fontFamily}
|
||||
fontSize={$fontSize}
|
||||
|
|
|
@ -13,6 +13,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
|
||||
import { mount } from "svelte";
|
||||
|
||||
export let isLegacy = false;
|
||||
|
||||
export let callback: (styles: Record<string, any>) => void;
|
||||
|
||||
const [userBaseStyle, userBaseResolve] = promiseWithResolver<StyleObject>();
|
||||
|
@ -47,7 +49,17 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
$: setStyling("fontSize", fontSize + "px");
|
||||
$: setStyling("direction", direction);
|
||||
|
||||
const styles: StyleLinkType[] = [
|
||||
let styles: StyleLinkType[];
|
||||
if (isLegacy) {
|
||||
styles = [
|
||||
{
|
||||
id: "rootStyle",
|
||||
type: "link",
|
||||
href: "./_anki/css/editable.css",
|
||||
},
|
||||
];
|
||||
} else {
|
||||
styles = [
|
||||
{
|
||||
id: "editableBaseStyle",
|
||||
type: "link",
|
||||
|
@ -64,6 +76,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
href: mathjaxCSS,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function attachToShadow(element: Element) {
|
||||
const customStyles = mount(CustomStyles, {
|
||||
|
|
Loading…
Reference in a new issue