chore(lists): move list keymap to extra extension (#4290)

* move list keymap to extra extension

* update docs and readme

* move list helpers out of core
This commit is contained in:
bdbch 2023-08-10 16:44:46 -07:00 committed by GitHub
parent a2ce734d68
commit 7e7057ea43
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 356 additions and 23 deletions

View File

@ -0,0 +1,57 @@
import './styles.scss'
import BulletList from '@tiptap/extension-bullet-list'
import Document from '@tiptap/extension-document'
import ListItem from '@tiptap/extension-list-item'
import ListKeymap from '@tiptap/extension-list-keymap'
import Paragraph from '@tiptap/extension-paragraph'
import Text from '@tiptap/extension-text'
import { EditorContent, useEditor } from '@tiptap/react'
import React from 'react'
export default () => {
const editor = useEditor({
extensions: [Document, Paragraph, Text, BulletList, ListItem, ListKeymap],
content: `
<ul>
<li>A list item</li>
<li>And another one</li>
</ul>
`,
})
if (!editor) {
return null
}
return (
<>
<button
onClick={() => editor.chain().focus().toggleBulletList().run()}
className={editor.isActive('bulletList') ? 'is-active' : ''}
>
toggleBulletList
</button>
<button
onClick={() => editor.chain().focus().splitListItem('listItem').run()}
disabled={!editor.can().splitListItem('listItem')}
>
splitListItem
</button>
<button
onClick={() => editor.chain().focus().sinkListItem('listItem').run()}
disabled={!editor.can().sinkListItem('listItem')}
>
sinkListItem
</button>
<button
onClick={() => editor.chain().focus().liftListItem('listItem').run()}
disabled={!editor.can().liftListItem('listItem')}
>
liftListItem
</button>
<EditorContent editor={editor} />
</>
)
}

View File

@ -0,0 +1,11 @@
/* Basic editor styles */
.tiptap {
> * + * {
margin-top: 0.75em;
}
ul,
ol {
padding: 0 1rem;
}
}

View File

@ -0,0 +1,52 @@
---
description: Add extra keymap handlers to change the default backspace and delete behavior for lists.
icon: asterisk
---
# ListKeymap
[![Version](https://img.shields.io/npm/v/@tiptap/extension-list-keymap.svg?label=version)](https://www.npmjs.com/package/@tiptap/extension-list-keymap)
[![Downloads](https://img.shields.io/npm/dm/@tiptap/extension-list-keymap.svg)](https://npmcharts.com/compare/@tiptap/extension-list-keymap?minimal=true)
This extensions adds extra keymap handlers to change the default backspace and delete behavior for lists. Those are not included in the core package, because they are not required for the most basic use cases.
## Installation
```bash
npm install @tiptap/extension-list-keymap
```
## Settings
### listTypes
A array of list items and their parent wrapper node types.
Default:
```js
[
{
itemName: 'listItem',
wrapperNames: ['bulletList', 'orderedList'],
},
{
itemName: 'taskItem',
wrapperNames: ['taskList'],
},
]
```
```js
ListKeymap.configure({
listTypes: [
{
itemName: 'taskItem',
wrapperNames: ['customTaskList'],
},
],
})
```
## Source code
[packages/extension-list-keymap/](https://github.com/ueberdosis/tiptap/blob/main/packages/extension-list-keymap/)
## Usage
https://embed.tiptap.dev/preview/Extensions/ListKeymap

View File

@ -1,3 +1,4 @@
export * from '../../../extension-list-keymap/src/listHelpers/index.js'
export * from './combineTransactionSteps.js'
export * from './createChainableState.js'
export * from './createDocument.js'
@ -44,7 +45,6 @@ export * from './isNodeActive.js'
export * from './isNodeEmpty.js'
export * from './isNodeSelection.js'
export * from './isTextSelection.js'
export * from './listHelpers/index.js'
export * from './posToDOMRect.js'
export * from './resolveFocusPosition.js'
export * from './selectionToInsertionEnd.js'

View File

@ -1,7 +1,4 @@
import {
handleBackspace, handleDelete,
mergeAttributes, Node,
} from '@tiptap/core'
import { mergeAttributes, Node } from '@tiptap/core'
export interface ListItemOptions {
HTMLAttributes: Record<string, any>,
@ -41,10 +38,6 @@ export const ListItem = Node.create<ListItemOptions>({
Enter: () => this.editor.commands.splitListItem(this.name),
Tab: () => this.editor.commands.sinkListItem(this.name),
'Shift-Tab': () => this.editor.commands.liftListItem(this.name),
Delete: ({ editor }) => handleDelete(editor, this.name),
'Mod-Delete': ({ editor }) => handleDelete(editor, this.name),
Backspace: ({ editor }) => handleBackspace(editor, this.name, [this.options.bulletListTypeName, this.options.orderedListTypeName]),
'Mod-Backspace': ({ editor }) => handleBackspace(editor, this.name, [this.options.bulletListTypeName, this.options.orderedListTypeName]),
}
},
})

View File

@ -0,0 +1,4 @@
# Change Log
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.

View File

@ -0,0 +1,14 @@
# @tiptap/extension-list-keymap
[![Version](https://img.shields.io/npm/v/@tiptap/extension-list-keymap.svg?label=version)](https://www.npmjs.com/package/@tiptap/extension-list-keymap)
[![Downloads](https://img.shields.io/npm/dm/@tiptap/extension-list-keymap.svg)](https://npmcharts.com/compare/tiptap?minimal=true)
[![License](https://img.shields.io/npm/l/@tiptap/extension-list-keymap.svg)](https://www.npmjs.com/package/@tiptap/extension-list-keymap)
[![Sponsor](https://img.shields.io/static/v1?label=Sponsor&message=%E2%9D%A4&logo=GitHub)](https://github.com/sponsors/ueberdosis)
## Introduction
Tiptap is a headless wrapper around [ProseMirror](https://ProseMirror.net) a toolkit for building rich text WYSIWYG editors, which is already in use at many well-known companies such as *New York Times*, *The Guardian* or *Atlassian*.
## Official Documentation
Documentation can be found on the [Tiptap website](https://tiptap.dev).
## License
Tiptap is open sourced software licensed under the [MIT license](https://github.com/ueberdosis/tiptap/blob/main/LICENSE.md).

View File

@ -0,0 +1,46 @@
{
"name": "@tiptap/extension-list-keymap",
"description": "list keymap extension for tiptap",
"version": "2.1.0-rc.12",
"homepage": "https://tiptap.dev",
"keywords": [
"tiptap",
"tiptap extension"
],
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"type": "module",
"exports": {
".": {
"types": "./dist/packages/extension-list-keymap/src/index.d.ts",
"import": "./dist/index.js",
"require": "./dist/index.cjs"
}
},
"main": "dist/index.cjs",
"module": "dist/index.js",
"umd": "dist/index.umd.js",
"types": "dist/packages/extension-list-keymap/src/index.d.ts",
"files": [
"src",
"dist"
],
"devDependencies": {
"@tiptap/core": "^2.1.0-rc.12"
},
"peerDependencies": {
"@tiptap/core": "^2.0.0"
},
"repository": {
"type": "git",
"url": "https://github.com/ueberdosis/tiptap",
"directory": "packages/extension-list-keymap"
},
"scripts": {
"clean": "rm -rf dist",
"build": "npm run clean && rollup -c"
}
}

View File

@ -0,0 +1,60 @@
import sizes from '@atomico/rollup-plugin-sizes'
import babel from '@rollup/plugin-babel'
import commonjs from '@rollup/plugin-commonjs'
import resolve from '@rollup/plugin-node-resolve'
import autoExternal from 'rollup-plugin-auto-external'
import sourcemaps from 'rollup-plugin-sourcemaps'
import typescript from 'rollup-plugin-typescript2'
import pkg from './package.json'
export default {
external: [/@tiptap\/pm\/.*/],
input: 'src/index.ts',
output: [
{
name: pkg.name,
file: pkg.umd,
format: 'umd',
sourcemap: true,
},
{
name: pkg.name,
file: pkg.main,
format: 'cjs',
sourcemap: true,
exports: 'auto',
},
{
name: pkg.name,
file: pkg.module,
format: 'es',
sourcemap: true,
},
],
plugins: [
autoExternal({
packagePath: './package.json',
}),
sourcemaps(),
resolve(),
commonjs(),
babel({
babelHelpers: 'bundled',
exclude: '../../node_modules/**',
}),
sizes(),
typescript({
tsconfig: '../../tsconfig.json',
tsconfigOverride: {
compilerOptions: {
declaration: true,
paths: {
'@tiptap/*': ['packages/*/src'],
},
},
include: null,
},
}),
],
}

View File

@ -0,0 +1,6 @@
import { ListKeymap } from './list-keymap.js'
export * from './list-keymap.js'
export * as listHelpers from './listHelpers/index.js'
export default ListKeymap

View File

@ -0,0 +1,94 @@
import { Extension } from '@tiptap/core'
import { handleBackspace, handleDelete } from './listHelpers/index.js'
export type ListKeymapOptions = {
listTypes: Array<{
itemName: string,
wrapperNames: string[],
}>
}
export const ListKeymap = Extension.create<ListKeymapOptions>({
name: 'listKeymap',
addOptions() {
return {
listTypes: [
{
itemName: 'listItem',
wrapperNames: ['bulletList', 'orderedList'],
},
{
itemName: 'taskItem',
wrapperNames: ['taskList'],
},
],
}
},
addKeyboardShortcuts() {
return {
Delete: ({ editor }) => {
let handled = false
this.options.listTypes.forEach(({ itemName }) => {
if (editor.state.schema.nodes[itemName] === undefined) {
return
}
if (handleDelete(editor, itemName)) {
handled = true
}
})
return handled
},
'Mod-Delete': ({ editor }) => {
let handled = false
this.options.listTypes.forEach(({ itemName }) => {
if (editor.state.schema.nodes[itemName] === undefined) {
return
}
if (handleDelete(editor, itemName)) {
handled = true
}
})
return handled
},
Backspace: ({ editor }) => {
let handled = false
this.options.listTypes.forEach(({ itemName, wrapperNames }) => {
if (editor.state.schema.nodes[itemName] === undefined) {
return
}
if (handleBackspace(editor, itemName, wrapperNames)) {
handled = true
}
})
return handled
},
'Mod-Backspace': ({ editor }) => {
let handled = false
this.options.listTypes.forEach(({ itemName, wrapperNames }) => {
if (editor.state.schema.nodes[itemName] === undefined) {
return
}
if (handleBackspace(editor, itemName, wrapperNames)) {
handled = true
}
})
return handled
},
}
},
})

View File

@ -1,7 +1,7 @@
import { NodeType } from '@tiptap/pm/model'
import { EditorState } from '@tiptap/pm/state'
import { getNodeType } from '../getNodeType.js'
import { getNodeType } from '../../../core/src/helpers/getNodeType.js'
export const findListItemPos = (typeOrName: string | NodeType, state: EditorState) => {
const { $from } = state.selection

View File

@ -1,6 +1,6 @@
import { EditorState } from '@tiptap/pm/state'
import { getNodeAtPosition } from '../getNodeAtPosition.js'
import { getNodeAtPosition } from '../../../core/src/helpers/getNodeAtPosition.js'
import { findListItemPos } from './findListItemPos.js'
export const getNextListDepth = (typeOrName: string, state: EditorState) => {

View File

@ -1,8 +1,8 @@
import { Node } from '@tiptap/pm/model'
import { Editor } from '../../Editor.js'
import { isAtStartOfNode } from '../isAtStartOfNode.js'
import { isNodeActive } from '../isNodeActive.js'
import { Editor } from '../../../core/src/Editor.js'
import { isAtStartOfNode } from '../../../core/src/helpers/isAtStartOfNode.js'
import { isNodeActive } from '../../../core/src/helpers/isNodeActive.js'
import { findListItemPos } from './findListItemPos.js'
import { hasListBefore } from './hasListBefore.js'
import { hasListItemBefore } from './hasListItemBefore.js'

View File

@ -1,6 +1,6 @@
import { Editor } from '../../Editor.js'
import { isAtEndOfNode } from '../isAtEndOfNode.js'
import { isNodeActive } from '../isNodeActive.js'
import { Editor } from '../../../core/src/Editor.js'
import { isAtEndOfNode } from '../../../core/src/helpers/isAtEndOfNode.js'
import { isNodeActive } from '../../../core/src/helpers/isNodeActive.js'
import { nextListIsDeeper } from './nextListIsDeeper.js'
import { nextListIsHigher } from './nextListIsHigher.js'

View File

@ -1,7 +1,7 @@
import { Node } from '@tiptap/pm/model'
import { EditorState } from '@tiptap/pm/state'
import { getNodeType } from '../getNodeType.js'
import { getNodeType } from '../../../core/src/helpers/getNodeType.js'
export const listItemHasSubList = (typeOrName: string, state: EditorState, node?: Node) => {
if (!node) {

View File

@ -1,5 +1,5 @@
import {
handleBackspace, handleDelete, KeyboardShortcutCommand, mergeAttributes, Node, wrappingInputRule,
KeyboardShortcutCommand, mergeAttributes, Node, wrappingInputRule,
} from '@tiptap/core'
import { Node as ProseMirrorNode } from '@tiptap/pm/model'
@ -78,10 +78,6 @@ export const TaskItem = Node.create<TaskItemOptions>({
} = {
Enter: () => this.editor.commands.splitListItem(this.name),
'Shift-Tab': () => this.editor.commands.liftListItem(this.name),
Delete: ({ editor }) => handleDelete(editor, this.name),
'Mod-Delete': ({ editor }) => handleDelete(editor, this.name),
Backspace: ({ editor }) => handleBackspace(editor, this.name, [this.options.taskListTypeName]),
'Mod-Backspace': ({ editor }) => handleBackspace(editor, this.name, [this.options.taskListTypeName]),
}
if (!this.options.nested) {