Add mutation handling to norm editor; disable socket connection for now
This commit is contained in:
parent
c4e641545c
commit
60e08d8a76
@ -1,7 +1,9 @@
|
||||
<div [ngClass]="isDark() ? 'container dark' : 'container'">
|
||||
<wysiwyg-editor
|
||||
#wysiwygComponent
|
||||
[contents]="contents"
|
||||
(contentsChanged)="onContentsChanged($event)"
|
||||
(contentsMutated)="onContentsMutated($event)"
|
||||
(selectionChanged)="onSelectionChanged($event)"
|
||||
[readonly]="isReadonly"
|
||||
[editingUsers]="editorGroupUsers"
|
||||
|
@ -4,7 +4,7 @@ import {EditorService} from '../../../service/editor.service';
|
||||
import {FlitterSocketConnection, FlitterSocketServerClientTransaction} from '../../../flitter-socket';
|
||||
import {ApiService} from '../../../service/api.service';
|
||||
import {debug} from '../../../utility';
|
||||
import { EditingUserSelect } from '../../wysiwyg/wysiwyg.component';
|
||||
import {EditingUserSelect, MutationBroadcast, WysiwygComponent} from '../../wysiwyg/wysiwyg.component';
|
||||
|
||||
@Component({
|
||||
selector: 'editor-norm',
|
||||
@ -13,6 +13,7 @@ import { EditingUserSelect } from '../../wysiwyg/wysiwyg.component';
|
||||
})
|
||||
export class NormComponent extends EditorNodeContract implements OnInit, OnDestroy {
|
||||
@ViewChild('editable') editable;
|
||||
@ViewChild('wysiwygComponent') wysiwygComponent: WysiwygComponent;
|
||||
@Input() nodeId: string;
|
||||
@Input() editorUUID?: string;
|
||||
|
||||
@ -93,6 +94,7 @@ export class NormComponent extends EditorNodeContract implements OnInit, OnDestr
|
||||
|
||||
public async performLoad(): Promise<void> {
|
||||
// This is called after the Node record has been loaded.
|
||||
return;
|
||||
|
||||
// FIXME need to find a consistent way of doing this on prod/development
|
||||
// FIXME Probably make use of the systemBase, but allow overriding it in the environment
|
||||
@ -138,6 +140,12 @@ export class NormComponent extends EditorNodeContract implements OnInit, OnDestr
|
||||
}
|
||||
}
|
||||
|
||||
applyRemoteContentMutation(transaction: FlitterSocketServerClientTransaction, socket: any) {
|
||||
if ( this.wysiwygComponent && transaction?.incoming?.mutation ) {
|
||||
this.wysiwygComponent.applyRemoteContentMutation(transaction.incoming.mutation);
|
||||
}
|
||||
}
|
||||
|
||||
async onSelectionChanged(selection: { path: string, offset: number }) {
|
||||
if ( this.editorGroupSocket && this.editorGroupId ) {
|
||||
await this.editorGroupSocket.asyncRequest('set_member_selection', {
|
||||
@ -147,6 +155,15 @@ export class NormComponent extends EditorNodeContract implements OnInit, OnDestr
|
||||
}
|
||||
}
|
||||
|
||||
async onContentsMutated(data: MutationBroadcast) {
|
||||
if ( this.editorGroupSocket && this.editorGroupId ) {
|
||||
await this.editorGroupSocket.asyncRequest('broadcast_content_mutation', {
|
||||
editor_group_id: this.editorGroupId,
|
||||
data,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async refreshRemoteSelections() {
|
||||
if ( this.editorGroupSocket && this.editorGroupId ) {
|
||||
const [
|
||||
|
@ -61,10 +61,11 @@
|
||||
}
|
||||
|
||||
.remote-cursors-container {
|
||||
padding-left: 20px;
|
||||
|
||||
.remote-cursor {
|
||||
min-height: 20px;
|
||||
width: 3px;
|
||||
//position: absolute;
|
||||
position: fixed;
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import {Component, EventEmitter, HostListener, Input, OnInit, Output, ViewChild} from '@angular/core';
|
||||
import {Component, ElementRef, EventEmitter, HostListener, Input, OnInit, Output, ViewChild} from '@angular/core';
|
||||
import {debug} from '../../utility';
|
||||
import {DomSanitizer} from '@angular/platform-browser';
|
||||
|
||||
@ -13,6 +13,14 @@ export interface EditingUserSelect {
|
||||
};
|
||||
}
|
||||
|
||||
export interface MutationBroadcast {
|
||||
path: string;
|
||||
type: 'characterData' | 'attributes' | 'childList';
|
||||
data: string;
|
||||
fullContents: string;
|
||||
addedNodes?: Array<{previousSiblingPath: string, type: string, data: string}>;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'wysiwyg-editor',
|
||||
templateUrl: './wysiwyg.component.html',
|
||||
@ -52,6 +60,7 @@ export class WysiwygComponent implements OnInit {
|
||||
|
||||
@Output() contentsChanged: EventEmitter<string> = new EventEmitter<string>();
|
||||
@Output() selectionChanged: EventEmitter<{path: string, offset: number}> = new EventEmitter<{path: string; offset: number}>();
|
||||
@Output() contentsMutated: EventEmitter<MutationBroadcast> = new EventEmitter<MutationBroadcast>();
|
||||
|
||||
public currentContents = '';
|
||||
protected editingContents = '';
|
||||
@ -59,6 +68,8 @@ export class WysiwygComponent implements OnInit {
|
||||
public isFocused = false;
|
||||
protected hadOneFocusOut = false;
|
||||
protected isEditOnly = false;
|
||||
protected applyingRemoteMutation = false;
|
||||
protected ignoreNextMutation = false;
|
||||
|
||||
public get editonly() {
|
||||
return this.isEditOnly;
|
||||
@ -178,22 +189,34 @@ export class WysiwygComponent implements OnInit {
|
||||
}
|
||||
|
||||
processSelections() {
|
||||
if ( !this.editable ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const editableBase = this.editable.nativeElement;
|
||||
|
||||
for ( const sel of this.privEditingUserSelections ) {
|
||||
if ( !sel.path ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
sel.element = this.getElementFromPath(sel.path);
|
||||
if ( sel.element ) {
|
||||
sel.top = sel.element.offsetTop;
|
||||
sel.left = sel.element.offsetLeft;
|
||||
|
||||
const range = document.createRange();
|
||||
range.collapse(true);
|
||||
range.setStart(sel.element, sel.offset);
|
||||
range.setEnd(sel.element, sel.offset);
|
||||
|
||||
const rect = range.getClientRects()[0];
|
||||
sel.top = rect.top;
|
||||
sel.left = rect.left;
|
||||
}
|
||||
const rect = range.getBoundingClientRect();
|
||||
|
||||
console.log({sel, editable: this.editable});
|
||||
sel.top = rect.top;
|
||||
|
||||
// FIXME I'm not sure why the 45px offset is needed, but it is...
|
||||
sel.left = (rect.left - editableBase.getBoundingClientRect().left) + 45;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -220,14 +243,91 @@ export class WysiwygComponent implements OnInit {
|
||||
document.execCommand(cmd, false, '');
|
||||
}
|
||||
|
||||
onContentsChanged(contents: string) {
|
||||
onContentsChanged(mutation: MutationRecord) {
|
||||
if ( this.applyingRemoteMutation ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( this.ignoreNextMutation ) {
|
||||
this.ignoreNextMutation = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const target = this.getPathToElement(mutation.target);
|
||||
console.log({mutation, target, type: mutation.type, data: mutation.target.textContent});
|
||||
|
||||
const innerHTML = this.editable.nativeElement.innerHTML;
|
||||
|
||||
if ( mutation.type === 'characterData' ) {
|
||||
this.contentsMutated.emit({
|
||||
path: target,
|
||||
type: mutation.type,
|
||||
data: mutation.target.textContent,
|
||||
fullContents: innerHTML,
|
||||
});
|
||||
} else if ( mutation.type === 'childList' ) {
|
||||
const addedNodes: Array<{previousSiblingPath: string, type: string, data: string}> = [];
|
||||
|
||||
mutation.addedNodes.forEach(value => {
|
||||
const previousSiblingPath = this.getPathToElement(value.previousSibling);
|
||||
if ( previousSiblingPath ) {
|
||||
addedNodes.push({
|
||||
previousSiblingPath,
|
||||
type: value.nodeName,
|
||||
data: value.textContent,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
this.contentsMutated.emit({
|
||||
path: target,
|
||||
type: mutation.type,
|
||||
data: mutation.target.textContent,
|
||||
fullContents: innerHTML,
|
||||
addedNodes,
|
||||
});
|
||||
}
|
||||
|
||||
if ( this.contents !== innerHTML ) {
|
||||
this.contents = innerHTML;
|
||||
this.contentsChanged.emit(innerHTML);
|
||||
}
|
||||
}
|
||||
|
||||
applyRemoteContentMutation(mutation: MutationBroadcast) {
|
||||
this.applyingRemoteMutation = true;
|
||||
console.log('got remote content mutation', mutation);
|
||||
|
||||
if ( this.editable ) {
|
||||
const target = this.getElementFromPath(mutation.path);
|
||||
console.log(target);
|
||||
if ( target ) {
|
||||
if ( mutation.type === 'characterData' ) {
|
||||
this.ignoreNextMutation = true;
|
||||
target.nodeValue = mutation.data;
|
||||
} else if ( mutation.type === 'childList' ) {
|
||||
if ( Array.isArray(mutation.addedNodes) ) {
|
||||
for ( const addedNode of mutation.addedNodes ) {
|
||||
const previousSibling = this.getElementFromPath(addedNode.previousSiblingPath);
|
||||
const elem = document.createElement(addedNode.type);
|
||||
elem.nodeValue = addedNode.data;
|
||||
|
||||
this.ignoreNextMutation = true;
|
||||
previousSibling.parentElement.insertBefore(elem, previousSibling.nextSibling);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ( this.contents !== mutation.fullContents ) {
|
||||
this.ignoreNextMutation = true;
|
||||
this.contents = mutation.fullContents;
|
||||
}
|
||||
}
|
||||
|
||||
this.applyingRemoteMutation = false;
|
||||
}
|
||||
|
||||
@HostListener('document:keydown.tab', ['$event'])
|
||||
onIndent(event) {
|
||||
event.preventDefault();
|
||||
|
Loading…
Reference in New Issue
Block a user