diff --git a/README.md b/README.md
index ee50840b..5c9fe593 100644
--- a/README.md
+++ b/README.md
@@ -100,9 +100,11 @@ By default the editor will only support paragraphs. Other nodes and marks are av
@@ -168,10 +196,15 @@ export default {
diff --git a/examples/Components/Routes/ReadOnly/index.vue b/examples/Components/Routes/ReadOnly/index.vue
index 5b631cf4..1897a306 100644
--- a/examples/Components/Routes/ReadOnly/index.vue
+++ b/examples/Components/Routes/ReadOnly/index.vue
@@ -16,47 +16,29 @@
\ No newline at end of file
+
+
+
\ No newline at end of file
diff --git a/examples/Components/Subnavigation/index.vue b/examples/Components/Subnavigation/index.vue
index 4b6397f9..18a4a202 100644
--- a/examples/Components/Subnavigation/index.vue
+++ b/examples/Components/Subnavigation/index.vue
@@ -33,6 +33,9 @@
Mentions
+
+ Placeholder
+
Export HTML or JSON
diff --git a/examples/assets/images/icons/strike.svg b/examples/assets/images/icons/strike.svg
new file mode 100644
index 00000000..4e67b52d
--- /dev/null
+++ b/examples/assets/images/icons/strike.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/examples/assets/images/icons/underline.svg b/examples/assets/images/icons/underline.svg
new file mode 100644
index 00000000..ac70c47a
--- /dev/null
+++ b/examples/assets/images/icons/underline.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/examples/assets/sass/editor.scss b/examples/assets/sass/editor.scss
new file mode 100644
index 00000000..a92b6336
--- /dev/null
+++ b/examples/assets/sass/editor.scss
@@ -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;
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/examples/assets/sass/main.scss b/examples/assets/sass/main.scss
index bbab5872..bdba5825 100644
--- a/examples/assets/sass/main.scss
+++ b/examples/assets/sass/main.scss
@@ -59,4 +59,21 @@ h1,
h2,
h3 {
line-height: 1.3;
-}
\ No newline at end of file
+}
+
+.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";
\ No newline at end of file
diff --git a/examples/assets/sass/menubar.scss b/examples/assets/sass/menubar.scss
new file mode 100644
index 00000000..e7dea82b
--- /dev/null
+++ b/examples/assets/sass/menubar.scss
@@ -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);
+ }
+ }
+}
\ No newline at end of file
diff --git a/examples/assets/sass/menububble.scss b/examples/assets/sass/menububble.scss
new file mode 100644
index 00000000..818c5d5e
--- /dev/null
+++ b/examples/assets/sass/menububble.scss
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/examples/main.js b/examples/main.js
index 9bdf9dfd..fb46760d 100644
--- a/examples/main.js
+++ b/examples/main.js
@@ -3,18 +3,6 @@ import Vue from 'vue'
import VueRouter from 'vue-router'
import svgSpriteLoader from 'helpers/svg-sprite-loader'
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' }
svgSpriteLoader(__svg__.filename)
@@ -26,84 +14,91 @@ Vue.use(VueRouter)
const routes = [
{
path: '/',
- component: RouteBasic,
+ component: () => import('Components/Routes/Basic'),
meta: {
githubUrl: 'https://github.com/heyscrumpy/tiptap/tree/master/examples/Components/Routes/Basic',
},
},
{
path: '/menu-bubble',
- component: RouteMenuBubble,
+ component: () => import('Components/Routes/MenuBubble'),
meta: {
githubUrl: 'https://github.com/heyscrumpy/tiptap/tree/master/examples/Components/Routes/MenuBubble',
},
},
{
path: '/links',
- component: RouteLinks,
+ component: () => import('Components/Routes/Links'),
meta: {
githubUrl: 'https://github.com/heyscrumpy/tiptap/tree/master/examples/Components/Routes/Links',
},
},
{
path: '/images',
- component: RouteImages,
+ component: () => import('Components/Routes/Images'),
meta: {
githubUrl: 'https://github.com/heyscrumpy/tiptap/tree/master/examples/Components/Routes/Images',
},
},
{
path: '/hiding-menu-bar',
- component: RouteHidingMenuBar,
+ component: () => import('Components/Routes/HidingMenuBar'),
meta: {
githubUrl: 'https://github.com/heyscrumpy/tiptap/tree/master/examples/Components/Routes/HidingMenuBar',
},
},
{
path: '/todo-list',
- component: RouteTodoList,
+ component: () => import('Components/Routes/TodoList'),
meta: {
githubUrl: 'https://github.com/heyscrumpy/tiptap/tree/master/examples/Components/Routes/TodoList',
},
},
{
path: '/markdown-shortcuts',
- component: RouteMarkdownShortcuts,
+ component: () => import('Components/Routes/MarkdownShortcuts'),
meta: {
githubUrl: 'https://github.com/heyscrumpy/tiptap/tree/master/examples/Components/Routes/MarkdownShortcuts',
},
},
{
path: '/code-highlighting',
- component: RouteCodeHighlighting,
+ component: () => import('Components/Routes/CodeHighlighting'),
meta: {
githubUrl: 'https://github.com/heyscrumpy/tiptap/tree/master/examples/Components/Routes/CodeHighlighting',
},
},
{
path: '/read-only',
- component: RouteReadOnly,
+ component: () => import('Components/Routes/ReadOnly'),
meta: {
githubUrl: 'https://github.com/heyscrumpy/tiptap/tree/master/examples/Components/Routes/ReadOnly',
},
},
{
path: '/embeds',
- component: RouteEmbeds,
+ component: () => import('Components/Routes/Embeds'),
meta: {
githubUrl: 'https://github.com/heyscrumpy/tiptap/tree/master/examples/Components/Routes/Embeds',
},
},
{
path: '/mentions',
- component: RouteMentions,
+ component: () => import('Components/Routes/Mentions'),
meta: {
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',
- component: RouteExport,
+ component: () => import('Components/Routes/Export'),
meta: {
githubUrl: 'https://github.com/heyscrumpy/tiptap/tree/master/examples/Components/Routes/Export',
},
diff --git a/packages/tiptap-extensions/package.json b/packages/tiptap-extensions/package.json
index 184bf910..efe9a005 100644
--- a/packages/tiptap-extensions/package.json
+++ b/packages/tiptap-extensions/package.json
@@ -1,6 +1,6 @@
{
"name": "tiptap-extensions",
- "version": "0.8.0",
+ "version": "0.14.1",
"description": "Extensions for tiptap",
"homepage": "https://tiptap.scrumpy.io",
"license": "MIT",
@@ -8,6 +8,7 @@
"module": "dist/extensions.esm.js",
"unpkg": "dist/extensions.js",
"jsdelivr": "dist/extensions.js",
+ "sideEffects": false,
"files": [
"src",
"dist"
@@ -24,7 +25,7 @@
"prosemirror-history": "^1.0.2",
"prosemirror-state": "^1.2.2",
"prosemirror-view": "^1.5.1",
- "tiptap": "^0.10.0",
+ "tiptap": "^0.12.1",
"tiptap-commands": "^0.3.0"
}
}
diff --git a/packages/tiptap-extensions/src/extensions/Placeholder.js b/packages/tiptap-extensions/src/extensions/Placeholder.js
new file mode 100644
index 00000000..10f9da09
--- /dev/null
+++ b/packages/tiptap-extensions/src/extensions/Placeholder.js
@@ -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)
+ },
+ },
+ }),
+ ]
+ }
+
+}
diff --git a/packages/tiptap-extensions/src/index.js b/packages/tiptap-extensions/src/index.js
index 4ed33ccd..ce7fa0bb 100644
--- a/packages/tiptap-extensions/src/index.js
+++ b/packages/tiptap-extensions/src/index.js
@@ -15,5 +15,8 @@ export { default as BoldMark } from './marks/Bold'
export { default as CodeMark } from './marks/Code'
export { default as ItalicMark } from './marks/Italic'
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 PlaceholderExtension } from './extensions/Placeholder'
diff --git a/packages/tiptap-extensions/src/marks/Link.js b/packages/tiptap-extensions/src/marks/Link.js
index 20ed8be6..ccdbb315 100644
--- a/packages/tiptap-extensions/src/marks/Link.js
+++ b/packages/tiptap-extensions/src/marks/Link.js
@@ -7,20 +7,6 @@ export default class LinkMark extends Mark {
return 'link'
}
- get view() {
- return {
- props: ['node'],
- methods: {
- onClick() {
- console.log('click on link')
- },
- },
- template: `
-
- `,
- }
- }
-
get schema() {
return {
attrs: {
diff --git a/packages/tiptap-extensions/src/marks/Strike.js b/packages/tiptap-extensions/src/marks/Strike.js
new file mode 100644
index 00000000..23c71ec0
--- /dev/null
+++ b/packages/tiptap-extensions/src/marks/Strike.js
@@ -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),
+ ]
+ }
+
+}
diff --git a/packages/tiptap-extensions/src/marks/Underline.js b/packages/tiptap-extensions/src/marks/Underline.js
new file mode 100644
index 00000000..f8a25c6f
--- /dev/null
+++ b/packages/tiptap-extensions/src/marks/Underline.js
@@ -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)
+ }
+
+}
diff --git a/packages/tiptap-extensions/src/nodes/BulletList.js b/packages/tiptap-extensions/src/nodes/BulletList.js
index ab6888f4..d4a0b41b 100644
--- a/packages/tiptap-extensions/src/nodes/BulletList.js
+++ b/packages/tiptap-extensions/src/nodes/BulletList.js
@@ -1,5 +1,5 @@
import { Node } from 'tiptap'
-import { wrappingInputRule, wrapInList, toggleList } from 'tiptap-commands'
+import { wrappingInputRule, toggleList } from 'tiptap-commands'
export default class BulletNode extends Node {
@@ -22,9 +22,9 @@ export default class BulletNode extends Node {
return toggleList(type, schema.nodes.list_item)
}
- keys({ type }) {
+ keys({ type, schema }) {
return {
- 'Shift-Ctrl-8': wrapInList(type),
+ 'Shift-Ctrl-8': toggleList(type, schema.nodes.list_item),
}
}
diff --git a/packages/tiptap-extensions/src/nodes/ListItem.js b/packages/tiptap-extensions/src/nodes/ListItem.js
index e03ad57d..6ed4322e 100644
--- a/packages/tiptap-extensions/src/nodes/ListItem.js
+++ b/packages/tiptap-extensions/src/nodes/ListItem.js
@@ -1,7 +1,7 @@
import { Node } from 'tiptap'
import { splitListItem, liftListItem, sinkListItem } from 'tiptap-commands'
-export default class OrderedListNode extends Node {
+export default class ListItemNode extends Node {
get name() {
return 'list_item'
diff --git a/packages/tiptap-extensions/src/nodes/OrderedList.js b/packages/tiptap-extensions/src/nodes/OrderedList.js
index 61184c09..4d56d288 100644
--- a/packages/tiptap-extensions/src/nodes/OrderedList.js
+++ b/packages/tiptap-extensions/src/nodes/OrderedList.js
@@ -1,5 +1,5 @@
import { Node } from 'tiptap'
-import { wrappingInputRule, wrapInList, toggleList } from 'tiptap-commands'
+import { wrappingInputRule, toggleList } from 'tiptap-commands'
export default class OrderedListNode extends Node {
@@ -32,9 +32,9 @@ export default class OrderedListNode extends Node {
return toggleList(type, schema.nodes.list_item)
}
- keys({ type }) {
+ keys({ type, schema }) {
return {
- 'Shift-Ctrl-9': wrapInList(type),
+ 'Shift-Ctrl-9': toggleList(type, schema.nodes.list_item),
}
}
diff --git a/packages/tiptap/package.json b/packages/tiptap/package.json
index 46942c59..992e6819 100644
--- a/packages/tiptap/package.json
+++ b/packages/tiptap/package.json
@@ -1,6 +1,6 @@
{
"name": "tiptap",
- "version": "0.10.0",
+ "version": "0.12.1",
"description": "A rich-text editor for Vue.js",
"homepage": "https://tiptap.scrumpy.io",
"license": "MIT",
diff --git a/packages/tiptap/src/components/editor.js b/packages/tiptap/src/components/editor.js
index 4f54daeb..0cb0dcf2 100644
--- a/packages/tiptap/src/components/editor.js
+++ b/packages/tiptap/src/components/editor.js
@@ -1,6 +1,6 @@
import { EditorState, Plugin } from 'prosemirror-state'
import { EditorView } from 'prosemirror-view'
-import { Schema, DOMParser } from 'prosemirror-model'
+import { Schema, DOMParser, DOMSerializer } from 'prosemirror-model'
import { gapCursor } from 'prosemirror-gapcursor'
import { keymap } from 'prosemirror-keymap'
import { baseKeymap } from 'prosemirror-commands'
@@ -56,6 +56,17 @@ export default {
}
},
+ watch: {
+
+ doc: {
+ deep: true,
+ handler() {
+ this.setContent(this.doc, true)
+ },
+ },
+
+ },
+
render(createElement) {
const slots = []
@@ -70,7 +81,7 @@ export default {
nodes: this.menuActions ? this.menuActions.nodes : null,
marks: this.menuActions ? this.menuActions.marks : null,
focused: this.view ? this.view.focused : false,
- focus: () => this.view.focus(),
+ focus: this.focus,
})
slots.push(this.menubarNode)
} else if (name === 'menububble') {
@@ -78,7 +89,7 @@ export default {
nodes: this.menuActions ? this.menuActions.nodes : null,
marks: this.menuActions ? this.menuActions.marks : null,
focused: this.view ? this.view.focused : false,
- focus: () => this.view.focus(),
+ focus: this.focus,
})
slots.push(this.menububbleNode)
}
@@ -195,6 +206,12 @@ export default {
})
},
+ destroyEditor() {
+ if (this.view) {
+ this.view.destroy()
+ }
+ },
+
updateMenuActions() {
this.menuActions = buildMenuActions({
schema: this.schema,
@@ -212,6 +229,25 @@ export default {
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', {
getHTML: this.getHTML,
getJSON: this.getJSON,
@@ -219,12 +255,31 @@ export default {
})
},
- getHTML() {
- return this.view.dom.innerHTML
+ setContent(content = {}, emitUpdate = false) {
+ 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() {
- return this.state.doc.toJSON()
+ clearContent(emitUpdate = false) {
+ this.setContent({
+ type: 'doc',
+ content: [{
+ type: 'paragraph',
+ }],
+ }, emitUpdate)
+ },
+
+ focus() {
+ this.view.focus()
},
},
@@ -233,4 +288,8 @@ export default {
this.initEditor()
},
+ beforeDestroy() {
+ this.destroyEditor()
+ },
+
}
diff --git a/packages/tiptap/src/utils/ComponentView.js b/packages/tiptap/src/utils/ComponentView.js
index f54b690b..0d058f47 100644
--- a/packages/tiptap/src/utils/ComponentView.js
+++ b/packages/tiptap/src/utils/ComponentView.js
@@ -16,12 +16,12 @@ export default class ComponentView {
this.editable = editable
this.dom = this.createDOM()
- this.contentDOM = this._vm.$refs.content
+ this.contentDOM = this.vm.$refs.content
}
createDOM() {
const Component = Vue.extend(this.component)
- this._vm = new Component({
+ this.vm = new Component({
propsData: {
node: this.node,
view: this.view,
@@ -32,7 +32,7 @@ export default class ComponentView {
updateContent: content => this.updateContent(content),
},
}).$mount()
- return this._vm.$el
+ return this.vm.$el
}
updateAttrs(attrs) {
@@ -75,12 +75,12 @@ export default class ComponentView {
this.node = node
this.decorations = decorations
- this._vm._props.node = node
- this._vm._props.decorations = decorations
+ this.vm._props.node = node
+ this.vm._props.decorations = decorations
return true
}
destroy() {
- this._vm.$destroy()
+ this.vm.$destroy()
}
}
diff --git a/packages/tiptap/src/utils/ExtensionManager.js b/packages/tiptap/src/utils/ExtensionManager.js
index b1733a54..eac4cb91 100644
--- a/packages/tiptap/src/utils/ExtensionManager.js
+++ b/packages/tiptap/src/utils/ExtensionManager.js
@@ -94,11 +94,15 @@ export default class ExtensionManager {
...commands,
[name]: attrs => {
view.focus()
- command({
+
+ const provider = command({
type: schema[`${type}s`][name],
attrs,
schema,
- })(view.state, view.dispatch, view)
+ })
+ const callbacks = Array.isArray(provider) ? provider : [provider]
+
+ callbacks.forEach(callback => callback(view.state, view.dispatch, view))
},
}), {})
}