X-Git-Url: http://source.bookstackapp.com/bookstack/blobdiff_plain/e711290d8b1ce06b38e0560248806e8de2077870..refs/pull/4390/head:/resources/js/components/pointer.js diff --git a/resources/js/components/pointer.js b/resources/js/components/pointer.js index f1208ab76..b16a50de6 100644 --- a/resources/js/components/pointer.js +++ b/resources/js/components/pointer.js @@ -6,64 +6,74 @@ export class Pointer extends Component { setup() { this.container = this.$el; - this.input = this.$refs.input; - this.button = this.$refs.button; + this.pointer = this.$refs.pointer; + this.linkInput = this.$refs.linkInput; + this.linkButton = this.$refs.linkButton; + this.includeInput = this.$refs.includeInput; + this.includeButton = this.$refs.includeButton; + this.sectionModeButton = this.$refs.sectionModeButton; + this.modeToggles = this.$manyRefs.modeToggle; + this.modeSections = this.$manyRefs.modeSection; this.pageId = this.$opts.pageId; // Instance variables this.showing = false; this.isSelection = false; - this.pointerModeLink = true; - this.pointerSectionId = ''; this.setupListeners(); } setupListeners() { // Copy on copy button click - this.button.addEventListener('click', event => { - copyTextToClipboard(this.input.value); - }); + this.includeButton.addEventListener('click', () => copyTextToClipboard(this.includeInput.value)); + this.linkButton.addEventListener('click', () => copyTextToClipboard(this.linkInput.value)); // Select all contents on input click - this.input.addEventListener('click', event => { - this.input.select(); + DOM.onSelect([this.includeInput, this.linkInput], event => { + event.target.select(); event.stopPropagation(); }); // Prevent closing pointer when clicked or focused - DOM.onEvents(this.container, ['click', 'focus'], event => { + DOM.onEvents(this.pointer, ['click', 'focus'], event => { event.stopPropagation(); }); - // Pointer mode toggle - DOM.onChildEvent(this.container, 'span.icon', 'click', (event, icon) => { - event.stopPropagation(); - this.pointerModeLink = !this.pointerModeLink; - icon.querySelector('[data-icon="include"]').style.display = (!this.pointerModeLink) ? 'inline' : 'none'; - icon.querySelector('[data-icon="link"]').style.display = (this.pointerModeLink) ? 'inline' : 'none'; - this.updateForTarget(); - }); - // Hide pointer when clicking away - DOM.onEvents(document.body, ['click', 'focus'], event => { + DOM.onEvents(document.body, ['click', 'focus'], () => { if (!this.showing || this.isSelection) return; this.hidePointer(); }); + // Hide pointer on escape press + DOM.onEscapePress(this.pointer, this.hidePointer.bind(this)); + // Show pointer when selecting a single block of tagged content const pageContent = document.querySelector('.page-content'); DOM.onEvents(pageContent, ['mouseup', 'keyup'], event => { event.stopPropagation(); const targetEl = event.target.closest('[id^="bkmrk"]'); - if (targetEl) { - this.showPointerAtTarget(targetEl, event.pageX); + if (targetEl && window.getSelection().toString().length > 0) { + this.showPointerAtTarget(targetEl, event.pageX, false); } }); + + // Start section selection mode on button press + DOM.onSelect(this.sectionModeButton, this.enterSectionSelectMode.bind(this)); + + // Toggle between pointer modes + DOM.onSelect(this.modeToggles, event => { + for (const section of this.modeSections) { + const show = !section.contains(event.target); + section.toggleAttribute('hidden', !show); + } + + this.modeToggles.find(b => b !== event.target).focus(); + }); } hidePointer() { - this.container.style.display = null; + this.pointer.style.display = null; this.showing = false; } @@ -71,25 +81,21 @@ export class Pointer extends Component { * Move and display the pointer at the given element, targeting the given screen x-position if possible. * @param {Element} element * @param {Number} xPosition + * @param {Boolean} keyboardMode */ - showPointerAtTarget(element, xPosition) { - const selection = window.getSelection(); - if (selection.toString().length === 0) return; - - // Show pointer and set link - this.pointerSectionId = element.id; + showPointerAtTarget(element, xPosition, keyboardMode) { this.updateForTarget(element); - this.container.style.display = 'block'; + this.pointer.style.display = 'block'; const targetBounds = element.getBoundingClientRect(); - const pointerBounds = this.container.getBoundingClientRect(); + const pointerBounds = this.pointer.getBoundingClientRect(); const xTarget = Math.min(Math.max(xPosition, targetBounds.left), targetBounds.right); const xOffset = xTarget - (pointerBounds.width / 2); const yOffset = (targetBounds.top - pointerBounds.height) - 16; - this.container.style.left = `${xOffset}px`; - this.container.style.top = `${yOffset}px`; + this.pointer.style.left = `${xOffset}px`; + this.pointer.style.top = `${yOffset}px`; this.showing = true; this.isSelection = true; @@ -102,7 +108,11 @@ export class Pointer extends Component { this.hidePointer(); window.removeEventListener('scroll', scrollListener, {passive: true}); }; - window.addEventListener('scroll', scrollListener, {passive: true}); + + element.parentElement.insertBefore(this.pointer, element); + if (!keyboardMode) { + window.addEventListener('scroll', scrollListener, {passive: true}); + } } /** @@ -110,23 +120,36 @@ export class Pointer extends Component { * @param {?Element} element */ updateForTarget(element) { - let inputText = this.pointerModeLink ? window.baseUrl(`/link/${this.pageId}#${this.pointerSectionId}`) : `{{@${this.pageId}#${this.pointerSectionId}}}`; - if (this.pointerModeLink && !inputText.startsWith('http')) { - inputText = `${window.location.protocol}//${window.location.host}${inputText}`; - } + const permaLink = window.baseUrl(`/link/${this.pageId}#${element.id}`); + const includeTag = `{{@${this.pageId}#${element.id}}}`; - this.input.value = inputText; + this.linkInput.value = permaLink; + this.includeInput.value = includeTag; // Update anchor if present - const editAnchor = this.container.querySelector('#pointer-edit'); + const editAnchor = this.pointer.querySelector('#pointer-edit'); if (editAnchor && element) { const {editHref} = editAnchor.dataset; const elementId = element.id; - // get the first 50 characters. + // Get the first 50 characters. const queryContent = element.textContent && element.textContent.substring(0, 50); editAnchor.href = `${editHref}?content-id=${elementId}&content-text=${encodeURIComponent(queryContent)}`; } } + enterSectionSelectMode() { + const sections = Array.from(document.querySelectorAll('.page-content [id^="bkmrk"]')); + for (const section of sections) { + section.setAttribute('tabindex', '0'); + } + + sections[0].focus(); + + DOM.onEnterPress(sections, event => { + this.showPointerAtTarget(event.target, 0, true); + this.pointer.focus(); + }); + } + }