mirror of
https://github.com/BookStackApp/BookStack.git
synced 2025-07-21 09:22:09 +03:00
Added source code view/set button
This commit is contained in:
1
TODO
1
TODO
@ -20,7 +20,6 @@
|
|||||||
- Code blocks
|
- Code blocks
|
||||||
- Indents
|
- Indents
|
||||||
- Iframe/Media
|
- Iframe/Media
|
||||||
- View Code
|
|
||||||
- Attachment integration (Drag & drop)
|
- Attachment integration (Drag & drop)
|
||||||
- Template system integration.
|
- Template system integration.
|
||||||
|
|
||||||
|
@ -2,11 +2,12 @@ import {EditorState} from "prosemirror-state";
|
|||||||
import {EditorView} from "prosemirror-view";
|
import {EditorView} from "prosemirror-view";
|
||||||
import {exampleSetup} from "prosemirror-example-setup";
|
import {exampleSetup} from "prosemirror-example-setup";
|
||||||
|
|
||||||
import {DOMParser, DOMSerializer} from "prosemirror-model";
|
import {DOMParser} from "prosemirror-model";
|
||||||
|
|
||||||
import schema from "./schema";
|
import schema from "./schema";
|
||||||
import menu from "./menu";
|
import menu from "./menu";
|
||||||
import nodeViews from "./node-views";
|
import nodeViews from "./node-views";
|
||||||
|
import {stateToHtml} from "./util";
|
||||||
|
|
||||||
class ProseMirrorView {
|
class ProseMirrorView {
|
||||||
constructor(target, content) {
|
constructor(target, content) {
|
||||||
@ -28,13 +29,16 @@ class ProseMirrorView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get content() {
|
get content() {
|
||||||
const fragment = DOMSerializer.fromSchema(schema).serializeFragment(this.view.state.doc.content);
|
return stateToHtml(this.view.state);
|
||||||
const renderDoc = document.implementation.createHTMLDocument();
|
}
|
||||||
renderDoc.body.appendChild(fragment);
|
|
||||||
return renderDoc.body.innerHTML;
|
focus() {
|
||||||
|
this.view.focus()
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
this.view.destroy()
|
||||||
}
|
}
|
||||||
focus() { this.view.focus() }
|
|
||||||
destroy() { this.view.destroy() }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ProseMirrorView;
|
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: {
|
close: {
|
||||||
width: 24, height: 24,
|
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",
|
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 DialogInput from "./DialogInput";
|
||||||
|
|
||||||
import itemAnchorButtonItem from "./item-anchor-button";
|
import itemAnchorButtonItem from "./item-anchor-button";
|
||||||
|
import itemHtmlSourceButton from "./item-html-source-button";
|
||||||
|
|
||||||
|
|
||||||
function cmdItem(cmd, options) {
|
function cmdItem(cmd, options) {
|
||||||
@ -156,6 +157,7 @@ const inserts = [
|
|||||||
title: "Horizontal Rule",
|
title: "Horizontal Rule",
|
||||||
icon: icons.horizontal_rule,
|
icon: icons.horizontal_rule,
|
||||||
}),
|
}),
|
||||||
|
itemHtmlSourceButton(),
|
||||||
];
|
];
|
||||||
|
|
||||||
const utilities = [
|
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) {
|
function applyLink(formData, state, dispatch) {
|
||||||
const selection = state.selection;
|
const selection = state.selection;
|
||||||
const attrs = Object.fromEntries(formData);
|
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) {
|
removeHandlesListener(event) {
|
||||||
console.log(this.dom.contains(event.target), event.target);
|
|
||||||
if (!this.dom.contains(event.target)) {
|
if (!this.dom.contains(event.target)) {
|
||||||
this.removeHandles();
|
this.removeHandles();
|
||||||
this.handles = [];
|
this.handles = [];
|
||||||
|
@ -22,6 +22,17 @@ export function docToHtml(doc) {
|
|||||||
return renderDoc.body.innerHTML;
|
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
|
* @class KeyedMultiStack
|
||||||
* Holds many stacks, seperated via a key, with a simple
|
* 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 {
|
.ProseMirror-imagewrap {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
line-height: 0;
|
line-height: 0;
|
||||||
|
Reference in New Issue
Block a user