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