Skip to content

Commit

Permalink
Text Editor: Add bullet list, ordered list, "lift" button, sticky x s…
Browse files Browse the repository at this point in the history
…crollbar
  • Loading branch information
ChiriVulpes committed Oct 27, 2024
1 parent 3b42632 commit 33859c1
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 16 deletions.
3 changes: 3 additions & 0 deletions lang/en-nz.quilt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
30 changes: 27 additions & 3 deletions src/ui/component/core/TextEditor.ts
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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"
Expand Down Expand Up @@ -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<ButtonTypeNodes, `${string}-list`>) =>
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")
Expand Down Expand Up @@ -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
////////////////////////////////////

Expand Down Expand Up @@ -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
Expand All @@ -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, () => {
Expand All @@ -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) => {
Expand Down Expand Up @@ -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()
Expand All @@ -756,6 +777,7 @@ const TextEditor = Component.Builder((component): TextEditor => {

return createDefaultView(slot)
}))
.append(scrollbarProxy)

return editor

Expand Down Expand Up @@ -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()
Expand Down
26 changes: 22 additions & 4 deletions src/ui/utility/StyleManipulator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ interface StyleManipulatorFunctions<HOST> {
unbind (state?: State<boolean>): 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<string | number | undefined | null>): HOST
bindVariable (variable: string, state: State<string | number | undefined | null>): HOST
removeProperties (...properties: string[]): HOST
}

Expand All @@ -28,6 +30,7 @@ interface StyleManipulator<HOST> extends StyleManipulatorFunction<HOST>, StyleMa
function StyleManipulator (component: Component): StyleManipulator<Component> {
const styles = new Set<ComponentName>()
const stateUnsubscribers = new WeakMap<State<boolean>, [UnsubscribeState, ComponentName[]]>()
const unbindPropertyState: Record<string, UnsubscribeState | undefined> = {}

const result: StyleManipulator<Component> = Object.assign(
((...names) => {
Expand Down Expand Up @@ -86,13 +89,21 @@ function StyleManipulator (component: Component): StyleManipulator<Component> {
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)
Expand All @@ -113,6 +124,13 @@ function StyleManipulator (component: Component): StyleManipulator<Component> {
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
38 changes: 29 additions & 9 deletions style/component/text-editor.chiri
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -18,7 +15,6 @@
&-toolbar:
%font-2
%block
%unmargin-0-3
%sticky
%border-bottom-1
%cursor-default
Expand Down Expand Up @@ -69,6 +65,9 @@
$button-background: transparent
z-index: 2

&--hidden:
%hidden

&--enabled:
@after:
%block
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -207,6 +208,9 @@
&-document:
%block
%no-outline
%overflow-auto-x
%padding-2-3
scrollbar-width: none

& p:
margin-block: round(up, 1em, 1px)
Expand Down Expand Up @@ -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:
Expand Down

0 comments on commit 33859c1

Please sign in to comment.