Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions packages/vuetify/src/labs/VEditor/VEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { VToolbar } from '@/components/VToolbar/VToolbar'

// Composables
import { useCaret, useElement, useSelection } from './composables'
import { alignmentFormats, FormatCategory, generalFormats, headingFormats, useFormatter } from './composables/formatter'
import { alignmentFormats, FormatCategory, generalFormats, headingFormats, listFormats, useFormatter } from './composables/formatter'
import { useFocus } from '@/composables/focus'
import { forwardRefs } from '@/composables/forwardRefs'
import { useProxiedModel } from '@/composables/proxiedModel'
Expand Down Expand Up @@ -58,7 +58,9 @@ export const makeVEditorProps = propsFactory({
'align-left',
'align-right',
'align-justify',
'highlight',
'list-unordered',
'list-ordered',
'list-tasks',
],
},
height: {
Expand Down Expand Up @@ -121,6 +123,7 @@ export const VEditor = genericComponent<VEditorSlots>()({
const displayedGeneralFormats = computed(() => generalFormats.filter(format => props.formats.includes(format.name)))
const displayedHeadingFormats = computed(() => headingFormats.filter(format => props.formats.includes(format.name)))
const displayedAlignmentFormats = computed(() => alignmentFormats.filter(format => props.formats.includes(format.name)))
const displayedListFormats = computed(() => listFormats.filter(format => props.formats.includes(format.name)))

function onMouseUp () {
updateActiveFormats()
Expand Down Expand Up @@ -249,7 +252,12 @@ export const VEditor = genericComponent<VEditorSlots>()({

const newActiveFormats = new Set<string>()

const allDisplayedFormats = [...displayedGeneralFormats.value, ...displayedHeadingFormats.value, ...displayedAlignmentFormats.value]
const allDisplayedFormats = [
...displayedGeneralFormats.value,
...displayedHeadingFormats.value,
...displayedAlignmentFormats.value,
...displayedListFormats.value,
]

allDisplayedFormats.forEach((format: Formatter) => {
if (formatter.findElementWithFormat(format)) {
Expand All @@ -267,6 +275,8 @@ export const VEditor = genericComponent<VEditorSlots>()({
formatter.heading.toggle(format)
} else if (format.category === FormatCategory.Alignment) {
formatter.alignment.toggle(format)
} else if (format.category === FormatCategory.List) {
formatter.list.toggle(format)
} else {
formatter.inline.toggle(format)
}
Expand Down Expand Up @@ -423,6 +433,7 @@ export const VEditor = genericComponent<VEditorSlots>()({
{[
displayedHeadingFormats.value,
displayedAlignmentFormats.value,
displayedListFormats.value,
]
.map(groupFormats => {
const activeFormat = groupFormats.find(format => activeFormats.value.has(format.name))
Expand Down
80 changes: 77 additions & 3 deletions packages/vuetify/src/labs/VEditor/composables/formatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,26 @@ export type Formats = 'block' |
'align-center' |
'align-left' |
'align-right' |
'align-justify'
'align-justify' |
'list-unordered' |
'list-ordered' |
'list-tasks'

export enum FormatCategory {
Heading = 'heading',
Alignment = 'alignment',
List = 'list',
}

export type Formatter = {
name: Formats
icon: string
category?: FormatCategory
config: { tag?: string, styles?: Record<string, string>}
config: {
tag?: string
tagCondition?: string
styles?: Record<string, string>
}
}

export const blockFormatter: Formatter = {
Expand Down Expand Up @@ -155,6 +163,27 @@ export const alignmentFormats: Formatter[] = [
},
]

export const listFormats: Formatter[] = [
{
name: 'list-unordered',
icon: 'mdi-format-list-bulleted',
category: FormatCategory.List,
config: { tag: 'ul' },
},
{
name: 'list-ordered',
icon: 'mdi-format-list-numbered',
category: FormatCategory.List,
config: { tag: 'ol' },
},
{
name: 'list-tasks',
icon: 'mdi-format-list-checks',
category: FormatCategory.List,
config: { tag: 'ul', tagCondition: '>li>input[type="checkbox"]' },
},
]

export function useFormatter (editorRef: Ref<HTMLDivElement | undefined>) {
const selection = useSelection(editorRef)
const caret = useCaret(editorRef)
Expand All @@ -173,7 +202,7 @@ export function useFormatter (editorRef: Ref<HTMLDivElement | undefined>) {
}

function isApplied (format: Formatter, element: Element) {
const { tag, styles } = format.config
const { tag, tagCondition, styles } = format.config

const hasSameTag = tag ? element.tagName.toLowerCase() === tag.toLowerCase() : true

Expand Down Expand Up @@ -305,6 +334,46 @@ export function useFormatter (editorRef: Ref<HTMLDivElement | undefined>) {
}
}

function toggleListFormat (format: Formatter) {
const blockElement = editorElement.getCurrentBlock()

if (!editorRef.value) return

if (!blockElement) {
caret.save()
formatElementChildren(editorRef.value, format)
caret.restore()
} else {
const closestListParent = blockElement.closest('ul,ol')
const closestListItemParent = blockElement.closest('li')
const isTaskList = !!closestListItemParent?.children[0] &&
closestListItemParent.children[0].tagName === 'INPUT' &&
closestListItemParent.children[0].getAttribute('type') === 'checkbox'

const currentListType = isTaskList ? 'tasks' : closestListParent?.tagName.toLowerCase()
const targetListType = format.name.endsWith('-tasks') ? 'tasks' : format.name.endsWith('-ordered') ? 'ol' : 'ul'

if (currentListType && currentListType === targetListType) {
// TODO: unwrap selected `<li>` nodes from the list
// TODO: leave unselected `<li>` nodes within list (split if necessary)

// experimenting to have anything close...
closestListParent?.replaceWith(blockElement)
} else {
const listTag = targetListType === 'ol' ? 'ol' : 'ul'
const newList = document.createElement(listTag)
// TODO: wrap all lines/paragraphs individually, ignore/drop <hr>
// TODO: merge with neighbouring lists if possible

// experimenting to have anything close...
const newListItem = document.createElement('li')
blockElement.prepend(newList)
newList.appendChild(newListItem)
newList.appendChild(blockElement)
}
}
}

const inline = {
toggle: toggleInlineFormat,
add: addInlineFormat,
Expand All @@ -319,12 +388,17 @@ export function useFormatter (editorRef: Ref<HTMLDivElement | undefined>) {
toggle: toggleAlignmentFormat,
}

const list = {
toggle: toggleListFormat,
}

return {
isApplied,
findElementWithFormat,

inline,
heading,
alignment,
list,
}
}
Loading