Skip to content

Commit

Permalink
Fixed Paste, Replace Selection & Dragging Text
Browse files Browse the repository at this point in the history
  • Loading branch information
R1c4rdCo5t4 committed Mar 28, 2024
1 parent d6a6525 commit 2349077
Show file tree
Hide file tree
Showing 10 changed files with 124 additions and 78 deletions.
4 changes: 2 additions & 2 deletions code/client/dev-dist/sw.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ if (!self.define) {
});
};
}
define(['./workbox-a0f72815'], (function (workbox) { 'use strict';
define(['./workbox-fda11f75'], (function (workbox) { 'use strict';

self.skipWaiting();
workbox.clientsClaim();
Expand All @@ -82,7 +82,7 @@ define(['./workbox-a0f72815'], (function (workbox) { 'use strict';
"revision": "3ca0b8505b4bec776b69afdba2768812"
}, {
"url": "index.html",
"revision": "0.ptn1a5ctte"
"revision": "0.0e2rp9avb9o"
}], {});
workbox.cleanupOutdatedCaches();
workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), {
Expand Down
86 changes: 46 additions & 40 deletions code/client/src/editor/crdt/fugue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,37 +92,12 @@ export class Fugue {
* Deletes the nodes from the given start index to the given end index.
* @param selection
*/
deleteLocal({ start, end }: Selection): void {
// delete from start to end
let lineCounter = 0;
let columnCounter = 0;
let deleteFlag = false;
for (const node of this.traverseTree()) {
// delete condition
if (
lineCounter === start.line &&
(columnCounter === start.column || (isEqual(start, end) && columnCounter === start.column - 1))
) {
deleteFlag = true;
}
// delete operation
if (deleteFlag) {
const { id } = node;
this.removeNode(id);
const operation: DeleteOperation = { type: 'delete', id };
socket.emit('operation', operation); // TODO: break data into data chunks - less network traffic
}
// end condition
if (lineCounter === end.line && columnCounter === end.column) {
break;
}
// update counters
if (node.value === '\n') {
lineCounter++;
columnCounter = 0;
} else {
columnCounter++;
}
deleteLocal(selection: Selection): void {
for (const node of this.traverseBySelection(selection)) {
const { id } = node;
this.removeNode(id);
const operation: DeleteOperation = { type: 'delete', id };
socket.emit('operation', operation); // TODO: break data into data chunks - less network traffic
}
}

Expand All @@ -143,10 +118,9 @@ export class Fugue {
this.tree.deleteNode(id);
}

updateStyleLocal(start: number, end: number, value: boolean, format: string) {
for (let i = start; i < end; i++) {
const id = this.getElementId(i);
if (!id) continue;
updateStyleLocal(selection: Selection, value: boolean, format: string) {
for (const node of this.traverseBySelection(selection)) {
const { id } = node;
const style = format as Style;
const styleOperation: StyleOperation = {
type: 'style',
Expand All @@ -168,7 +142,43 @@ export class Fugue {
*/
traverseTree = () => this.tree.traverse(this.tree.root);

findNode(value: string, skip: number): Node<string> {
private *traverseBySelection(selection: Selection): IterableIterator<Node<string>> {
const { start, end } = selection;
let lineCounter = 0;
let columnCounter = 0;
let inBounds = false;
for (const node of this.traverseTree()) {
// start condition
if (
lineCounter === start.line &&
(columnCounter === start.column || (isEqual(start, end) && columnCounter === start.column - 1))
) {
inBounds = true;
}
// yield node if in bounds
if (inBounds && node.value !== '\n') {
yield node;
}
// end condition
if (lineCounter === end.line && columnCounter === end.column) {
break;
}
// update counters
if (node.value === '\n') {
lineCounter++;
columnCounter = 0;
} else {
columnCounter++;
}
}
}

getNodeByCursor(cursor: Cursor): Node<string> {
const iterator = this.traverseBySelection({ start: cursor, end: cursor });
return iterator.next().value;
}

private findNode(value: string, skip: number): Node<string> {
let lastMatch = this.tree.root;
for (const node of this.traverseTree()) {
if (node.value === value) {
Expand All @@ -187,10 +197,6 @@ export class Fugue {
return this.tree.toString();
}

getElementId(index: number): Id {
return this.tree.getByIndex(this.tree.root, index).id;
}

getRootNode(): Node<string> {
return this.tree.root!;
}
Expand Down
1 change: 1 addition & 0 deletions code/client/src/editor/slate.js/SlateEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ function SlateEditor() {
renderElement={renderElement}
renderLeaf={renderLeaf}
spellCheck={false}
onDragStart={e => e.preventDefault()}
placeholder={'Start writing...'}
onKeyDown={onKeyDown}
onPaste={onPaste}
Expand Down
32 changes: 23 additions & 9 deletions code/client/src/editor/slate.js/hooks/useInputHandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,34 +34,48 @@ function useInputHandlers(editor: Editor, fugue: Fugue) {
editor.insertText('\t');
fugue.insertLocal(start, insertNode('\t', []));
break;
default:
default: {
if (e.key.length !== 1) break;
fugue.insertLocal(start, insertNode(e.key, []));
if (selection.start.column !== selection.end.column) {
// replace selection
fugue.deleteLocal(selection);
}
const previousNode = fugue.getNodeByCursor({ line: start.line, column: start.column - 1 });
const styles = previousNode?.styles || [];
fugue.insertLocal(start, insertNode(e.key, styles));
break;
}
}
}

function onPaste(e: React.ClipboardEvent<HTMLDivElement>) {
const clipboardData = e.clipboardData?.getData('text');
if (!clipboardData) return;
const selection = getSelection(editor);
const { start } = selection;
fugue.insertLocal(start, insertNode(clipboardData, [])); // TODO: Fix this
const { start } = getSelection(editor);
for (const char of clipboardData.split('').reverse().join('')) {
fugue.insertLocal(start, insertNode(char, []));
}
}

function onCut() {
const selection = getSelection(editor);
const selection = getSelection(editor); // problem here
fugue.deleteLocal(selection); // TODO: Fix this
}

function onUndo() {
// TODO: Implement undo
}

function onRedo() {
// TODO: Implement redo
}

function shortcutHandler(event: React.KeyboardEvent<HTMLDivElement>) {
console.log(event.key)
const mark = hotkeys[event.key];
console.log(mark);
CustomEditor.toggleMark(editor, mark, fugue);
}

return { onKeyDown, onPaste, onCut };
return { onKeyDown, onPaste, onCut, onUndo, onRedo };
}

export default useInputHandlers;
6 changes: 3 additions & 3 deletions code/client/src/editor/slate.js/model/CustomEditor.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Editor } from 'slate';
import { type Fugue } from '@editor/crdt/fugue.ts';
import { getAbsoluteSelection } from '@editor/slate.js/model/utils.ts';
import { getSelection } from '@editor/slate.js/utils/selection.ts';

/**
* Custom editor operations.
Expand All @@ -14,8 +14,8 @@ const CustomEditor = {
const isActive = CustomEditor.isMarkActive(editor, mark);
Editor.addMark(editor, mark, !isActive);

const [start, end] = getAbsoluteSelection(editor)!;
fugue.updateStyleLocal(start, end, !isActive, mark);
const selection = getSelection(editor)!;
fugue.updateStyleLocal(selection, !isActive, mark);
},
};

Expand Down
45 changes: 34 additions & 11 deletions code/client/src/editor/slate.js/utils/selection.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,42 @@
import { Editor } from 'slate';
import { Selection } from '../model/cursor.ts';
import { Editor, Range, Point } from 'slate';
import { Cursor, Selection } from '../model/cursor.ts';

export function isSelected(editor: Editor) {
if (!editor.selection) return false;
const { anchor, focus } = editor.selection;
return anchor.offset !== focus.offset;
return anchor.path !== focus.path && anchor.offset !== focus.offset;
}

// export function getSelection(editor: Editor): Selection {
// if (!editor.selection) return { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } };
// const { anchor, focus } = editor.selection;
// const { path: startLine, offset: startColumn } = focus;
// const { path: endLine, offset: endColumn } = anchor;
// const start = { line: startLine[0], column: startColumn };
// const end = { line: endLine[0], column: endColumn };
// const isRightToLeft = start.line > end.line || (start.line === end.line && start.column > end.column);
// return isRightToLeft ? { start: end, end: start } : { start, end };
// }

// does the same as the function above
export function getSelection(editor: Editor): Selection {
if (!editor.selection) return { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } };
const { anchor, focus } = editor.selection;
const { path: startLine, offset: startColumn } = focus;
const { path: endLine, offset: endColumn } = anchor;
const start = { line: startLine[0], column: startColumn };
const end = { line: endLine[0], column: endColumn };
const isRightToLeft = start.line > end.line || (start.line === end.line && start.column > end.column);
return isRightToLeft ? { start: end, end: start } : { start, end };
const { selection } = editor;
if (!selection) {
return {
start: { line: 0, column: 0 },
end: { line: 0, column: 0 },
};
}
const [start, end] = Range.edges(selection);
return {
start: pointToCursor(start),
end: pointToCursor(end),
};
}

function pointToCursor(point: Point): Cursor {
return {
line: point.path[0],
column: point.offset,
};
}
23 changes: 13 additions & 10 deletions code/server/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,18 @@ module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
transform: {},
moduleNameMapper: pathsToModuleNameMapper({
/*Controllers*/
"@controllers/*": ["./src/controllers/*"],
/*Databases*/
"@database/*": ["./src/database/*"],
/*Others*/
'@src/*': ['./src/*'],
"@domain/*": ["./src/domain/*"],
"@services/*": ["./src/services/*"],
}, { prefix: '<rootDir>/' }),
moduleNameMapper: pathsToModuleNameMapper(
{
/*Controllers*/
'@controllers/*': ['./src/controllers/*'],
/*Databases*/
'@database/*': ['./src/database/*'],
/*Others*/
'@src/*': ['./src/*'],
'@domain/*': ['./src/domain/*'],
'@services/*': ['./src/services/*'],
},
{ prefix: '<rootDir>/' }
),
transformIgnorePatterns: ['/node_modules/(?!@notespace/shared).+\\.js$'],
};
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { Operation } from '@notespace/shared/crdt/types/operations';

function onOperation(service: DocumentService) {
return (socket: Socket, operation: Operation) => {
console.log('operation', operation);
switch (operation.type) {
case 'insert': {
service.insertCharacter(operation);
Expand Down
2 changes: 1 addition & 1 deletion code/server/src/controllers/socket.io/onConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ function onConnection(service: DocumentService, events: Record<string, SocketHan
Object.entries(events).forEach(([event, handler]) => {
socket.on(event, data => {
try {
console.log(event);
// console.log(event);
handler(socket, data);
} catch (e) {
socket.emit('error');
Expand Down
2 changes: 1 addition & 1 deletion code/server/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
/*Others*/
"@src/*": ["./server/src/*"],
"@domain/*": ["./server/src/domain/*"],
"@services/*": ["./server/src/services/*"],
"@services/*": ["./server/src/services/*"]
}
},
"include": ["src/**/*"],
Expand Down

0 comments on commit 2349077

Please sign in to comment.