Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nextcloud/text.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJulius Härtl <jus@bitgrid.net>2019-11-29 21:36:43 +0300
committerJulius Härtl <jus@bitgrid.net>2019-12-02 10:39:20 +0300
commitffdf26ff3dcf4295b6f296881226b08afa90235d (patch)
tree6f1eb62960d5df5fc46d114a49dbb4ae5baa63f6 /src
parent10488dcbf791388a49adb0e0d9d42e8ed48c167d (diff)
Implement checkbox support
Signed-off-by: Julius Härtl <jus@bitgrid.net>
Diffstat (limited to 'src')
-rw-r--r--src/EditorFactory.js5
-rw-r--r--src/mixins/menubar.js8
-rw-r--r--src/nodes/ListItem.js161
-rw-r--r--src/nodes/index.js2
-rw-r--r--src/tests/markdown.spec.js8
5 files changed, 181 insertions, 3 deletions
diff --git a/src/EditorFactory.js b/src/EditorFactory.js
index b9acc42dd..9b37cb5c1 100644
--- a/src/EditorFactory.js
+++ b/src/EditorFactory.js
@@ -27,7 +27,6 @@ import {
Link,
BulletList,
OrderedList,
- ListItem,
Blockquote,
CodeBlock,
CodeBlockHighlight,
@@ -36,8 +35,9 @@ import {
Placeholder,
} from 'tiptap-extensions'
import { Strong, Italic, Strike } from './marks'
-import { Image, PlainTextDocument } from './nodes'
+import { Image, PlainTextDocument, ListItem } from './nodes'
import MarkdownIt from 'markdown-it'
+import taskLists from 'markdown-it-task-lists'
import { MarkdownSerializer, defaultMarkdownSerializer } from 'prosemirror-markdown'
@@ -107,6 +107,7 @@ const createEditor = ({ content, onInit, onUpdate, extensions, enableRichEditing
const markdownit = MarkdownIt('commonmark', { html: false, breaks: false })
.enable('strikethrough')
+ .use(taskLists, { enable: true, labelAfter: true })
const SerializeException = function(message) {
this.message = message
diff --git a/src/mixins/menubar.js b/src/mixins/menubar.js
index 482a6e3ba..92b1a3c9b 100644
--- a/src/mixins/menubar.js
+++ b/src/mixins/menubar.js
@@ -124,7 +124,7 @@ export default [
class: 'icon-ul',
isActive: (isActive) => isActive.bullet_list(),
action: (command) => {
- return command.bullet_list()
+ return command.bullet_list_item()
},
},
{
@@ -136,6 +136,12 @@ export default [
},
},
{
+ label: t('text', 'ToDo list'),
+ class: 'icon-checkmark',
+ isActive: (isActive) => isActive.list_item(),
+ action: (command) => command.todo_item(),
+ },
+ {
label: t('text', 'Blockquote'),
class: 'icon-quote',
isActive: (isActive) => isActive.blockquote(),
diff --git a/src/nodes/ListItem.js b/src/nodes/ListItem.js
new file mode 100644
index 000000000..864c69d04
--- /dev/null
+++ b/src/nodes/ListItem.js
@@ -0,0 +1,161 @@
+/*
+ * @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net>
+ *
+ * @author Julius Härtl <jus@bitgrid.net>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+import { ListItem as TiptapListItem } from 'tiptap-extensions'
+import { Plugin } from 'tiptap'
+import { toggleList } from 'tiptap-commands'
+import { findParentNode } from 'prosemirror-utils'
+
+const TYPES = {
+ BULLET: 0,
+ CHECKBOX: 1,
+}
+
+export default class ListItem extends TiptapListItem {
+
+ get defaultOptions() {
+ return {
+ nested: true,
+ }
+ }
+
+ get schema() {
+ return {
+ attrs: {
+ done: {
+ default: false,
+ },
+ type: {
+ default: TYPES.BULLET,
+ },
+ },
+ draggable: true,
+ content: 'paragraph block*',
+ toDOM: node => {
+ if (node.attrs.type === TYPES.BULLET) {
+ return ['li', 0]
+ }
+ const checkboxAttributes = { type: 'checkbox', class: 'checkbox' }
+ if (node.attrs.done) {
+ checkboxAttributes.checked = true
+ }
+ return [
+ 'li',
+ [
+ 'input',
+ checkboxAttributes,
+ ],
+ [
+ 'label',
+ { class: 'checkbox-label' },
+ ['div', { class: 'checkbox-wrapper' }, 0],
+ ],
+ ]
+ },
+ parseDOM: [
+ {
+ priority: 100,
+ tag: 'li',
+ getAttrs: el => {
+ const checkbox = el.querySelector('input[type=checkbox]')
+ return { done: checkbox && checkbox.checked, type: checkbox ? TYPES.CHECKBOX : TYPES.BULLET }
+ },
+ },
+ ],
+ toMarkdown: (state, node) => {
+ if (node.attrs.type === TYPES.CHECKBOX) {
+ state.write(`[${node.attrs.done ? 'x' : ' '}] `)
+ }
+ state.renderContent(node)
+ },
+ }
+ }
+
+ commands({ type, schema }) {
+ return {
+ 'bullet_list_item': () => {
+ return (state, dispatch, view) => {
+ return toggleList(schema.nodes.bullet_list, type)(state, dispatch, view)
+ }
+ },
+ 'todo_item': () => {
+ return (state, dispatch, view) => {
+ const schema = state.schema
+ const selection = state.selection
+ const $from = selection.$from
+ const $to = selection.$to
+ const range = $from.blockRange($to)
+ const tr = state.tr
+
+ if (!range) {
+ return false
+ }
+
+ const parentList = findParentNode(function(node) {
+ return node.type === schema.nodes.list_item
+ })(selection)
+
+ tr.setNodeMarkup(parentList.pos, schema.nodes.list_item, { type: parentList.node.attrs.type === TYPES.CHECKBOX ? TYPES.BULLET : TYPES.CHECKBOX })
+
+ if (dispatch) {
+ dispatch(tr)
+ }
+
+ }
+ },
+ }
+ }
+
+ get plugins() {
+ return [
+ new Plugin({
+ props: {
+ handleClick: (view, pos, event) => {
+ const state = view.state
+ const schema = state.schema
+ const selection = state.selection
+ const $from = selection.$from
+ const $to = selection.$to
+ const range = $from.blockRange($to)
+
+ if (!range) {
+ return false
+ }
+
+ const parentList = findParentNode(function(node) {
+ return node.type === schema.nodes.list_item
+ })(selection)
+
+ if (parentList.node.attrs.type !== TYPES.CHECKBOX) {
+ return
+ }
+
+ const tr = state.tr
+ tr.setNodeMarkup(parentList.pos, schema.nodes.list_item, { done: !parentList.node.attrs.done, type: TYPES.CHECKBOX })
+ view.dispatch(tr)
+ },
+ },
+ }),
+ ]
+ }
+
+}
diff --git a/src/nodes/index.js b/src/nodes/index.js
index e0eaf4882..c24428ee7 100644
--- a/src/nodes/index.js
+++ b/src/nodes/index.js
@@ -22,8 +22,10 @@
import Image from './Image'
import PlainTextDocument from './PlainTextDocument'
+import ListItem from './ListItem'
export {
Image,
PlainTextDocument,
+ ListItem,
}
diff --git a/src/tests/markdown.spec.js b/src/tests/markdown.spec.js
index 2c69aa9d3..354665b8a 100644
--- a/src/tests/markdown.spec.js
+++ b/src/tests/markdown.spec.js
@@ -95,4 +95,12 @@ describe('Markdown serializer from html', () => {
test('images', () => {
expect(markdownThroughEditorHtml('<img src="image" alt="description" />')).toBe('![description](image)')
})
+ test('checkboxes', () => {
+ expect(markdownThroughEditor('- [ ] asd')).toBe('* [ ] asd')
+ expect(markdownThroughEditor('- [X] asd')).toBe('* [x] asd')
+ expect(markdownThroughEditorHtml('<ul><li><input type="checkbox" checked /><label>foo</label></li></ul>')).toBe('* [x] foo')
+ expect(markdownThroughEditorHtml('<ul><li><input type="checkbox" /><label>test</label></li></ul>')).toBe('* [ ] test')
+ expect(markdownThroughEditorHtml('<ul><li><input type="checkbox" checked /><div><h2>Test</h2><p><strong>content</strong></p></div></li></ul>')).toBe('* [x] Test\n\n **content**')
+ expect(markdownThroughEditorHtml('<ul><li><input type="checkbox" checked /><p>Test</p><h1>Block level headline</h1></li></ul>')).toBe('* [x] Test\n\n # Block level headline')
+ })
})