- Code blocks
- Indents
- Iframe/Media
-- View Code
- Attachment integration (Drag & drop)
- Template system integration.
import {EditorView} from "prosemirror-view";
import {exampleSetup} from "prosemirror-example-setup";
-import {DOMParser, DOMSerializer} from "prosemirror-model";
+import {DOMParser} from "prosemirror-model";
import schema from "./schema";
import menu from "./menu";
import nodeViews from "./node-views";
+import {stateToHtml} from "./util";
class ProseMirrorView {
constructor(target, content) {
}
get content() {
- const fragment = DOMSerializer.fromSchema(schema).serializeFragment(this.view.state.doc.content);
- const renderDoc = document.implementation.createHTMLDocument();
- renderDoc.body.appendChild(fragment);
- return renderDoc.body.innerHTML;
+ return stateToHtml(this.view.state);
+ }
+
+ focus() {
+ this.view.focus()
+ }
+
+ destroy() {
+ this.view.destroy()
}
- focus() { this.view.focus() }
- destroy() { this.view.destroy() }
}
export default ProseMirrorView;
\ No newline at end of file
--- /dev/null
+// ::- Represents a submenu wrapping a group of elements that start
+// hidden and expand to the right when hovered over or tapped.
+import {prefix, randHtmlId} from "./menu-utils";
+import crel from "crelt";
+
+class DialogTextArea {
+ // :: (?Object)
+ // The following options are recognized:
+ //
+ // **`label`**`: string`
+ // : The label to show for the input.
+ // **`id`**`: string`
+ // : The id to use for this input
+ // **`attrs`**`: Object`
+ // : The attributes to add to the input element.
+ // **`value`**`: function(state) -> string`
+ // : The getter for the input value.
+ constructor(options) {
+ this.options = options || {};
+ }
+
+ // :: (EditorView) → {dom: dom.Node, update: (EditorState) → bool}
+ // Renders the submenu.
+ render(view) {
+ const id = randHtmlId();
+ const inputAttrs = Object.assign({type: "text", name: this.options.id, id: this.options.id}, this.options.attrs || {})
+ const input = crel("textarea", inputAttrs);
+ const label = this.options.label ? crel("label", {for: id}, this.options.label) : null;
+
+ const rowRap = crel("div", {class: prefix + '-dialog-textarea-wrap'}, label, input);
+
+ const update = (state) => {
+ input.value = this.options.value(state);
+ return true;
+ }
+
+ return {dom: rowRap, update}
+ }
+
+}
+
+export default DialogTextArea;
\ No newline at end of file
close: {
width: 24, height: 24,
path: "M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z",
+ },
+ source_code: {
+ width: 24, height: 24,
+ path: "M9.4 16.6L4.8 12l4.6-4.6L8 6l-6 6 6 6 1.4-1.4zm5.2 0l4.6-4.6-4.6-4.6L16 6l6 6-6 6-1.4-1.4z",
}
};
import DialogInput from "./DialogInput";
import itemAnchorButtonItem from "./item-anchor-button";
+import itemHtmlSourceButton from "./item-html-source-button";
function cmdItem(cmd, options) {
title: "Horizontal Rule",
icon: icons.horizontal_rule,
}),
+ itemHtmlSourceButton(),
];
const utilities = [
});
}
+/**
+ * @param {FormData} formData
+ * @param {PmEditorState} state
+ * @param {PmDispatchFunction} dispatch
+ * @return {boolean}
+ */
function applyLink(formData, state, dispatch) {
const selection = state.selection;
const attrs = Object.fromEntries(formData);
--- /dev/null
+import DialogBox from "./DialogBox";
+import DialogForm from "./DialogForm";
+import DialogTextArea from "./DialogTextArea";
+
+import {MenuItem} from "./menu";
+import {icons} from "./icons";
+import {htmlToDoc, stateToHtml} from "../util";
+
+/**
+ * @param {(function(FormData))} submitter
+ * @param {Function} closer
+ * @return {DialogBox}
+ */
+function getLinkDialog(submitter, closer) {
+ return new DialogBox([
+ new DialogForm([
+ new DialogTextArea({
+ id: 'source',
+ value: stateToHtml,
+ attrs: {
+ rows: 10,
+ cols: 50,
+ }
+ }),
+ ], {
+ canceler: closer,
+ action: submitter,
+ }),
+ ], {
+ label: 'View/Edit HTML Source',
+ closer: closer,
+ });
+}
+
+/**
+ * @param {FormData} formData
+ * @param {PmEditorState} state
+ * @param {PmDispatchFunction} dispatch
+ * @return {boolean}
+ */
+function replaceEditorHtml(formData, state, dispatch) {
+ const html = formData.get('source');
+
+ if (dispatch) {
+ const tr = state.tr;
+
+ const newDoc = htmlToDoc(html);
+ tr.replaceWith(0, state.doc.content.size, newDoc.content);
+ dispatch(tr);
+ }
+
+ return true;
+}
+
+
+/**
+ * @param {PmEditorState} state
+ * @param {PmDispatchFunction} dispatch
+ * @param {PmView} view
+ * @param {Event} e
+ */
+function onPress(state, dispatch, view, e) {
+ const dialog = getLinkDialog((data) => {
+ replaceEditorHtml(data, state, dispatch);
+ dom.remove();
+ }, () => {
+ dom.remove();
+ })
+
+ const {dom, update} = dialog.render(view);
+ update(state);
+ document.body.appendChild(dom);
+}
+
+/**
+ * @return {MenuItem}
+ */
+function htmlSourceButtonItem() {
+ return new MenuItem({
+ title: "View HTML Source",
+ run: onPress,
+ enable: state => true,
+ icon: icons.source_code,
+ });
+}
+
+export default htmlSourceButtonItem;
\ No newline at end of file
}
removeHandlesListener(event) {
- console.log(this.dom.contains(event.target), event.target);
if (!this.dom.contains(event.target)) {
this.removeHandles();
this.handles = [];
return renderDoc.body.innerHTML;
}
+/**
+ * @param {PmEditorState} state
+ * @return {String}
+ */
+export function stateToHtml(state) {
+ const fragment = DOMSerializer.fromSchema(schema).serializeFragment(state.doc.content);
+ const renderDoc = document.implementation.createHTMLDocument();
+ renderDoc.body.appendChild(fragment);
+ return renderDoc.body.innerHTML;
+}
+
/**
* @class KeyedMultiStack
* Holds many stacks, seperated via a key, with a simple
}
}
+.ProseMirror-menu-dialog-textarea-wrap {
+ padding: $-xs $-s;
+ label {
+ padding: 0 $-s;
+ font-size: .9rem;
+ }
+ textarea {
+ width: 100%;
+ font-size: 0.8rem;
+ }
+}
+
.ProseMirror-imagewrap {
display: inline-block;
line-height: 0;