gdpr audit implemented, email log, vollmachten, pdf delete cancel data privacy and vollmachten, removed message no id card in engergy car, and other contracts that are not telecom contracts, added insert counter for engery
This commit is contained in:
+8
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"libs": ["browser"],
|
||||
"plugins": {
|
||||
"node": {},
|
||||
"complete_strings": {},
|
||||
"es_modules": {}
|
||||
}
|
||||
}
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
## 1.2.5 (2025-04-22)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Make sure the menu is re-rendered when the editor's root changes, so that it doesn't reference icons whose SVG lives in another root.
|
||||
|
||||
## 1.2.4 (2023-08-20)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Fix a bug where icon creation crashed because it couldn't find a Document value.
|
||||
|
||||
## 1.2.3 (2023-08-16)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Don't directly use the global `window`/`document`, to fix use in a different frame or shadow root.
|
||||
|
||||
## 1.2.2 (2023-05-17)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Include CommonJS type declarations in the package to please new TypeScript resolution settings.
|
||||
|
||||
## 1.2.1 (2022-06-22)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Export CSS file from package.json.
|
||||
|
||||
## 1.2.0 (2022-05-30)
|
||||
|
||||
### New features
|
||||
|
||||
Include TypeScript type declarations.
|
||||
|
||||
## 1.1.4 (2020-03-12)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Restore compatibility with IE11.
|
||||
|
||||
## 1.1.3 (2020-03-04)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Update crel dependency to a version that exposes an ES module.
|
||||
|
||||
## 1.1.2 (2019-12-02)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Downgrade a dependency so that the package can run in IE11 again.
|
||||
|
||||
## 1.1.1 (2019-11-20)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
The file referred to in the package's `module` field now is compiled down to ES5.
|
||||
|
||||
Rename ES module files to use a .js extension, since Webpack gets confused by .mjs
|
||||
|
||||
## 1.1.0 (2019-11-08)
|
||||
|
||||
### New features
|
||||
|
||||
Add a `module` field to package json file.
|
||||
|
||||
## 1.0.5 (2018-07-19)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Fix issue where menu items would still execute their command when clicked even if disabled.
|
||||
|
||||
## 1.0.4 (2018-03-09)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Fixes a bug that prevented the menu bar from properly unregistering its `"scroll"` event handlers when destroyed.
|
||||
|
||||
## 1.0.3 (2018-02-15)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
The floating menu bar now works better in a scrollable parent node.
|
||||
|
||||
## 1.0.2 (2018-01-17)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
Make the `render` property of a menu item spec work as documented again.
|
||||
|
||||
## 1.0.1 (2017-10-18)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
The menu no longer flips enabled/disabled styles on each update in IE11.
|
||||
|
||||
## 1.0.0 (2017-10-13)
|
||||
|
||||
First stable release.
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
# How to contribute
|
||||
|
||||
- [Getting help](#getting-help)
|
||||
- [Submitting bug reports](#submitting-bug-reports)
|
||||
- [Contributing code](#contributing-code)
|
||||
|
||||
## Getting help
|
||||
|
||||
Community discussion, questions, and informal bug reporting is done on the
|
||||
[discuss.ProseMirror forum](http://discuss.prosemirror.net).
|
||||
|
||||
## Submitting bug reports
|
||||
|
||||
Report bugs on the
|
||||
[GitHub issue tracker](http://github.com/prosemirror/prosemirror/issues).
|
||||
Before reporting a bug, please read these pointers.
|
||||
|
||||
- The issue tracker is for *bugs*, not requests for help. Questions
|
||||
should be asked on the [forum](http://discuss.prosemirror.net).
|
||||
|
||||
- Include information about the version of the code that exhibits the
|
||||
problem. For browser-related issues, include the browser and browser
|
||||
version on which the problem occurred.
|
||||
|
||||
- Mention very precisely what went wrong. "X is broken" is not a good
|
||||
bug report. What did you expect to happen? What happened instead?
|
||||
Describe the exact steps a maintainer has to take to make the
|
||||
problem occur. A screencast can be useful, but is no substitute for
|
||||
a textual description.
|
||||
|
||||
- A great way to make it easy to reproduce your problem, if it can not
|
||||
be trivially reproduced on the website demos, is to submit a script
|
||||
that triggers the issue.
|
||||
|
||||
## Contributing code
|
||||
|
||||
- Make sure you have a [GitHub Account](https://github.com/signup/free)
|
||||
|
||||
- Fork the relevant repository
|
||||
([how to fork a repo](https://help.github.com/articles/fork-a-repo))
|
||||
|
||||
- Create a local checkout of the code. You can use the
|
||||
[main repository](https://github.com/prosemirror/prosemirror) to
|
||||
easily check out all core modules.
|
||||
|
||||
- Make your changes, and commit them
|
||||
|
||||
- Follow the code style of the rest of the project (see below). Run
|
||||
`npm run lint` (in the main repository checkout) to make sure that
|
||||
the linter is happy.
|
||||
|
||||
- If your changes are easy to test or likely to regress, add tests in
|
||||
the relevant `test/` directory. Either put them in an existing
|
||||
`test-*.js` file, if they fit there, or add a new file.
|
||||
|
||||
- Make sure all tests pass. Run `npm run test` to verify tests pass
|
||||
(you will need Node.js v6+).
|
||||
|
||||
- Submit a pull request ([how to create a pull request](https://help.github.com/articles/fork-a-repo)).
|
||||
Don't put more than one feature/fix in a single pull request.
|
||||
|
||||
By contributing code to ProseMirror you
|
||||
|
||||
- Agree to license the contributed code under the project's [MIT
|
||||
license](https://github.com/ProseMirror/prosemirror/blob/master/LICENSE).
|
||||
|
||||
- Confirm that you have the right to contribute and license the code
|
||||
in question. (Either you hold all rights on the code, or the rights
|
||||
holder has explicitly granted the right to use it like this,
|
||||
through a compatible open source license or through a direct
|
||||
agreement with you.)
|
||||
|
||||
### Coding standards
|
||||
|
||||
- ES6 syntax, targeting an ES5 runtime (i.e. don't use library
|
||||
elements added by ES6, don't use ES7/ES.next syntax).
|
||||
|
||||
- 2 spaces per indentation level, no tabs.
|
||||
|
||||
- No semicolons except when necessary.
|
||||
|
||||
- Follow the surrounding code when it comes to spacing, brace
|
||||
placement, etc.
|
||||
|
||||
- Brace-less single-statement bodies are encouraged (whenever they
|
||||
don't impact readability).
|
||||
|
||||
- [getdocs](https://github.com/marijnh/getdocs)-style doc comments
|
||||
above items that are part of the public API.
|
||||
|
||||
- When documenting non-public items, you can put the type after a
|
||||
single colon, so that getdocs doesn't pick it up and add it to the
|
||||
API reference.
|
||||
|
||||
- The linter (`npm run lint`) complains about unused variables and
|
||||
functions. Prefix their names with an underscore to muffle it.
|
||||
|
||||
- ProseMirror does *not* follow JSHint or JSLint prescribed style.
|
||||
Patches that try to 'fix' code to pass one of these linters will not
|
||||
be accepted.
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
Copyright (C) 2015-2017 by Marijn Haverbeke <marijn@haverbeke.berlin> and others
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
+195
@@ -0,0 +1,195 @@
|
||||
# prosemirror-menu
|
||||
|
||||
[ [**WEBSITE**](https://prosemirror.net) | [**ISSUES**](https://github.com/prosemirror/prosemirror-menu/issues) | [**FORUM**](https://discuss.prosemirror.net) | [**GITTER**](https://gitter.im/ProseMirror/prosemirror) ]
|
||||
|
||||
This is a non-core example module for [ProseMirror](https://prosemirror.net).
|
||||
ProseMirror is a well-behaved rich semantic content editor based on
|
||||
contentEditable, with support for collaborative editing and custom
|
||||
document schemas.
|
||||
|
||||
This module defines an abstraction for building a menu for the
|
||||
ProseMirror editor, along with an implementation of a menubar.
|
||||
|
||||
**Note** that this module exists mostly as an example of how you
|
||||
_might_ want to approach adding a menu to ProseMirror, but is not
|
||||
maintained as actively as the core modules related to actual editing.
|
||||
If you want to extend or improve it, the recommended way is to fork
|
||||
it. If you are interested in maintaining a serious menu component for
|
||||
ProseMirror, publish your fork, and if it works for me, I'll gladly
|
||||
deprecate this in favor of your module.
|
||||
|
||||
This code is released under an
|
||||
[MIT license](https://github.com/prosemirror/prosemirror/tree/master/LICENSE).
|
||||
There's a [forum](http://discuss.prosemirror.net) for general
|
||||
discussion and support requests, and the
|
||||
[Github bug tracker](https://github.com/prosemirror/prosemirror-menu/issues)
|
||||
is the place to report issues.
|
||||
|
||||
## Documentation
|
||||
|
||||
This module defines a number of building blocks for ProseMirror menus,
|
||||
along with a [menu bar](#menu.menuBar) implementation.
|
||||
|
||||
When using this module, you should make sure its
|
||||
[`style/menu.css`](https://github.com/ProseMirror/prosemirror-menu/blob/master/style/menu.css)
|
||||
file is loaded into your page.
|
||||
|
||||
### interface MenuElement
|
||||
|
||||
The types defined in this module aren't the only thing you can
|
||||
display in your menu. Anything that conforms to this interface can
|
||||
be put into a menu structure.
|
||||
|
||||
* **`render`**`(pm: EditorView) → {dom: HTMLElement, update: fn(state: EditorState) → boolean}`\
|
||||
Render the element for display in the menu. Must return a DOM
|
||||
element and a function that can be used to update the element to
|
||||
a new state. The `update` function must return false if the
|
||||
update hid the entire element.
|
||||
|
||||
### class MenuItem
|
||||
|
||||
implements `MenuElement`An icon or label that, when clicked, executes a command.
|
||||
|
||||
* `new `**`MenuItem`**`(spec: MenuItemSpec)`\
|
||||
Create a menu item.
|
||||
|
||||
* **`spec`**`: MenuItemSpec`\
|
||||
The spec used to create this item.
|
||||
|
||||
* **`render`**`(view: EditorView) → {dom: HTMLElement, update: fn(state: EditorState) → boolean}`\
|
||||
Renders the icon according to its [display
|
||||
spec](#menu.MenuItemSpec.display), and adds an event handler which
|
||||
executes the command when the representation is clicked.
|
||||
|
||||
### interface MenuItemSpec
|
||||
|
||||
The configuration object passed to the `MenuItem` constructor.
|
||||
|
||||
* **`run`**`(state: EditorState, dispatch: fn(tr: Transaction), view: EditorView, event: Event)`\
|
||||
The function to execute when the menu item is activated.
|
||||
|
||||
* **`select`**`: ?fn(state: EditorState) → boolean`\
|
||||
Optional function that is used to determine whether the item is
|
||||
appropriate at the moment. Deselected items will be hidden.
|
||||
|
||||
* **`enable`**`: ?fn(state: EditorState) → boolean`\
|
||||
Function that is used to determine if the item is enabled. If
|
||||
given and returning false, the item will be given a disabled
|
||||
styling.
|
||||
|
||||
* **`active`**`: ?fn(state: EditorState) → boolean`\
|
||||
A predicate function to determine whether the item is 'active' (for
|
||||
example, the item for toggling the strong mark might be active then
|
||||
the cursor is in strong text).
|
||||
|
||||
* **`render`**`: ?fn(view: EditorView) → HTMLElement`\
|
||||
A function that renders the item. You must provide either this,
|
||||
[`icon`](#menu.MenuItemSpec.icon), or [`label`](#MenuItemSpec.label).
|
||||
|
||||
* **`icon`**`: ?IconSpec`\
|
||||
Describes an icon to show for this item.
|
||||
|
||||
* **`label`**`: ?string`\
|
||||
Makes the item show up as a text label. Mostly useful for items
|
||||
wrapped in a [drop-down](#menu.Dropdown) or similar menu. The object
|
||||
should have a `label` property providing the text to display.
|
||||
|
||||
* **`title`**`: ?string | fn(state: EditorState) → string`\
|
||||
Defines DOM title (mouseover) text for the item.
|
||||
|
||||
* **`class`**`: ?string`\
|
||||
Optionally adds a CSS class to the item's DOM representation.
|
||||
|
||||
* **`css`**`: ?string`\
|
||||
Optionally adds a string of inline CSS to the item's DOM
|
||||
representation.
|
||||
|
||||
* type **`IconSpec`**
|
||||
` = {path: string, width: number, height: number} | {text: string, css?: ?string} | {dom: Node}`\
|
||||
Specifies an icon. May be either an SVG icon, in which case its
|
||||
`path` property should be an [SVG path
|
||||
spec](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d),
|
||||
and `width` and `height` should provide the viewbox in which that
|
||||
path exists. Alternatively, it may have a `text` property
|
||||
specifying a string of text that makes up the icon, with an
|
||||
optional `css` property giving additional CSS styling for the
|
||||
text. _Or_ it may contain `dom` property containing a DOM node.
|
||||
|
||||
### class Dropdown
|
||||
|
||||
implements `MenuElement`A drop-down menu, displayed as a label with a downwards-pointing
|
||||
triangle to the right of it.
|
||||
|
||||
* `new `**`Dropdown`**`(content: readonly MenuElement[] | MenuElement, options: ?Object = {})`\
|
||||
Create a dropdown wrapping the elements.
|
||||
|
||||
* **`render`**`(view: EditorView) → {dom: HTMLElement, update: fn(state: EditorState) → boolean}`\
|
||||
Render the dropdown menu and sub-items.
|
||||
|
||||
### class DropdownSubmenu
|
||||
|
||||
implements `MenuElement`Represents a submenu wrapping a group of elements that start
|
||||
hidden and expand to the right when hovered over or tapped.
|
||||
|
||||
* `new `**`DropdownSubmenu`**`(content: readonly MenuElement[] | MenuElement, options: ?Object = {})`\
|
||||
Creates a submenu for the given group of menu elements. The
|
||||
following options are recognized:
|
||||
|
||||
* **`render`**`(view: EditorView) → {dom: HTMLElement, update: fn(state: EditorState) → boolean}`\
|
||||
Renders the submenu.
|
||||
|
||||
* **`menuBar`**`(options: Object) → Plugin`\
|
||||
A plugin that will place a menu bar above the editor. Note that
|
||||
this involves wrapping the editor in an additional `<div>`.
|
||||
|
||||
|
||||
This module exports the following pre-built items or item
|
||||
constructors:
|
||||
|
||||
* **`joinUpItem`**`: MenuItem`\
|
||||
Menu item for the `joinUp` command.
|
||||
|
||||
* **`liftItem`**`: MenuItem`\
|
||||
Menu item for the `lift` command.
|
||||
|
||||
* **`selectParentNodeItem`**`: MenuItem`\
|
||||
Menu item for the `selectParentNode` command.
|
||||
|
||||
* **`undoItem`**`: MenuItem`\
|
||||
Menu item for the `undo` command.
|
||||
|
||||
* **`redoItem`**`: MenuItem`\
|
||||
Menu item for the `redo` command.
|
||||
|
||||
* **`wrapItem`**`(nodeType: NodeType, options: Partial & {attrs?: ?Attrs}) → MenuItem`\
|
||||
Build a menu item for wrapping the selection in a given node type.
|
||||
Adds `run` and `select` properties to the ones present in
|
||||
`options`. `options.attrs` may be an object that provides
|
||||
attributes for the wrapping node.
|
||||
|
||||
* **`blockTypeItem`**`(nodeType: NodeType, options: Partial & {attrs?: ?Attrs}) → MenuItem`\
|
||||
Build a menu item for changing the type of the textblock around the
|
||||
selection to the given type. Provides `run`, `active`, and `select`
|
||||
properties. Others must be given in `options`. `options.attrs` may
|
||||
be an object to provide the attributes for the textblock node.
|
||||
|
||||
|
||||
To construct your own items, these icons may be useful:
|
||||
|
||||
* **`icons`**`: Object`\
|
||||
A set of basic editor-related icons. Contains the properties
|
||||
`join`, `lift`, `selectParentNode`, `undo`, `redo`, `strong`, `em`,
|
||||
`code`, `link`, `bulletList`, `orderedList`, and `blockquote`, each
|
||||
holding an object that can be used as the `icon` option to
|
||||
`MenuItem`.
|
||||
|
||||
|
||||
* **`renderGrouped`**`(view: EditorView, content: readonly readonly MenuElement[][]) → {`\
|
||||
` dom: DocumentFragment,`\
|
||||
` update: fn(state: EditorState) → boolean`\
|
||||
`}`\
|
||||
Render the given, possibly nested, array of menu elements into a
|
||||
document fragment, placing separators between them (and ensuring no
|
||||
superfluous separators appear when some of the groups turn out to
|
||||
be empty).
|
||||
|
||||
+619
@@ -0,0 +1,619 @@
|
||||
'use strict';
|
||||
|
||||
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); }
|
||||
function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
|
||||
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
|
||||
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
|
||||
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
|
||||
function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
|
||||
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
|
||||
var crel = require('crelt');
|
||||
var prosemirrorCommands = require('prosemirror-commands');
|
||||
var prosemirrorHistory = require('prosemirror-history');
|
||||
var prosemirrorState = require('prosemirror-state');
|
||||
var SVG = "http://www.w3.org/2000/svg";
|
||||
var XLINK = "http://www.w3.org/1999/xlink";
|
||||
var prefix$2 = "ProseMirror-icon";
|
||||
function hashPath(path) {
|
||||
var hash = 0;
|
||||
for (var i = 0; i < path.length; i++) hash = (hash << 5) - hash + path.charCodeAt(i) | 0;
|
||||
return hash;
|
||||
}
|
||||
function getIcon(root, icon) {
|
||||
var doc = (root.nodeType == 9 ? root : root.ownerDocument) || document;
|
||||
var node = doc.createElement("div");
|
||||
node.className = prefix$2;
|
||||
if (icon.path) {
|
||||
var path = icon.path,
|
||||
width = icon.width,
|
||||
height = icon.height;
|
||||
var name = "pm-icon-" + hashPath(path).toString(16);
|
||||
if (!doc.getElementById(name)) buildSVG(root, name, icon);
|
||||
var svg = node.appendChild(doc.createElementNS(SVG, "svg"));
|
||||
svg.style.width = width / height + "em";
|
||||
var use = svg.appendChild(doc.createElementNS(SVG, "use"));
|
||||
use.setAttributeNS(XLINK, "href", /([^#]*)/.exec(doc.location.toString())[1] + "#" + name);
|
||||
} else if (icon.dom) {
|
||||
node.appendChild(icon.dom.cloneNode(true));
|
||||
} else {
|
||||
var text = icon.text,
|
||||
css = icon.css;
|
||||
node.appendChild(doc.createElement("span")).textContent = text || '';
|
||||
if (css) node.firstChild.style.cssText = css;
|
||||
}
|
||||
return node;
|
||||
}
|
||||
function buildSVG(root, name, data) {
|
||||
var _ref = root.nodeType == 9 ? [root, root.body] : [root.ownerDocument || document, root],
|
||||
_ref2 = _slicedToArray(_ref, 2),
|
||||
doc = _ref2[0],
|
||||
top = _ref2[1];
|
||||
var collection = doc.getElementById(prefix$2 + "-collection");
|
||||
if (!collection) {
|
||||
collection = doc.createElementNS(SVG, "svg");
|
||||
collection.id = prefix$2 + "-collection";
|
||||
collection.style.display = "none";
|
||||
top.insertBefore(collection, top.firstChild);
|
||||
}
|
||||
var sym = doc.createElementNS(SVG, "symbol");
|
||||
sym.id = name;
|
||||
sym.setAttribute("viewBox", "0 0 " + data.width + " " + data.height);
|
||||
var path = sym.appendChild(doc.createElementNS(SVG, "path"));
|
||||
path.setAttribute("d", data.path);
|
||||
collection.appendChild(sym);
|
||||
}
|
||||
var prefix$1 = "ProseMirror-menu";
|
||||
var MenuItem = function () {
|
||||
function MenuItem(spec) {
|
||||
_classCallCheck(this, MenuItem);
|
||||
this.spec = spec;
|
||||
}
|
||||
_createClass(MenuItem, [{
|
||||
key: "render",
|
||||
value: function render(view) {
|
||||
var spec = this.spec;
|
||||
var dom = spec.render ? spec.render(view) : spec.icon ? getIcon(view.root, spec.icon) : spec.label ? crel("div", null, translate(view, spec.label)) : null;
|
||||
if (!dom) throw new RangeError("MenuItem without icon or label property");
|
||||
if (spec.title) {
|
||||
var title = typeof spec.title === "function" ? spec.title(view.state) : spec.title;
|
||||
dom.setAttribute("title", translate(view, title));
|
||||
}
|
||||
if (spec["class"]) dom.classList.add(spec["class"]);
|
||||
if (spec.css) dom.style.cssText += spec.css;
|
||||
dom.addEventListener("mousedown", function (e) {
|
||||
e.preventDefault();
|
||||
if (!dom.classList.contains(prefix$1 + "-disabled")) spec.run(view.state, view.dispatch, view, e);
|
||||
});
|
||||
function update(state) {
|
||||
if (spec.select) {
|
||||
var selected = spec.select(state);
|
||||
dom.style.display = selected ? "" : "none";
|
||||
if (!selected) return false;
|
||||
}
|
||||
var enabled = true;
|
||||
if (spec.enable) {
|
||||
enabled = spec.enable(state) || false;
|
||||
setClass(dom, prefix$1 + "-disabled", !enabled);
|
||||
}
|
||||
if (spec.active) {
|
||||
var active = enabled && spec.active(state) || false;
|
||||
setClass(dom, prefix$1 + "-active", active);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return {
|
||||
dom: dom,
|
||||
update: update
|
||||
};
|
||||
}
|
||||
}]);
|
||||
return MenuItem;
|
||||
}();
|
||||
function translate(view, text) {
|
||||
return view._props.translate ? view._props.translate(text) : text;
|
||||
}
|
||||
var lastMenuEvent = {
|
||||
time: 0,
|
||||
node: null
|
||||
};
|
||||
function markMenuEvent(e) {
|
||||
lastMenuEvent.time = Date.now();
|
||||
lastMenuEvent.node = e.target;
|
||||
}
|
||||
function isMenuEvent(wrapper) {
|
||||
return Date.now() - 100 < lastMenuEvent.time && lastMenuEvent.node && wrapper.contains(lastMenuEvent.node);
|
||||
}
|
||||
var Dropdown = function () {
|
||||
function Dropdown(content) {
|
||||
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
||||
_classCallCheck(this, Dropdown);
|
||||
this.options = options;
|
||||
this.options = options || {};
|
||||
this.content = Array.isArray(content) ? content : [content];
|
||||
}
|
||||
_createClass(Dropdown, [{
|
||||
key: "render",
|
||||
value: function render(view) {
|
||||
var _this = this;
|
||||
var content = renderDropdownItems(this.content, view);
|
||||
var win = view.dom.ownerDocument.defaultView || window;
|
||||
var label = crel("div", {
|
||||
"class": prefix$1 + "-dropdown " + (this.options["class"] || ""),
|
||||
style: this.options.css
|
||||
}, translate(view, this.options.label || ""));
|
||||
if (this.options.title) label.setAttribute("title", translate(view, this.options.title));
|
||||
var wrap = crel("div", {
|
||||
"class": prefix$1 + "-dropdown-wrap"
|
||||
}, label);
|
||||
var open = null;
|
||||
var listeningOnClose = null;
|
||||
var close = function close() {
|
||||
if (open && open.close()) {
|
||||
open = null;
|
||||
win.removeEventListener("mousedown", listeningOnClose);
|
||||
}
|
||||
};
|
||||
label.addEventListener("mousedown", function (e) {
|
||||
e.preventDefault();
|
||||
markMenuEvent(e);
|
||||
if (open) {
|
||||
close();
|
||||
} else {
|
||||
open = _this.expand(wrap, content.dom);
|
||||
win.addEventListener("mousedown", listeningOnClose = function listeningOnClose() {
|
||||
if (!isMenuEvent(wrap)) close();
|
||||
});
|
||||
}
|
||||
});
|
||||
function update(state) {
|
||||
var inner = content.update(state);
|
||||
wrap.style.display = inner ? "" : "none";
|
||||
return inner;
|
||||
}
|
||||
return {
|
||||
dom: wrap,
|
||||
update: update
|
||||
};
|
||||
}
|
||||
}, {
|
||||
key: "expand",
|
||||
value: function expand(dom, items) {
|
||||
var menuDOM = crel("div", {
|
||||
"class": prefix$1 + "-dropdown-menu " + (this.options["class"] || "")
|
||||
}, items);
|
||||
var done = false;
|
||||
function close() {
|
||||
if (done) return false;
|
||||
done = true;
|
||||
dom.removeChild(menuDOM);
|
||||
return true;
|
||||
}
|
||||
dom.appendChild(menuDOM);
|
||||
return {
|
||||
close: close,
|
||||
node: menuDOM
|
||||
};
|
||||
}
|
||||
}]);
|
||||
return Dropdown;
|
||||
}();
|
||||
function renderDropdownItems(items, view) {
|
||||
var rendered = [],
|
||||
updates = [];
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
var _items$i$render = items[i].render(view),
|
||||
dom = _items$i$render.dom,
|
||||
update = _items$i$render.update;
|
||||
rendered.push(crel("div", {
|
||||
"class": prefix$1 + "-dropdown-item"
|
||||
}, dom));
|
||||
updates.push(update);
|
||||
}
|
||||
return {
|
||||
dom: rendered,
|
||||
update: combineUpdates(updates, rendered)
|
||||
};
|
||||
}
|
||||
function combineUpdates(updates, nodes) {
|
||||
return function (state) {
|
||||
var something = false;
|
||||
for (var i = 0; i < updates.length; i++) {
|
||||
var up = updates[i](state);
|
||||
nodes[i].style.display = up ? "" : "none";
|
||||
if (up) something = true;
|
||||
}
|
||||
return something;
|
||||
};
|
||||
}
|
||||
var DropdownSubmenu = function () {
|
||||
function DropdownSubmenu(content) {
|
||||
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
||||
_classCallCheck(this, DropdownSubmenu);
|
||||
this.options = options;
|
||||
this.content = Array.isArray(content) ? content : [content];
|
||||
}
|
||||
_createClass(DropdownSubmenu, [{
|
||||
key: "render",
|
||||
value: function render(view) {
|
||||
var items = renderDropdownItems(this.content, view);
|
||||
var win = view.dom.ownerDocument.defaultView || window;
|
||||
var label = crel("div", {
|
||||
"class": prefix$1 + "-submenu-label"
|
||||
}, translate(view, this.options.label || ""));
|
||||
var wrap = crel("div", {
|
||||
"class": prefix$1 + "-submenu-wrap"
|
||||
}, label, crel("div", {
|
||||
"class": prefix$1 + "-submenu"
|
||||
}, items.dom));
|
||||
var _listeningOnClose = null;
|
||||
label.addEventListener("mousedown", function (e) {
|
||||
e.preventDefault();
|
||||
markMenuEvent(e);
|
||||
setClass(wrap, prefix$1 + "-submenu-wrap-active", false);
|
||||
if (!_listeningOnClose) win.addEventListener("mousedown", _listeningOnClose = function listeningOnClose() {
|
||||
if (!isMenuEvent(wrap)) {
|
||||
wrap.classList.remove(prefix$1 + "-submenu-wrap-active");
|
||||
win.removeEventListener("mousedown", _listeningOnClose);
|
||||
_listeningOnClose = null;
|
||||
}
|
||||
});
|
||||
});
|
||||
function update(state) {
|
||||
var inner = items.update(state);
|
||||
wrap.style.display = inner ? "" : "none";
|
||||
return inner;
|
||||
}
|
||||
return {
|
||||
dom: wrap,
|
||||
update: update
|
||||
};
|
||||
}
|
||||
}]);
|
||||
return DropdownSubmenu;
|
||||
}();
|
||||
function renderGrouped(view, content) {
|
||||
var result = document.createDocumentFragment();
|
||||
var updates = [],
|
||||
separators = [];
|
||||
for (var i = 0; i < content.length; i++) {
|
||||
var items = content[i],
|
||||
localUpdates = [],
|
||||
localNodes = [];
|
||||
for (var j = 0; j < items.length; j++) {
|
||||
var _items$j$render = items[j].render(view),
|
||||
dom = _items$j$render.dom,
|
||||
_update = _items$j$render.update;
|
||||
var span = crel("span", {
|
||||
"class": prefix$1 + "item"
|
||||
}, dom);
|
||||
result.appendChild(span);
|
||||
localNodes.push(span);
|
||||
localUpdates.push(_update);
|
||||
}
|
||||
if (localUpdates.length) {
|
||||
updates.push(combineUpdates(localUpdates, localNodes));
|
||||
if (i < content.length - 1) separators.push(result.appendChild(separator()));
|
||||
}
|
||||
}
|
||||
function update(state) {
|
||||
var something = false,
|
||||
needSep = false;
|
||||
for (var _i = 0; _i < updates.length; _i++) {
|
||||
var hasContent = updates[_i](state);
|
||||
if (_i) separators[_i - 1].style.display = needSep && hasContent ? "" : "none";
|
||||
needSep = hasContent;
|
||||
if (hasContent) something = true;
|
||||
}
|
||||
return something;
|
||||
}
|
||||
return {
|
||||
dom: result,
|
||||
update: update
|
||||
};
|
||||
}
|
||||
function separator() {
|
||||
return crel("span", {
|
||||
"class": prefix$1 + "separator"
|
||||
});
|
||||
}
|
||||
var icons = {
|
||||
join: {
|
||||
width: 800,
|
||||
height: 900,
|
||||
path: "M0 75h800v125h-800z M0 825h800v-125h-800z M250 400h100v-100h100v100h100v100h-100v100h-100v-100h-100z"
|
||||
},
|
||||
lift: {
|
||||
width: 1024,
|
||||
height: 1024,
|
||||
path: "M219 310v329q0 7-5 12t-12 5q-8 0-13-5l-164-164q-5-5-5-13t5-13l164-164q5-5 13-5 7 0 12 5t5 12zM1024 749v109q0 7-5 12t-12 5h-987q-7 0-12-5t-5-12v-109q0-7 5-12t12-5h987q7 0 12 5t5 12zM1024 530v109q0 7-5 12t-12 5h-621q-7 0-12-5t-5-12v-109q0-7 5-12t12-5h621q7 0 12 5t5 12zM1024 310v109q0 7-5 12t-12 5h-621q-7 0-12-5t-5-12v-109q0-7 5-12t12-5h621q7 0 12 5t5 12zM1024 91v109q0 7-5 12t-12 5h-987q-7 0-12-5t-5-12v-109q0-7 5-12t12-5h987q7 0 12 5t5 12z"
|
||||
},
|
||||
selectParentNode: {
|
||||
text: "\u2B1A",
|
||||
css: "font-weight: bold"
|
||||
},
|
||||
undo: {
|
||||
width: 1024,
|
||||
height: 1024,
|
||||
path: "M761 1024c113-206 132-520-313-509v253l-384-384 384-384v248c534-13 594 472 313 775z"
|
||||
},
|
||||
redo: {
|
||||
width: 1024,
|
||||
height: 1024,
|
||||
path: "M576 248v-248l384 384-384 384v-253c-446-10-427 303-313 509-280-303-221-789 313-775z"
|
||||
},
|
||||
strong: {
|
||||
width: 805,
|
||||
height: 1024,
|
||||
path: "M317 869q42 18 80 18 214 0 214-191 0-65-23-102-15-25-35-42t-38-26-46-14-48-6-54-1q-41 0-57 5 0 30-0 90t-0 90q0 4-0 38t-0 55 2 47 6 38zM309 442q24 4 62 4 46 0 81-7t62-25 42-51 14-81q0-40-16-70t-45-46-61-24-70-8q-28 0-74 7 0 28 2 86t2 86q0 15-0 45t-0 45q0 26 0 39zM0 950l1-53q8-2 48-9t60-15q4-6 7-15t4-19 3-18 1-21 0-19v-37q0-561-12-585-2-4-12-8t-25-6-28-4-27-2-17-1l-2-47q56-1 194-6t213-5q13 0 39 0t38 0q40 0 78 7t73 24 61 40 42 59 16 78q0 29-9 54t-22 41-36 32-41 25-48 22q88 20 146 76t58 141q0 57-20 102t-53 74-78 48-93 27-100 8q-25 0-75-1t-75-1q-60 0-175 6t-132 6z"
|
||||
},
|
||||
em: {
|
||||
width: 585,
|
||||
height: 1024,
|
||||
path: "M0 949l9-48q3-1 46-12t63-21q16-20 23-57 0-4 35-165t65-310 29-169v-14q-13-7-31-10t-39-4-33-3l10-58q18 1 68 3t85 4 68 1q27 0 56-1t69-4 56-3q-2 22-10 50-17 5-58 16t-62 19q-4 10-8 24t-5 22-4 26-3 24q-15 84-50 239t-44 203q-1 5-7 33t-11 51-9 47-3 32l0 10q9 2 105 17-1 25-9 56-6 0-18 0t-18 0q-16 0-49-5t-49-5q-78-1-117-1-29 0-81 5t-69 6z"
|
||||
},
|
||||
code: {
|
||||
width: 896,
|
||||
height: 1024,
|
||||
path: "M608 192l-96 96 224 224-224 224 96 96 288-320-288-320zM288 192l-288 320 288 320 96-96-224-224 224-224-96-96z"
|
||||
},
|
||||
link: {
|
||||
width: 951,
|
||||
height: 1024,
|
||||
path: "M832 694q0-22-16-38l-118-118q-16-16-38-16-24 0-41 18 1 1 10 10t12 12 8 10 7 14 2 15q0 22-16 38t-38 16q-8 0-15-2t-14-7-10-8-12-12-10-10q-18 17-18 41 0 22 16 38l117 118q15 15 38 15 22 0 38-14l84-83q16-16 16-38zM430 292q0-22-16-38l-117-118q-16-16-38-16-22 0-38 15l-84 83q-16 16-16 38 0 22 16 38l118 118q15 15 38 15 24 0 41-17-1-1-10-10t-12-12-8-10-7-14-2-15q0-22 16-38t38-16q8 0 15 2t14 7 10 8 12 12 10 10q18-17 18-41zM941 694q0 68-48 116l-84 83q-47 47-116 47-69 0-116-48l-117-118q-47-47-47-116 0-70 50-119l-50-50q-49 50-118 50-68 0-116-48l-118-118q-48-48-48-116t48-116l84-83q47-47 116-47 69 0 116 48l117 118q47 47 47 116 0 70-50 119l50 50q49-50 118-50 68 0 116 48l118 118q48 48 48 116z"
|
||||
},
|
||||
bulletList: {
|
||||
width: 768,
|
||||
height: 896,
|
||||
path: "M0 512h128v-128h-128v128zM0 256h128v-128h-128v128zM0 768h128v-128h-128v128zM256 512h512v-128h-512v128zM256 256h512v-128h-512v128zM256 768h512v-128h-512v128z"
|
||||
},
|
||||
orderedList: {
|
||||
width: 768,
|
||||
height: 896,
|
||||
path: "M320 512h448v-128h-448v128zM320 768h448v-128h-448v128zM320 128v128h448v-128h-448zM79 384h78v-256h-36l-85 23v50l43-2v185zM189 590c0-36-12-78-96-78-33 0-64 6-83 16l1 66c21-10 42-15 67-15s32 11 32 28c0 26-30 58-110 112v50h192v-67l-91 2c49-30 87-66 87-113l1-1z"
|
||||
},
|
||||
blockquote: {
|
||||
width: 640,
|
||||
height: 896,
|
||||
path: "M0 448v256h256v-256h-128c0 0 0-128 128-128v-128c0 0-256 0-256 256zM640 320v-128c0 0-256 0-256 256v256h256v-256h-128c0 0 0-128 128-128z"
|
||||
}
|
||||
};
|
||||
var joinUpItem = new MenuItem({
|
||||
title: "Join with above block",
|
||||
run: prosemirrorCommands.joinUp,
|
||||
select: function select(state) {
|
||||
return prosemirrorCommands.joinUp(state);
|
||||
},
|
||||
icon: icons.join
|
||||
});
|
||||
var liftItem = new MenuItem({
|
||||
title: "Lift out of enclosing block",
|
||||
run: prosemirrorCommands.lift,
|
||||
select: function select(state) {
|
||||
return prosemirrorCommands.lift(state);
|
||||
},
|
||||
icon: icons.lift
|
||||
});
|
||||
var selectParentNodeItem = new MenuItem({
|
||||
title: "Select parent node",
|
||||
run: prosemirrorCommands.selectParentNode,
|
||||
select: function select(state) {
|
||||
return prosemirrorCommands.selectParentNode(state);
|
||||
},
|
||||
icon: icons.selectParentNode
|
||||
});
|
||||
var undoItem = new MenuItem({
|
||||
title: "Undo last change",
|
||||
run: prosemirrorHistory.undo,
|
||||
enable: function enable(state) {
|
||||
return prosemirrorHistory.undo(state);
|
||||
},
|
||||
icon: icons.undo
|
||||
});
|
||||
var redoItem = new MenuItem({
|
||||
title: "Redo last undone change",
|
||||
run: prosemirrorHistory.redo,
|
||||
enable: function enable(state) {
|
||||
return prosemirrorHistory.redo(state);
|
||||
},
|
||||
icon: icons.redo
|
||||
});
|
||||
function wrapItem(nodeType, options) {
|
||||
var passedOptions = {
|
||||
run: function run(state, dispatch) {
|
||||
return prosemirrorCommands.wrapIn(nodeType, options.attrs)(state, dispatch);
|
||||
},
|
||||
select: function select(state) {
|
||||
return prosemirrorCommands.wrapIn(nodeType, options.attrs)(state);
|
||||
}
|
||||
};
|
||||
for (var prop in options) passedOptions[prop] = options[prop];
|
||||
return new MenuItem(passedOptions);
|
||||
}
|
||||
function blockTypeItem(nodeType, options) {
|
||||
var command = prosemirrorCommands.setBlockType(nodeType, options.attrs);
|
||||
var passedOptions = {
|
||||
run: command,
|
||||
enable: function enable(state) {
|
||||
return command(state);
|
||||
},
|
||||
active: function active(state) {
|
||||
var _state$selection = state.selection,
|
||||
$from = _state$selection.$from,
|
||||
to = _state$selection.to,
|
||||
node = _state$selection.node;
|
||||
if (node) return node.hasMarkup(nodeType, options.attrs);
|
||||
return to <= $from.end() && $from.parent.hasMarkup(nodeType, options.attrs);
|
||||
}
|
||||
};
|
||||
for (var prop in options) passedOptions[prop] = options[prop];
|
||||
return new MenuItem(passedOptions);
|
||||
}
|
||||
function setClass(dom, cls, on) {
|
||||
if (on) dom.classList.add(cls);else dom.classList.remove(cls);
|
||||
}
|
||||
var prefix = "ProseMirror-menubar";
|
||||
function isIOS() {
|
||||
if (typeof navigator == "undefined") return false;
|
||||
var agent = navigator.userAgent;
|
||||
return !/Edge\/\d/.test(agent) && /AppleWebKit/.test(agent) && /Mobile\/\w+/.test(agent);
|
||||
}
|
||||
function menuBar(options) {
|
||||
return new prosemirrorState.Plugin({
|
||||
view: function view(editorView) {
|
||||
return new MenuBarView(editorView, options);
|
||||
}
|
||||
});
|
||||
}
|
||||
var MenuBarView = function () {
|
||||
function MenuBarView(editorView, options) {
|
||||
var _this2 = this;
|
||||
_classCallCheck(this, MenuBarView);
|
||||
this.editorView = editorView;
|
||||
this.options = options;
|
||||
this.spacer = null;
|
||||
this.maxHeight = 0;
|
||||
this.widthForMaxHeight = 0;
|
||||
this.floating = false;
|
||||
this.scrollHandler = null;
|
||||
this.root = editorView.root;
|
||||
this.wrapper = crel("div", {
|
||||
"class": prefix + "-wrapper"
|
||||
});
|
||||
this.menu = this.wrapper.appendChild(crel("div", {
|
||||
"class": prefix
|
||||
}));
|
||||
this.menu.className = prefix;
|
||||
if (editorView.dom.parentNode) editorView.dom.parentNode.replaceChild(this.wrapper, editorView.dom);
|
||||
this.wrapper.appendChild(editorView.dom);
|
||||
var _renderGrouped = renderGrouped(this.editorView, this.options.content),
|
||||
dom = _renderGrouped.dom,
|
||||
update = _renderGrouped.update;
|
||||
this.contentUpdate = update;
|
||||
this.menu.appendChild(dom);
|
||||
this.update();
|
||||
if (options.floating && !isIOS()) {
|
||||
this.updateFloat();
|
||||
var potentialScrollers = getAllWrapping(this.wrapper);
|
||||
this.scrollHandler = function (e) {
|
||||
var root = _this2.editorView.root;
|
||||
if (!(root.body || root).contains(_this2.wrapper)) potentialScrollers.forEach(function (el) {
|
||||
return el.removeEventListener("scroll", _this2.scrollHandler);
|
||||
});else _this2.updateFloat(e.target.getBoundingClientRect ? e.target : undefined);
|
||||
};
|
||||
potentialScrollers.forEach(function (el) {
|
||||
return el.addEventListener('scroll', _this2.scrollHandler);
|
||||
});
|
||||
}
|
||||
}
|
||||
_createClass(MenuBarView, [{
|
||||
key: "update",
|
||||
value: function update() {
|
||||
if (this.editorView.root != this.root) {
|
||||
var _renderGrouped2 = renderGrouped(this.editorView, this.options.content),
|
||||
dom = _renderGrouped2.dom,
|
||||
update = _renderGrouped2.update;
|
||||
this.contentUpdate = update;
|
||||
this.menu.replaceChild(dom, this.menu.firstChild);
|
||||
this.root = this.editorView.root;
|
||||
}
|
||||
this.contentUpdate(this.editorView.state);
|
||||
if (this.floating) {
|
||||
this.updateScrollCursor();
|
||||
} else {
|
||||
if (this.menu.offsetWidth != this.widthForMaxHeight) {
|
||||
this.widthForMaxHeight = this.menu.offsetWidth;
|
||||
this.maxHeight = 0;
|
||||
}
|
||||
if (this.menu.offsetHeight > this.maxHeight) {
|
||||
this.maxHeight = this.menu.offsetHeight;
|
||||
this.menu.style.minHeight = this.maxHeight + "px";
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
key: "updateScrollCursor",
|
||||
value: function updateScrollCursor() {
|
||||
var selection = this.editorView.root.getSelection();
|
||||
if (!selection.focusNode) return;
|
||||
var rects = selection.getRangeAt(0).getClientRects();
|
||||
var selRect = rects[selectionIsInverted(selection) ? 0 : rects.length - 1];
|
||||
if (!selRect) return;
|
||||
var menuRect = this.menu.getBoundingClientRect();
|
||||
if (selRect.top < menuRect.bottom && selRect.bottom > menuRect.top) {
|
||||
var scrollable = findWrappingScrollable(this.wrapper);
|
||||
if (scrollable) scrollable.scrollTop -= menuRect.bottom - selRect.top;
|
||||
}
|
||||
}
|
||||
}, {
|
||||
key: "updateFloat",
|
||||
value: function updateFloat(scrollAncestor) {
|
||||
var parent = this.wrapper,
|
||||
editorRect = parent.getBoundingClientRect(),
|
||||
top = scrollAncestor ? Math.max(0, scrollAncestor.getBoundingClientRect().top) : 0;
|
||||
if (this.floating) {
|
||||
if (editorRect.top >= top || editorRect.bottom < this.menu.offsetHeight + 10) {
|
||||
this.floating = false;
|
||||
this.menu.style.position = this.menu.style.left = this.menu.style.top = this.menu.style.width = "";
|
||||
this.menu.style.display = "";
|
||||
this.spacer.parentNode.removeChild(this.spacer);
|
||||
this.spacer = null;
|
||||
} else {
|
||||
var border = (parent.offsetWidth - parent.clientWidth) / 2;
|
||||
this.menu.style.left = editorRect.left + border + "px";
|
||||
this.menu.style.display = editorRect.top > (this.editorView.dom.ownerDocument.defaultView || window).innerHeight ? "none" : "";
|
||||
if (scrollAncestor) this.menu.style.top = top + "px";
|
||||
}
|
||||
} else {
|
||||
if (editorRect.top < top && editorRect.bottom >= this.menu.offsetHeight + 10) {
|
||||
this.floating = true;
|
||||
var menuRect = this.menu.getBoundingClientRect();
|
||||
this.menu.style.left = menuRect.left + "px";
|
||||
this.menu.style.width = menuRect.width + "px";
|
||||
if (scrollAncestor) this.menu.style.top = top + "px";
|
||||
this.menu.style.position = "fixed";
|
||||
this.spacer = crel("div", {
|
||||
"class": prefix + "-spacer",
|
||||
style: "height: ".concat(menuRect.height, "px")
|
||||
});
|
||||
parent.insertBefore(this.spacer, this.menu);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
key: "destroy",
|
||||
value: function destroy() {
|
||||
if (this.wrapper.parentNode) this.wrapper.parentNode.replaceChild(this.editorView.dom, this.wrapper);
|
||||
}
|
||||
}]);
|
||||
return MenuBarView;
|
||||
}();
|
||||
function selectionIsInverted(selection) {
|
||||
if (selection.anchorNode == selection.focusNode) return selection.anchorOffset > selection.focusOffset;
|
||||
return selection.anchorNode.compareDocumentPosition(selection.focusNode) == Node.DOCUMENT_POSITION_FOLLOWING;
|
||||
}
|
||||
function findWrappingScrollable(node) {
|
||||
for (var cur = node.parentNode; cur; cur = cur.parentNode) if (cur.scrollHeight > cur.clientHeight) return cur;
|
||||
}
|
||||
function getAllWrapping(node) {
|
||||
var res = [node.ownerDocument.defaultView || window];
|
||||
for (var cur = node.parentNode; cur; cur = cur.parentNode) res.push(cur);
|
||||
return res;
|
||||
}
|
||||
exports.Dropdown = Dropdown;
|
||||
exports.DropdownSubmenu = DropdownSubmenu;
|
||||
exports.MenuItem = MenuItem;
|
||||
exports.blockTypeItem = blockTypeItem;
|
||||
exports.icons = icons;
|
||||
exports.joinUpItem = joinUpItem;
|
||||
exports.liftItem = liftItem;
|
||||
exports.menuBar = menuBar;
|
||||
exports.redoItem = redoItem;
|
||||
exports.renderGrouped = renderGrouped;
|
||||
exports.selectParentNodeItem = selectParentNodeItem;
|
||||
exports.undoItem = undoItem;
|
||||
exports.wrapItem = wrapItem;
|
||||
+266
@@ -0,0 +1,266 @@
|
||||
import { EditorView } from 'prosemirror-view';
|
||||
import { EditorState, Transaction, Plugin } from 'prosemirror-state';
|
||||
import { NodeType, Attrs } from 'prosemirror-model';
|
||||
|
||||
/**
|
||||
The types defined in this module aren't the only thing you can
|
||||
display in your menu. Anything that conforms to this interface can
|
||||
be put into a menu structure.
|
||||
*/
|
||||
interface MenuElement {
|
||||
/**
|
||||
Render the element for display in the menu. Must return a DOM
|
||||
element and a function that can be used to update the element to
|
||||
a new state. The `update` function must return false if the
|
||||
update hid the entire element.
|
||||
*/
|
||||
render(pm: EditorView): {
|
||||
dom: HTMLElement;
|
||||
update: (state: EditorState) => boolean;
|
||||
};
|
||||
}
|
||||
/**
|
||||
An icon or label that, when clicked, executes a command.
|
||||
*/
|
||||
declare class MenuItem implements MenuElement {
|
||||
/**
|
||||
The spec used to create this item.
|
||||
*/
|
||||
readonly spec: MenuItemSpec;
|
||||
/**
|
||||
Create a menu item.
|
||||
*/
|
||||
constructor(
|
||||
/**
|
||||
The spec used to create this item.
|
||||
*/
|
||||
spec: MenuItemSpec);
|
||||
/**
|
||||
Renders the icon according to its [display
|
||||
spec](https://prosemirror.net/docs/ref/#menu.MenuItemSpec.display), and adds an event handler which
|
||||
executes the command when the representation is clicked.
|
||||
*/
|
||||
render(view: EditorView): {
|
||||
dom: HTMLElement;
|
||||
update: (state: EditorState) => boolean;
|
||||
};
|
||||
}
|
||||
/**
|
||||
Specifies an icon. May be either an SVG icon, in which case its
|
||||
`path` property should be an [SVG path
|
||||
spec](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d),
|
||||
and `width` and `height` should provide the viewbox in which that
|
||||
path exists. Alternatively, it may have a `text` property
|
||||
specifying a string of text that makes up the icon, with an
|
||||
optional `css` property giving additional CSS styling for the
|
||||
text. _Or_ it may contain `dom` property containing a DOM node.
|
||||
*/
|
||||
type IconSpec = {
|
||||
path: string;
|
||||
width: number;
|
||||
height: number;
|
||||
} | {
|
||||
text: string;
|
||||
css?: string;
|
||||
} | {
|
||||
dom: Node;
|
||||
};
|
||||
/**
|
||||
The configuration object passed to the `MenuItem` constructor.
|
||||
*/
|
||||
interface MenuItemSpec {
|
||||
/**
|
||||
The function to execute when the menu item is activated.
|
||||
*/
|
||||
run: (state: EditorState, dispatch: (tr: Transaction) => void, view: EditorView, event: Event) => void;
|
||||
/**
|
||||
Optional function that is used to determine whether the item is
|
||||
appropriate at the moment. Deselected items will be hidden.
|
||||
*/
|
||||
select?: (state: EditorState) => boolean;
|
||||
/**
|
||||
Function that is used to determine if the item is enabled. If
|
||||
given and returning false, the item will be given a disabled
|
||||
styling.
|
||||
*/
|
||||
enable?: (state: EditorState) => boolean;
|
||||
/**
|
||||
A predicate function to determine whether the item is 'active' (for
|
||||
example, the item for toggling the strong mark might be active then
|
||||
the cursor is in strong text).
|
||||
*/
|
||||
active?: (state: EditorState) => boolean;
|
||||
/**
|
||||
A function that renders the item. You must provide either this,
|
||||
[`icon`](https://prosemirror.net/docs/ref/#menu.MenuItemSpec.icon), or [`label`](https://prosemirror.net/docs/ref/#MenuItemSpec.label).
|
||||
*/
|
||||
render?: (view: EditorView) => HTMLElement;
|
||||
/**
|
||||
Describes an icon to show for this item.
|
||||
*/
|
||||
icon?: IconSpec;
|
||||
/**
|
||||
Makes the item show up as a text label. Mostly useful for items
|
||||
wrapped in a [drop-down](https://prosemirror.net/docs/ref/#menu.Dropdown) or similar menu. The object
|
||||
should have a `label` property providing the text to display.
|
||||
*/
|
||||
label?: string;
|
||||
/**
|
||||
Defines DOM title (mouseover) text for the item.
|
||||
*/
|
||||
title?: string | ((state: EditorState) => string);
|
||||
/**
|
||||
Optionally adds a CSS class to the item's DOM representation.
|
||||
*/
|
||||
class?: string;
|
||||
/**
|
||||
Optionally adds a string of inline CSS to the item's DOM
|
||||
representation.
|
||||
*/
|
||||
css?: string;
|
||||
}
|
||||
/**
|
||||
A drop-down menu, displayed as a label with a downwards-pointing
|
||||
triangle to the right of it.
|
||||
*/
|
||||
declare class Dropdown implements MenuElement {
|
||||
/**
|
||||
Create a dropdown wrapping the elements.
|
||||
*/
|
||||
constructor(content: readonly MenuElement[] | MenuElement,
|
||||
/**
|
||||
@internal
|
||||
*/
|
||||
options?: {
|
||||
/**
|
||||
The label to show on the drop-down control.
|
||||
*/
|
||||
label?: string;
|
||||
/**
|
||||
Sets the
|
||||
[`title`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/title)
|
||||
attribute given to the menu control.
|
||||
*/
|
||||
title?: string;
|
||||
/**
|
||||
When given, adds an extra CSS class to the menu control.
|
||||
*/
|
||||
class?: string;
|
||||
/**
|
||||
When given, adds an extra set of CSS styles to the menu control.
|
||||
*/
|
||||
css?: string;
|
||||
});
|
||||
/**
|
||||
Render the dropdown menu and sub-items.
|
||||
*/
|
||||
render(view: EditorView): {
|
||||
dom: HTMLElement;
|
||||
update: (state: EditorState) => boolean;
|
||||
};
|
||||
}
|
||||
/**
|
||||
Represents a submenu wrapping a group of elements that start
|
||||
hidden and expand to the right when hovered over or tapped.
|
||||
*/
|
||||
declare class DropdownSubmenu implements MenuElement {
|
||||
/**
|
||||
Creates a submenu for the given group of menu elements. The
|
||||
following options are recognized:
|
||||
*/
|
||||
constructor(content: readonly MenuElement[] | MenuElement,
|
||||
/**
|
||||
@internal
|
||||
*/
|
||||
options?: {
|
||||
/**
|
||||
The label to show on the submenu.
|
||||
*/
|
||||
label?: string;
|
||||
});
|
||||
/**
|
||||
Renders the submenu.
|
||||
*/
|
||||
render(view: EditorView): {
|
||||
dom: HTMLElement;
|
||||
update: (state: EditorState) => boolean;
|
||||
};
|
||||
}
|
||||
/**
|
||||
Render the given, possibly nested, array of menu elements into a
|
||||
document fragment, placing separators between them (and ensuring no
|
||||
superfluous separators appear when some of the groups turn out to
|
||||
be empty).
|
||||
*/
|
||||
declare function renderGrouped(view: EditorView, content: readonly (readonly MenuElement[])[]): {
|
||||
dom: DocumentFragment;
|
||||
update: (state: EditorState) => boolean;
|
||||
};
|
||||
/**
|
||||
A set of basic editor-related icons. Contains the properties
|
||||
`join`, `lift`, `selectParentNode`, `undo`, `redo`, `strong`, `em`,
|
||||
`code`, `link`, `bulletList`, `orderedList`, and `blockquote`, each
|
||||
holding an object that can be used as the `icon` option to
|
||||
`MenuItem`.
|
||||
*/
|
||||
declare const icons: {
|
||||
[name: string]: IconSpec;
|
||||
};
|
||||
/**
|
||||
Menu item for the `joinUp` command.
|
||||
*/
|
||||
declare const joinUpItem: MenuItem;
|
||||
/**
|
||||
Menu item for the `lift` command.
|
||||
*/
|
||||
declare const liftItem: MenuItem;
|
||||
/**
|
||||
Menu item for the `selectParentNode` command.
|
||||
*/
|
||||
declare const selectParentNodeItem: MenuItem;
|
||||
/**
|
||||
Menu item for the `undo` command.
|
||||
*/
|
||||
declare let undoItem: MenuItem;
|
||||
/**
|
||||
Menu item for the `redo` command.
|
||||
*/
|
||||
declare let redoItem: MenuItem;
|
||||
/**
|
||||
Build a menu item for wrapping the selection in a given node type.
|
||||
Adds `run` and `select` properties to the ones present in
|
||||
`options`. `options.attrs` may be an object that provides
|
||||
attributes for the wrapping node.
|
||||
*/
|
||||
declare function wrapItem(nodeType: NodeType, options: Partial<MenuItemSpec> & {
|
||||
attrs?: Attrs | null;
|
||||
}): MenuItem;
|
||||
/**
|
||||
Build a menu item for changing the type of the textblock around the
|
||||
selection to the given type. Provides `run`, `active`, and `select`
|
||||
properties. Others must be given in `options`. `options.attrs` may
|
||||
be an object to provide the attributes for the textblock node.
|
||||
*/
|
||||
declare function blockTypeItem(nodeType: NodeType, options: Partial<MenuItemSpec> & {
|
||||
attrs?: Attrs | null;
|
||||
}): MenuItem;
|
||||
|
||||
/**
|
||||
A plugin that will place a menu bar above the editor. Note that
|
||||
this involves wrapping the editor in an additional `<div>`.
|
||||
*/
|
||||
declare function menuBar(options: {
|
||||
/**
|
||||
Provides the content of the menu, as a nested array to be
|
||||
passed to `renderGrouped`.
|
||||
*/
|
||||
content: readonly (readonly MenuElement[])[];
|
||||
/**
|
||||
Determines whether the menu floats, i.e. whether it sticks to
|
||||
the top of the viewport when the editor is partially scrolled
|
||||
out of view.
|
||||
*/
|
||||
floating?: boolean;
|
||||
}): Plugin;
|
||||
|
||||
export { Dropdown, DropdownSubmenu, type IconSpec, type MenuElement, MenuItem, type MenuItemSpec, blockTypeItem, icons, joinUpItem, liftItem, menuBar, redoItem, renderGrouped, selectParentNodeItem, undoItem, wrapItem };
|
||||
+266
@@ -0,0 +1,266 @@
|
||||
import { EditorView } from 'prosemirror-view';
|
||||
import { EditorState, Transaction, Plugin } from 'prosemirror-state';
|
||||
import { NodeType, Attrs } from 'prosemirror-model';
|
||||
|
||||
/**
|
||||
The types defined in this module aren't the only thing you can
|
||||
display in your menu. Anything that conforms to this interface can
|
||||
be put into a menu structure.
|
||||
*/
|
||||
interface MenuElement {
|
||||
/**
|
||||
Render the element for display in the menu. Must return a DOM
|
||||
element and a function that can be used to update the element to
|
||||
a new state. The `update` function must return false if the
|
||||
update hid the entire element.
|
||||
*/
|
||||
render(pm: EditorView): {
|
||||
dom: HTMLElement;
|
||||
update: (state: EditorState) => boolean;
|
||||
};
|
||||
}
|
||||
/**
|
||||
An icon or label that, when clicked, executes a command.
|
||||
*/
|
||||
declare class MenuItem implements MenuElement {
|
||||
/**
|
||||
The spec used to create this item.
|
||||
*/
|
||||
readonly spec: MenuItemSpec;
|
||||
/**
|
||||
Create a menu item.
|
||||
*/
|
||||
constructor(
|
||||
/**
|
||||
The spec used to create this item.
|
||||
*/
|
||||
spec: MenuItemSpec);
|
||||
/**
|
||||
Renders the icon according to its [display
|
||||
spec](https://prosemirror.net/docs/ref/#menu.MenuItemSpec.display), and adds an event handler which
|
||||
executes the command when the representation is clicked.
|
||||
*/
|
||||
render(view: EditorView): {
|
||||
dom: HTMLElement;
|
||||
update: (state: EditorState) => boolean;
|
||||
};
|
||||
}
|
||||
/**
|
||||
Specifies an icon. May be either an SVG icon, in which case its
|
||||
`path` property should be an [SVG path
|
||||
spec](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d),
|
||||
and `width` and `height` should provide the viewbox in which that
|
||||
path exists. Alternatively, it may have a `text` property
|
||||
specifying a string of text that makes up the icon, with an
|
||||
optional `css` property giving additional CSS styling for the
|
||||
text. _Or_ it may contain `dom` property containing a DOM node.
|
||||
*/
|
||||
type IconSpec = {
|
||||
path: string;
|
||||
width: number;
|
||||
height: number;
|
||||
} | {
|
||||
text: string;
|
||||
css?: string;
|
||||
} | {
|
||||
dom: Node;
|
||||
};
|
||||
/**
|
||||
The configuration object passed to the `MenuItem` constructor.
|
||||
*/
|
||||
interface MenuItemSpec {
|
||||
/**
|
||||
The function to execute when the menu item is activated.
|
||||
*/
|
||||
run: (state: EditorState, dispatch: (tr: Transaction) => void, view: EditorView, event: Event) => void;
|
||||
/**
|
||||
Optional function that is used to determine whether the item is
|
||||
appropriate at the moment. Deselected items will be hidden.
|
||||
*/
|
||||
select?: (state: EditorState) => boolean;
|
||||
/**
|
||||
Function that is used to determine if the item is enabled. If
|
||||
given and returning false, the item will be given a disabled
|
||||
styling.
|
||||
*/
|
||||
enable?: (state: EditorState) => boolean;
|
||||
/**
|
||||
A predicate function to determine whether the item is 'active' (for
|
||||
example, the item for toggling the strong mark might be active then
|
||||
the cursor is in strong text).
|
||||
*/
|
||||
active?: (state: EditorState) => boolean;
|
||||
/**
|
||||
A function that renders the item. You must provide either this,
|
||||
[`icon`](https://prosemirror.net/docs/ref/#menu.MenuItemSpec.icon), or [`label`](https://prosemirror.net/docs/ref/#MenuItemSpec.label).
|
||||
*/
|
||||
render?: (view: EditorView) => HTMLElement;
|
||||
/**
|
||||
Describes an icon to show for this item.
|
||||
*/
|
||||
icon?: IconSpec;
|
||||
/**
|
||||
Makes the item show up as a text label. Mostly useful for items
|
||||
wrapped in a [drop-down](https://prosemirror.net/docs/ref/#menu.Dropdown) or similar menu. The object
|
||||
should have a `label` property providing the text to display.
|
||||
*/
|
||||
label?: string;
|
||||
/**
|
||||
Defines DOM title (mouseover) text for the item.
|
||||
*/
|
||||
title?: string | ((state: EditorState) => string);
|
||||
/**
|
||||
Optionally adds a CSS class to the item's DOM representation.
|
||||
*/
|
||||
class?: string;
|
||||
/**
|
||||
Optionally adds a string of inline CSS to the item's DOM
|
||||
representation.
|
||||
*/
|
||||
css?: string;
|
||||
}
|
||||
/**
|
||||
A drop-down menu, displayed as a label with a downwards-pointing
|
||||
triangle to the right of it.
|
||||
*/
|
||||
declare class Dropdown implements MenuElement {
|
||||
/**
|
||||
Create a dropdown wrapping the elements.
|
||||
*/
|
||||
constructor(content: readonly MenuElement[] | MenuElement,
|
||||
/**
|
||||
@internal
|
||||
*/
|
||||
options?: {
|
||||
/**
|
||||
The label to show on the drop-down control.
|
||||
*/
|
||||
label?: string;
|
||||
/**
|
||||
Sets the
|
||||
[`title`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/title)
|
||||
attribute given to the menu control.
|
||||
*/
|
||||
title?: string;
|
||||
/**
|
||||
When given, adds an extra CSS class to the menu control.
|
||||
*/
|
||||
class?: string;
|
||||
/**
|
||||
When given, adds an extra set of CSS styles to the menu control.
|
||||
*/
|
||||
css?: string;
|
||||
});
|
||||
/**
|
||||
Render the dropdown menu and sub-items.
|
||||
*/
|
||||
render(view: EditorView): {
|
||||
dom: HTMLElement;
|
||||
update: (state: EditorState) => boolean;
|
||||
};
|
||||
}
|
||||
/**
|
||||
Represents a submenu wrapping a group of elements that start
|
||||
hidden and expand to the right when hovered over or tapped.
|
||||
*/
|
||||
declare class DropdownSubmenu implements MenuElement {
|
||||
/**
|
||||
Creates a submenu for the given group of menu elements. The
|
||||
following options are recognized:
|
||||
*/
|
||||
constructor(content: readonly MenuElement[] | MenuElement,
|
||||
/**
|
||||
@internal
|
||||
*/
|
||||
options?: {
|
||||
/**
|
||||
The label to show on the submenu.
|
||||
*/
|
||||
label?: string;
|
||||
});
|
||||
/**
|
||||
Renders the submenu.
|
||||
*/
|
||||
render(view: EditorView): {
|
||||
dom: HTMLElement;
|
||||
update: (state: EditorState) => boolean;
|
||||
};
|
||||
}
|
||||
/**
|
||||
Render the given, possibly nested, array of menu elements into a
|
||||
document fragment, placing separators between them (and ensuring no
|
||||
superfluous separators appear when some of the groups turn out to
|
||||
be empty).
|
||||
*/
|
||||
declare function renderGrouped(view: EditorView, content: readonly (readonly MenuElement[])[]): {
|
||||
dom: DocumentFragment;
|
||||
update: (state: EditorState) => boolean;
|
||||
};
|
||||
/**
|
||||
A set of basic editor-related icons. Contains the properties
|
||||
`join`, `lift`, `selectParentNode`, `undo`, `redo`, `strong`, `em`,
|
||||
`code`, `link`, `bulletList`, `orderedList`, and `blockquote`, each
|
||||
holding an object that can be used as the `icon` option to
|
||||
`MenuItem`.
|
||||
*/
|
||||
declare const icons: {
|
||||
[name: string]: IconSpec;
|
||||
};
|
||||
/**
|
||||
Menu item for the `joinUp` command.
|
||||
*/
|
||||
declare const joinUpItem: MenuItem;
|
||||
/**
|
||||
Menu item for the `lift` command.
|
||||
*/
|
||||
declare const liftItem: MenuItem;
|
||||
/**
|
||||
Menu item for the `selectParentNode` command.
|
||||
*/
|
||||
declare const selectParentNodeItem: MenuItem;
|
||||
/**
|
||||
Menu item for the `undo` command.
|
||||
*/
|
||||
declare let undoItem: MenuItem;
|
||||
/**
|
||||
Menu item for the `redo` command.
|
||||
*/
|
||||
declare let redoItem: MenuItem;
|
||||
/**
|
||||
Build a menu item for wrapping the selection in a given node type.
|
||||
Adds `run` and `select` properties to the ones present in
|
||||
`options`. `options.attrs` may be an object that provides
|
||||
attributes for the wrapping node.
|
||||
*/
|
||||
declare function wrapItem(nodeType: NodeType, options: Partial<MenuItemSpec> & {
|
||||
attrs?: Attrs | null;
|
||||
}): MenuItem;
|
||||
/**
|
||||
Build a menu item for changing the type of the textblock around the
|
||||
selection to the given type. Provides `run`, `active`, and `select`
|
||||
properties. Others must be given in `options`. `options.attrs` may
|
||||
be an object to provide the attributes for the textblock node.
|
||||
*/
|
||||
declare function blockTypeItem(nodeType: NodeType, options: Partial<MenuItemSpec> & {
|
||||
attrs?: Attrs | null;
|
||||
}): MenuItem;
|
||||
|
||||
/**
|
||||
A plugin that will place a menu bar above the editor. Note that
|
||||
this involves wrapping the editor in an additional `<div>`.
|
||||
*/
|
||||
declare function menuBar(options: {
|
||||
/**
|
||||
Provides the content of the menu, as a nested array to be
|
||||
passed to `renderGrouped`.
|
||||
*/
|
||||
content: readonly (readonly MenuElement[])[];
|
||||
/**
|
||||
Determines whether the menu floats, i.e. whether it sticks to
|
||||
the top of the viewport when the editor is partially scrolled
|
||||
out of view.
|
||||
*/
|
||||
floating?: boolean;
|
||||
}): Plugin;
|
||||
|
||||
export { Dropdown, DropdownSubmenu, type IconSpec, type MenuElement, MenuItem, type MenuItemSpec, blockTypeItem, icons, joinUpItem, liftItem, menuBar, redoItem, renderGrouped, selectParentNodeItem, undoItem, wrapItem };
|
||||
+604
@@ -0,0 +1,604 @@
|
||||
import crel from 'crelt';
|
||||
import { joinUp, lift, selectParentNode, setBlockType, wrapIn } from 'prosemirror-commands';
|
||||
import { undo, redo } from 'prosemirror-history';
|
||||
import { Plugin } from 'prosemirror-state';
|
||||
|
||||
const SVG = "http://www.w3.org/2000/svg";
|
||||
const XLINK = "http://www.w3.org/1999/xlink";
|
||||
const prefix$2 = "ProseMirror-icon";
|
||||
function hashPath(path) {
|
||||
let hash = 0;
|
||||
for (let i = 0; i < path.length; i++)
|
||||
hash = (((hash << 5) - hash) + path.charCodeAt(i)) | 0;
|
||||
return hash;
|
||||
}
|
||||
function getIcon(root, icon) {
|
||||
let doc = (root.nodeType == 9 ? root : root.ownerDocument) || document;
|
||||
let node = doc.createElement("div");
|
||||
node.className = prefix$2;
|
||||
if (icon.path) {
|
||||
let { path, width, height } = icon;
|
||||
let name = "pm-icon-" + hashPath(path).toString(16);
|
||||
if (!doc.getElementById(name))
|
||||
buildSVG(root, name, icon);
|
||||
let svg = node.appendChild(doc.createElementNS(SVG, "svg"));
|
||||
svg.style.width = (width / height) + "em";
|
||||
let use = svg.appendChild(doc.createElementNS(SVG, "use"));
|
||||
use.setAttributeNS(XLINK, "href", /([^#]*)/.exec(doc.location.toString())[1] + "#" + name);
|
||||
}
|
||||
else if (icon.dom) {
|
||||
node.appendChild(icon.dom.cloneNode(true));
|
||||
}
|
||||
else {
|
||||
let { text, css } = icon;
|
||||
node.appendChild(doc.createElement("span")).textContent = text || '';
|
||||
if (css)
|
||||
node.firstChild.style.cssText = css;
|
||||
}
|
||||
return node;
|
||||
}
|
||||
function buildSVG(root, name, data) {
|
||||
let [doc, top] = root.nodeType == 9 ? [root, root.body] : [root.ownerDocument || document, root];
|
||||
let collection = doc.getElementById(prefix$2 + "-collection");
|
||||
if (!collection) {
|
||||
collection = doc.createElementNS(SVG, "svg");
|
||||
collection.id = prefix$2 + "-collection";
|
||||
collection.style.display = "none";
|
||||
top.insertBefore(collection, top.firstChild);
|
||||
}
|
||||
let sym = doc.createElementNS(SVG, "symbol");
|
||||
sym.id = name;
|
||||
sym.setAttribute("viewBox", "0 0 " + data.width + " " + data.height);
|
||||
let path = sym.appendChild(doc.createElementNS(SVG, "path"));
|
||||
path.setAttribute("d", data.path);
|
||||
collection.appendChild(sym);
|
||||
}
|
||||
|
||||
const prefix$1 = "ProseMirror-menu";
|
||||
/**
|
||||
An icon or label that, when clicked, executes a command.
|
||||
*/
|
||||
class MenuItem {
|
||||
/**
|
||||
Create a menu item.
|
||||
*/
|
||||
constructor(
|
||||
/**
|
||||
The spec used to create this item.
|
||||
*/
|
||||
spec) {
|
||||
this.spec = spec;
|
||||
}
|
||||
/**
|
||||
Renders the icon according to its [display
|
||||
spec](https://prosemirror.net/docs/ref/#menu.MenuItemSpec.display), and adds an event handler which
|
||||
executes the command when the representation is clicked.
|
||||
*/
|
||||
render(view) {
|
||||
let spec = this.spec;
|
||||
let dom = spec.render ? spec.render(view)
|
||||
: spec.icon ? getIcon(view.root, spec.icon)
|
||||
: spec.label ? crel("div", null, translate(view, spec.label))
|
||||
: null;
|
||||
if (!dom)
|
||||
throw new RangeError("MenuItem without icon or label property");
|
||||
if (spec.title) {
|
||||
const title = (typeof spec.title === "function" ? spec.title(view.state) : spec.title);
|
||||
dom.setAttribute("title", translate(view, title));
|
||||
}
|
||||
if (spec.class)
|
||||
dom.classList.add(spec.class);
|
||||
if (spec.css)
|
||||
dom.style.cssText += spec.css;
|
||||
dom.addEventListener("mousedown", e => {
|
||||
e.preventDefault();
|
||||
if (!dom.classList.contains(prefix$1 + "-disabled"))
|
||||
spec.run(view.state, view.dispatch, view, e);
|
||||
});
|
||||
function update(state) {
|
||||
if (spec.select) {
|
||||
let selected = spec.select(state);
|
||||
dom.style.display = selected ? "" : "none";
|
||||
if (!selected)
|
||||
return false;
|
||||
}
|
||||
let enabled = true;
|
||||
if (spec.enable) {
|
||||
enabled = spec.enable(state) || false;
|
||||
setClass(dom, prefix$1 + "-disabled", !enabled);
|
||||
}
|
||||
if (spec.active) {
|
||||
let active = enabled && spec.active(state) || false;
|
||||
setClass(dom, prefix$1 + "-active", active);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return { dom, update };
|
||||
}
|
||||
}
|
||||
function translate(view, text) {
|
||||
return view._props.translate ? view._props.translate(text) : text;
|
||||
}
|
||||
let lastMenuEvent = { time: 0, node: null };
|
||||
function markMenuEvent(e) {
|
||||
lastMenuEvent.time = Date.now();
|
||||
lastMenuEvent.node = e.target;
|
||||
}
|
||||
function isMenuEvent(wrapper) {
|
||||
return Date.now() - 100 < lastMenuEvent.time &&
|
||||
lastMenuEvent.node && wrapper.contains(lastMenuEvent.node);
|
||||
}
|
||||
/**
|
||||
A drop-down menu, displayed as a label with a downwards-pointing
|
||||
triangle to the right of it.
|
||||
*/
|
||||
class Dropdown {
|
||||
/**
|
||||
Create a dropdown wrapping the elements.
|
||||
*/
|
||||
constructor(content,
|
||||
/**
|
||||
@internal
|
||||
*/
|
||||
options = {}) {
|
||||
this.options = options;
|
||||
this.options = options || {};
|
||||
this.content = Array.isArray(content) ? content : [content];
|
||||
}
|
||||
/**
|
||||
Render the dropdown menu and sub-items.
|
||||
*/
|
||||
render(view) {
|
||||
let content = renderDropdownItems(this.content, view);
|
||||
let win = view.dom.ownerDocument.defaultView || window;
|
||||
let label = crel("div", { class: prefix$1 + "-dropdown " + (this.options.class || ""),
|
||||
style: this.options.css }, translate(view, this.options.label || ""));
|
||||
if (this.options.title)
|
||||
label.setAttribute("title", translate(view, this.options.title));
|
||||
let wrap = crel("div", { class: prefix$1 + "-dropdown-wrap" }, label);
|
||||
let open = null;
|
||||
let listeningOnClose = null;
|
||||
let close = () => {
|
||||
if (open && open.close()) {
|
||||
open = null;
|
||||
win.removeEventListener("mousedown", listeningOnClose);
|
||||
}
|
||||
};
|
||||
label.addEventListener("mousedown", e => {
|
||||
e.preventDefault();
|
||||
markMenuEvent(e);
|
||||
if (open) {
|
||||
close();
|
||||
}
|
||||
else {
|
||||
open = this.expand(wrap, content.dom);
|
||||
win.addEventListener("mousedown", listeningOnClose = () => {
|
||||
if (!isMenuEvent(wrap))
|
||||
close();
|
||||
});
|
||||
}
|
||||
});
|
||||
function update(state) {
|
||||
let inner = content.update(state);
|
||||
wrap.style.display = inner ? "" : "none";
|
||||
return inner;
|
||||
}
|
||||
return { dom: wrap, update };
|
||||
}
|
||||
/**
|
||||
@internal
|
||||
*/
|
||||
expand(dom, items) {
|
||||
let menuDOM = crel("div", { class: prefix$1 + "-dropdown-menu " + (this.options.class || "") }, items);
|
||||
let done = false;
|
||||
function close() {
|
||||
if (done)
|
||||
return false;
|
||||
done = true;
|
||||
dom.removeChild(menuDOM);
|
||||
return true;
|
||||
}
|
||||
dom.appendChild(menuDOM);
|
||||
return { close, node: menuDOM };
|
||||
}
|
||||
}
|
||||
function renderDropdownItems(items, view) {
|
||||
let rendered = [], updates = [];
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
let { dom, update } = items[i].render(view);
|
||||
rendered.push(crel("div", { class: prefix$1 + "-dropdown-item" }, dom));
|
||||
updates.push(update);
|
||||
}
|
||||
return { dom: rendered, update: combineUpdates(updates, rendered) };
|
||||
}
|
||||
function combineUpdates(updates, nodes) {
|
||||
return (state) => {
|
||||
let something = false;
|
||||
for (let i = 0; i < updates.length; i++) {
|
||||
let up = updates[i](state);
|
||||
nodes[i].style.display = up ? "" : "none";
|
||||
if (up)
|
||||
something = true;
|
||||
}
|
||||
return something;
|
||||
};
|
||||
}
|
||||
/**
|
||||
Represents a submenu wrapping a group of elements that start
|
||||
hidden and expand to the right when hovered over or tapped.
|
||||
*/
|
||||
class DropdownSubmenu {
|
||||
/**
|
||||
Creates a submenu for the given group of menu elements. The
|
||||
following options are recognized:
|
||||
*/
|
||||
constructor(content,
|
||||
/**
|
||||
@internal
|
||||
*/
|
||||
options = {}) {
|
||||
this.options = options;
|
||||
this.content = Array.isArray(content) ? content : [content];
|
||||
}
|
||||
/**
|
||||
Renders the submenu.
|
||||
*/
|
||||
render(view) {
|
||||
let items = renderDropdownItems(this.content, view);
|
||||
let win = view.dom.ownerDocument.defaultView || window;
|
||||
let label = crel("div", { class: prefix$1 + "-submenu-label" }, translate(view, this.options.label || ""));
|
||||
let wrap = crel("div", { class: prefix$1 + "-submenu-wrap" }, label, crel("div", { class: prefix$1 + "-submenu" }, items.dom));
|
||||
let listeningOnClose = null;
|
||||
label.addEventListener("mousedown", e => {
|
||||
e.preventDefault();
|
||||
markMenuEvent(e);
|
||||
setClass(wrap, prefix$1 + "-submenu-wrap-active", false);
|
||||
if (!listeningOnClose)
|
||||
win.addEventListener("mousedown", listeningOnClose = () => {
|
||||
if (!isMenuEvent(wrap)) {
|
||||
wrap.classList.remove(prefix$1 + "-submenu-wrap-active");
|
||||
win.removeEventListener("mousedown", listeningOnClose);
|
||||
listeningOnClose = null;
|
||||
}
|
||||
});
|
||||
});
|
||||
function update(state) {
|
||||
let inner = items.update(state);
|
||||
wrap.style.display = inner ? "" : "none";
|
||||
return inner;
|
||||
}
|
||||
return { dom: wrap, update };
|
||||
}
|
||||
}
|
||||
/**
|
||||
Render the given, possibly nested, array of menu elements into a
|
||||
document fragment, placing separators between them (and ensuring no
|
||||
superfluous separators appear when some of the groups turn out to
|
||||
be empty).
|
||||
*/
|
||||
function renderGrouped(view, content) {
|
||||
let result = document.createDocumentFragment();
|
||||
let updates = [], separators = [];
|
||||
for (let i = 0; i < content.length; i++) {
|
||||
let items = content[i], localUpdates = [], localNodes = [];
|
||||
for (let j = 0; j < items.length; j++) {
|
||||
let { dom, update } = items[j].render(view);
|
||||
let span = crel("span", { class: prefix$1 + "item" }, dom);
|
||||
result.appendChild(span);
|
||||
localNodes.push(span);
|
||||
localUpdates.push(update);
|
||||
}
|
||||
if (localUpdates.length) {
|
||||
updates.push(combineUpdates(localUpdates, localNodes));
|
||||
if (i < content.length - 1)
|
||||
separators.push(result.appendChild(separator()));
|
||||
}
|
||||
}
|
||||
function update(state) {
|
||||
let something = false, needSep = false;
|
||||
for (let i = 0; i < updates.length; i++) {
|
||||
let hasContent = updates[i](state);
|
||||
if (i)
|
||||
separators[i - 1].style.display = needSep && hasContent ? "" : "none";
|
||||
needSep = hasContent;
|
||||
if (hasContent)
|
||||
something = true;
|
||||
}
|
||||
return something;
|
||||
}
|
||||
return { dom: result, update };
|
||||
}
|
||||
function separator() {
|
||||
return crel("span", { class: prefix$1 + "separator" });
|
||||
}
|
||||
/**
|
||||
A set of basic editor-related icons. Contains the properties
|
||||
`join`, `lift`, `selectParentNode`, `undo`, `redo`, `strong`, `em`,
|
||||
`code`, `link`, `bulletList`, `orderedList`, and `blockquote`, each
|
||||
holding an object that can be used as the `icon` option to
|
||||
`MenuItem`.
|
||||
*/
|
||||
const icons = {
|
||||
join: {
|
||||
width: 800, height: 900,
|
||||
path: "M0 75h800v125h-800z M0 825h800v-125h-800z M250 400h100v-100h100v100h100v100h-100v100h-100v-100h-100z"
|
||||
},
|
||||
lift: {
|
||||
width: 1024, height: 1024,
|
||||
path: "M219 310v329q0 7-5 12t-12 5q-8 0-13-5l-164-164q-5-5-5-13t5-13l164-164q5-5 13-5 7 0 12 5t5 12zM1024 749v109q0 7-5 12t-12 5h-987q-7 0-12-5t-5-12v-109q0-7 5-12t12-5h987q7 0 12 5t5 12zM1024 530v109q0 7-5 12t-12 5h-621q-7 0-12-5t-5-12v-109q0-7 5-12t12-5h621q7 0 12 5t5 12zM1024 310v109q0 7-5 12t-12 5h-621q-7 0-12-5t-5-12v-109q0-7 5-12t12-5h621q7 0 12 5t5 12zM1024 91v109q0 7-5 12t-12 5h-987q-7 0-12-5t-5-12v-109q0-7 5-12t12-5h987q7 0 12 5t5 12z"
|
||||
},
|
||||
selectParentNode: { text: "\u2b1a", css: "font-weight: bold" },
|
||||
undo: {
|
||||
width: 1024, height: 1024,
|
||||
path: "M761 1024c113-206 132-520-313-509v253l-384-384 384-384v248c534-13 594 472 313 775z"
|
||||
},
|
||||
redo: {
|
||||
width: 1024, height: 1024,
|
||||
path: "M576 248v-248l384 384-384 384v-253c-446-10-427 303-313 509-280-303-221-789 313-775z"
|
||||
},
|
||||
strong: {
|
||||
width: 805, height: 1024,
|
||||
path: "M317 869q42 18 80 18 214 0 214-191 0-65-23-102-15-25-35-42t-38-26-46-14-48-6-54-1q-41 0-57 5 0 30-0 90t-0 90q0 4-0 38t-0 55 2 47 6 38zM309 442q24 4 62 4 46 0 81-7t62-25 42-51 14-81q0-40-16-70t-45-46-61-24-70-8q-28 0-74 7 0 28 2 86t2 86q0 15-0 45t-0 45q0 26 0 39zM0 950l1-53q8-2 48-9t60-15q4-6 7-15t4-19 3-18 1-21 0-19v-37q0-561-12-585-2-4-12-8t-25-6-28-4-27-2-17-1l-2-47q56-1 194-6t213-5q13 0 39 0t38 0q40 0 78 7t73 24 61 40 42 59 16 78q0 29-9 54t-22 41-36 32-41 25-48 22q88 20 146 76t58 141q0 57-20 102t-53 74-78 48-93 27-100 8q-25 0-75-1t-75-1q-60 0-175 6t-132 6z"
|
||||
},
|
||||
em: {
|
||||
width: 585, height: 1024,
|
||||
path: "M0 949l9-48q3-1 46-12t63-21q16-20 23-57 0-4 35-165t65-310 29-169v-14q-13-7-31-10t-39-4-33-3l10-58q18 1 68 3t85 4 68 1q27 0 56-1t69-4 56-3q-2 22-10 50-17 5-58 16t-62 19q-4 10-8 24t-5 22-4 26-3 24q-15 84-50 239t-44 203q-1 5-7 33t-11 51-9 47-3 32l0 10q9 2 105 17-1 25-9 56-6 0-18 0t-18 0q-16 0-49-5t-49-5q-78-1-117-1-29 0-81 5t-69 6z"
|
||||
},
|
||||
code: {
|
||||
width: 896, height: 1024,
|
||||
path: "M608 192l-96 96 224 224-224 224 96 96 288-320-288-320zM288 192l-288 320 288 320 96-96-224-224 224-224-96-96z"
|
||||
},
|
||||
link: {
|
||||
width: 951, height: 1024,
|
||||
path: "M832 694q0-22-16-38l-118-118q-16-16-38-16-24 0-41 18 1 1 10 10t12 12 8 10 7 14 2 15q0 22-16 38t-38 16q-8 0-15-2t-14-7-10-8-12-12-10-10q-18 17-18 41 0 22 16 38l117 118q15 15 38 15 22 0 38-14l84-83q16-16 16-38zM430 292q0-22-16-38l-117-118q-16-16-38-16-22 0-38 15l-84 83q-16 16-16 38 0 22 16 38l118 118q15 15 38 15 24 0 41-17-1-1-10-10t-12-12-8-10-7-14-2-15q0-22 16-38t38-16q8 0 15 2t14 7 10 8 12 12 10 10q18-17 18-41zM941 694q0 68-48 116l-84 83q-47 47-116 47-69 0-116-48l-117-118q-47-47-47-116 0-70 50-119l-50-50q-49 50-118 50-68 0-116-48l-118-118q-48-48-48-116t48-116l84-83q47-47 116-47 69 0 116 48l117 118q47 47 47 116 0 70-50 119l50 50q49-50 118-50 68 0 116 48l118 118q48 48 48 116z"
|
||||
},
|
||||
bulletList: {
|
||||
width: 768, height: 896,
|
||||
path: "M0 512h128v-128h-128v128zM0 256h128v-128h-128v128zM0 768h128v-128h-128v128zM256 512h512v-128h-512v128zM256 256h512v-128h-512v128zM256 768h512v-128h-512v128z"
|
||||
},
|
||||
orderedList: {
|
||||
width: 768, height: 896,
|
||||
path: "M320 512h448v-128h-448v128zM320 768h448v-128h-448v128zM320 128v128h448v-128h-448zM79 384h78v-256h-36l-85 23v50l43-2v185zM189 590c0-36-12-78-96-78-33 0-64 6-83 16l1 66c21-10 42-15 67-15s32 11 32 28c0 26-30 58-110 112v50h192v-67l-91 2c49-30 87-66 87-113l1-1z"
|
||||
},
|
||||
blockquote: {
|
||||
width: 640, height: 896,
|
||||
path: "M0 448v256h256v-256h-128c0 0 0-128 128-128v-128c0 0-256 0-256 256zM640 320v-128c0 0-256 0-256 256v256h256v-256h-128c0 0 0-128 128-128z"
|
||||
}
|
||||
};
|
||||
/**
|
||||
Menu item for the `joinUp` command.
|
||||
*/
|
||||
const joinUpItem = new MenuItem({
|
||||
title: "Join with above block",
|
||||
run: joinUp,
|
||||
select: state => joinUp(state),
|
||||
icon: icons.join
|
||||
});
|
||||
/**
|
||||
Menu item for the `lift` command.
|
||||
*/
|
||||
const liftItem = new MenuItem({
|
||||
title: "Lift out of enclosing block",
|
||||
run: lift,
|
||||
select: state => lift(state),
|
||||
icon: icons.lift
|
||||
});
|
||||
/**
|
||||
Menu item for the `selectParentNode` command.
|
||||
*/
|
||||
const selectParentNodeItem = new MenuItem({
|
||||
title: "Select parent node",
|
||||
run: selectParentNode,
|
||||
select: state => selectParentNode(state),
|
||||
icon: icons.selectParentNode
|
||||
});
|
||||
/**
|
||||
Menu item for the `undo` command.
|
||||
*/
|
||||
let undoItem = new MenuItem({
|
||||
title: "Undo last change",
|
||||
run: undo,
|
||||
enable: state => undo(state),
|
||||
icon: icons.undo
|
||||
});
|
||||
/**
|
||||
Menu item for the `redo` command.
|
||||
*/
|
||||
let redoItem = new MenuItem({
|
||||
title: "Redo last undone change",
|
||||
run: redo,
|
||||
enable: state => redo(state),
|
||||
icon: icons.redo
|
||||
});
|
||||
/**
|
||||
Build a menu item for wrapping the selection in a given node type.
|
||||
Adds `run` and `select` properties to the ones present in
|
||||
`options`. `options.attrs` may be an object that provides
|
||||
attributes for the wrapping node.
|
||||
*/
|
||||
function wrapItem(nodeType, options) {
|
||||
let passedOptions = {
|
||||
run(state, dispatch) {
|
||||
return wrapIn(nodeType, options.attrs)(state, dispatch);
|
||||
},
|
||||
select(state) {
|
||||
return wrapIn(nodeType, options.attrs)(state);
|
||||
}
|
||||
};
|
||||
for (let prop in options)
|
||||
passedOptions[prop] = options[prop];
|
||||
return new MenuItem(passedOptions);
|
||||
}
|
||||
/**
|
||||
Build a menu item for changing the type of the textblock around the
|
||||
selection to the given type. Provides `run`, `active`, and `select`
|
||||
properties. Others must be given in `options`. `options.attrs` may
|
||||
be an object to provide the attributes for the textblock node.
|
||||
*/
|
||||
function blockTypeItem(nodeType, options) {
|
||||
let command = setBlockType(nodeType, options.attrs);
|
||||
let passedOptions = {
|
||||
run: command,
|
||||
enable(state) { return command(state); },
|
||||
active(state) {
|
||||
let { $from, to, node } = state.selection;
|
||||
if (node)
|
||||
return node.hasMarkup(nodeType, options.attrs);
|
||||
return to <= $from.end() && $from.parent.hasMarkup(nodeType, options.attrs);
|
||||
}
|
||||
};
|
||||
for (let prop in options)
|
||||
passedOptions[prop] = options[prop];
|
||||
return new MenuItem(passedOptions);
|
||||
}
|
||||
// Work around classList.toggle being broken in IE11
|
||||
function setClass(dom, cls, on) {
|
||||
if (on)
|
||||
dom.classList.add(cls);
|
||||
else
|
||||
dom.classList.remove(cls);
|
||||
}
|
||||
|
||||
const prefix = "ProseMirror-menubar";
|
||||
function isIOS() {
|
||||
if (typeof navigator == "undefined")
|
||||
return false;
|
||||
let agent = navigator.userAgent;
|
||||
return !/Edge\/\d/.test(agent) && /AppleWebKit/.test(agent) && /Mobile\/\w+/.test(agent);
|
||||
}
|
||||
/**
|
||||
A plugin that will place a menu bar above the editor. Note that
|
||||
this involves wrapping the editor in an additional `<div>`.
|
||||
*/
|
||||
function menuBar(options) {
|
||||
return new Plugin({
|
||||
view(editorView) { return new MenuBarView(editorView, options); }
|
||||
});
|
||||
}
|
||||
class MenuBarView {
|
||||
constructor(editorView, options) {
|
||||
this.editorView = editorView;
|
||||
this.options = options;
|
||||
this.spacer = null;
|
||||
this.maxHeight = 0;
|
||||
this.widthForMaxHeight = 0;
|
||||
this.floating = false;
|
||||
this.scrollHandler = null;
|
||||
this.root = editorView.root;
|
||||
this.wrapper = crel("div", { class: prefix + "-wrapper" });
|
||||
this.menu = this.wrapper.appendChild(crel("div", { class: prefix }));
|
||||
this.menu.className = prefix;
|
||||
if (editorView.dom.parentNode)
|
||||
editorView.dom.parentNode.replaceChild(this.wrapper, editorView.dom);
|
||||
this.wrapper.appendChild(editorView.dom);
|
||||
let { dom, update } = renderGrouped(this.editorView, this.options.content);
|
||||
this.contentUpdate = update;
|
||||
this.menu.appendChild(dom);
|
||||
this.update();
|
||||
if (options.floating && !isIOS()) {
|
||||
this.updateFloat();
|
||||
let potentialScrollers = getAllWrapping(this.wrapper);
|
||||
this.scrollHandler = (e) => {
|
||||
let root = this.editorView.root;
|
||||
if (!(root.body || root).contains(this.wrapper))
|
||||
potentialScrollers.forEach(el => el.removeEventListener("scroll", this.scrollHandler));
|
||||
else
|
||||
this.updateFloat(e.target.getBoundingClientRect ? e.target : undefined);
|
||||
};
|
||||
potentialScrollers.forEach(el => el.addEventListener('scroll', this.scrollHandler));
|
||||
}
|
||||
}
|
||||
update() {
|
||||
if (this.editorView.root != this.root) {
|
||||
let { dom, update } = renderGrouped(this.editorView, this.options.content);
|
||||
this.contentUpdate = update;
|
||||
this.menu.replaceChild(dom, this.menu.firstChild);
|
||||
this.root = this.editorView.root;
|
||||
}
|
||||
this.contentUpdate(this.editorView.state);
|
||||
if (this.floating) {
|
||||
this.updateScrollCursor();
|
||||
}
|
||||
else {
|
||||
if (this.menu.offsetWidth != this.widthForMaxHeight) {
|
||||
this.widthForMaxHeight = this.menu.offsetWidth;
|
||||
this.maxHeight = 0;
|
||||
}
|
||||
if (this.menu.offsetHeight > this.maxHeight) {
|
||||
this.maxHeight = this.menu.offsetHeight;
|
||||
this.menu.style.minHeight = this.maxHeight + "px";
|
||||
}
|
||||
}
|
||||
}
|
||||
updateScrollCursor() {
|
||||
let selection = this.editorView.root.getSelection();
|
||||
if (!selection.focusNode)
|
||||
return;
|
||||
let rects = selection.getRangeAt(0).getClientRects();
|
||||
let selRect = rects[selectionIsInverted(selection) ? 0 : rects.length - 1];
|
||||
if (!selRect)
|
||||
return;
|
||||
let menuRect = this.menu.getBoundingClientRect();
|
||||
if (selRect.top < menuRect.bottom && selRect.bottom > menuRect.top) {
|
||||
let scrollable = findWrappingScrollable(this.wrapper);
|
||||
if (scrollable)
|
||||
scrollable.scrollTop -= (menuRect.bottom - selRect.top);
|
||||
}
|
||||
}
|
||||
updateFloat(scrollAncestor) {
|
||||
let parent = this.wrapper, editorRect = parent.getBoundingClientRect(), top = scrollAncestor ? Math.max(0, scrollAncestor.getBoundingClientRect().top) : 0;
|
||||
if (this.floating) {
|
||||
if (editorRect.top >= top || editorRect.bottom < this.menu.offsetHeight + 10) {
|
||||
this.floating = false;
|
||||
this.menu.style.position = this.menu.style.left = this.menu.style.top = this.menu.style.width = "";
|
||||
this.menu.style.display = "";
|
||||
this.spacer.parentNode.removeChild(this.spacer);
|
||||
this.spacer = null;
|
||||
}
|
||||
else {
|
||||
let border = (parent.offsetWidth - parent.clientWidth) / 2;
|
||||
this.menu.style.left = (editorRect.left + border) + "px";
|
||||
this.menu.style.display = editorRect.top > (this.editorView.dom.ownerDocument.defaultView || window).innerHeight
|
||||
? "none" : "";
|
||||
if (scrollAncestor)
|
||||
this.menu.style.top = top + "px";
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (editorRect.top < top && editorRect.bottom >= this.menu.offsetHeight + 10) {
|
||||
this.floating = true;
|
||||
let menuRect = this.menu.getBoundingClientRect();
|
||||
this.menu.style.left = menuRect.left + "px";
|
||||
this.menu.style.width = menuRect.width + "px";
|
||||
if (scrollAncestor)
|
||||
this.menu.style.top = top + "px";
|
||||
this.menu.style.position = "fixed";
|
||||
this.spacer = crel("div", { class: prefix + "-spacer", style: `height: ${menuRect.height}px` });
|
||||
parent.insertBefore(this.spacer, this.menu);
|
||||
}
|
||||
}
|
||||
}
|
||||
destroy() {
|
||||
if (this.wrapper.parentNode)
|
||||
this.wrapper.parentNode.replaceChild(this.editorView.dom, this.wrapper);
|
||||
}
|
||||
}
|
||||
// Not precise, but close enough
|
||||
function selectionIsInverted(selection) {
|
||||
if (selection.anchorNode == selection.focusNode)
|
||||
return selection.anchorOffset > selection.focusOffset;
|
||||
return selection.anchorNode.compareDocumentPosition(selection.focusNode) == Node.DOCUMENT_POSITION_FOLLOWING;
|
||||
}
|
||||
function findWrappingScrollable(node) {
|
||||
for (let cur = node.parentNode; cur; cur = cur.parentNode)
|
||||
if (cur.scrollHeight > cur.clientHeight)
|
||||
return cur;
|
||||
}
|
||||
function getAllWrapping(node) {
|
||||
let res = [node.ownerDocument.defaultView || window];
|
||||
for (let cur = node.parentNode; cur; cur = cur.parentNode)
|
||||
res.push(cur);
|
||||
return res;
|
||||
}
|
||||
|
||||
export { Dropdown, DropdownSubmenu, MenuItem, blockTypeItem, icons, joinUpItem, liftItem, menuBar, redoItem, renderGrouped, selectParentNodeItem, undoItem, wrapItem };
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"name": "prosemirror-menu",
|
||||
"version": "1.2.5",
|
||||
"description": "Simple menu elements for ProseMirror",
|
||||
"type": "module",
|
||||
"main": "dist/index.cjs",
|
||||
"module": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/index.js",
|
||||
"require": "./dist/index.cjs"
|
||||
},
|
||||
"./style/menu.css": "./style/menu.css"
|
||||
},
|
||||
"sideEffects": ["./style/menu.css"],
|
||||
"style": "style/menu.css",
|
||||
"license": "MIT",
|
||||
"maintainers": [
|
||||
{
|
||||
"name": "Marijn Haverbeke",
|
||||
"email": "marijn@haverbeke.berlin",
|
||||
"web": "http://marijnhaverbeke.nl"
|
||||
}
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/prosemirror/prosemirror-menu.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"crelt": "^1.0.0",
|
||||
"prosemirror-state": "^1.0.0",
|
||||
"prosemirror-commands": "^1.0.0",
|
||||
"prosemirror-history": "^1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@prosemirror/buildhelper": "^0.1.5"
|
||||
},
|
||||
"scripts": {
|
||||
"prepare": "pm-buildhelper src/index.ts"
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
This module defines a number of building blocks for ProseMirror menus,
|
||||
along with a [menu bar](#menu.menuBar) implementation.
|
||||
|
||||
When using this module, you should make sure its
|
||||
[`style/menu.css`](https://github.com/ProseMirror/prosemirror-menu/blob/master/style/menu.css)
|
||||
file is loaded into your page.
|
||||
|
||||
@MenuElement
|
||||
@MenuItem
|
||||
@MenuItemSpec
|
||||
@IconSpec
|
||||
@Dropdown
|
||||
@DropdownSubmenu
|
||||
@menuBar
|
||||
|
||||
This module exports the following pre-built items or item
|
||||
constructors:
|
||||
|
||||
@joinUpItem
|
||||
@liftItem
|
||||
@selectParentNodeItem
|
||||
@undoItem
|
||||
@redoItem
|
||||
@wrapItem
|
||||
@blockTypeItem
|
||||
|
||||
To construct your own items, these icons may be useful:
|
||||
|
||||
@icons
|
||||
|
||||
@renderGrouped
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
const SVG = "http://www.w3.org/2000/svg"
|
||||
const XLINK = "http://www.w3.org/1999/xlink"
|
||||
|
||||
const prefix = "ProseMirror-icon"
|
||||
|
||||
function hashPath(path: string) {
|
||||
let hash = 0
|
||||
for (let i = 0; i < path.length; i++)
|
||||
hash = (((hash << 5) - hash) + path.charCodeAt(i)) | 0
|
||||
return hash
|
||||
}
|
||||
|
||||
export function getIcon(
|
||||
root: Document | ShadowRoot,
|
||||
icon: {path: string, width: number, height: number} | {text: string, css?: string} | {dom: Node}
|
||||
): HTMLElement {
|
||||
let doc = (root.nodeType == 9 ? root as Document : root.ownerDocument) || document
|
||||
let node = doc.createElement("div")
|
||||
node.className = prefix
|
||||
if ((icon as any).path) {
|
||||
let {path, width, height} = icon as {path: string, width: number, height: number}
|
||||
let name = "pm-icon-" + hashPath(path).toString(16)
|
||||
if (!doc.getElementById(name)) buildSVG(root, name, icon as {path: string, width: number, height: number})
|
||||
let svg = node.appendChild(doc.createElementNS(SVG, "svg"))
|
||||
svg.style.width = (width / height) + "em"
|
||||
let use = svg.appendChild(doc.createElementNS(SVG, "use"))
|
||||
use.setAttributeNS(XLINK, "href", /([^#]*)/.exec(doc.location.toString())![1] + "#" + name)
|
||||
} else if ((icon as any).dom) {
|
||||
node.appendChild((icon as any).dom.cloneNode(true))
|
||||
} else {
|
||||
let {text, css} = icon as {text: string, css?: string}
|
||||
node.appendChild(doc.createElement("span")).textContent = text || ''
|
||||
if (css) (node.firstChild as HTMLElement).style.cssText = css
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
function buildSVG(root: Document | ShadowRoot, name: string, data: {width: number, height: number, path: string}) {
|
||||
let [doc, top] = root.nodeType == 9 ? [root as Document, (root as Document).body] : [root.ownerDocument || document, root]
|
||||
let collection = doc.getElementById(prefix + "-collection") as Element
|
||||
if (!collection) {
|
||||
collection = doc.createElementNS(SVG, "svg")
|
||||
collection.id = prefix + "-collection"
|
||||
;(collection as HTMLElement).style.display = "none"
|
||||
top.insertBefore(collection, top.firstChild)
|
||||
}
|
||||
let sym = doc.createElementNS(SVG, "symbol")
|
||||
sym.id = name
|
||||
sym.setAttribute("viewBox", "0 0 " + data.width + " " + data.height)
|
||||
let path = sym.appendChild(doc.createElementNS(SVG, "path"))
|
||||
path.setAttribute("d", data.path)
|
||||
collection.appendChild(sym)
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
export {MenuElement, IconSpec, MenuItem, MenuItemSpec, Dropdown, DropdownSubmenu, renderGrouped,
|
||||
icons, joinUpItem, liftItem, selectParentNodeItem,
|
||||
undoItem, redoItem, wrapItem, blockTypeItem} from "./menu"
|
||||
export {menuBar} from "./menubar"
|
||||
+476
@@ -0,0 +1,476 @@
|
||||
import crel from "crelt"
|
||||
import {lift, joinUp, selectParentNode, wrapIn, setBlockType} from "prosemirror-commands"
|
||||
import {undo, redo} from "prosemirror-history"
|
||||
import {EditorView} from "prosemirror-view"
|
||||
import {EditorState, Transaction, NodeSelection} from "prosemirror-state"
|
||||
import {NodeType, Attrs} from "prosemirror-model"
|
||||
|
||||
import {getIcon} from "./icons"
|
||||
|
||||
/// The types defined in this module aren't the only thing you can
|
||||
/// display in your menu. Anything that conforms to this interface can
|
||||
/// be put into a menu structure.
|
||||
export interface MenuElement {
|
||||
/// Render the element for display in the menu. Must return a DOM
|
||||
/// element and a function that can be used to update the element to
|
||||
/// a new state. The `update` function must return false if the
|
||||
/// update hid the entire element.
|
||||
render(pm: EditorView): {dom: HTMLElement, update: (state: EditorState) => boolean}
|
||||
}
|
||||
|
||||
const prefix = "ProseMirror-menu"
|
||||
|
||||
/// An icon or label that, when clicked, executes a command.
|
||||
export class MenuItem implements MenuElement {
|
||||
/// Create a menu item.
|
||||
constructor(
|
||||
/// The spec used to create this item.
|
||||
readonly spec: MenuItemSpec
|
||||
) {}
|
||||
|
||||
/// Renders the icon according to its [display
|
||||
/// spec](#menu.MenuItemSpec.display), and adds an event handler which
|
||||
/// executes the command when the representation is clicked.
|
||||
render(view: EditorView) {
|
||||
let spec = this.spec
|
||||
let dom = spec.render ? spec.render(view)
|
||||
: spec.icon ? getIcon(view.root, spec.icon)
|
||||
: spec.label ? crel("div", null, translate(view, spec.label))
|
||||
: null
|
||||
if (!dom) throw new RangeError("MenuItem without icon or label property")
|
||||
if (spec.title) {
|
||||
const title = (typeof spec.title === "function" ? spec.title(view.state) : spec.title)
|
||||
;(dom as HTMLElement).setAttribute("title", translate(view, title))
|
||||
}
|
||||
if (spec.class) dom.classList.add(spec.class)
|
||||
if (spec.css) dom.style.cssText += spec.css
|
||||
|
||||
dom.addEventListener("mousedown", e => {
|
||||
e.preventDefault()
|
||||
if (!dom!.classList.contains(prefix + "-disabled"))
|
||||
spec.run(view.state, view.dispatch, view, e)
|
||||
})
|
||||
|
||||
function update(state: EditorState) {
|
||||
if (spec.select) {
|
||||
let selected = spec.select(state)
|
||||
dom!.style.display = selected ? "" : "none"
|
||||
if (!selected) return false
|
||||
}
|
||||
let enabled = true
|
||||
if (spec.enable) {
|
||||
enabled = spec.enable(state) || false
|
||||
setClass(dom!, prefix + "-disabled", !enabled)
|
||||
}
|
||||
if (spec.active) {
|
||||
let active = enabled && spec.active(state) || false
|
||||
setClass(dom!, prefix + "-active", active)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
return {dom, update}
|
||||
}
|
||||
}
|
||||
|
||||
function translate(view: EditorView, text: string): string {
|
||||
return (view as any)._props.translate ? (view as any)._props.translate(text) : text
|
||||
}
|
||||
|
||||
/// Specifies an icon. May be either an SVG icon, in which case its
|
||||
/// `path` property should be an [SVG path
|
||||
/// spec](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d),
|
||||
/// and `width` and `height` should provide the viewbox in which that
|
||||
/// path exists. Alternatively, it may have a `text` property
|
||||
/// specifying a string of text that makes up the icon, with an
|
||||
/// optional `css` property giving additional CSS styling for the
|
||||
/// text. _Or_ it may contain `dom` property containing a DOM node.
|
||||
export type IconSpec = {path: string, width: number, height: number} | {text: string, css?: string} | {dom: Node}
|
||||
|
||||
/// The configuration object passed to the `MenuItem` constructor.
|
||||
export interface MenuItemSpec {
|
||||
/// The function to execute when the menu item is activated.
|
||||
run: (state: EditorState, dispatch: (tr: Transaction) => void, view: EditorView, event: Event) => void
|
||||
|
||||
/// Optional function that is used to determine whether the item is
|
||||
/// appropriate at the moment. Deselected items will be hidden.
|
||||
select?: (state: EditorState) => boolean
|
||||
|
||||
/// Function that is used to determine if the item is enabled. If
|
||||
/// given and returning false, the item will be given a disabled
|
||||
/// styling.
|
||||
enable?: (state: EditorState) => boolean
|
||||
|
||||
/// A predicate function to determine whether the item is 'active' (for
|
||||
/// example, the item for toggling the strong mark might be active then
|
||||
/// the cursor is in strong text).
|
||||
active?: (state: EditorState) => boolean
|
||||
|
||||
/// A function that renders the item. You must provide either this,
|
||||
/// [`icon`](#menu.MenuItemSpec.icon), or [`label`](#MenuItemSpec.label).
|
||||
render?: (view: EditorView) => HTMLElement
|
||||
|
||||
/// Describes an icon to show for this item.
|
||||
icon?: IconSpec
|
||||
|
||||
/// Makes the item show up as a text label. Mostly useful for items
|
||||
/// wrapped in a [drop-down](#menu.Dropdown) or similar menu. The object
|
||||
/// should have a `label` property providing the text to display.
|
||||
label?: string
|
||||
|
||||
/// Defines DOM title (mouseover) text for the item.
|
||||
title?: string | ((state: EditorState) => string)
|
||||
|
||||
/// Optionally adds a CSS class to the item's DOM representation.
|
||||
class?: string
|
||||
|
||||
/// Optionally adds a string of inline CSS to the item's DOM
|
||||
/// representation.
|
||||
css?: string
|
||||
}
|
||||
|
||||
let lastMenuEvent: {time: number, node: null | Node} = {time: 0, node: null}
|
||||
function markMenuEvent(e: Event) {
|
||||
lastMenuEvent.time = Date.now()
|
||||
lastMenuEvent.node = e.target as Node
|
||||
}
|
||||
function isMenuEvent(wrapper: HTMLElement) {
|
||||
return Date.now() - 100 < lastMenuEvent.time &&
|
||||
lastMenuEvent.node && wrapper.contains(lastMenuEvent.node)
|
||||
}
|
||||
|
||||
/// A drop-down menu, displayed as a label with a downwards-pointing
|
||||
/// triangle to the right of it.
|
||||
export class Dropdown implements MenuElement {
|
||||
/// @internal
|
||||
content: readonly MenuElement[]
|
||||
|
||||
/// Create a dropdown wrapping the elements.
|
||||
constructor(
|
||||
content: readonly MenuElement[] | MenuElement,
|
||||
/// @internal
|
||||
readonly options: {
|
||||
/// The label to show on the drop-down control.
|
||||
label?: string
|
||||
|
||||
/// Sets the
|
||||
/// [`title`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/title)
|
||||
/// attribute given to the menu control.
|
||||
title?: string
|
||||
|
||||
/// When given, adds an extra CSS class to the menu control.
|
||||
class?: string
|
||||
|
||||
/// When given, adds an extra set of CSS styles to the menu control.
|
||||
css?: string
|
||||
} = {}) {
|
||||
this.options = options || {}
|
||||
this.content = Array.isArray(content) ? content : [content]
|
||||
}
|
||||
|
||||
/// Render the dropdown menu and sub-items.
|
||||
render(view: EditorView) {
|
||||
let content = renderDropdownItems(this.content, view)
|
||||
let win = view.dom.ownerDocument.defaultView || window
|
||||
|
||||
let label = crel("div", {class: prefix + "-dropdown " + (this.options.class || ""),
|
||||
style: this.options.css},
|
||||
translate(view, this.options.label || ""))
|
||||
if (this.options.title) label.setAttribute("title", translate(view, this.options.title))
|
||||
let wrap = crel("div", {class: prefix + "-dropdown-wrap"}, label)
|
||||
let open: {close: () => boolean, node: HTMLElement} | null = null
|
||||
let listeningOnClose: (() => void) | null = null
|
||||
let close = () => {
|
||||
if (open && open.close()) {
|
||||
open = null
|
||||
win.removeEventListener("mousedown", listeningOnClose!)
|
||||
}
|
||||
}
|
||||
label.addEventListener("mousedown", e => {
|
||||
e.preventDefault()
|
||||
markMenuEvent(e)
|
||||
if (open) {
|
||||
close()
|
||||
} else {
|
||||
open = this.expand(wrap, content.dom)
|
||||
win.addEventListener("mousedown", listeningOnClose = () => {
|
||||
if (!isMenuEvent(wrap)) close()
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
function update(state: EditorState) {
|
||||
let inner = content.update(state)
|
||||
wrap.style.display = inner ? "" : "none"
|
||||
return inner
|
||||
}
|
||||
|
||||
return {dom: wrap, update}
|
||||
}
|
||||
|
||||
/// @internal
|
||||
expand(dom: HTMLElement, items: readonly Node[]) {
|
||||
let menuDOM = crel("div", {class: prefix + "-dropdown-menu " + (this.options.class || "")}, items)
|
||||
|
||||
let done = false
|
||||
function close(): boolean {
|
||||
if (done) return false
|
||||
done = true
|
||||
dom.removeChild(menuDOM)
|
||||
return true
|
||||
}
|
||||
dom.appendChild(menuDOM)
|
||||
return {close, node: menuDOM}
|
||||
}
|
||||
}
|
||||
|
||||
function renderDropdownItems(items: readonly MenuElement[], view: EditorView) {
|
||||
let rendered = [], updates = []
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
let {dom, update} = items[i].render(view)
|
||||
rendered.push(crel("div", {class: prefix + "-dropdown-item"}, dom))
|
||||
updates.push(update)
|
||||
}
|
||||
return {dom: rendered, update: combineUpdates(updates, rendered)}
|
||||
}
|
||||
|
||||
function combineUpdates(
|
||||
updates: readonly ((state: EditorState) => boolean)[],
|
||||
nodes: readonly HTMLElement[]
|
||||
) {
|
||||
return (state: EditorState) => {
|
||||
let something = false
|
||||
for (let i = 0; i < updates.length; i++) {
|
||||
let up = updates[i](state)
|
||||
nodes[i].style.display = up ? "" : "none"
|
||||
if (up) something = true
|
||||
}
|
||||
return something
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a submenu wrapping a group of elements that start
|
||||
/// hidden and expand to the right when hovered over or tapped.
|
||||
export class DropdownSubmenu implements MenuElement {
|
||||
/// @internal
|
||||
content: readonly MenuElement[]
|
||||
|
||||
/// Creates a submenu for the given group of menu elements. The
|
||||
/// following options are recognized:
|
||||
constructor(
|
||||
content: readonly MenuElement[] | MenuElement,
|
||||
/// @internal
|
||||
readonly options: {
|
||||
/// The label to show on the submenu.
|
||||
label?: string
|
||||
} = {}
|
||||
) {
|
||||
this.content = Array.isArray(content) ? content : [content]
|
||||
}
|
||||
|
||||
/// Renders the submenu.
|
||||
render(view: EditorView) {
|
||||
let items = renderDropdownItems(this.content, view)
|
||||
let win = view.dom.ownerDocument.defaultView || window
|
||||
|
||||
let label = crel("div", {class: prefix + "-submenu-label"}, translate(view, this.options.label || ""))
|
||||
let wrap = crel("div", {class: prefix + "-submenu-wrap"}, label,
|
||||
crel("div", {class: prefix + "-submenu"}, items.dom))
|
||||
let listeningOnClose: (() => void) | null = null
|
||||
label.addEventListener("mousedown", e => {
|
||||
e.preventDefault()
|
||||
markMenuEvent(e)
|
||||
setClass(wrap, prefix + "-submenu-wrap-active", false)
|
||||
if (!listeningOnClose)
|
||||
win.addEventListener("mousedown", listeningOnClose = () => {
|
||||
if (!isMenuEvent(wrap)) {
|
||||
wrap.classList.remove(prefix + "-submenu-wrap-active")
|
||||
win.removeEventListener("mousedown", listeningOnClose!)
|
||||
listeningOnClose = null
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
function update(state: EditorState) {
|
||||
let inner = items.update(state)
|
||||
wrap.style.display = inner ? "" : "none"
|
||||
return inner
|
||||
}
|
||||
return {dom: wrap, update}
|
||||
}
|
||||
}
|
||||
|
||||
/// Render the given, possibly nested, array of menu elements into a
|
||||
/// document fragment, placing separators between them (and ensuring no
|
||||
/// superfluous separators appear when some of the groups turn out to
|
||||
/// be empty).
|
||||
export function renderGrouped(view: EditorView, content: readonly (readonly MenuElement[])[]) {
|
||||
let result = document.createDocumentFragment()
|
||||
let updates: ((state: EditorState) => boolean)[] = [], separators: HTMLElement[] = []
|
||||
for (let i = 0; i < content.length; i++) {
|
||||
let items = content[i], localUpdates = [], localNodes = []
|
||||
for (let j = 0; j < items.length; j++) {
|
||||
let {dom, update} = items[j].render(view)
|
||||
let span = crel("span", {class: prefix + "item"}, dom)
|
||||
result.appendChild(span)
|
||||
localNodes.push(span)
|
||||
localUpdates.push(update)
|
||||
}
|
||||
if (localUpdates.length) {
|
||||
updates.push(combineUpdates(localUpdates, localNodes))
|
||||
if (i < content.length - 1)
|
||||
separators.push(result.appendChild(separator()))
|
||||
}
|
||||
}
|
||||
|
||||
function update(state: EditorState) {
|
||||
let something = false, needSep = false
|
||||
for (let i = 0; i < updates.length; i++) {
|
||||
let hasContent = updates[i](state)
|
||||
if (i) separators[i - 1].style.display = needSep && hasContent ? "" : "none"
|
||||
needSep = hasContent
|
||||
if (hasContent) something = true
|
||||
}
|
||||
return something
|
||||
}
|
||||
return {dom: result, update}
|
||||
}
|
||||
|
||||
function separator() {
|
||||
return crel("span", {class: prefix + "separator"})
|
||||
}
|
||||
|
||||
/// A set of basic editor-related icons. Contains the properties
|
||||
/// `join`, `lift`, `selectParentNode`, `undo`, `redo`, `strong`, `em`,
|
||||
/// `code`, `link`, `bulletList`, `orderedList`, and `blockquote`, each
|
||||
/// holding an object that can be used as the `icon` option to
|
||||
/// `MenuItem`.
|
||||
export const icons: {[name: string]: IconSpec} = {
|
||||
join: {
|
||||
width: 800, height: 900,
|
||||
path: "M0 75h800v125h-800z M0 825h800v-125h-800z M250 400h100v-100h100v100h100v100h-100v100h-100v-100h-100z"
|
||||
},
|
||||
lift: {
|
||||
width: 1024, height: 1024,
|
||||
path: "M219 310v329q0 7-5 12t-12 5q-8 0-13-5l-164-164q-5-5-5-13t5-13l164-164q5-5 13-5 7 0 12 5t5 12zM1024 749v109q0 7-5 12t-12 5h-987q-7 0-12-5t-5-12v-109q0-7 5-12t12-5h987q7 0 12 5t5 12zM1024 530v109q0 7-5 12t-12 5h-621q-7 0-12-5t-5-12v-109q0-7 5-12t12-5h621q7 0 12 5t5 12zM1024 310v109q0 7-5 12t-12 5h-621q-7 0-12-5t-5-12v-109q0-7 5-12t12-5h621q7 0 12 5t5 12zM1024 91v109q0 7-5 12t-12 5h-987q-7 0-12-5t-5-12v-109q0-7 5-12t12-5h987q7 0 12 5t5 12z"
|
||||
},
|
||||
selectParentNode: {text: "\u2b1a", css: "font-weight: bold"},
|
||||
undo: {
|
||||
width: 1024, height: 1024,
|
||||
path: "M761 1024c113-206 132-520-313-509v253l-384-384 384-384v248c534-13 594 472 313 775z"
|
||||
},
|
||||
redo: {
|
||||
width: 1024, height: 1024,
|
||||
path: "M576 248v-248l384 384-384 384v-253c-446-10-427 303-313 509-280-303-221-789 313-775z"
|
||||
},
|
||||
strong: {
|
||||
width: 805, height: 1024,
|
||||
path: "M317 869q42 18 80 18 214 0 214-191 0-65-23-102-15-25-35-42t-38-26-46-14-48-6-54-1q-41 0-57 5 0 30-0 90t-0 90q0 4-0 38t-0 55 2 47 6 38zM309 442q24 4 62 4 46 0 81-7t62-25 42-51 14-81q0-40-16-70t-45-46-61-24-70-8q-28 0-74 7 0 28 2 86t2 86q0 15-0 45t-0 45q0 26 0 39zM0 950l1-53q8-2 48-9t60-15q4-6 7-15t4-19 3-18 1-21 0-19v-37q0-561-12-585-2-4-12-8t-25-6-28-4-27-2-17-1l-2-47q56-1 194-6t213-5q13 0 39 0t38 0q40 0 78 7t73 24 61 40 42 59 16 78q0 29-9 54t-22 41-36 32-41 25-48 22q88 20 146 76t58 141q0 57-20 102t-53 74-78 48-93 27-100 8q-25 0-75-1t-75-1q-60 0-175 6t-132 6z"
|
||||
},
|
||||
em: {
|
||||
width: 585, height: 1024,
|
||||
path: "M0 949l9-48q3-1 46-12t63-21q16-20 23-57 0-4 35-165t65-310 29-169v-14q-13-7-31-10t-39-4-33-3l10-58q18 1 68 3t85 4 68 1q27 0 56-1t69-4 56-3q-2 22-10 50-17 5-58 16t-62 19q-4 10-8 24t-5 22-4 26-3 24q-15 84-50 239t-44 203q-1 5-7 33t-11 51-9 47-3 32l0 10q9 2 105 17-1 25-9 56-6 0-18 0t-18 0q-16 0-49-5t-49-5q-78-1-117-1-29 0-81 5t-69 6z"
|
||||
},
|
||||
code: {
|
||||
width: 896, height: 1024,
|
||||
path: "M608 192l-96 96 224 224-224 224 96 96 288-320-288-320zM288 192l-288 320 288 320 96-96-224-224 224-224-96-96z"
|
||||
},
|
||||
link: {
|
||||
width: 951, height: 1024,
|
||||
path: "M832 694q0-22-16-38l-118-118q-16-16-38-16-24 0-41 18 1 1 10 10t12 12 8 10 7 14 2 15q0 22-16 38t-38 16q-8 0-15-2t-14-7-10-8-12-12-10-10q-18 17-18 41 0 22 16 38l117 118q15 15 38 15 22 0 38-14l84-83q16-16 16-38zM430 292q0-22-16-38l-117-118q-16-16-38-16-22 0-38 15l-84 83q-16 16-16 38 0 22 16 38l118 118q15 15 38 15 24 0 41-17-1-1-10-10t-12-12-8-10-7-14-2-15q0-22 16-38t38-16q8 0 15 2t14 7 10 8 12 12 10 10q18-17 18-41zM941 694q0 68-48 116l-84 83q-47 47-116 47-69 0-116-48l-117-118q-47-47-47-116 0-70 50-119l-50-50q-49 50-118 50-68 0-116-48l-118-118q-48-48-48-116t48-116l84-83q47-47 116-47 69 0 116 48l117 118q47 47 47 116 0 70-50 119l50 50q49-50 118-50 68 0 116 48l118 118q48 48 48 116z"
|
||||
},
|
||||
bulletList: {
|
||||
width: 768, height: 896,
|
||||
path: "M0 512h128v-128h-128v128zM0 256h128v-128h-128v128zM0 768h128v-128h-128v128zM256 512h512v-128h-512v128zM256 256h512v-128h-512v128zM256 768h512v-128h-512v128z"
|
||||
},
|
||||
orderedList: {
|
||||
width: 768, height: 896,
|
||||
path: "M320 512h448v-128h-448v128zM320 768h448v-128h-448v128zM320 128v128h448v-128h-448zM79 384h78v-256h-36l-85 23v50l43-2v185zM189 590c0-36-12-78-96-78-33 0-64 6-83 16l1 66c21-10 42-15 67-15s32 11 32 28c0 26-30 58-110 112v50h192v-67l-91 2c49-30 87-66 87-113l1-1z"
|
||||
},
|
||||
blockquote: {
|
||||
width: 640, height: 896,
|
||||
path: "M0 448v256h256v-256h-128c0 0 0-128 128-128v-128c0 0-256 0-256 256zM640 320v-128c0 0-256 0-256 256v256h256v-256h-128c0 0 0-128 128-128z"
|
||||
}
|
||||
}
|
||||
|
||||
/// Menu item for the `joinUp` command.
|
||||
export const joinUpItem = new MenuItem({
|
||||
title: "Join with above block",
|
||||
run: joinUp,
|
||||
select: state => joinUp(state),
|
||||
icon: icons.join
|
||||
})
|
||||
|
||||
/// Menu item for the `lift` command.
|
||||
export const liftItem = new MenuItem({
|
||||
title: "Lift out of enclosing block",
|
||||
run: lift,
|
||||
select: state => lift(state),
|
||||
icon: icons.lift
|
||||
})
|
||||
|
||||
/// Menu item for the `selectParentNode` command.
|
||||
export const selectParentNodeItem = new MenuItem({
|
||||
title: "Select parent node",
|
||||
run: selectParentNode,
|
||||
select: state => selectParentNode(state),
|
||||
icon: icons.selectParentNode
|
||||
})
|
||||
|
||||
/// Menu item for the `undo` command.
|
||||
export let undoItem = new MenuItem({
|
||||
title: "Undo last change",
|
||||
run: undo,
|
||||
enable: state => undo(state),
|
||||
icon: icons.undo
|
||||
})
|
||||
|
||||
/// Menu item for the `redo` command.
|
||||
export let redoItem = new MenuItem({
|
||||
title: "Redo last undone change",
|
||||
run: redo,
|
||||
enable: state => redo(state),
|
||||
icon: icons.redo
|
||||
})
|
||||
|
||||
/// Build a menu item for wrapping the selection in a given node type.
|
||||
/// Adds `run` and `select` properties to the ones present in
|
||||
/// `options`. `options.attrs` may be an object that provides
|
||||
/// attributes for the wrapping node.
|
||||
export function wrapItem(nodeType: NodeType, options: Partial<MenuItemSpec> & {attrs?: Attrs | null}) {
|
||||
let passedOptions: MenuItemSpec = {
|
||||
run(state, dispatch) {
|
||||
return wrapIn(nodeType, options.attrs)(state, dispatch)
|
||||
},
|
||||
select(state) {
|
||||
return wrapIn(nodeType, options.attrs)(state)
|
||||
}
|
||||
}
|
||||
for (let prop in options) (passedOptions as any)[prop] = (options as any)[prop]
|
||||
return new MenuItem(passedOptions)
|
||||
}
|
||||
|
||||
/// Build a menu item for changing the type of the textblock around the
|
||||
/// selection to the given type. Provides `run`, `active`, and `select`
|
||||
/// properties. Others must be given in `options`. `options.attrs` may
|
||||
/// be an object to provide the attributes for the textblock node.
|
||||
export function blockTypeItem(nodeType: NodeType, options: Partial<MenuItemSpec> & {attrs?: Attrs | null}) {
|
||||
let command = setBlockType(nodeType, options.attrs)
|
||||
let passedOptions: MenuItemSpec = {
|
||||
run: command,
|
||||
enable(state) { return command(state) },
|
||||
active(state) {
|
||||
let {$from, to, node} = state.selection as NodeSelection
|
||||
if (node) return node.hasMarkup(nodeType, options.attrs)
|
||||
return to <= $from.end() && $from.parent.hasMarkup(nodeType, options.attrs)
|
||||
}
|
||||
}
|
||||
for (let prop in options) (passedOptions as any)[prop] = (options as any)[prop]
|
||||
return new MenuItem(passedOptions)
|
||||
}
|
||||
|
||||
// Work around classList.toggle being broken in IE11
|
||||
function setClass(dom: HTMLElement, cls: string, on: boolean) {
|
||||
if (on) dom.classList.add(cls)
|
||||
else dom.classList.remove(cls)
|
||||
}
|
||||
+165
@@ -0,0 +1,165 @@
|
||||
import crel from "crelt"
|
||||
import {Plugin, EditorState} from "prosemirror-state"
|
||||
import {EditorView} from "prosemirror-view"
|
||||
|
||||
import {renderGrouped, MenuElement} from "./menu"
|
||||
|
||||
const prefix = "ProseMirror-menubar"
|
||||
|
||||
function isIOS() {
|
||||
if (typeof navigator == "undefined") return false
|
||||
let agent = navigator.userAgent
|
||||
return !/Edge\/\d/.test(agent) && /AppleWebKit/.test(agent) && /Mobile\/\w+/.test(agent)
|
||||
}
|
||||
|
||||
/// A plugin that will place a menu bar above the editor. Note that
|
||||
/// this involves wrapping the editor in an additional `<div>`.
|
||||
export function menuBar(options: {
|
||||
/// Provides the content of the menu, as a nested array to be
|
||||
/// passed to `renderGrouped`.
|
||||
content: readonly (readonly MenuElement[])[]
|
||||
|
||||
/// Determines whether the menu floats, i.e. whether it sticks to
|
||||
/// the top of the viewport when the editor is partially scrolled
|
||||
/// out of view.
|
||||
floating?: boolean
|
||||
}): Plugin {
|
||||
return new Plugin({
|
||||
view(editorView) { return new MenuBarView(editorView, options) }
|
||||
})
|
||||
}
|
||||
|
||||
class MenuBarView {
|
||||
wrapper: HTMLElement
|
||||
menu: HTMLElement
|
||||
spacer: HTMLElement | null = null
|
||||
maxHeight = 0
|
||||
widthForMaxHeight = 0
|
||||
floating = false
|
||||
contentUpdate: (state: EditorState) => boolean
|
||||
scrollHandler: ((event: Event) => void) | null = null
|
||||
root: Document | ShadowRoot
|
||||
|
||||
constructor(
|
||||
readonly editorView: EditorView,
|
||||
readonly options: Parameters<typeof menuBar>[0]
|
||||
) {
|
||||
this.root = editorView.root
|
||||
this.wrapper = crel("div", {class: prefix + "-wrapper"})
|
||||
this.menu = this.wrapper.appendChild(crel("div", {class: prefix}))
|
||||
this.menu.className = prefix
|
||||
|
||||
if (editorView.dom.parentNode)
|
||||
editorView.dom.parentNode.replaceChild(this.wrapper, editorView.dom)
|
||||
this.wrapper.appendChild(editorView.dom)
|
||||
|
||||
let {dom, update} = renderGrouped(this.editorView, this.options.content)
|
||||
this.contentUpdate = update
|
||||
this.menu.appendChild(dom)
|
||||
this.update()
|
||||
|
||||
if (options.floating && !isIOS()) {
|
||||
this.updateFloat()
|
||||
let potentialScrollers = getAllWrapping(this.wrapper)
|
||||
this.scrollHandler = (e: Event) => {
|
||||
let root = this.editorView.root
|
||||
if (!((root as Document).body || root).contains(this.wrapper))
|
||||
potentialScrollers.forEach(el => el.removeEventListener("scroll", this.scrollHandler!))
|
||||
else
|
||||
this.updateFloat((e.target as HTMLElement).getBoundingClientRect ? e.target as HTMLElement : undefined)
|
||||
}
|
||||
potentialScrollers.forEach(el => el.addEventListener('scroll', this.scrollHandler!))
|
||||
}
|
||||
}
|
||||
|
||||
update() {
|
||||
if (this.editorView.root != this.root) {
|
||||
let {dom, update} = renderGrouped(this.editorView, this.options.content)
|
||||
this.contentUpdate = update
|
||||
this.menu.replaceChild(dom, this.menu.firstChild!)
|
||||
this.root = this.editorView.root
|
||||
}
|
||||
this.contentUpdate(this.editorView.state)
|
||||
|
||||
if (this.floating) {
|
||||
this.updateScrollCursor()
|
||||
} else {
|
||||
if (this.menu.offsetWidth != this.widthForMaxHeight) {
|
||||
this.widthForMaxHeight = this.menu.offsetWidth
|
||||
this.maxHeight = 0
|
||||
}
|
||||
if (this.menu.offsetHeight > this.maxHeight) {
|
||||
this.maxHeight = this.menu.offsetHeight
|
||||
this.menu.style.minHeight = this.maxHeight + "px"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateScrollCursor() {
|
||||
let selection = (this.editorView.root as Document).getSelection()!
|
||||
if (!selection.focusNode) return
|
||||
let rects = selection.getRangeAt(0).getClientRects()
|
||||
let selRect = rects[selectionIsInverted(selection) ? 0 : rects.length - 1]
|
||||
if (!selRect) return
|
||||
let menuRect = this.menu.getBoundingClientRect()
|
||||
if (selRect.top < menuRect.bottom && selRect.bottom > menuRect.top) {
|
||||
let scrollable = findWrappingScrollable(this.wrapper)
|
||||
if (scrollable) scrollable.scrollTop -= (menuRect.bottom - selRect.top)
|
||||
}
|
||||
}
|
||||
|
||||
updateFloat(scrollAncestor?: HTMLElement) {
|
||||
let parent = this.wrapper, editorRect = parent.getBoundingClientRect(),
|
||||
top = scrollAncestor ? Math.max(0, scrollAncestor.getBoundingClientRect().top) : 0
|
||||
|
||||
if (this.floating) {
|
||||
if (editorRect.top >= top || editorRect.bottom < this.menu.offsetHeight + 10) {
|
||||
this.floating = false
|
||||
this.menu.style.position = this.menu.style.left = this.menu.style.top = this.menu.style.width = ""
|
||||
this.menu.style.display = ""
|
||||
this.spacer!.parentNode!.removeChild(this.spacer!)
|
||||
this.spacer = null
|
||||
} else {
|
||||
let border = (parent.offsetWidth - parent.clientWidth) / 2
|
||||
this.menu.style.left = (editorRect.left + border) + "px"
|
||||
this.menu.style.display = editorRect.top > (this.editorView.dom.ownerDocument.defaultView || window).innerHeight
|
||||
? "none" : ""
|
||||
if (scrollAncestor) this.menu.style.top = top + "px"
|
||||
}
|
||||
} else {
|
||||
if (editorRect.top < top && editorRect.bottom >= this.menu.offsetHeight + 10) {
|
||||
this.floating = true
|
||||
let menuRect = this.menu.getBoundingClientRect()
|
||||
this.menu.style.left = menuRect.left + "px"
|
||||
this.menu.style.width = menuRect.width + "px"
|
||||
if (scrollAncestor) this.menu.style.top = top + "px"
|
||||
this.menu.style.position = "fixed"
|
||||
this.spacer = crel("div", {class: prefix + "-spacer", style: `height: ${menuRect.height}px`})
|
||||
parent.insertBefore(this.spacer, this.menu)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (this.wrapper.parentNode)
|
||||
this.wrapper.parentNode.replaceChild(this.editorView.dom, this.wrapper)
|
||||
}
|
||||
}
|
||||
|
||||
// Not precise, but close enough
|
||||
function selectionIsInverted(selection: Selection) {
|
||||
if (selection.anchorNode == selection.focusNode) return selection.anchorOffset > selection.focusOffset
|
||||
return selection.anchorNode!.compareDocumentPosition(selection.focusNode!) == Node.DOCUMENT_POSITION_FOLLOWING
|
||||
}
|
||||
|
||||
function findWrappingScrollable(node: Node) {
|
||||
for (let cur = node.parentNode; cur; cur = cur.parentNode)
|
||||
if ((cur as HTMLElement).scrollHeight > (cur as HTMLElement).clientHeight) return cur as HTMLElement
|
||||
}
|
||||
|
||||
function getAllWrapping(node: Node) {
|
||||
let res: (Node | Window)[] = [node.ownerDocument!.defaultView || window]
|
||||
for (let cur = node.parentNode; cur; cur = cur.parentNode)
|
||||
res.push(cur)
|
||||
return res
|
||||
}
|
||||
+148
@@ -0,0 +1,148 @@
|
||||
.ProseMirror-textblock-dropdown {
|
||||
min-width: 3em;
|
||||
}
|
||||
|
||||
.ProseMirror-menu {
|
||||
margin: 0 -4px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.ProseMirror-tooltip .ProseMirror-menu {
|
||||
width: -webkit-fit-content;
|
||||
width: fit-content;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.ProseMirror-menuitem {
|
||||
margin-right: 3px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.ProseMirror-menuseparator {
|
||||
border-right: 1px solid #ddd;
|
||||
margin-right: 3px;
|
||||
}
|
||||
|
||||
.ProseMirror-menu-dropdown, .ProseMirror-menu-dropdown-menu {
|
||||
font-size: 90%;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.ProseMirror-menu-dropdown {
|
||||
vertical-align: 1px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
padding-right: 15px;
|
||||
}
|
||||
|
||||
.ProseMirror-menu-dropdown-wrap {
|
||||
padding: 1px 0 1px 4px;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.ProseMirror-menu-dropdown:after {
|
||||
content: "";
|
||||
border-left: 4px solid transparent;
|
||||
border-right: 4px solid transparent;
|
||||
border-top: 4px solid currentColor;
|
||||
opacity: .6;
|
||||
position: absolute;
|
||||
right: 4px;
|
||||
top: calc(50% - 2px);
|
||||
}
|
||||
|
||||
.ProseMirror-menu-dropdown-menu, .ProseMirror-menu-submenu {
|
||||
position: absolute;
|
||||
background: white;
|
||||
color: #666;
|
||||
border: 1px solid #aaa;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.ProseMirror-menu-dropdown-menu {
|
||||
z-index: 15;
|
||||
min-width: 6em;
|
||||
}
|
||||
|
||||
.ProseMirror-menu-dropdown-item {
|
||||
cursor: pointer;
|
||||
padding: 2px 8px 2px 4px;
|
||||
}
|
||||
|
||||
.ProseMirror-menu-dropdown-item:hover {
|
||||
background: #f2f2f2;
|
||||
}
|
||||
|
||||
.ProseMirror-menu-submenu-wrap {
|
||||
position: relative;
|
||||
margin-right: -4px;
|
||||
}
|
||||
|
||||
.ProseMirror-menu-submenu-label:after {
|
||||
content: "";
|
||||
border-top: 4px solid transparent;
|
||||
border-bottom: 4px solid transparent;
|
||||
border-left: 4px solid currentColor;
|
||||
opacity: .6;
|
||||
position: absolute;
|
||||
right: 4px;
|
||||
top: calc(50% - 4px);
|
||||
}
|
||||
|
||||
.ProseMirror-menu-submenu {
|
||||
display: none;
|
||||
min-width: 4em;
|
||||
left: 100%;
|
||||
top: -3px;
|
||||
}
|
||||
|
||||
.ProseMirror-menu-active {
|
||||
background: #eee;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.ProseMirror-menu-disabled {
|
||||
opacity: .3;
|
||||
}
|
||||
|
||||
.ProseMirror-menu-submenu-wrap:hover .ProseMirror-menu-submenu, .ProseMirror-menu-submenu-wrap-active .ProseMirror-menu-submenu {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.ProseMirror-menubar {
|
||||
border-top-left-radius: inherit;
|
||||
border-top-right-radius: inherit;
|
||||
position: relative;
|
||||
min-height: 1em;
|
||||
color: #666;
|
||||
padding: 1px 6px;
|
||||
top: 0; left: 0; right: 0;
|
||||
border-bottom: 1px solid silver;
|
||||
background: white;
|
||||
z-index: 10;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.ProseMirror-icon {
|
||||
display: inline-block;
|
||||
line-height: .8;
|
||||
vertical-align: -2px; /* Compensate for padding */
|
||||
padding: 2px 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ProseMirror-menu-disabled.ProseMirror-icon {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.ProseMirror-icon svg {
|
||||
fill: currentColor;
|
||||
height: 1em;
|
||||
}
|
||||
|
||||
.ProseMirror-icon span {
|
||||
vertical-align: text-top;
|
||||
}
|
||||
Reference in New Issue
Block a user