-
Notifications
You must be signed in to change notification settings - Fork 2.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Svelte Editor Toolbar #1109
Svelte Editor Toolbar #1109
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My two initial impressions:
- there's a lot of good functionality here
- the use of generic Svelte components, stores, and lazy property setting makes the code hard to follow, and loses a lot of type information, and I wonder whether it could be simplified.
For example, withLazyProperties(). The editor toolbar is not instantiated until after i18n has loaded; couldn't we just defer building the components until after that point, like we do with the graphs? By dynamically injecting getters into the components, we're losing the static type checking TypeScript provides us, and the declarations are not as clear.
For the writable stores for the buttons, what goals are we trying to solve? The two I can think of are:
- buttons that hide and show depending on state
- the ability for add-ons to modify the available buttons
Are there others I've missed?
Could we solve those goals with a static list that is created after i18n setup instead?
- for buttons that may not always apply, what if they remained in the list, but just hide or show themselves as appropriate?
- for add-ons, adding new buttons seems to be the main use case? Are there any add-ons that modify the order or presence of the standard Anki buttons, and is that something we really need to support?
I am wondering if we could skip the whole writable store business in favour of a more standard Svelte declarative approach. Eg, having the toolbar include components like
<FormatButtons />
which in turn lists out the buttons in the standard way:
<CommandIconButton icon={boldIcon} command="bold" tooltip={tr.editingBoldTextCtrlandb()} />
<CommandIconButton icon={italicIcon} command="italic" tooltip={tr.editingBoldTextCtrlandi()} />
...
and at the end of the standard buttons, we can extend the list by iterating over some global that has been modified by add-ons, eg
anki.addons.additionalFormatButtons: (string | SvelteComponent)[]
The string case would be raw HTML, which we can add with {@html}. We could modify Python's _addButton() to transparently add the HTML to that JS list, which would hopefully mean (most) existing add-ons continue to work without changes.
I know your stores-based approach is more flexible, but I feel like it comes at a cost in terms of type checking and ability to reason about the code easily, and I wonder whether it's worth it? What do you think?
@glutanimate are you aware of add-ons where an "append only" approach to the buttons would not work?
Re some of the other points:
resized toolbar:
looks like the same image was attached twice?
Remove all unnecessary uses of bridgeCommand
I think we'd need the HTML editor as well, unless the plan was to show the HTML inline as a toggle like AnkiMobile does.
Remove the @ts-ignore. Currently Bazel will not recognise methods which were exposed via the Module context
I think this is a svelte2tsx issue rather than a Bazel one - SvelteComponent is defined as not having any exports apart from a default one.
@dae there are a few add-ons that currently prepend their buttons to the list (so that they're positioned in front of the default buttons), e.g.:
I think I also remember writing a couple of add-ons back in the 2.0 days that would position their buttons after particular default buttons, in an attempt to group buttons with similar functionalities together. Generally speaking: This looks cool, @hgiesel, both as an extensibility study and in actual use. A few thoughts purely from a bird's eye add-on dev / user standpoint and no deep dive into the implementation details:
A few thoughts regarding the toolbar API:
|
I tracked it to here sveltejs/language-tools#550
Yes true. I'd love to tackle the HTML editor in a future PR, but not this one. With
If we added a
The need I saw here wasn't necessarily changing the order or presence of standard Anki buttons, but rather for more freedom over where to place your custom button. Let's say I wanted to add a If we defined a component like so: // FormattingButtons.svelte
<CommandIconButton icon={boldIcon} command="bold" tooltip={tr.editingBoldTextCtrlandb()} />
<CommandIconButton icon={italicIcon} command="italic" tooltip={tr.editingBoldTextCtrlandi()} />
... We could not easily insert any new button in between there. I could only replace the whole (or create my own button group, recreating the old one) One possibility would be to create single files for the individual buttons, let's say // BoldButton.svelte
<CommandIconButton icon={boldIcon} command="bold" tooltip={tr.editingBoldTextCtrlandb()} /> Obviously that'd necessitate one file for each button. But we'd also be able to skip
That's a good idea and I'll implement it. I think it would be better suited in a In the code you'll see that I generally didn't pass Thanks for your suggestions, all of them sound good, especially group identifiers.
Absolutely correct. |
Thanks for elaborating and for the link, that helps me understand the issue you're trying to solve a bit better. Since we're effectively losing the typechecking when constructing components dynamically like this, how about we a) define a helper function to construct each type of component with the proper arguments, and b) turn the string properties into callbacks? Eg diff --git a/ts/editor-toolbar/CommandIconButton.svelte b/ts/editor-toolbar/CommandIconButton.svelte
index ed45d23cc..d41bce0ee 100644
--- a/ts/editor-toolbar/CommandIconButton.svelte
+++ b/ts/editor-toolbar/CommandIconButton.svelte
@@ -39,7 +39,7 @@
export let id = "";
export let className = "";
export let props: Record<string, string> = {};
- export let title: string;
+ export let tooltip: () => string;
export let icon = "";
export let command: string;
@@ -61,6 +61,6 @@
}
</script>
-<SquareButton {id} {className} {props} {title} {active} {onClick} on:mount>
+<SquareButton {id} {className} {props} title={tooltip()} {active} {onClick} on:mount>
{@html icon}
</SquareButton> and then something like import type { SvelteComponentDev } from "svelte/internal";
interface CommandIconArgs {
icon: string;
bridgeCommand: string;
tooltip: () => string;
}
interface DynamicSvelteComponent {
component: typeof SvelteComponentDev;
}
type DynamicCommandIcon = CommandIconArgs & DynamicSvelteComponent;
function commandIconButton(args: CommandIconArgs): DynamicCommandIcon {
return {
component: CommandIconButton,
...args,
};
}
const boldButton = commandIconButton({
icon: boldIcon,
bridgeCommand: "bold",
tooltip: tr.editingBoldTextCtrlandb,
});
const italicButton = commandIconButton({
icon: italicIcon,
bridgeCommand: "italic",
tooltip: tr.editingItalicTextCtrlandi,
}); I noticed
The latter part doesn't provide me with any information about what I can put in it, and I think maintenance will be a lot easier if it's clear exactly what needs to be placed in it. (As an aside, in ButtonGroup.svelte you define A well-typed writable store that avoids any generic dictionaries would largely alleviate my concerns there. But coming back to the other point for the sake of argument:
My thinking was to have each section have its own global. So add-ons wouldn't be able to insert their icon before a standard one in a particular group, but they would be able to append to the group that made most sense (eg anki.addons.additionalTemplateButtons, etc). I'm not strongly pushing for this approach, just think it is worth considering, as it would enable us to bypass the whole writable stores thing. I gather from your comments @glutanimate that targeting the relevant section seems to be the important part?
Yep, it could just use a derived variable that filters out any disabled components
For the multi-key shortcuts, I just realised that we're using cmd+m for MathJax on Macs, which conflicts with the standard minimize shortcut - Qt seems to override the default. If we try to capture it in JS, does that work, or does the window get minimized instead? Assuming we can capture it, what if the multi-shortcut keys brought up the relevant menu, eg cmd+m → shows mathjax dropdown, with each option showing the shortcut key next to it, and a subsequent keystroke activates the menu. Not sure how tricky that would be to implement though :-) |
I'd say so, yeah, but I would also very much love the ability to create new sections/groups for add-ons that add multiple buttons to the editor. But any additional configurability, i.e. in terms of the position within sections, would of course also be much appreciated and contribute towards the overall user experiences we can create. E.g. similar to what Henrik said, if my add-on adds a button that relates to audio recording, or working with clozes, as a user I would expect it to be positioned next to the buttons in question. |
@dae There are a few gotchas:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@glutanimate If you don't mind, could you check whether that fixes the scrollbars appearing on Linux?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like it's coming together!
I wanted to write the ComponentProps types inside of x.svelte, however that didn't work, because all exported objects from the module context loose their type information, which is why I put them into x.d.ts files.
I tried to minimise the amount of boilerplate, which is why there's some TS magic in sveltelib/dynamicComponent. One frustrating fact is that exported Svelte components, do not type the props themselves, which explains the need for the .d.ts files. The default value for the Props type has as its base the untyped props type (Record<string, unknown>) that is defined on the svelte component: NonNullable<ConstructorParameters[0]["props"]>. If our tooling started to type this, we could just start to use this type directly instead of Props and could remove the .d.ts files.
Yeah, both of these are unfortunate. I'm not a huge fan of React, but I do look upon its first-class TypeScript support with envy.
Since we are already using a custom rule to build the Svelte, we could potentially take matters into our own hands at one point and modify the generated .d.ts files to our needs, but I do not know how hard it would end up being, and I'm hoping if we wait a while, Svelte's tooling will improve.
I left in the logic for setting lazy properties, which is now typed however. I couldn't bring myself to delete it just yet, because I don't like how the upstream i18n implementation would impact the logic of downstream components. If you still dislike it, I'll remove it.
The fact that types are preserved now is definitely an improvement, but I can't help but feel that the solution is worse than the problem here. When you speak of "downstream components", am I understanding correctly that you're concerned add-on authors will need to write:
const customButton = commandIconButton({
icon: "custom.png,
command: "custom",
tooltip: () => "custom tooltip",
);
Instead of the following?
const customButton = commandIconButton({
icon: "custom.png,
command: "custom",
tooltip: "custom tooltip",
);
In exchange for saving those 6 characters, every button definition in Anki's codebase goes from
const boldButton = commandIconButton({
icon: boldIcon,
command: "bold",
tooltip: tr.editingBoldTextCtrlandb,
);
to
const boldButton = commandIconButton<CommandIconButtonProps, "tooltip">(
{
icon: boldIcon,
command: "bold",
},
{
tooltip: tr.editingBoldTextCtrlandb,
}
);
and to support this, we need this extra helper that uses dynamic property injection and advanced TS typing features:
export interface DynamicSvelteComponent<
T extends typeof SvelteComponentDev = typeof SvelteComponentDev
> {
component: T;
}
export const dynamicComponent = <Comp extends typeof SvelteComponentDev>(
component: Comp
) => <
Props extends NonNullable<ConstructorParameters<Comp>[0]["props"]>,
Lazy extends string = never
>(
props: Omit<Props, Lazy>,
lazyProps: {
[Property in keyof Pick<Props, Lazy>]: () => Pick<Props, Lazy>[Property];
}
): DynamicSvelteComponent<Comp> & Props => {
const dynamicComponent = { component, ...props };
for (const property in lazyProps) {
const get = lazyProps[property];
const propertyDescriptor: TypedPropertyDescriptor<
Pick<Props, Lazy>[Extract<keyof Pick<Props, Lazy>, string>]
> = { get, enumerable: true };
Object.defineProperty(dynamicComponent, property, propertyDescriptor);
}
return dynamicComponent as DynamicSvelteComponent<Comp> & Props;
};
The logic there may seem obvious having just written it, but I fear it will be a time sink in the future when we return to this part of the code and try to follow the code flow. It feels like what we're losing in terms of code clarity and maintainability isn't worth the minor inconvenience to add-on authors?
I don't have a strong preference for individual callbacks, and I think there are other ways this could be solved as well that are less magical. We could have a dynamic component be an interface of { component, propBuilder: () → Props }, or get a bit more OOPy and turn it into a factory class with a .build() method that returns an instance of the component. Not suggesting that's a better option than individual callbacks, just throwing out ideas :-)
What I'm worried here is that we're guessing which properties might be provided by external APIs in the future. So right now
(o: any, p: string | number | symbol, attributes: PropertyDescriptor & ThisType<any>): any I will think about this a bit more and the alternative implementations you suggested... EDIT: What do you think about mimicing the old
I assume when you changed the i18n API, it was so we can be more selective about which strings to load? |
Yep, that's a good point. Tree shaking was the motivation for moving to free-standing functions - if they're not at the top level, tools like esbuild don't know if they'll be used or not, and we end up including 1000+ rather verbose functions into the output JS. In your example, isn't passing tr around unnecessary? I would have thought simply placing the button definitions inside another function would be sufficient, since in either case, the i18n data should be available by the time the outer function is called. |
Ah, yes of course :) Okay, I see. In that case I'll move forward with the functions solution. |
Sounds good :-) One final comment about the example above; if we can eliminate the need for the lazy properties, maybe dynamicComponent could take the props type up front, so we don't have to pass them in to each labelButton() / buttonGroup() call? |
Thanks for making those changes Henrik, it feels much easier to follow now :-) |
3f21864
to
9222df1
Compare
ts/editor-toolbar/dropdown.scss
Outdated
@import "ts/node_modules/bootstrap/scss/functions"; | ||
@import "ts/node_modules/bootstrap/scss/variables"; | ||
@import "ts/node_modules/bootstrap/scss/mixins"; | ||
@import "ts/node_modules/bootstrap/scss/dropdown"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Most of the ts/svelte code I pushed in 7f738c1 is just me fumbling my way through and is probably not best practice :-) But one thing you may want to adopt is the way I've added a ts/bootstrap folder that exposes a sass library - that way you can add it as a dependency and import from it cleanly:
7f738c1#diff-3dfaaa14709dfa6fec287bccf73739804b971790e23d6b24dea8661c7bacd6d2R14
7f738c1#diff-eeb95d9e3e56521ca955e5a954d0d63e0063981a63bdf62404b130151e6860ebR4
@dae I had a look at the output directory, and tried the following, which passes the @import "../../../../ts/bootstrap/functions";
@import "../../../../ts/bootstrap/variables"; |
When we compile with sass_binary(), it takes care of declaring all the inputs so they trigger a recompile if necessary, and it tells sass the folders it should look for files:
When we build svelte files, we're indirectly calling the sass compiler via svelte-preprocess, and we're not declaring the sass files we depend on, which could lead to a flaky build. I think we'll need to update the svelte rule to pass these in - I will look into it. |
A short update on my progress here. I am somewhat intent on merging this PR soon, and not let it sit and grow much longer.
|
Guh, so this turns out to be a big pain in the neck. The standard way of specifying preprocess options to svelte-check is a svelte.config.js file, which doesn't seem to work when running under Bazel. Luckily sass will also take paths in an env var, so that unblocks us. Here's an example of how you can use the latest code: diff --git a/ts/deckconfig/BUILD.bazel b/ts/deckconfig/BUILD.bazel
index b53e6d0a6..0bfa900a6 100644
--- a/ts/deckconfig/BUILD.bazel
+++ b/ts/deckconfig/BUILD.bazel
@@ -24,6 +24,9 @@ svelte_names = [f.replace(".svelte", "") for f in svelte_files]
compile_svelte(
name = "svelte",
srcs = svelte_files,
+ deps = [
+ "//ts/bootstrap:scss",
+ ],
)
copy_bootstrap_icons(
@@ -112,5 +115,5 @@ svelte_check(
srcs = glob([
"*.ts",
"*.svelte",
- ]),
+ ]) + ["//ts/bootstrap:scss"],
)
diff --git a/ts/deckconfig/DeckConfigPage.svelte b/ts/deckconfig/DeckConfigPage.svelte
index a6ae44078..2ac04e93d 100644
--- a/ts/deckconfig/DeckConfigPage.svelte
+++ b/ts/deckconfig/DeckConfigPage.svelte
@@ -23,7 +23,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
let defaults = state.defaults;
</script>
-<style>
+<style lang="scss">
+ @import "ts/bootstrap/mixins";
.outer {
display: flex;
justify-content: center;
|
Merging sooner rather than later is definitely good for avoiding merge conflicts and duplicated effort. To make that easier in the future, I'd recommend trying to keep changes (mostly) isolated to start with - putting them behind a feature flag, or just not hooking them up. Provided the tests pass, it's much easier to merge such code early, as there's no risk of breaking things for existing users - while using git HEAD as a daily driver is not the best idea, some people do do that. I do realise this PR involved a lot of experimental changes and refactoring though, making it not as practical in this case.
I don't recall anyone ever asking for separate colours per note type, so I wonder if the demand is there? As an example of why this might not be a clear-cut decision, AnkiMobile stores the "scratchpad active" state separately for each card template. There have been cases where users thought the setting was being forgotten because it differed as they went through their cards, and they expected it to be a global on/off.
@glutanimate can you confirm the scrollbars went away?
yep
sounds good. One related thing that will need fixing at one point, but is not urgent: the tooltip for the MathJax buttons shows Ctrl+m on a Mac; we need to replace Ctrl with Command, doing something like aqt/utils.py:shortcut()
While I think MathJax is a better solution for most users, it doesn't fit everyone's use case, so I think we probably still need to show those options in the computer version. Some other points:
|
(I don't see any scrollbars on a Linux machine here, but did not check on Linux with the earlier commits) |
However, I still cannot seem to pass |
* Format shortcuts in monospace font and increase padding a little bit
You need to pass the sass as a dep for both the Svelte compile and the Svelte check. Sorry for the noisy diff, I've got VSCode set up to auto-format. diff --git a/ts/editor-toolbar/BUILD.bazel b/ts/editor-toolbar/BUILD.bazel
index 5576245f3..acf06ef08 100644
--- a/ts/editor-toolbar/BUILD.bazel
+++ b/ts/editor-toolbar/BUILD.bazel
@@ -14,22 +14,23 @@ compile_svelte(
name = "svelte",
srcs = svelte_files,
deps = [
+ "//ts/sass:button_mixins_lib",
"//ts/sass/bootstrap",
],
)
compile_sass(
- group = "local_css",
srcs = [
+ "bootstrap.scss",
"color.scss",
"legacy.scss",
- "bootstrap.scss",
],
+ group = "local_css",
+ visibility = ["//visibility:public"],
deps = [
- "//ts/sass/bootstrap",
"//ts/sass:button_mixins_lib",
+ "//ts/sass/bootstrap",
],
- visibility = ["//visibility:public"],
)
ts_library(
@@ -52,14 +53,14 @@ ts_library(
exclude = ["index.ts"],
),
deps = [
+ "//ts:image_module_support",
"//ts/lib",
"//ts/lib:backend_proto",
"//ts/sveltelib",
- "//ts:image_module_support",
- "@npm//svelte",
- "@npm//bootstrap",
"@npm//@popperjs/core",
"@npm//@types/bootstrap",
+ "@npm//bootstrap",
+ "@npm//svelte",
],
)
@@ -140,6 +141,8 @@ svelte_check(
srcs = glob([
"*.ts",
"*.svelte",
- ]) + ["//ts/sass/bootstrap"],
+ ]) + [
+ "//ts/sass/bootstrap",
+ "//ts/sass:button_mixins_lib",
+ ],
)
- The other errors look to be caused by referencing ts/bootstrap instead of ts/sass/bootstrap. I actually wrote that add-on ;-) I've always thought people are better off binding shortcut keys of their liking than clicking with the mouse, but do realise some people prefer to use the mouse. AnkIMobile uses a small palette of choices as well, and I have no objections to such an implementation on the desktop version, and it'll likely be easier to do in HTML than it was with Qt. |
(note how the bootstrap is a single dep listing to get access to any of the partials - we could do the same thing with the various ts/sass/*.scss files, building them into a single sass_library instead of separate ones for each file) |
I guess that road cleaner, the SASS compilation is very quick, and before we have 10+ |
The library doesn't do any compilation, it's just a way of bundling up a bunch of inputs that downstream code may or may not want to use. So putting 10 files in a library doesn't mean they'll all be compiled, unless you import all of them. |
(there is a small cost to create a symlink to each one when building a target (more so on Windows), but I suspect it won't be a big issue) |
So now I get the issue with Bootstrap Javascript:
I do not get this locally when running So here's a compilation of things I'd want to tackle in upcoming PRs, in rough order of priority:
In this PR, at this point I only want to tackle the color clashes between Bootstrap and Qt, you mentioned above. |
@@ -144,6 +144,6 @@ svelte_check(
]) + [
"//ts/sass:button_mixins_lib",
"//ts/sass/bootstrap",
+ "@npm//@types/bootstrap",
],
) |
I got the error on this Mac - I suspect it would have been reproducible with a clean build, but that would be a pain. Linux's sandboxing is also better than the Mac sandboxing, so the Linux CI can sometimes catch things the others don't. Those priorities sound fine, if you can get this passing and the colours fixed, I'll try to merge this tomorrow |
This was an experiment, to adjust the field border-radius to the buttons, but I think it looks cleaner if the fields are square
@glutanimate
Svelte puts a lot of focus on local SCSS for maintainability, which means their bound to components. However we could add some global button styling into |
Hmm, interesting. Would definitely make sense as one scenario where this appears. However, in the case above it's just the plain AnkiWeb version of Image Occlusion, and its only interaction with the button API is via the Python interface, i.e.: b = editor.addButton(
icon,
_("I/O"),
lambda o=editor: onImgOccButton(o),
tip="{} ({})".format(tt, hotkey),
keys=hotkey,
disables=False,
)
buttons.append(b) So maybe there's a different race scenario at play also? |
…night and btn-day
The new colors are also not a perfect match, but close enough. The Qt buttons have a box shadow on the bottom edge, which I don't want to recreate, as a box shadow is used to indicate the impressed (active) state. However I could also move the AddCards dialog into the the toolbar in the next PR, making a custom |
Looks good enough to me! The missing icon reproduces for me every time after starting Anki + opening the editor. Reopening the editor, the icon is there. this.buttons seems to be undefined on the first call, presumably due to the delay in setupI18n. Maybe we should either retry after a delay, or expose a hook/promise? addButtonGroup(newGroup: ButtonGroupProps, group: string | number = -1): void {
this.buttons?.update((buttonGroups) => { |
…s in EditorToolbar
There was indeed a race condition going on. I think if two asynchronous evals both called |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks Henrik! 🚀
|
||
righttopbtns_defs = "\n".join( | ||
[ | ||
f"{{ component: editorToolbar.RawButton, html: `{button}` }}," |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This'll break if there's a backtick in the HTML - safer would be to encode the string with json.dumps()
class:active | ||
class:dropdown-toggle={dropdownToggle} | ||
class:btn-day={!nightMode} | ||
class:btn-night={nightMode} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just out of curiosity, is there any advantage to having a separate day class instead of day being the default, with night mode overriding it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
|
||
const nightMode = checkNightMode(); | ||
|
||
const { loading, value: i18n } = useAsync(() => setupI18n()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this dead code? If not, setupI18n() call signature needs updating
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, yes. I abandoned this, once I realized I had to do the i18n setup outside of Svelte.
First of all, I'm sorry for opening yet another PR.
After the Tag Editor, and potential worries about extensibility, I thought that the Toolbar would be a better target for trying out ways to make Svelte components extensible.
/ts/editor-toolbar
.IconButton
for buttons which have a big iconCommandIconButton
for icon buttons, which are based ondocument.execCommand
(they support the notion of being active)LabelButton
for buttons, which have a text label (like "Fields...")SelectButton
, which have a<select>
menu.DropdownMenu
, which is a menu with text items (like the More button currently on AnkiDesktop)ButtonDropdown
, which can contain a set of buttons, like the Mathjax button on AnkiMobile currently.Showing off the toolbar, with two unused features: SelectButton and ButtonDropdown:
flex-flow
inButtonGroup
. This could also be turned into a parameter for the whole toolbar.The Toolbar exposes its API in two ways:
editorToolbar
is a new global in the editor. It contains the Svelte components which were used to create the buttons, and it also methodsupdateActiveButtons
, andclearActiveButtons
, which are exposed via the Module Context ofCommandIconButton.svelte
.document.getElementById("editorToolbar")
, exposes the buttons and menus as Svelte stores, which can be used to update the buttons and menus reactively. This way you can even listen to changes to the toolbar.Todos (feel free to edit):
bridgeCommand
. I think only the fields, cards, attachment, and record button will actually still need it. Does that sound right?size
.CommandIconButton.svelte
). (Exchanged with@ts-expect-error
)RawButton
/ passing string as buttonBoldButton
etc. (I instead useddynamicComponent
which is more a reiteration onwithLazyAttributes
with better type support)Remove the png iconsShortcut APIUnknowns:
editor.py
can be removedhtml-filter
, which would then be used within editor-toolbar).Updates: