Skip to content

Commit

Permalink
wip: list indentation
Browse files Browse the repository at this point in the history
  • Loading branch information
davidmyersdev committed Sep 20, 2024
1 parent b55c656 commit 83af4b7
Show file tree
Hide file tree
Showing 2 changed files with 138 additions and 122 deletions.
234 changes: 127 additions & 107 deletions src/editor/extensions/lists.ts
Original file line number Diff line number Diff line change
@@ -1,161 +1,155 @@
import { syntaxTree } from '@codemirror/language'
import { RangeSet, RangeSetBuilder, StateField } from '@codemirror/state'
import type { EditorState, Extension, Range } from '@codemirror/state'
import type { EditorState, Extension } from '@codemirror/state'
import { Decoration, EditorView, ViewPlugin } from '@codemirror/view'
import type { DecorationSet } from '@codemirror/view'
import type { DecorationSet, WidgetType } from '@codemirror/view'
import { buildWidget } from '/lib/codemirror-kit'

const dotWidget = () => buildWidget({
eq: () => {
return false
},
toDOM: () => {
const span = document.createElement('span')
const createWrapper = (indentation: number) => {
const wrapper = document.createElement('label')

span.innerHTML = '•'
span.setAttribute('aria-hidden', 'true')
wrapper.setAttribute('aria-hidden', 'true')
wrapper.setAttribute('tabindex', '-1')
wrapper.className = 'ink-mde-list-marker-wrapper'
wrapper.style.minWidth = `calc((${indentation} * 1rem) + 2rem)`

return span
},
})
const spacer = document.createElement('span')

spacer.style.width = `calc(${indentation} * 1rem)`
spacer.innerHTML = ' '

wrapper.appendChild(spacer)

const taskWidget = (isChecked: boolean) => buildWidget({
return wrapper
}

const taskWidget = (isChecked: boolean, indentation = 0) => buildWidget({
eq: (other) => {
return other.isChecked === isChecked
},
ignoreEvent: () => false,
isChecked,
toDOM: () => {
const wrapper = document.createElement('label')
const wrapper = createWrapper(indentation)
const input = document.createElement('input')

wrapper.appendChild(input)

wrapper.setAttribute('aria-hidden', 'true')
wrapper.setAttribute('tabindex', '-1')
wrapper.className = 'ink-mde-task'

input.setAttribute('aria-hidden', 'true')
input.setAttribute('tabindex', '-1')
input.className = 'ink-mde-task-input'
input.className = 'ink-mde-task-input ink-mde-list-marker'
input.type = 'checkbox'
input.checked = isChecked

return wrapper
},
})

const hasOverlap = (x1: number, x2: number, y1: number, y2: number) => {
return Math.max(x1, y1) <= Math.min(x2, y2)
}
const dotWidget = (indentation = 0) => {
return buildWidget({
toDOM: () => {
const wrapper = createWrapper(indentation)
const content = document.createElement('span')

const isCursorInRange = (state: EditorState, from: number, to: number) => {
return state.selection.ranges.some((range) => {
return hasOverlap(from, to, range.from, range.to)
})
}
wrapper.appendChild(content)

const toggleTask = (view: EditorView, position: number) => {
const from = position - 3
const to = position
const before = view.state.sliceDoc(from, to)
content.setAttribute('aria-hidden', 'true')
content.setAttribute('tabindex', '-1')
content.className = 'ink-mde-list-marker'
content.innerHTML = '&#x2022;'

view.dispatch({
changes: {
from,
to,
insert: before === '[ ]' ? '[x]' : '[ ]',
return wrapper
},
})

return true
}

export const lists = (): Extension => {
const decorateMarker = (classes) => {
return Decoration.mark({
attributes: {
class: `list-marker ${classes}`,
},
})
}
const dotDecoration = () => Decoration.mark({
attributes: {
'class': 'list-marker',
'data-content': '•',
},
})
const numberWidget = (marker: string, indentation = 0) => {
return buildWidget({
toDOM: () => {
const wrapper = createWrapper(indentation)
const content = document.createElement('span')

const spacingDecoration = (spaces = 0) => Decoration.mark({
attributes: {
class: 'list-spacing',
},
})
wrapper.appendChild(content)

const listDecoration = (spaces = 0) => Decoration.line({
attributes: {
class: 'list',
style: `--indentation: ${spaces}ch;`,
},
})
content.setAttribute('aria-hidden', 'true')
content.setAttribute('tabindex', '-1')
content.className = 'ink-mde-list-marker'
content.innerHTML = `${marker}`

const lineDec = Decoration.mark({
attributes: {
class: 'list-line',
return wrapper
},
})
}

const listDecoration = (indentation: number, extra?: string) => Decoration.line({
attributes: {
class: 'list',
style: `--indentation: calc((${indentation} * 1rem) + 2rem + ${extra || '0rem'})`,
},
})

const taskDecoration = (isChecked: boolean) => Decoration.replace({
widget: taskWidget(isChecked),
const markerDecoration = (widget: WidgetType) => {
return Decoration.replace({
widget,
})
}

export const lists = (): Extension => {
const decorate = (state: EditorState): [DecorationSet, DecorationSet] => {
const atomicRanges = new RangeSetBuilder<Decoration>()
const decorations = <{ from: number, to: number, dec: Decoration }[]>[]
const builder = new RangeSetBuilder<Decoration>()

// Todo: Make this configurable.
syntaxTree(state).iterate({
enter: ({ type, from, to }) => {
if (type.name === 'ListMark') {
const line = state.doc.lineAt(from)
const lineStart = line.from
const marker = state.sliceDoc(from, to)
const task = state.sliceDoc(to + 1, to + 4)

if (['[ ]', '[x]'].includes(task)) {
const taskDec = taskDecoration(task === '[x]')
const listDec = listDecoration(((to + 2) - line.from) - 1)
const spc = from - line.from
console.log('spc', spc)
const spacingDec = spacingDecoration()

console.log('')
console.log('listDec', line.from, line.from, listDec.startSide)
console.log('taskDec', from, to + 4, taskDec.startSide)
console.log('lineDec', line.from, line.to, lineDec.startSide)
console.log('spacingDec', line.from, to + 5, spacingDec.startSide)

decorations.push({ from: line.from, to: line.from, dec: listDec })
decorations.push({ from: to + 5, to: line.to, dec: lineDec })
decorations.push({ from: line.from, to: to + 5, dec: spacingDec })

decorations.push({ from, to: to + 4, dec: taskDec })
atomicRanges.add(from, to + 4, taskDec)
} else if (['-', '*'].includes(marker)) {
const listDec = listDecoration((to + 1) - line.from)
const spacingDec = spacingDecoration(from - line.from)
const dotDec = dotDecoration()

decorations.push({ from: line.from, to: line.from, dec: listDec })
decorations.push({ from, to, dec: dotDec })
decorations.push({ from: to + 1, to: line.to, dec: lineDec })
decorations.push({ from: line.from, to: to + 1, dec: spacingDec })

// builder.add(line.from, line.from, listDecoration((to + 1) - line.from))
// builder.add(line.from, line.to, lineDec)
// builder.add(line.from, to + 1, spacingDecoration(from - line.from))
// builder.add(from, to, dotDecoration())
const markerStart = from
const markerEnd = to
const markerHasTrailingSpace = state.sliceDoc(markerEnd, markerEnd + 1) === ' '
const indentation = markerStart - lineStart

if (!markerHasTrailingSpace) {
return
}

if (['-', '*'].includes(marker)) {
const taskStart = markerEnd + 1
const taskEnd = taskStart + 3
const task = state.sliceDoc(taskStart, taskEnd)
const taskHasTrailingSpace = state.sliceDoc(taskEnd, taskEnd + 1) === ' '

if (['[ ]', '[x]'].includes(task) && taskHasTrailingSpace) {
const textStart = taskEnd + 1

const listDec = listDecoration(indentation)
const taskDec = markerDecoration(taskWidget(task === '[x]', indentation))

decorations.push({ from: lineStart, to: lineStart, dec: listDec })
decorations.push({ from: lineStart, to: textStart, dec: taskDec })
atomicRanges.add(lineStart, textStart, taskDec)
} else {
const textStart = markerEnd + 1

const listDec = listDecoration(indentation)
const dotDec = markerDecoration(dotWidget(indentation))

decorations.push({ from: lineStart, to: lineStart, dec: listDec })
decorations.push({ from: lineStart, to: textStart, dec: dotDec })
atomicRanges.add(lineStart, textStart, dotDec)
}
} else {
console.log('marker', marker)
const textStart = markerEnd + 1

const listDec = listDecoration(indentation)
const markerDec = markerDecoration(numberWidget(marker, indentation))

decorations.push({ from: lineStart, to: lineStart, dec: listDec })
decorations.push({ from: lineStart, to: textStart, dec: markerDec })
atomicRanges.add(lineStart, textStart, markerDec)
}
}
},
Expand All @@ -177,9 +171,35 @@ export const lists = (): Extension => {
eventHandlers: {
mousedown: (event, view) => {
const target = event.target as HTMLElement
const realTarget = target.closest('.ink-mde-widget')

if (realTarget) {
const position = view.posAtDOM(realTarget)
const from = position + 2
const to = position + 5
const before = view.state.sliceDoc(from, to)

if (before === '[ ]') {
view.dispatch({
changes: {
from,
to,
insert: '[x]',
},
})
}

if (before === '[x]') {
view.dispatch({
changes: {
from,
to,
insert: '[ ]',
},
})
}

if (target?.nodeName === 'INPUT' && target.classList.contains('ink-mde-task-input')) {
return toggleTask(view, view.posAtDOM(target))
return true
}
},
},
Expand Down
26 changes: 11 additions & 15 deletions src/ui/components/root/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -119,28 +119,26 @@
}


.ink-mde .ink-mde-task {
.ink-mde .ink-mde-list-marker-wrapper {
cursor: pointer;
display: inline-block;
display: inline-flex;
font-family: inherit;
font-size: inherit;
height: 1em;
width: 1ch;
position: relative;
vertical-align: middle;
scale: 2;
align-items: center;
justify-content: center;
min-width: 2rem;
}

.ink-mde .ink-mde-task input {
cursor: inherit;
display: block;
height: 100%;
width: 100%;
display: inline-block;
font-family: inherit;
font-size: inherit;
margin: 0;
top: 0;
scale: 0.6;
position: relative;
width: 1rem;
/* translate: 1px; */
}

.ink-mde .cm-editor {
Expand All @@ -156,15 +154,12 @@
}

.list {
font-size: calc((5 / 4) * 1em);
font-family: var(--ink-internal-code-font-family) !important;
padding-left: calc(var(--indentation)) !important;
text-indent: calc((var(--indentation)) * -1);
}

.list .list-line {
font-size: calc((4 / 5) * 1em);
font-family: var(--ink-internal-font-family) !important;
/* font-family: var(--ink-internal-font-family) !important; */
}

.list *:not(.list) {
Expand All @@ -185,6 +180,7 @@
inset-block: auto;
position: absolute;
content: attr(data-content);
translate: 2px;
}

.list-spacing {
Expand Down

0 comments on commit 83af4b7

Please sign in to comment.