1 import * as DOM from '../services/dom';
2 import {Component} from './component';
3 import {copyTextToClipboard} from '../services/clipboard';
5 export class Pointer extends Component {
8 this.container = this.$el;
9 this.pointer = this.$refs.pointer;
10 this.linkInput = this.$refs.linkInput;
11 this.linkButton = this.$refs.linkButton;
12 this.includeInput = this.$refs.includeInput;
13 this.includeButton = this.$refs.includeButton;
14 this.sectionModeButton = this.$refs.sectionModeButton;
15 this.modeToggles = this.$manyRefs.modeToggle;
16 this.modeSections = this.$manyRefs.modeSection;
17 this.pageId = this.$opts.pageId;
21 this.isSelection = false;
23 this.setupListeners();
27 // Copy on copy button click
28 this.includeButton.addEventListener('click', () => copyTextToClipboard(this.includeInput.value));
29 this.linkButton.addEventListener('click', () => copyTextToClipboard(this.linkInput.value));
31 // Select all contents on input click
32 DOM.onSelect([this.includeInput, this.linkInput], event => {
33 event.target.select();
34 event.stopPropagation();
37 // Prevent closing pointer when clicked or focused
38 DOM.onEvents(this.pointer, ['click', 'focus'], event => {
39 event.stopPropagation();
42 // Hide pointer when clicking away
43 DOM.onEvents(document.body, ['click', 'focus'], () => {
44 if (!this.showing || this.isSelection) return;
48 // Hide pointer on escape press
49 DOM.onEscapePress(this.pointer, this.hidePointer.bind(this));
51 // Show pointer when selecting a single block of tagged content
52 const pageContent = document.querySelector('.page-content');
53 DOM.onEvents(pageContent, ['mouseup', 'keyup'], event => {
54 event.stopPropagation();
55 const targetEl = event.target.closest('[id^="bkmrk"]');
56 if (targetEl && window.getSelection().toString().length > 0) {
57 this.showPointerAtTarget(targetEl, event.pageX, false);
61 // Start section selection mode on button press
62 DOM.onSelect(this.sectionModeButton, this.enterSectionSelectMode.bind(this));
64 // Toggle between pointer modes
65 DOM.onSelect(this.modeToggles, event => {
66 for (const section of this.modeSections) {
67 const show = !section.contains(event.target);
68 section.toggleAttribute('hidden', !show);
71 this.modeToggles.find(b => b !== event.target).focus();
76 this.pointer.style.display = null;
81 * Move and display the pointer at the given element, targeting the given screen x-position if possible.
82 * @param {Element} element
83 * @param {Number} xPosition
84 * @param {Boolean} keyboardMode
86 showPointerAtTarget(element, xPosition, keyboardMode) {
87 this.updateForTarget(element);
89 this.pointer.style.display = 'block';
90 const targetBounds = element.getBoundingClientRect();
91 const pointerBounds = this.pointer.getBoundingClientRect();
93 const xTarget = Math.min(Math.max(xPosition, targetBounds.left), targetBounds.right);
94 const xOffset = xTarget - (pointerBounds.width / 2);
95 const yOffset = (targetBounds.top - pointerBounds.height) - 16;
97 this.pointer.style.left = `${xOffset}px`;
98 this.pointer.style.top = `${yOffset}px`;
101 this.isSelection = true;
104 this.isSelection = false;
107 const scrollListener = () => {
109 window.removeEventListener('scroll', scrollListener, {passive: true});
112 element.parentElement.insertBefore(this.pointer, element);
114 window.addEventListener('scroll', scrollListener, {passive: true});
119 * Update the pointer inputs/content for the given target element.
120 * @param {?Element} element
122 updateForTarget(element) {
123 const permaLink = window.baseUrl(`/link/${this.pageId}#${element.id}`);
124 const includeTag = `{{@${this.pageId}#${element.id}}}`;
126 this.linkInput.value = permaLink;
127 this.includeInput.value = includeTag;
129 // Update anchor if present
130 const editAnchor = this.pointer.querySelector('#pointer-edit');
131 if (editAnchor && element) {
132 const {editHref} = editAnchor.dataset;
133 const elementId = element.id;
135 // Get the first 50 characters.
136 const queryContent = element.textContent && element.textContent.substring(0, 50);
137 editAnchor.href = `${editHref}?content-id=${elementId}&content-text=${encodeURIComponent(queryContent)}`;
141 enterSectionSelectMode() {
142 const sections = Array.from(document.querySelectorAll('.page-content [id^="bkmrk"]'));
143 for (const section of sections) {
144 section.setAttribute('tabindex', '0');
149 DOM.onEnterPress(sections, event => {
150 this.showPointerAtTarget(event.target, 0, true);
151 this.pointer.focus();