]> BookStack Code Mirror - bookstack/blob - resources/js/components/pointer.js
Added shortcut input controls to make custom shortcuts work
[bookstack] / resources / js / components / pointer.js
1 import * as DOM from "../services/dom";
2 import Clipboard from "clipboard/dist/clipboard.min";
3
4 /**
5  * @extends Component
6  */
7 class Pointer {
8
9     setup() {
10         this.container = this.$el;
11         this.pageId = this.$opts.pageId;
12
13         // Instance variables
14         this.showing = false;
15         this.isSelection = false;
16         this.pointerModeLink = true;
17         this.pointerSectionId = '';
18
19         this.setupListeners();
20
21         // Set up clipboard
22         new Clipboard(this.container.querySelector('button'));
23     }
24
25     setupListeners() {
26         // Select all contents on input click
27         DOM.onChildEvent(this.container, 'input', 'click', (event, input) => {
28             input.select();
29             event.stopPropagation();
30         });
31
32         // Prevent closing pointer when clicked or focused
33         DOM.onEvents(this.container, ['click', 'focus'], event => {
34             event.stopPropagation();
35         });
36
37         // Pointer mode toggle
38         DOM.onChildEvent(this.container, 'span.icon', 'click', (event, icon) => {
39             event.stopPropagation();
40             this.pointerModeLink = !this.pointerModeLink;
41             icon.querySelector('[data-icon="include"]').style.display = (!this.pointerModeLink) ? 'inline' : 'none';
42             icon.querySelector('[data-icon="link"]').style.display = (this.pointerModeLink) ? 'inline' : 'none';
43             this.updateForTarget();
44         });
45
46         // Hide pointer when clicking away
47         DOM.onEvents(document.body, ['click', 'focus'], event => {
48             if (!this.showing || this.isSelection) return;
49             this.hidePointer();
50         });
51
52         // Show pointer when selecting a single block of tagged content
53         const pageContent = document.querySelector('.page-content');
54         DOM.onEvents(pageContent, ['mouseup', 'keyup'], event => {
55             event.stopPropagation();
56             const targetEl = event.target.closest('[id^="bkmrk"]');
57             if (targetEl) {
58                 this.showPointerAtTarget(targetEl, event.pageX);
59             }
60         });
61     }
62
63     hidePointer() {
64         this.container.style.display = null;
65         this.showing = false;
66     }
67
68     /**
69      * Move and display the pointer at the given element, targeting the given screen x-position if possible.
70      * @param {Element} element
71      * @param {Number} xPosition
72      */
73     showPointerAtTarget(element, xPosition) {
74         const selection = window.getSelection();
75         if (selection.toString().length === 0) return;
76
77         // Show pointer and set link
78         this.pointerSectionId = element.id;
79         this.updateForTarget(element);
80
81         this.container.style.display = 'block';
82         const targetBounds = element.getBoundingClientRect();
83         const pointerBounds = this.container.getBoundingClientRect();
84
85         const xTarget = Math.min(Math.max(xPosition, targetBounds.left), targetBounds.right);
86         const xOffset = xTarget - (pointerBounds.width / 2);
87         const yOffset = (targetBounds.top - pointerBounds.height) - 16;
88
89         this.container.style.left = `${xOffset}px`;
90         this.container.style.top = `${yOffset}px`;
91
92         this.showing = true;
93         this.isSelection = true;
94
95         setTimeout(() => {
96             this.isSelection = false;
97         }, 100);
98
99         const scrollListener = () => {
100             this.hidePointer();
101             window.removeEventListener('scroll', scrollListener, {passive: true});
102         };
103         window.addEventListener('scroll', scrollListener, {passive: true});
104     }
105
106     /**
107      * Update the pointer inputs/content for the given target element.
108      * @param {?Element} element
109      */
110     updateForTarget(element) {
111         let inputText = this.pointerModeLink ? window.baseUrl(`/link/${this.pageId}#${this.pointerSectionId}`) : `{{@${this.pageId}#${this.pointerSectionId}}}`;
112         if (this.pointerModeLink && !inputText.startsWith('http')) {
113             inputText = window.location.protocol + "//" + window.location.host + inputText;
114         }
115
116         this.container.querySelector('input').value = inputText;
117
118         // Update anchor if present
119         const editAnchor = this.container.querySelector('#pointer-edit');
120         if (editAnchor && element) {
121             const editHref = editAnchor.dataset.editHref;
122             const elementId = element.id;
123
124             // get the first 50 characters.
125             const queryContent = element.textContent && element.textContent.substring(0, 50);
126             editAnchor.href = `${editHref}?content-id=${elementId}&content-text=${encodeURIComponent(queryContent)}`;
127         }
128     }
129 }
130
131 export default Pointer;