Added source code view/set button
This commit is contained in:
parent
7125530e55
commit
b2283106fc
10 changed files with 175 additions and 9 deletions
1
TODO
1
TODO
|
@ -20,7 +20,6 @@
|
|||
- Code blocks
|
||||
- Indents
|
||||
- Iframe/Media
|
||||
- View Code
|
||||
- Attachment integration (Drag & drop)
|
||||
- Template system integration.
|
||||
|
||||
|
|
|
@ -2,11 +2,12 @@ import {EditorState} from "prosemirror-state";
|
|||
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) {
|
||||
|
@ -28,13 +29,16 @@ class ProseMirrorView {
|
|||
}
|
||||
|
||||
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;
|
42
resources/js/editor/menu/DialogTextArea.js
Normal file
42
resources/js/editor/menu/DialogTextArea.js
Normal file
|
@ -0,0 +1,42 @@
|
|||
// ::- 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;
|
|
@ -95,6 +95,10 @@ export const icons = {
|
|||
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",
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ import DialogForm from "./DialogForm";
|
|||
import DialogInput from "./DialogInput";
|
||||
|
||||
import itemAnchorButtonItem from "./item-anchor-button";
|
||||
import itemHtmlSourceButton from "./item-html-source-button";
|
||||
|
||||
|
||||
function cmdItem(cmd, options) {
|
||||
|
@ -156,6 +157,7 @@ const inserts = [
|
|||
title: "Horizontal Rule",
|
||||
icon: icons.horizontal_rule,
|
||||
}),
|
||||
itemHtmlSourceButton(),
|
||||
];
|
||||
|
||||
const utilities = [
|
||||
|
|
|
@ -57,6 +57,12 @@ function getLinkDialog(submitter, closer) {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @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);
|
||||
|
|
87
resources/js/editor/menu/item-html-source-button.js
Normal file
87
resources/js/editor/menu/item-html-source-button.js
Normal file
|
@ -0,0 +1,87 @@
|
|||
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;
|
|
@ -65,7 +65,6 @@ class ImageView {
|
|||
}
|
||||
|
||||
removeHandlesListener(event) {
|
||||
console.log(this.dom.contains(event.target), event.target);
|
||||
if (!this.dom.contains(event.target)) {
|
||||
this.removeHandles();
|
||||
this.handles = [];
|
||||
|
|
|
@ -22,6 +22,17 @@ export function docToHtml(doc) {
|
|||
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
|
||||
|
|
|
@ -454,6 +454,18 @@ img.ProseMirror-separator {
|
|||
}
|
||||
}
|
||||
|
||||
.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;
|
||||
|
|
Loading…
Reference in a new issue