Convert BetterFormattingRedux toolbar from manual DOM to Svelte 5#837
Convert BetterFormattingRedux toolbar from manual DOM to Svelte 5#837
Conversation
Co-authored-by: zerebos <6865942+zerebos@users.noreply.github.com> Agent-Logs-Url: https://github.com/zerebos/BetterDiscordAddons/sessions/08f248d2-2388-4cd5-9761-fb53380fad75
Co-authored-by: zerebos <6865942+zerebos@users.noreply.github.com> Agent-Logs-Url: https://github.com/zerebos/BetterDiscordAddons/sessions/08f248d2-2388-4cd5-9761-fb53380fad75
There was a problem hiding this comment.
Pull request overview
Converts the BetterFormattingRedux plugin’s formatting toolbar UI from manual DOM/CSS/template assets to a Svelte 5 component, aligning its UI approach with other Svelte-based plugins in the repo.
Changes:
- Replace DOM-built toolbar with a new
Toolbar.sveltecomponent mounted via Sveltemount/unmount. - Simplify settings updates by remounting toolbars when toolbar/style settings change and persisting open state.
- Remove legacy
toolbar.html/styles.cssassets and adjustconfig.tsto useimport type.
Reviewed changes
Copilot reviewed 5 out of 8 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| src/plugins/BetterFormattingRedux/toolbar.html | Removed legacy toolbar template (replaced by Svelte component). |
| src/plugins/BetterFormattingRedux/styles.css | Removed legacy toolbar stylesheet (styles moved into Svelte component). |
| src/plugins/BetterFormattingRedux/index.ts | Integrates Svelte toolbar mounting/unmounting and refactors toolbar lifecycle/settings handling. |
| src/plugins/BetterFormattingRedux/config.ts | Switches to import type for Manifest to avoid runtime import issues. |
| src/plugins/BetterFormattingRedux/Toolbar.svelte | New Svelte 5 toolbar component (render + styling + interactions). |
| package-lock.json | Adds npm lockfile (potentially conflicts with existing bun workflow). |
| .gitignore | Ignores package-lock.json (conflicts with committing it). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
@copilot consider and resolve the review comments in some way. Either by fixing it, or by explaining how the review comment is wrong. |
…erver DOM shape, type cleanup, onRemoved cleanup Co-authored-by: zerebos <6865942+zerebos@users.noreply.github.com> Agent-Logs-Url: https://github.com/zerebos/BetterDiscordAddons/sessions/667f05d8-99c0-4c1a-83aa-4150bd950e62
All five review comments addressed in c825b10:
|
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 8 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| for (const node of Array.from(e.addedNodes)) { | ||
| if (!(node instanceof Element)) continue; | ||
| const textarea = node.matches(`.${TextareaClasses.textArea}`) ? node : node.querySelector(`.${TextareaClasses.textArea}`); | ||
| if (textarea) this.addToolbar(textarea.children[0] as HTMLDivElement); |
There was a problem hiding this comment.
observer() calls this.addToolbar(textarea.children[0] as HTMLDivElement). If the matched .textArea is a <textarea> (or if the element has no children), children[0] will be undefined and addToolbar() will throw when accessing parentElement. Consider passing the matched element itself and guarding for TEXTAREA/missing children before calling addToolbar(), or make addToolbar() accept Element and resolve the correct insertion anchor internally.
| if (textarea) this.addToolbar(textarea.children[0] as HTMLDivElement); | |
| if (textarea) { | |
| const firstChild = textarea.firstElementChild; | |
| if (firstChild instanceof HTMLDivElement) { | |
| this.addToolbar(firstChild); | |
| } | |
| } |
| setupToolbar() { | ||
| this.removeAllToolbars(); | ||
| document.querySelectorAll(`.${TextareaClasses.textArea}`).forEach(elem => { | ||
| this.addToolbar(elem.children[0] as HTMLDivElement); | ||
| }); |
There was a problem hiding this comment.
setupToolbar() iterates all .${TextareaClasses.textArea} elements and passes elem.children[0] into addToolbar(). This will break when the matched element is a <textarea> (no children) or Discord changes the subtree shape. Add a guard (e.g., check elem.tagName === "TEXTAREA" or elem.firstElementChild) before calling addToolbar(), or update addToolbar() to handle both slate and textarea variants safely.
|
@copilot 2 new review comments to address. |
…tarea/missing-child elements Co-authored-by: zerebos <6865942+zerebos@users.noreply.github.com> Agent-Logs-Url: https://github.com/zerebos/BetterDiscordAddons/sessions/2e480ea0-df1c-41b5-9cfb-6943c835cfec
Fixed in dda2ba4. Both const firstChild = textarea.firstElementChild;
if (firstChild instanceof HTMLDivElement) this.addToolbar(firstChild);This silently skips plain |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 8 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| padding: 10px 30px 15px 5px; | ||
| margin: 0 5px 0 0; | ||
| } | ||
|
|
There was a problem hiding this comment.
The old stylesheet had special-case rules for contexts like .upload-modal .bf-toolbar (switching to relative positioning/transform: none) and .message-group .bf-toolbar (animation), which are not present in this component’s <style> block. If the toolbar is still expected to work inside Discord’s upload modal / other composer variants, it may now render off-screen due to position: absolute + transform: translate(0, -100%). Consider porting those contextual selectors using :global(.upload-modal) ... (and any other still-relevant ones), or explicitly confirm they’re no longer needed.
| /* In Discord's upload modal and similar contexts, the default | |
| absolute positioning and upward transform can place the toolbar | |
| off-screen. Restore a relative, in-flow position there. */ | |
| :global(.upload-modal) .bf-toolbar { | |
| position: relative; | |
| top: auto; | |
| right: auto; | |
| bottom: auto; | |
| transform: none; | |
| margin: 0; | |
| } |
BetterFormattingRedux was the last major plugin still using manual DOM manipulation for its UI. This converts it to Svelte 5 runes, consistent with RoleMembers and PermissionsViewer.
New:
Toolbar.svelteReplaces
buildToolbar()DOM construction,toolbar.html, andstyles.css. Self-contained Svelte 5 component with:$props()/$state()/untrack()for open-state seeding from pluginclose()method called by the plugin on message send (closeOnSend):global(.theme-light)for Discord theme variants;:global(img, svg)for icon opacity within{@html}blobslet isOpen = $state(untrack(() => initiallyOpen)); // seed from prop, then manage independently export function close(): void { isOpen = false; onToggle?.(false); }Rewritten:
index.tsbuildToolbar(),addToolbar(),updateStyle/Side/Opacity/FontSize(),openClose()withmount/unmountfrom svelteToolbarEntry[] = [{container, instance}]setupToolbar()tears down all entries and remounts; called uniformly fromupdateSettings()for any toolbar/style changetoolbarOpen: booleanpersists open state across textarea remounts (channel navigation)onButtonClick(key)—discordWrappers[key] ?? settings[key + "Wrapper"]Removed
toolbar.html— template replaced by componentstyles.css— all rules ported intoToolbar.svelte <style>Fixed
config.ts:import {Manifest}→import type {Manifest}— the build script compiles config separately without path-alias resolution, causing a runtime module-not-found error on non-type imports⌨️ Start Copilot coding agent tasks without leaving your editor — available in VS Code, Visual Studio, JetBrains IDEs and Eclipse.