Skip to content

Convert BetterFormattingRedux toolbar from manual DOM to Svelte 5#837

Draft
Copilot wants to merge 5 commits intomasterfrom
copilot/update-betterformattingredux-to-svelte
Draft

Convert BetterFormattingRedux toolbar from manual DOM to Svelte 5#837
Copilot wants to merge 5 commits intomasterfrom
copilot/update-betterformattingredux-to-svelte

Conversation

Copy link
Copy Markdown

Copilot AI commented Mar 20, 2026

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.svelte

Replaces buildToolbar() DOM construction, toolbar.html, and styles.css. Self-contained Svelte 5 component with:

  • $props() / $state() / untrack() for open-state seeding from plugin
  • Exported close() method called by the plugin on message send (closeOnSend)
  • All slide-up animation CSS scoped inside the component; :global(.theme-light) for Discord theme variants; :global(img, svg) for icon opacity within {@html} blobs
let isOpen = $state(untrack(() => initiallyOpen)); // seed from prop, then manage independently

export function close(): void {
    isOpen = false;
    onToggle?.(false);
}

Rewritten: index.ts

  • Replaced buildToolbar(), addToolbar(), updateStyle/Side/Opacity/FontSize(), openClose() with mount/unmount from svelte
  • Tracks instances as ToolbarEntry[] = [{container, instance}]
  • setupToolbar() tears down all entries and remounts; called uniformly from updateSettings() for any toolbar/style change
  • toolbarOpen: boolean persists open state across textarea remounts (channel navigation)
  • Button click dispatch consolidated to onButtonClick(key)discordWrappers[key] ?? settings[key + "Wrapper"]

Removed

  • toolbar.html — template replaced by component
  • styles.css — all rules ported into Toolbar.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.

Copilot AI changed the title [WIP] Update BetterFormattingRedux to use Svelte Convert BetterFormattingRedux toolbar from manual DOM to Svelte 5 Mar 20, 2026
Copilot AI requested a review from zerebos March 20, 2026 18:35
@zerebos zerebos requested a review from Copilot March 20, 2026 20:42
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.svelte component mounted via Svelte mount/unmount.
  • Simplify settings updates by remounting toolbars when toolbar/style settings change and persisting open state.
  • Remove legacy toolbar.html/styles.css assets and adjust config.ts to use import 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.

Comment thread src/plugins/BetterFormattingRedux/Toolbar.svelte Outdated
Comment thread src/plugins/BetterFormattingRedux/Toolbar.svelte
Comment thread src/plugins/BetterFormattingRedux/index.ts
Comment thread src/plugins/BetterFormattingRedux/index.ts
Comment thread src/plugins/BetterFormattingRedux/index.ts Outdated
@zerebos
Copy link
Copy Markdown
Owner

zerebos commented Mar 20, 2026

@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
Copy link
Copy Markdown
Author

Copilot AI commented Mar 20, 2026

@copilot consider and resolve the review comments in some way. Either by fixing it, or by explaining how the review comment is wrong.

All five review comments addressed in c825b10:

  1. style:font-size directive – changed "{fontSize}%" to {fontSize + "%"} so the value is computed correctly.
  2. Arrow tabindex – changed from -1 to 0 so keyboard users can focus and toggle the toolbar via Enter/Space.
  3. observer() DOM shape – now iterates all addedNodes (not just index 0) and passes textarea.children[0] to addToolbar(), matching exactly what setupToolbar() does.
  4. ToolbarEntry typeinstance is now typed as ReturnType<typeof mount> directly; unmount() needs no cast; the close() call narrows inline with as unknown as {close(): void}.
  5. Svelte instance leakBdApi.DOM.onRemoved(container, …) removes and unmounts the entry automatically when Discord removes the container, so toolbarEntries stays clean across channel navigations.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
if (textarea) this.addToolbar(textarea.children[0] as HTMLDivElement);
if (textarea) {
const firstChild = textarea.firstElementChild;
if (firstChild instanceof HTMLDivElement) {
this.addToolbar(firstChild);
}
}

Copilot uses AI. Check for mistakes.
Comment on lines +96 to +100
setupToolbar() {
this.removeAllToolbars();
document.querySelectorAll(`.${TextareaClasses.textArea}`).forEach(elem => {
this.addToolbar(elem.children[0] as HTMLDivElement);
});
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 uses AI. Check for mistakes.
@zerebos
Copy link
Copy Markdown
Owner

zerebos commented Mar 21, 2026

@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
Copy link
Copy Markdown
Author

Copilot AI commented Mar 21, 2026

@copilot 2 new review comments to address.

Fixed in dda2ba4. Both observer() and setupToolbar() now guard against missing/wrong-type children:

const firstChild = textarea.firstElementChild;
if (firstChild instanceof HTMLDivElement) this.addToolbar(firstChild);

This silently skips plain <textarea> elements (no children) and any future Discord subtree changes where the first child isn't a <div>, rather than passing undefined or an unexpected type into addToolbar().

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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;
}

Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
/* 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;
}

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants