mirror of
https://github.com/ankitects/anki.git
synced 2025-09-24 16:56:36 -04:00
Merge remote-tracking branch 'upstream/HEAD' into text-import-export
This commit is contained in:
commit
6d93a578f3
52 changed files with 544 additions and 562 deletions
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
|
@ -1,7 +1,9 @@
|
||||||
{
|
{
|
||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
|
"[python]": {
|
||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
"source.organizeImports": true
|
"source.organizeImports": true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"files.watcherExclude": {
|
"files.watcherExclude": {
|
||||||
"**/.git/objects/**": true,
|
"**/.git/objects/**": true,
|
||||||
|
|
|
@ -15,6 +15,7 @@ editing-customize-card-templates = Customize Card Templates
|
||||||
editing-customize-fields = Customize Fields
|
editing-customize-fields = Customize Fields
|
||||||
editing-cut = Cut
|
editing-cut = Cut
|
||||||
editing-double-click-image = double-click image
|
editing-double-click-image = double-click image
|
||||||
|
editing-double-click-to-expand = (double-click to expand)
|
||||||
editing-edit-current = Edit Current
|
editing-edit-current = Edit Current
|
||||||
editing-edit-html = Edit HTML
|
editing-edit-html = Edit HTML
|
||||||
editing-fields = Fields
|
editing-fields = Fields
|
||||||
|
@ -38,6 +39,7 @@ editing-outdent = Decrease indent
|
||||||
editing-paste = Paste
|
editing-paste = Paste
|
||||||
editing-record-audio = Record audio
|
editing-record-audio = Record audio
|
||||||
editing-remove-formatting = Remove formatting
|
editing-remove-formatting = Remove formatting
|
||||||
|
editing-restore-original-size = Restore original size
|
||||||
editing-select-remove-formatting = Select formatting to remove
|
editing-select-remove-formatting = Select formatting to remove
|
||||||
editing-show-duplicates = Show Duplicates
|
editing-show-duplicates = Show Duplicates
|
||||||
editing-subscript = Subscript
|
editing-subscript = Subscript
|
||||||
|
|
|
@ -76,7 +76,6 @@
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"marked": "^4.0.0",
|
"marked": "^4.0.0",
|
||||||
"mathjax": "^3.1.2",
|
"mathjax": "^3.1.2",
|
||||||
"mathjax-full": "^3.2.0",
|
|
||||||
"protobufjs": "^6.10.2"
|
"protobufjs": "^6.10.2"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
|
|
|
@ -41,13 +41,21 @@ copy_files_into_group(
|
||||||
package = "//ts/reviewer",
|
package = "//ts/reviewer",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
copy_files_into_group(
|
||||||
|
name = "mathjax",
|
||||||
|
srcs = [
|
||||||
|
"mathjax.js",
|
||||||
|
],
|
||||||
|
package = "//ts/mathjax",
|
||||||
|
)
|
||||||
|
|
||||||
filegroup(
|
filegroup(
|
||||||
name = "js",
|
name = "js",
|
||||||
srcs = [
|
srcs = [
|
||||||
"aqt",
|
"aqt",
|
||||||
"mathjax.js",
|
|
||||||
"reviewer",
|
"reviewer",
|
||||||
"editor",
|
"editor",
|
||||||
|
"mathjax",
|
||||||
"//qt/aqt/data/web/js/vendor",
|
"//qt/aqt/data/web/js/vendor",
|
||||||
],
|
],
|
||||||
visibility = ["//qt:__subpackages__"],
|
visibility = ["//qt:__subpackages__"],
|
||||||
|
@ -68,6 +76,5 @@ eslint_test(
|
||||||
)
|
)
|
||||||
|
|
||||||
exports_files([
|
exports_files([
|
||||||
"mathjax.js",
|
|
||||||
"tsconfig.json",
|
"tsconfig.json",
|
||||||
])
|
])
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
window.MathJax = {
|
|
||||||
tex: {
|
|
||||||
displayMath: [["\\[", "\\]"]],
|
|
||||||
processRefs: false,
|
|
||||||
processEnvironments: false,
|
|
||||||
processEscapes: false,
|
|
||||||
packages: {
|
|
||||||
"[+]": ["noerrors", "mhchem"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
startup: {
|
|
||||||
typeset: false,
|
|
||||||
pageReady: () => {
|
|
||||||
return MathJax.startup.defaultPageReady();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
renderActions: {
|
|
||||||
addMenu: [],
|
|
||||||
checkLoading: [],
|
|
||||||
},
|
|
||||||
ignoreHtmlClass: "tex2jax_ignore",
|
|
||||||
processHtmlClass: "tex2jax_process",
|
|
||||||
},
|
|
||||||
loader: {
|
|
||||||
load: ["[tex]/noerrors", "[tex]/mhchem"],
|
|
||||||
},
|
|
||||||
};
|
|
|
@ -155,7 +155,10 @@ class Editor:
|
||||||
self.web.stdHtml(
|
self.web.stdHtml(
|
||||||
"",
|
"",
|
||||||
css=[f"css/{file}.css"],
|
css=[f"css/{file}.css"],
|
||||||
js=[f"js/{file}.js"],
|
js=[
|
||||||
|
"js/mathjax.js",
|
||||||
|
f"js/{file}.js",
|
||||||
|
],
|
||||||
context=self,
|
context=self,
|
||||||
default_css=False,
|
default_css=False,
|
||||||
)
|
)
|
||||||
|
|
|
@ -149,6 +149,14 @@ class AVPlayer:
|
||||||
self._enqueued = tags[:]
|
self._enqueued = tags[:]
|
||||||
self._play_next_if_idle()
|
self._play_next_if_idle()
|
||||||
|
|
||||||
|
def append_tags(self, tags: list[AVTag]) -> None:
|
||||||
|
"""Append provided tags to the queue, then start playing them if the current player is idle."""
|
||||||
|
self._enqueued.extend(tags)
|
||||||
|
self._play_next_if_idle()
|
||||||
|
|
||||||
|
def queue_is_empty(self) -> bool:
|
||||||
|
return bool(self._enqueued)
|
||||||
|
|
||||||
def stop_and_clear_queue(self) -> None:
|
def stop_and_clear_queue(self) -> None:
|
||||||
self._enqueued = []
|
self._enqueued = []
|
||||||
self._stop_if_playing()
|
self._stop_if_playing()
|
||||||
|
|
|
@ -21,8 +21,8 @@ _ts_deps = [
|
||||||
"//ts/lib",
|
"//ts/lib",
|
||||||
"//ts/domlib",
|
"//ts/domlib",
|
||||||
"//ts/sveltelib",
|
"//ts/sveltelib",
|
||||||
|
"//ts/mathjax:mathjax_ts",
|
||||||
"@npm//mathjax",
|
"@npm//mathjax",
|
||||||
"@npm//mathjax-full",
|
|
||||||
"@npm//svelte",
|
"@npm//svelte",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
11
ts/editable/ResizableImage.svelte
Normal file
11
ts/editable/ResizableImage.svelte
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<!--
|
||||||
|
Copyright: Ankitects Pty Ltd and contributors
|
||||||
|
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
-->
|
||||||
|
<style lang="scss">
|
||||||
|
:global([data-editor-shrink]:not([data-editor-shrink="false"])) {
|
||||||
|
max-width: var(--editor-shrink-max-width);
|
||||||
|
max-height: var(--editor-shrink-max-height);
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -5,3 +5,4 @@ import "./editable-base.css";
|
||||||
/* only imported for the CSS */
|
/* only imported for the CSS */
|
||||||
import "./ContentEditable.svelte";
|
import "./ContentEditable.svelte";
|
||||||
import "./Mathjax.svelte";
|
import "./Mathjax.svelte";
|
||||||
|
import "./ResizableImage.svelte";
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
// Copyright: Ankitects Pty Ltd and contributors
|
// Copyright: Ankitects Pty Ltd and contributors
|
||||||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
import "mathjax/es5/tex-svg-full";
|
|
||||||
|
|
||||||
import { placeCaretAfter, placeCaretBefore } from "../domlib/place-caret";
|
import { placeCaretAfter, placeCaretBefore } from "../domlib/place-caret";
|
||||||
import { on } from "../lib/events";
|
import { on } from "../lib/events";
|
||||||
import type { DecoratedElement, DecoratedElementConstructor } from "./decorated";
|
import type { DecoratedElement, DecoratedElementConstructor } from "./decorated";
|
||||||
|
@ -15,6 +13,13 @@ const mathjaxTagPattern =
|
||||||
const mathjaxBlockDelimiterPattern = /\\\[(.*?)\\\]/gsu;
|
const mathjaxBlockDelimiterPattern = /\\\[(.*?)\\\]/gsu;
|
||||||
const mathjaxInlineDelimiterPattern = /\\\((.*?)\\\)/gsu;
|
const mathjaxInlineDelimiterPattern = /\\\((.*?)\\\)/gsu;
|
||||||
|
|
||||||
|
function trimBreaks(text: string): string {
|
||||||
|
return text
|
||||||
|
.replace(/<br[ ]*\/?>/gsu, "\n")
|
||||||
|
.replace(/^\n*/, "")
|
||||||
|
.replace(/\n*$/, "");
|
||||||
|
}
|
||||||
|
|
||||||
export const Mathjax: DecoratedElementConstructor = class Mathjax
|
export const Mathjax: DecoratedElementConstructor = class Mathjax
|
||||||
extends HTMLElement
|
extends HTMLElement
|
||||||
implements DecoratedElement
|
implements DecoratedElement
|
||||||
|
@ -25,9 +30,10 @@ export const Mathjax: DecoratedElementConstructor = class Mathjax
|
||||||
const stored = undecorated.replace(
|
const stored = undecorated.replace(
|
||||||
mathjaxTagPattern,
|
mathjaxTagPattern,
|
||||||
(_match: string, block: string | undefined, text: string) => {
|
(_match: string, block: string | undefined, text: string) => {
|
||||||
|
const trimmed = trimBreaks(text);
|
||||||
return typeof block === "string" && block !== "false"
|
return typeof block === "string" && block !== "false"
|
||||||
? `\\[${text}\\]`
|
? `\\[${trimmed}\\]`
|
||||||
: `\\(${text}\\)`;
|
: `\\(${trimmed}\\)`;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -37,10 +43,12 @@ export const Mathjax: DecoratedElementConstructor = class Mathjax
|
||||||
static toUndecorated(stored: string): string {
|
static toUndecorated(stored: string): string {
|
||||||
return stored
|
return stored
|
||||||
.replace(mathjaxBlockDelimiterPattern, (_match: string, text: string) => {
|
.replace(mathjaxBlockDelimiterPattern, (_match: string, text: string) => {
|
||||||
return `<${Mathjax.tagName} block="true">${text}</${Mathjax.tagName}>`;
|
const trimmed = trimBreaks(text);
|
||||||
|
return `<${Mathjax.tagName} block="true">${trimmed}</${Mathjax.tagName}>`;
|
||||||
})
|
})
|
||||||
.replace(mathjaxInlineDelimiterPattern, (_match: string, text: string) => {
|
.replace(mathjaxInlineDelimiterPattern, (_match: string, text: string) => {
|
||||||
return `<${Mathjax.tagName}>${text}</${Mathjax.tagName}>`;
|
const trimmed = trimBreaks(text);
|
||||||
|
return `<${Mathjax.tagName}>${trimmed}</${Mathjax.tagName}>`;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,8 @@
|
||||||
@typescript-eslint/no-explicit-any: "off",
|
@typescript-eslint/no-explicit-any: "off",
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import "mathjax/es5/tex-svg";
|
||||||
|
|
||||||
import { mathIcon } from "./icons";
|
import { mathIcon } from "./icons";
|
||||||
|
|
||||||
const parser = new DOMParser();
|
const parser = new DOMParser();
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
{ "path": "../components" },
|
{ "path": "../components" },
|
||||||
{ "path": "../lib" },
|
{ "path": "../lib" },
|
||||||
{ "path": "../domlib" },
|
{ "path": "../domlib" },
|
||||||
{ "path": "../sveltelib" }
|
{ "path": "../sveltelib" },
|
||||||
|
{ "path": "../mathjax" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,19 +3,18 @@ Copyright: Ankitects Pty Ltd and contributors
|
||||||
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { createEventDispatcher } from "svelte";
|
||||||
import { get } from "svelte/store";
|
import { get } from "svelte/store";
|
||||||
|
|
||||||
import ButtonGroup from "../../components/ButtonGroup.svelte";
|
import ButtonGroup from "../components/ButtonGroup.svelte";
|
||||||
import IconButton from "../../components/IconButton.svelte";
|
import IconButton from "../components/IconButton.svelte";
|
||||||
import Shortcut from "../../components/Shortcut.svelte";
|
import Shortcut from "../components/Shortcut.svelte";
|
||||||
import * as tr from "../../lib/ftl";
|
import * as tr from "../lib/ftl";
|
||||||
import { isApplePlatform } from "../../lib/platform";
|
import { isApplePlatform } from "../lib/platform";
|
||||||
import { getPlatformString } from "../../lib/shortcuts";
|
import { getPlatformString } from "../lib/shortcuts";
|
||||||
import { wrapInternal } from "../../lib/wrap";
|
|
||||||
import { context as noteEditorContext } from "../NoteEditor.svelte";
|
|
||||||
import type { RichTextInputAPI } from "../rich-text-input";
|
|
||||||
import { editingInputIsRichText } from "../rich-text-input";
|
|
||||||
import { clozeIcon, incrementClozeIcon } from "./icons";
|
import { clozeIcon, incrementClozeIcon } from "./icons";
|
||||||
|
import { context as noteEditorContext } from "./NoteEditor.svelte";
|
||||||
|
import { editingInputIsRichText } from "./rich-text-input";
|
||||||
|
|
||||||
const { focusedInput, fields } = noteEditorContext.get();
|
const { focusedInput, fields } = noteEditorContext.get();
|
||||||
|
|
||||||
|
@ -48,20 +47,24 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
return Math.max(1, highest);
|
return Math.max(1, highest);
|
||||||
}
|
}
|
||||||
|
|
||||||
$: richTextAPI = $focusedInput as RichTextInputAPI;
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
async function onIncrementCloze(): Promise<void> {
|
async function onIncrementCloze(): Promise<void> {
|
||||||
const richText = await richTextAPI.element;
|
|
||||||
|
|
||||||
const highestCloze = getCurrentHighestCloze(true);
|
const highestCloze = getCurrentHighestCloze(true);
|
||||||
wrapInternal(richText, `{{c${highestCloze}::`, "}}", false);
|
|
||||||
|
dispatch("surround", {
|
||||||
|
prefix: `{{c${highestCloze}::`,
|
||||||
|
suffix: "}}",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onSameCloze(): Promise<void> {
|
async function onSameCloze(): Promise<void> {
|
||||||
const richText = await richTextAPI.element;
|
|
||||||
|
|
||||||
const highestCloze = getCurrentHighestCloze(false);
|
const highestCloze = getCurrentHighestCloze(false);
|
||||||
wrapInternal(richText, `{{c${highestCloze}::`, "}}", false);
|
|
||||||
|
dispatch("surround", {
|
||||||
|
prefix: `{{c${highestCloze}::`,
|
||||||
|
suffix: "}}",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
$: disabled = !editingInputIsRichText($focusedInput);
|
$: disabled = !editingInputIsRichText($focusedInput);
|
|
@ -7,7 +7,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
import contextProperty from "../sveltelib/context-property";
|
import contextProperty from "../sveltelib/context-property";
|
||||||
|
|
||||||
export interface EditingInputAPI {
|
export interface FocusableInputAPI {
|
||||||
readonly name: string;
|
readonly name: string;
|
||||||
focusable: boolean;
|
focusable: boolean;
|
||||||
/**
|
/**
|
||||||
|
@ -22,6 +22,16 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
refocus(): void;
|
refocus(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface EditingInputAPI extends FocusableInputAPI {
|
||||||
|
/**
|
||||||
|
* Check whether blurred target belongs to an editing input.
|
||||||
|
* The editing area can then restore focus to this input.
|
||||||
|
*
|
||||||
|
* @returns An editing input api that is associated with the event target.
|
||||||
|
*/
|
||||||
|
getInputAPI(target: EventTarget): Promise<FocusableInputAPI | null>;
|
||||||
|
}
|
||||||
|
|
||||||
export interface EditingAreaAPI {
|
export interface EditingAreaAPI {
|
||||||
content: Writable<string>;
|
content: Writable<string>;
|
||||||
editingInputs: Writable<EditingInputAPI[]>;
|
editingInputs: Writable<EditingInputAPI[]>;
|
||||||
|
@ -36,7 +46,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { setContext as svelteSetContext } from "svelte";
|
import { setContext as svelteSetContext, tick } from "svelte";
|
||||||
import { writable } from "svelte/store";
|
import { writable } from "svelte/store";
|
||||||
|
|
||||||
import { fontFamilyKey, fontSizeKey } from "../lib/context-keys";
|
import { fontFamilyKey, fontSizeKey } from "../lib/context-keys";
|
||||||
|
@ -116,12 +126,39 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// prevents editor field being entirely deselected when
|
// Prevents editor field being entirely deselected when
|
||||||
// closing active field
|
// closing active field.
|
||||||
function trapFocusOnBlurOut(event: FocusEvent): void {
|
async function trapFocusOnBlurOut(event: FocusEvent): Promise<void> {
|
||||||
if (!event.relatedTarget && editingInputs.every((input) => !input.focusable)) {
|
if (event.relatedTarget) {
|
||||||
focusTrap.focus();
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
|
const oldInputElement = event.target;
|
||||||
|
|
||||||
|
await tick();
|
||||||
|
|
||||||
|
let focusableInput: FocusableInputAPI | null = null;
|
||||||
|
|
||||||
|
const focusableInputs = editingInputs.filter(
|
||||||
|
(input: EditingInputAPI): boolean => input.focusable,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (oldInputElement) {
|
||||||
|
for (const input of focusableInputs) {
|
||||||
|
focusableInput = await input.getInputAPI(oldInputElement);
|
||||||
|
|
||||||
|
if (focusableInput) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (focusableInput || (focusableInput = focusableInputs[0])) {
|
||||||
|
focusableInput.focus();
|
||||||
|
} else {
|
||||||
|
focusTrap.focus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,53 +4,32 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getContext } from "svelte";
|
import { getContext } from "svelte";
|
||||||
import { afterUpdate, createEventDispatcher, onMount } from "svelte";
|
import { createEventDispatcher, onMount } from "svelte";
|
||||||
import type { Readable } from "svelte/store";
|
import type { Readable } from "svelte/store";
|
||||||
|
|
||||||
import { directionKey } from "../lib/context-keys";
|
import { directionKey } from "../lib/context-keys";
|
||||||
|
|
||||||
let dimensions: HTMLDivElement;
|
|
||||||
let overflowFix = 0;
|
|
||||||
|
|
||||||
const direction = getContext<Readable<"ltr" | "rtl">>(directionKey);
|
const direction = getContext<Readable<"ltr" | "rtl">>(directionKey);
|
||||||
|
|
||||||
function updateOverflow(dimensions: HTMLDivElement): void {
|
|
||||||
const boundingClientRect = dimensions.getBoundingClientRect();
|
|
||||||
const overflow =
|
|
||||||
$direction === "ltr"
|
|
||||||
? boundingClientRect.x
|
|
||||||
: window.innerWidth - boundingClientRect.x - boundingClientRect.width;
|
|
||||||
|
|
||||||
overflowFix = Math.min(0, overflowFix + overflow, overflow);
|
|
||||||
}
|
|
||||||
|
|
||||||
afterUpdate(() => updateOverflow(dimensions));
|
|
||||||
|
|
||||||
function updateOverflowAsync(dimensions: HTMLDivElement): void {
|
|
||||||
setTimeout(() => updateOverflow(dimensions));
|
|
||||||
}
|
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
onMount(() => dispatch("mount"));
|
onMount(() => dispatch("mount"));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div class="image-handle-dimensions" class:is-rtl={$direction === "rtl"}>
|
||||||
bind:this={dimensions}
|
|
||||||
class="image-handle-dimensions"
|
|
||||||
class:is-rtl={$direction === "rtl"}
|
|
||||||
style="--overflow-fix: {overflowFix}px"
|
|
||||||
use:updateOverflowAsync
|
|
||||||
>
|
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
div {
|
div {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
width: fit-content;
|
||||||
|
|
||||||
pointer-events: none;
|
left: 0;
|
||||||
user-select: none;
|
right: 0;
|
||||||
|
bottom: 3px;
|
||||||
|
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: white;
|
color: white;
|
||||||
|
@ -59,16 +38,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
padding: 0 5px;
|
padding: 0 5px;
|
||||||
|
|
||||||
bottom: 3px;
|
pointer-events: none;
|
||||||
right: 3px;
|
user-select: none;
|
||||||
margin-left: 3px;
|
|
||||||
margin-right: var(--overflow-fix, 0);
|
|
||||||
|
|
||||||
&.is-rtl {
|
|
||||||
right: auto;
|
|
||||||
left: 3px;
|
|
||||||
margin-right: 3px;
|
|
||||||
margin-left: var(--overflow-fix, 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -324,8 +324,26 @@ the AddCards dialog) should be implemented in the user of this component.
|
||||||
{#if cols[index] === "dupe"}
|
{#if cols[index] === "dupe"}
|
||||||
<DuplicateLink />
|
<DuplicateLink />
|
||||||
{/if}
|
{/if}
|
||||||
<RichTextBadge bind:off={richTextsHidden[index]} />
|
<RichTextBadge
|
||||||
<PlainTextBadge bind:off={plainTextsHidden[index]} />
|
bind:off={richTextsHidden[index]}
|
||||||
|
on:toggle={() => {
|
||||||
|
richTextsHidden[index] = !richTextsHidden[index];
|
||||||
|
|
||||||
|
if (!richTextsHidden[index]) {
|
||||||
|
richTextInputs[index].api.refocus();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<PlainTextBadge
|
||||||
|
bind:off={plainTextsHidden[index]}
|
||||||
|
on:toggle={() => {
|
||||||
|
plainTextsHidden[index] = !plainTextsHidden[index];
|
||||||
|
|
||||||
|
if (!plainTextsHidden[index]) {
|
||||||
|
plainTextInputs[index].api.refocus();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
<slot name="field-state" {field} {index} />
|
<slot name="field-state" {field} {index} />
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
|
@ -339,7 +357,7 @@ the AddCards dialog) should be implemented in the user of this component.
|
||||||
}}
|
}}
|
||||||
bind:this={richTextInputs[index]}
|
bind:this={richTextInputs[index]}
|
||||||
>
|
>
|
||||||
<ImageHandle />
|
<ImageHandle maxWidth={250} maxHeight={125} />
|
||||||
<MathjaxHandle />
|
<MathjaxHandle />
|
||||||
</RichTextInput>
|
</RichTextInput>
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ Copyright: Ankitects Pty Ltd and contributors
|
||||||
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from "svelte";
|
import { createEventDispatcher, onMount } from "svelte";
|
||||||
|
|
||||||
import Badge from "../components/Badge.svelte";
|
import Badge from "../components/Badge.svelte";
|
||||||
import * as tr from "../lib/ftl";
|
import * as tr from "../lib/ftl";
|
||||||
|
@ -13,13 +13,14 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
const editorField = editorFieldContext.get();
|
const editorField = editorFieldContext.get();
|
||||||
const keyCombination = "Control+Shift+X";
|
const keyCombination = "Control+Shift+X";
|
||||||
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
export let off = false;
|
export let off = false;
|
||||||
|
|
||||||
$: icon = off ? htmlOff : htmlOn;
|
$: icon = off ? htmlOff : htmlOn;
|
||||||
|
|
||||||
function toggle() {
|
function toggle() {
|
||||||
off = !off;
|
dispatch("toggle");
|
||||||
}
|
}
|
||||||
|
|
||||||
function shortcut(target: HTMLElement): () => void {
|
function shortcut(target: HTMLElement): () => void {
|
||||||
|
|
|
@ -3,14 +3,18 @@ Copyright: Ankitects Pty Ltd and contributors
|
||||||
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { createEventDispatcher } from "svelte";
|
||||||
|
|
||||||
import Badge from "../components/Badge.svelte";
|
import Badge from "../components/Badge.svelte";
|
||||||
import * as tr from "../lib/ftl";
|
import * as tr from "../lib/ftl";
|
||||||
import { richTextOff, richTextOn } from "./icons";
|
import { richTextOff, richTextOn } from "./icons";
|
||||||
|
|
||||||
export let off: boolean;
|
export let off: boolean;
|
||||||
|
|
||||||
function toggle(): void {
|
const dispatch = createEventDispatcher();
|
||||||
off = !off;
|
|
||||||
|
function toggle() {
|
||||||
|
dispatch("toggle");
|
||||||
}
|
}
|
||||||
|
|
||||||
$: icon = off ? richTextOff : richTextOn;
|
$: icon = off ? richTextOff : richTextOn;
|
||||||
|
|
|
@ -9,9 +9,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
import type { MatchType } from "../../domlib/surround";
|
import type { MatchType } from "../../domlib/surround";
|
||||||
import * as tr from "../../lib/ftl";
|
import * as tr from "../../lib/ftl";
|
||||||
import { getPlatformString } from "../../lib/shortcuts";
|
import { getPlatformString } from "../../lib/shortcuts";
|
||||||
|
import { removeStyleProperties } from "../../lib/styling";
|
||||||
import { context as noteEditorContext } from "../NoteEditor.svelte";
|
import { context as noteEditorContext } from "../NoteEditor.svelte";
|
||||||
import { editingInputIsRichText } from "../rich-text-input";
|
import { editingInputIsRichText } from "../rich-text-input";
|
||||||
import { removeEmptyStyle, Surrounder } from "../surround";
|
import { Surrounder } from "../surround";
|
||||||
import { context as editorToolbarContext } from "./EditorToolbar.svelte";
|
import { context as editorToolbarContext } from "./EditorToolbar.svelte";
|
||||||
import { boldIcon } from "./icons";
|
import { boldIcon } from "./icons";
|
||||||
|
|
||||||
|
@ -25,9 +26,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
const fontWeight = element.style.fontWeight;
|
const fontWeight = element.style.fontWeight;
|
||||||
if (fontWeight === "bold" || Number(fontWeight) >= 400) {
|
if (fontWeight === "bold" || Number(fontWeight) >= 400) {
|
||||||
return match.clear((): void => {
|
return match.clear((): void => {
|
||||||
element.style.removeProperty("font-weight");
|
if (
|
||||||
|
removeStyleProperties(element, "font-weight") &&
|
||||||
if (removeEmptyStyle(element) && element.className.length === 0) {
|
element.className.length === 0
|
||||||
|
) {
|
||||||
match.remove();
|
match.remove();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -56,9 +56,9 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
import Item from "../../components/Item.svelte";
|
import Item from "../../components/Item.svelte";
|
||||||
import StickyContainer from "../../components/StickyContainer.svelte";
|
import StickyContainer from "../../components/StickyContainer.svelte";
|
||||||
import BlockButtons from "./BlockButtons.svelte";
|
import BlockButtons from "./BlockButtons.svelte";
|
||||||
import ClozeButtons from "./ClozeButtons.svelte";
|
|
||||||
import InlineButtons from "./InlineButtons.svelte";
|
import InlineButtons from "./InlineButtons.svelte";
|
||||||
import NotetypeButtons from "./NotetypeButtons.svelte";
|
import NotetypeButtons from "./NotetypeButtons.svelte";
|
||||||
|
import RichTextClozeButtons from "./RichTextClozeButtons.svelte";
|
||||||
import TemplateButtons from "./TemplateButtons.svelte";
|
import TemplateButtons from "./TemplateButtons.svelte";
|
||||||
|
|
||||||
export let size: number;
|
export let size: number;
|
||||||
|
@ -108,7 +108,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
</Item>
|
</Item>
|
||||||
|
|
||||||
<Item id="cloze">
|
<Item id="cloze">
|
||||||
<ClozeButtons />
|
<RichTextClozeButtons />
|
||||||
</Item>
|
</Item>
|
||||||
</DynamicallySlottable>
|
</DynamicallySlottable>
|
||||||
</ButtonToolbar>
|
</ButtonToolbar>
|
||||||
|
|
|
@ -11,9 +11,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
} from "../../domlib/surround";
|
} from "../../domlib/surround";
|
||||||
import { bridgeCommand } from "../../lib/bridgecommand";
|
import { bridgeCommand } from "../../lib/bridgecommand";
|
||||||
import * as tr from "../../lib/ftl";
|
import * as tr from "../../lib/ftl";
|
||||||
|
import { removeStyleProperties } from "../../lib/styling";
|
||||||
import { context as noteEditorContext } from "../NoteEditor.svelte";
|
import { context as noteEditorContext } from "../NoteEditor.svelte";
|
||||||
import { editingInputIsRichText } from "../rich-text-input";
|
import { editingInputIsRichText } from "../rich-text-input";
|
||||||
import { removeEmptyStyle, Surrounder } from "../surround";
|
import { Surrounder } from "../surround";
|
||||||
import ColorPicker from "./ColorPicker.svelte";
|
import ColorPicker from "./ColorPicker.svelte";
|
||||||
import type { RemoveFormat } from "./EditorToolbar.svelte";
|
import type { RemoveFormat } from "./EditorToolbar.svelte";
|
||||||
import { context as editorToolbarContext } from "./EditorToolbar.svelte";
|
import { context as editorToolbarContext } from "./EditorToolbar.svelte";
|
||||||
|
@ -45,9 +46,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
match.setCache(value);
|
match.setCache(value);
|
||||||
match.clear((): void => {
|
match.clear((): void => {
|
||||||
element.style.removeProperty("background-color");
|
if (
|
||||||
|
removeStyleProperties(element, "background-color") &&
|
||||||
if (removeEmptyStyle(element) && element.className.length === 0) {
|
element.className.length === 0
|
||||||
|
) {
|
||||||
match.remove();
|
match.remove();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -9,9 +9,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
import type { MatchType } from "../../domlib/surround";
|
import type { MatchType } from "../../domlib/surround";
|
||||||
import * as tr from "../../lib/ftl";
|
import * as tr from "../../lib/ftl";
|
||||||
import { getPlatformString } from "../../lib/shortcuts";
|
import { getPlatformString } from "../../lib/shortcuts";
|
||||||
|
import { removeStyleProperties } from "../../lib/styling";
|
||||||
import { context as noteEditorContext } from "../NoteEditor.svelte";
|
import { context as noteEditorContext } from "../NoteEditor.svelte";
|
||||||
import { editingInputIsRichText } from "../rich-text-input";
|
import { editingInputIsRichText } from "../rich-text-input";
|
||||||
import { removeEmptyStyle, Surrounder } from "../surround";
|
import { Surrounder } from "../surround";
|
||||||
import { context as editorToolbarContext } from "./EditorToolbar.svelte";
|
import { context as editorToolbarContext } from "./EditorToolbar.svelte";
|
||||||
import { italicIcon } from "./icons";
|
import { italicIcon } from "./icons";
|
||||||
|
|
||||||
|
@ -24,9 +25,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
if (["italic", "oblique"].includes(element.style.fontStyle)) {
|
if (["italic", "oblique"].includes(element.style.fontStyle)) {
|
||||||
return match.clear((): void => {
|
return match.clear((): void => {
|
||||||
element.style.removeProperty("font-style");
|
if (
|
||||||
|
removeStyleProperties(element, "font-style") &&
|
||||||
if (removeEmptyStyle(element) && element.className.length === 0) {
|
element.className.length === 0
|
||||||
|
) {
|
||||||
return match.remove();
|
return match.remove();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
23
ts/editor/editor-toolbar/RichTextClozeButtons.svelte
Normal file
23
ts/editor/editor-toolbar/RichTextClozeButtons.svelte
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
<!--
|
||||||
|
Copyright: Ankitects Pty Ltd and contributors
|
||||||
|
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
-->
|
||||||
|
<script lang="ts">
|
||||||
|
import { wrapInternal } from "../../lib/wrap";
|
||||||
|
import ClozeButtons from "../ClozeButtons.svelte";
|
||||||
|
import { context as noteEditorContext } from "../NoteEditor.svelte";
|
||||||
|
import type { RichTextInputAPI } from "../rich-text-input";
|
||||||
|
|
||||||
|
const { focusedInput } = noteEditorContext.get();
|
||||||
|
|
||||||
|
$: richTextAPI = $focusedInput as RichTextInputAPI;
|
||||||
|
|
||||||
|
async function onSurround({ detail }): Promise<void> {
|
||||||
|
const richText = await richTextAPI.element;
|
||||||
|
const { prefix, suffix } = detail;
|
||||||
|
|
||||||
|
wrapInternal(richText, prefix, suffix, false);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ClozeButtons on:surround={onSurround} />
|
|
@ -4,7 +4,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
-->
|
-->
|
||||||
<script context="module" lang="ts">
|
<script context="module" lang="ts">
|
||||||
import type { MatchType } from "../../domlib/surround";
|
import type { MatchType } from "../../domlib/surround";
|
||||||
import { removeEmptyStyle } from "../surround";
|
import { removeStyleProperties } from "../../lib/styling";
|
||||||
|
|
||||||
const surroundElement = document.createElement("sub");
|
const surroundElement = document.createElement("sub");
|
||||||
|
|
||||||
|
@ -15,9 +15,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
if (element.style.verticalAlign === "sub") {
|
if (element.style.verticalAlign === "sub") {
|
||||||
return match.clear((): void => {
|
return match.clear((): void => {
|
||||||
element.style.removeProperty("vertical-align");
|
if (
|
||||||
|
removeStyleProperties(element, "vertical-align") &&
|
||||||
if (removeEmptyStyle(element) && element.className.length === 0) {
|
element.className.length === 0
|
||||||
|
) {
|
||||||
return match.remove();
|
return match.remove();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,7 +4,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
-->
|
-->
|
||||||
<script context="module" lang="ts">
|
<script context="module" lang="ts">
|
||||||
import type { MatchType } from "../../domlib/surround";
|
import type { MatchType } from "../../domlib/surround";
|
||||||
import { removeEmptyStyle } from "../surround";
|
import { removeStyleProperties } from "../../lib/styling";
|
||||||
|
|
||||||
const surroundElement = document.createElement("sup");
|
const surroundElement = document.createElement("sup");
|
||||||
|
|
||||||
|
@ -15,9 +15,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
if (element.style.verticalAlign === "super") {
|
if (element.style.verticalAlign === "super") {
|
||||||
return match.clear((): void => {
|
return match.clear((): void => {
|
||||||
element.style.removeProperty("vertical-align");
|
if (
|
||||||
|
removeStyleProperties(element, "vertical-align") &&
|
||||||
if (removeEmptyStyle(element) && element.className.length === 0) {
|
element.className.length === 0
|
||||||
|
) {
|
||||||
return match.remove();
|
return match.remove();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -13,10 +13,11 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
import { bridgeCommand } from "../../lib/bridgecommand";
|
import { bridgeCommand } from "../../lib/bridgecommand";
|
||||||
import * as tr from "../../lib/ftl";
|
import * as tr from "../../lib/ftl";
|
||||||
import { getPlatformString } from "../../lib/shortcuts";
|
import { getPlatformString } from "../../lib/shortcuts";
|
||||||
|
import { removeStyleProperties } from "../../lib/styling";
|
||||||
import { withFontColor } from "../helpers";
|
import { withFontColor } from "../helpers";
|
||||||
import { context as noteEditorContext } from "../NoteEditor.svelte";
|
import { context as noteEditorContext } from "../NoteEditor.svelte";
|
||||||
import { editingInputIsRichText } from "../rich-text-input";
|
import { editingInputIsRichText } from "../rich-text-input";
|
||||||
import { removeEmptyStyle, Surrounder } from "../surround";
|
import { Surrounder } from "../surround";
|
||||||
import ColorPicker from "./ColorPicker.svelte";
|
import ColorPicker from "./ColorPicker.svelte";
|
||||||
import type { RemoveFormat } from "./EditorToolbar.svelte";
|
import type { RemoveFormat } from "./EditorToolbar.svelte";
|
||||||
import { context as editorToolbarContext } from "./EditorToolbar.svelte";
|
import { context as editorToolbarContext } from "./EditorToolbar.svelte";
|
||||||
|
@ -59,9 +60,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
match.setCache(value);
|
match.setCache(value);
|
||||||
match.clear((): void => {
|
match.clear((): void => {
|
||||||
element.style.removeProperty("color");
|
if (
|
||||||
|
removeStyleProperties(element, "color") &&
|
||||||
if (removeEmptyStyle(element) && element.className.length === 0) {
|
element.className.length === 0
|
||||||
|
) {
|
||||||
match.remove();
|
match.remove();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -24,8 +24,6 @@ export { default as underlineIcon } from "bootstrap-icons/icons/type-underline.s
|
||||||
export const arrowIcon =
|
export const arrowIcon =
|
||||||
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill="transparent" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2 5l6 6 6-6"/></svg>';
|
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill="transparent" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2 5l6 6 6-6"/></svg>';
|
||||||
|
|
||||||
export { default as incrementClozeIcon } from "../../icons/contain-plus.svg";
|
|
||||||
export { default as clozeIcon } from "@mdi/svg/svg/contain.svg";
|
|
||||||
export { default as functionIcon } from "@mdi/svg/svg/function-variant.svg";
|
export { default as functionIcon } from "@mdi/svg/svg/function-variant.svg";
|
||||||
export { default as paperclipIcon } from "@mdi/svg/svg/paperclip.svg";
|
export { default as paperclipIcon } from "@mdi/svg/svg/paperclip.svg";
|
||||||
export { default as micIcon } from "bootstrap-icons/icons/mic.svg";
|
export { default as micIcon } from "bootstrap-icons/icons/mic.svg";
|
||||||
|
|
|
@ -3,3 +3,4 @@
|
||||||
|
|
||||||
export type { EditorToolbarAPI } from "./EditorToolbar.svelte";
|
export type { EditorToolbarAPI } from "./EditorToolbar.svelte";
|
||||||
export { default as EditorToolbar, editorToolbar } from "./EditorToolbar.svelte";
|
export { default as EditorToolbar, editorToolbar } from "./EditorToolbar.svelte";
|
||||||
|
export { default as ClozeButtons } from "./EditorToolbar.svelte";
|
||||||
|
|
|
@ -3,8 +3,10 @@
|
||||||
|
|
||||||
/// <reference types="../lib/image-import" />
|
/// <reference types="../lib/image-import" />
|
||||||
|
|
||||||
|
export { default as incrementClozeIcon } from "../icons/contain-plus.svg";
|
||||||
export { default as alertIcon } from "@mdi/svg/svg/alert.svg";
|
export { default as alertIcon } from "@mdi/svg/svg/alert.svg";
|
||||||
export { default as htmlOn } from "@mdi/svg/svg/code-tags.svg";
|
export { default as htmlOn } from "@mdi/svg/svg/code-tags.svg";
|
||||||
|
export { default as clozeIcon } from "@mdi/svg/svg/contain.svg";
|
||||||
export { default as richTextOff } from "@mdi/svg/svg/eye-off-outline.svg";
|
export { default as richTextOff } from "@mdi/svg/svg/eye-off-outline.svg";
|
||||||
export { default as richTextOn } from "@mdi/svg/svg/eye-outline.svg";
|
export { default as richTextOn } from "@mdi/svg/svg/eye-outline.svg";
|
||||||
export { default as stickyOff } from "@mdi/svg/svg/pin-off-outline.svg";
|
export { default as stickyOff } from "@mdi/svg/svg/pin-off-outline.svg";
|
||||||
|
|
|
@ -7,6 +7,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
import ButtonDropdown from "../../components/ButtonDropdown.svelte";
|
import ButtonDropdown from "../../components/ButtonDropdown.svelte";
|
||||||
import WithDropdown from "../../components/WithDropdown.svelte";
|
import WithDropdown from "../../components/WithDropdown.svelte";
|
||||||
|
import * as tr from "../../lib/ftl";
|
||||||
import HandleBackground from "../HandleBackground.svelte";
|
import HandleBackground from "../HandleBackground.svelte";
|
||||||
import HandleControl from "../HandleControl.svelte";
|
import HandleControl from "../HandleControl.svelte";
|
||||||
import HandleLabel from "../HandleLabel.svelte";
|
import HandleLabel from "../HandleLabel.svelte";
|
||||||
|
@ -14,16 +15,33 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
import { context } from "../rich-text-input";
|
import { context } from "../rich-text-input";
|
||||||
import FloatButtons from "./FloatButtons.svelte";
|
import FloatButtons from "./FloatButtons.svelte";
|
||||||
import SizeSelect from "./SizeSelect.svelte";
|
import SizeSelect from "./SizeSelect.svelte";
|
||||||
import WithImageConstrained from "./WithImageConstrained.svelte";
|
|
||||||
|
|
||||||
const { container, styles } = context.get();
|
export let maxWidth: number;
|
||||||
|
export let maxHeight: number;
|
||||||
|
|
||||||
const sheetPromise = styles
|
const { container } = context.get();
|
||||||
.addStyleTag("imageOverlay")
|
|
||||||
.then((styleObject) => styleObject.element.sheet!);
|
$: {
|
||||||
|
container.style.setProperty("--editor-shrink-max-width", `${maxWidth}px`);
|
||||||
|
container.style.setProperty("--editor-shrink-max-height", `${maxHeight}px`);
|
||||||
|
}
|
||||||
|
|
||||||
let activeImage: HTMLImageElement | null = null;
|
let activeImage: HTMLImageElement | null = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For element dataset attributes which work like the contenteditable attribute
|
||||||
|
*/
|
||||||
|
function isDatasetAttributeFlagSet(
|
||||||
|
element: HTMLElement | SVGElement,
|
||||||
|
attribute: string,
|
||||||
|
): boolean {
|
||||||
|
return attribute in element.dataset && element.dataset[attribute] !== "false";
|
||||||
|
}
|
||||||
|
|
||||||
|
$: isSizeConstrained = activeImage
|
||||||
|
? isDatasetAttributeFlagSet(activeImage, "editorShrink")
|
||||||
|
: false;
|
||||||
|
|
||||||
async function resetHandle(): Promise<void> {
|
async function resetHandle(): Promise<void> {
|
||||||
activeImage = null;
|
activeImage = null;
|
||||||
await tick();
|
await tick();
|
||||||
|
@ -166,6 +184,20 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
activeImage!.width = width;
|
activeImage!.width = width;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toggleActualSize(): void {
|
||||||
|
if (isSizeConstrained) {
|
||||||
|
delete activeImage!.dataset.editorShrink;
|
||||||
|
} else {
|
||||||
|
activeImage!.dataset.editorShrink = "true";
|
||||||
|
}
|
||||||
|
|
||||||
|
isSizeConstrained = !isSizeConstrained;
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearActualSize(): void {
|
||||||
|
activeImage!.removeAttribute("width");
|
||||||
|
}
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
resizeObserver.disconnect();
|
resizeObserver.disconnect();
|
||||||
container.removeEventListener("click", maybeShowHandle);
|
container.removeEventListener("click", maybeShowHandle);
|
||||||
|
@ -173,6 +205,24 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
container.removeEventListener("key", resetHandle);
|
container.removeEventListener("key", resetHandle);
|
||||||
container.removeEventListener("paste", resetHandle);
|
container.removeEventListener("paste", resetHandle);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let shrinkingDisabled: boolean;
|
||||||
|
$: shrinkingDisabled =
|
||||||
|
Number(actualWidth) <= maxWidth && Number(actualHeight) <= maxHeight;
|
||||||
|
|
||||||
|
let restoringDisabled: boolean;
|
||||||
|
$: restoringDisabled = !activeImage?.hasAttribute("width") ?? true;
|
||||||
|
|
||||||
|
const widthObserver = new MutationObserver(
|
||||||
|
() => (restoringDisabled = !activeImage!.hasAttribute("width")),
|
||||||
|
);
|
||||||
|
|
||||||
|
$: activeImage
|
||||||
|
? widthObserver.observe(activeImage, {
|
||||||
|
attributes: true,
|
||||||
|
attributeFilter: ["width"],
|
||||||
|
})
|
||||||
|
: widthObserver.disconnect();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<WithDropdown
|
<WithDropdown
|
||||||
|
@ -183,20 +233,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
let:createDropdown
|
let:createDropdown
|
||||||
let:dropdownObject
|
let:dropdownObject
|
||||||
>
|
>
|
||||||
{#await sheetPromise then sheet}
|
|
||||||
<WithImageConstrained
|
|
||||||
{sheet}
|
|
||||||
{container}
|
|
||||||
{activeImage}
|
|
||||||
maxWidth={250}
|
|
||||||
maxHeight={125}
|
|
||||||
on:update={() => {
|
|
||||||
updateSizesWithDimensions();
|
|
||||||
dropdownObject.update();
|
|
||||||
}}
|
|
||||||
let:toggleActualSize
|
|
||||||
let:active
|
|
||||||
>
|
|
||||||
{#if activeImage}
|
{#if activeImage}
|
||||||
<HandleSelection
|
<HandleSelection
|
||||||
bind:updateSelection
|
bind:updateSelection
|
||||||
|
@ -204,23 +240,35 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
image={activeImage}
|
image={activeImage}
|
||||||
on:mount={(event) => createDropdown(event.detail.selection)}
|
on:mount={(event) => createDropdown(event.detail.selection)}
|
||||||
>
|
>
|
||||||
<HandleBackground on:dblclick={toggleActualSize} />
|
<HandleBackground
|
||||||
|
on:dblclick={() => {
|
||||||
|
if (shrinkingDisabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
toggleActualSize();
|
||||||
|
updateSizesWithDimensions();
|
||||||
|
dropdownObject.update();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
<HandleLabel on:mount={updateDimensions}>
|
<HandleLabel on:mount={updateDimensions}>
|
||||||
|
{#if isSizeConstrained}
|
||||||
|
<span>{tr.editingDoubleClickToExpand()}</span>
|
||||||
|
{:else}
|
||||||
<span>{actualWidth}×{actualHeight}</span>
|
<span>{actualWidth}×{actualHeight}</span>
|
||||||
{#if customDimensions}
|
{#if customDimensions}
|
||||||
<span>(Original: {naturalWidth}×{naturalHeight})</span
|
<span>(Original: {naturalWidth}×{naturalHeight})</span>
|
||||||
>
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
</HandleLabel>
|
</HandleLabel>
|
||||||
|
|
||||||
<HandleControl
|
<HandleControl
|
||||||
{active}
|
active={!isSizeConstrained}
|
||||||
activeSize={8}
|
activeSize={8}
|
||||||
offsetX={5}
|
offsetX={5}
|
||||||
offsetY={5}
|
offsetY={5}
|
||||||
on:pointerclick={(event) => {
|
on:pointerclick={(event) => {
|
||||||
if (active) {
|
if (!isSizeConstrained) {
|
||||||
setPointerCapture(event);
|
setPointerCapture(event);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
@ -232,13 +280,22 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
/>
|
/>
|
||||||
</HandleSelection>
|
</HandleSelection>
|
||||||
<ButtonDropdown on:click={updateSizesWithDimensions}>
|
<ButtonDropdown on:click={updateSizesWithDimensions}>
|
||||||
<FloatButtons
|
<FloatButtons image={activeImage} on:update={dropdownObject.update} />
|
||||||
image={activeImage}
|
<SizeSelect
|
||||||
on:update={dropdownObject.update}
|
{shrinkingDisabled}
|
||||||
|
{restoringDisabled}
|
||||||
|
{isSizeConstrained}
|
||||||
|
on:imagetoggle={() => {
|
||||||
|
toggleActualSize();
|
||||||
|
updateSizesWithDimensions();
|
||||||
|
dropdownObject.update();
|
||||||
|
}}
|
||||||
|
on:imageclear={() => {
|
||||||
|
clearActualSize();
|
||||||
|
updateSizesWithDimensions();
|
||||||
|
dropdownObject.update();
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<SizeSelect {active} on:click={toggleActualSize} />
|
|
||||||
</ButtonDropdown>
|
</ButtonDropdown>
|
||||||
{/if}
|
{/if}
|
||||||
</WithImageConstrained>
|
|
||||||
{/await}
|
|
||||||
</WithDropdown>
|
</WithDropdown>
|
||||||
|
|
|
@ -3,29 +3,38 @@ Copyright: Ankitects Pty Ltd and contributors
|
||||||
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getContext } from "svelte";
|
import { createEventDispatcher, getContext } from "svelte";
|
||||||
import type { Readable } from "svelte/store";
|
import type { Readable } from "svelte/store";
|
||||||
|
|
||||||
import ButtonGroup from "../../components/ButtonGroup.svelte";
|
import ButtonGroup from "../../components/ButtonGroup.svelte";
|
||||||
import IconButton from "../../components/IconButton.svelte";
|
import IconButton from "../../components/IconButton.svelte";
|
||||||
import { directionKey } from "../../lib/context-keys";
|
import { directionKey } from "../../lib/context-keys";
|
||||||
import * as tr from "../../lib/ftl";
|
import * as tr from "../../lib/ftl";
|
||||||
import { sizeActual, sizeMinimized } from "./icons";
|
import { sizeActual, sizeClear, sizeMinimized } from "./icons";
|
||||||
|
|
||||||
export let active: boolean;
|
export let isSizeConstrained: boolean;
|
||||||
|
export let shrinkingDisabled: boolean;
|
||||||
|
export let restoringDisabled: boolean;
|
||||||
|
|
||||||
$: icon = active ? sizeActual : sizeMinimized;
|
$: icon = isSizeConstrained ? sizeMinimized : sizeActual;
|
||||||
|
|
||||||
const direction = getContext<Readable<"ltr" | "rtl">>(directionKey);
|
const direction = getContext<Readable<"ltr" | "rtl">>(directionKey);
|
||||||
|
const dispatch = createEventDispatcher();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ButtonGroup size={1.6}>
|
<ButtonGroup size={1.6}>
|
||||||
<IconButton
|
<IconButton
|
||||||
{active}
|
disabled={shrinkingDisabled}
|
||||||
flipX={$direction === "rtl"}
|
flipX={$direction === "rtl"}
|
||||||
tooltip="{tr.editingActualSize()} ({tr.editingDoubleClickImage()})"
|
tooltip="{tr.editingActualSize()} ({tr.editingDoubleClickImage()})"
|
||||||
on:click
|
on:click={() => dispatch("imagetoggle")}
|
||||||
--border-left-radius="5px"
|
--border-left-radius="5px">{@html icon}</IconButton
|
||||||
--border-right-radius="5px">{@html icon}</IconButton
|
>
|
||||||
|
|
||||||
|
<IconButton
|
||||||
|
disabled={restoringDisabled}
|
||||||
|
tooltip={tr.editingRestoreOriginalSize()}
|
||||||
|
on:click={() => dispatch("imageclear")}
|
||||||
|
--border-right-radius="5px">{@html sizeClear}</IconButton
|
||||||
>
|
>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
|
|
|
@ -1,203 +0,0 @@
|
||||||
<!--
|
|
||||||
Copyright: Ankitects Pty Ltd and contributors
|
|
||||||
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|
||||||
-->
|
|
||||||
<script lang="ts">
|
|
||||||
import { createEventDispatcher, onDestroy } from "svelte";
|
|
||||||
|
|
||||||
import { nodeIsElement } from "../../lib/dom";
|
|
||||||
|
|
||||||
export let activeImage: HTMLImageElement | null;
|
|
||||||
export let container: HTMLElement;
|
|
||||||
export let sheet: CSSStyleSheet;
|
|
||||||
|
|
||||||
let active: boolean = false;
|
|
||||||
|
|
||||||
$: {
|
|
||||||
const index = images.indexOf(activeImage!);
|
|
||||||
|
|
||||||
if (index >= 0) {
|
|
||||||
const rule = sheet.cssRules[index] as CSSStyleRule;
|
|
||||||
active = rule.cssText.endsWith("{ }");
|
|
||||||
} else {
|
|
||||||
activeImage = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export let maxWidth: number;
|
|
||||||
export let maxHeight: number;
|
|
||||||
|
|
||||||
$: restrictionAspectRatio = maxWidth / maxHeight;
|
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
|
||||||
|
|
||||||
function createPathRecursive(tokens: string[], element: Element): string[] {
|
|
||||||
const tagName = element.tagName.toLowerCase();
|
|
||||||
|
|
||||||
if (!element.parentElement) {
|
|
||||||
const nth =
|
|
||||||
Array.prototype.indexOf.call(
|
|
||||||
(element.parentNode! as Document | ShadowRoot).children,
|
|
||||||
element,
|
|
||||||
) + 1;
|
|
||||||
return [`${tagName}:nth-child(${nth})`, ...tokens];
|
|
||||||
} else {
|
|
||||||
const nth =
|
|
||||||
Array.prototype.indexOf.call(element.parentElement.children, element) +
|
|
||||||
1;
|
|
||||||
return createPathRecursive(
|
|
||||||
[`${tagName}:nth-child(${nth})`, ...tokens],
|
|
||||||
element.parentElement,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function createPath(element: Element): string {
|
|
||||||
return createPathRecursive([], element).join(" > ");
|
|
||||||
}
|
|
||||||
|
|
||||||
const images: HTMLImageElement[] = [];
|
|
||||||
|
|
||||||
$: for (const [index, image] of images.entries()) {
|
|
||||||
const rule = sheet.cssRules[index] as CSSStyleRule;
|
|
||||||
rule.selectorText = createPath(image);
|
|
||||||
}
|
|
||||||
|
|
||||||
function filterImages(nodes: HTMLCollection | Node[]): HTMLImageElement[] {
|
|
||||||
const result: HTMLImageElement[] = [];
|
|
||||||
|
|
||||||
for (const node of nodes) {
|
|
||||||
if (!nodeIsElement(node)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node.tagName === "IMG" && !(node as HTMLElement).dataset.anki) {
|
|
||||||
result.push(node as HTMLImageElement);
|
|
||||||
} else {
|
|
||||||
result.push(...filterImages(node.children));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
function setImageRule(image: HTMLImageElement, rule: CSSStyleRule): void {
|
|
||||||
const aspectRatio = image.naturalWidth / image.naturalHeight;
|
|
||||||
|
|
||||||
if (restrictionAspectRatio - aspectRatio > 1) {
|
|
||||||
// restricted by height
|
|
||||||
rule.style.setProperty("width", "auto", "important");
|
|
||||||
|
|
||||||
const width = Number(image.getAttribute("width")) || image.width;
|
|
||||||
const height = Number(image.getAttribute("height")) || width / aspectRatio;
|
|
||||||
rule.style.setProperty(
|
|
||||||
"height",
|
|
||||||
height < maxHeight ? `${height}px` : "auto",
|
|
||||||
"important",
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// square or restricted by width
|
|
||||||
const width = Number(image.getAttribute("width")) || image.width;
|
|
||||||
rule.style.setProperty(
|
|
||||||
"width",
|
|
||||||
width < maxWidth ? `${width}px` : "auto",
|
|
||||||
"important",
|
|
||||||
);
|
|
||||||
|
|
||||||
rule.style.setProperty("height", "auto", "important");
|
|
||||||
}
|
|
||||||
|
|
||||||
rule.style.setProperty("max-width", `min(${maxWidth}px, 100%)`, "important");
|
|
||||||
rule.style.setProperty("max-height", `${maxHeight}px`, "important");
|
|
||||||
}
|
|
||||||
|
|
||||||
function resetImageRule(rule: CSSStyleRule): void {
|
|
||||||
rule.style.removeProperty("width");
|
|
||||||
rule.style.removeProperty("height");
|
|
||||||
rule.style.removeProperty("max-width");
|
|
||||||
rule.style.removeProperty("max-height");
|
|
||||||
}
|
|
||||||
|
|
||||||
function addImage(image: HTMLImageElement): void {
|
|
||||||
if (!container.contains(image)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
images.push(image);
|
|
||||||
const index = sheet.insertRule(
|
|
||||||
`${createPath(image)} {}`,
|
|
||||||
sheet.cssRules.length,
|
|
||||||
);
|
|
||||||
const rule = sheet.cssRules[index] as CSSStyleRule;
|
|
||||||
setImageRule(image, rule);
|
|
||||||
}
|
|
||||||
|
|
||||||
function addImageOnLoad(image: HTMLImageElement): void {
|
|
||||||
if (image.complete && image.naturalWidth > 0 && image.naturalHeight > 0) {
|
|
||||||
addImage(image);
|
|
||||||
} else {
|
|
||||||
image.addEventListener("load", () => addImage(image));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeImage(image: HTMLImageElement): void {
|
|
||||||
const index = images.indexOf(image);
|
|
||||||
if (index < 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
images.splice(index, 1);
|
|
||||||
sheet.deleteRule(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
const mutationObserver = new MutationObserver((mutations) => {
|
|
||||||
const addedImages = mutations.flatMap((mutation) =>
|
|
||||||
filterImages([...mutation.addedNodes]),
|
|
||||||
);
|
|
||||||
|
|
||||||
for (const image of addedImages) {
|
|
||||||
addImageOnLoad(image);
|
|
||||||
}
|
|
||||||
|
|
||||||
const removedImages = mutations.flatMap((mutation) =>
|
|
||||||
filterImages([...mutation.removedNodes]),
|
|
||||||
);
|
|
||||||
|
|
||||||
for (const image of removedImages) {
|
|
||||||
removeImage(image);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
mutationObserver.observe(container, {
|
|
||||||
childList: true,
|
|
||||||
subtree: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const image of filterImages([...container.childNodes])) {
|
|
||||||
addImageOnLoad(image);
|
|
||||||
}
|
|
||||||
|
|
||||||
onDestroy(() => mutationObserver.disconnect());
|
|
||||||
|
|
||||||
export function toggleActualSize() {
|
|
||||||
const index = images.indexOf(activeImage!);
|
|
||||||
if (index === -1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const rule = sheet.cssRules[index] as CSSStyleRule;
|
|
||||||
active = !active;
|
|
||||||
|
|
||||||
if (active) {
|
|
||||||
resetImageRule(rule);
|
|
||||||
} else {
|
|
||||||
setImageRule(activeImage!, rule);
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatch("update", active);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{#if activeImage}
|
|
||||||
<slot {toggleActualSize} {active} />
|
|
||||||
{/if}
|
|
|
@ -4,5 +4,6 @@
|
||||||
export { default as floatLeftIcon } from "@mdi/svg/svg/format-float-left.svg";
|
export { default as floatLeftIcon } from "@mdi/svg/svg/format-float-left.svg";
|
||||||
export { default as floatNoneIcon } from "@mdi/svg/svg/format-float-none.svg";
|
export { default as floatNoneIcon } from "@mdi/svg/svg/format-float-none.svg";
|
||||||
export { default as floatRightIcon } from "@mdi/svg/svg/format-float-right.svg";
|
export { default as floatRightIcon } from "@mdi/svg/svg/format-float-right.svg";
|
||||||
|
export { default as sizeClear } from "@mdi/svg/svg/image-remove.svg";
|
||||||
export { default as sizeActual } from "@mdi/svg/svg/image-size-select-actual.svg";
|
export { default as sizeActual } from "@mdi/svg/svg/image-size-select-actual.svg";
|
||||||
export { default as sizeMinimized } from "@mdi/svg/svg/image-size-select-large.svg";
|
export { default as sizeMinimized } from "@mdi/svg/svg/image-size-select-large.svg";
|
||||||
|
|
|
@ -10,6 +10,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
import IconButton from "../../components/IconButton.svelte";
|
import IconButton from "../../components/IconButton.svelte";
|
||||||
import { hasBlockAttribute } from "../../lib/dom";
|
import { hasBlockAttribute } from "../../lib/dom";
|
||||||
import * as tr from "../../lib/ftl";
|
import * as tr from "../../lib/ftl";
|
||||||
|
import ClozeButtons from "../ClozeButtons.svelte";
|
||||||
import { blockIcon, deleteIcon, inlineIcon } from "./icons";
|
import { blockIcon, deleteIcon, inlineIcon } from "./icons";
|
||||||
|
|
||||||
export let element: Element;
|
export let element: Element;
|
||||||
|
@ -48,6 +49,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
>
|
>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
|
|
||||||
|
<ClozeButtons on:surround />
|
||||||
|
|
||||||
<ButtonGroup>
|
<ButtonGroup>
|
||||||
<IconButton
|
<IconButton
|
||||||
tooltip={tr.actionsDelete()}
|
tooltip={tr.actionsDelete()}
|
||||||
|
|
|
@ -99,6 +99,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<slot editor={codeMirror} />
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.mathjax-editor {
|
.mathjax-editor {
|
||||||
:global(.CodeMirror) {
|
:global(.CodeMirror) {
|
||||||
|
|
|
@ -47,8 +47,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
on:blur={() => dispatch("reset")}
|
on:blur={() => dispatch("reset")}
|
||||||
on:moveoutstart
|
on:moveoutstart
|
||||||
on:moveoutend
|
on:moveoutend
|
||||||
/>
|
let:editor={mathjaxEditor}
|
||||||
|
>
|
||||||
<Shortcut
|
<Shortcut
|
||||||
keyCombination={acceptShortcut}
|
keyCombination={acceptShortcut}
|
||||||
on:action={() => dispatch("moveoutend")}
|
on:action={() => dispatch("moveoutend")}
|
||||||
|
@ -61,7 +61,14 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
element.remove();
|
element.remove();
|
||||||
dispatch("reset");
|
dispatch("reset");
|
||||||
}}
|
}}
|
||||||
|
on:surround={async ({ detail }) => {
|
||||||
|
const editor = await mathjaxEditor.editor;
|
||||||
|
const { prefix, suffix } = detail;
|
||||||
|
|
||||||
|
editor.replaceSelection(prefix + editor.getSelection() + suffix);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
</MathjaxEditor>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
import { registerPackage } from "../../lib/runtime-require";
|
import { registerPackage } from "../../lib/runtime-require";
|
||||||
import lifecycleHooks from "../../sveltelib/lifecycle-hooks";
|
import lifecycleHooks from "../../sveltelib/lifecycle-hooks";
|
||||||
import type { CodeMirrorAPI } from "../CodeMirror.svelte";
|
import type { CodeMirrorAPI } from "../CodeMirror.svelte";
|
||||||
import type { EditingInputAPI } from "../EditingArea.svelte";
|
import type { EditingInputAPI, FocusableInputAPI } from "../EditingArea.svelte";
|
||||||
|
|
||||||
export interface PlainTextInputAPI extends EditingInputAPI {
|
export interface PlainTextInputAPI extends EditingInputAPI {
|
||||||
name: "plain-text";
|
name: "plain-text";
|
||||||
|
@ -39,6 +39,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
import removeProhibitedTags from "./remove-prohibited";
|
import removeProhibitedTags from "./remove-prohibited";
|
||||||
import { storedToUndecorated, undecoratedToStored } from "./transform";
|
import { storedToUndecorated, undecoratedToStored } from "./transform";
|
||||||
|
|
||||||
|
export let hidden: boolean;
|
||||||
|
|
||||||
const configuration = {
|
const configuration = {
|
||||||
mode: htmlanki,
|
mode: htmlanki,
|
||||||
...baseOptions,
|
...baseOptions,
|
||||||
|
@ -46,7 +48,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
};
|
};
|
||||||
|
|
||||||
const { focusedInput } = noteEditorContext.get();
|
const { focusedInput } = noteEditorContext.get();
|
||||||
|
|
||||||
const { editingInputs, content } = editingAreaContext.get();
|
const { editingInputs, content } = editingAreaContext.get();
|
||||||
const code = writable($content);
|
const code = writable($content);
|
||||||
|
|
||||||
|
@ -70,13 +71,21 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
moveCaretToEnd();
|
moveCaretToEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
export let hidden = false;
|
|
||||||
|
|
||||||
function toggle(): boolean {
|
function toggle(): boolean {
|
||||||
hidden = !hidden;
|
hidden = !hidden;
|
||||||
return hidden;
|
return hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getInputAPI(target: EventTarget): Promise<FocusableInputAPI | null> {
|
||||||
|
const editor = (await codeMirror.editor) as any;
|
||||||
|
|
||||||
|
if (target === editor.display.input.textarea) {
|
||||||
|
return api;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
export const api: PlainTextInputAPI = {
|
export const api: PlainTextInputAPI = {
|
||||||
name: "plain-text",
|
name: "plain-text",
|
||||||
focus,
|
focus,
|
||||||
|
@ -84,6 +93,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
moveCaretToEnd,
|
moveCaretToEnd,
|
||||||
refocus,
|
refocus,
|
||||||
toggle,
|
toggle,
|
||||||
|
getInputAPI,
|
||||||
codeMirror,
|
codeMirror,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
import type { InputHandlerAPI } from "../../sveltelib/input-handler";
|
import type { InputHandlerAPI } from "../../sveltelib/input-handler";
|
||||||
import useInputHandler from "../../sveltelib/input-handler";
|
import useInputHandler from "../../sveltelib/input-handler";
|
||||||
import { pageTheme } from "../../sveltelib/theme";
|
import { pageTheme } from "../../sveltelib/theme";
|
||||||
import type { EditingInputAPI } from "../EditingArea.svelte";
|
import type { EditingInputAPI, FocusableInputAPI } from "../EditingArea.svelte";
|
||||||
import type CustomStyles from "./CustomStyles.svelte";
|
import type CustomStyles from "./CustomStyles.svelte";
|
||||||
|
|
||||||
export interface RichTextInputAPI extends EditingInputAPI {
|
export interface RichTextInputAPI extends EditingInputAPI {
|
||||||
|
@ -92,6 +92,17 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
return hidden;
|
return hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const className = "rich-text-editable";
|
||||||
|
let richTextDiv: HTMLElement;
|
||||||
|
|
||||||
|
async function getInputAPI(target: EventTarget): Promise<FocusableInputAPI | null> {
|
||||||
|
if (target === richTextDiv) {
|
||||||
|
return api;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
export const api: RichTextInputAPI = {
|
export const api: RichTextInputAPI = {
|
||||||
name: "rich-text",
|
name: "rich-text",
|
||||||
element: richTextPromise,
|
element: richTextPromise,
|
||||||
|
@ -99,6 +110,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
refocus,
|
refocus,
|
||||||
focusable: !hidden,
|
focusable: !hidden,
|
||||||
toggle,
|
toggle,
|
||||||
|
getInputAPI,
|
||||||
moveCaretToEnd,
|
moveCaretToEnd,
|
||||||
preventResubscription,
|
preventResubscription,
|
||||||
inputHandler,
|
inputHandler,
|
||||||
|
@ -155,7 +167,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
let:stylesDidLoad
|
let:stylesDidLoad
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="rich-text-editable"
|
bind:this={richTextDiv}
|
||||||
|
class={className}
|
||||||
class:hidden
|
class:hidden
|
||||||
class:night-mode={$pageTheme.isDark}
|
class:night-mode={$pageTheme.isDark}
|
||||||
use:attachShadow
|
use:attachShadow
|
||||||
|
|
|
@ -243,19 +243,6 @@ export class Surrounder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns True, if element has no style attribute (anymore).
|
|
||||||
*/
|
|
||||||
export function removeEmptyStyle(element: HTMLElement | SVGElement): boolean {
|
|
||||||
if (element.style.cssText.length === 0) {
|
|
||||||
element.removeAttribute("style");
|
|
||||||
// Calling `.hasAttribute` right after `.removeAttribute` might return true.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
registerPackage("anki/surround", {
|
registerPackage("anki/surround", {
|
||||||
Surrounder,
|
Surrounder,
|
||||||
});
|
});
|
||||||
|
|
|
@ -346,7 +346,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectAllTags() {
|
function selectAllTags() {
|
||||||
tagTypes.forEach((tag) => (tag.selected = true));
|
for (const tag of tagTypes) {
|
||||||
|
tag.selected = true;
|
||||||
|
}
|
||||||
|
|
||||||
tagTypes = tagTypes;
|
tagTypes = tagTypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -451,7 +454,18 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
splitTag(index, detail.start, detail.end)}
|
splitTag(index, detail.start, detail.end)}
|
||||||
on:tagadd={() => insertTagKeepFocus(index)}
|
on:tagadd={() => insertTagKeepFocus(index)}
|
||||||
on:tagdelete={() => deleteTagAt(index)}
|
on:tagdelete={() => deleteTagAt(index)}
|
||||||
on:tagselectall={selectAllTags}
|
on:tagselectall={async () => {
|
||||||
|
if (tagTypes.length <= 1) {
|
||||||
|
// Noop if no other tags exist
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
activeInput.blur();
|
||||||
|
// Ensure blur events are processed first
|
||||||
|
await tick();
|
||||||
|
|
||||||
|
selectAllTags();
|
||||||
|
}}
|
||||||
on:tagjoinprevious={() => joinWithPreviousTag(index)}
|
on:tagjoinprevious={() => joinWithPreviousTag(index)}
|
||||||
on:tagjoinnext={() => joinWithNextTag(index)}
|
on:tagjoinnext={() => joinWithNextTag(index)}
|
||||||
on:tagmoveprevious={() => moveToPreviousTag(index)}
|
on:tagmoveprevious={() => moveToPreviousTag(index)}
|
||||||
|
|
|
@ -231,13 +231,11 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
name = last;
|
name = last;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onSelectAll(event: KeyboardEvent) {
|
function onSelectAll(event: KeyboardEvent) {
|
||||||
if (name.length === 0) {
|
if (name.length === 0) {
|
||||||
input.blur();
|
|
||||||
await tick(); // ensure blur events are processed before tagselectall
|
|
||||||
dispatch("tagselectall");
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
dispatch("tagselectall");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,7 @@ export function gatherData(data: Stats.GraphsResponse): GraphData {
|
||||||
};
|
};
|
||||||
let haveBacklog = false;
|
let haveBacklog = false;
|
||||||
const due = (data.cards as Cards.Card[])
|
const due = (data.cards as Cards.Card[])
|
||||||
.filter((c: Cards.Card) => c.queue >= 0)
|
.filter((c: Cards.Card) => c.queue > 0)
|
||||||
.map((c: Cards.Card) => {
|
.map((c: Cards.Card) => {
|
||||||
// - testing just odue fails on day 1
|
// - testing just odue fails on day 1
|
||||||
// - testing just odid fails on lapsed cards that
|
// - testing just odid fails on lapsed cards that
|
||||||
|
|
|
@ -54,7 +54,7 @@ function filterSpan(element: Element): void {
|
||||||
|
|
||||||
const tagsAllowedBasic: TagsAllowed = {
|
const tagsAllowedBasic: TagsAllowed = {
|
||||||
BR: allowNone,
|
BR: allowNone,
|
||||||
IMG: allow(["SRC"]),
|
IMG: allow(["SRC", "ALT"]),
|
||||||
DIV: allowNone,
|
DIV: allowNone,
|
||||||
P: allowNone,
|
P: allowNone,
|
||||||
SUB: allowNone,
|
SUB: allowNone,
|
||||||
|
|
31
ts/lib/styling.ts
Normal file
31
ts/lib/styling.ts
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
// Copyright: Ankitects Pty Ltd and contributors
|
||||||
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns True, if element has no style attribute (anymore).
|
||||||
|
*/
|
||||||
|
function removeEmptyStyle(element: HTMLElement | SVGElement): boolean {
|
||||||
|
if (element.style.cssText.length === 0) {
|
||||||
|
element.removeAttribute("style");
|
||||||
|
// Calling `.hasAttribute` right after `.removeAttribute` might return true.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will remove the style attribute, if all properties were removed.
|
||||||
|
*
|
||||||
|
* @returns True, if element has no style attributes anymore
|
||||||
|
*/
|
||||||
|
export function removeStyleProperties(
|
||||||
|
element: HTMLElement | SVGElement,
|
||||||
|
...props: string[]
|
||||||
|
): boolean {
|
||||||
|
for (const prop of props) {
|
||||||
|
element.style.removeProperty(prop);
|
||||||
|
}
|
||||||
|
|
||||||
|
return removeEmptyStyle(element);
|
||||||
|
}
|
|
@ -410,14 +410,6 @@
|
||||||
"path": "node_modules/commander",
|
"path": "node_modules/commander",
|
||||||
"licenseFile": "node_modules/commander/LICENSE"
|
"licenseFile": "node_modules/commander/LICENSE"
|
||||||
},
|
},
|
||||||
"commander@9.1.0": {
|
|
||||||
"licenses": "MIT",
|
|
||||||
"repository": "https://github.com/tj/commander.js",
|
|
||||||
"publisher": "TJ Holowaychuk",
|
|
||||||
"email": "tj@vision-media.ca",
|
|
||||||
"path": "node_modules/speech-rule-engine/node_modules/commander",
|
|
||||||
"licenseFile": "node_modules/speech-rule-engine/node_modules/commander/LICENSE"
|
|
||||||
},
|
|
||||||
"concat-map@0.0.1": {
|
"concat-map@0.0.1": {
|
||||||
"licenses": "MIT",
|
"licenses": "MIT",
|
||||||
"repository": "https://github.com/substack/node-concat-map",
|
"repository": "https://github.com/substack/node-concat-map",
|
||||||
|
@ -875,14 +867,6 @@
|
||||||
"path": "node_modules/eslint",
|
"path": "node_modules/eslint",
|
||||||
"licenseFile": "node_modules/eslint/LICENSE"
|
"licenseFile": "node_modules/eslint/LICENSE"
|
||||||
},
|
},
|
||||||
"esm@3.2.25": {
|
|
||||||
"licenses": "MIT",
|
|
||||||
"repository": "https://github.com/standard-things/esm",
|
|
||||||
"publisher": "John-David Dalton",
|
|
||||||
"email": "john.david.dalton@gmail.com",
|
|
||||||
"path": "node_modules/esm",
|
|
||||||
"licenseFile": "node_modules/esm/LICENSE"
|
|
||||||
},
|
|
||||||
"espree@7.3.1": {
|
"espree@7.3.1": {
|
||||||
"licenses": "BSD-2-Clause",
|
"licenses": "BSD-2-Clause",
|
||||||
"repository": "https://github.com/eslint/espree",
|
"repository": "https://github.com/eslint/espree",
|
||||||
|
@ -1437,25 +1421,12 @@
|
||||||
"path": "node_modules/marked",
|
"path": "node_modules/marked",
|
||||||
"licenseFile": "node_modules/marked/LICENSE.md"
|
"licenseFile": "node_modules/marked/LICENSE.md"
|
||||||
},
|
},
|
||||||
"mathjax-full@3.2.0": {
|
|
||||||
"licenses": "Apache-2.0",
|
|
||||||
"repository": "https://github.com/mathjax/Mathjax-src",
|
|
||||||
"path": "node_modules/mathjax-full",
|
|
||||||
"licenseFile": "node_modules/mathjax-full/LICENSE"
|
|
||||||
},
|
|
||||||
"mathjax@3.2.0": {
|
"mathjax@3.2.0": {
|
||||||
"licenses": "Apache-2.0",
|
"licenses": "Apache-2.0",
|
||||||
"repository": "https://github.com/mathjax/MathJax",
|
"repository": "https://github.com/mathjax/MathJax",
|
||||||
"path": "node_modules/mathjax",
|
"path": "node_modules/mathjax",
|
||||||
"licenseFile": "node_modules/mathjax/LICENSE"
|
"licenseFile": "node_modules/mathjax/LICENSE"
|
||||||
},
|
},
|
||||||
"mhchemparser@4.1.1": {
|
|
||||||
"licenses": "Apache-2.0",
|
|
||||||
"repository": "https://github.com/mhchem/mhchemParser",
|
|
||||||
"publisher": "Martin Hensel",
|
|
||||||
"path": "node_modules/mhchemparser",
|
|
||||||
"licenseFile": "node_modules/mhchemparser/LICENSE.txt"
|
|
||||||
},
|
|
||||||
"minimatch@3.1.2": {
|
"minimatch@3.1.2": {
|
||||||
"licenses": "ISC",
|
"licenses": "ISC",
|
||||||
"repository": "https://github.com/isaacs/minimatch",
|
"repository": "https://github.com/isaacs/minimatch",
|
||||||
|
@ -1474,12 +1445,6 @@
|
||||||
"path": "node_modules/minimist",
|
"path": "node_modules/minimist",
|
||||||
"licenseFile": "node_modules/minimist/LICENSE"
|
"licenseFile": "node_modules/minimist/LICENSE"
|
||||||
},
|
},
|
||||||
"mj-context-menu@0.6.1": {
|
|
||||||
"licenses": "Apache-2.0",
|
|
||||||
"repository": "https://github.com/zorkow/context-menu",
|
|
||||||
"path": "node_modules/mj-context-menu",
|
|
||||||
"licenseFile": "node_modules/mj-context-menu/README.md"
|
|
||||||
},
|
|
||||||
"ms@2.0.0": {
|
"ms@2.0.0": {
|
||||||
"licenses": "MIT",
|
"licenses": "MIT",
|
||||||
"repository": "https://github.com/zeit/ms",
|
"repository": "https://github.com/zeit/ms",
|
||||||
|
@ -1765,12 +1730,6 @@
|
||||||
"path": "node_modules/slice-ansi",
|
"path": "node_modules/slice-ansi",
|
||||||
"licenseFile": "node_modules/slice-ansi/license"
|
"licenseFile": "node_modules/slice-ansi/license"
|
||||||
},
|
},
|
||||||
"speech-rule-engine@3.3.3": {
|
|
||||||
"licenses": "Apache-2.0",
|
|
||||||
"repository": "https://github.com/zorkow/speech-rule-engine",
|
|
||||||
"path": "node_modules/speech-rule-engine",
|
|
||||||
"licenseFile": "node_modules/speech-rule-engine/LICENSE"
|
|
||||||
},
|
|
||||||
"sprintf-js@1.0.3": {
|
"sprintf-js@1.0.3": {
|
||||||
"licenses": "BSD-3-Clause",
|
"licenses": "BSD-3-Clause",
|
||||||
"repository": "https://github.com/alexei/sprintf.js",
|
"repository": "https://github.com/alexei/sprintf.js",
|
||||||
|
@ -1858,7 +1817,7 @@
|
||||||
"path": "node_modules/supports-preserve-symlinks-flag",
|
"path": "node_modules/supports-preserve-symlinks-flag",
|
||||||
"licenseFile": "node_modules/supports-preserve-symlinks-flag/LICENSE"
|
"licenseFile": "node_modules/supports-preserve-symlinks-flag/LICENSE"
|
||||||
},
|
},
|
||||||
"svelte@3.46.4": {
|
"svelte@3.48.0": {
|
||||||
"licenses": "MIT",
|
"licenses": "MIT",
|
||||||
"repository": "https://github.com/sveltejs/svelte",
|
"repository": "https://github.com/sveltejs/svelte",
|
||||||
"publisher": "Rich Harris",
|
"publisher": "Rich Harris",
|
||||||
|
@ -1948,13 +1907,6 @@
|
||||||
"path": "node_modules/which",
|
"path": "node_modules/which",
|
||||||
"licenseFile": "node_modules/which/LICENSE"
|
"licenseFile": "node_modules/which/LICENSE"
|
||||||
},
|
},
|
||||||
"wicked-good-xpath@1.3.0": {
|
|
||||||
"licenses": "MIT",
|
|
||||||
"repository": "https://github.com/google/wicked-good-xpath",
|
|
||||||
"publisher": "Google Inc.",
|
|
||||||
"path": "node_modules/wicked-good-xpath",
|
|
||||||
"licenseFile": "node_modules/wicked-good-xpath/LICENSE"
|
|
||||||
},
|
|
||||||
"word-wrap@1.2.3": {
|
"word-wrap@1.2.3": {
|
||||||
"licenses": "MIT",
|
"licenses": "MIT",
|
||||||
"repository": "https://github.com/jonschlinkert/word-wrap",
|
"repository": "https://github.com/jonschlinkert/word-wrap",
|
||||||
|
@ -1972,15 +1924,6 @@
|
||||||
"path": "node_modules/wrappy",
|
"path": "node_modules/wrappy",
|
||||||
"licenseFile": "node_modules/wrappy/LICENSE"
|
"licenseFile": "node_modules/wrappy/LICENSE"
|
||||||
},
|
},
|
||||||
"xmldom-sre@0.1.31": {
|
|
||||||
"licenses": "MIT*",
|
|
||||||
"repository": "https://github.com/zorkow/xmldom",
|
|
||||||
"publisher": "jindw",
|
|
||||||
"email": "jindw@xidea.org",
|
|
||||||
"url": "http://www.xidea.org",
|
|
||||||
"path": "node_modules/xmldom-sre",
|
|
||||||
"licenseFile": "node_modules/xmldom-sre/LICENSE"
|
|
||||||
},
|
|
||||||
"yallist@4.0.0": {
|
"yallist@4.0.0": {
|
||||||
"licenses": "ISC",
|
"licenses": "ISC",
|
||||||
"repository": "https://github.com/isaacs/yallist",
|
"repository": "https://github.com/isaacs/yallist",
|
||||||
|
|
24
ts/mathjax/BUILD.bazel
Normal file
24
ts/mathjax/BUILD.bazel
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
load("//ts:prettier.bzl", "prettier_test")
|
||||||
|
load("//ts:eslint.bzl", "eslint_test")
|
||||||
|
load("//ts:esbuild.bzl", "esbuild")
|
||||||
|
load("//ts:typescript.bzl", "typescript")
|
||||||
|
|
||||||
|
typescript(name = "mathjax_ts")
|
||||||
|
|
||||||
|
_esbuild_deps = [
|
||||||
|
":mathjax_ts",
|
||||||
|
]
|
||||||
|
|
||||||
|
esbuild(
|
||||||
|
name = "mathjax",
|
||||||
|
entry_point = "index.ts",
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = _esbuild_deps,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Tests
|
||||||
|
################
|
||||||
|
|
||||||
|
prettier_test()
|
||||||
|
|
||||||
|
eslint_test()
|
31
ts/mathjax/index.ts
Normal file
31
ts/mathjax/index.ts
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
// Copyright: Ankitects Pty Ltd and contributors
|
||||||
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
|
/// <reference types="./mathjax-types" />
|
||||||
|
|
||||||
|
const packages = ["noerrors", "mathtools", "mhchem"];
|
||||||
|
|
||||||
|
function packagesForLoading(packages: string[]): string[] {
|
||||||
|
return packages.map((value: string): string => `[tex]/${value}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.MathJax = {
|
||||||
|
tex: {
|
||||||
|
displayMath: [["\\[", "\\]"]],
|
||||||
|
processEscapes: false,
|
||||||
|
processEnvironments: false,
|
||||||
|
processRefs: false,
|
||||||
|
packages: {
|
||||||
|
"[+]": packages,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
loader: {
|
||||||
|
load: packagesForLoading(packages),
|
||||||
|
paths: {
|
||||||
|
mathjax: "/_anki/js/vendor/mathjax",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
startup: {
|
||||||
|
typeset: false,
|
||||||
|
},
|
||||||
|
};
|
15
ts/mathjax/mathjax-types.d.ts
vendored
Normal file
15
ts/mathjax/mathjax-types.d.ts
vendored
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
// Copyright: Ankitects Pty Ltd and contributors
|
||||||
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
|
/* eslint
|
||||||
|
@typescript-eslint/no-explicit-any: "off",
|
||||||
|
*/
|
||||||
|
|
||||||
|
export {};
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
// Mathjax does not provide a full type
|
||||||
|
MathJax: { [name: string]: any };
|
||||||
|
}
|
||||||
|
}
|
5
ts/mathjax/tsconfig.json
Normal file
5
ts/mathjax/tsconfig.json
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"extends": "../tsconfig.json",
|
||||||
|
"include": ["*"],
|
||||||
|
"references": []
|
||||||
|
}
|
|
@ -11,6 +11,7 @@
|
||||||
{ "path": "html-filter" },
|
{ "path": "html-filter" },
|
||||||
{ "path": "reviewer" },
|
{ "path": "reviewer" },
|
||||||
{ "path": "lib" },
|
{ "path": "lib" },
|
||||||
|
{ "path": "mathjax" },
|
||||||
{ "path": "domlib" },
|
{ "path": "domlib" },
|
||||||
{ "path": "sveltelib" },
|
{ "path": "sveltelib" },
|
||||||
{ "path": "icons" }
|
{ "path": "icons" }
|
||||||
|
|
55
yarn.lock
55
yarn.lock
|
@ -1699,11 +1699,6 @@ commander@7:
|
||||||
resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7"
|
resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7"
|
||||||
integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==
|
integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==
|
||||||
|
|
||||||
commander@>=7.0.0:
|
|
||||||
version "9.1.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/commander/-/commander-9.1.0.tgz#a6b263b2327f2e188c6402c42623327909f2dbec"
|
|
||||||
integrity sha512-i0/MaqBtdbnJ4XQs4Pmyb+oFQl+q0lsAmokVUH92SlSw4fkeAcG3bVon+Qt7hmtF+u3Het6o4VgrcY3qAoEB6w==
|
|
||||||
|
|
||||||
concat-map@0.0.1:
|
concat-map@0.0.1:
|
||||||
version "0.0.1"
|
version "0.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
|
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
|
||||||
|
@ -2402,11 +2397,6 @@ eslint@^7.24.0:
|
||||||
text-table "^0.2.0"
|
text-table "^0.2.0"
|
||||||
v8-compile-cache "^2.0.3"
|
v8-compile-cache "^2.0.3"
|
||||||
|
|
||||||
esm@^3.2.25:
|
|
||||||
version "3.2.25"
|
|
||||||
resolved "https://registry.yarnpkg.com/esm/-/esm-3.2.25.tgz#342c18c29d56157688ba5ce31f8431fbb795cc10"
|
|
||||||
integrity sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==
|
|
||||||
|
|
||||||
espree@^7.3.0, espree@^7.3.1:
|
espree@^7.3.0, espree@^7.3.1:
|
||||||
version "7.3.1"
|
version "7.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.1.tgz#f2df330b752c6f55019f8bd89b7660039c1bbbb6"
|
resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.1.tgz#f2df330b752c6f55019f8bd89b7660039c1bbbb6"
|
||||||
|
@ -3743,16 +3733,6 @@ marked@^4.0.0, marked@^4.0.10:
|
||||||
resolved "https://registry.yarnpkg.com/marked/-/marked-4.0.12.tgz#2262a4e6fd1afd2f13557726238b69a48b982f7d"
|
resolved "https://registry.yarnpkg.com/marked/-/marked-4.0.12.tgz#2262a4e6fd1afd2f13557726238b69a48b982f7d"
|
||||||
integrity sha512-hgibXWrEDNBWgGiK18j/4lkS6ihTe9sxtV4Q1OQppb/0zzyPSzoFANBa5MfsG/zgsWklmNnhm0XACZOH/0HBiQ==
|
integrity sha512-hgibXWrEDNBWgGiK18j/4lkS6ihTe9sxtV4Q1OQppb/0zzyPSzoFANBa5MfsG/zgsWklmNnhm0XACZOH/0HBiQ==
|
||||||
|
|
||||||
mathjax-full@^3.2.0:
|
|
||||||
version "3.2.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/mathjax-full/-/mathjax-full-3.2.0.tgz#e53269842a943d4df10502937518991268996c5c"
|
|
||||||
integrity sha512-D2EBNvUG+mJyhn+M1C858k0f2Fc4KxXvbEX2WCMXroV10212JwfYqaBJ336ECBSz5X9L5LRoamxb7AJtg3KaJA==
|
|
||||||
dependencies:
|
|
||||||
esm "^3.2.25"
|
|
||||||
mhchemparser "^4.1.0"
|
|
||||||
mj-context-menu "^0.6.1"
|
|
||||||
speech-rule-engine "^3.3.3"
|
|
||||||
|
|
||||||
mathjax@^3.1.2:
|
mathjax@^3.1.2:
|
||||||
version "3.2.0"
|
version "3.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/mathjax/-/mathjax-3.2.0.tgz#ea7c317f8c81776ecfc83b294fc313046bf7f726"
|
resolved "https://registry.yarnpkg.com/mathjax/-/mathjax-3.2.0.tgz#ea7c317f8c81776ecfc83b294fc313046bf7f726"
|
||||||
|
@ -3773,11 +3753,6 @@ merge2@^1.3.0, merge2@^1.4.1:
|
||||||
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
|
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
|
||||||
integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
|
integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
|
||||||
|
|
||||||
mhchemparser@^4.1.0:
|
|
||||||
version "4.1.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/mhchemparser/-/mhchemparser-4.1.1.tgz#a2142fdab37a02ec8d1b48a445059287790becd5"
|
|
||||||
integrity sha512-R75CUN6O6e1t8bgailrF1qPq+HhVeFTM3XQ0uzI+mXTybmphy3b6h4NbLOYhemViQ3lUs+6CKRkC3Ws1TlYREA==
|
|
||||||
|
|
||||||
micromatch@^4.0.2, micromatch@^4.0.4:
|
micromatch@^4.0.2, micromatch@^4.0.4:
|
||||||
version "4.0.4"
|
version "4.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9"
|
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9"
|
||||||
|
@ -3820,11 +3795,6 @@ minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6:
|
||||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
|
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
|
||||||
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
|
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
|
||||||
|
|
||||||
mj-context-menu@^0.6.1:
|
|
||||||
version "0.6.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/mj-context-menu/-/mj-context-menu-0.6.1.tgz#a043c5282bf7e1cf3821de07b13525ca6f85aa69"
|
|
||||||
integrity sha512-7NO5s6n10TIV96d4g2uDpG7ZDpIhMh0QNfGdJw/W47JswFcosz457wqz/b5sAKvl12sxINGFCn80NZHKwxQEXA==
|
|
||||||
|
|
||||||
mkdirp@^0.5.1:
|
mkdirp@^0.5.1:
|
||||||
version "0.5.6"
|
version "0.5.6"
|
||||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6"
|
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6"
|
||||||
|
@ -4626,15 +4596,6 @@ spdx-satisfies@^5.0.1:
|
||||||
spdx-expression-parse "^3.0.0"
|
spdx-expression-parse "^3.0.0"
|
||||||
spdx-ranges "^2.0.0"
|
spdx-ranges "^2.0.0"
|
||||||
|
|
||||||
speech-rule-engine@^3.3.3:
|
|
||||||
version "3.3.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/speech-rule-engine/-/speech-rule-engine-3.3.3.tgz#781ed03cbcf3279f94d1d80241025ea954c6d571"
|
|
||||||
integrity sha512-0exWw+0XauLjat+f/aFeo5T8SiDsO1JtwpY3qgJE4cWt+yL/Stl0WP4VNDWdh7lzGkubUD9lWP4J1ASnORXfyQ==
|
|
||||||
dependencies:
|
|
||||||
commander ">=7.0.0"
|
|
||||||
wicked-good-xpath "^1.3.0"
|
|
||||||
xmldom-sre "^0.1.31"
|
|
||||||
|
|
||||||
sprintf-js@~1.0.2:
|
sprintf-js@~1.0.2:
|
||||||
version "1.0.3"
|
version "1.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
|
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
|
||||||
|
@ -4784,9 +4745,9 @@ svelte2tsx@^0.4.6:
|
||||||
pascal-case "^3.1.1"
|
pascal-case "^3.1.1"
|
||||||
|
|
||||||
svelte@^3.25.0:
|
svelte@^3.25.0:
|
||||||
version "3.46.4"
|
version "3.48.0"
|
||||||
resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.46.4.tgz#0c46bc4a3e20a2617a1b7dc43a722f9d6c084a38"
|
resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.48.0.tgz#f98c866d45e155bad8e1e88f15f9c03cd28753d3"
|
||||||
integrity sha512-qKJzw6DpA33CIa+C/rGp4AUdSfii0DOTCzj/2YpSKKayw5WGSS624Et9L1nU1k2OVRS9vaENQXp2CVZNU+xvIg==
|
integrity sha512-fN2YRm/bGumvjUpu6yI3BpvZnpIm9I6A7HR4oUNYd7ggYyIwSA/BX7DJ+UXXffLp6XNcUijyLvttbPVCYa/3xQ==
|
||||||
|
|
||||||
symbol-tree@^3.2.4:
|
symbol-tree@^3.2.4:
|
||||||
version "3.2.4"
|
version "3.2.4"
|
||||||
|
@ -5084,11 +5045,6 @@ which@^2.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
isexe "^2.0.0"
|
isexe "^2.0.0"
|
||||||
|
|
||||||
wicked-good-xpath@^1.3.0:
|
|
||||||
version "1.3.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/wicked-good-xpath/-/wicked-good-xpath-1.3.0.tgz#81b0e95e8650e49c94b22298fff8686b5553cf6c"
|
|
||||||
integrity sha1-gbDpXoZQ5JyUsiKY//hoa1VTz2w=
|
|
||||||
|
|
||||||
word-wrap@^1.2.3, word-wrap@~1.2.3:
|
word-wrap@^1.2.3, word-wrap@~1.2.3:
|
||||||
version "1.2.3"
|
version "1.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
|
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
|
||||||
|
@ -5136,11 +5092,6 @@ xmlcreate@^2.0.4:
|
||||||
resolved "https://registry.yarnpkg.com/xmlcreate/-/xmlcreate-2.0.4.tgz#0c5ab0f99cdd02a81065fa9cd8f8ae87624889be"
|
resolved "https://registry.yarnpkg.com/xmlcreate/-/xmlcreate-2.0.4.tgz#0c5ab0f99cdd02a81065fa9cd8f8ae87624889be"
|
||||||
integrity sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==
|
integrity sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==
|
||||||
|
|
||||||
xmldom-sre@^0.1.31:
|
|
||||||
version "0.1.31"
|
|
||||||
resolved "https://registry.yarnpkg.com/xmldom-sre/-/xmldom-sre-0.1.31.tgz#10860d5bab2c603144597d04bf2c4980e98067f4"
|
|
||||||
integrity sha512-f9s+fUkX04BxQf+7mMWAp5zk61pciie+fFLC9hX9UVvCeJQfNHRHXpeo5MPcR0EUf57PYLdt+ZO4f3Ipk2oZUw==
|
|
||||||
|
|
||||||
y18n@^5.0.5:
|
y18n@^5.0.5:
|
||||||
version "5.0.8"
|
version "5.0.8"
|
||||||
resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"
|
resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"
|
||||||
|
|
Loading…
Reference in a new issue