]> BookStack Code Mirror - bookstack/blob - resources/js/editor/menu/item-anchor-button.js
02dfba1ab830a62e676ebed9e8b5f4916006034c
[bookstack] / resources / js / editor / menu / item-anchor-button.js
1 import DialogBox from "./DialogBox";
2 import DialogForm from "./DialogForm";
3 import DialogInput from "./DialogInput";
4 import schema from "../schema";
5
6 import {MenuItem} from "./menu";
7 import {icons} from "./icons";
8
9 /**
10  * @param {PmMarkType} markType
11  * @param {String} attribute
12  * @return {(function(PmEditorState): (string|null))}
13  */
14 function getMarkAttribute(markType, attribute) {
15     return function (state) {
16         const marks = state.selection.$head.marks();
17         for (const mark of marks) {
18             if (mark.type === markType) {
19                 return mark.attrs[attribute];
20             }
21         }
22
23         return null;
24     };
25 }
26
27 /**
28  * @param {(function(FormData))} submitter
29  * @param {Function} closer
30  * @return {DialogBox}
31  */
32 function getLinkDialog(submitter, closer) {
33     return new DialogBox([
34         new DialogForm([
35             new DialogInput({
36                 label: 'URL',
37                 id: 'href',
38                 value: getMarkAttribute(schema.marks.link, 'href'),
39             }),
40             new DialogInput({
41                 label: 'Title',
42                 id: 'title',
43                 value: getMarkAttribute(schema.marks.link, 'title'),
44             }),
45             new DialogInput({
46                 label: 'Target',
47                 id: 'target',
48                 value: getMarkAttribute(schema.marks.link, 'target'),
49             })
50         ], {
51             canceler: closer,
52             action: submitter,
53         }),
54     ], {
55         label: 'Insert Link',
56         closer: closer,
57     });
58 }
59
60 /**
61  * @param {FormData} formData
62  * @param {PmEditorState} state
63  * @param {PmDispatchFunction} dispatch
64  * @return {boolean}
65  */
66 function applyLink(formData, state, dispatch) {
67     const selection = state.selection;
68     const attrs = Object.fromEntries(formData);
69     if (dispatch) {
70         const tr = state.tr;
71
72         if (attrs.href) {
73             tr.addMark(selection.from, selection.to, schema.marks.link.create(attrs));
74         } else {
75             tr.removeMark(selection.from, selection.to, schema.marks.link);
76         }
77         dispatch(tr);
78     }
79     return true;
80 }
81
82 /**
83  * @param {PmEditorState} state
84  * @param {PmDispatchFunction} dispatch
85  * @param {PmView} view
86  * @param {Event} e
87  */
88 function onPress(state, dispatch, view, e) {
89     const dialog = getLinkDialog((data) => {
90         applyLink(data, state, dispatch);
91         dom.remove();
92     }, () => {
93         dom.remove();
94     })
95
96     const {dom, update} = dialog.render(view);
97     update(state);
98     document.body.appendChild(dom);
99 }
100
101 /**
102  * @return {MenuItem}
103  */
104 function anchorButtonItem() {
105     return new MenuItem({
106         title: "Insert/Edit Anchor Link",
107         run: onPress,
108         enable: state => true,
109         icon: icons.link,
110     });
111 }
112
113 export default anchorButtonItem;