diff --git a/lang/en-nz.quilt b/lang/en-nz.quilt index 1dcc7bb..9538612 100644 --- a/lang/en-nz.quilt +++ b/lang/en-nz.quilt @@ -36,6 +36,9 @@ toolbar/button/block-type/currently/paragraph: paragraph toolbar/button/block-type/currently/mixed: mixed toolbar/group/wrapper: block wrappers +toolbar/button/lift: unwrap +toolbar/button/bullet-list: wrap in list +toolbar/button/ordered-list: wrap in ordered list toolbar/button/blockquote: wrap in block quote toolbar/group/insert: insert diff --git a/src/ui/component/core/TextEditor.ts b/src/ui/component/core/TextEditor.ts index 7bb07a8..05ab3f7 100644 --- a/src/ui/component/core/TextEditor.ts +++ b/src/ui/component/core/TextEditor.ts @@ -1,6 +1,6 @@ import quilt from "lang/en-nz" import MarkdownIt from "markdown-it" -import { baseKeymap, setBlockType, toggleMark, wrapIn } from "prosemirror-commands" +import { baseKeymap, lift, setBlockType, toggleMark, wrapIn } from "prosemirror-commands" import { dropCursor } from "prosemirror-dropcursor" import { buildInputRules, buildKeymap } from "prosemirror-example-setup" import { gapCursor } from "prosemirror-gapcursor" @@ -11,6 +11,7 @@ import type { ParseSpec } from "prosemirror-markdown" import { schema as baseSchema, defaultMarkdownParser, defaultMarkdownSerializer, MarkdownParser } from "prosemirror-markdown" import type { Attrs, MarkSpec, MarkType, NodeSpec, NodeType } from "prosemirror-model" import { Fragment, Node, NodeRange, ResolvedPos, Schema } from "prosemirror-model" +import { wrapInList } from "prosemirror-schema-list" import type { Command, PluginSpec, PluginView } from "prosemirror-state" import { EditorState, Plugin } from "prosemirror-state" import { findWrapping, liftTarget, Transform } from "prosemirror-transform" @@ -543,6 +544,10 @@ const TextEditor = Component.Builder((component): TextEditor => { ToolbarButton(wrapper(schema.nodes[type.replaceAll("-", "_")])) .and(ToolbarButtonTypeNode, type)) + const ToolbarButtonList = Component.Builder((_, type: Extract) => + ToolbarButton(listWrapper(schema.nodes[type.replaceAll("-", "_")])) + .and(ToolbarButtonTypeNode, type)) + const ToolbarButtonPopover = Component.Builder((_, align: "left" | "centre" | "right") => { return Button() .style("text-editor-toolbar-button", "text-editor-toolbar-button--has-popover") @@ -629,6 +634,10 @@ const TextEditor = Component.Builder((component): TextEditor => { return wrapCmd(setBlockType(node, attrs)) } + function listWrapper (node: NodeType, attrs?: Attrs) { + return wrapCmd(wrapInList(node, attrs)) + } + //#endregion //////////////////////////////////// @@ -675,7 +684,6 @@ const TextEditor = Component.Builder((component): TextEditor => { .tweakPopover(popover => popover .ariaRole("radiogroup") .append(ToolbarButtonBlockType("paragraph")) - .append(ToolbarButtonBlockType("code-block")) .append(ToolbarButtonPopover("centre") .style("text-editor-toolbar-heading") .tweakPopover(popover => popover @@ -686,6 +694,7 @@ const TextEditor = Component.Builder((component): TextEditor => { .append(ToolbarButtonHeading(5)) .append(ToolbarButtonHeading(6)) )) + .append(ToolbarButtonBlockType("code-block")) ) .tweak(button => { state.use(button, () => { @@ -701,7 +710,12 @@ const TextEditor = Component.Builder((component): TextEditor => { }))) .append(ToolbarButtonGroup() .ariaLabel.use("component/text-editor/toolbar/group/wrapper") - .append(ToolbarButtonWrap("blockquote"))) + .append(ToolbarButton(wrapCmd(lift)).and(ToolbarButtonTypeOther, "lift") + .style.bind(state.map(component, value => !value || !lift(value)), "text-editor-toolbar-button--hidden")) + .append(ToolbarButtonWrap("blockquote")) + .append(ToolbarButtonList("bullet-list")) + .append(ToolbarButtonList("ordered-list")) + ) .append(ToolbarButtonGroup() .ariaLabel.use("component/text-editor/toolbar/group/insert") .append(ToolbarButton(wrapCmd((state, dispatch) => { @@ -745,6 +759,13 @@ const TextEditor = Component.Builder((component): TextEditor => { }, })) + const scrollbarProxy: Component = Component() + .style("text-editor-document-scrollbar-proxy") + .style.bindVariable("content-width", + state.map(component, () => `${editor.document?.element.scrollWidth ?? 0}px`)) + .event.subscribe("scroll", () => + editor.document?.element.scrollTo({ left: scrollbarProxy.element.scrollLeft, behavior: "instant" })) + editor .append(toolbar) .append(Slot() @@ -756,6 +777,7 @@ const TextEditor = Component.Builder((component): TextEditor => { return createDefaultView(slot) })) + .append(scrollbarProxy) return editor @@ -834,6 +856,8 @@ const TextEditor = Component.Builder((component): TextEditor => { .style("text-editor-document") .setId(`text-editor-${id}`) .attributes.set("aria-multiline", "true") + .event.subscribe("scroll", () => + scrollbarProxy.element.scrollTo({ left: editor.document?.element.scrollLeft ?? 0, behavior: "instant" })) toolbar.ariaControls(editor.document) refresh() diff --git a/src/ui/utility/StyleManipulator.ts b/src/ui/utility/StyleManipulator.ts index 004f495..803ed1a 100644 --- a/src/ui/utility/StyleManipulator.ts +++ b/src/ui/utility/StyleManipulator.ts @@ -13,8 +13,10 @@ interface StyleManipulatorFunctions { unbind (state?: State): HOST refresh (): HOST - setProperty (property: string, value: string | number): HOST - setVariable (variable: string, value: string | number): HOST + setProperty (property: string, value?: string | number | null): HOST + setVariable (variable: string, value?: string | number | null): HOST + bindProperty (property: string, state: State): HOST + bindVariable (variable: string, state: State): HOST removeProperties (...properties: string[]): HOST } @@ -28,6 +30,7 @@ interface StyleManipulator extends StyleManipulatorFunction, StyleMa function StyleManipulator (component: Component): StyleManipulator { const styles = new Set() const stateUnsubscribers = new WeakMap, [UnsubscribeState, ComponentName[]]>() + const unbindPropertyState: Record = {} const result: StyleManipulator = Object.assign( ((...names) => { @@ -86,13 +89,21 @@ function StyleManipulator (component: Component): StyleManipulator { refresh: () => updateClasses(), setProperty (property, value) { - component.element.style.setProperty(property, `${value}`) + unbindPropertyState[property]?.() + setProperty(property, value) return component }, setVariable (variable, value) { - component.element.style.setProperty(`--${variable}`, `${value}`) + return result.setProperty(`--${variable}`, value) + }, + bindProperty (property, state) { + unbindPropertyState[property]?.() + unbindPropertyState[property] = state.use(component, value => setProperty(property, value)) return component }, + bindVariable (variable, state) { + return result.bindProperty(`--${variable}`, state) + }, removeProperties (...properties) { for (const property of properties) component.element.style.removeProperty(property) @@ -113,6 +124,13 @@ function StyleManipulator (component: Component): StyleManipulator { component.element.classList.add(...toAdd) return component } + + function setProperty (property: string, value?: string | number | null) { + if (value === undefined || value === null) + component.element.style.removeProperty(property) + else + component.element.style.setProperty(property, `${value}`) + } } export default StyleManipulator diff --git a/style/component/text-editor.chiri b/style/component/text-editor.chiri index 5f0d845..9566123 100644 --- a/style/component/text-editor.chiri +++ b/style/component/text-editor.chiri @@ -1,9 +1,6 @@ .text-editor: - %block + %grid %width-100 - %padding-2-3 - %padding-top-0 - %border-box %border-1 %border-radius-1 %cursor-text @@ -18,7 +15,6 @@ &-toolbar: %font-2 %block - %unmargin-0-3 %sticky %border-bottom-1 %cursor-default @@ -69,6 +65,9 @@ $button-background: transparent z-index: 2 + &--hidden: + %hidden + &--enabled: @after: %block @@ -138,12 +137,14 @@ #icon name="superscript &-blockquote: #icon name="quote-left - &-paragraph: - #icon name="paragraph - &-list-ul: + &-bullet-list: #icon name="list-ul - &-list-ol: + &-ordered-list: #icon name="list-ol + &-lift: + #icon name="outdent + &-paragraph: + #icon name="paragraph &-heading: #icon name="heading #each in 1...6 as var i: @@ -207,6 +208,9 @@ &-document: %block %no-outline + %overflow-auto-x + %padding-2-3 + scrollbar-width: none & p: margin-block: round(up, 1em, 1px) @@ -234,6 +238,22 @@ $code-padding: 0 background: light-dark(#{alpha(0.1, $dark-0)}, #{alpha(0.2, $dark-0)}) + &-scrollbar-proxy: + %block + %sticky + %bottom-0 + %overflow-auto-x + %backdrop-blur + %border-bottom-radius-1 + scrollbar-colour: $colour-5 transparent + @before: + %block + width: $content-width + height: 1px + + ::hover: + scrollbar-colour: $colour-3 transparent + &--required: .ProseMirror-gapcursor: