Merge branch 'master' into feature/suggestions
# Conflicts: # examples/Components/App/style.scss # examples/Components/Subnavigation/index.vue # examples/main.js # packages/tiptap-extensions/package.json
This commit is contained in:
14
README.md
14
README.md
@@ -100,9 +100,11 @@ By default the editor will only support paragraphs. Other nodes and marks are av
|
|||||||
<script>
|
<script>
|
||||||
import { Editor } from 'tiptap'
|
import { Editor } from 'tiptap'
|
||||||
import {
|
import {
|
||||||
|
// Nodes
|
||||||
BlockquoteNode,
|
BlockquoteNode,
|
||||||
BulletListNode,
|
BulletListNode,
|
||||||
CodeBlockNode,
|
CodeBlockNode,
|
||||||
|
CodeBlockHighlightNode,
|
||||||
HardBreakNode,
|
HardBreakNode,
|
||||||
HeadingNode,
|
HeadingNode,
|
||||||
ImageNode,
|
ImageNode,
|
||||||
@@ -110,11 +112,18 @@ import {
|
|||||||
OrderedListNode,
|
OrderedListNode,
|
||||||
TodoItemNode,
|
TodoItemNode,
|
||||||
TodoListNode,
|
TodoListNode,
|
||||||
|
|
||||||
|
// Marks
|
||||||
BoldMark,
|
BoldMark,
|
||||||
CodeMark,
|
CodeMark,
|
||||||
ItalicMark,
|
ItalicMark,
|
||||||
LinkMark,
|
LinkMark,
|
||||||
|
StrikeMark,
|
||||||
|
UnderlineMark,
|
||||||
|
|
||||||
|
// General Extensions
|
||||||
HistoryExtension,
|
HistoryExtension,
|
||||||
|
PlaceholderExtension,
|
||||||
} from 'tiptap-extensions'
|
} from 'tiptap-extensions'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -138,7 +147,10 @@ export default {
|
|||||||
new CodeMark(),
|
new CodeMark(),
|
||||||
new ItalicMark(),
|
new ItalicMark(),
|
||||||
new LinkMark(),
|
new LinkMark(),
|
||||||
|
new StrikeMark(),
|
||||||
|
new UnderlineMark(),
|
||||||
new HistoryExtension(),
|
new HistoryExtension(),
|
||||||
|
new PlaceholderExtension(),
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -322,6 +334,7 @@ This is a basic example of building a custom menu. A more advanced menu can be f
|
|||||||
<template>
|
<template>
|
||||||
<editor :extensions="extensions">
|
<editor :extensions="extensions">
|
||||||
<div slot="menubar" slot-scope="{ nodes, marks }">
|
<div slot="menubar" slot-scope="{ nodes, marks }">
|
||||||
|
<div v-if="nodes && marks">
|
||||||
<button :class="{ 'is-active': nodes.heading.active({ level: 1 }) }" @click="nodes.heading.command({ level: 1 })">
|
<button :class="{ 'is-active': nodes.heading.active({ level: 1 }) }" @click="nodes.heading.command({ level: 1 })">
|
||||||
H1
|
H1
|
||||||
</button>
|
</button>
|
||||||
@@ -329,6 +342,7 @@ This is a basic example of building a custom menu. A more advanced menu can be f
|
|||||||
Bold
|
Bold
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div slot="content" slot-scope="props">
|
<div slot="content" slot-scope="props">
|
||||||
<p>This text can be made bold.</p>
|
<p>This text can be made bold.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -21,6 +21,22 @@
|
|||||||
<icon name="italic" />
|
<icon name="italic" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="menubar__button"
|
||||||
|
:class="{ 'is-active': marks.strike.active() }"
|
||||||
|
@click="marks.strike.command"
|
||||||
|
>
|
||||||
|
<icon name="strike" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="menubar__button"
|
||||||
|
:class="{ 'is-active': marks.underline.active() }"
|
||||||
|
@click="marks.underline.command"
|
||||||
|
>
|
||||||
|
<icon name="underline" />
|
||||||
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class="menubar__button"
|
class="menubar__button"
|
||||||
@click="marks.code.command"
|
@click="marks.code.command"
|
||||||
@@ -132,6 +148,8 @@ import {
|
|||||||
CodeMark,
|
CodeMark,
|
||||||
ItalicMark,
|
ItalicMark,
|
||||||
LinkMark,
|
LinkMark,
|
||||||
|
StrikeMark,
|
||||||
|
UnderlineMark,
|
||||||
HistoryExtension,
|
HistoryExtension,
|
||||||
} from 'tiptap-extensions'
|
} from 'tiptap-extensions'
|
||||||
|
|
||||||
@@ -156,6 +174,8 @@ export default {
|
|||||||
new CodeMark(),
|
new CodeMark(),
|
||||||
new ItalicMark(),
|
new ItalicMark(),
|
||||||
new LinkMark(),
|
new LinkMark(),
|
||||||
|
new StrikeMark(),
|
||||||
|
new UnderlineMark(),
|
||||||
new HistoryExtension(),
|
new HistoryExtension(),
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,49 +18,34 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Icon from 'Components/Icon'
|
|
||||||
import { Editor } from 'tiptap'
|
import { Editor } from 'tiptap'
|
||||||
import {
|
import {
|
||||||
BlockquoteNode,
|
|
||||||
BulletListNode,
|
|
||||||
CodeBlockHighlightNode,
|
CodeBlockHighlightNode,
|
||||||
HardBreakNode,
|
HardBreakNode,
|
||||||
HeadingNode,
|
HeadingNode,
|
||||||
ListItemNode,
|
|
||||||
OrderedListNode,
|
|
||||||
TodoItemNode,
|
|
||||||
TodoListNode,
|
|
||||||
BoldMark,
|
BoldMark,
|
||||||
CodeMark,
|
CodeMark,
|
||||||
ItalicMark,
|
ItalicMark,
|
||||||
LinkMark,
|
|
||||||
HistoryExtension,
|
|
||||||
} from 'tiptap-extensions'
|
} from 'tiptap-extensions'
|
||||||
|
|
||||||
import { javascript, css } from './examples'
|
import {
|
||||||
|
javascript,
|
||||||
|
css,
|
||||||
|
} from './examples'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
Editor,
|
Editor,
|
||||||
Icon,
|
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
extensions: [
|
extensions: [
|
||||||
new BlockquoteNode(),
|
|
||||||
new BulletListNode(),
|
|
||||||
new CodeBlockHighlightNode(),
|
new CodeBlockHighlightNode(),
|
||||||
new HardBreakNode(),
|
new HardBreakNode(),
|
||||||
new HeadingNode({ maxLevel: 3 }),
|
new HeadingNode({ maxLevel: 3 }),
|
||||||
new ListItemNode(),
|
|
||||||
new OrderedListNode(),
|
|
||||||
new TodoItemNode(),
|
|
||||||
new TodoListNode(),
|
|
||||||
new BoldMark(),
|
new BoldMark(),
|
||||||
new CodeMark(),
|
new CodeMark(),
|
||||||
new ItalicMark(),
|
new ItalicMark(),
|
||||||
new LinkMark(),
|
|
||||||
new HistoryExtension(),
|
|
||||||
],
|
],
|
||||||
javascript,
|
javascript,
|
||||||
css,
|
css,
|
||||||
|
|||||||
@@ -17,22 +17,12 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Icon from 'Components/Icon'
|
|
||||||
import { Editor } from 'tiptap'
|
import { Editor } from 'tiptap'
|
||||||
import {
|
import {
|
||||||
BlockquoteNode,
|
|
||||||
BulletListNode,
|
|
||||||
CodeBlockNode,
|
|
||||||
HardBreakNode,
|
HardBreakNode,
|
||||||
HeadingNode,
|
HeadingNode,
|
||||||
ListItemNode,
|
|
||||||
OrderedListNode,
|
|
||||||
TodoItemNode,
|
|
||||||
TodoListNode,
|
|
||||||
BoldMark,
|
BoldMark,
|
||||||
CodeMark,
|
|
||||||
ItalicMark,
|
ItalicMark,
|
||||||
LinkMark,
|
|
||||||
HistoryExtension,
|
HistoryExtension,
|
||||||
} from 'tiptap-extensions'
|
} from 'tiptap-extensions'
|
||||||
import IframeNode from './Iframe.js'
|
import IframeNode from './Iframe.js'
|
||||||
@@ -40,24 +30,14 @@ import IframeNode from './Iframe.js'
|
|||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
Editor,
|
Editor,
|
||||||
Icon,
|
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
extensions: [
|
extensions: [
|
||||||
new BlockquoteNode(),
|
|
||||||
new BulletListNode(),
|
|
||||||
new CodeBlockNode(),
|
|
||||||
new HardBreakNode(),
|
new HardBreakNode(),
|
||||||
new HeadingNode({ maxLevel: 3 }),
|
new HeadingNode({ maxLevel: 3 }),
|
||||||
new ListItemNode(),
|
|
||||||
new OrderedListNode(),
|
|
||||||
new TodoItemNode(),
|
|
||||||
new TodoListNode(),
|
|
||||||
new BoldMark(),
|
new BoldMark(),
|
||||||
new CodeMark(),
|
|
||||||
new ItalicMark(),
|
new ItalicMark(),
|
||||||
new LinkMark(),
|
|
||||||
new HistoryExtension(),
|
new HistoryExtension(),
|
||||||
// custom extension
|
// custom extension
|
||||||
new IframeNode(),
|
new IframeNode(),
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<editor class="editor" :extensions="extensions" @update="onUpdate">
|
<editor class="editor" :extensions="extensions" @update="onUpdate" ref="editor">
|
||||||
|
|
||||||
<div class="menubar" slot="menubar" slot-scope="{ nodes, marks }">
|
<div class="menubar" slot="menubar" slot-scope="{ nodes, marks }">
|
||||||
<div v-if="nodes && marks">
|
<div v-if="nodes && marks">
|
||||||
@@ -99,6 +99,15 @@
|
|||||||
|
|
||||||
</editor>
|
</editor>
|
||||||
|
|
||||||
|
<div class="actions">
|
||||||
|
<button class="button" @click="clearContent">
|
||||||
|
Clear Content
|
||||||
|
</button>
|
||||||
|
<button class="button" @click="setContent">
|
||||||
|
Set Content
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="export">
|
<div class="export">
|
||||||
<h3>JSON</h3>
|
<h3>JSON</h3>
|
||||||
<pre><code v-html="json"></code></pre>
|
<pre><code v-html="json"></code></pre>
|
||||||
@@ -161,6 +170,25 @@ export default {
|
|||||||
this.json = getJSON()
|
this.json = getJSON()
|
||||||
this.html = getHTML()
|
this.html = getHTML()
|
||||||
},
|
},
|
||||||
|
clearContent() {
|
||||||
|
this.$refs.editor.clearContent()
|
||||||
|
this.$refs.editor.focus()
|
||||||
|
},
|
||||||
|
setContent() {
|
||||||
|
this.$refs.editor.setContent({
|
||||||
|
type: 'doc',
|
||||||
|
content: [{
|
||||||
|
type: 'paragraph',
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
text: 'This is some inserted text. 👋',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}],
|
||||||
|
})
|
||||||
|
this.$refs.editor.focus()
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -168,10 +196,15 @@ export default {
|
|||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import "~variables";
|
@import "~variables";
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
max-width: 30rem;
|
||||||
|
margin: 0 auto 2rem auto;
|
||||||
|
}
|
||||||
|
|
||||||
.export {
|
.export {
|
||||||
|
|
||||||
max-width: 30rem;
|
max-width: 30rem;
|
||||||
margin: 0 auto 5rem auto;
|
margin: 0 auto 2rem auto;
|
||||||
|
|
||||||
pre {
|
pre {
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
|
|||||||
@@ -17,49 +17,29 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Icon from 'Components/Icon'
|
|
||||||
import { Editor } from 'tiptap'
|
import { Editor } from 'tiptap'
|
||||||
import {
|
import {
|
||||||
BlockquoteNode,
|
|
||||||
BulletListNode,
|
|
||||||
CodeBlockNode,
|
|
||||||
HardBreakNode,
|
HardBreakNode,
|
||||||
HeadingNode,
|
HeadingNode,
|
||||||
ImageNode,
|
ImageNode,
|
||||||
ListItemNode,
|
|
||||||
OrderedListNode,
|
|
||||||
TodoItemNode,
|
|
||||||
TodoListNode,
|
|
||||||
BoldMark,
|
BoldMark,
|
||||||
CodeMark,
|
CodeMark,
|
||||||
ItalicMark,
|
ItalicMark,
|
||||||
LinkMark,
|
|
||||||
HistoryExtension,
|
|
||||||
} from 'tiptap-extensions'
|
} from 'tiptap-extensions'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
Editor,
|
Editor,
|
||||||
Icon,
|
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
extensions: [
|
extensions: [
|
||||||
new BlockquoteNode(),
|
|
||||||
new BulletListNode(),
|
|
||||||
new CodeBlockNode(),
|
|
||||||
new HardBreakNode(),
|
new HardBreakNode(),
|
||||||
new HeadingNode({ maxLevel: 3 }),
|
new HeadingNode({ maxLevel: 3 }),
|
||||||
new ImageNode(),
|
new ImageNode(),
|
||||||
new ListItemNode(),
|
|
||||||
new OrderedListNode(),
|
|
||||||
new TodoItemNode(),
|
|
||||||
new TodoListNode(),
|
|
||||||
new BoldMark(),
|
new BoldMark(),
|
||||||
new CodeMark(),
|
new CodeMark(),
|
||||||
new ItalicMark(),
|
new ItalicMark(),
|
||||||
new LinkMark(),
|
|
||||||
new HistoryExtension(),
|
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
44
examples/Components/Routes/Placeholder/index.vue
Normal file
44
examples/Components/Routes/Placeholder/index.vue
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<editor class="editor" :extensions="extensions">
|
||||||
|
<div class="editor__content" slot="content" slot-scope="props"></div>
|
||||||
|
</editor>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { Editor } from 'tiptap'
|
||||||
|
import {
|
||||||
|
BulletListNode,
|
||||||
|
ListItemNode,
|
||||||
|
PlaceholderExtension,
|
||||||
|
} from 'tiptap-extensions'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
Editor,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
extensions: [
|
||||||
|
new BulletListNode(),
|
||||||
|
new ListItemNode(),
|
||||||
|
new PlaceholderExtension({
|
||||||
|
emptyNodeClass: 'is-empty',
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.editor p.is-empty:first-child::before {
|
||||||
|
content: 'Start typing…';
|
||||||
|
float: left;
|
||||||
|
color: #aaa;
|
||||||
|
pointer-events: none;
|
||||||
|
height: 0;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -16,47 +16,29 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Icon from 'Components/Icon'
|
|
||||||
import { Editor } from 'tiptap'
|
import { Editor } from 'tiptap'
|
||||||
import {
|
import {
|
||||||
BlockquoteNode,
|
|
||||||
BulletListNode,
|
|
||||||
CodeBlockNode,
|
|
||||||
HardBreakNode,
|
HardBreakNode,
|
||||||
HeadingNode,
|
HeadingNode,
|
||||||
ListItemNode,
|
|
||||||
OrderedListNode,
|
|
||||||
TodoItemNode,
|
|
||||||
TodoListNode,
|
|
||||||
BoldMark,
|
BoldMark,
|
||||||
CodeMark,
|
CodeMark,
|
||||||
ItalicMark,
|
ItalicMark,
|
||||||
LinkMark,
|
LinkMark,
|
||||||
HistoryExtension,
|
|
||||||
} from 'tiptap-extensions'
|
} from 'tiptap-extensions'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
Editor,
|
Editor,
|
||||||
Icon,
|
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
extensions: [
|
extensions: [
|
||||||
new BlockquoteNode(),
|
|
||||||
new BulletListNode(),
|
|
||||||
new CodeBlockNode(),
|
|
||||||
new HardBreakNode(),
|
new HardBreakNode(),
|
||||||
new HeadingNode({ maxLevel: 3 }),
|
new HeadingNode({ maxLevel: 3 }),
|
||||||
new ListItemNode(),
|
|
||||||
new OrderedListNode(),
|
|
||||||
new TodoItemNode(),
|
|
||||||
new TodoListNode(),
|
|
||||||
new BoldMark(),
|
new BoldMark(),
|
||||||
new CodeMark(),
|
new CodeMark(),
|
||||||
new ItalicMark(),
|
new ItalicMark(),
|
||||||
new LinkMark(),
|
new LinkMark(),
|
||||||
new HistoryExtension(),
|
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -71,20 +71,14 @@
|
|||||||
import Icon from 'Components/Icon'
|
import Icon from 'Components/Icon'
|
||||||
import { Editor } from 'tiptap'
|
import { Editor } from 'tiptap'
|
||||||
import {
|
import {
|
||||||
BlockquoteNode,
|
|
||||||
BulletListNode,
|
|
||||||
CodeBlockNode,
|
CodeBlockNode,
|
||||||
HardBreakNode,
|
HardBreakNode,
|
||||||
HeadingNode,
|
HeadingNode,
|
||||||
ListItemNode,
|
|
||||||
OrderedListNode,
|
|
||||||
TodoItemNode,
|
TodoItemNode,
|
||||||
TodoListNode,
|
TodoListNode,
|
||||||
BoldMark,
|
BoldMark,
|
||||||
CodeMark,
|
CodeMark,
|
||||||
ItalicMark,
|
ItalicMark,
|
||||||
LinkMark,
|
|
||||||
HistoryExtension,
|
|
||||||
} from 'tiptap-extensions'
|
} from 'tiptap-extensions'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -96,22 +90,60 @@ export default {
|
|||||||
return {
|
return {
|
||||||
customProp: 2,
|
customProp: 2,
|
||||||
extensions: [
|
extensions: [
|
||||||
new BlockquoteNode(),
|
|
||||||
new BulletListNode(),
|
|
||||||
new CodeBlockNode(),
|
new CodeBlockNode(),
|
||||||
new HardBreakNode(),
|
new HardBreakNode(),
|
||||||
new HeadingNode({ maxLevel: 3 }),
|
new HeadingNode({ maxLevel: 3 }),
|
||||||
new ListItemNode(),
|
|
||||||
new OrderedListNode(),
|
|
||||||
new TodoItemNode(),
|
new TodoItemNode(),
|
||||||
new TodoListNode(),
|
new TodoListNode(),
|
||||||
new BoldMark(),
|
new BoldMark(),
|
||||||
new CodeMark(),
|
new CodeMark(),
|
||||||
new ItalicMark(),
|
new ItalicMark(),
|
||||||
new LinkMark(),
|
|
||||||
new HistoryExtension(),
|
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@import "~variables";
|
||||||
|
|
||||||
|
ul[data-type="todo_list"] {
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
li[data-type="todo_item"] {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-checkbox {
|
||||||
|
border: 2px solid $color-black;
|
||||||
|
height: 0.9em;
|
||||||
|
width: 0.9em;
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin-right: 10px;
|
||||||
|
margin-top: 0.3rem;
|
||||||
|
user-select: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 0.2em;
|
||||||
|
background-color: transparent;
|
||||||
|
transition: 0.4s background;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-content {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
li[data-done="true"] {
|
||||||
|
text-decoration: line-through;
|
||||||
|
}
|
||||||
|
|
||||||
|
li[data-done="true"] .todo-checkbox {
|
||||||
|
background-color: $color-black;
|
||||||
|
}
|
||||||
|
|
||||||
|
li[data-done="false"] {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -33,6 +33,9 @@
|
|||||||
<router-link class="subnavigation__link" to="/mentions">
|
<router-link class="subnavigation__link" to="/mentions">
|
||||||
Mentions
|
Mentions
|
||||||
</router-link>
|
</router-link>
|
||||||
|
<router-link class="subnavigation__link" to="/placeholder">
|
||||||
|
Placeholder
|
||||||
|
</router-link>
|
||||||
<router-link class="subnavigation__link" to="/export">
|
<router-link class="subnavigation__link" to="/export">
|
||||||
Export HTML or JSON
|
Export HTML or JSON
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|||||||
1
examples/assets/images/icons/strike.svg
Normal file
1
examples/assets/images/icons/strike.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>text-strike-through</title><path d="M23.75,12.952A1.25,1.25,0,0,0,22.5,11.7H13.564a.492.492,0,0,1-.282-.09c-.722-.513-1.482-.981-2.218-1.432-2.8-1.715-4.5-2.9-4.5-4.863,0-2.235,2.207-2.569,3.523-2.569a4.54,4.54,0,0,1,3.081.764A2.662,2.662,0,0,1,13.615,5.5l0,.3a1.25,1.25,0,1,0,2.5,0l0-.268A4.887,4.887,0,0,0,14.95,1.755C13.949.741,12.359.248,10.091.248c-3.658,0-6.023,1.989-6.023,5.069,0,2.773,1.892,4.512,4,5.927a.25.25,0,0,1-.139.458H1.5a1.25,1.25,0,0,0,0,2.5H12.477a.251.251,0,0,1,.159.058,4.339,4.339,0,0,1,1.932,3.466c0,3.268-3.426,3.522-4.477,3.522-1.814,0-3.139-.405-3.834-1.173a3.394,3.394,0,0,1-.65-2.7,1.25,1.25,0,0,0-2.488-.246A5.76,5.76,0,0,0,4.4,21.753c1.2,1.324,3.114,2,5.688,2,4.174,0,6.977-2.42,6.977-6.022a6.059,6.059,0,0,0-.849-3.147.25.25,0,0,1,.216-.377H22.5A1.25,1.25,0,0,0,23.75,12.952Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 884 B |
1
examples/assets/images/icons/underline.svg
Normal file
1
examples/assets/images/icons/underline.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>text-underline</title><path d="M22.5,21.248H1.5a1.25,1.25,0,0,0,0,2.5h21a1.25,1.25,0,0,0,0-2.5Z"/><path d="M1.978,2.748H3.341a.25.25,0,0,1,.25.25v8.523a8.409,8.409,0,0,0,16.818,0V3a.25.25,0,0,1,.25-.25h1.363a1.25,1.25,0,0,0,0-2.5H16.3a1.25,1.25,0,0,0,0,2.5h1.363a.25.25,0,0,1,.25.25v8.523a5.909,5.909,0,0,1-11.818,0V3a.25.25,0,0,1,.25-.25H7.7a1.25,1.25,0,1,0,0-2.5H1.978a1.25,1.25,0,0,0,0,2.5Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 469 B |
56
examples/assets/sass/editor.scss
Normal file
56
examples/assets/sass/editor.scss
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
.editor {
|
||||||
|
position: relative;
|
||||||
|
max-width: 30rem;
|
||||||
|
margin: 0 auto 5rem auto;
|
||||||
|
|
||||||
|
&__content {
|
||||||
|
pre {
|
||||||
|
padding: 0.7rem 1rem;
|
||||||
|
border-radius: 5px;
|
||||||
|
background: $color-black;
|
||||||
|
color: $color-white;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
overflow-x: auto;
|
||||||
|
|
||||||
|
code {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p code {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 0.4rem;
|
||||||
|
border-radius: 5px;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: bold;
|
||||||
|
background: rgba($color-black, 0.1);
|
||||||
|
color: rgba($color-black, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
ul,
|
||||||
|
ol {
|
||||||
|
padding-left: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
border-left: 3px solid rgba($color-black, 0.1);
|
||||||
|
color: rgba($color-black, 0.8);
|
||||||
|
padding-left: 0.8rem;
|
||||||
|
font-style: italic;
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-width: 100%;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -60,3 +60,20 @@ h2,
|
|||||||
h3 {
|
h3 {
|
||||||
line-height: 1.3;
|
line-height: 1.3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
font-weight: bold;
|
||||||
|
display: inline-flex;
|
||||||
|
background: transparent;
|
||||||
|
border: 0;
|
||||||
|
color: $color-black;
|
||||||
|
padding: 0.2rem 0.5rem;
|
||||||
|
margin-right: 0.2rem;
|
||||||
|
border-radius: 3px;
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: rgba($color-black, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@import "./editor";
|
||||||
|
@import "./menubar";
|
||||||
|
@import "./menububble";
|
||||||
37
examples/assets/sass/menubar.scss
Normal file
37
examples/assets/sass/menubar.scss
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
.menubar {
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
transition: visibility 0.2s 0.4s, opacity 0.2s 0.4s;
|
||||||
|
|
||||||
|
&.is-hidden {
|
||||||
|
visibility: hidden;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-focused {
|
||||||
|
visibility: visible;
|
||||||
|
opacity: 1;
|
||||||
|
transition: visibility 0.2s, opacity 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__button {
|
||||||
|
font-weight: bold;
|
||||||
|
display: inline-flex;
|
||||||
|
background: transparent;
|
||||||
|
border: 0;
|
||||||
|
color: $color-black;
|
||||||
|
padding: 0.2rem 0.5rem;
|
||||||
|
margin-right: 0.2rem;
|
||||||
|
border-radius: 3px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: rgba($color-black, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-active {
|
||||||
|
background-color: rgba($color-black, 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
48
examples/assets/sass/menububble.scss
Normal file
48
examples/assets/sass/menububble.scss
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
.menububble {
|
||||||
|
position: absolute;
|
||||||
|
display: flex;
|
||||||
|
z-index: 20;
|
||||||
|
background: $color-black;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 0.3rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
visibility: hidden;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.2s, visibility 0.2s;
|
||||||
|
|
||||||
|
&__button {
|
||||||
|
display: inline-flex;
|
||||||
|
background: transparent;
|
||||||
|
border: 0;
|
||||||
|
color: $color-white;
|
||||||
|
padding: 0.2rem 0.5rem;
|
||||||
|
margin-right: 0.2rem;
|
||||||
|
border-radius: 3px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: rgba($color-white, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-active {
|
||||||
|
background-color: rgba($color-white, 0.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__form {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__input {
|
||||||
|
font: inherit;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
color: $color-white;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,18 +3,6 @@ import Vue from 'vue'
|
|||||||
import VueRouter from 'vue-router'
|
import VueRouter from 'vue-router'
|
||||||
import svgSpriteLoader from 'helpers/svg-sprite-loader'
|
import svgSpriteLoader from 'helpers/svg-sprite-loader'
|
||||||
import App from 'Components/App'
|
import App from 'Components/App'
|
||||||
import RouteBasic from 'Components/Routes/Basic'
|
|
||||||
import RouteMenuBubble from 'Components/Routes/MenuBubble'
|
|
||||||
import RouteLinks from 'Components/Routes/Links'
|
|
||||||
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'
|
|
||||||
import RouteExport from 'Components/Routes/Export'
|
|
||||||
|
|
||||||
const __svg__ = { path: './assets/images/icons/*.svg', name: 'assets/images/[hash].sprite.svg' }
|
const __svg__ = { path: './assets/images/icons/*.svg', name: 'assets/images/[hash].sprite.svg' }
|
||||||
svgSpriteLoader(__svg__.filename)
|
svgSpriteLoader(__svg__.filename)
|
||||||
@@ -26,84 +14,91 @@ Vue.use(VueRouter)
|
|||||||
const routes = [
|
const routes = [
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
component: RouteBasic,
|
component: () => import('Components/Routes/Basic'),
|
||||||
meta: {
|
meta: {
|
||||||
githubUrl: 'https://github.com/heyscrumpy/tiptap/tree/master/examples/Components/Routes/Basic',
|
githubUrl: 'https://github.com/heyscrumpy/tiptap/tree/master/examples/Components/Routes/Basic',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/menu-bubble',
|
path: '/menu-bubble',
|
||||||
component: RouteMenuBubble,
|
component: () => import('Components/Routes/MenuBubble'),
|
||||||
meta: {
|
meta: {
|
||||||
githubUrl: 'https://github.com/heyscrumpy/tiptap/tree/master/examples/Components/Routes/MenuBubble',
|
githubUrl: 'https://github.com/heyscrumpy/tiptap/tree/master/examples/Components/Routes/MenuBubble',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/links',
|
path: '/links',
|
||||||
component: RouteLinks,
|
component: () => import('Components/Routes/Links'),
|
||||||
meta: {
|
meta: {
|
||||||
githubUrl: 'https://github.com/heyscrumpy/tiptap/tree/master/examples/Components/Routes/Links',
|
githubUrl: 'https://github.com/heyscrumpy/tiptap/tree/master/examples/Components/Routes/Links',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/images',
|
path: '/images',
|
||||||
component: RouteImages,
|
component: () => import('Components/Routes/Images'),
|
||||||
meta: {
|
meta: {
|
||||||
githubUrl: 'https://github.com/heyscrumpy/tiptap/tree/master/examples/Components/Routes/Images',
|
githubUrl: 'https://github.com/heyscrumpy/tiptap/tree/master/examples/Components/Routes/Images',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/hiding-menu-bar',
|
path: '/hiding-menu-bar',
|
||||||
component: RouteHidingMenuBar,
|
component: () => import('Components/Routes/HidingMenuBar'),
|
||||||
meta: {
|
meta: {
|
||||||
githubUrl: 'https://github.com/heyscrumpy/tiptap/tree/master/examples/Components/Routes/HidingMenuBar',
|
githubUrl: 'https://github.com/heyscrumpy/tiptap/tree/master/examples/Components/Routes/HidingMenuBar',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/todo-list',
|
path: '/todo-list',
|
||||||
component: RouteTodoList,
|
component: () => import('Components/Routes/TodoList'),
|
||||||
meta: {
|
meta: {
|
||||||
githubUrl: 'https://github.com/heyscrumpy/tiptap/tree/master/examples/Components/Routes/TodoList',
|
githubUrl: 'https://github.com/heyscrumpy/tiptap/tree/master/examples/Components/Routes/TodoList',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/markdown-shortcuts',
|
path: '/markdown-shortcuts',
|
||||||
component: RouteMarkdownShortcuts,
|
component: () => import('Components/Routes/MarkdownShortcuts'),
|
||||||
meta: {
|
meta: {
|
||||||
githubUrl: 'https://github.com/heyscrumpy/tiptap/tree/master/examples/Components/Routes/MarkdownShortcuts',
|
githubUrl: 'https://github.com/heyscrumpy/tiptap/tree/master/examples/Components/Routes/MarkdownShortcuts',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/code-highlighting',
|
path: '/code-highlighting',
|
||||||
component: RouteCodeHighlighting,
|
component: () => import('Components/Routes/CodeHighlighting'),
|
||||||
meta: {
|
meta: {
|
||||||
githubUrl: 'https://github.com/heyscrumpy/tiptap/tree/master/examples/Components/Routes/CodeHighlighting',
|
githubUrl: 'https://github.com/heyscrumpy/tiptap/tree/master/examples/Components/Routes/CodeHighlighting',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/read-only',
|
path: '/read-only',
|
||||||
component: RouteReadOnly,
|
component: () => import('Components/Routes/ReadOnly'),
|
||||||
meta: {
|
meta: {
|
||||||
githubUrl: 'https://github.com/heyscrumpy/tiptap/tree/master/examples/Components/Routes/ReadOnly',
|
githubUrl: 'https://github.com/heyscrumpy/tiptap/tree/master/examples/Components/Routes/ReadOnly',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/embeds',
|
path: '/embeds',
|
||||||
component: RouteEmbeds,
|
component: () => import('Components/Routes/Embeds'),
|
||||||
meta: {
|
meta: {
|
||||||
githubUrl: 'https://github.com/heyscrumpy/tiptap/tree/master/examples/Components/Routes/Embeds',
|
githubUrl: 'https://github.com/heyscrumpy/tiptap/tree/master/examples/Components/Routes/Embeds',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/mentions',
|
path: '/mentions',
|
||||||
component: RouteMentions,
|
component: () => import('Components/Routes/Mentions'),
|
||||||
meta: {
|
meta: {
|
||||||
githubUrl: 'https://github.com/heyscrumpy/tiptap/tree/master/examples/Components/Routes/Mentions',
|
githubUrl: 'https://github.com/heyscrumpy/tiptap/tree/master/examples/Components/Routes/Mentions',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/placeholder',
|
||||||
|
component: () => import('Components/Routes/Placeholder'),
|
||||||
|
meta: {
|
||||||
|
githubUrl: 'https://github.com/heyscrumpy/tiptap/tree/master/examples/Components/Routes/Placeholder',
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/export',
|
path: '/export',
|
||||||
component: RouteExport,
|
component: () => import('Components/Routes/Export'),
|
||||||
meta: {
|
meta: {
|
||||||
githubUrl: 'https://github.com/heyscrumpy/tiptap/tree/master/examples/Components/Routes/Export',
|
githubUrl: 'https://github.com/heyscrumpy/tiptap/tree/master/examples/Components/Routes/Export',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "tiptap-extensions",
|
"name": "tiptap-extensions",
|
||||||
"version": "0.8.0",
|
"version": "0.14.1",
|
||||||
"description": "Extensions for tiptap",
|
"description": "Extensions for tiptap",
|
||||||
"homepage": "https://tiptap.scrumpy.io",
|
"homepage": "https://tiptap.scrumpy.io",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
"module": "dist/extensions.esm.js",
|
"module": "dist/extensions.esm.js",
|
||||||
"unpkg": "dist/extensions.js",
|
"unpkg": "dist/extensions.js",
|
||||||
"jsdelivr": "dist/extensions.js",
|
"jsdelivr": "dist/extensions.js",
|
||||||
|
"sideEffects": false,
|
||||||
"files": [
|
"files": [
|
||||||
"src",
|
"src",
|
||||||
"dist"
|
"dist"
|
||||||
@@ -24,7 +25,7 @@
|
|||||||
"prosemirror-history": "^1.0.2",
|
"prosemirror-history": "^1.0.2",
|
||||||
"prosemirror-state": "^1.2.2",
|
"prosemirror-state": "^1.2.2",
|
||||||
"prosemirror-view": "^1.5.1",
|
"prosemirror-view": "^1.5.1",
|
||||||
"tiptap": "^0.10.0",
|
"tiptap": "^0.12.1",
|
||||||
"tiptap-commands": "^0.3.0"
|
"tiptap-commands": "^0.3.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
42
packages/tiptap-extensions/src/extensions/Placeholder.js
Normal file
42
packages/tiptap-extensions/src/extensions/Placeholder.js
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import { Extension, Plugin } from 'tiptap'
|
||||||
|
import { Decoration, DecorationSet } from 'prosemirror-view'
|
||||||
|
|
||||||
|
export default class PlaceholderExtension extends Extension {
|
||||||
|
|
||||||
|
get name() {
|
||||||
|
return 'placeholder'
|
||||||
|
}
|
||||||
|
|
||||||
|
get defaultOptions() {
|
||||||
|
return {
|
||||||
|
emptyNodeClass: 'is-empty',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get plugins() {
|
||||||
|
return [
|
||||||
|
new Plugin({
|
||||||
|
props: {
|
||||||
|
decorations: ({ doc }) => {
|
||||||
|
const decorations = []
|
||||||
|
const completelyEmpty = doc.textContent === '' && doc.childCount <= 1 && doc.content.size <= 2
|
||||||
|
|
||||||
|
doc.descendants((node, pos) => {
|
||||||
|
if (!completelyEmpty) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const decoration = Decoration.node(pos, pos + node.nodeSize, {
|
||||||
|
class: this.options.emptyNodeClass,
|
||||||
|
})
|
||||||
|
decorations.push(decoration)
|
||||||
|
})
|
||||||
|
|
||||||
|
return DecorationSet.create(doc, decorations)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -15,5 +15,8 @@ export { default as BoldMark } from './marks/Bold'
|
|||||||
export { default as CodeMark } from './marks/Code'
|
export { default as CodeMark } from './marks/Code'
|
||||||
export { default as ItalicMark } from './marks/Italic'
|
export { default as ItalicMark } from './marks/Italic'
|
||||||
export { default as LinkMark } from './marks/Link'
|
export { default as LinkMark } from './marks/Link'
|
||||||
|
export { default as StrikeMark } from './marks/Strike'
|
||||||
|
export { default as UnderlineMark } from './marks/Underline'
|
||||||
|
|
||||||
export { default as HistoryExtension } from './extensions/History'
|
export { default as HistoryExtension } from './extensions/History'
|
||||||
|
export { default as PlaceholderExtension } from './extensions/Placeholder'
|
||||||
|
|||||||
@@ -7,20 +7,6 @@ export default class LinkMark extends Mark {
|
|||||||
return 'link'
|
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() {
|
get schema() {
|
||||||
return {
|
return {
|
||||||
attrs: {
|
attrs: {
|
||||||
|
|||||||
47
packages/tiptap-extensions/src/marks/Strike.js
Normal file
47
packages/tiptap-extensions/src/marks/Strike.js
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import { Mark } from 'tiptap'
|
||||||
|
import { toggleMark, markInputRule } from 'tiptap-commands'
|
||||||
|
|
||||||
|
export default class StrikeMark extends Mark {
|
||||||
|
|
||||||
|
get name() {
|
||||||
|
return 'strike'
|
||||||
|
}
|
||||||
|
|
||||||
|
get schema() {
|
||||||
|
return {
|
||||||
|
parseDOM: [
|
||||||
|
{
|
||||||
|
tag: 's',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tag: 'del',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tag: 'strike',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
style: 'text-decoration',
|
||||||
|
getAttrs: value => value === 'line-through',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
toDOM: () => ['s', 0],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
keys({ type }) {
|
||||||
|
return {
|
||||||
|
'Mod-d': toggleMark(type),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
command({ type }) {
|
||||||
|
return toggleMark(type)
|
||||||
|
}
|
||||||
|
|
||||||
|
inputRules({ type }) {
|
||||||
|
return [
|
||||||
|
markInputRule(/~([^~]+)~$/, type),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
35
packages/tiptap-extensions/src/marks/Underline.js
Normal file
35
packages/tiptap-extensions/src/marks/Underline.js
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import { Mark } from 'tiptap'
|
||||||
|
import { toggleMark } from 'tiptap-commands'
|
||||||
|
|
||||||
|
export default class UnderlineMark extends Mark {
|
||||||
|
|
||||||
|
get name() {
|
||||||
|
return 'underline'
|
||||||
|
}
|
||||||
|
|
||||||
|
get schema() {
|
||||||
|
return {
|
||||||
|
parseDOM: [
|
||||||
|
{
|
||||||
|
tag: 'u',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
style: 'text-decoration',
|
||||||
|
getAttrs: value => value === 'underline',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
toDOM: () => ['u', 0],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
keys({ type }) {
|
||||||
|
return {
|
||||||
|
'Mod-u': toggleMark(type),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
command({ type }) {
|
||||||
|
return toggleMark(type)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Node } from 'tiptap'
|
import { Node } from 'tiptap'
|
||||||
import { wrappingInputRule, wrapInList, toggleList } from 'tiptap-commands'
|
import { wrappingInputRule, toggleList } from 'tiptap-commands'
|
||||||
|
|
||||||
export default class BulletNode extends Node {
|
export default class BulletNode extends Node {
|
||||||
|
|
||||||
@@ -22,9 +22,9 @@ export default class BulletNode extends Node {
|
|||||||
return toggleList(type, schema.nodes.list_item)
|
return toggleList(type, schema.nodes.list_item)
|
||||||
}
|
}
|
||||||
|
|
||||||
keys({ type }) {
|
keys({ type, schema }) {
|
||||||
return {
|
return {
|
||||||
'Shift-Ctrl-8': wrapInList(type),
|
'Shift-Ctrl-8': toggleList(type, schema.nodes.list_item),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Node } from 'tiptap'
|
import { Node } from 'tiptap'
|
||||||
import { splitListItem, liftListItem, sinkListItem } from 'tiptap-commands'
|
import { splitListItem, liftListItem, sinkListItem } from 'tiptap-commands'
|
||||||
|
|
||||||
export default class OrderedListNode extends Node {
|
export default class ListItemNode extends Node {
|
||||||
|
|
||||||
get name() {
|
get name() {
|
||||||
return 'list_item'
|
return 'list_item'
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Node } from 'tiptap'
|
import { Node } from 'tiptap'
|
||||||
import { wrappingInputRule, wrapInList, toggleList } from 'tiptap-commands'
|
import { wrappingInputRule, toggleList } from 'tiptap-commands'
|
||||||
|
|
||||||
export default class OrderedListNode extends Node {
|
export default class OrderedListNode extends Node {
|
||||||
|
|
||||||
@@ -32,9 +32,9 @@ export default class OrderedListNode extends Node {
|
|||||||
return toggleList(type, schema.nodes.list_item)
|
return toggleList(type, schema.nodes.list_item)
|
||||||
}
|
}
|
||||||
|
|
||||||
keys({ type }) {
|
keys({ type, schema }) {
|
||||||
return {
|
return {
|
||||||
'Shift-Ctrl-9': wrapInList(type),
|
'Shift-Ctrl-9': toggleList(type, schema.nodes.list_item),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "tiptap",
|
"name": "tiptap",
|
||||||
"version": "0.10.0",
|
"version": "0.12.1",
|
||||||
"description": "A rich-text editor for Vue.js",
|
"description": "A rich-text editor for Vue.js",
|
||||||
"homepage": "https://tiptap.scrumpy.io",
|
"homepage": "https://tiptap.scrumpy.io",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { EditorState, Plugin } from 'prosemirror-state'
|
import { EditorState, Plugin } from 'prosemirror-state'
|
||||||
import { EditorView } from 'prosemirror-view'
|
import { EditorView } from 'prosemirror-view'
|
||||||
import { Schema, DOMParser } from 'prosemirror-model'
|
import { Schema, DOMParser, DOMSerializer } from 'prosemirror-model'
|
||||||
import { gapCursor } from 'prosemirror-gapcursor'
|
import { gapCursor } from 'prosemirror-gapcursor'
|
||||||
import { keymap } from 'prosemirror-keymap'
|
import { keymap } from 'prosemirror-keymap'
|
||||||
import { baseKeymap } from 'prosemirror-commands'
|
import { baseKeymap } from 'prosemirror-commands'
|
||||||
@@ -56,6 +56,17 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
|
||||||
|
doc: {
|
||||||
|
deep: true,
|
||||||
|
handler() {
|
||||||
|
this.setContent(this.doc, true)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
render(createElement) {
|
render(createElement) {
|
||||||
const slots = []
|
const slots = []
|
||||||
|
|
||||||
@@ -70,7 +81,7 @@ export default {
|
|||||||
nodes: this.menuActions ? this.menuActions.nodes : null,
|
nodes: this.menuActions ? this.menuActions.nodes : null,
|
||||||
marks: this.menuActions ? this.menuActions.marks : null,
|
marks: this.menuActions ? this.menuActions.marks : null,
|
||||||
focused: this.view ? this.view.focused : false,
|
focused: this.view ? this.view.focused : false,
|
||||||
focus: () => this.view.focus(),
|
focus: this.focus,
|
||||||
})
|
})
|
||||||
slots.push(this.menubarNode)
|
slots.push(this.menubarNode)
|
||||||
} else if (name === 'menububble') {
|
} else if (name === 'menububble') {
|
||||||
@@ -78,7 +89,7 @@ export default {
|
|||||||
nodes: this.menuActions ? this.menuActions.nodes : null,
|
nodes: this.menuActions ? this.menuActions.nodes : null,
|
||||||
marks: this.menuActions ? this.menuActions.marks : null,
|
marks: this.menuActions ? this.menuActions.marks : null,
|
||||||
focused: this.view ? this.view.focused : false,
|
focused: this.view ? this.view.focused : false,
|
||||||
focus: () => this.view.focus(),
|
focus: this.focus,
|
||||||
})
|
})
|
||||||
slots.push(this.menububbleNode)
|
slots.push(this.menububbleNode)
|
||||||
}
|
}
|
||||||
@@ -195,6 +206,12 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
destroyEditor() {
|
||||||
|
if (this.view) {
|
||||||
|
this.view.destroy()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
updateMenuActions() {
|
updateMenuActions() {
|
||||||
this.menuActions = buildMenuActions({
|
this.menuActions = buildMenuActions({
|
||||||
schema: this.schema,
|
schema: this.schema,
|
||||||
@@ -212,6 +229,25 @@ export default {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.emitUpdate()
|
||||||
|
},
|
||||||
|
|
||||||
|
getHTML() {
|
||||||
|
const div = document.createElement('div')
|
||||||
|
const fragment = DOMSerializer
|
||||||
|
.fromSchema(this.schema)
|
||||||
|
.serializeFragment(this.state.doc.content)
|
||||||
|
|
||||||
|
div.appendChild(fragment)
|
||||||
|
|
||||||
|
return div.innerHTML
|
||||||
|
},
|
||||||
|
|
||||||
|
getJSON() {
|
||||||
|
return this.state.doc.toJSON()
|
||||||
|
},
|
||||||
|
|
||||||
|
emitUpdate() {
|
||||||
this.$emit('update', {
|
this.$emit('update', {
|
||||||
getHTML: this.getHTML,
|
getHTML: this.getHTML,
|
||||||
getJSON: this.getJSON,
|
getJSON: this.getJSON,
|
||||||
@@ -219,12 +255,31 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
getHTML() {
|
setContent(content = {}, emitUpdate = false) {
|
||||||
return this.view.dom.innerHTML
|
this.state = EditorState.create({
|
||||||
|
schema: this.state.schema,
|
||||||
|
doc: this.schema.nodeFromJSON(content),
|
||||||
|
plugins: this.state.plugins,
|
||||||
|
})
|
||||||
|
|
||||||
|
this.view.updateState(this.state)
|
||||||
|
|
||||||
|
if (emitUpdate) {
|
||||||
|
this.emitUpdate()
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
getJSON() {
|
clearContent(emitUpdate = false) {
|
||||||
return this.state.doc.toJSON()
|
this.setContent({
|
||||||
|
type: 'doc',
|
||||||
|
content: [{
|
||||||
|
type: 'paragraph',
|
||||||
|
}],
|
||||||
|
}, emitUpdate)
|
||||||
|
},
|
||||||
|
|
||||||
|
focus() {
|
||||||
|
this.view.focus()
|
||||||
},
|
},
|
||||||
|
|
||||||
},
|
},
|
||||||
@@ -233,4 +288,8 @@ export default {
|
|||||||
this.initEditor()
|
this.initEditor()
|
||||||
},
|
},
|
||||||
|
|
||||||
|
beforeDestroy() {
|
||||||
|
this.destroyEditor()
|
||||||
|
},
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,12 +16,12 @@ export default class ComponentView {
|
|||||||
this.editable = editable
|
this.editable = editable
|
||||||
|
|
||||||
this.dom = this.createDOM()
|
this.dom = this.createDOM()
|
||||||
this.contentDOM = this._vm.$refs.content
|
this.contentDOM = this.vm.$refs.content
|
||||||
}
|
}
|
||||||
|
|
||||||
createDOM() {
|
createDOM() {
|
||||||
const Component = Vue.extend(this.component)
|
const Component = Vue.extend(this.component)
|
||||||
this._vm = new Component({
|
this.vm = new Component({
|
||||||
propsData: {
|
propsData: {
|
||||||
node: this.node,
|
node: this.node,
|
||||||
view: this.view,
|
view: this.view,
|
||||||
@@ -32,7 +32,7 @@ export default class ComponentView {
|
|||||||
updateContent: content => this.updateContent(content),
|
updateContent: content => this.updateContent(content),
|
||||||
},
|
},
|
||||||
}).$mount()
|
}).$mount()
|
||||||
return this._vm.$el
|
return this.vm.$el
|
||||||
}
|
}
|
||||||
|
|
||||||
updateAttrs(attrs) {
|
updateAttrs(attrs) {
|
||||||
@@ -75,12 +75,12 @@ export default class ComponentView {
|
|||||||
|
|
||||||
this.node = node
|
this.node = node
|
||||||
this.decorations = decorations
|
this.decorations = decorations
|
||||||
this._vm._props.node = node
|
this.vm._props.node = node
|
||||||
this._vm._props.decorations = decorations
|
this.vm._props.decorations = decorations
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
this._vm.$destroy()
|
this.vm.$destroy()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,11 +94,15 @@ export default class ExtensionManager {
|
|||||||
...commands,
|
...commands,
|
||||||
[name]: attrs => {
|
[name]: attrs => {
|
||||||
view.focus()
|
view.focus()
|
||||||
command({
|
|
||||||
|
const provider = command({
|
||||||
type: schema[`${type}s`][name],
|
type: schema[`${type}s`][name],
|
||||||
attrs,
|
attrs,
|
||||||
schema,
|
schema,
|
||||||
})(view.state, view.dispatch, view)
|
})
|
||||||
|
const callbacks = Array.isArray(provider) ? provider : [provider]
|
||||||
|
|
||||||
|
callbacks.forEach(callback => callback(view.state, view.dispatch, view))
|
||||||
},
|
},
|
||||||
}), {})
|
}), {})
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user