diff --git a/menu.js b/menu.js new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/tiddlywiki/prosemirror/development.tid b/plugins/tiddlywiki/prosemirror/development.tid index 8d50f720b..6e660187e 100755 --- a/plugins/tiddlywiki/prosemirror/development.tid +++ b/plugins/tiddlywiki/prosemirror/development.tid @@ -25,8 +25,3 @@ Downloaded from JsDelivr CDN (like `https://cdn.jsdelivr.net/npm/prosemirror-sta * [ext[crelt|https://cdn.jsdelivr.net/npm/crelt@latest/dist/index.cjs]]: v1.0.6 * [ext[rope-sequence|https://cdn.jsdelivr.net/npm/rope-sequence@latest/dist/index.cjs]]: v1.3.4 * [ext[prosemirror-safari-ime-span|https://cdn.jsdelivr.net/npm/prosemirror-safari-ime-span@latest/dist/index.cjs]]: v1.0.2 - -!! TODO - -* remove prosemirror-example-setup, move all configs here. And remove `prosemirror-schema-list` that it depends on. -* prosemirror-flat-list only add dot style to list container, not each list item. Make it looks weird. And when creating new list, it will make each item to be a full new list, which is also weird. diff --git a/plugins/tiddlywiki/prosemirror/files/prosemirror-example-setup.cjs b/plugins/tiddlywiki/prosemirror/files/prosemirror-example-setup.cjs deleted file mode 100644 index 2bab07c32..000000000 --- a/plugins/tiddlywiki/prosemirror/files/prosemirror-example-setup.cjs +++ /dev/null @@ -1,469 +0,0 @@ -'use strict'; - -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); Object.defineProperty(subClass, "prototype", { writable: false }); if (superClass) _setPrototypeOf(subClass, superClass); } -function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } -function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } -function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } else if (call !== void 0) { throw new TypeError("Derived constructors may only return object or undefined"); } return _assertThisInitialized(self); } -function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } -function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } } -function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } -function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } -function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } } -function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; } -function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return _typeof(key) === "symbol" ? key : String(key); } -function _toPrimitive(input, hint) { if (_typeof(input) !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (_typeof(res) !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); } -var prosemirrorKeymap = require('prosemirror-keymap'); -var prosemirrorHistory = require('prosemirror-history'); -var prosemirrorCommands = require('prosemirror-commands'); -var prosemirrorState = require('prosemirror-state'); -var prosemirrorDropcursor = require('prosemirror-dropcursor'); -var prosemirrorGapcursor = require('prosemirror-gapcursor'); -var prosemirrorMenu = require('prosemirror-menu'); -var prosemirrorSchemaList = require('prosemirror-schema-list'); -var prosemirrorInputrules = require('prosemirror-inputrules'); -var prefix = "ProseMirror-prompt"; -function openPrompt(options) { - var wrapper = document.body.appendChild(document.createElement("div")); - wrapper.className = prefix; - var mouseOutside = function mouseOutside(e) { - if (!wrapper.contains(e.target)) close(); - }; - setTimeout(function () { - return window.addEventListener("mousedown", mouseOutside); - }, 50); - var close = function close() { - window.removeEventListener("mousedown", mouseOutside); - if (wrapper.parentNode) wrapper.parentNode.removeChild(wrapper); - }; - var domFields = []; - for (var name in options.fields) domFields.push(options.fields[name].render()); - var submitButton = document.createElement("button"); - submitButton.type = "submit"; - submitButton.className = prefix + "-submit"; - submitButton.textContent = "OK"; - var cancelButton = document.createElement("button"); - cancelButton.type = "button"; - cancelButton.className = prefix + "-cancel"; - cancelButton.textContent = "Cancel"; - cancelButton.addEventListener("click", close); - var form = wrapper.appendChild(document.createElement("form")); - if (options.title) form.appendChild(document.createElement("h5")).textContent = options.title; - domFields.forEach(function (field) { - form.appendChild(document.createElement("div")).appendChild(field); - }); - var buttons = form.appendChild(document.createElement("div")); - buttons.className = prefix + "-buttons"; - buttons.appendChild(submitButton); - buttons.appendChild(document.createTextNode(" ")); - buttons.appendChild(cancelButton); - var box = wrapper.getBoundingClientRect(); - wrapper.style.top = (window.innerHeight - box.height) / 2 + "px"; - wrapper.style.left = (window.innerWidth - box.width) / 2 + "px"; - var submit = function submit() { - var params = getValues(options.fields, domFields); - if (params) { - close(); - options.callback(params); - } - }; - form.addEventListener("submit", function (e) { - e.preventDefault(); - submit(); - }); - form.addEventListener("keydown", function (e) { - if (e.keyCode == 27) { - e.preventDefault(); - close(); - } else if (e.keyCode == 13 && !(e.ctrlKey || e.metaKey || e.shiftKey)) { - e.preventDefault(); - submit(); - } else if (e.keyCode == 9) { - window.setTimeout(function () { - if (!wrapper.contains(document.activeElement)) close(); - }, 500); - } - }); - var input = form.elements[0]; - if (input) input.focus(); -} -function getValues(fields, domFields) { - var result = Object.create(null), - i = 0; - for (var name in fields) { - var field = fields[name], - dom = domFields[i++]; - var value = field.read(dom), - bad = field.validate(value); - if (bad) { - reportInvalid(dom, bad); - return null; - } - result[name] = field.clean(value); - } - return result; -} -function reportInvalid(dom, message) { - var parent = dom.parentNode; - var msg = parent.appendChild(document.createElement("div")); - msg.style.left = dom.offsetLeft + dom.offsetWidth + 2 + "px"; - msg.style.top = dom.offsetTop - 5 + "px"; - msg.className = "ProseMirror-invalid"; - msg.textContent = message; - setTimeout(function () { - return parent.removeChild(msg); - }, 1500); -} -var Field = function () { - function Field(options) { - _classCallCheck(this, Field); - this.options = options; - } - _createClass(Field, [{ - key: "read", - value: function read(dom) { - return dom.value; - } - }, { - key: "validateType", - value: function validateType(value) { - return null; - } - }, { - key: "validate", - value: function validate(value) { - if (!value && this.options.required) return "Required field"; - return this.validateType(value) || (this.options.validate ? this.options.validate(value) : null); - } - }, { - key: "clean", - value: function clean(value) { - return this.options.clean ? this.options.clean(value) : value; - } - }]); - return Field; -}(); -var TextField = function (_Field) { - _inherits(TextField, _Field); - var _super = _createSuper(TextField); - function TextField() { - _classCallCheck(this, TextField); - return _super.apply(this, arguments); - } - _createClass(TextField, [{ - key: "render", - value: function render() { - var input = document.createElement("input"); - input.type = "text"; - input.placeholder = this.options.label; - input.value = this.options.value || ""; - input.autocomplete = "off"; - return input; - } - }]); - return TextField; -}(Field); -function canInsert(state, nodeType) { - var $from = state.selection.$from; - for (var d = $from.depth; d >= 0; d--) { - var index = $from.index(d); - if ($from.node(d).canReplaceWith(index, index, nodeType)) return true; - } - return false; -} -function insertImageItem(nodeType) { - return new prosemirrorMenu.MenuItem({ - title: "Insert image", - label: "Image", - enable: function enable(state) { - return canInsert(state, nodeType); - }, - run: function run(state, _, view) { - var _state$selection = state.selection, - from = _state$selection.from, - to = _state$selection.to, - attrs = null; - if (state.selection instanceof prosemirrorState.NodeSelection && state.selection.node.type == nodeType) attrs = state.selection.node.attrs; - openPrompt({ - title: "Insert image", - fields: { - src: new TextField({ - label: "Location", - required: true, - value: attrs && attrs.src - }), - title: new TextField({ - label: "Title", - value: attrs && attrs.title - }), - alt: new TextField({ - label: "Description", - value: attrs ? attrs.alt : state.doc.textBetween(from, to, " ") - }) - }, - callback: function callback(attrs) { - view.dispatch(view.state.tr.replaceSelectionWith(nodeType.createAndFill(attrs))); - view.focus(); - } - }); - } - }); -} -function cmdItem(cmd, options) { - var passedOptions = { - label: options.title, - run: cmd - }; - for (var prop in options) passedOptions[prop] = options[prop]; - if (!options.enable && !options.select) passedOptions[options.enable ? "enable" : "select"] = function (state) { - return cmd(state); - }; - return new prosemirrorMenu.MenuItem(passedOptions); -} -function markActive(state, type) { - var _state$selection2 = state.selection, - from = _state$selection2.from, - $from = _state$selection2.$from, - to = _state$selection2.to, - empty = _state$selection2.empty; - if (empty) return !!type.isInSet(state.storedMarks || $from.marks());else return state.doc.rangeHasMark(from, to, type); -} -function markItem(markType, options) { - var passedOptions = { - active: function active(state) { - return markActive(state, markType); - } - }; - for (var prop in options) passedOptions[prop] = options[prop]; - return cmdItem(prosemirrorCommands.toggleMark(markType), passedOptions); -} -function linkItem(markType) { - return new prosemirrorMenu.MenuItem({ - title: "Add or remove link", - icon: prosemirrorMenu.icons.link, - active: function active(state) { - return markActive(state, markType); - }, - enable: function enable(state) { - return !state.selection.empty; - }, - run: function run(state, dispatch, view) { - if (markActive(state, markType)) { - prosemirrorCommands.toggleMark(markType)(state, dispatch); - return true; - } - openPrompt({ - title: "Create a link", - fields: { - href: new TextField({ - label: "Link target", - required: true - }), - title: new TextField({ - label: "Title" - }) - }, - callback: function callback(attrs) { - prosemirrorCommands.toggleMark(markType, attrs)(view.state, view.dispatch); - view.focus(); - } - }); - } - }); -} -function wrapListItem(nodeType, options) { - return cmdItem(prosemirrorSchemaList.wrapInList(nodeType, options.attrs), options); -} -function buildMenuItems(schema) { - var r = {}; - var mark; - if (mark = schema.marks.strong) r.toggleStrong = markItem(mark, { - title: "Toggle strong style", - icon: prosemirrorMenu.icons.strong - }); - if (mark = schema.marks.em) r.toggleEm = markItem(mark, { - title: "Toggle emphasis", - icon: prosemirrorMenu.icons.em - }); - if (mark = schema.marks.code) r.toggleCode = markItem(mark, { - title: "Toggle code font", - icon: prosemirrorMenu.icons.code - }); - if (mark = schema.marks.link) r.toggleLink = linkItem(mark); - var node; - if (node = schema.nodes.image) r.insertImage = insertImageItem(node); - if (node = schema.nodes.bullet_list) r.wrapBulletList = wrapListItem(node, { - title: "Wrap in bullet list", - icon: prosemirrorMenu.icons.bulletList - }); - if (node = schema.nodes.ordered_list) r.wrapOrderedList = wrapListItem(node, { - title: "Wrap in ordered list", - icon: prosemirrorMenu.icons.orderedList - }); - if (node = schema.nodes.blockquote) r.wrapBlockQuote = prosemirrorMenu.wrapItem(node, { - title: "Wrap in block quote", - icon: prosemirrorMenu.icons.blockquote - }); - if (node = schema.nodes.paragraph) r.makeParagraph = prosemirrorMenu.blockTypeItem(node, { - title: "Change to paragraph", - label: "Plain" - }); - if (node = schema.nodes.code_block) r.makeCodeBlock = prosemirrorMenu.blockTypeItem(node, { - title: "Change to code block", - label: "Code" - }); - if (node = schema.nodes.heading) for (var i = 1; i <= 10; i++) r["makeHead" + i] = prosemirrorMenu.blockTypeItem(node, { - title: "Change to heading " + i, - label: "Level " + i, - attrs: { - level: i - } - }); - if (node = schema.nodes.horizontal_rule) { - var hr = node; - r.insertHorizontalRule = new prosemirrorMenu.MenuItem({ - title: "Insert horizontal rule", - label: "Horizontal rule", - enable: function enable(state) { - return canInsert(state, hr); - }, - run: function run(state, dispatch) { - dispatch(state.tr.replaceSelectionWith(hr.create())); - } - }); - } - var cut = function cut(arr) { - return arr.filter(function (x) { - return x; - }); - }; - r.insertMenu = new prosemirrorMenu.Dropdown(cut([r.insertImage, r.insertHorizontalRule]), { - label: "Insert" - }); - r.typeMenu = new prosemirrorMenu.Dropdown(cut([r.makeParagraph, r.makeCodeBlock, r.makeHead1 && new prosemirrorMenu.DropdownSubmenu(cut([r.makeHead1, r.makeHead2, r.makeHead3, r.makeHead4, r.makeHead5, r.makeHead6]), { - label: "Heading" - })]), { - label: "Type..." - }); - r.inlineMenu = [cut([r.toggleStrong, r.toggleEm, r.toggleCode, r.toggleLink])]; - r.blockMenu = [cut([r.wrapBulletList, r.wrapOrderedList, r.wrapBlockQuote, prosemirrorMenu.joinUpItem, prosemirrorMenu.liftItem, prosemirrorMenu.selectParentNodeItem])]; - r.fullMenu = r.inlineMenu.concat([[r.insertMenu, r.typeMenu]], [[prosemirrorMenu.undoItem, prosemirrorMenu.redoItem]], r.blockMenu); - return r; -} -var mac = typeof navigator != "undefined" ? /Mac|iP(hone|[oa]d)/.test(navigator.platform) : false; -function buildKeymap(schema, mapKeys) { - var keys = {}, - type; - function bind(key, cmd) { - if (mapKeys) { - var mapped = mapKeys[key]; - if (mapped === false) return; - if (mapped) key = mapped; - } - keys[key] = cmd; - } - bind("Mod-z", prosemirrorHistory.undo); - bind("Shift-Mod-z", prosemirrorHistory.redo); - bind("Backspace", prosemirrorInputrules.undoInputRule); - if (!mac) bind("Mod-y", prosemirrorHistory.redo); - bind("Alt-ArrowUp", prosemirrorCommands.joinUp); - bind("Alt-ArrowDown", prosemirrorCommands.joinDown); - bind("Mod-BracketLeft", prosemirrorCommands.lift); - bind("Escape", prosemirrorCommands.selectParentNode); - if (type = schema.marks.strong) { - bind("Mod-b", prosemirrorCommands.toggleMark(type)); - bind("Mod-B", prosemirrorCommands.toggleMark(type)); - } - if (type = schema.marks.em) { - bind("Mod-i", prosemirrorCommands.toggleMark(type)); - bind("Mod-I", prosemirrorCommands.toggleMark(type)); - } - if (type = schema.marks.code) bind("Mod-`", prosemirrorCommands.toggleMark(type)); - if (type = schema.nodes.bullet_list) bind("Shift-Ctrl-8", prosemirrorSchemaList.wrapInList(type)); - if (type = schema.nodes.ordered_list) bind("Shift-Ctrl-9", prosemirrorSchemaList.wrapInList(type)); - if (type = schema.nodes.blockquote) bind("Ctrl->", prosemirrorCommands.wrapIn(type)); - if (type = schema.nodes.hard_break) { - var br = type, - cmd = prosemirrorCommands.chainCommands(prosemirrorCommands.exitCode, function (state, dispatch) { - if (dispatch) dispatch(state.tr.replaceSelectionWith(br.create()).scrollIntoView()); - return true; - }); - bind("Mod-Enter", cmd); - bind("Shift-Enter", cmd); - if (mac) bind("Ctrl-Enter", cmd); - } - if (type = schema.nodes.list_item) { - bind("Enter", prosemirrorSchemaList.splitListItem(type)); - bind("Mod-[", prosemirrorSchemaList.liftListItem(type)); - bind("Mod-]", prosemirrorSchemaList.sinkListItem(type)); - } - if (type = schema.nodes.paragraph) bind("Shift-Ctrl-0", prosemirrorCommands.setBlockType(type)); - if (type = schema.nodes.code_block) bind("Shift-Ctrl-\\", prosemirrorCommands.setBlockType(type)); - if (type = schema.nodes.heading) for (var i = 1; i <= 6; i++) bind("Shift-Ctrl-" + i, prosemirrorCommands.setBlockType(type, { - level: i - })); - if (type = schema.nodes.horizontal_rule) { - var hr = type; - bind("Mod-_", function (state, dispatch) { - if (dispatch) dispatch(state.tr.replaceSelectionWith(hr.create()).scrollIntoView()); - return true; - }); - } - return keys; -} -function blockQuoteRule(nodeType) { - return prosemirrorInputrules.wrappingInputRule(/^\s*>\s$/, nodeType); -} -function orderedListRule(nodeType) { - return prosemirrorInputrules.wrappingInputRule(/^(\d+)\.\s$/, nodeType, function (match) { - return { - order: +match[1] - }; - }, function (match, node) { - return node.childCount + node.attrs.order == +match[1]; - }); -} -function bulletListRule(nodeType) { - return prosemirrorInputrules.wrappingInputRule(/^\s*([-+*])\s$/, nodeType); -} -function codeBlockRule(nodeType) { - return prosemirrorInputrules.textblockTypeInputRule(/^```$/, nodeType); -} -function headingRule(nodeType, maxLevel) { - return prosemirrorInputrules.textblockTypeInputRule(new RegExp("^(#{1," + maxLevel + "})\\s$"), nodeType, function (match) { - return { - level: match[1].length - }; - }); -} -function buildInputRules(schema) { - var rules = prosemirrorInputrules.smartQuotes.concat(prosemirrorInputrules.ellipsis, prosemirrorInputrules.emDash), - type; - if (type = schema.nodes.blockquote) rules.push(blockQuoteRule(type)); - if (type = schema.nodes.ordered_list) rules.push(orderedListRule(type)); - if (type = schema.nodes.bullet_list) rules.push(bulletListRule(type)); - if (type = schema.nodes.code_block) rules.push(codeBlockRule(type)); - if (type = schema.nodes.heading) rules.push(headingRule(type, 6)); - return prosemirrorInputrules.inputRules({ - rules: rules - }); -} -function exampleSetup(options) { - var plugins = [buildInputRules(options.schema), prosemirrorKeymap.keymap(buildKeymap(options.schema, options.mapKeys)), prosemirrorKeymap.keymap(prosemirrorCommands.baseKeymap), prosemirrorDropcursor.dropCursor(), prosemirrorGapcursor.gapCursor()]; - if (options.menuBar !== false) plugins.push(prosemirrorMenu.menuBar({ - floating: options.floatingMenu !== false, - content: options.menuContent || buildMenuItems(options.schema).fullMenu - })); - if (options.history !== false) plugins.push(prosemirrorHistory.history()); - return plugins.concat(new prosemirrorState.Plugin({ - props: { - attributes: { - "class": "ProseMirror-example-setup-style" - } - } - })); -} -exports.buildInputRules = buildInputRules; -exports.buildKeymap = buildKeymap; -exports.buildMenuItems = buildMenuItems; -exports.exampleSetup = exampleSetup; diff --git a/plugins/tiddlywiki/prosemirror/files/tiddlywiki.files b/plugins/tiddlywiki/prosemirror/files/tiddlywiki.files index acb522071..98eb820ae 100755 --- a/plugins/tiddlywiki/prosemirror/files/tiddlywiki.files +++ b/plugins/tiddlywiki/prosemirror/files/tiddlywiki.files @@ -35,13 +35,6 @@ "title": "prosemirror-flat-list", "module-type": "library" } - },{ - "file": "prosemirror-schema-list.cjs", - "fields": { - "type": "application/javascript", - "title": "prosemirror-schema-list", - "module-type": "library" - } },{ "file": "prosemirror-flat-list.css", "fields": { @@ -49,13 +42,6 @@ "title": "$:/plugins/tiddlywiki/prosemirror/lib/prosemirror-flat-list.css", "tags": "[[$:/tags/Stylesheet]]" } - },{ - "file": "prosemirror-example-setup.cjs", - "fields": { - "type": "application/javascript", - "title": "prosemirror-example-setup", - "module-type": "library" - } },{ "file": "prosemirror-schema-basic.cjs", "fields": { diff --git a/plugins/tiddlywiki/prosemirror/setup/inputrules.js b/plugins/tiddlywiki/prosemirror/setup/inputrules.js new file mode 100644 index 000000000..ff62bf375 --- /dev/null +++ b/plugins/tiddlywiki/prosemirror/setup/inputrules.js @@ -0,0 +1,44 @@ +/*\ +title: $:/plugins/tiddlywiki/prosemirror/setup/inputrules.js +type: application/javascript +module-type: library + +\*/ + +"use strict"; + +var { inputRules, wrappingInputRule, textblockTypeInputRule, smartQuotes, emDash, ellipsis } = require("prosemirror-inputrules"); +var { NodeType, Schema } = require("prosemirror-model"); + +function blockQuoteRule(nodeType) { + return wrappingInputRule(/^\s*>\s$/, nodeType); +} + +function orderedListRule(nodeType) { + return wrappingInputRule(/^(\d+)\.\s$/, nodeType, function(match) { return { order: +match[1] }; }, + function(match, node) { return node.childCount + node.attrs.order == +match[1]; }); +} + +function bulletListRule(nodeType) { + return wrappingInputRule(/^\s*([-+*])\s$/, nodeType); +} + +function codeBlockRule(nodeType) { + return textblockTypeInputRule(/^```$/, nodeType); +} + +function headingRule(nodeType, maxLevel) { + return textblockTypeInputRule(new RegExp("^(#{1," + maxLevel + "})\\s$"), nodeType, function(match) { return { level: match[1].length }; }); +} + +function buildInputRules(schema) { + var rules = smartQuotes.concat(ellipsis, emDash), type; + if (type = schema.nodes.blockquote) rules.push(blockQuoteRule(type)); + if (type = schema.nodes.ordered_list) rules.push(orderedListRule(type)); + if (type = schema.nodes.bullet_list) rules.push(bulletListRule(type)); + if (type = schema.nodes.code_block) rules.push(codeBlockRule(type)); + if (type = schema.nodes.heading) rules.push(headingRule(type, 6)); + return inputRules({ rules: rules }); +} + +exports.buildInputRules = buildInputRules; diff --git a/plugins/tiddlywiki/prosemirror/setup/keymap.js b/plugins/tiddlywiki/prosemirror/setup/keymap.js new file mode 100644 index 000000000..d5ad980fd --- /dev/null +++ b/plugins/tiddlywiki/prosemirror/setup/keymap.js @@ -0,0 +1,84 @@ +/*\ +title: $:/plugins/tiddlywiki/prosemirror/setup/keymap.js +type: application/javascript +module-type: library + +\*/ + +"use strict"; + +var prosemirrorCommands = require("prosemirror-commands"); +var prosemirrorFlatList = require("prosemirror-flat-list"); +var prosemirrorHistory = require("prosemirror-history"); +var prosemirrorInputrules = require("prosemirror-inputrules"); +var prosemirrorState = require("prosemirror-state"); +var prosemirrorModel = require("prosemirror-model"); + +var mac = typeof navigator != "undefined" ? /Mac|iP(hone|[oa]d)/.test(navigator.platform) : false; + +function buildKeymap(schema, mapKeys) { + var keys = {}, type; + function bind(key, cmd) { + if (mapKeys) { + var mapped = mapKeys[key]; + if (mapped === false) return; + if (mapped) key = mapped; + } + keys[key] = cmd; + } + + bind("Mod-z", prosemirrorHistory.undo); + bind("Shift-Mod-z", prosemirrorHistory.redo); + bind("Backspace", prosemirrorInputrules.undoInputRule); + if (!mac) bind("Mod-y", prosemirrorHistory.redo); + + bind("Alt-ArrowUp", prosemirrorCommands.joinUp); + bind("Alt-ArrowDown", prosemirrorCommands.joinDown); + bind("Mod-BracketLeft", prosemirrorCommands.lift); + bind("Escape", prosemirrorCommands.selectParentNode); + + if (type = schema.marks.strong) { + bind("Mod-b", prosemirrorCommands.toggleMark(type)); + bind("Mod-B", prosemirrorCommands.toggleMark(type)); + } + if (type = schema.marks.em) { + bind("Mod-i", prosemirrorCommands.toggleMark(type)); + bind("Mod-I", prosemirrorCommands.toggleMark(type)); + } + if (type = schema.marks.code) + bind("Mod-`", prosemirrorCommands.toggleMark(type)); + if (type = schema.nodes.blockquote) + bind("Ctrl->", prosemirrorCommands.wrapIn(type)); + if (type = schema.nodes.hard_break) { + var br = type, cmd = prosemirrorCommands.chainCommands(prosemirrorCommands.exitCode, function(state, dispatch) { + if (dispatch) dispatch(state.tr.replaceSelectionWith(br.create()).scrollIntoView()); + return true; + }); + bind("Mod-Enter", cmd); + bind("Shift-Enter", cmd); + if (mac) bind("Ctrl-Enter", cmd); + } + if (type = schema.nodes.list) { + bind("Shift-Tab", prosemirrorFlatList.createDedentListCommand(type)); + bind("Tab", prosemirrorFlatList.createIndentListCommand(type)); + } + if (type = schema.nodes.paragraph) + bind("Shift-Ctrl-0", prosemirrorCommands.setBlockType(type)); + if (type = schema.nodes.code_block) + bind("Shift-Ctrl-\\", prosemirrorCommands.setBlockType(type)); + if (type = schema.nodes.heading) + for (var i = 1; i <= 6; i++) bind("Shift-Ctrl-" + i, prosemirrorCommands.setBlockType(type, {level: i})); + if (type = schema.nodes.horizontal_rule) { + var hr = type; + bind("Mod-_", function(state, dispatch) { + if (dispatch) dispatch(state.tr.replaceSelectionWith(hr.create()).scrollIntoView()); + return true; + }); + } + + return keys; +} + +module.exports = { + buildKeymap: buildKeymap +}; diff --git a/plugins/tiddlywiki/prosemirror/setup/menu.js b/plugins/tiddlywiki/prosemirror/setup/menu.js new file mode 100644 index 000000000..a25c456b7 --- /dev/null +++ b/plugins/tiddlywiki/prosemirror/setup/menu.js @@ -0,0 +1,171 @@ +/*\ +title: $:/plugins/tiddlywiki/prosemirror/setup/menu.js +type: application/javascript +module-type: library + +\*/ + +"use strict"; + +var { + wrapItem, + blockTypeItem, + Dropdown, + DropdownSubmenu, + joinUpItem, + liftItem, + selectParentNodeItem, + undoItem, + redoItem, + icons, + MenuItem, + MenuElement +} = require("prosemirror-menu"); +var { NodeSelection, EditorState } = require("prosemirror-state"); +var { Schema, NodeType, MarkType } = require("prosemirror-model"); +var { toggleMark } = require("prosemirror-commands"); +var { wrapInList } = require("prosemirror-flat-list"); +var { TextField, openPrompt } = require("$:/plugins/tiddlywiki/prosemirror/setup/prompt.js"); + +function canInsert(state, nodeType) { + var $from = state.selection.$from; + for (var d = $from.depth; d >= 0; d--) { + var index = $from.index(d); + if ($from.node(d).canReplaceWith(index, index, nodeType)) return true; + } + return false; +} + +function insertImageItem(nodeType) { + return new MenuItem({ + title: "Insert image", + label: "Image", + enable: function(state) { return canInsert(state, nodeType); }, + run: function(state, _, view) { + var from = state.selection.from, to = state.selection.to, attrs = null; + if (state.selection instanceof NodeSelection && state.selection.node.type == nodeType) + attrs = state.selection.node.attrs; + openPrompt({ + title: "Insert image", + fields: { + src: new TextField({label: "Location", required: true, value: attrs && attrs.src}), + title: new TextField({label: "Title", value: attrs && attrs.title}), + alt: new TextField({label: "Description", value: attrs ? attrs.alt : state.doc.textBetween(from, to, " ")}) + }, + callback: function(attrs) { + view.dispatch(view.state.tr.replaceSelectionWith(nodeType.createAndFill(attrs))); + view.focus(); + } + }); + } + }); +} + +function cmdItem(cmd, options) { + var passedOptions = { + label: options.title, + run: cmd + }; + for (var prop in options) passedOptions[prop] = options[prop]; + if (!options.enable && !options.select) + passedOptions[options.enable ? "enable" : "select"] = function(state) { return cmd(state); }; + + return new MenuItem(passedOptions); +} + +function markActive(state, type) { + var from = state.selection.from, $from = state.selection.$from, to = state.selection.to, empty = state.selection.empty; + if (empty) return !!type.isInSet(state.storedMarks || $from.marks()); + else return state.doc.rangeHasMark(from, to, type); +} + +function markItem(markType, options) { + var passedOptions = { + active: function(state) { return markActive(state, markType); } + }; + for (var prop in options) passedOptions[prop] = options[prop]; + return cmdItem(toggleMark(markType), passedOptions); +} + +function linkItem(markType) { + return new MenuItem({ + title: "Add or remove link", + icon: icons.link, + active: function(state) { return markActive(state, markType); }, + enable: function(state) { return !state.selection.empty; }, + run: function(state, dispatch, view) { + if (markActive(state, markType)) { + toggleMark(markType)(state, dispatch); + return true; + } + openPrompt({ + title: "Create a link", + fields: { + href: new TextField({label: "Link target", required: true}), + title: new TextField({label: "Title"}) + }, + callback: function(attrs) { + toggleMark(markType, attrs)(view.state, view.dispatch); + view.focus(); + } + }); + } + }); +} + +function wrapListItem(nodeType, options) { + return cmdItem(wrapInList(nodeType, options.attrs), options); +} + +function buildMenuItems(schema) { + var r = {}; + var mark; + if (mark = schema.marks.strong) + r.toggleStrong = markItem(mark, {title: "Toggle strong style", icon: icons.strong}); + if (mark = schema.marks.em) + r.toggleEm = markItem(mark, {title: "Toggle emphasis", icon: icons.em}); + if (mark = schema.marks.code) + r.toggleCode = markItem(mark, {title: "Toggle code font", icon: icons.code}); + if (mark = schema.marks.link) + r.toggleLink = linkItem(mark); + + var node; + if (node = schema.nodes.image) + r.insertImage = insertImageItem(node); + if (node = schema.nodes.bullet_list) + r.wrapBulletList = wrapListItem(node, {title: "Wrap in bullet list", icon: icons.bulletList}); + if (node = schema.nodes.ordered_list) + r.wrapOrderedList = wrapListItem(node, {title: "Wrap in ordered list", icon: icons.orderedList}); + if (node = schema.nodes.blockquote) + r.wrapBlockQuote = wrapItem(node, {title: "Wrap in block quote", icon: icons.blockquote}); + if (node = schema.nodes.paragraph) + r.makeParagraph = blockTypeItem(node, {title: "Change to paragraph", label: "Plain"}); + if (node = schema.nodes.code_block) + r.makeCodeBlock = blockTypeItem(node, {title: "Change to code block", label: "Code"}); + if (node = schema.nodes.heading) + for (var i = 1; i <= 10; i++) + r["makeHead" + i] = blockTypeItem(node, {title: "Change to heading " + i, label: "Level " + i, attrs: {level: i}}); + if (node = schema.nodes.horizontal_rule) { + var hr = node; + r.insertHorizontalRule = new MenuItem({ + title: "Insert horizontal rule", + label: "Horizontal rule", + enable: function(state) { return canInsert(state, hr); }, + run: function(state, dispatch) { dispatch(state.tr.replaceSelectionWith(hr.create())); } + }); + } + + var cut = function(arr) { return arr.filter(function(x) { return x; }); }; + r.insertMenu = new Dropdown(cut([r.insertImage, r.insertHorizontalRule]), {label: "Insert"}); + r.typeMenu = new Dropdown(cut([r.makeParagraph, r.makeCodeBlock, r.makeHead1 && new DropdownSubmenu(cut([ + r.makeHead1, r.makeHead2, r.makeHead3, r.makeHead4, r.makeHead5, r.makeHead6 + ]), {label: "Heading"})]), {label: "Type..."}); + + r.inlineMenu = [cut([r.toggleStrong, r.toggleEm, r.toggleCode, r.toggleLink])]; + r.blockMenu = [cut([r.wrapBulletList, r.wrapOrderedList, r.wrapBlockQuote, joinUpItem, liftItem, selectParentNodeItem])]; + r.fullMenu = r.inlineMenu.concat([[r.insertMenu, r.typeMenu]], [[undoItem, redoItem]], r.blockMenu); + + return r; +} + +exports.buildMenuItems = buildMenuItems; diff --git a/plugins/tiddlywiki/prosemirror/setup/prompt.js b/plugins/tiddlywiki/prosemirror/setup/prompt.js new file mode 100644 index 000000000..fb0669a22 --- /dev/null +++ b/plugins/tiddlywiki/prosemirror/setup/prompt.js @@ -0,0 +1,161 @@ +/*\ +title: $:/plugins/tiddlywiki/prosemirror/setup/prompt.js +type: application/javascript +module-type: library + +\*/ + +"use strict"; + +var { Attrs } = require("prosemirror-model"); + +var prefix = "ProseMirror-prompt"; + +function openPrompt(options) { + var wrapper = document.body.appendChild(document.createElement("div")); + wrapper.className = prefix; + + var mouseOutside = function(e) { if (!wrapper.contains(e.target)) close(); }; + setTimeout(function() { window.addEventListener("mousedown", mouseOutside); }, 50); + var close = function() { + window.removeEventListener("mousedown", mouseOutside); + if (wrapper.parentNode) wrapper.parentNode.removeChild(wrapper); + }; + + var domFields = []; + for (var name in options.fields) domFields.push(options.fields[name].render()); + + var submitButton = document.createElement("button"); + submitButton.type = "submit"; + submitButton.className = prefix + "-submit"; + submitButton.textContent = "OK"; + var cancelButton = document.createElement("button"); + cancelButton.type = "button"; + cancelButton.className = prefix + "-cancel"; + cancelButton.textContent = "Cancel"; + cancelButton.addEventListener("click", close); + + var form = wrapper.appendChild(document.createElement("form")); + if (options.title) form.appendChild(document.createElement("h5")).textContent = options.title; + domFields.forEach(function(field) { + form.appendChild(document.createElement("div")).appendChild(field); + }); + var buttons = form.appendChild(document.createElement("div")); + buttons.className = prefix + "-buttons"; + buttons.appendChild(submitButton); + buttons.appendChild(document.createTextNode(" ")); + buttons.appendChild(cancelButton); + + var box = wrapper.getBoundingClientRect(); + wrapper.style.top = ((window.innerHeight - box.height) / 2) + "px"; + wrapper.style.left = ((window.innerWidth - box.width) / 2) + "px"; + + var submit = function() { + var params = getValues(options.fields, domFields); + if (params) { + close(); + options.callback(params); + } + }; + + form.addEventListener("submit", function(e) { + e.preventDefault(); + submit(); + }); + + form.addEventListener("keydown", function(e) { + if (e.keyCode == 27) { + e.preventDefault(); + close(); + } else if (e.keyCode == 13 && !(e.ctrlKey || e.metaKey || e.shiftKey)) { + e.preventDefault(); + submit(); + } else if (e.keyCode == 9) { + window.setTimeout(function() { + if (!wrapper.contains(document.activeElement)) close(); + }, 500); + } + }); + + var input = form.elements[0]; + if (input) input.focus(); +} + +function getValues(fields, domFields) { + var result = Object.create(null), i = 0; + for (var name in fields) { + var field = fields[name], dom = domFields[i++]; + var value = field.read(dom), bad = field.validate(value); + if (bad) { + reportInvalid(dom, bad); + return null; + } + result[name] = field.clean(value); + } + return result; +} + +function reportInvalid(dom, message) { + var parent = dom.parentNode; + var msg = parent.appendChild(document.createElement("div")); + msg.style.left = (dom.offsetLeft + dom.offsetWidth + 2) + "px"; + msg.style.top = (dom.offsetTop - 5) + "px"; + msg.className = "ProseMirror-invalid"; + msg.textContent = message; + setTimeout(function() { parent.removeChild(msg); }, 1500); +} + +function Field(options) { + this.options = options; +} + +Field.prototype.read = function(dom) { return dom.value; }; + +Field.prototype.validateType = function(value) { return null; }; + +Field.prototype.validate = function(value) { + if (!value && this.options.required) + return "Required field"; + return this.validateType(value) || (this.options.validate ? this.options.validate(value) : null); +}; + +Field.prototype.clean = function(value) { + return this.options.clean ? this.options.clean(value) : value; +}; + +function TextField(options) { + Field.call(this, options); +} + +TextField.prototype = Object.create(Field.prototype); + +TextField.prototype.render = function() { + var input = document.createElement("input"); + input.type = "text"; + input.placeholder = this.options.label; + input.value = this.options.value || ""; + input.autocomplete = "off"; + return input; +}; + +function SelectField(options) { + Field.call(this, options); +} + +SelectField.prototype = Object.create(Field.prototype); + +SelectField.prototype.render = function() { + var select = document.createElement("select"); + this.options.options.forEach(function(o) { + var opt = select.appendChild(document.createElement("option")); + opt.value = o.value; + opt.selected = o.value == this.options.value; + opt.label = o.label; + }, this); + return select; +}; + +exports.openPrompt = openPrompt; +exports.Field = Field; +exports.TextField = TextField; +exports.SelectField = SelectField; diff --git a/plugins/tiddlywiki/prosemirror/setup/setup.js b/plugins/tiddlywiki/prosemirror/setup/setup.js new file mode 100644 index 000000000..0affc0fe5 --- /dev/null +++ b/plugins/tiddlywiki/prosemirror/setup/setup.js @@ -0,0 +1,47 @@ +/*\ +title: $:/plugins/tiddlywiki/prosemirror/setup/setup.js +type: application/javascript +module-type: library + +\*/ + +"use strict"; + +var { keymap } = require("prosemirror-keymap"); +var { history } = require("prosemirror-history"); +var { baseKeymap } = require("prosemirror-commands"); +var { Plugin } = require("prosemirror-state"); +var { dropCursor } = require("prosemirror-dropcursor"); +var { gapCursor } = require("prosemirror-gapcursor"); +var { menuBar } = require("prosemirror-menu"); +var { Schema } = require("prosemirror-model"); + +var { buildMenuItems } = require("$:/plugins/tiddlywiki/prosemirror/setup/menu.js"); +var { buildKeymap } = require("$:/plugins/tiddlywiki/prosemirror/setup/keymap.js"); +var { buildInputRules } = require("$:/plugins/tiddlywiki/prosemirror/setup/inputrules.js"); + +exports.buildMenuItems = buildMenuItems; +exports.buildKeymap = buildKeymap; +exports.buildInputRules = buildInputRules; + +function exampleSetup(options) { + var plugins = [ + buildInputRules(options.schema), + keymap(buildKeymap(options.schema, options.mapKeys)), + keymap(baseKeymap), + dropCursor(), + gapCursor() + ]; + if (options.menuBar !== false) + plugins.push(menuBar({ floating: options.floatingMenu !== false, content: options.menuContent || buildMenuItems(options.schema).fullMenu })); + if (options.history !== false) + plugins.push(history()); + + return plugins.concat(new Plugin({ + props: { + attributes: { class: "ProseMirror-example-setup-style" } + } + })); +} + +exports.exampleSetup = exampleSetup; diff --git a/plugins/tiddlywiki/prosemirror/widget.js b/plugins/tiddlywiki/prosemirror/widget.js index 29b85bb76..3a3afd2bd 100644 --- a/plugins/tiddlywiki/prosemirror/widget.js +++ b/plugins/tiddlywiki/prosemirror/widget.js @@ -22,7 +22,7 @@ var { listInputRules, listKeymap } = require("prosemirror-flat-list"); -var { exampleSetup } = require("prosemirror-example-setup"); +var { exampleSetup } = require("$:/plugins/tiddlywiki/prosemirror/setup/setup.js"); var { keymap } = require("prosemirror-keymap"); var { inputRules } = require("prosemirror-inputrules");