mirror of
https://github.com/BookStackApp/BookStack.git
synced 2025-06-24 14:41:25 +08:00
Comments: Started logic for content references
Adds button for comments to pointer. Adds logic to generate a content reference point.
This commit is contained in:
@ -1,6 +1,9 @@
|
||||
import * as DOM from '../services/dom.ts';
|
||||
import {Component} from './component';
|
||||
import {copyTextToClipboard} from '../services/clipboard.ts';
|
||||
import {el} from "../wysiwyg/utils/dom";
|
||||
import {cyrb53} from "../services/util";
|
||||
import {normalizeNodeTextOffsetToParent} from "../services/dom.ts";
|
||||
|
||||
export class Pointer extends Component {
|
||||
|
||||
@ -12,13 +15,16 @@ export class Pointer extends Component {
|
||||
this.includeInput = this.$refs.includeInput;
|
||||
this.includeButton = this.$refs.includeButton;
|
||||
this.sectionModeButton = this.$refs.sectionModeButton;
|
||||
this.commentButton = this.$refs.commentButton;
|
||||
this.modeToggles = this.$manyRefs.modeToggle;
|
||||
this.modeSections = this.$manyRefs.modeSection;
|
||||
this.pageId = this.$opts.pageId;
|
||||
|
||||
// Instance variables
|
||||
this.showing = false;
|
||||
this.isSelection = false;
|
||||
this.isMakingSelection = false;
|
||||
this.targetElement = null;
|
||||
this.targetSelectionRange = null;
|
||||
|
||||
this.setupListeners();
|
||||
}
|
||||
@ -41,7 +47,7 @@ export class Pointer extends Component {
|
||||
|
||||
// Hide pointer when clicking away
|
||||
DOM.onEvents(document.body, ['click', 'focus'], () => {
|
||||
if (!this.showing || this.isSelection) return;
|
||||
if (!this.showing || this.isMakingSelection) return;
|
||||
this.hidePointer();
|
||||
});
|
||||
|
||||
@ -70,11 +76,17 @@ export class Pointer extends Component {
|
||||
|
||||
this.modeToggles.find(b => b !== event.target).focus();
|
||||
});
|
||||
|
||||
if (this.commentButton) {
|
||||
DOM.onSelect(this.commentButton, this.createCommentAtPointer.bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
hidePointer() {
|
||||
this.pointer.style.display = null;
|
||||
this.showing = false;
|
||||
this.targetElement = null;
|
||||
this.targetSelectionRange = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -84,7 +96,9 @@ export class Pointer extends Component {
|
||||
* @param {Boolean} keyboardMode
|
||||
*/
|
||||
showPointerAtTarget(element, xPosition, keyboardMode) {
|
||||
this.updateForTarget(element);
|
||||
this.targetElement = element;
|
||||
this.targetSelectionRange = window.getSelection()?.getRangeAt(0);
|
||||
this.updateDomForTarget(element);
|
||||
|
||||
this.pointer.style.display = 'block';
|
||||
const targetBounds = element.getBoundingClientRect();
|
||||
@ -98,10 +112,10 @@ export class Pointer extends Component {
|
||||
this.pointer.style.top = `${yOffset}px`;
|
||||
|
||||
this.showing = true;
|
||||
this.isSelection = true;
|
||||
this.isMakingSelection = true;
|
||||
|
||||
setTimeout(() => {
|
||||
this.isSelection = false;
|
||||
this.isMakingSelection = false;
|
||||
}, 100);
|
||||
|
||||
const scrollListener = () => {
|
||||
@ -119,7 +133,7 @@ export class Pointer extends Component {
|
||||
* Update the pointer inputs/content for the given target element.
|
||||
* @param {?Element} element
|
||||
*/
|
||||
updateForTarget(element) {
|
||||
updateDomForTarget(element) {
|
||||
const permaLink = window.baseUrl(`/link/${this.pageId}#${element.id}`);
|
||||
const includeTag = `{{@${this.pageId}#${element.id}}}`;
|
||||
|
||||
@ -152,4 +166,34 @@ export class Pointer extends Component {
|
||||
});
|
||||
}
|
||||
|
||||
createCommentAtPointer(event) {
|
||||
if (!this.targetElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
const normalisedElemHtml = this.targetElement.outerHTML.replace(/\s{2,}/g, '');
|
||||
const refId = this.targetElement.id;
|
||||
const hash = cyrb53(normalisedElemHtml);
|
||||
let range = '';
|
||||
if (this.targetSelectionRange) {
|
||||
const commonContainer = this.targetSelectionRange.commonAncestorContainer;
|
||||
if (this.targetElement.contains(commonContainer)) {
|
||||
const start = normalizeNodeTextOffsetToParent(
|
||||
this.targetSelectionRange.startContainer,
|
||||
this.targetSelectionRange.startOffset,
|
||||
this.targetElement
|
||||
);
|
||||
const end = normalizeNodeTextOffsetToParent(
|
||||
this.targetSelectionRange.endContainer,
|
||||
this.targetSelectionRange.endOffset,
|
||||
this.targetElement
|
||||
);
|
||||
range = `${start}-${end}`;
|
||||
}
|
||||
}
|
||||
|
||||
const reference = `${refId}:${hash}:${range}`;
|
||||
console.log(reference);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -178,3 +178,24 @@ export function htmlToDom(html: string): HTMLElement {
|
||||
|
||||
return firstChild;
|
||||
}
|
||||
|
||||
export function normalizeNodeTextOffsetToParent(node: Node, offset: number, parentElement: HTMLElement): number {
|
||||
if (!parentElement.contains(node)) {
|
||||
throw new Error('ParentElement must be a prent of element');
|
||||
}
|
||||
|
||||
let normalizedOffset = offset;
|
||||
let currentNode: Node|null = node.nodeType === Node.TEXT_NODE ?
|
||||
node : node.childNodes[offset];
|
||||
|
||||
while (currentNode !== parentElement && currentNode) {
|
||||
if (currentNode.previousSibling) {
|
||||
currentNode = currentNode.previousSibling;
|
||||
normalizedOffset += (currentNode.textContent?.length || 0);
|
||||
} else {
|
||||
currentNode = currentNode.parentNode;
|
||||
}
|
||||
}
|
||||
|
||||
return normalizedOffset;
|
||||
}
|
||||
|
@ -144,4 +144,25 @@ function getVersion(): string {
|
||||
export function importVersioned(moduleName: string): Promise<object> {
|
||||
const importPath = window.baseUrl(`dist/${moduleName}.js?version=${getVersion()}`);
|
||||
return import(importPath);
|
||||
}
|
||||
|
||||
/*
|
||||
cyrb53 (c) 2018 bryc (github.com/bryc)
|
||||
License: Public domain (or MIT if needed). Attribution appreciated.
|
||||
A fast and simple 53-bit string hash function with decent collision resistance.
|
||||
Largely inspired by MurmurHash2/3, but with a focus on speed/simplicity.
|
||||
Taken from: https://github.com/bryc/code/blob/master/jshash/experimental/cyrb53.js
|
||||
*/
|
||||
export function cyrb53(str: string, seed: number = 0): string {
|
||||
let h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed;
|
||||
for(let i = 0, ch; i < str.length; i++) {
|
||||
ch = str.charCodeAt(i);
|
||||
h1 = Math.imul(h1 ^ ch, 2654435761);
|
||||
h2 = Math.imul(h2 ^ ch, 1597334677);
|
||||
}
|
||||
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507);
|
||||
h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909);
|
||||
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507);
|
||||
h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909);
|
||||
return (4294967296 * (2097151 & h2) + (h1 >>> 0)) as string;
|
||||
}
|
Reference in New Issue
Block a user