Remove individual .html files + other refactorings (#1588)

* Move some AddCards specific code to NoteCreator.svelte

* Add new strings for Toggling the Visual / HTML editor

* Set LabelContainer vertical-align to text-top

- Makes them look more centered

* Remove appendInParentheses helper

* Make all ts/*.html files include only module.js and module.css

* Move any JS from .html to index files

* Remove .html files from ts modules

* Remove Python with Starlark implemenation

* Remove reference to non-existing file

* Remove deck-option.html as well

* fix change-notetype screen (dae)
This commit is contained in:
Henrik Giesel 2022-01-16 06:05:35 +01:00 committed by GitHub
parent 492f0a5e32
commit 478b3a53f1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
48 changed files with 376 additions and 394 deletions

View File

@ -19,7 +19,6 @@ editing-fields = Fields
editing-float-left = Float left
editing-float-right = Float right
editing-float-none = No float
editing-html-editor = HTML Editor
editing-indent = Increase indent
editing-italic-text = Italic text
editing-jump-to-tags-with-ctrlandshiftandt = Jump to tags with Ctrl+Shift+T
@ -44,10 +43,13 @@ editing-subscript = Subscript
editing-superscript = Superscript
editing-tags = Tags
editing-to-make-a-cloze-deletion-on = To make a cloze deletion on an existing note, you need to change it to a cloze type first, via 'Notes>Change Note Type'
editing-toggle-html-editor = Toggle HTML Editor
editing-toggle-sticky = Toggle sticky
editing-toggle-visual-editor = Toggle Visual Editor
editing-underline-text = Underline text
editing-unordered-list = Unordered list
editing-warning-cloze-deletions-will-not-work = Warning, cloze deletions will not work until you switch the type at the top to Cloze.
## You don't need to translate these strings, as they will be replaced with different ones soon.
editing-html-editor = HTML Editor

View File

@ -77,7 +77,6 @@ class AddCards(QMainWindow):
self,
editor_mode=aqt.editor.EditorMode.ADD_CARDS,
)
self.editor.web.eval("noteEditorPromise.then(() => activateStickyShortcuts());")
def setup_choosers(self) -> None:
defaults = self.col.defaults_for_adding(

View File

@ -63,15 +63,11 @@ class CardInfoDialog(QDialog):
layout.addWidget(buttons)
qconnect(buttons.rejected, self.reject)
self.setLayout(layout)
self.web.eval(
"const cardInfo = anki.cardInfo(document.getElementById('main'));"
)
self.update_card(card_id)
def update_card(self, card_id: CardId | None) -> None:
self.web.eval(
f"cardInfo.then((c) => c.$set({{ cardId: {json.dumps(card_id)} }}));"
f"anki.cardInfoPromise.then((c) => c.$set({{ cardId: {json.dumps(card_id)} }}));"
)
def reject(self) -> None:

View File

@ -59,8 +59,7 @@ class ChangeNotetypeDialog(QDialog):
self.setLayout(layout)
self.web.eval(
f"""anki.changeNotetypePage(
document.getElementById('main'), {notetype_id}, {notetype_id});"""
f"""anki.setupChangeNotetypePage({notetype_id}, {notetype_id});"""
)
self.setWindowTitle(tr.browsing_change_notetype())

View File

@ -51,8 +51,7 @@ class DeckOptionsDialog(QDialog):
self.setLayout(layout)
self.web.eval(
f"""const $deckOptions = anki.deckOptions(
document.getElementById('main'), {self._deck["id"]});"""
f"""const $deckOptions = anki.setupDeckOptions({self._deck["id"]});"""
)
self.setWindowTitle(
without_unicode_isolation(tr.actions_options_for(val=self._deck["name"]))

View File

@ -15,6 +15,7 @@ exports_files([
"jest.config.js",
"package.json",
"protobuf-no-long.js",
"page.html",
])
# a copy needs to be placed in bazel-bin for libs with

View File

@ -2,12 +2,15 @@ load("//ts:prettier.bzl", "prettier_test")
load("//ts:eslint.bzl", "eslint_test")
load("//ts/svelte:svelte.bzl", "compile_svelte", "svelte_check")
load("//ts:esbuild.bzl", "esbuild")
load("//ts:generate_page.bzl", "generate_page")
load("//ts:compile_sass.bzl", "compile_sass")
load("//ts:typescript.bzl", "typescript")
generate_page(page = "card-info")
compile_sass(
srcs = ["card-info-base.scss"],
group = "base_css",
srcs = ["card-info-base.scss"],
visibility = ["//visibility:public"],
deps = [
"//sass:base_lib",
@ -17,8 +20,8 @@ compile_sass(
)
_ts_deps = [
"//ts/components",
"//ts/lib",
"//ts/components",
"@npm//@fluent",
]
@ -50,8 +53,6 @@ esbuild(
],
)
exports_files(["card-info.html"])
# Tests
################

View File

@ -1,23 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" id="viewport" content="width=device-width" />
<link href="card-info-base.css" rel="stylesheet" />
<link href="card-info.css" rel="stylesheet" />
<script src="../js/vendor/bootstrap.bundle.min.js"></script>
<script src="card-info.js"></script>
</head>
<body>
<div id="main"></div>
<script>
// use #testXXXX where XXXX is card ID to test
if (window.location.hash.startsWith("#test")) {
const cid = parseInt(window.location.hash.substr("#test".length), 10);
const cardInfo = anki.cardInfo(document.getElementById("main"));
cardInfo.then((c) => c.$set({ cardId: cid, includeRevlog: true }));
}
</script>
</body>
</html>

View File

@ -5,11 +5,23 @@ import { setupI18n, ModuleName } from "../lib/i18n";
import { checkNightMode } from "../lib/nightmode";
import CardInfo from "./CardInfo.svelte";
import "./card-info-base.css";
export async function cardInfo(target: HTMLDivElement): Promise<CardInfo> {
const i18n = setupI18n({
modules: [ModuleName.CARD_STATS, ModuleName.SCHEDULING, ModuleName.STATISTICS],
});
export async function setupCardInfo(): Promise<CardInfo> {
checkNightMode();
await setupI18n({
modules: [ModuleName.CARD_STATS, ModuleName.SCHEDULING, ModuleName.STATISTICS],
});
return new CardInfo({ target });
await i18n;
return new CardInfo({ target: document.body, props: { includeRevlog: true } });
}
export const cardInfoPromise = setupCardInfo();
if (window.location.hash.startsWith("#test")) {
// use #testXXXX where XXXX is card ID to test
const cardId = parseInt(window.location.hash.substr("#test".length), 10);
cardInfoPromise.then((cardInfo: CardInfo): void => cardInfo.$set({ cardId }));
}

View File

@ -2,10 +2,13 @@ load("//ts:prettier.bzl", "prettier_test")
load("//ts:eslint.bzl", "eslint_test")
load("//ts/svelte:svelte.bzl", "compile_svelte", "svelte_check")
load("//ts:esbuild.bzl", "esbuild")
load("//ts:generate_page.bzl", "generate_page")
load("//ts:compile_sass.bzl", "compile_sass")
load("//ts:typescript.bzl", "typescript")
load("//ts:jest.bzl", "jest_test")
generate_page(page = "change-notetype")
compile_sass(
srcs = ["change-notetype-base.scss"],
group = "base_css",
@ -54,8 +57,6 @@ esbuild(
],
)
exports_files(["change-notetype.html"])
# Tests
################

View File

@ -1,22 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" id="viewport" content="width=device-width" />
<link href="change-notetype-base.css" rel="stylesheet" />
<link href="change-notetype.css" rel="stylesheet" />
<script src="../js/vendor/bootstrap.bundle.min.js"></script>
<script src="change-notetype.js"></script>
</head>
<body>
<div id="main"></div>
<script>
// use #testXXXX where XXXX is notetype ID to test
if (window.location.hash.startsWith("#test")) {
const ntid = parseInt(window.location.hash.substr("#test".length), 10);
anki.changeNotetypePage(document.getElementById("main"), ntid, ntid);
}
</script>
</body>
</html>

View File

@ -8,10 +8,11 @@
import { ChangeNotetypeState, getChangeNotetypeInfo, getNotetypeNames } from "./lib";
import { setupI18n, ModuleName } from "../lib/i18n";
import { checkNightMode } from "../lib/nightmode";
import ChangeNotetypePage from "./ChangeNotetypePage.svelte";
export async function changeNotetypePage(
target: HTMLDivElement,
import ChangeNotetypePage from "./ChangeNotetypePage.svelte";
import "./change-notetype-base.css";
export async function setupChangeNotetypePage(
oldNotetypeId: number,
newNotetypeId: number,
): Promise<ChangeNotetypePage> {
@ -31,7 +32,13 @@ export async function changeNotetypePage(
const state = new ChangeNotetypeState(names, info);
return new ChangeNotetypePage({
target,
props: { state },
} as any);
target: document.body,
props: { state } as any,
});
}
// use #testXXXX where XXXX is notetype ID to test
if (window.location.hash.startsWith("#test")) {
const ntid = parseInt(window.location.hash.substr("#test".length), 10);
setupChangeNotetypePage(ntid, ntid);
}

View File

@ -5,8 +5,6 @@ def compile_sass(group, srcs, deps = [], visibility = ["//visibility:private"]):
for scss_file in srcs:
base = scss_file.replace(".scss", "")
name = base + "_sass"
css_file = base + ".css"
css_files.append(css_file)
sass_binary(
name = name,
@ -22,6 +20,9 @@ def compile_sass(group, srcs, deps = [], visibility = ["//visibility:private"]):
],
)
css_file = base + ".css"
css_files.append(css_file)
native.filegroup(
name = group,
srcs = css_files,

View File

@ -85,12 +85,14 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
api = {
show: dropdown.show.bind(dropdown),
toggle: dropdown.toggle.bind(dropdown),
// TODO this is quite confusing, but commenting this fixes Bootstrap
// in the deck-options when not including Bootstrap via <script />
toggle: () => {}, // toggle: dropdown.toggle.bind(dropdown),
hide: dropdown.hide.bind(dropdown),
update: dropdown.update.bind(dropdown),
dispose: dropdown.dispose.bind(dropdown),
isVisible,
};
} as any;
return api;
}

View File

@ -2,12 +2,15 @@ load("//ts:prettier.bzl", "prettier_test")
load("//ts:eslint.bzl", "eslint_test")
load("//ts/svelte:svelte.bzl", "compile_svelte", "svelte_check")
load("//ts:esbuild.bzl", "esbuild")
load("//ts:generate_page.bzl", "generate_page")
load("//ts:compile_sass.bzl", "compile_sass")
load("//ts:typescript.bzl", "typescript")
generate_page(page = "congrats")
compile_sass(
srcs = ["congrats-base.scss"],
group = "congrats_css",
group = "base_css",
visibility = ["//visibility:public"],
deps = [
"//sass:base_lib",
@ -20,7 +23,7 @@ compile_svelte(
)
typescript(
name = "congrats_ts",
name = "index",
deps = [
":svelte",
"//ts/components",
@ -35,19 +38,18 @@ esbuild(
name = "congrats",
args = {
"globalName": "anki",
"loader": {".svg": "text"},
},
entry_point = "index.ts",
output_css = "congrats.css",
visibility = ["//visibility:public"],
deps = [
":congrats_css",
":congrats_ts",
":base_css",
":index",
":svelte",
],
)
exports_files(["congrats.html"])
# Tests
################

View File

@ -1,17 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" id="viewport" content="width=device-width" />
<link href="congrats-base.css" rel="stylesheet" />
<link href="congrats.css" rel="stylesheet" />
<script src="congrats.js"></script>
</head>
<body>
<div id="main"></div>
<script>
anki.congrats(document.getElementById("main"));
</script>
</body>
</html>

View File

@ -6,18 +6,27 @@ import { setupI18n, ModuleName } from "../lib/i18n";
import { checkNightMode } from "../lib/nightmode";
import CongratsPage from "./CongratsPage.svelte";
import "./congrats-base.css";
export async function congrats(target: HTMLDivElement): Promise<void> {
const i18n = setupI18n({ modules: [ModuleName.SCHEDULING] });
export async function setupCongrats(): Promise<CongratsPage> {
checkNightMode();
await setupI18n({ modules: [ModuleName.SCHEDULING] });
await i18n;
const info = await getCongratsInfo();
const page = new CongratsPage({
target,
target: document.body,
props: { info },
});
setInterval(() => {
getCongratsInfo().then((info) => {
page.$set({ info });
});
}, 60000);
return page;
}
setupCongrats();

View File

@ -2,10 +2,13 @@ load("//ts:prettier.bzl", "prettier_test")
load("//ts:eslint.bzl", "eslint_test")
load("//ts/svelte:svelte.bzl", "compile_svelte", "svelte_check")
load("//ts:esbuild.bzl", "esbuild")
load("//ts:generate_page.bzl", "generate_page")
load("//ts:compile_sass.bzl", "compile_sass")
load("//ts:jest.bzl", "jest_test")
load("//ts:typescript.bzl", "typescript")
generate_page(page = "deck-options")
compile_sass(
srcs = ["deck-options-base.scss"],
group = "base_css",
@ -26,6 +29,7 @@ _ts_deps = [
"@npm//@popperjs",
"@npm//@types/jest",
"@npm//bootstrap-icons",
"@npm//bootstrap",
"@npm//lodash-es",
"@npm//svelte",
"@npm//marked",
@ -60,8 +64,6 @@ esbuild(
],
)
exports_files(["deck-options.html"])
# Tests
################
@ -79,11 +81,11 @@ svelte_check(
"//sass:night_mode_lib",
"//sass:breakpoints_lib",
"//sass/bootstrap",
"//ts/components",
"//ts/sveltelib:sveltelib_pkg",
"@npm//@types/bootstrap",
"@npm//@types/lodash-es",
"@npm//@types/marked",
"//ts/components",
"//ts/sveltelib:sveltelib_pkg",
],
)

View File

@ -62,7 +62,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
<ButtonGroup>
<ButtonGroupItem>
<WithShortcut shortcut={"Control+Enter"} let:createShortcut let:shortcutLabel>
<WithShortcut shortcut="Control+Enter" let:createShortcut let:shortcutLabel>
<LabelButton
theme="primary"
on:click={() => save(false)}

View File

@ -1,20 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" id="viewport" content="width=device-width" />
<link href="deck-options-base.css" rel="stylesheet" />
<link href="deck-options.css" rel="stylesheet" />
<script src="../js/vendor/bootstrap.bundle.min.js"></script>
<script src="deck-options.js"></script>
</head>
<body>
<div id="main"></div>
<script>
if (window.location.hash.startsWith("#test")) {
anki.deckOptions(document.getElementById("main"), 1);
}
</script>
</body>
</html>

View File

@ -10,41 +10,35 @@ import "../sveltelib/export-runtime";
import { getDeckOptionsInfo, DeckOptionsState } from "./lib";
import { setupI18n, ModuleName } from "../lib/i18n";
import { checkNightMode } from "../lib/nightmode";
import DeckOptionsPage from "./DeckOptionsPage.svelte";
import { touchDeviceKey, modalsKey } from "../components/context-keys";
export async function deckOptions(
target: HTMLDivElement,
deckId: number,
): Promise<DeckOptionsPage> {
const [info] = await Promise.all([
getDeckOptionsInfo(deckId),
setupI18n({
modules: [
ModuleName.SCHEDULING,
ModuleName.ACTIONS,
ModuleName.DECK_CONFIG,
ModuleName.KEYBOARD,
],
}),
]);
import DeckOptionsPage from "./DeckOptionsPage.svelte";
import "./deck-options-base.css";
const i18n = setupI18n({
modules: [
ModuleName.SCHEDULING,
ModuleName.ACTIONS,
ModuleName.DECK_CONFIG,
ModuleName.KEYBOARD,
],
});
export async function setupDeckOptions(deckId: number): Promise<DeckOptionsPage> {
const [info] = await Promise.all([getDeckOptionsInfo(deckId), i18n]);
checkNightMode();
const context = new Map();
const modals = new Map();
context.set(modalsKey, modals);
const touchDevice = "ontouchstart" in document.documentElement;
context.set(touchDeviceKey, touchDevice);
context.set(modalsKey, new Map());
context.set(touchDeviceKey, "ontouchstart" in document.documentElement);
const state = new DeckOptionsState(deckId, info);
return new DeckOptionsPage({
target,
target: document.body,
props: { state },
context,
} as any);
});
}
import TitledContainer from "./TitledContainer.svelte";
@ -60,3 +54,7 @@ export const components = {
EnumSelectorRow,
SwitchRow,
};
if (window.location.hash.startsWith("#test")) {
setupDeckOptions(1);
}

View File

@ -5,6 +5,8 @@ import { on, preventDefault } from "../lib/events";
import { registerShortcut } from "../lib/shortcuts";
import { placeCaretAfterContent } from "../domlib/place-caret";
import { saveSelection, restoreSelection } from "../domlib/location";
import { isApplePlatform } from "../lib/platform";
import { bridgeCommand } from "../lib/bridgecommand";
import type { SelectionLocation } from "../domlib/location";
const locationEvents: (() => void)[] = [];
@ -76,6 +78,10 @@ export function saveLocation(editable: HTMLElement): { destroy(): void } {
};
}
if (isApplePlatform()) {
registerShortcut(() => bridgeCommand("paste"), "Control+Shift+V");
}
export function preventBuiltinContentEditableShortcuts(editable: HTMLElement): void {
for (const keyCombination of ["Control+B", "Control+U", "Control+I", "Control+R"]) {
registerShortcut(preventDefault, keyCombination, editable);

View File

@ -7,7 +7,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import Badge from "../components/Badge.svelte";
import { withSpan } from "../components/helpers";
import { appendInParentheses } from "./helpers";
import { tagIcon, addTagIcon } from "./icons";
const tooltip = "Add tag";
@ -17,7 +16,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
<div class="add-icon">
<Badge
class="d-flex me-1"
tooltip={appendInParentheses(tooltip, shortcutLabel)}
tooltip="{tooltip} ({shortcutLabel})"
on:click
on:mount={withSpan(createShortcut)}
>

View File

@ -14,7 +14,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import { bridgeCommand } from "../lib/bridgecommand";
import { withButton } from "../components/helpers";
import { textColorIcon, highlightColorIcon, arrowIcon } from "./icons";
import { appendInParentheses, execCommand } from "./helpers";
import { execCommand } from "./helpers";
import { getNoteEditor } from "./OldEditorAdapter.svelte";
export let api = {};
@ -41,10 +41,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
<ButtonGroupItem>
<WithShortcut shortcut={"F7"} let:createShortcut let:shortcutLabel>
<IconButton
tooltip={appendInParentheses(
tr.editingSetTextColor(),
shortcutLabel,
)}
tooltip="{tr.editingSetTextColor()} ({shortcutLabel})"
{disabled}
on:click={forecolorWrap}
on:mount={withButton(createShortcut)}
@ -58,10 +55,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
<ButtonGroupItem>
<WithShortcut shortcut={"F8"} let:createShortcut let:shortcutLabel>
<IconButton
tooltip={appendInParentheses(
tr.editingChangeColor(),
shortcutLabel,
)}
tooltip="{tr.editingChangeColor()} ({shortcutLabel})"
{disabled}
widthMultiplier={0.5}
>

View File

@ -8,7 +8,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import WithState from "../components/WithState.svelte";
import { withButton } from "../components/helpers";
import { appendInParentheses, execCommand, queryCommandState } from "./helpers";
import { execCommand, queryCommandState } from "./helpers";
import { getNoteEditor } from "./OldEditorAdapter.svelte";
export let key: string;
@ -49,7 +49,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
{:else if withoutState}
<WithShortcut {shortcut} let:createShortcut let:shortcutLabel>
<IconButton
tooltip={appendInParentheses(tooltip, shortcutLabel)}
tooltip="{tooltip} ({shortcutLabel})"
{disabled}
on:click={() => execCommand(key)}
on:mount={withButton(createShortcut)}
@ -66,7 +66,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
let:updateState
>
<IconButton
tooltip={appendInParentheses(tooltip, shortcutLabel)}
tooltip="{tooltip} ({shortcutLabel})"
{active}
{disabled}
on:click={(event) => {

View File

@ -1,23 +0,0 @@
<!--
Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script context="module" lang="ts">
import IconButton from "../components/IconButton.svelte";
import LabelButton from "../components/LabelButton.svelte";
import WithShortcut from "../components/WithShortcut.svelte";
import WithContext from "../components/WithContext.svelte";
import WithState from "../components/WithState.svelte";
import * as contextKeys from "../components/context-keys";
import * as editorContextKeys from "./NoteEditor.svelte";
export const components = {
IconButton,
LabelButton,
WithShortcut,
WithContext,
WithState,
contextKeys: { ...contextKeys, ...editorContextKeys },
};
</script>

View File

@ -13,7 +13,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import * as tr from "../lib/ftl";
import { getListItem } from "../lib/dom";
import { appendInParentheses, execCommand } from "./helpers";
import { execCommand } from "./helpers";
import {
ulIcon,
olIcon,
@ -129,10 +129,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
<IconButton
on:click={outdentListItem}
on:mount={withButton(createShortcut)}
tooltip={appendInParentheses(
tr.editingOutdent(),
shortcutLabel,
)}
tooltip="{tr.editingOutdent()} ({shortcutLabel})"
{disabled}
>
{@html outdentIcon}
@ -149,10 +146,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
<IconButton
on:click={indentListItem}
on:mount={withButton(createShortcut)}
tooltip={appendInParentheses(
tr.editingIndent(),
shortcutLabel,
)}
tooltip="{tr.editingIndent()} ({shortcutLabel})"
{disabled}
>
{@html indentIcon}

View File

@ -3,8 +3,12 @@ Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import OldEditorAdapter from "../editor/OldEditorAdapter.svelte";
import type { NoteEditorAPI } from "../editor/OldEditorAdapter.svelte";
import { onMount, onDestroy } from "svelte";
import { bridgeCommand } from "../lib/bridgecommand";
import { registerShortcut } from "../lib/shortcuts";
import StickyBadge from "./StickyBadge.svelte";
import OldEditorAdapter from "./OldEditorAdapter.svelte";
import type { NoteEditorAPI } from "./OldEditorAdapter.svelte";
const api: Partial<NoteEditorAPI> = {};
let noteEditor: OldEditorAdapter;
@ -14,6 +18,33 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
$: if (noteEditor) {
uiResolve(api as NoteEditorAPI);
}
let stickies: boolean[] = [];
function setSticky(stckies: boolean[]): void {
stickies = stckies;
}
function toggleStickyAll(): void {
bridgeCommand("toggleStickyAll", (values: boolean[]) => (stickies = values));
}
let deregisterSticky: () => void;
export function activateStickyShortcuts() {
deregisterSticky = registerShortcut(toggleStickyAll, "Shift+F9");
}
onMount(() => {
Object.assign(globalThis, {
setSticky,
});
});
onDestroy(() => deregisterSticky);
</script>
<OldEditorAdapter bind:this={noteEditor} {api} />
<OldEditorAdapter bind:this={noteEditor} {api}>
<svelte:fragment slot="field-state" let:index>
<StickyBadge active={stickies[index]} {index} />
</svelte:fragment>
</OldEditorAdapter>

View File

@ -52,12 +52,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import RichTextBadge from "./RichTextBadge.svelte";
import PlainTextBadge from "./PlainTextBadge.svelte";
import StickyBadge from "./StickyBadge.svelte";
import { onMount, onDestroy } from "svelte";
import { onMount } from "svelte";
import type { Writable } from "svelte/store";
import { bridgeCommand } from "../lib/bridgecommand";
import { registerShortcut } from "../lib/shortcuts";
import { isApplePlatform } from "../lib/platform";
import { ChangeTimer } from "./change-timer";
import { alertIcon } from "./icons";
@ -146,11 +144,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
$tags = ts;
}
let stickies: boolean[] | null = null;
export function setSticky(sts: boolean[]): void {
stickies = sts;
}
let noteId: number | null = null;
export function setNoteId(ntid: number): void {
noteId = ntid;
@ -203,15 +196,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
}
}
function toggleStickyAll(): void {
bridgeCommand("toggleStickyAll", (values: boolean[]) => (stickies = values));
}
let deregisterSticky: () => void;
export function activateStickyShortcuts() {
deregisterSticky = registerShortcut(toggleStickyAll, "Shift+F9");
}
export function focusIfField(x: number, y: number): boolean {
const elements = document.elementsFromPoint(x, y);
const first = elements[0];
@ -249,6 +233,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
);
import { wrapInternal } from "../lib/wrap";
import * as oldEditorAdapter from "./old-editor-adapter";
onMount(() => {
function wrap(before: string, after: string): void {
@ -270,21 +255,18 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
focusField,
setColorButtons,
setTags,
setSticky,
setBackgrounds,
setClozeHint,
saveNow: saveFieldNow,
activateStickyShortcuts,
focusIfField,
setNoteId,
wrap,
...oldEditorAdapter,
});
document.addEventListener("visibilitychange", saveOnPageHide);
return () => document.removeEventListener("visibilitychange", saveOnPageHide);
});
onDestroy(() => deregisterSticky);
</script>
<NoteEditor>
@ -332,9 +314,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
{/if}
<RichTextBadge bind:off={richTextsHidden[index]} />
<PlainTextBadge bind:off={plainTextsHidden[index]} />
{#if stickies}
<StickyBadge active={stickies[index]} {index} />
{/if}
<slot name="field-state" {field} {index} />
</svelte:fragment>
<svelte:fragment slot="editing-inputs">

View File

@ -4,7 +4,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import Badge from "../components/Badge.svelte";
import * as tr from "../lib/ftl";
import { onMount } from "svelte";
import { htmlOn, htmlOff } from "./icons";
@ -29,10 +28,15 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
onMount(() => editorField.element.then(shortcut));
</script>
<span class:highlighted={!off} on:click|stopPropagation={toggle}>
<span
class="plain-text-badge"
class:highlighted={!off}
on:click|stopPropagation={toggle}
>
<Badge
tooltip="{tr.editingHtmlEditor()} ({getPlatformString(keyCombination)})"
iconSize={80}>{@html icon}</Badge
tooltip="{tr.editingToggleHtmlEditor()} ({getPlatformString(keyCombination)})"
iconSize={80}
--icon-align="text-top">{@html icon}</Badge
>
</span>

View File

@ -4,6 +4,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import Badge from "../components/Badge.svelte";
import * as tr from "../lib/ftl";
import { richTextOn, richTextOff } from "./icons";
export let off: boolean;
@ -15,8 +16,12 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
$: icon = off ? richTextOff : richTextOn;
</script>
<span class:highlighted={off} on:click|stopPropagation={toggle}>
<Badge iconSize={80}>{@html icon}</Badge>
<span class="rich-text-badge" class:highlighted={off} on:click|stopPropagation={toggle}>
<Badge
tooltip={tr.editingToggleVisualEditor()}
iconSize={80}
--icon-align="text-top">{@html icon}</Badge
>
</span>
<style lang="scss">

View File

@ -12,7 +12,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import DropdownItem from "../components/DropdownItem.svelte";
import { withSpan, withButton } from "../components/helpers";
import { appendInParentheses } from "./helpers";
import { dotsIcon } from "./icons";
const dispatch = createEventDispatcher();
@ -34,21 +33,21 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
event.stopImmediatePropagation();
}}
on:mount={withButton(createShortcut)}
>{appendInParentheses(allLabel, shortcutLabel)}</DropdownItem
>{allLabel} ({shortcutLabel})</DropdownItem
>
</WithShortcut>
<WithShortcut shortcut="Control+C" let:createShortcut let:shortcutLabel>
<DropdownItem
on:click={() => dispatch("tagcopy")}
on:mount={withButton(createShortcut)}
>{appendInParentheses(copyLabel, shortcutLabel)}</DropdownItem
>{copyLabel} ({shortcutLabel})</DropdownItem
>
</WithShortcut>
<WithShortcut shortcut="Backspace" let:createShortcut let:shortcutLabel>
<DropdownItem
on:click={() => dispatch("tagdelete")}
on:mount={withButton(createShortcut)}
>{appendInParentheses(removeLabel, shortcutLabel)}</DropdownItem
>{removeLabel} ({shortcutLabel})</DropdownItem
>
</WithShortcut>
</DropdownMenu>

View File

@ -36,7 +36,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
<span class:highlighted={active} on:click|stopPropagation={toggle}>
<Badge
tooltip="{tr.editingToggleSticky()} ({getPlatformString(keyCombination)})"
widthMultiplier={0.7}>{@html icon}</Badge
widthMultiplier={0.7}
--icon-align="text-top">{@html icon}</Badge
>
</span>

View File

@ -16,7 +16,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import { bridgeCommand } from "../lib/bridgecommand";
import { wrapInternal } from "../lib/wrap";
import { getNoteEditor } from "./OldEditorAdapter.svelte";
import { appendInParentheses } from "./helpers";
import { withButton } from "../components/helpers";
import { paperclipIcon, micIcon, functionIcon } from "./icons";
import type { RichTextInputAPI } from "./RichTextInput.svelte";
@ -45,10 +44,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
<ButtonGroupItem>
<WithShortcut shortcut="F3" let:createShortcut let:shortcutLabel>
<IconButton
tooltip={appendInParentheses(
tr.editingAttachPicturesaudiovideo(),
shortcutLabel,
)}
tooltip="{tr.editingAttachPicturesaudiovideo} ({shortcutLabel})"
iconSize={70}
{disabled}
on:click={onAttachment}
@ -62,7 +58,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
<ButtonGroupItem>
<WithShortcut shortcut="F5" let:createShortcut let:shortcutLabel>
<IconButton
tooltip={appendInParentheses(tr.editingRecordAudio(), shortcutLabel)}
tooltip="{tr.editingRecordAudio()} ({shortcutLabel})"
iconSize={70}
{disabled}
on:click={onRecord}

49
ts/editor/base.ts Normal file
View File

@ -0,0 +1,49 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
/**
* Code that is shared among all entry points in /ts/editor
*/
import "./legacy.css";
import "./editor-base.css";
import "../lib/register-package";
import "../sveltelib/export-runtime";
declare global {
interface Selection {
modify(s: string, t: string, u: string): void;
addRange(r: Range): void;
removeAllRanges(): void;
getRangeAt(n: number): Range;
}
}
import { ModuleName } from "../lib/i18n";
export const editorModules = [
ModuleName.EDITING,
ModuleName.KEYBOARD,
ModuleName.ACTIONS,
ModuleName.BROWSING,
];
export { editorToolbar } from "./EditorToolbar.svelte";
import IconButton from "../components/IconButton.svelte";
import LabelButton from "../components/LabelButton.svelte";
import WithShortcut from "../components/WithShortcut.svelte";
import WithContext from "../components/WithContext.svelte";
import WithState from "../components/WithState.svelte";
import * as contextKeys from "../components/context-keys";
import * as editorContextKeys from "./NoteEditor.svelte";
export const components = {
IconButton,
LabelButton,
WithShortcut,
WithContext,
WithState,
contextKeys: { ...contextKeys, ...editorContextKeys },
};

View File

@ -1,10 +1,6 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
export function appendInParentheses(text: string, appendix: string): string {
return `${text} (${appendix})`;
}
/// trivial wrapper to silence Svelte deprecation warnings
export function execCommand(
command: string,

View File

@ -1,63 +0,0 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import "./legacy.css";
import "./editor-base.css";
/* eslint
@typescript-eslint/no-explicit-any: "off",
*/
import "../sveltelib/export-runtime";
import "../lib/register-package";
import "../domlib/surround";
import { filterHTML } from "../html-filter";
import { execCommand } from "./helpers";
import { updateAllState } from "../components/WithState.svelte";
export function pasteHTML(
html: string,
internal: boolean,
extendedMode: boolean,
): void {
html = filterHTML(html, internal, extendedMode);
if (html !== "") {
setFormat("inserthtml", html);
}
}
export function setFormat(cmd: string, arg?: string, _nosave = false): void {
execCommand(cmd, false, arg);
updateAllState(new Event(cmd));
}
import { setupI18n, ModuleName } from "../lib/i18n";
import { isApplePlatform } from "../lib/platform";
import { registerShortcut } from "../lib/shortcuts";
import { bridgeCommand } from "../lib/bridgecommand";
declare global {
interface Selection {
modify(s: string, t: string, u: string): void;
addRange(r: Range): void;
removeAllRanges(): void;
getRangeAt(n: number): Range;
}
}
if (isApplePlatform()) {
registerShortcut(() => bridgeCommand("paste"), "Control+Shift+V");
}
export const i18n = setupI18n({
modules: [
ModuleName.EDITING,
ModuleName.KEYBOARD,
ModuleName.ACTIONS,
ModuleName.BROWSING,
],
});
export { editorToolbar } from "./EditorToolbar.svelte";

View File

@ -1,14 +1,15 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import { i18n } from ".";
import BrowserEditor from "./BrowserEditor.svelte";
import { editorModules } from "./base";
import { promiseWithResolver } from "../lib/promise";
import { globalExport } from "../lib/globals";
import { setupI18n } from "../lib/i18n";
const [uiPromise, uiResolve] = promiseWithResolver();
async function setupBrowserEditor(): Promise<void> {
await i18n;
await setupI18n({ modules: editorModules });
new BrowserEditor({
target: document.body,
@ -18,10 +19,10 @@ async function setupBrowserEditor(): Promise<void> {
setupBrowserEditor();
import * as editor from ".";
import * as base from "./base";
globalExport({
...editor,
...base,
uiPromise,
noteEditorPromise: uiPromise,
});

View File

@ -1,14 +1,15 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import { i18n } from ".";
import NoteCreator from "./NoteCreator.svelte";
import { editorModules } from "./base";
import { promiseWithResolver } from "../lib/promise";
import { globalExport } from "../lib/globals";
import { setupI18n } from "../lib/i18n";
const [uiPromise, uiResolve] = promiseWithResolver();
async function setupNoteCreator(): Promise<void> {
await i18n;
await setupI18n({ modules: editorModules });
new NoteCreator({
target: document.body,
@ -18,10 +19,10 @@ async function setupNoteCreator(): Promise<void> {
setupNoteCreator();
import * as editor from ".";
import * as base from "./base";
globalExport({
...editor,
...base,
uiPromise,
noteEditorPromise: uiPromise,
});

View File

@ -1,14 +1,15 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import { i18n } from "../editor";
import ReviewerEditor from "./ReviewerEditor.svelte";
import { editorModules } from "./base";
import { promiseWithResolver } from "../lib/promise";
import { globalExport } from "../lib/globals";
import { setupI18n } from "../lib/i18n";
const [uiPromise, uiResolve] = promiseWithResolver();
async function setupReviewerEditor(): Promise<void> {
await i18n;
await setupI18n({ modules: editorModules });
new ReviewerEditor({
target: document.body,
@ -18,10 +19,10 @@ async function setupReviewerEditor(): Promise<void> {
setupReviewerEditor();
import * as editor from "../editor";
import * as base from "./base";
globalExport({
...editor,
...base,
uiPromise,
noteEditorPromise: uiPromise,
});

View 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
import { filterHTML } from "../html-filter";
import { updateAllState } from "../components/WithState.svelte";
import { execCommand } from "./helpers";
export function pasteHTML(
html: string,
internal: boolean,
extendedMode: boolean,
): void {
html = filterHTML(html, internal, extendedMode);
if (html !== "") {
setFormat("inserthtml", html);
}
}
export function setFormat(cmd: string, arg?: string, _nosave = false): void {
execCommand(cmd, false, arg);
updateAllState(new Event(cmd));
}

30
ts/generate_page.bzl Normal file
View File

@ -0,0 +1,30 @@
def _generate_html_page_impl(ctx):
ctx.actions.expand_template(
template = ctx.file.template,
output = ctx.outputs.output,
substitutions = {
"{PAGE}": ctx.attr.page,
},
)
generate_html_page = rule(
implementation = _generate_html_page_impl,
attrs = {
"template": attr.label(
default = Label("//ts:page.html"),
allow_single_file = [".html"],
),
"page": attr.string(mandatory = True),
"output": attr.output(mandatory = True),
},
)
def generate_page(page, name = "page"):
output = page + ".html"
generate_html_page(
name = name,
page = page,
output = output,
visibility = ["//visibility:public"],
)

View File

@ -2,9 +2,12 @@ load("//ts/svelte:svelte.bzl", "compile_svelte", "svelte_check")
load("//ts:prettier.bzl", "prettier_test")
load("//ts:eslint.bzl", "eslint_test")
load("//ts:esbuild.bzl", "esbuild")
load("//ts:generate_page.bzl", "generate_page")
load("//ts:compile_sass.bzl", "compile_sass")
load("//ts:typescript.bzl", "typescript")
generate_page(page = "graphs")
compile_sass(
srcs = ["graphs-base.scss"],
group = "base_css",
@ -52,8 +55,6 @@ esbuild(
],
)
exports_files(["graphs.html"])
# Tests
################

View File

@ -3,23 +3,21 @@ Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import type { SvelteComponent } from "svelte/internal";
import type { SvelteComponentDev } from "svelte/internal";
import { writable } from "svelte/store";
import { pageTheme } from "../sveltelib/theme";
import { bridgeCommand } from "../lib/bridgecommand";
import WithGraphData from "./WithGraphData.svelte";
export let graphs: SvelteComponent[];
export let initialSearch: string;
export let initialDays: number;
export let controller: SvelteComponent | null;
const search = writable(initialSearch);
const days = writable(initialDays);
export let graphs: typeof SvelteComponentDev[];
export let controller: typeof SvelteComponentDev | null;
function browserSearch(event: CustomEvent) {
bridgeCommand(`browserSearch: ${$search} ${event.detail.query}`);
}

View File

@ -1,34 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" id="viewport" content="width=device-width" />
<link href="graphs-base.css" rel="stylesheet" />
<link href="graphs.css" rel="stylesheet" />
<script src="graphs.js"></script>
</head>
<body>
<div id="main"></div>
<script>
anki.graphs(
document.getElementById("main"),
[
anki.TodayStats,
anki.FutureDue,
anki.CalendarGraph,
anki.ReviewsGraph,
anki.CardCounts,
anki.IntervalsGraph,
anki.EaseGraph,
anki.HourGraph,
anki.ButtonsGraph,
anki.AddedGraph,
],
{
controller: anki.RangeBox,
},
);
</script>
</body>
</html>

View File

@ -1,46 +1,79 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import type { SvelteComponent } from "svelte/internal";
import type { SvelteComponentDev } from "svelte/internal";
import { setupI18n, ModuleName } from "../lib/i18n";
import { checkNightMode } from "../lib/nightmode";
import GraphsPage from "./GraphsPage.svelte";
import "./graphs-base.css";
export { default as RangeBox } from "./RangeBox.svelte";
const i18n = setupI18n({ modules: [ModuleName.STATISTICS, ModuleName.SCHEDULING] });
export { default as IntervalsGraph } from "./IntervalsGraph.svelte";
export { default as EaseGraph } from "./EaseGraph.svelte";
export { default as AddedGraph } from "./AddedGraph.svelte";
export { default as TodayStats } from "./TodayStats.svelte";
export { default as ButtonsGraph } from "./ButtonsGraph.svelte";
export { default as CardCounts } from "./CardCounts.svelte";
export { default as HourGraph } from "./HourGraph.svelte";
export { default as FutureDue } from "./FutureDue.svelte";
export { default as ReviewsGraph } from "./ReviewsGraph.svelte";
export { default as CalendarGraph } from "./CalendarGraph.svelte";
export { RevlogRange } from "./graph-helpers";
export function graphs(
target: HTMLDivElement,
graphs: SvelteComponent[],
export async function setupGraphs(
graphs: typeof SvelteComponentDev[],
{
search = "deck:current",
days = 365,
controller = null as SvelteComponent | null,
controller = null as typeof SvelteComponentDev | null,
} = {},
): void {
): Promise<GraphsPage> {
checkNightMode();
setupI18n({ modules: [ModuleName.STATISTICS, ModuleName.SCHEDULING] }).then(() => {
new GraphsPage({
target,
props: {
graphs,
initialSearch: search,
initialDays: days,
controller,
},
});
await i18n;
return new GraphsPage({
target: document.body,
props: {
initialSearch: search,
initialDays: days,
graphs,
controller,
},
});
}
import TodayStats from "./TodayStats.svelte";
import FutureDue from "./FutureDue.svelte";
import CalendarGraph from "./CalendarGraph.svelte";
import ReviewsGraph from "./ReviewsGraph.svelte";
import CardCounts from "./CardCounts.svelte";
import IntervalsGraph from "./IntervalsGraph.svelte";
import EaseGraph from "./EaseGraph.svelte";
import HourGraph from "./HourGraph.svelte";
import ButtonsGraph from "./ButtonsGraph.svelte";
import AddedGraph from "./AddedGraph.svelte";
import { RevlogRange } from "./graph-helpers";
import RangeBox from "./RangeBox.svelte";
setupGraphs(
[
TodayStats,
FutureDue,
CalendarGraph,
ReviewsGraph,
CardCounts,
IntervalsGraph,
EaseGraph,
HourGraph,
ButtonsGraph,
AddedGraph,
],
{
controller: RangeBox,
},
);
export const graphComponents = {
TodayStats,
FutureDue,
CalendarGraph,
ReviewsGraph,
CardCounts,
IntervalsGraph,
EaseGraph,
HourGraph,
ButtonsGraph,
AddedGraph,
RangeBox,
RevlogRange,
};

10
ts/page.html Normal file
View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" id="viewport" content="width=device-width" />
<link href="{PAGE}.css" rel="stylesheet" />
<script src="{PAGE}.js"></script>
</head>
<body></body>
</html>

View File

@ -1,6 +1,6 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
//
// Expose the Svelte runtime bundled with Anki, so that add-ons can require() it.
// If they were to bundle their own runtime, things like bindings and contexts
// would not work.