Got link insert/editor working
This commit is contained in:
parent
7703face52
commit
89194a3f85
6 changed files with 107 additions and 66 deletions
12
TODO
12
TODO
|
@ -1,15 +1,8 @@
|
|||
### In-Progress
|
||||
|
||||
- Modal Dialogs for details such as links
|
||||
- Got dialog + form + input ready
|
||||
- Next stage is creating a button (eg, anchor insert) which toggles/shows dialog box.
|
||||
- Dialog box should attach at bottom of dom (Prevent z-index issues).
|
||||
- At some level a layer is needed to wire up the existing components.
|
||||
|
||||
### Features
|
||||
|
||||
- Tables
|
||||
- Links
|
||||
- Images
|
||||
- Image Resizing in editor
|
||||
- Drawings
|
||||
|
@ -33,4 +26,7 @@
|
|||
- Color picker buttons should be split, with button to re-apply last selected color.
|
||||
- Color picker options should change color if different instead of remove.
|
||||
- Clear formatting, If no selection range, clear the formatting of parent block.
|
||||
- If no marks, clear the block type if text type?
|
||||
- If no marks, clear the block type if text type?
|
||||
- Remove links button? (Action already in place if link href is empty).
|
||||
- Links - Limit target attribute options and validate URL.
|
||||
- Links - Integrate entity picker.
|
|
@ -53,10 +53,6 @@ class DialogBox {
|
|||
if (this.options.closer) {
|
||||
this.options.closer();
|
||||
}
|
||||
|
||||
if (this.closeMouseDownListener) {
|
||||
this.wrap.removeEventListener("click", this.closeMouseDownListener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,15 +12,6 @@
|
|||
// holding an object that can be used as the `icon` option to
|
||||
// `MenuItem`.
|
||||
export 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: 24, height: 24,
|
||||
path: "M12.5 8c-2.65 0-5.05.99-6.9 2.6L2 7v9h9l-3.62-3.62c1.39-1.16 3.16-1.88 5.12-1.88 3.54 0 6.55 2.31 7.6 5.5l2.37-.78C21.08 11.03 17.15 8 12.5 8z"
|
||||
|
@ -37,13 +28,9 @@ export const icons = {
|
|||
width: 24, height: 24,
|
||||
path: "M10 4v3h2.21l-3.42 8H6v3h8v-3h-2.21l3.42-8H18V4z"
|
||||
},
|
||||
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"
|
||||
width: 24, height: 24,
|
||||
path: "M3.9 12c0-1.71 1.39-3.1 3.1-3.1h4V7H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-1.9H7c-1.71 0-3.1-1.39-3.1-3.1zM8 13h8v-2H8v2zm9-6h-4v1.9h4c1.71 0 3.1 1.39 3.1 3.1s-1.39 3.1-3.1 3.1h-4V17h4c2.76 0 5-2.24 5-5s-2.24-5-5-5z"
|
||||
},
|
||||
bullet_list: {
|
||||
width: 24, height: 24,
|
||||
|
@ -57,10 +44,6 @@ export const icons = {
|
|||
width: 24, height: 24,
|
||||
path: "M22,7h-9v2h9V7z M22,15h-9v2h9V15z M5.54,11L2,7.46l1.41-1.41l2.12,2.12l4.24-4.24l1.41,1.41L5.54,11z M5.54,19L2,15.46 l1.41-1.41l2.12,2.12l4.24-4.24l1.41,1.41L5.54,19z"
|
||||
},
|
||||
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"
|
||||
},
|
||||
underline: {
|
||||
width: 24, height: 24,
|
||||
path: "M12 17c3.31 0 6-2.69 6-6V3h-2.5v8c0 1.93-1.57 3.5-3.5 3.5S8.5 12.93 8.5 11V3H6v8c0 3.31 2.69 6 6 6zm-7 2v2h14v-2H5z"
|
||||
|
|
|
@ -12,6 +12,8 @@ import {removeMarks} from "../commands";
|
|||
import DialogForm from "./DialogForm";
|
||||
import DialogInput from "./DialogInput";
|
||||
|
||||
import itemAnchorButtonItem from "./item-anchor-button";
|
||||
|
||||
|
||||
function cmdItem(cmd, options) {
|
||||
const passedOptions = {
|
||||
|
@ -149,6 +151,7 @@ const lists = [
|
|||
];
|
||||
|
||||
const inserts = [
|
||||
itemAnchorButtonItem(),
|
||||
insertBlockBeforeItem(schema.nodes.horizontal_rule, {
|
||||
title: "Horizontal Rule",
|
||||
icon: icons.horizontal_rule,
|
||||
|
@ -164,37 +167,6 @@ const utilities = [
|
|||
}),
|
||||
];
|
||||
|
||||
function getMarkAttribute(markType, attribute) {
|
||||
return function(state) {
|
||||
const marks = state.selection.$head.marks();
|
||||
for (const mark of marks) {
|
||||
if (mark.type === markType) {
|
||||
return mark.attrs[attribute];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
let box = new DialogBox([
|
||||
new DialogForm([
|
||||
new DialogInput({
|
||||
label: 'URL',
|
||||
id: 'url',
|
||||
value: getMarkAttribute(schema.marks.link, 'href'),
|
||||
}),
|
||||
new DialogInput({
|
||||
label: 'Title',
|
||||
id: 'title',
|
||||
value: getMarkAttribute(schema.marks.link, 'title'),
|
||||
})
|
||||
], {
|
||||
canceler: () => box.close(),
|
||||
action: (data) => console.log('submit', data),
|
||||
}),
|
||||
], {label: 'Insert Link', closer: () => {console.log('close')}});
|
||||
|
||||
const menu = menuBar({
|
||||
floating: false,
|
||||
content: [
|
||||
|
@ -206,7 +178,6 @@ const menu = menuBar({
|
|||
lists,
|
||||
inserts,
|
||||
utilities,
|
||||
[box]
|
||||
],
|
||||
});
|
||||
|
||||
|
|
89
resources/js/editor/menu/item-anchor-button.js
Normal file
89
resources/js/editor/menu/item-anchor-button.js
Normal file
|
@ -0,0 +1,89 @@
|
|||
import DialogBox from "./DialogBox";
|
||||
import DialogForm from "./DialogForm";
|
||||
import DialogInput from "./DialogInput";
|
||||
import schema from "../schema";
|
||||
|
||||
import {MenuItem} from "./menu";
|
||||
import {icons} from "./icons";
|
||||
|
||||
|
||||
function getMarkAttribute(markType, attribute) {
|
||||
return function (state) {
|
||||
const marks = state.selection.$head.marks();
|
||||
for (const mark of marks) {
|
||||
if (mark.type === markType) {
|
||||
return mark.attrs[attribute];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
function getLinkDialog(submitter, closer) {
|
||||
return new DialogBox([
|
||||
new DialogForm([
|
||||
new DialogInput({
|
||||
label: 'URL',
|
||||
id: 'href',
|
||||
value: getMarkAttribute(schema.marks.link, 'href'),
|
||||
}),
|
||||
new DialogInput({
|
||||
label: 'Title',
|
||||
id: 'title',
|
||||
value: getMarkAttribute(schema.marks.link, 'title'),
|
||||
}),
|
||||
new DialogInput({
|
||||
label: 'Target',
|
||||
id: 'target',
|
||||
value: getMarkAttribute(schema.marks.link, 'target'),
|
||||
})
|
||||
], {
|
||||
canceler: closer,
|
||||
action: submitter,
|
||||
}),
|
||||
], {
|
||||
label: 'Insert Link',
|
||||
closer: closer,
|
||||
});
|
||||
}
|
||||
|
||||
function applyLink(formData, state, dispatch) {
|
||||
const selection = state.selection;
|
||||
const attrs = Object.fromEntries(formData);
|
||||
if (dispatch) {
|
||||
const tr = state.tr;
|
||||
|
||||
if (attrs.href) {
|
||||
tr.addMark(selection.from, selection.to, schema.marks.link.create(attrs));
|
||||
} else {
|
||||
tr.removeMark(selection.from, selection.to, schema.marks.link);
|
||||
}
|
||||
dispatch(tr);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function onPress(state, dispatch, view, e) {
|
||||
const dialog = getLinkDialog((data) => {
|
||||
applyLink(data, state, dispatch);
|
||||
dom.remove();
|
||||
}, () => {
|
||||
dom.remove();
|
||||
})
|
||||
|
||||
const {dom, update} = dialog.render(view);
|
||||
update(state);
|
||||
document.body.appendChild(dom);
|
||||
}
|
||||
|
||||
function anchorButtonItem() {
|
||||
return new MenuItem({
|
||||
title: "Insert/Edit Anchor Link",
|
||||
run: onPress,
|
||||
enable: state => true,
|
||||
icon: icons.link,
|
||||
});
|
||||
}
|
||||
|
||||
export default anchorButtonItem;
|
|
@ -1,19 +1,25 @@
|
|||
const link = {
|
||||
attrs: {
|
||||
href: {},
|
||||
title: {default: null}
|
||||
title: {default: null},
|
||||
target: {default: null}
|
||||
},
|
||||
inclusive: false,
|
||||
parseDOM: [{
|
||||
tag: "a[href]", getAttrs: function getAttrs(dom) {
|
||||
return {href: dom.getAttribute("href"), title: dom.getAttribute("title")}
|
||||
return {
|
||||
href: dom.getAttribute("href"),
|
||||
title: dom.getAttribute("title"),
|
||||
target: dom.getAttribute("target"),
|
||||
}
|
||||
}
|
||||
}],
|
||||
toDOM: function toDOM(node) {
|
||||
const ref = node.attrs;
|
||||
const href = ref.href;
|
||||
const title = ref.title;
|
||||
return ["a", {href: href, title: title}, 0]
|
||||
const target = ref.target;
|
||||
return ["a", {href, title, target}, 0]
|
||||
}
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in a new issue