]> BookStack Code Mirror - bookstack/blob - resources/js/editor/menu/item-anchor-button.js
Added jsdoc types for prosemirror
[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 function applyLink(formData, state, dispatch) {
61     const selection = state.selection;
62     const attrs = Object.fromEntries(formData);
63     if (dispatch) {
64         const tr = state.tr;
65
66         if (attrs.href) {
67             tr.addMark(selection.from, selection.to, schema.marks.link.create(attrs));
68         } else {
69             tr.removeMark(selection.from, selection.to, schema.marks.link);
70         }
71         dispatch(tr);
72     }
73     return true;
74 }
75
76 /**
77  * @param {PmEditorState} state
78  * @param {PmDispatchFunction} dispatch
79  * @param {PmView} view
80  * @param {Event} e
81  */
82 function onPress(state, dispatch, view, e) {
83     const dialog = getLinkDialog((data) => {
84         applyLink(data, state, dispatch);
85         dom.remove();
86     }, () => {
87         dom.remove();
88     })
89
90     const {dom, update} = dialog.render(view);
91     update(state);
92     document.body.appendChild(dom);
93 }
94
95 /**
96  * @return {MenuItem}
97  */
98 function anchorButtonItem() {
99     return new MenuItem({
100         title: "Insert/Edit Anchor Link",
101         run: onPress,
102         enable: state => true,
103         icon: icons.link,
104     });
105 }
106
107 export default anchorButtonItem;