move extensions to own package

This commit is contained in:
Philipp Kühn
2018-08-23 22:08:19 +02:00
parent 2ef23d3003
commit e2176f00fd
32 changed files with 420 additions and 65 deletions

View File

@@ -0,0 +1,26 @@
{
"name": "tiptap-extensions",
"version": "0.1.0",
"description": "Extensions for tiptap",
"homepage": "https://tiptap.scrumpy.io",
"license": "MIT",
"main": "dist/extensions.common.js",
"module": "dist/extensions.esm.js",
"unpkg": "dist/extensions.js",
"jsdelivr": "dist/extensions.js",
"files": [
"src",
"dist"
],
"repository": {
"type": "git",
"url": "git+https://github.com/heyscrumpy/tiptap.git"
},
"bugs": {
"url": "https://github.com/heyscrumpy/tiptap/issues"
},
"dependencies": {
"tiptap": "^0.2.1",
"tiptap-commands": "^0.1.1"
}
}

View File

@@ -0,0 +1,14 @@
export { default as Blockquote } from './nodes/Blockquote'
export { default as BulletList } from './nodes/BulletList'
export { default as CodeBlock } from './nodes/CodeBlock'
export { default as HardBreak } from './nodes/HardBreak'
export { default as Heading } from './nodes/Heading'
export { default as ListItem } from './nodes/ListItem'
export { default as OrderedList } from './nodes/OrderedList'
export { default as TodoItem } from './nodes/TodoItem'
export { default as TodoList } from './nodes/TodoList'
export { default as Bold } from './marks/Bold'
export { default as Code } from './marks/Code'
export { default as Italic } from './marks/Italic'
export { default as Link } from './marks/Link'

View File

@@ -0,0 +1,39 @@
import { Mark } from 'tiptap'
import { toggleMark } from 'tiptap-commands'
export default class BoldMark extends Mark {
get name() {
return 'bold'
}
get schema() {
return {
parseDOM: [
{
tag: 'strong',
},
{
tag: 'b',
getAttrs: node => node.style.fontWeight != 'normal' && null,
},
{
style: 'font-weight',
getAttrs: value => /^(bold(er)?|[5-9]\d{2,})$/.test(value) && null,
},
],
toDOM: () => ['strong', 0],
}
}
keys({ type }) {
return {
'Mod-b': toggleMark(type),
}
}
command({ type }) {
return toggleMark(type)
}
}

View File

@@ -0,0 +1,29 @@
import { Mark } from 'tiptap'
import { toggleMark } from 'tiptap-commands'
export default class CodeMark extends Mark {
get name() {
return 'code'
}
get schema() {
return {
parseDOM: [
{ tag: 'code' },
],
toDOM: () => ['code', 0],
}
}
keys({ type }) {
return {
'Mod-`': toggleMark(type),
}
}
command({ type }) {
return toggleMark(type)
}
}

View File

@@ -0,0 +1,31 @@
import { Mark } from 'tiptap'
import { toggleMark } from 'tiptap-commands'
export default class ItalicMark extends Mark {
get name() {
return 'italic'
}
get schema() {
return {
parseDOM: [
{ tag: 'i' },
{ tag: 'em' },
{ style: 'font-style=italic' },
],
toDOM: () => ['em', 0],
}
}
keys({ type }) {
return {
'Mod-i': toggleMark(type),
}
}
command({ type }) {
return toggleMark(type)
}
}

View File

@@ -0,0 +1,55 @@
import { Mark } from 'tiptap'
import { updateMark, removeMark } from 'tiptap-commands'
export default class LinkMark extends Mark {
get name() {
return 'link'
}
get view() {
return {
props: ['node'],
methods: {
onClick() {
console.log('click on link')
},
},
template: `
<a :href="node.attrs.href" rel="noopener noreferrer nofollow" ref="content" @click="onClick"></a>
`,
}
}
get schema() {
return {
attrs: {
href: {
default: null,
},
},
inclusive: false,
parseDOM: [
{
tag: 'a[href]',
getAttrs: dom => ({
href: dom.getAttribute('href'),
}),
},
],
toDOM: node => ['a', {
...node.attrs,
rel: 'noopener noreferrer nofollow',
}, 0],
}
}
command({ type, attrs }) {
if (attrs.href) {
return updateMark(type, attrs)
}
return removeMark(type)
}
}

View File

@@ -0,0 +1,39 @@
import { Node } from 'tiptap'
import { wrappingInputRule, setBlockType, wrapIn } from 'tiptap-commands'
export default class BlockquoteNode extends Node {
get name() {
return 'blockquote'
}
get schema() {
return {
content: 'block+',
group: 'block',
defining: true,
draggable: false,
parseDOM: [
{ tag: 'blockquote' },
],
toDOM: () => ['blockquote', 0],
}
}
command({ type }) {
return setBlockType(type)
}
keys({ type }) {
return {
'Ctrl->': wrapIn(type),
}
}
inputRules({ type }) {
return [
wrappingInputRule(/^\s*>\s$/, type),
]
}
}

View File

@@ -0,0 +1,37 @@
import { Node } from 'tiptap'
import { wrappingInputRule, wrapInList, toggleList } from 'tiptap-commands'
export default class BulletNode extends Node {
get name() {
return 'bullet_list'
}
get schema() {
return {
content: 'list_item+',
group: 'block',
parseDOM: [
{ tag: 'ul' },
],
toDOM: () => ['ul', 0],
}
}
command({ type, schema }) {
return toggleList(type, schema.nodes.list_item)
}
keys({ type }) {
return {
'Shift-Ctrl-8': wrapInList(type),
}
}
inputRules({ type }) {
return [
wrappingInputRule(/^\s*([-+*])\s$/, type),
]
}
}

View File

@@ -0,0 +1,41 @@
import { Node } from 'tiptap'
import { toggleBlockType, setBlockType, textblockTypeInputRule } from 'tiptap-commands'
export default class CodeBlockNode extends Node {
get name() {
return 'code_block'
}
get schema() {
return {
content: 'text*',
marks: '',
group: 'block',
code: true,
defining: true,
draggable: false,
parseDOM: [
{ tag: 'pre', preserveWhitespace: 'full' },
],
toDOM: () => ['pre', ['code', 0]],
}
}
command({ type, schema }) {
return toggleBlockType(type, schema.nodes.paragraph)
}
keys({ type }) {
return {
'Shift-Ctrl-\\': setBlockType(type),
}
}
inputRules({ type }) {
return [
textblockTypeInputRule(/^```$/, type),
]
}
}

View File

@@ -0,0 +1,33 @@
import { Node } from 'tiptap'
import { chainCommands, exitCode } from 'tiptap-commands'
export default class HardBreakNode extends Node {
get name() {
return 'hard_break'
}
get schema() {
return {
inline: true,
group: 'inline',
selectable: false,
parseDOM: [
{ tag: 'br' },
],
toDOM: () => ['br'],
}
}
keys({ type }) {
const command = chainCommands(exitCode, (state, dispatch) => {
dispatch(state.tr.replaceSelectionWith(type.create()).scrollIntoView())
return true
})
return {
'Mod-Enter': command,
'Shift-Enter': command,
}
}
}

View File

@@ -0,0 +1,59 @@
import { Node } from 'tiptap'
import { setBlockType, textblockTypeInputRule, toggleBlockType } from 'tiptap-commands'
export default class HeadingNode extends Node {
get name() {
return 'heading'
}
get defaultOptions() {
return {
maxLevel: 6,
}
}
get levels() {
return Array.from(new Array(this.options.maxLevel), (value, index) => index + 1)
}
get schema() {
return {
attrs: {
level: {
default: 1,
},
},
content: 'inline*',
group: 'block',
defining: true,
draggable: false,
parseDOM: this.levels.map(level => ({ tag: `h${level}`, attrs: { level } })),
toDOM: node => [`h${node.attrs.level}`, 0],
}
}
command({ type, schema, attrs }) {
return toggleBlockType(type, schema.nodes.paragraph, attrs)
}
keys({ type }) {
return this.levels.reduce((items, level) => ({
...items,
...{
[`Shift-Ctrl-${level}`]: setBlockType(type, { level }),
},
}), {})
}
inputRules({ type }) {
return [
textblockTypeInputRule(
new RegExp(`^(#{1,${this.options.maxLevel}})\\s$`),
type,
match => ({ level: match[1].length }),
),
]
}
}

View File

@@ -0,0 +1,31 @@
import { Node } from 'tiptap'
import { splitListItem, liftListItem, sinkListItem } from 'tiptap-commands'
export default class OrderedListNode extends Node {
get name() {
return 'list_item'
}
get schema() {
return {
content: 'paragraph block*',
group: 'block',
defining: true,
draggable: false,
parseDOM: [
{ tag: 'li' },
],
toDOM: () => ['li', 0],
}
}
keys({ type }) {
return {
Enter: splitListItem(type),
Tab: sinkListItem(type),
'Shift-Tab': liftListItem(type),
}
}
}

View File

@@ -0,0 +1,52 @@
import { Node } from 'tiptap'
import { wrappingInputRule, wrapInList, toggleList } from 'tiptap-commands'
export default class OrderedListNode extends Node {
get name() {
return 'ordered_list'
}
get schema() {
return {
attrs: {
order: {
default: 1,
},
},
content: 'list_item+',
group: 'block',
parseDOM: [
{
tag: 'ol',
getAttrs: dom => ({
order: dom.hasAttribute('start') ? +dom.getAttribute('start') : 1,
}),
},
],
toDOM: node => (node.attrs.order === 1 ? ['ol', 0] : ['ol', { start: node.attrs.order }, 0]),
}
}
command({ type, schema }) {
return toggleList(type, schema.nodes.list_item)
}
keys({ type }) {
return {
'Shift-Ctrl-9': wrapInList(type),
}
}
inputRules({ type }) {
return [
wrappingInputRule(
/^(\d+)\.\s$/,
type,
match => ({ order: +match[1] }),
(match, node) => node.childCount + node.attrs.order === +match[1],
),
]
}
}

View File

@@ -0,0 +1,69 @@
import { Node } from 'tiptap'
import { splitListItem, liftListItem } from 'tiptap-commands'
export default class TodoItemNode extends Node {
get name() {
return 'todo_item'
}
get view() {
return {
props: ['node', 'updateAttrs', 'editable'],
methods: {
onChange() {
if (!this.editable) {
return
}
this.updateAttrs({
done: !this.node.attrs.done,
})
},
},
template: `
<li data-type="todo_item" :data-done="node.attrs.done.toString()">
<span class="todo-checkbox" contenteditable="false" @click="onChange"></span>
<div class="todo-content" ref="content" :contenteditable="editable.toString()"></div>
</li>
`,
}
}
get schema() {
return {
attrs: {
done: {
default: false,
},
},
draggable: false,
content: 'paragraph',
toDOM(node) {
const { done } = node.attrs
return ['li', {
'data-type': 'todo_item',
'data-done': done.toString(),
},
['span', { class: 'todo-checkbox', contenteditable: 'false' }],
['div', { class: 'todo-content' }, 0],
]
},
parseDOM: [{
priority: 51,
tag: '[data-type="todo_item"]',
getAttrs: dom => ({
done: dom.getAttribute('data-done') === 'true',
}),
}],
}
}
keys({ type }) {
return {
Enter: splitListItem(type),
'Shift-Tab': liftListItem(type),
}
}
}

View File

@@ -0,0 +1,32 @@
import { Node } from 'tiptap'
import { wrapInList, wrappingInputRule } from 'tiptap-commands'
export default class BulletNode extends Node {
get name() {
return 'todo_list'
}
get schema() {
return {
group: 'block',
content: 'todo_item+',
toDOM: () => ['ul', { 'data-type': 'todo_list' }, 0],
parseDOM: [{
priority: 51,
tag: '[data-type="todo_list"]',
}],
}
}
command({ type }) {
return wrapInList(type)
}
inputRules({ type }) {
return [
wrappingInputRule(/^\s*(\[ \])\s$/, type),
]
}
}