Merge branch 'master' into feature/suggestions
# Conflicts: # packages/tiptap-extensions/package.json
This commit is contained in:
@@ -59,6 +59,7 @@ export default {
|
||||
| `editable` | `Boolean` | `true` | When set to `false` the editor is read-only. |
|
||||
| `doc` | `Object` | `null` | The editor state object used by Prosemirror. You can also pass HTML to the `content` slot. When used both, the `content` slot will be ignored. |
|
||||
| `extensions` | `Array` | `[]` | A list of extensions used, by the editor. This can be `Nodes`, `Marks` or `Plugins`. |
|
||||
| `@init` | `Object` | `undefined` | This will return an Object with the current `state` and `view` of Prosemirror on init. |
|
||||
| `@update` | `Object` | `undefined` | This will return an Object with the current `state` of Prosemirror, a `getJSON()` and `getHTML()` function on every change. |
|
||||
|
||||
## Scoped Slots
|
||||
|
||||
@@ -4,6 +4,7 @@ import webpack from 'webpack'
|
||||
import httpProxyMiddleware from 'http-proxy-middleware'
|
||||
import webpackDevMiddleware from 'webpack-dev-middleware'
|
||||
import webpackHotMiddleware from 'webpack-hot-middleware'
|
||||
import historyApiFallbackMiddleware from 'connect-history-api-fallback'
|
||||
import config from './webpack.config'
|
||||
import { sassImport } from './utilities'
|
||||
import { srcPath, sassImportPath } from './paths'
|
||||
@@ -11,6 +12,8 @@ import { srcPath, sassImportPath } from './paths'
|
||||
const bundler = webpack(config)
|
||||
const middlewares = []
|
||||
|
||||
middlewares.push(historyApiFallbackMiddleware())
|
||||
|
||||
// add webpack stuff
|
||||
middlewares.push(webpackDevMiddleware(bundler, {
|
||||
publicPath: config.output.publicPath,
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
background: $color-black;
|
||||
color: $color-white;
|
||||
font-size: 0.8rem;
|
||||
overflow-x: auto;
|
||||
|
||||
code {
|
||||
display: block;
|
||||
|
||||
28
examples/Components/Routes/CodeHighlighting/examples.js
Normal file
28
examples/Components/Routes/CodeHighlighting/examples.js
Normal file
@@ -0,0 +1,28 @@
|
||||
export const javascript = `function $initHighlight(block, flags) {
|
||||
try {
|
||||
if (block.className.search(/\bno\-highlight\b/) != -1)
|
||||
return processBlock(block, true, 0x0F) + ' class=""';
|
||||
} catch (e) {
|
||||
/* handle exception */
|
||||
}
|
||||
for (var i = 0 / 2; i < classes.length; i++) { // "0 / 2" should not be parsed as regexp
|
||||
if (checkCondition(classes[i]) === undefined)
|
||||
return /\d+/g;
|
||||
}
|
||||
}`
|
||||
|
||||
export const css = `@font-face {
|
||||
font-family: Chunkfive; src: url('Chunkfive.otf');
|
||||
}
|
||||
|
||||
body, .usertext {
|
||||
color: #F0F0F0; background: #600;
|
||||
font-family: Chunkfive, sans;
|
||||
}
|
||||
|
||||
@import url(print.css);
|
||||
@media print {
|
||||
a[href^=http]::after {
|
||||
content: attr(href)
|
||||
}
|
||||
}`
|
||||
139
examples/Components/Routes/CodeHighlighting/index.vue
Normal file
139
examples/Components/Routes/CodeHighlighting/index.vue
Normal file
@@ -0,0 +1,139 @@
|
||||
<template>
|
||||
<div>
|
||||
<editor class="editor" :extensions="extensions">
|
||||
|
||||
<div class="editor__content" slot="content" slot-scope="props">
|
||||
<h2>
|
||||
Code Highlighting
|
||||
</h2>
|
||||
<p>
|
||||
These are code blocks with <strong>automatic syntax highlighting</strong> based on highlight.js.
|
||||
</p>
|
||||
<pre><code v-html="javascript"></code></pre>
|
||||
<pre><code v-html="css"></code></pre>
|
||||
</div>
|
||||
|
||||
</editor>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Icon from 'Components/Icon'
|
||||
import { Editor } from 'tiptap'
|
||||
import {
|
||||
BlockquoteNode,
|
||||
BulletListNode,
|
||||
CodeBlockHighlightNode,
|
||||
HardBreakNode,
|
||||
HeadingNode,
|
||||
ListItemNode,
|
||||
OrderedListNode,
|
||||
TodoItemNode,
|
||||
TodoListNode,
|
||||
BoldMark,
|
||||
CodeMark,
|
||||
ItalicMark,
|
||||
LinkMark,
|
||||
HistoryExtension,
|
||||
} from 'tiptap-extensions'
|
||||
|
||||
import { javascript, css } from './examples'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Editor,
|
||||
Icon,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
extensions: [
|
||||
new BlockquoteNode(),
|
||||
new BulletListNode(),
|
||||
new CodeBlockHighlightNode(),
|
||||
new HardBreakNode(),
|
||||
new HeadingNode({ maxLevel: 3 }),
|
||||
new ListItemNode(),
|
||||
new OrderedListNode(),
|
||||
new TodoItemNode(),
|
||||
new TodoListNode(),
|
||||
new BoldMark(),
|
||||
new CodeMark(),
|
||||
new ItalicMark(),
|
||||
new LinkMark(),
|
||||
new HistoryExtension(),
|
||||
],
|
||||
javascript,
|
||||
css,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
pre {
|
||||
&::before {
|
||||
content: attr(data-language);
|
||||
text-transform: uppercase;
|
||||
display: block;
|
||||
text-align: right;
|
||||
font-weight: bold;
|
||||
font-size: 0.6rem;
|
||||
}
|
||||
|
||||
code {
|
||||
|
||||
.hljs-comment,
|
||||
.hljs-quote {
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.hljs-variable,
|
||||
.hljs-template-variable,
|
||||
.hljs-attribute,
|
||||
.hljs-tag,
|
||||
.hljs-name,
|
||||
.hljs-regexp,
|
||||
.hljs-link,
|
||||
.hljs-name,
|
||||
.hljs-selector-id,
|
||||
.hljs-selector-class {
|
||||
color: #f2777a;
|
||||
}
|
||||
|
||||
.hljs-number,
|
||||
.hljs-meta,
|
||||
.hljs-built_in,
|
||||
.hljs-builtin-name,
|
||||
.hljs-literal,
|
||||
.hljs-type,
|
||||
.hljs-params {
|
||||
color: #f99157;
|
||||
}
|
||||
|
||||
.hljs-string,
|
||||
.hljs-symbol,
|
||||
.hljs-bullet {
|
||||
color: #99cc99;
|
||||
}
|
||||
|
||||
.hljs-title,
|
||||
.hljs-section {
|
||||
color: #ffcc66;
|
||||
}
|
||||
|
||||
.hljs-keyword,
|
||||
.hljs-selector-tag {
|
||||
color: #6699cc;
|
||||
}
|
||||
|
||||
.hljs-emphasis {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.hljs-strong {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -21,6 +21,9 @@
|
||||
<router-link class="subnavigation__link" to="/markdown-shortcuts">
|
||||
Markdown Shortcuts
|
||||
</router-link>
|
||||
<router-link class="subnavigation__link" to="/code-highlighting">
|
||||
Code Highlighting
|
||||
</router-link>
|
||||
<router-link class="subnavigation__link" to="/read-only">
|
||||
Read-Only
|
||||
</router-link>
|
||||
|
||||
@@ -10,6 +10,7 @@ import RouteImages from 'Components/Routes/Images'
|
||||
import RouteHidingMenuBar from 'Components/Routes/HidingMenuBar'
|
||||
import RouteTodoList from 'Components/Routes/TodoList'
|
||||
import RouteMarkdownShortcuts from 'Components/Routes/MarkdownShortcuts'
|
||||
import RouteCodeHighlighting from 'Components/Routes/CodeHighlighting'
|
||||
import RouteReadOnly from 'Components/Routes/ReadOnly'
|
||||
import RouteEmbeds from 'Components/Routes/Embeds'
|
||||
import RouteMentions from 'Components/Routes/Mentions'
|
||||
@@ -72,6 +73,13 @@ const routes = [
|
||||
githubUrl: 'https://github.com/heyscrumpy/tiptap/tree/master/examples/Components/Routes/MarkdownShortcuts',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/code-highlighting',
|
||||
component: RouteCodeHighlighting,
|
||||
meta: {
|
||||
githubUrl: 'https://github.com/heyscrumpy/tiptap/tree/master/examples/Components/Routes/CodeHighlighting',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/read-only',
|
||||
component: RouteReadOnly,
|
||||
@@ -104,6 +112,7 @@ const routes = [
|
||||
|
||||
const router = new VueRouter({
|
||||
routes,
|
||||
mode: 'history',
|
||||
linkActiveClass: 'is-active',
|
||||
linkExactActiveClass: 'is-exact-active',
|
||||
})
|
||||
|
||||
4
netlify.toml
Normal file
4
netlify.toml
Normal file
@@ -0,0 +1,4 @@
|
||||
[[redirects]]
|
||||
from = "/*"
|
||||
to = "/index.html"
|
||||
status = 200
|
||||
43
package.json
43
package.json
@@ -29,36 +29,37 @@
|
||||
"ie >= 9"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.0.0-rc.2",
|
||||
"@babel/node": "^7.0.0-rc.2",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.0.0-rc.2",
|
||||
"@babel/plugin-transform-runtime": "^7.0.0-rc.2",
|
||||
"@babel/polyfill": "^7.0.0-rc.2",
|
||||
"@babel/preset-env": "^7.0.0-rc.2",
|
||||
"@babel/preset-stage-2": "^7.0.0-rc.2",
|
||||
"@babel/runtime": "^7.0.0-rc.2",
|
||||
"@babel/core": "^7.0.0",
|
||||
"@babel/node": "^7.0.0",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.0.0",
|
||||
"@babel/plugin-transform-runtime": "^7.0.0",
|
||||
"@babel/polyfill": "^7.0.0",
|
||||
"@babel/preset-env": "^7.0.0",
|
||||
"@babel/preset-stage-2": "^7.0.0",
|
||||
"@babel/runtime": "^7.0.0",
|
||||
"autoprefixer": "^9.1.3",
|
||||
"babel-eslint": "^8.2.5",
|
||||
"babel-loader": "^8.0.0-beta.6",
|
||||
"browser-sync": "^2.24.5",
|
||||
"babel-eslint": "^9.0.0",
|
||||
"babel-loader": "^8.0.2",
|
||||
"browser-sync": "^2.24.7",
|
||||
"connect-history-api-fallback": "^1.5.0",
|
||||
"copy-webpack-plugin": "^4.5.2",
|
||||
"css-loader": "^1.0.0",
|
||||
"eslint": "^5.4.0",
|
||||
"eslint": "^5.5.0",
|
||||
"eslint-config-airbnb-base": "^13.0.0",
|
||||
"eslint-plugin-html": "^4.0.5",
|
||||
"eslint-plugin-import": "^2.13.0",
|
||||
"eslint-plugin-vue": "4.7.1",
|
||||
"file-loader": "^2.0.0",
|
||||
"glob": "^7.1.2",
|
||||
"glob": "^7.1.3",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"http-proxy-middleware": "^0.18.0",
|
||||
"http-proxy-middleware": "^0.19.0",
|
||||
"http-server": "^0.11.1",
|
||||
"imagemin-webpack-plugin": "^2.1.5",
|
||||
"lerna": "^3.1.4",
|
||||
"lerna": "^3.2.1",
|
||||
"mini-css-extract-plugin": "^0.4.2",
|
||||
"minimist": "^1.2.0",
|
||||
"node-sass": "^4.9.1",
|
||||
"optimize-css-assets-webpack-plugin": "^5.0.0",
|
||||
"optimize-css-assets-webpack-plugin": "^5.0.1",
|
||||
"ora": "^3.0.0",
|
||||
"postcss": "^7.0.2",
|
||||
"postcss-loader": "^3.0.0",
|
||||
@@ -71,16 +72,16 @@
|
||||
"rollup-plugin-replace": "^2.0.0",
|
||||
"rollup-plugin-vue": "^4.3.2",
|
||||
"sass-loader": "^7.0.3",
|
||||
"style-loader": "^0.22.1",
|
||||
"uglify-js": "^3.4.7",
|
||||
"style-loader": "^0.23.0",
|
||||
"uglify-js": "^3.4.9",
|
||||
"vue": "^2.5.17",
|
||||
"vue-loader": "^15.4.0",
|
||||
"vue-loader": "^15.4.1",
|
||||
"vue-router": "^3.0.1",
|
||||
"vue-style-loader": "^4.1.0",
|
||||
"vue-template-compiler": "^2.5.17",
|
||||
"webpack": "^4.17.1",
|
||||
"webpack": "^4.17.2",
|
||||
"webpack-dev-middleware": "^3.1.3",
|
||||
"webpack-hot-middleware": "^2.22.2",
|
||||
"webpack-hot-middleware": "^2.23.1",
|
||||
"webpack-manifest-plugin": "^2.0.3",
|
||||
"webpack-svgstore-plugin": "^4.0.3",
|
||||
"zlib": "^1.0.5"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "tiptap-commands",
|
||||
"version": "0.2.4",
|
||||
"version": "0.3.0",
|
||||
"description": "Commands for tiptap",
|
||||
"homepage": "https://tiptap.scrumpy.io",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
// this is a copy of canSplit
|
||||
// see https://github.com/ProseMirror/prosemirror-transform/blob/master/src/structure.js
|
||||
|
||||
function canSplit(doc, pos, depth = 1, typesAfter) {
|
||||
let $pos = doc.resolve(pos), base = $pos.depth - depth
|
||||
let innerType = (typesAfter && typesAfter[typesAfter.length - 1]) || $pos.parent
|
||||
if (base < 0 || $pos.parent.type.spec.isolating ||
|
||||
!$pos.parent.canReplace($pos.index(), $pos.parent.childCount) ||
|
||||
!innerType.type.validContent($pos.parent.content.cutByIndex($pos.index(), $pos.parent.childCount)))
|
||||
return false
|
||||
for (let d = $pos.depth - 1, i = depth - 2; d > base; d--, i--) {
|
||||
let node = $pos.node(d), index = $pos.index(d)
|
||||
if (node.type.spec.isolating) return false
|
||||
let rest = node.content.cutByIndex(index, node.childCount)
|
||||
let after = (typesAfter && typesAfter[i]) || node
|
||||
if (after != node) rest = rest.replaceChild(0, after.type.create(after.attrs))
|
||||
|
||||
/* Change starts from here */
|
||||
// if (!node.canReplace(index + 1, node.childCount) || !after.type.validContent(rest))
|
||||
// return false
|
||||
if (!node.canReplace(index + 1, node.childCount))
|
||||
return false
|
||||
/* Change ends here */
|
||||
}
|
||||
let index = $pos.indexAfter(base)
|
||||
let baseType = typesAfter && typesAfter[0]
|
||||
return $pos.node(base).canReplaceWith(index, index, baseType ? baseType.type : $pos.node(base + 1).type)
|
||||
}
|
||||
|
||||
// this is a copy of splitListItem
|
||||
// see https://github.com/ProseMirror/prosemirror-schema-list/blob/master/src/schema-list.js
|
||||
|
||||
export default function (itemType) {
|
||||
return function(state, dispatch) {
|
||||
let {$from, $to, node} = state.selection
|
||||
if ((node && node.isBlock) || $from.depth < 2 || !$from.sameParent($to)) return false
|
||||
let grandParent = $from.node(-1)
|
||||
if (grandParent.type != itemType) return false
|
||||
if ($from.parent.content.size == 0) {
|
||||
// In an empty block. If this is a nested list, the wrapping
|
||||
// list item should be split. Otherwise, bail out and let next
|
||||
// command handle lifting.
|
||||
if ($from.depth == 2 || $from.node(-3).type != itemType ||
|
||||
$from.index(-2) != $from.node(-2).childCount - 1) return false
|
||||
|
||||
if (dispatch) {
|
||||
let wrap = Fragment.empty, keepItem = $from.index(-1) > 0
|
||||
// Build a fragment containing empty versions of the structure
|
||||
// from the outer list item to the parent node of the cursor
|
||||
for (let d = $from.depth - (keepItem ? 1 : 2); d >= $from.depth - 3; d--)
|
||||
wrap = Fragment.from($from.node(d).copy(wrap))
|
||||
// Add a second list item with an empty default start node
|
||||
wrap = wrap.append(Fragment.from(itemType.createAndFill()))
|
||||
let tr = state.tr.replace($from.before(keepItem ? null : -1), $from.after(-3), new Slice(wrap, keepItem ? 3 : 2, 2))
|
||||
tr.setSelection(state.selection.constructor.near(tr.doc.resolve($from.pos + (keepItem ? 3 : 2))))
|
||||
dispatch(tr.scrollIntoView())
|
||||
}
|
||||
return true
|
||||
}
|
||||
let nextType = $to.pos == $from.end() ? grandParent.contentMatchAt($from.indexAfter(-1)).defaultType : null
|
||||
let tr = state.tr.delete($from.pos, $to.pos)
|
||||
|
||||
/* Change starts from here */
|
||||
// let types = nextType && [null, {type: nextType}]
|
||||
let types = nextType && [{type: itemType}, {type: nextType}]
|
||||
if (!types) types = [{type: itemType}, null]
|
||||
/* Change ends here */
|
||||
|
||||
if (!canSplit(tr.doc, $from.pos, 2, types)) return false
|
||||
if (dispatch) dispatch(tr.split($from.pos, 2, [{type: state.schema.nodes.todo_item, attrs: { done: false }}]).scrollIntoView())
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -40,6 +40,7 @@ import {
|
||||
|
||||
import markInputRule from './commands/markInputRule'
|
||||
import removeMark from './commands/removeMark'
|
||||
import splitToDefaultListItem from './commands/splitToDefaultListItem'
|
||||
import toggleBlockType from './commands/toggleBlockType'
|
||||
import toggleList from './commands/toggleList'
|
||||
import updateMark from './commands/updateMark'
|
||||
@@ -85,6 +86,7 @@ export {
|
||||
// custom
|
||||
markInputRule,
|
||||
removeMark,
|
||||
splitToDefaultListItem,
|
||||
toggleBlockType,
|
||||
toggleList,
|
||||
updateMark,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "tiptap-extensions",
|
||||
"version": "0.6.1",
|
||||
"version": "0.8.0",
|
||||
"description": "Extensions for tiptap",
|
||||
"homepage": "https://tiptap.scrumpy.io",
|
||||
"license": "MIT",
|
||||
@@ -20,10 +20,11 @@
|
||||
"url": "https://github.com/heyscrumpy/tiptap/issues"
|
||||
},
|
||||
"dependencies": {
|
||||
"lowlight": "^1.10.0",
|
||||
"prosemirror-history": "^1.0.2",
|
||||
"prosemirror-state": "^1.2.2",
|
||||
"prosemirror-view": "^1.5.1",
|
||||
"tiptap": "^0.8.0",
|
||||
"tiptap-commands": "^0.2.4"
|
||||
"tiptap": "^0.10.0",
|
||||
"tiptap-commands": "^0.3.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
export { default as BlockquoteNode } from './nodes/Blockquote'
|
||||
export { default as BulletListNode } from './nodes/BulletList'
|
||||
export { default as CodeBlockNode } from './nodes/CodeBlock'
|
||||
export { default as CodeBlockHighlightNode } from './nodes/CodeBlockHighlight'
|
||||
export { default as HardBreakNode } from './nodes/HardBreak'
|
||||
export { default as HeadingNode } from './nodes/Heading'
|
||||
export { default as ImageNode } from './nodes/Image'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Node } from 'tiptap'
|
||||
import { wrappingInputRule, setBlockType, wrapIn } from 'tiptap-commands'
|
||||
import { Node, Plugin } from 'tiptap'
|
||||
import { wrappingInputRule, wrapIn } from 'tiptap-commands'
|
||||
|
||||
export default class BlockquoteNode extends Node {
|
||||
|
||||
|
||||
126
packages/tiptap-extensions/src/nodes/CodeBlockHighlight.js
Normal file
126
packages/tiptap-extensions/src/nodes/CodeBlockHighlight.js
Normal file
@@ -0,0 +1,126 @@
|
||||
import { Node, Plugin } from 'tiptap'
|
||||
import { Decoration, DecorationSet } from 'prosemirror-view'
|
||||
import { toggleBlockType, setBlockType, textblockTypeInputRule } from 'tiptap-commands'
|
||||
import { findBlockNodes } from 'prosemirror-utils'
|
||||
import low from 'lowlight'
|
||||
|
||||
function getDecorations(doc) {
|
||||
const decorations = []
|
||||
|
||||
const blocks = findBlockNodes(doc)
|
||||
.filter(item => item.node.type.name === 'code_block')
|
||||
|
||||
const flatten = list => list.reduce(
|
||||
(a, b) => a.concat(Array.isArray(b) ? flatten(b) : b), [],
|
||||
)
|
||||
|
||||
function parseNodes(nodes, className = []) {
|
||||
return nodes.map(node => {
|
||||
|
||||
const classes = [
|
||||
...className,
|
||||
...node.properties ? node.properties.className : [],
|
||||
]
|
||||
|
||||
if (node.children) {
|
||||
return parseNodes(node.children, classes)
|
||||
}
|
||||
|
||||
return {
|
||||
text: node.value,
|
||||
classes,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
blocks.forEach(block => {
|
||||
let startPos = block.pos + 1
|
||||
const nodes = low.highlightAuto(block.node.textContent).value
|
||||
|
||||
flatten(parseNodes(nodes))
|
||||
.map(node => {
|
||||
const from = startPos
|
||||
const to = from + node.text.length
|
||||
|
||||
startPos = to
|
||||
|
||||
return {
|
||||
...node,
|
||||
from,
|
||||
to,
|
||||
}
|
||||
})
|
||||
.forEach(node => {
|
||||
const decoration = Decoration.inline(node.from, node.to, {
|
||||
class: node.classes.join(' '),
|
||||
})
|
||||
decorations.push(decoration)
|
||||
})
|
||||
})
|
||||
|
||||
return DecorationSet.create(doc, decorations)
|
||||
}
|
||||
|
||||
export default class CodeBlockHighlightNode 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),
|
||||
]
|
||||
}
|
||||
|
||||
get plugins() {
|
||||
return [
|
||||
new Plugin({
|
||||
state: {
|
||||
init(_, { doc }) {
|
||||
return getDecorations(doc)
|
||||
},
|
||||
apply(tr, set) {
|
||||
// TODO: find way to cache decorations
|
||||
// see: https://discuss.prosemirror.net/t/how-to-update-multiple-inline-decorations-on-node-change/1493
|
||||
if (tr.docChanged) {
|
||||
return getDecorations(tr.doc)
|
||||
}
|
||||
return set.map(tr.mapping, tr.doc)
|
||||
},
|
||||
},
|
||||
props: {
|
||||
decorations(state) {
|
||||
return this.getState(state)
|
||||
},
|
||||
},
|
||||
}),
|
||||
]
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Node } from 'tiptap'
|
||||
import { splitListItem, liftListItem } from 'tiptap-commands'
|
||||
import { splitToDefaultListItem, liftListItem } from 'tiptap-commands'
|
||||
|
||||
export default class TodoItemNode extends Node {
|
||||
|
||||
@@ -58,7 +58,7 @@ export default class TodoItemNode extends Node {
|
||||
|
||||
keys({ type }) {
|
||||
return {
|
||||
Enter: splitListItem(type),
|
||||
Enter: splitToDefaultListItem(type),
|
||||
'Shift-Tab': liftListItem(type),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "tiptap",
|
||||
"version": "0.8.0",
|
||||
"version": "0.10.0",
|
||||
"description": "A rich-text editor for Vue.js",
|
||||
"homepage": "https://tiptap.scrumpy.io",
|
||||
"license": "MIT",
|
||||
@@ -31,7 +31,7 @@
|
||||
"prosemirror-model": "^1.5.0",
|
||||
"prosemirror-state": "^1.2.1",
|
||||
"prosemirror-view": "^1.4.3",
|
||||
"tiptap-commands": "^0.2.4",
|
||||
"tiptap-commands": "^0.3.0",
|
||||
"tiptap-utils": "^0.2.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,6 +100,10 @@ export default {
|
||||
this.view = this.createView()
|
||||
this.commands = this.createCommands()
|
||||
this.updateMenuActions()
|
||||
this.$emit('init', {
|
||||
view: this.view,
|
||||
state: this.state,
|
||||
})
|
||||
},
|
||||
|
||||
createSchema() {
|
||||
@@ -202,16 +206,21 @@ export default {
|
||||
dispatchTransaction(transaction) {
|
||||
this.state = this.state.apply(transaction)
|
||||
this.view.updateState(this.state)
|
||||
this.updateMenuActions()
|
||||
|
||||
if (!transaction.docChanged) {
|
||||
return
|
||||
}
|
||||
|
||||
this.$emit('update', {
|
||||
getHTML: this.getHTML,
|
||||
getJSON: this.getJSON,
|
||||
state: this.state,
|
||||
})
|
||||
this.updateMenuActions()
|
||||
},
|
||||
|
||||
getHTML() {
|
||||
return this.contentNode.elm.innerHTML
|
||||
return this.view.dom.innerHTML
|
||||
},
|
||||
|
||||
getJSON() {
|
||||
|
||||
@@ -61,15 +61,6 @@ export default class ExtensionManager {
|
||||
...extensionKeymaps,
|
||||
...nodeMarkKeymaps,
|
||||
].map(keys => keymap(keys))
|
||||
|
||||
// return this.extensions
|
||||
// .filter(extension => ['node', 'mark'].includes(extension.type))
|
||||
// .filter(extension => extension.keys)
|
||||
// .map(extension => extension.keys({
|
||||
// type: schema[`${extension.type}s`][extension.name],
|
||||
// schema,
|
||||
// }))
|
||||
// .map(keys => keymap(keys))
|
||||
}
|
||||
|
||||
inputRules({ schema }) {
|
||||
|
||||
@@ -8,19 +8,13 @@ export default function ({ schema, state, commands }) {
|
||||
const command = commands[name] ? commands[name] : () => {}
|
||||
return { name, active, command }
|
||||
})
|
||||
.reduce((actions, { name, active, command }) => Object.assign({}, actions, {
|
||||
.reduce((actions, { name, active, command }) => ({
|
||||
...actions,
|
||||
[name]: {
|
||||
active,
|
||||
command,
|
||||
},
|
||||
}), {})
|
||||
// .reduce((actions, { name, active, command }) => ({
|
||||
// ...actions,
|
||||
// [name]: {
|
||||
// active,
|
||||
// command,
|
||||
// },
|
||||
// }), {})
|
||||
|
||||
const marks = Object.entries(schema.marks)
|
||||
.map(([name]) => {
|
||||
@@ -34,21 +28,14 @@ export default function ({ schema, state, commands }) {
|
||||
command,
|
||||
}
|
||||
})
|
||||
.reduce((actions, { name, active, attrs, command }) => Object.assign({}, actions, {
|
||||
.reduce((actions, { name, active, attrs, command }) => ({
|
||||
...actions,
|
||||
[name]: {
|
||||
active,
|
||||
attrs,
|
||||
command,
|
||||
},
|
||||
}), {})
|
||||
// .reduce((actions, { name, active, attrs, command }) => ({
|
||||
// ...actions,
|
||||
// [name]: {
|
||||
// active,
|
||||
// attrs,
|
||||
// command,
|
||||
// },
|
||||
// }), {})
|
||||
|
||||
return {
|
||||
nodes,
|
||||
|
||||
@@ -2,9 +2,11 @@ import ComponentView from './ComponentView'
|
||||
|
||||
export default function initNodeViews({ nodes, editable }) {
|
||||
const nodeViews = {}
|
||||
|
||||
Object.keys(nodes).forEach(nodeName => {
|
||||
nodeViews[nodeName] = (node, view, getPos, decorations) => {
|
||||
const component = nodes[nodeName]
|
||||
|
||||
return new ComponentView(component, {
|
||||
node,
|
||||
view,
|
||||
@@ -14,5 +16,6 @@ export default function initNodeViews({ nodes, editable }) {
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
return nodeViews
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ class Toolbar {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// Otherwise, reposition it and update its content
|
||||
this.show()
|
||||
const { from, to } = state.selection
|
||||
@@ -54,6 +53,7 @@ class Toolbar {
|
||||
if (event && event.relatedTarget) {
|
||||
return
|
||||
}
|
||||
|
||||
this.element.style.visibility = 'hidden'
|
||||
this.element.style.opacity = 0
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user