mirror of
https://github.com/ankitects/anki.git
synced 2025-09-21 07:22:23 -04:00
Merge pull request #1140 from hgiesel/paragrapho
Restructure editor-toolbar and editor + Change Enter behavior within paragraphs + Restrict indent / outdent to <li>
This commit is contained in:
commit
e3a7d1a9e3
31 changed files with 442 additions and 342 deletions
|
@ -26,20 +26,11 @@ copy_files_into_group(
|
||||||
package = "//ts/editor",
|
package = "//ts/editor",
|
||||||
)
|
)
|
||||||
|
|
||||||
copy_files_into_group(
|
|
||||||
name = "editor-toolbar",
|
|
||||||
srcs = [
|
|
||||||
"editor-toolbar.css",
|
|
||||||
],
|
|
||||||
package = "//ts/editor-toolbar",
|
|
||||||
)
|
|
||||||
|
|
||||||
filegroup(
|
filegroup(
|
||||||
name = "css",
|
name = "css",
|
||||||
srcs = [
|
srcs = [
|
||||||
"css_local",
|
"css_local",
|
||||||
"editor",
|
"editor",
|
||||||
"editor-toolbar",
|
|
||||||
],
|
],
|
||||||
visibility = ["//qt:__subpackages__"],
|
visibility = ["//qt:__subpackages__"],
|
||||||
)
|
)
|
||||||
|
|
|
@ -37,20 +37,11 @@ copy_files_into_group(
|
||||||
package = "//ts/editor",
|
package = "//ts/editor",
|
||||||
)
|
)
|
||||||
|
|
||||||
copy_files_into_group(
|
|
||||||
name = "editor-toolbar",
|
|
||||||
srcs = [
|
|
||||||
"editor-toolbar.js",
|
|
||||||
],
|
|
||||||
package = "//ts/editor-toolbar",
|
|
||||||
)
|
|
||||||
|
|
||||||
filegroup(
|
filegroup(
|
||||||
name = "js",
|
name = "js",
|
||||||
srcs = [
|
srcs = [
|
||||||
"aqt_es5",
|
"aqt_es5",
|
||||||
"editor",
|
"editor",
|
||||||
"editor-toolbar",
|
|
||||||
"mathjax.js",
|
"mathjax.js",
|
||||||
"//qt/aqt/data/web/js/vendor",
|
"//qt/aqt/data/web/js/vendor",
|
||||||
],
|
],
|
||||||
|
|
|
@ -141,13 +141,11 @@ class Editor:
|
||||||
_html % (bgcol, tr.editing_show_duplicates()),
|
_html % (bgcol, tr.editing_show_duplicates()),
|
||||||
css=[
|
css=[
|
||||||
"css/editor.css",
|
"css/editor.css",
|
||||||
"css/editor-toolbar.css",
|
|
||||||
],
|
],
|
||||||
js=[
|
js=[
|
||||||
"js/vendor/jquery.min.js",
|
"js/vendor/jquery.min.js",
|
||||||
"js/vendor/protobuf.min.js",
|
"js/vendor/protobuf.min.js",
|
||||||
"js/editor.js",
|
"js/editor.js",
|
||||||
"js/editor-toolbar.js",
|
|
||||||
],
|
],
|
||||||
context=self,
|
context=self,
|
||||||
default_css=False,
|
default_css=False,
|
||||||
|
@ -184,13 +182,7 @@ $editorToolbar.addButtonGroup({{
|
||||||
else ""
|
else ""
|
||||||
)
|
)
|
||||||
|
|
||||||
self.web.eval(
|
self.web.eval(f"{lefttopbtns_js} {righttopbtns_js}")
|
||||||
f"""
|
|
||||||
$editorToolbar = document.getElementById("editorToolbar");
|
|
||||||
{lefttopbtns_js}
|
|
||||||
{righttopbtns_js}
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
|
|
||||||
# Top buttons
|
# Top buttons
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
load("@io_bazel_rules_sass//:defs.bzl", "sass_binary")
|
load("@io_bazel_rules_sass//:defs.bzl", "sass_binary")
|
||||||
|
|
||||||
def compile_sass(group, srcs, visibility, deps):
|
def compile_sass(group, srcs, deps = [], visibility = ["//visibility:private"]):
|
||||||
css_files = []
|
css_files = []
|
||||||
for scss_file in srcs:
|
for scss_file in srcs:
|
||||||
base = scss_file.replace(".scss", "")
|
base = scss_file.replace(".scss", "")
|
||||||
|
|
|
@ -4,12 +4,17 @@ load("//ts:prettier.bzl", "prettier_test")
|
||||||
load("//ts:eslint.bzl", "eslint_test")
|
load("//ts:eslint.bzl", "eslint_test")
|
||||||
load("//ts:esbuild.bzl", "esbuild")
|
load("//ts:esbuild.bzl", "esbuild")
|
||||||
load("//ts:compile_sass.bzl", "compile_sass")
|
load("//ts:compile_sass.bzl", "compile_sass")
|
||||||
load("//ts:vendor.bzl", "copy_bootstrap_icons", "copy_mdi_icons")
|
|
||||||
|
|
||||||
svelte_files = glob(["*.svelte"])
|
svelte_files = glob(["*.svelte"])
|
||||||
|
|
||||||
svelte_names = [f.replace(".svelte", "") for f in svelte_files]
|
svelte_names = [f.replace(".svelte", "") for f in svelte_files]
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "svelte_components",
|
||||||
|
srcs = svelte_names,
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
|
|
||||||
compile_svelte(
|
compile_svelte(
|
||||||
name = "svelte",
|
name = "svelte",
|
||||||
srcs = svelte_files,
|
srcs = svelte_files,
|
||||||
|
@ -17,43 +22,32 @@ compile_svelte(
|
||||||
"//ts/sass:button_mixins_lib",
|
"//ts/sass:button_mixins_lib",
|
||||||
"//ts/sass/bootstrap",
|
"//ts/sass/bootstrap",
|
||||||
],
|
],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
)
|
)
|
||||||
|
|
||||||
compile_sass(
|
compile_sass(
|
||||||
srcs = [
|
srcs = [
|
||||||
"bootstrap.scss",
|
"bootstrap.scss",
|
||||||
"color.scss",
|
|
||||||
"legacy.scss",
|
"legacy.scss",
|
||||||
],
|
],
|
||||||
group = "local_css",
|
group = "local_css",
|
||||||
visibility = ["//visibility:public"],
|
|
||||||
deps = [
|
deps = [
|
||||||
"//ts/sass:button_mixins_lib",
|
"//ts/sass:button_mixins_lib",
|
||||||
"//ts/sass/bootstrap",
|
"//ts/sass/bootstrap",
|
||||||
],
|
],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
)
|
)
|
||||||
|
|
||||||
ts_library(
|
ts_library(
|
||||||
name = "index",
|
name = "editor-toolbar",
|
||||||
srcs = ["index.ts"],
|
module_name = "editor-toolbar",
|
||||||
deps = [
|
|
||||||
"EditorToolbar",
|
|
||||||
"lib",
|
|
||||||
"//ts/lib",
|
|
||||||
"//ts/sveltelib",
|
|
||||||
"@npm//svelte",
|
|
||||||
"@npm//svelte2tsx",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
ts_library(
|
|
||||||
name = "lib",
|
|
||||||
srcs = glob(
|
srcs = glob(
|
||||||
["*.ts"],
|
["*.ts"],
|
||||||
exclude = ["index.ts"],
|
exclude = ["*.test.ts"],
|
||||||
),
|
),
|
||||||
|
tsconfig = "//ts:tsconfig.json",
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
deps = [
|
deps = [
|
||||||
"//ts:image_module_support",
|
|
||||||
"//ts/lib",
|
"//ts/lib",
|
||||||
"//ts/lib:backend_proto",
|
"//ts/lib:backend_proto",
|
||||||
"//ts/sveltelib",
|
"//ts/sveltelib",
|
||||||
|
@ -64,71 +58,6 @@ ts_library(
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
copy_bootstrap_icons(
|
|
||||||
name = "bootstrap-icons",
|
|
||||||
icons = [
|
|
||||||
# inline formatting
|
|
||||||
"type-bold.svg",
|
|
||||||
"type-italic.svg",
|
|
||||||
"type-underline.svg",
|
|
||||||
"eraser.svg",
|
|
||||||
"square-fill.svg",
|
|
||||||
"paperclip.svg",
|
|
||||||
"mic.svg",
|
|
||||||
|
|
||||||
# block formatting
|
|
||||||
"list-ul.svg",
|
|
||||||
"list-ol.svg",
|
|
||||||
"text-paragraph.svg",
|
|
||||||
"justify.svg",
|
|
||||||
"text-left.svg",
|
|
||||||
"text-right.svg",
|
|
||||||
"text-center.svg",
|
|
||||||
"text-indent-left.svg",
|
|
||||||
"text-indent-right.svg",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
copy_mdi_icons(
|
|
||||||
name = "mdi-icons",
|
|
||||||
icons = [
|
|
||||||
"format-superscript.svg",
|
|
||||||
"format-subscript.svg",
|
|
||||||
"function-variant.svg",
|
|
||||||
"code-brackets.svg",
|
|
||||||
"xml.svg",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
esbuild(
|
|
||||||
name = "editor-toolbar",
|
|
||||||
srcs = [
|
|
||||||
"//ts:protobuf-shim.js",
|
|
||||||
],
|
|
||||||
args = [
|
|
||||||
"--global-name=editorToolbar",
|
|
||||||
"--inject:$(location //ts:protobuf-shim.js)",
|
|
||||||
"--resolve-extensions=.mjs,.js",
|
|
||||||
"--loader:.svg=text",
|
|
||||||
"--log-level=warning",
|
|
||||||
],
|
|
||||||
entry_point = "index.ts",
|
|
||||||
external = [
|
|
||||||
"protobufjs/light",
|
|
||||||
],
|
|
||||||
output_css = "editor-toolbar.css",
|
|
||||||
visibility = ["//visibility:public"],
|
|
||||||
deps = [
|
|
||||||
"//ts/lib",
|
|
||||||
"//ts/lib:backend_proto",
|
|
||||||
"//ts:image_module_support",
|
|
||||||
"index",
|
|
||||||
"bootstrap-icons",
|
|
||||||
"mdi-icons",
|
|
||||||
"local_css",
|
|
||||||
] + svelte_names,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Tests
|
# Tests
|
||||||
################
|
################
|
||||||
|
|
||||||
|
|
8
ts/editor-toolbar/CommandIconButton.d.ts
vendored
8
ts/editor-toolbar/CommandIconButton.d.ts
vendored
|
@ -5,6 +5,12 @@ export interface CommandIconButtonProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
tooltip: string;
|
tooltip: string;
|
||||||
icon: string;
|
icon: string;
|
||||||
|
|
||||||
command: string;
|
command: string;
|
||||||
activatable?: boolean;
|
onClick: (event: MouseEvent) => void;
|
||||||
|
|
||||||
|
onUpdate: (event: Event) => boolean;
|
||||||
|
|
||||||
|
disables?: boolean;
|
||||||
|
dropdownToggle?: boolean;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,19 +5,24 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
<script lang="typescript" context="module">
|
<script lang="typescript" context="module">
|
||||||
import { writable } from "svelte/store";
|
import { writable } from "svelte/store";
|
||||||
|
|
||||||
const commandMap = writable(new Map<string, boolean>());
|
type UpdateMap = Map<string, (event: Event) => boolean>;
|
||||||
|
type ActiveMap = Map<string, boolean>;
|
||||||
|
|
||||||
function updateButton(key: string): void {
|
const updateMap = new Map() as UpdateMap;
|
||||||
commandMap.update(
|
const activeMap = new Map() as ActiveMap;
|
||||||
(map: Map<string, boolean>): Map<string, boolean> =>
|
const activeStore = writable(activeMap);
|
||||||
new Map([...map, [key, document.queryCommandState(key)]])
|
|
||||||
|
function updateButton(key: string, event: MouseEvent): void {
|
||||||
|
activeStore.update(
|
||||||
|
(map: ActiveMap): ActiveMap =>
|
||||||
|
new Map([...map, [key, updateMap.get(key)(event)]])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateButtons(callback: (key: string) => boolean): void {
|
function updateButtons(callback: (key: string) => boolean): void {
|
||||||
commandMap.update(
|
activeStore.update(
|
||||||
(map: Map<string, boolean>): Map<string, boolean> => {
|
(map: ActiveMap): ActiveMap => {
|
||||||
const newMap = new Map<string, boolean>();
|
const newMap = new Map() as ActiveMap;
|
||||||
|
|
||||||
for (const key of map.keys()) {
|
for (const key of map.keys()) {
|
||||||
newMap.set(key, callback(key));
|
newMap.set(key, callback(key));
|
||||||
|
@ -28,8 +33,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateActiveButtons() {
|
export function updateActiveButtons(event: Event) {
|
||||||
updateButtons((key: string): boolean => document.queryCommandState(key));
|
updateButtons((key: string): boolean => updateMap.get(key)(event));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function clearActiveButtons() {
|
export function clearActiveButtons() {
|
||||||
|
@ -43,28 +48,32 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
export let id: string;
|
export let id: string;
|
||||||
export let className = "";
|
export let className = "";
|
||||||
export let tooltip: string;
|
export let tooltip: string;
|
||||||
|
|
||||||
export let icon: string;
|
export let icon: string;
|
||||||
|
|
||||||
export let command: string;
|
export let command: string;
|
||||||
export let activatable = true;
|
export let onClick = (_event: MouseEvent) => {
|
||||||
export let disables = true;
|
document.execCommand(command);
|
||||||
export let dropdownToggle = false;
|
};
|
||||||
|
|
||||||
|
function onClickWrapped(event: MouseEvent): void {
|
||||||
|
onClick(event);
|
||||||
|
updateButton(command, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
export let onUpdate = (_event: Event) => document.queryCommandState(command);
|
||||||
|
|
||||||
|
updateMap.set(command, onUpdate);
|
||||||
|
|
||||||
let active = false;
|
let active = false;
|
||||||
|
|
||||||
if (activatable) {
|
activeStore.subscribe((map: ActiveMap): (() => void) => {
|
||||||
updateButton(command);
|
|
||||||
|
|
||||||
commandMap.subscribe((map: Map<string, boolean>): (() => void) => {
|
|
||||||
active = Boolean(map.get(command));
|
active = Boolean(map.get(command));
|
||||||
return () => map.delete(command);
|
return () => map.delete(command);
|
||||||
});
|
});
|
||||||
}
|
activeMap.set(command, active);
|
||||||
|
|
||||||
function onClick(): void {
|
export let disables = true;
|
||||||
document.execCommand(command);
|
export let dropdownToggle = false;
|
||||||
updateButton(command);
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<SquareButton
|
<SquareButton
|
||||||
|
@ -74,7 +83,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
{active}
|
{active}
|
||||||
{disables}
|
{disables}
|
||||||
{dropdownToggle}
|
{dropdownToggle}
|
||||||
{onClick}
|
onClick={onClickWrapped}
|
||||||
on:mount>
|
on:mount>
|
||||||
{@html icon}
|
{@html icon}
|
||||||
</SquareButton>
|
</SquareButton>
|
||||||
|
|
|
@ -8,11 +8,12 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
export let id: string;
|
export let id: string;
|
||||||
export let className = "";
|
export let className = "";
|
||||||
export let tooltip: string;
|
export let tooltip: string;
|
||||||
|
export let icon: string;
|
||||||
|
|
||||||
|
export let onClick: (event: MouseEvent) => void;
|
||||||
|
|
||||||
export let disables = true;
|
export let disables = true;
|
||||||
export let dropdownToggle = false;
|
export let dropdownToggle = false;
|
||||||
|
|
||||||
export let icon = "";
|
|
||||||
export let onClick: (event: MouseEvent) => void;
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<SquareButton {id} {className} {tooltip} {onClick} {disables} {dropdownToggle} on:mount>
|
<SquareButton {id} {className} {tooltip} {onClick} {disables} {dropdownToggle} on:mount>
|
||||||
|
|
57
ts/editor-toolbar/dynamicComponents.ts
Normal file
57
ts/editor-toolbar/dynamicComponents.ts
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
// Copyright: Ankitects Pty Ltd and contributors
|
||||||
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
import LabelButton from "./LabelButton.svelte";
|
||||||
|
import type { LabelButtonProps } from "./LabelButton";
|
||||||
|
import IconButton from "./IconButton.svelte";
|
||||||
|
import type { IconButtonProps } from "./IconButton";
|
||||||
|
import CommandIconButton from "./CommandIconButton.svelte";
|
||||||
|
import type { CommandIconButtonProps } from "./CommandIconButton";
|
||||||
|
import ColorPicker from "./ColorPicker.svelte";
|
||||||
|
import type { ColorPickerProps } from "./ColorPicker";
|
||||||
|
import ButtonGroup from "./ButtonGroup.svelte";
|
||||||
|
import type { ButtonGroupProps } from "./ButtonGroup";
|
||||||
|
|
||||||
|
import ButtonDropdown from "./ButtonDropdown.svelte";
|
||||||
|
import type { ButtonDropdownProps } from "./ButtonDropdown";
|
||||||
|
import DropdownMenu from "./DropdownMenu.svelte";
|
||||||
|
import type { DropdownMenuProps } from "./DropdownMenu";
|
||||||
|
import DropdownItem from "./DropdownItem.svelte";
|
||||||
|
import type { DropdownItemProps } from "./DropdownItem";
|
||||||
|
import WithDropdownMenu from "./WithDropdownMenu.svelte";
|
||||||
|
import type { WithDropdownMenuProps } from "./WithDropdownMenu";
|
||||||
|
|
||||||
|
import { dynamicComponent } from "sveltelib/dynamicComponent";
|
||||||
|
|
||||||
|
export const labelButton = dynamicComponent<typeof LabelButton, LabelButtonProps>(
|
||||||
|
LabelButton
|
||||||
|
);
|
||||||
|
export const iconButton = dynamicComponent<typeof IconButton, IconButtonProps>(
|
||||||
|
IconButton
|
||||||
|
);
|
||||||
|
export const commandIconButton = dynamicComponent<
|
||||||
|
typeof CommandIconButton,
|
||||||
|
CommandIconButtonProps
|
||||||
|
>(CommandIconButton);
|
||||||
|
export const colorPicker = dynamicComponent<typeof ColorPicker, ColorPickerProps>(
|
||||||
|
ColorPicker
|
||||||
|
);
|
||||||
|
|
||||||
|
export const buttonGroup = dynamicComponent<typeof ButtonGroup, ButtonGroupProps>(
|
||||||
|
ButtonGroup
|
||||||
|
);
|
||||||
|
export const buttonDropdown = dynamicComponent<
|
||||||
|
typeof ButtonDropdown,
|
||||||
|
ButtonDropdownProps
|
||||||
|
>(ButtonDropdown);
|
||||||
|
|
||||||
|
export const dropdownMenu = dynamicComponent<typeof DropdownMenu, DropdownMenuProps>(
|
||||||
|
DropdownMenu
|
||||||
|
);
|
||||||
|
export const dropdownItem = dynamicComponent<typeof DropdownItem, DropdownItemProps>(
|
||||||
|
DropdownItem
|
||||||
|
);
|
||||||
|
|
||||||
|
export const withDropdownMenu = dynamicComponent<
|
||||||
|
typeof WithDropdownMenu,
|
||||||
|
WithDropdownMenuProps
|
||||||
|
>(WithDropdownMenu);
|
5
ts/editor-toolbar/editorToolbar.d.ts
vendored
Normal file
5
ts/editor-toolbar/editorToolbar.d.ts
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
import type { EditorToolbar } from ".";
|
||||||
|
|
||||||
|
declare namespace globalThis {
|
||||||
|
const $editorToolbar: EditorToolbar;
|
||||||
|
}
|
|
@ -11,15 +11,8 @@ import { Writable, writable } from "svelte/store";
|
||||||
|
|
||||||
import EditorToolbarSvelte from "./EditorToolbar.svelte";
|
import EditorToolbarSvelte from "./EditorToolbar.svelte";
|
||||||
|
|
||||||
import { setupI18n, ModuleName } from "anki/i18n";
|
|
||||||
|
|
||||||
import "./bootstrap.css";
|
import "./bootstrap.css";
|
||||||
|
|
||||||
import { getNotetypeGroup } from "./notetype";
|
|
||||||
import { getFormatInlineGroup } from "./formatInline";
|
|
||||||
import { getFormatBlockGroup, getFormatBlockMenus } from "./formatBlock";
|
|
||||||
import { getColorGroup } from "./color";
|
|
||||||
import { getTemplateGroup, getTemplateMenus } from "./template";
|
|
||||||
import { Identifiable, search, add, insert } from "./identifiable";
|
import { Identifiable, search, add, insert } from "./identifiable";
|
||||||
|
|
||||||
interface Hideable {
|
interface Hideable {
|
||||||
|
@ -45,7 +38,7 @@ let buttonsResolve: (
|
||||||
) => void;
|
) => void;
|
||||||
let menusResolve: (value: Writable<ToolbarItem[]>) => void;
|
let menusResolve: (value: Writable<ToolbarItem[]>) => void;
|
||||||
|
|
||||||
class EditorToolbar extends HTMLElement {
|
export class EditorToolbar extends HTMLElement {
|
||||||
component?: SvelteComponentDev;
|
component?: SvelteComponentDev;
|
||||||
|
|
||||||
buttonsPromise: Promise<
|
buttonsPromise: Promise<
|
||||||
|
@ -58,30 +51,22 @@ class EditorToolbar extends HTMLElement {
|
||||||
});
|
});
|
||||||
|
|
||||||
connectedCallback(): void {
|
connectedCallback(): void {
|
||||||
setupI18n({ modules: [ModuleName.EDITING] }).then(() => {
|
globalThis.$editorToolbar = this;
|
||||||
const buttons = writable([
|
|
||||||
getNotetypeGroup(),
|
const buttons = writable([]);
|
||||||
getFormatInlineGroup(),
|
const menus = writable([]);
|
||||||
getFormatBlockGroup(),
|
|
||||||
getColorGroup(),
|
|
||||||
getTemplateGroup(),
|
|
||||||
]);
|
|
||||||
const menus = writable([...getTemplateMenus(), ...getFormatBlockMenus()]);
|
|
||||||
|
|
||||||
this.component = new EditorToolbarSvelte({
|
this.component = new EditorToolbarSvelte({
|
||||||
target: this,
|
target: this,
|
||||||
props: {
|
props: {
|
||||||
buttons,
|
buttons,
|
||||||
menus,
|
menus,
|
||||||
nightMode: document.documentElement.classList.contains(
|
nightMode: document.documentElement.classList.contains("night-mode"),
|
||||||
"night-mode"
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
buttonsResolve(buttons);
|
buttonsResolve(buttons);
|
||||||
menusResolve(menus);
|
menusResolve(menus);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updateButtonGroup<T>(
|
updateButtonGroup<T>(
|
||||||
|
@ -200,19 +185,8 @@ class EditorToolbar extends HTMLElement {
|
||||||
|
|
||||||
customElements.define("anki-editor-toolbar", EditorToolbar);
|
customElements.define("anki-editor-toolbar", EditorToolbar);
|
||||||
|
|
||||||
/* Exports for editor
|
/* Exports for editor */
|
||||||
* @ts-expect-error */
|
// @ts-expect-error
|
||||||
export { updateActiveButtons, clearActiveButtons } from "./CommandIconButton.svelte";
|
export { updateActiveButtons, clearActiveButtons } from "./CommandIconButton.svelte";
|
||||||
|
// @ts-expect-error
|
||||||
export { enableButtons, disableButtons } from "./EditorToolbar.svelte";
|
export { enableButtons, disableButtons } from "./EditorToolbar.svelte";
|
||||||
|
|
||||||
/* Exports for add-ons */
|
|
||||||
export { default as RawButton } from "./RawButton.svelte";
|
|
||||||
export { default as LabelButton } from "./LabelButton.svelte";
|
|
||||||
export { default as IconButton } from "./IconButton.svelte";
|
|
||||||
export { default as CommandIconButton } from "./CommandIconButton.svelte";
|
|
||||||
export { default as SelectButton } from "./SelectButton.svelte";
|
|
||||||
|
|
||||||
export { default as DropdownMenu } from "./DropdownMenu.svelte";
|
|
||||||
export { default as DropdownItem } from "./DropdownItem.svelte";
|
|
||||||
export { default as ButtonDropdown } from "./DropdownMenu.svelte";
|
|
||||||
export { default as WithDropdownMenu } from "./WithDropdownMenu.svelte";
|
|
||||||
|
|
|
@ -2,13 +2,24 @@ load("@npm//@bazel/typescript:index.bzl", "ts_library")
|
||||||
load("//ts:prettier.bzl", "prettier_test")
|
load("//ts:prettier.bzl", "prettier_test")
|
||||||
load("//ts:eslint.bzl", "eslint_test")
|
load("//ts:eslint.bzl", "eslint_test")
|
||||||
load("//ts:esbuild.bzl", "esbuild")
|
load("//ts:esbuild.bzl", "esbuild")
|
||||||
load("//ts:vendor.bzl", "copy_bootstrap_icons")
|
load("//ts:vendor.bzl", "copy_bootstrap_icons", "copy_mdi_icons")
|
||||||
load("//ts:compile_sass.bzl", "compile_sass")
|
load("//ts:compile_sass.bzl", "compile_sass")
|
||||||
|
|
||||||
compile_sass(
|
compile_sass(
|
||||||
srcs = [
|
srcs = [
|
||||||
"editable.scss",
|
"editable.scss",
|
||||||
"editor.scss",
|
],
|
||||||
|
group = "editable_scss",
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = [
|
||||||
|
"//ts/sass:scrollbar_lib",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
compile_sass(
|
||||||
|
srcs = [
|
||||||
|
"fields.scss",
|
||||||
|
"color.scss",
|
||||||
],
|
],
|
||||||
group = "base_css",
|
group = "base_css",
|
||||||
visibility = ["//visibility:public"],
|
visibility = ["//visibility:public"],
|
||||||
|
@ -25,28 +36,78 @@ ts_library(
|
||||||
tsconfig = "//ts:tsconfig.json",
|
tsconfig = "//ts:tsconfig.json",
|
||||||
deps = [
|
deps = [
|
||||||
"//ts:image_module_support",
|
"//ts:image_module_support",
|
||||||
|
"//ts/lib",
|
||||||
|
"//ts/sveltelib",
|
||||||
"//ts/html-filter",
|
"//ts/html-filter",
|
||||||
|
"//ts/editor-toolbar",
|
||||||
|
"@npm//svelte",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
copy_bootstrap_icons(
|
copy_bootstrap_icons(
|
||||||
name = "bootstrap-icons",
|
name = "bootstrap-icons",
|
||||||
icons = ["pin-angle.svg"],
|
icons = [
|
||||||
|
"pin-angle.svg",
|
||||||
|
|
||||||
|
# inline formatting
|
||||||
|
"type-bold.svg",
|
||||||
|
"type-italic.svg",
|
||||||
|
"type-underline.svg",
|
||||||
|
"eraser.svg",
|
||||||
|
"square-fill.svg",
|
||||||
|
"paperclip.svg",
|
||||||
|
"mic.svg",
|
||||||
|
|
||||||
|
# block formatting
|
||||||
|
"list-ul.svg",
|
||||||
|
"list-ol.svg",
|
||||||
|
"text-paragraph.svg",
|
||||||
|
"justify.svg",
|
||||||
|
"text-left.svg",
|
||||||
|
"text-right.svg",
|
||||||
|
"text-center.svg",
|
||||||
|
"text-indent-left.svg",
|
||||||
|
"text-indent-right.svg",
|
||||||
|
],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
|
|
||||||
|
copy_mdi_icons(
|
||||||
|
name = "mdi-icons",
|
||||||
|
icons = [
|
||||||
|
"format-superscript.svg",
|
||||||
|
"format-subscript.svg",
|
||||||
|
"function-variant.svg",
|
||||||
|
"code-brackets.svg",
|
||||||
|
"xml.svg",
|
||||||
|
],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
)
|
)
|
||||||
|
|
||||||
esbuild(
|
esbuild(
|
||||||
name = "editor",
|
name = "editor",
|
||||||
|
srcs = [
|
||||||
|
"//ts:protobuf-shim.js",
|
||||||
|
],
|
||||||
args = [
|
args = [
|
||||||
"--loader:.svg=text",
|
"--loader:.svg=text",
|
||||||
|
"--inject:$(location //ts:protobuf-shim.js)",
|
||||||
"--resolve-extensions=.mjs,.js",
|
"--resolve-extensions=.mjs,.js",
|
||||||
"--log-level=warning",
|
"--log-level=warning",
|
||||||
],
|
],
|
||||||
|
output_css = "editor.css",
|
||||||
|
external = [
|
||||||
|
"protobufjs/light",
|
||||||
|
],
|
||||||
entry_point = "index_wrapper.ts",
|
entry_point = "index_wrapper.ts",
|
||||||
visibility = ["//visibility:public"],
|
visibility = ["//visibility:public"],
|
||||||
deps = [
|
deps = [
|
||||||
"base_css",
|
"base_css",
|
||||||
":bootstrap-icons",
|
"bootstrap-icons",
|
||||||
":editor_ts",
|
"mdi-icons",
|
||||||
|
"editor_ts",
|
||||||
|
"//ts/editor-toolbar:local_css",
|
||||||
|
"//ts/editor-toolbar:svelte_components",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
24
ts/editor/addons.ts
Normal file
24
ts/editor/addons.ts
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
// Copyright: Ankitects Pty Ltd and contributors
|
||||||
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
import { default as RawButton } from "editor-toolbar/RawButton.svelte";
|
||||||
|
import { default as LabelButton } from "editor-toolbar/LabelButton.svelte";
|
||||||
|
import { default as IconButton } from "editor-toolbar/IconButton.svelte";
|
||||||
|
import { default as CommandIconButton } from "editor-toolbar/CommandIconButton.svelte";
|
||||||
|
import { default as SelectButton } from "editor-toolbar/SelectButton.svelte";
|
||||||
|
|
||||||
|
import { default as DropdownMenu } from "editor-toolbar/DropdownMenu.svelte";
|
||||||
|
import { default as DropdownItem } from "editor-toolbar/DropdownItem.svelte";
|
||||||
|
import { default as ButtonDropdown } from "editor-toolbar/DropdownMenu.svelte";
|
||||||
|
import { default as WithDropdownMenu } from "editor-toolbar/WithDropdownMenu.svelte";
|
||||||
|
|
||||||
|
export const editorToolbar = {
|
||||||
|
RawButton,
|
||||||
|
LabelButton,
|
||||||
|
IconButton,
|
||||||
|
CommandIconButton,
|
||||||
|
SelectButton,
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownItem,
|
||||||
|
ButtonDropdown,
|
||||||
|
WithDropdownMenu,
|
||||||
|
};
|
|
@ -1,24 +1,31 @@
|
||||||
// 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 IconButton from "./IconButton.svelte";
|
import type IconButton from "editor-toolbar/IconButton.svelte";
|
||||||
import type { IconButtonProps } from "./IconButton";
|
import type { IconButtonProps } from "editor-toolbar/IconButton";
|
||||||
|
import type { DynamicSvelteComponent } from "sveltelib/dynamicComponent";
|
||||||
|
|
||||||
import { DynamicSvelteComponent, dynamicComponent } from "sveltelib/dynamicComponent";
|
|
||||||
import * as tr from "anki/i18n";
|
import * as tr from "anki/i18n";
|
||||||
|
import { iconButton } from "editor-toolbar/dynamicComponents";
|
||||||
|
|
||||||
import bracketsIcon from "./code-brackets.svg";
|
import bracketsIcon from "./code-brackets.svg";
|
||||||
|
|
||||||
|
import { forEditorField } from ".";
|
||||||
|
import { wrap } from "./wrap";
|
||||||
|
|
||||||
const clozePattern = /\{\{c(\d+)::/gu;
|
const clozePattern = /\{\{c(\d+)::/gu;
|
||||||
function getCurrentHighestCloze(increment: boolean): number {
|
function getCurrentHighestCloze(increment: boolean): number {
|
||||||
let highest = 0;
|
let highest = 0;
|
||||||
|
|
||||||
// @ts-expect-error
|
|
||||||
forEditorField([], (field) => {
|
forEditorField([], (field) => {
|
||||||
const matches = field.editingArea.editable.fieldHTML.matchAll(clozePattern);
|
const fieldHTML = field.editingArea.editable.fieldHTML;
|
||||||
highest = Math.max(
|
const matches: number[] = [];
|
||||||
highest,
|
let match: RegExpMatchArray | null = null;
|
||||||
...[...matches].map((match: RegExpMatchArray): number => Number(match[1]))
|
|
||||||
);
|
while ((match = clozePattern.exec(fieldHTML))) {
|
||||||
|
matches.push(Number(match[1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
highest = Math.max(highest, ...matches);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (increment) {
|
if (increment) {
|
||||||
|
@ -30,13 +37,9 @@ function getCurrentHighestCloze(increment: boolean): number {
|
||||||
|
|
||||||
function onCloze(event: MouseEvent): void {
|
function onCloze(event: MouseEvent): void {
|
||||||
const highestCloze = getCurrentHighestCloze(!event.altKey);
|
const highestCloze = getCurrentHighestCloze(!event.altKey);
|
||||||
|
|
||||||
// @ts-expect-error
|
|
||||||
wrap(`{{c${highestCloze}::`, "}}");
|
wrap(`{{c${highestCloze}::`, "}}");
|
||||||
}
|
}
|
||||||
|
|
||||||
const iconButton = dynamicComponent<typeof IconButton, IconButtonProps>(IconButton);
|
|
||||||
|
|
||||||
export function getClozeButton(): DynamicSvelteComponent<typeof IconButton> &
|
export function getClozeButton(): DynamicSvelteComponent<typeof IconButton> &
|
||||||
IconButtonProps {
|
IconButtonProps {
|
||||||
return iconButton({
|
return iconButton({
|
|
@ -1,13 +1,10 @@
|
||||||
// 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 IconButton from "./IconButton.svelte";
|
import type ButtonGroup from "editor-toolbar/ButtonGroup.svelte";
|
||||||
import type { IconButtonProps } from "./IconButton";
|
import type { ButtonGroupProps } from "editor-toolbar/ButtonGroup";
|
||||||
import ColorPicker from "./ColorPicker.svelte";
|
import type { DynamicSvelteComponent } from "sveltelib/dynamicComponent";
|
||||||
import type { ColorPickerProps } from "./ColorPicker";
|
|
||||||
import ButtonGroup from "./ButtonGroup.svelte";
|
|
||||||
import type { ButtonGroupProps } from "./ButtonGroup";
|
|
||||||
|
|
||||||
import { DynamicSvelteComponent, dynamicComponent } from "sveltelib/dynamicComponent";
|
import { iconButton, colorPicker, buttonGroup } from "editor-toolbar/dynamicComponents";
|
||||||
import * as tr from "anki/i18n";
|
import * as tr from "anki/i18n";
|
||||||
|
|
||||||
import squareFillIcon from "./square-fill.svg";
|
import squareFillIcon from "./square-fill.svg";
|
||||||
|
@ -27,10 +24,6 @@ function wrapWithForecolor(color: string): void {
|
||||||
document.execCommand("forecolor", false, color);
|
document.execCommand("forecolor", false, color);
|
||||||
}
|
}
|
||||||
|
|
||||||
const iconButton = dynamicComponent<typeof IconButton, IconButtonProps>(IconButton);
|
|
||||||
const colorPicker = dynamicComponent<typeof ColorPicker, ColorPickerProps>(ColorPicker);
|
|
||||||
const buttonGroup = dynamicComponent<typeof ButtonGroup, ButtonGroupProps>(ButtonGroup);
|
|
||||||
|
|
||||||
export function getColorGroup(): DynamicSvelteComponent<typeof ButtonGroup> &
|
export function getColorGroup(): DynamicSvelteComponent<typeof ButtonGroup> &
|
||||||
ButtonGroupProps {
|
ButtonGroupProps {
|
||||||
const forecolorButton = iconButton({
|
const forecolorButton = iconButton({
|
|
@ -16,6 +16,16 @@ anki-editable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
|
||||||
|
&:empty::after {
|
||||||
|
content: "\a";
|
||||||
|
white-space: pre;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
:host-context(.nightMode) * {
|
:host-context(.nightMode) * {
|
||||||
@include scrollbar.night-mode;
|
@include scrollbar.night-mode;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
import type { Editable } from "./editable";
|
import type { Editable } from "./editable";
|
||||||
|
|
||||||
|
import { updateActiveButtons } from "editor-toolbar";
|
||||||
import { bridgeCommand } from "./lib";
|
import { bridgeCommand } from "./lib";
|
||||||
import { onInput, onKey, onKeyUp } from "./inputHandlers";
|
import { onInput, onKey, onKeyUp } from "./inputHandlers";
|
||||||
import { onFocus, onBlur } from "./focusHandlers";
|
import { onFocus, onBlur } from "./focusHandlers";
|
||||||
|
@ -59,8 +60,7 @@ export class EditingArea extends HTMLDivElement {
|
||||||
this.addEventListener("paste", onPaste);
|
this.addEventListener("paste", onPaste);
|
||||||
this.addEventListener("copy", onCutOrCopy);
|
this.addEventListener("copy", onCutOrCopy);
|
||||||
this.addEventListener("oncut", onCutOrCopy);
|
this.addEventListener("oncut", onCutOrCopy);
|
||||||
// @ts-expect-error
|
this.addEventListener("mouseup", updateActiveButtons);
|
||||||
this.addEventListener("mouseup", editorToolbar.updateActiveButtons);
|
|
||||||
|
|
||||||
const baseStyleSheet = this.baseStyle.sheet as CSSStyleSheet;
|
const baseStyleSheet = this.baseStyle.sheet as CSSStyleSheet;
|
||||||
baseStyleSheet.insertRule("anki-editable {}", 0);
|
baseStyleSheet.insertRule("anki-editable {}", 0);
|
||||||
|
@ -75,8 +75,7 @@ export class EditingArea extends HTMLDivElement {
|
||||||
this.removeEventListener("paste", onPaste);
|
this.removeEventListener("paste", onPaste);
|
||||||
this.removeEventListener("copy", onCutOrCopy);
|
this.removeEventListener("copy", onCutOrCopy);
|
||||||
this.removeEventListener("oncut", onCutOrCopy);
|
this.removeEventListener("oncut", onCutOrCopy);
|
||||||
// @ts-expect-error
|
this.removeEventListener("mouseup", updateActiveButtons);
|
||||||
this.removeEventListener("mouseup", editorToolbar.updateActiveButtons);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
initialize(color: string, content: string): void {
|
initialize(color: string, content: string): void {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// 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 { enableButtons, disableButtons } from "editor-toolbar";
|
||||||
import type { EditingArea } from "./editingArea";
|
import type { EditingArea } from "./editingArea";
|
||||||
|
|
||||||
import { saveField } from "./changeTimer";
|
import { saveField } from "./changeTimer";
|
||||||
|
@ -10,8 +11,7 @@ export function onFocus(evt: FocusEvent): void {
|
||||||
const currentField = evt.currentTarget as EditingArea;
|
const currentField = evt.currentTarget as EditingArea;
|
||||||
currentField.focusEditable();
|
currentField.focusEditable();
|
||||||
bridgeCommand(`focus:${currentField.ord}`);
|
bridgeCommand(`focus:${currentField.ord}`);
|
||||||
// @ts-expect-error
|
enableButtons();
|
||||||
editorToolbar.enableButtons();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function onBlur(evt: FocusEvent): void {
|
export function onBlur(evt: FocusEvent): void {
|
||||||
|
@ -19,6 +19,5 @@ export function onBlur(evt: FocusEvent): void {
|
||||||
const currentFieldUnchanged = previousFocus === document.activeElement;
|
const currentFieldUnchanged = previousFocus === document.activeElement;
|
||||||
|
|
||||||
saveField(previousFocus, currentFieldUnchanged ? "key" : "blur");
|
saveField(previousFocus, currentFieldUnchanged ? "key" : "blur");
|
||||||
// @ts-expect-error
|
disableButtons();
|
||||||
editorToolbar.disableButtons();
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,23 @@
|
||||||
// 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 ButtonGroup from "./ButtonGroup.svelte";
|
import type ButtonGroup from "editor-toolbar/ButtonGroup.svelte";
|
||||||
import type { ButtonGroupProps } from "./ButtonGroup";
|
import type { ButtonGroupProps } from "editor-toolbar/ButtonGroup";
|
||||||
import ButtonDropdown from "./ButtonDropdown.svelte";
|
import type ButtonDropdown from "editor-toolbar/ButtonDropdown.svelte";
|
||||||
import type { ButtonDropdownProps } from "./ButtonDropdown";
|
import type { ButtonDropdownProps } from "editor-toolbar/ButtonDropdown";
|
||||||
import WithDropdownMenu from "./WithDropdownMenu.svelte";
|
import type { DynamicSvelteComponent } from "sveltelib/dynamicComponent";
|
||||||
import type { WithDropdownMenuProps } from "./WithDropdownMenu";
|
|
||||||
|
|
||||||
import CommandIconButton from "./CommandIconButton.svelte";
|
import type { EditingArea } from "./editingArea";
|
||||||
import type { CommandIconButtonProps } from "./CommandIconButton";
|
|
||||||
import IconButton from "./IconButton.svelte";
|
|
||||||
import type { IconButtonProps } from "./IconButton";
|
|
||||||
|
|
||||||
import { DynamicSvelteComponent, dynamicComponent } from "sveltelib/dynamicComponent";
|
|
||||||
import * as tr from "anki/i18n";
|
import * as tr from "anki/i18n";
|
||||||
|
import {
|
||||||
|
commandIconButton,
|
||||||
|
iconButton,
|
||||||
|
buttonGroup,
|
||||||
|
buttonDropdown,
|
||||||
|
withDropdownMenu,
|
||||||
|
} from "editor-toolbar/dynamicComponents";
|
||||||
|
|
||||||
|
import { getListItem } from "./helpers";
|
||||||
|
|
||||||
import ulIcon from "./list-ul.svg";
|
import ulIcon from "./list-ul.svg";
|
||||||
import olIcon from "./list-ol.svg";
|
import olIcon from "./list-ol.svg";
|
||||||
|
@ -27,20 +31,19 @@ import justifyCenterIcon from "./text-center.svg";
|
||||||
import indentIcon from "./text-indent-left.svg";
|
import indentIcon from "./text-indent-left.svg";
|
||||||
import outdentIcon from "./text-indent-right.svg";
|
import outdentIcon from "./text-indent-right.svg";
|
||||||
|
|
||||||
const commandIconButton = dynamicComponent<
|
const outdentListItem = () => {
|
||||||
typeof CommandIconButton,
|
const currentField = document.activeElement as EditingArea;
|
||||||
CommandIconButtonProps
|
if (getListItem(currentField.shadowRoot!)) {
|
||||||
>(CommandIconButton);
|
document.execCommand("outdent");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const buttonGroup = dynamicComponent<typeof ButtonGroup, ButtonGroupProps>(ButtonGroup);
|
const indentListItem = () => {
|
||||||
const buttonDropdown = dynamicComponent<typeof ButtonDropdown, ButtonDropdownProps>(
|
const currentField = document.activeElement as EditingArea;
|
||||||
ButtonDropdown
|
if (getListItem(currentField.shadowRoot!)) {
|
||||||
);
|
document.execCommand("indent");
|
||||||
|
}
|
||||||
const withDropdownMenu = dynamicComponent<
|
};
|
||||||
typeof WithDropdownMenu,
|
|
||||||
WithDropdownMenuProps
|
|
||||||
>(WithDropdownMenu);
|
|
||||||
|
|
||||||
export function getFormatBlockMenus(): (DynamicSvelteComponent<typeof ButtonDropdown> &
|
export function getFormatBlockMenus(): (DynamicSvelteComponent<typeof ButtonDropdown> &
|
||||||
ButtonDropdownProps)[] {
|
ButtonDropdownProps)[] {
|
||||||
|
@ -78,18 +81,16 @@ export function getFormatBlockMenus(): (DynamicSvelteComponent<typeof ButtonDrop
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
const outdentButton = commandIconButton({
|
const outdentButton = iconButton({
|
||||||
icon: outdentIcon,
|
icon: outdentIcon,
|
||||||
command: "outdent",
|
onClick: outdentListItem,
|
||||||
tooltip: tr.editingOutdent(),
|
tooltip: tr.editingOutdent(),
|
||||||
activatable: false,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const indentButton = commandIconButton({
|
const indentButton = iconButton({
|
||||||
icon: indentIcon,
|
icon: indentIcon,
|
||||||
command: "indent",
|
onClick: indentListItem,
|
||||||
tooltip: tr.editingIndent(),
|
tooltip: tr.editingIndent(),
|
||||||
activatable: false,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const indentationGroup = buttonGroup({
|
const indentationGroup = buttonGroup({
|
||||||
|
@ -105,8 +106,6 @@ export function getFormatBlockMenus(): (DynamicSvelteComponent<typeof ButtonDrop
|
||||||
return [formattingOptions];
|
return [formattingOptions];
|
||||||
}
|
}
|
||||||
|
|
||||||
const iconButton = dynamicComponent<typeof IconButton, IconButtonProps>(IconButton);
|
|
||||||
|
|
||||||
export function getFormatBlockGroup(): DynamicSvelteComponent<typeof ButtonGroup> &
|
export function getFormatBlockGroup(): DynamicSvelteComponent<typeof ButtonGroup> &
|
||||||
ButtonGroupProps {
|
ButtonGroupProps {
|
||||||
const ulButton = commandIconButton({
|
const ulButton = commandIconButton({
|
||||||
|
@ -131,7 +130,7 @@ export function getFormatBlockGroup(): DynamicSvelteComponent<typeof ButtonGroup
|
||||||
});
|
});
|
||||||
|
|
||||||
return buttonGroup({
|
return buttonGroup({
|
||||||
id: "formatBlock",
|
id: "blockFormatting",
|
||||||
buttons: [ulButton, olButton, listFormatting],
|
buttons: [ulButton, olButton, listFormatting],
|
||||||
});
|
});
|
||||||
}
|
}
|
|
@ -1,12 +1,15 @@
|
||||||
// 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 CommandIconButton from "./CommandIconButton.svelte";
|
import type ButtonGroup from "editor-toolbar/ButtonGroup.svelte";
|
||||||
import type { CommandIconButtonProps } from "./CommandIconButton";
|
import type { ButtonGroupProps } from "editor-toolbar/ButtonGroup";
|
||||||
import ButtonGroup from "./ButtonGroup.svelte";
|
import type { DynamicSvelteComponent } from "sveltelib/dynamicComponent";
|
||||||
import type { ButtonGroupProps } from "./ButtonGroup";
|
|
||||||
|
|
||||||
import { DynamicSvelteComponent, dynamicComponent } from "sveltelib/dynamicComponent";
|
|
||||||
import * as tr from "anki/i18n";
|
import * as tr from "anki/i18n";
|
||||||
|
import {
|
||||||
|
commandIconButton,
|
||||||
|
iconButton,
|
||||||
|
buttonGroup,
|
||||||
|
} from "editor-toolbar/dynamicComponents";
|
||||||
|
|
||||||
import boldIcon from "./type-bold.svg";
|
import boldIcon from "./type-bold.svg";
|
||||||
import italicIcon from "./type-italic.svg";
|
import italicIcon from "./type-italic.svg";
|
||||||
|
@ -15,53 +18,48 @@ import superscriptIcon from "./format-superscript.svg";
|
||||||
import subscriptIcon from "./format-subscript.svg";
|
import subscriptIcon from "./format-subscript.svg";
|
||||||
import eraserIcon from "./eraser.svg";
|
import eraserIcon from "./eraser.svg";
|
||||||
|
|
||||||
const commandIconButton = dynamicComponent<
|
|
||||||
typeof CommandIconButton,
|
|
||||||
CommandIconButtonProps
|
|
||||||
>(CommandIconButton);
|
|
||||||
const buttonGroup = dynamicComponent<typeof ButtonGroup, ButtonGroupProps>(ButtonGroup);
|
|
||||||
|
|
||||||
export function getFormatInlineGroup(): DynamicSvelteComponent<typeof ButtonGroup> &
|
export function getFormatInlineGroup(): DynamicSvelteComponent<typeof ButtonGroup> &
|
||||||
ButtonGroupProps {
|
ButtonGroupProps {
|
||||||
const boldButton = commandIconButton({
|
const boldButton = commandIconButton({
|
||||||
icon: boldIcon,
|
icon: boldIcon,
|
||||||
command: "bold",
|
|
||||||
tooltip: tr.editingBoldTextCtrlandb(),
|
tooltip: tr.editingBoldTextCtrlandb(),
|
||||||
|
command: "bold",
|
||||||
});
|
});
|
||||||
|
|
||||||
const italicButton = commandIconButton({
|
const italicButton = commandIconButton({
|
||||||
icon: italicIcon,
|
icon: italicIcon,
|
||||||
command: "italic",
|
|
||||||
tooltip: tr.editingItalicTextCtrlandi(),
|
tooltip: tr.editingItalicTextCtrlandi(),
|
||||||
|
command: "italic",
|
||||||
});
|
});
|
||||||
|
|
||||||
const underlineButton = commandIconButton({
|
const underlineButton = commandIconButton({
|
||||||
icon: underlineIcon,
|
icon: underlineIcon,
|
||||||
command: "underline",
|
|
||||||
tooltip: tr.editingUnderlineTextCtrlandu(),
|
tooltip: tr.editingUnderlineTextCtrlandu(),
|
||||||
|
command: "underline",
|
||||||
});
|
});
|
||||||
|
|
||||||
const superscriptButton = commandIconButton({
|
const superscriptButton = commandIconButton({
|
||||||
icon: superscriptIcon,
|
icon: superscriptIcon,
|
||||||
command: "superscript",
|
|
||||||
tooltip: tr.editingSuperscriptCtrlandand(),
|
tooltip: tr.editingSuperscriptCtrlandand(),
|
||||||
|
command: "superscript",
|
||||||
});
|
});
|
||||||
|
|
||||||
const subscriptButton = commandIconButton({
|
const subscriptButton = commandIconButton({
|
||||||
icon: subscriptIcon,
|
icon: subscriptIcon,
|
||||||
command: "subscript",
|
|
||||||
tooltip: tr.editingSubscriptCtrland(),
|
tooltip: tr.editingSubscriptCtrland(),
|
||||||
|
command: "subscript",
|
||||||
});
|
});
|
||||||
|
|
||||||
const removeFormatButton = commandIconButton({
|
const removeFormatButton = iconButton({
|
||||||
icon: eraserIcon,
|
icon: eraserIcon,
|
||||||
command: "removeFormat",
|
|
||||||
activatable: false,
|
|
||||||
tooltip: tr.editingRemoveFormattingCtrlandr(),
|
tooltip: tr.editingRemoveFormattingCtrlandr(),
|
||||||
|
onClick: () => {
|
||||||
|
document.execCommand("removeFormat");
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return buttonGroup({
|
return buttonGroup({
|
||||||
id: "formatInline",
|
id: "inlineFormatting",
|
||||||
buttons: [
|
buttons: [
|
||||||
boldButton,
|
boldButton,
|
||||||
italicButton,
|
italicButton,
|
|
@ -77,3 +77,36 @@ export function caretToEnd(currentField: EditingArea): void {
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
selection.addRange(range);
|
selection.addRange(range);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getAnchorParent = <T extends Element>(
|
||||||
|
predicate: (element: Element) => element is T
|
||||||
|
) => (currentField: DocumentOrShadowRoot): T | null => {
|
||||||
|
const anchor = currentField.getSelection()?.anchorNode;
|
||||||
|
|
||||||
|
if (!anchor) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let anchorParent: T | null = null;
|
||||||
|
let element = nodeIsElement(anchor) ? anchor : anchor.parentElement;
|
||||||
|
|
||||||
|
while (element) {
|
||||||
|
anchorParent = anchorParent || (predicate(element) ? element : null);
|
||||||
|
element = element.parentElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
return anchorParent;
|
||||||
|
};
|
||||||
|
|
||||||
|
const isListItem = (element: Element): element is HTMLLIElement =>
|
||||||
|
window.getComputedStyle(element).display === "list-item";
|
||||||
|
const isParagraph = (element: Element): element is HTMLParamElement =>
|
||||||
|
element.tagName === "P";
|
||||||
|
const isBlockElement = (
|
||||||
|
element: Element
|
||||||
|
): element is HTMLLIElement & HTMLParamElement =>
|
||||||
|
isListItem(element) || isParagraph(element);
|
||||||
|
|
||||||
|
export const getListItem = getAnchorParent(isListItem);
|
||||||
|
export const getParagraph = getAnchorParent(isParagraph);
|
||||||
|
export const getBlockElement = getAnchorParent(isBlockElement);
|
||||||
|
|
|
@ -2,6 +2,10 @@
|
||||||
// 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 { filterHTML } from "html-filter";
|
import { filterHTML } from "html-filter";
|
||||||
|
import { updateActiveButtons, disableButtons } from "editor-toolbar";
|
||||||
|
import { setupI18n, ModuleName } from "anki/i18n";
|
||||||
|
|
||||||
|
import "./fields.css";
|
||||||
|
|
||||||
import { caretToEnd } from "./helpers";
|
import { caretToEnd } from "./helpers";
|
||||||
import { saveField } from "./changeTimer";
|
import { saveField } from "./changeTimer";
|
||||||
|
@ -10,11 +14,14 @@ import { EditorField } from "./editorField";
|
||||||
import { LabelContainer } from "./labelContainer";
|
import { LabelContainer } from "./labelContainer";
|
||||||
import { EditingArea } from "./editingArea";
|
import { EditingArea } from "./editingArea";
|
||||||
import { Editable } from "./editable";
|
import { Editable } from "./editable";
|
||||||
|
import { initToolbar } from "./toolbar";
|
||||||
|
|
||||||
export { setNoteId, getNoteId } from "./noteId";
|
export { setNoteId, getNoteId } from "./noteId";
|
||||||
export { saveNow } from "./changeTimer";
|
export { saveNow } from "./changeTimer";
|
||||||
export { wrap, wrapIntoText } from "./wrap";
|
export { wrap, wrapIntoText } from "./wrap";
|
||||||
|
|
||||||
|
export * from "./addons";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Selection {
|
interface Selection {
|
||||||
modify(s: string, t: string, u: string): void;
|
modify(s: string, t: string, u: string): void;
|
||||||
|
@ -24,6 +31,7 @@ declare global {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
import "editor-toolbar";
|
||||||
customElements.define("anki-editable", Editable);
|
customElements.define("anki-editable", Editable);
|
||||||
customElements.define("anki-editing-area", EditingArea, { extends: "div" });
|
customElements.define("anki-editing-area", EditingArea, { extends: "div" });
|
||||||
customElements.define("anki-label-container", LabelContainer, { extends: "div" });
|
customElements.define("anki-label-container", LabelContainer, { extends: "div" });
|
||||||
|
@ -41,8 +49,7 @@ export function focusField(n: number): void {
|
||||||
if (field) {
|
if (field) {
|
||||||
field.editingArea.focusEditable();
|
field.editingArea.focusEditable();
|
||||||
caretToEnd(field.editingArea);
|
caretToEnd(field.editingArea);
|
||||||
// @ts-expect-error
|
updateActiveButtons();
|
||||||
editorToolbar.updateActiveButtons();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,8 +129,7 @@ export function setFields(fields: [string, string][]): void {
|
||||||
|
|
||||||
if (!getCurrentField()) {
|
if (!getCurrentField()) {
|
||||||
// when initial focus of the window is not on editor (e.g. browser)
|
// when initial focus of the window is not on editor (e.g. browser)
|
||||||
// @ts-expect-error
|
disableButtons();
|
||||||
editorToolbar.disableButtons();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -158,7 +164,10 @@ export function setFormat(cmd: string, arg?: any, nosave: boolean = false): void
|
||||||
document.execCommand(cmd, false, arg);
|
document.execCommand(cmd, false, arg);
|
||||||
if (!nosave) {
|
if (!nosave) {
|
||||||
saveField(getCurrentField() as EditingArea, "key");
|
saveField(getCurrentField() as EditingArea, "key");
|
||||||
// @ts-expect-error
|
updateActiveButtons();
|
||||||
editorToolbar.updateActiveButtons();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const i18n = setupI18n({ modules: [ModuleName.EDITING] });
|
||||||
|
|
||||||
|
initToolbar(i18n);
|
||||||
|
|
|
@ -1,28 +1,15 @@
|
||||||
// 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 { updateActiveButtons } from "editor-toolbar";
|
||||||
import { EditingArea } from "./editingArea";
|
import { EditingArea } from "./editingArea";
|
||||||
import { caretToEnd, nodeIsElement } from "./helpers";
|
import { caretToEnd, nodeIsElement, getBlockElement } from "./helpers";
|
||||||
import { triggerChangeTimer } from "./changeTimer";
|
import { triggerChangeTimer } from "./changeTimer";
|
||||||
|
|
||||||
function inListItem(currentField: EditingArea): boolean {
|
|
||||||
const anchor = currentField.getSelection()!.anchorNode!;
|
|
||||||
|
|
||||||
let inList = false;
|
|
||||||
let n = nodeIsElement(anchor) ? anchor : anchor.parentElement;
|
|
||||||
while (n) {
|
|
||||||
inList = inList || window.getComputedStyle(n).display == "list-item";
|
|
||||||
n = n.parentElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
return inList;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function onInput(event: Event): void {
|
export function onInput(event: Event): void {
|
||||||
// make sure IME changes get saved
|
// make sure IME changes get saved
|
||||||
triggerChangeTimer(event.currentTarget as EditingArea);
|
triggerChangeTimer(event.currentTarget as EditingArea);
|
||||||
// @ts-ignore
|
updateActiveButtons();
|
||||||
editorToolbar.updateActiveButtons();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function onKey(evt: KeyboardEvent): void {
|
export function onKey(evt: KeyboardEvent): void {
|
||||||
|
@ -35,7 +22,10 @@ export function onKey(evt: KeyboardEvent): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
// prefer <br> instead of <div></div>
|
// prefer <br> instead of <div></div>
|
||||||
if (evt.code === "Enter" && !inListItem(currentField)) {
|
if (
|
||||||
|
evt.code === "Enter" &&
|
||||||
|
!getBlockElement(currentField.shadowRoot!) !== evt.shiftKey
|
||||||
|
) {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
document.execCommand("insertLineBreak");
|
document.execCommand("insertLineBreak");
|
||||||
}
|
}
|
||||||
|
@ -69,8 +59,7 @@ globalThis.addEventListener("keydown", (evt: KeyboardEvent) => {
|
||||||
const newFocusTarget = evt.target;
|
const newFocusTarget = evt.target;
|
||||||
if (newFocusTarget instanceof EditingArea) {
|
if (newFocusTarget instanceof EditingArea) {
|
||||||
caretToEnd(newFocusTarget);
|
caretToEnd(newFocusTarget);
|
||||||
// @ts-ignore
|
updateActiveButtons();
|
||||||
editorToolbar.updateActiveButtons();
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ once: true }
|
{ once: true }
|
||||||
|
|
|
@ -1,16 +1,12 @@
|
||||||
// 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 LabelButton from "./LabelButton.svelte";
|
import type ButtonGroup from "editor-toolbar/ButtonGroup.svelte";
|
||||||
import type { LabelButtonProps } from "./LabelButton";
|
import type { ButtonGroupProps } from "editor-toolbar/ButtonGroup";
|
||||||
import ButtonGroup from "./ButtonGroup.svelte";
|
import type { DynamicSvelteComponent } from "sveltelib/dynamicComponent";
|
||||||
import type { ButtonGroupProps } from "./ButtonGroup";
|
|
||||||
|
|
||||||
import { DynamicSvelteComponent, dynamicComponent } from "sveltelib/dynamicComponent";
|
|
||||||
import { bridgeCommand } from "anki/bridgecommand";
|
import { bridgeCommand } from "anki/bridgecommand";
|
||||||
import * as tr from "anki/i18n";
|
import * as tr from "anki/i18n";
|
||||||
|
import { labelButton, buttonGroup } from "editor-toolbar/dynamicComponents";
|
||||||
const labelButton = dynamicComponent<typeof LabelButton, LabelButtonProps>(LabelButton);
|
|
||||||
const buttonGroup = dynamicComponent<typeof ButtonGroup, ButtonGroupProps>(ButtonGroup);
|
|
||||||
|
|
||||||
export function getNotetypeGroup(): DynamicSvelteComponent<typeof ButtonGroup> &
|
export function getNotetypeGroup(): DynamicSvelteComponent<typeof ButtonGroup> &
|
||||||
ButtonGroupProps {
|
ButtonGroupProps {
|
|
@ -1,20 +1,23 @@
|
||||||
// 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 IconButton from "./IconButton.svelte";
|
import type DropdownMenu from "editor-toolbar/DropdownMenu.svelte";
|
||||||
import type { IconButtonProps } from "./IconButton";
|
import type { DropdownMenuProps } from "editor-toolbar/DropdownMenu";
|
||||||
import DropdownMenu from "./DropdownMenu.svelte";
|
import type ButtonGroup from "editor-toolbar/ButtonGroup.svelte";
|
||||||
import type { DropdownMenuProps } from "./DropdownMenu";
|
import type { ButtonGroupProps } from "editor-toolbar/ButtonGroup";
|
||||||
import DropdownItem from "./DropdownItem.svelte";
|
import type { DynamicSvelteComponent } from "sveltelib/dynamicComponent";
|
||||||
import type { DropdownItemProps } from "./DropdownItem";
|
|
||||||
import WithDropdownMenu from "./WithDropdownMenu.svelte";
|
|
||||||
import type { WithDropdownMenuProps } from "./WithDropdownMenu";
|
|
||||||
import ButtonGroup from "./ButtonGroup.svelte";
|
|
||||||
import type { ButtonGroupProps } from "./ButtonGroup";
|
|
||||||
|
|
||||||
import { bridgeCommand } from "anki/bridgecommand";
|
import { bridgeCommand } from "anki/bridgecommand";
|
||||||
import { DynamicSvelteComponent, dynamicComponent } from "sveltelib/dynamicComponent";
|
import {
|
||||||
|
iconButton,
|
||||||
|
withDropdownMenu,
|
||||||
|
dropdownMenu,
|
||||||
|
dropdownItem,
|
||||||
|
buttonGroup,
|
||||||
|
} from "editor-toolbar/dynamicComponents";
|
||||||
import * as tr from "anki/i18n";
|
import * as tr from "anki/i18n";
|
||||||
|
|
||||||
|
import { wrap } from "./wrap";
|
||||||
|
|
||||||
import paperclipIcon from "./paperclip.svg";
|
import paperclipIcon from "./paperclip.svg";
|
||||||
import micIcon from "./mic.svg";
|
import micIcon from "./mic.svg";
|
||||||
import functionIcon from "./function-variant.svg";
|
import functionIcon from "./function-variant.svg";
|
||||||
|
@ -36,19 +39,6 @@ function onHtmlEdit(): void {
|
||||||
|
|
||||||
const mathjaxMenuId = "mathjaxMenu";
|
const mathjaxMenuId = "mathjaxMenu";
|
||||||
|
|
||||||
const iconButton = dynamicComponent<typeof IconButton, IconButtonProps>(IconButton);
|
|
||||||
const withDropdownMenu = dynamicComponent<
|
|
||||||
typeof WithDropdownMenu,
|
|
||||||
WithDropdownMenuProps
|
|
||||||
>(WithDropdownMenu);
|
|
||||||
const dropdownMenu = dynamicComponent<typeof DropdownMenu, DropdownMenuProps>(
|
|
||||||
DropdownMenu
|
|
||||||
);
|
|
||||||
const dropdownItem = dynamicComponent<typeof DropdownItem, DropdownItemProps>(
|
|
||||||
DropdownItem
|
|
||||||
);
|
|
||||||
const buttonGroup = dynamicComponent<typeof ButtonGroup, ButtonGroupProps>(ButtonGroup);
|
|
||||||
|
|
||||||
export function getTemplateGroup(): DynamicSvelteComponent<typeof ButtonGroup> &
|
export function getTemplateGroup(): DynamicSvelteComponent<typeof ButtonGroup> &
|
||||||
ButtonGroupProps {
|
ButtonGroupProps {
|
||||||
const attachmentButton = iconButton({
|
const attachmentButton = iconButton({
|
||||||
|
@ -95,19 +85,16 @@ export function getTemplateMenus(): (DynamicSvelteComponent<typeof DropdownMenu>
|
||||||
DropdownMenuProps)[] {
|
DropdownMenuProps)[] {
|
||||||
const mathjaxMenuItems = [
|
const mathjaxMenuItems = [
|
||||||
dropdownItem({
|
dropdownItem({
|
||||||
// @ts-expect-error
|
|
||||||
onClick: () => wrap("\\(", "\\)"),
|
onClick: () => wrap("\\(", "\\)"),
|
||||||
label: tr.editingMathjaxInline(),
|
label: tr.editingMathjaxInline(),
|
||||||
endLabel: "Ctrl+M, M",
|
endLabel: "Ctrl+M, M",
|
||||||
}),
|
}),
|
||||||
dropdownItem({
|
dropdownItem({
|
||||||
// @ts-expect-error
|
|
||||||
onClick: () => wrap("\\[", "\\]"),
|
onClick: () => wrap("\\[", "\\]"),
|
||||||
label: tr.editingMathjaxBlock(),
|
label: tr.editingMathjaxBlock(),
|
||||||
endLabel: "Ctrl+M, E",
|
endLabel: "Ctrl+M, E",
|
||||||
}),
|
}),
|
||||||
dropdownItem({
|
dropdownItem({
|
||||||
// @ts-expect-error
|
|
||||||
onClick: () => wrap("\\(\\ce{", "}\\)"),
|
onClick: () => wrap("\\(\\ce{", "}\\)"),
|
||||||
label: tr.editingMathjaxChemistry(),
|
label: tr.editingMathjaxChemistry(),
|
||||||
endLabel: "Ctrl+M, C",
|
endLabel: "Ctrl+M, C",
|
||||||
|
@ -116,19 +103,16 @@ export function getTemplateMenus(): (DynamicSvelteComponent<typeof DropdownMenu>
|
||||||
|
|
||||||
const latexMenuItems = [
|
const latexMenuItems = [
|
||||||
dropdownItem({
|
dropdownItem({
|
||||||
// @ts-expect-error
|
|
||||||
onClick: () => wrap("[latex]", "[/latex]"),
|
onClick: () => wrap("[latex]", "[/latex]"),
|
||||||
label: tr.editingLatex(),
|
label: tr.editingLatex(),
|
||||||
endLabel: "Ctrl+T, T",
|
endLabel: "Ctrl+T, T",
|
||||||
}),
|
}),
|
||||||
dropdownItem({
|
dropdownItem({
|
||||||
// @ts-expect-error
|
|
||||||
onClick: () => wrap("[$]", "[/$]"),
|
onClick: () => wrap("[$]", "[/$]"),
|
||||||
label: tr.editingLatexEquation(),
|
label: tr.editingLatexEquation(),
|
||||||
endLabel: "Ctrl+T, E",
|
endLabel: "Ctrl+T, E",
|
||||||
}),
|
}),
|
||||||
dropdownItem({
|
dropdownItem({
|
||||||
// @ts-expect-error
|
|
||||||
onClick: () => wrap("[$$]", "[/$$]"),
|
onClick: () => wrap("[$$]", "[/$$]"),
|
||||||
label: tr.editingLatexMathEnv(),
|
label: tr.editingLatexMathEnv(),
|
||||||
endLabel: "Ctrl+T, M",
|
endLabel: "Ctrl+T, M",
|
45
ts/editor/toolbar.ts
Normal file
45
ts/editor/toolbar.ts
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
// Copyright: Ankitects Pty Ltd and contributors
|
||||||
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
import type { ToolbarItem } from "editor-toolbar/types";
|
||||||
|
import type ButtonGroup from "editor-toolbar/ButtonGroup.svelte";
|
||||||
|
import type { ButtonGroupProps } from "editor-toolbar/ButtonGroup";
|
||||||
|
import type { Writable } from "svelte/store";
|
||||||
|
|
||||||
|
import { getNotetypeGroup } from "./notetype";
|
||||||
|
import { getFormatInlineGroup } from "./formatInline";
|
||||||
|
import { getFormatBlockGroup, getFormatBlockMenus } from "./formatBlock";
|
||||||
|
import { getColorGroup } from "./color";
|
||||||
|
import { getTemplateGroup, getTemplateMenus } from "./template";
|
||||||
|
|
||||||
|
export function initToolbar(i18n: Promise<void>): void {
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
i18n.then(() => {
|
||||||
|
globalThis.$editorToolbar.buttonsPromise.then(
|
||||||
|
(
|
||||||
|
buttons: Writable<
|
||||||
|
(ToolbarItem<typeof ButtonGroup> & ButtonGroupProps)[]
|
||||||
|
>
|
||||||
|
): Writable<(ToolbarItem<typeof ButtonGroup> & ButtonGroupProps)[]> => {
|
||||||
|
buttons.update(() => [
|
||||||
|
getNotetypeGroup(),
|
||||||
|
getFormatInlineGroup(),
|
||||||
|
getFormatBlockGroup(),
|
||||||
|
getColorGroup(),
|
||||||
|
getTemplateGroup(),
|
||||||
|
]);
|
||||||
|
return buttons;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
globalThis.$editorToolbar.menusPromise.then(
|
||||||
|
(menus: Writable<ToolbarItem[]>): Writable<ToolbarItem[]> => {
|
||||||
|
menus.update(() => [
|
||||||
|
...getFormatBlockMenus(),
|
||||||
|
...getTemplateMenus(),
|
||||||
|
]);
|
||||||
|
return menus;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
|
@ -5,11 +5,11 @@ load("//ts:eslint.bzl", "eslint_test")
|
||||||
|
|
||||||
ts_library(
|
ts_library(
|
||||||
name = "html-filter",
|
name = "html-filter",
|
||||||
|
module_name = "html-filter",
|
||||||
srcs = glob(
|
srcs = glob(
|
||||||
["*.ts"],
|
["*.ts"],
|
||||||
exclude = ["*.test.ts"],
|
exclude = ["*.test.ts"],
|
||||||
),
|
),
|
||||||
module_name = "html-filter",
|
|
||||||
tsconfig = "//ts:tsconfig.json",
|
tsconfig = "//ts:tsconfig.json",
|
||||||
visibility = ["//visibility:public"],
|
visibility = ["//visibility:public"],
|
||||||
deps = [],
|
deps = [],
|
||||||
|
|
|
@ -63,17 +63,19 @@ svelte = rule(
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
def compile_svelte(name, srcs, deps = []):
|
def compile_svelte(name, srcs, deps = [], visibility = ["//visibility:private"]):
|
||||||
for src in srcs:
|
for src in srcs:
|
||||||
svelte(
|
svelte(
|
||||||
name = src.replace(".svelte", ""),
|
name = src.replace(".svelte", ""),
|
||||||
entry_point = src,
|
entry_point = src,
|
||||||
deps = deps,
|
deps = deps,
|
||||||
|
visibility = visibility,
|
||||||
)
|
)
|
||||||
|
|
||||||
native.filegroup(
|
native.filegroup(
|
||||||
name = name,
|
name = name,
|
||||||
srcs = srcs,
|
srcs = srcs,
|
||||||
|
visibility = visibility,
|
||||||
)
|
)
|
||||||
|
|
||||||
def svelte_check(name = "svelte_check", srcs = []):
|
def svelte_check(name = "svelte_check", srcs = []):
|
||||||
|
|
|
@ -8,7 +8,8 @@
|
||||||
"paths": {
|
"paths": {
|
||||||
"anki/*": ["../bazel-bin/ts/lib/*"],
|
"anki/*": ["../bazel-bin/ts/lib/*"],
|
||||||
"sveltelib/*": ["../bazel-bin/ts/sveltelib/*"],
|
"sveltelib/*": ["../bazel-bin/ts/sveltelib/*"],
|
||||||
"html-filter/*": ["../bazel-bin/ts/html-filter/*"]
|
"html-filter/*": ["../bazel-bin/ts/html-filter/*"],
|
||||||
|
"editor-toolbar/*": ["../bazel-bin/ts/editor-toolbar/*"]
|
||||||
},
|
},
|
||||||
"importsNotUsedAsValues": "error",
|
"importsNotUsedAsValues": "error",
|
||||||
"outDir": "dist",
|
"outDir": "dist",
|
||||||
|
|
Loading…
Reference in a new issue