Skip to content

Commit 8d159f7

Browse files
committed
Comments: Started logic for content references
Adds button for comments to pointer. Adds logic to generate a content reference point.
1 parent fa566f1 commit 8d159f7

File tree

5 files changed

+113
-19
lines changed

5 files changed

+113
-19
lines changed

resources/js/components/pointer.js

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import * as DOM from '../services/dom.ts';
22
import {Component} from './component';
33
import {copyTextToClipboard} from '../services/clipboard.ts';
4+
import {el} from "../wysiwyg/utils/dom";
5+
import {cyrb53} from "../services/util";
6+
import {normalizeNodeTextOffsetToParent} from "../services/dom.ts";
47

58
export class Pointer extends Component {
69

@@ -12,13 +15,16 @@ export class Pointer extends Component {
1215
this.includeInput = this.$refs.includeInput;
1316
this.includeButton = this.$refs.includeButton;
1417
this.sectionModeButton = this.$refs.sectionModeButton;
18+
this.commentButton = this.$refs.commentButton;
1519
this.modeToggles = this.$manyRefs.modeToggle;
1620
this.modeSections = this.$manyRefs.modeSection;
1721
this.pageId = this.$opts.pageId;
1822

1923
// Instance variables
2024
this.showing = false;
21-
this.isSelection = false;
25+
this.isMakingSelection = false;
26+
this.targetElement = null;
27+
this.targetSelectionRange = null;
2228

2329
this.setupListeners();
2430
}
@@ -41,7 +47,7 @@ export class Pointer extends Component {
4147

4248
// Hide pointer when clicking away
4349
DOM.onEvents(document.body, ['click', 'focus'], () => {
44-
if (!this.showing || this.isSelection) return;
50+
if (!this.showing || this.isMakingSelection) return;
4551
this.hidePointer();
4652
});
4753

@@ -70,11 +76,17 @@ export class Pointer extends Component {
7076

7177
this.modeToggles.find(b => b !== event.target).focus();
7278
});
79+
80+
if (this.commentButton) {
81+
DOM.onSelect(this.commentButton, this.createCommentAtPointer.bind(this));
82+
}
7383
}
7484

7585
hidePointer() {
7686
this.pointer.style.display = null;
7787
this.showing = false;
88+
this.targetElement = null;
89+
this.targetSelectionRange = null;
7890
}
7991

8092
/**
@@ -84,7 +96,9 @@ export class Pointer extends Component {
8496
* @param {Boolean} keyboardMode
8597
*/
8698
showPointerAtTarget(element, xPosition, keyboardMode) {
87-
this.updateForTarget(element);
99+
this.targetElement = element;
100+
this.targetSelectionRange = window.getSelection()?.getRangeAt(0);
101+
this.updateDomForTarget(element);
88102

89103
this.pointer.style.display = 'block';
90104
const targetBounds = element.getBoundingClientRect();
@@ -98,10 +112,10 @@ export class Pointer extends Component {
98112
this.pointer.style.top = `${yOffset}px`;
99113

100114
this.showing = true;
101-
this.isSelection = true;
115+
this.isMakingSelection = true;
102116

103117
setTimeout(() => {
104-
this.isSelection = false;
118+
this.isMakingSelection = false;
105119
}, 100);
106120

107121
const scrollListener = () => {
@@ -119,7 +133,7 @@ export class Pointer extends Component {
119133
* Update the pointer inputs/content for the given target element.
120134
* @param {?Element} element
121135
*/
122-
updateForTarget(element) {
136+
updateDomForTarget(element) {
123137
const permaLink = window.baseUrl(`/link/${this.pageId}#${element.id}`);
124138
const includeTag = `{{@${this.pageId}#${element.id}}}`;
125139

@@ -152,4 +166,34 @@ export class Pointer extends Component {
152166
});
153167
}
154168

169+
createCommentAtPointer(event) {
170+
if (!this.targetElement) {
171+
return;
172+
}
173+
174+
const normalisedElemHtml = this.targetElement.outerHTML.replace(/\s{2,}/g, '');
175+
const refId = this.targetElement.id;
176+
const hash = cyrb53(normalisedElemHtml);
177+
let range = '';
178+
if (this.targetSelectionRange) {
179+
const commonContainer = this.targetSelectionRange.commonAncestorContainer;
180+
if (this.targetElement.contains(commonContainer)) {
181+
const start = normalizeNodeTextOffsetToParent(
182+
this.targetSelectionRange.startContainer,
183+
this.targetSelectionRange.startOffset,
184+
this.targetElement
185+
);
186+
const end = normalizeNodeTextOffsetToParent(
187+
this.targetSelectionRange.endContainer,
188+
this.targetSelectionRange.endOffset,
189+
this.targetElement
190+
);
191+
range = `${start}-${end}`;
192+
}
193+
}
194+
195+
const reference = `${refId}:${hash}:${range}`;
196+
console.log(reference);
197+
}
198+
155199
}

resources/js/services/dom.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,3 +178,24 @@ export function htmlToDom(html: string): HTMLElement {
178178

179179
return firstChild;
180180
}
181+
182+
export function normalizeNodeTextOffsetToParent(node: Node, offset: number, parentElement: HTMLElement): number {
183+
if (!parentElement.contains(node)) {
184+
throw new Error('ParentElement must be a prent of element');
185+
}
186+
187+
let normalizedOffset = offset;
188+
let currentNode: Node|null = node.nodeType === Node.TEXT_NODE ?
189+
node : node.childNodes[offset];
190+
191+
while (currentNode !== parentElement && currentNode) {
192+
if (currentNode.previousSibling) {
193+
currentNode = currentNode.previousSibling;
194+
normalizedOffset += (currentNode.textContent?.length || 0);
195+
} else {
196+
currentNode = currentNode.parentNode;
197+
}
198+
}
199+
200+
return normalizedOffset;
201+
}

resources/js/services/util.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,4 +144,25 @@ function getVersion(): string {
144144
export function importVersioned(moduleName: string): Promise<object> {
145145
const importPath = window.baseUrl(`dist/${moduleName}.js?version=${getVersion()}`);
146146
return import(importPath);
147+
}
148+
149+
/*
150+
cyrb53 (c) 2018 bryc (github.com/bryc)
151+
License: Public domain (or MIT if needed). Attribution appreciated.
152+
A fast and simple 53-bit string hash function with decent collision resistance.
153+
Largely inspired by MurmurHash2/3, but with a focus on speed/simplicity.
154+
Taken from: https://github.com/bryc/code/blob/master/jshash/experimental/cyrb53.js
155+
*/
156+
export function cyrb53(str: string, seed: number = 0): string {
157+
let h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed;
158+
for(let i = 0, ch; i < str.length; i++) {
159+
ch = str.charCodeAt(i);
160+
h1 = Math.imul(h1 ^ ch, 2654435761);
161+
h2 = Math.imul(h2 ^ ch, 1597334677);
162+
}
163+
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507);
164+
h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909);
165+
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507);
166+
h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909);
167+
return (4294967296 * (2097151 & h2) + (h1 >>> 0)) as string;
147168
}

resources/sass/_pages.scss

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,6 @@ body.tox-fullscreen, body.markdown-fullscreen {
183183
}
184184
input, button, a {
185185
position: relative;
186-
border-radius: 0;
187186
height: 28px;
188187
font-size: 12px;
189188
vertical-align: top;
@@ -194,17 +193,19 @@ body.tox-fullscreen, body.markdown-fullscreen {
194193
border: 1px solid #DDD;
195194
@include mixins.lightDark(border-color, #ddd, #000);
196195
color: #666;
197-
width: 160px;
198-
z-index: 40;
199-
padding: 5px 10px;
196+
width: 180px;
197+
z-index: 58;
198+
padding: 5px;
199+
border-radius: 0;
200200
}
201201
.text-button {
202202
@include mixins.lightDark(color, #444, #AAA);
203203
}
204204
.input-group .button {
205205
line-height: 1;
206-
margin: 0 0 0 -4px;
206+
margin: 0 0 0 -5px;
207207
box-shadow: none;
208+
border-radius: 0;
208209
}
209210
a.button {
210211
margin: 0;

resources/views/pages/parts/pointer.blade.php

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@
66
tabindex="-1"
77
aria-label="{{ trans('entities.pages_pointer_label') }}"
88
class="pointer-container">
9-
<div class="pointer flex-container-row items-center justify-space-between p-s anim {{ userCan('page-update', $page) ? 'is-page-editable' : ''}}" >
10-
<div refs="pointer@mode-section" class="flex-container-row items-center gap-s">
9+
<div class="pointer flex-container-row items-center justify-space-between gap-xs p-xs anim {{ userCan('page-update', $page) ? 'is-page-editable' : ''}}" >
10+
<div refs="pointer@mode-section" class="flex-container-row items-center gap-xs">
1111
<button refs="pointer@mode-toggle"
1212
title="{{ trans('entities.pages_pointer_toggle_link') }}"
1313
class="text-button icon px-xs">@icon('link')</button>
1414
<div class="input-group">
1515
<input refs="pointer@link-input" aria-label="{{ trans('entities.pages_pointer_permalink') }}" readonly="readonly" type="text" id="pointer-url" placeholder="url">
16-
<button refs="pointer@link-button" class="button outline icon" type="button" title="{{ trans('entities.pages_copy_link') }}">@icon('copy')</button>
16+
<button refs="pointer@link-button" class="button outline icon px-xs" type="button" title="{{ trans('entities.pages_copy_link') }}">@icon('copy')</button>
1717
</div>
1818
</div>
1919
<div refs="pointer@mode-section" hidden class="flex-container-row items-center gap-s">
@@ -22,13 +22,20 @@ class="text-button icon px-xs">@icon('link')</button>
2222
class="text-button icon px-xs">@icon('include')</button>
2323
<div class="input-group">
2424
<input refs="pointer@include-input" aria-label="{{ trans('entities.pages_pointer_include_tag') }}" readonly="readonly" type="text" id="pointer-include" placeholder="include">
25-
<button refs="pointer@include-button" class="button outline icon" type="button" title="{{ trans('entities.pages_copy_link') }}">@icon('copy')</button>
25+
<button refs="pointer@include-button" class="button outline icon px-xs" type="button" title="{{ trans('entities.pages_copy_link') }}">@icon('copy')</button>
2626
</div>
2727
</div>
28-
@if(userCan('page-update', $page))
29-
<a href="{{ $page->getUrl('/edit') }}" id="pointer-edit" data-edit-href="{{ $page->getUrl('/edit') }}"
30-
class="button primary outline icon heading-edit-icon ml-s px-s" title="{{ trans('entities.pages_edit_content_link')}}">@icon('edit')</a>
31-
@endif
28+
<div>
29+
@if(userCan('page-update', $page))
30+
<a href="{{ $page->getUrl('/edit') }}" id="pointer-edit" data-edit-href="{{ $page->getUrl('/edit') }}"
31+
class="button primary outline icon heading-edit-icon px-xs" title="{{ trans('entities.pages_edit_content_link')}}">@icon('edit')</a>
32+
@endif
33+
@if($commentTree->enabled() && userCan('comment-create-all'))
34+
<button type="button"
35+
refs="pointer@comment-button"
36+
class="button primary outline icon px-xs m-none" title="{{ trans('entities.comment_add')}}">@icon('comment')</button>
37+
@endif
38+
</div>
3239
</div>
3340
</div>
3441

0 commit comments

Comments
 (0)