mirror of
https://github.com/penpot/penpot.git
synced 2025-05-18 11:46:11 +02:00
✨ Import text-editor code into the repository
This commit is contained in:
parent
68397edd4d
commit
04a0d867b0
65 changed files with 11112 additions and 7 deletions
92
frontend/text-editor/editor/controllers/ChangeController.js
Normal file
92
frontend/text-editor/editor/controllers/ChangeController.js
Normal file
|
@ -0,0 +1,92 @@
|
|||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright (c) KALEIDOS INC
|
||||
*/
|
||||
|
||||
/**
|
||||
* Change controller is responsible of notifying when a change happens.
|
||||
*/
|
||||
export class ChangeController extends EventTarget {
|
||||
/**
|
||||
* Keeps the timeout id.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
#timeout = null;
|
||||
|
||||
/**
|
||||
* Keeps the time at which we're going to
|
||||
* call the debounced change calls.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
#time = 1000;
|
||||
|
||||
/**
|
||||
* Keeps if we have some pending changes or not.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
#hasPendingChanges = false;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param {number} [time=500]
|
||||
*/
|
||||
constructor(time = 500) {
|
||||
super()
|
||||
if (typeof time === "number" && (!Number.isInteger(time) || time <= 0)) {
|
||||
throw new TypeError("Invalid time");
|
||||
}
|
||||
this.#time = time ?? 500;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates that there are some pending changes.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
get hasPendingChanges() {
|
||||
return this.#hasPendingChanges;
|
||||
}
|
||||
|
||||
#onTimeout = () => {
|
||||
this.dispatchEvent(new Event("change"));
|
||||
};
|
||||
|
||||
/**
|
||||
* Tells the ChangeController that a change has been made
|
||||
* but that you need to delay the notification (and debounce)
|
||||
* for sometime.
|
||||
*/
|
||||
notifyDebounced() {
|
||||
this.#hasPendingChanges = true;
|
||||
clearTimeout(this.#timeout);
|
||||
this.#timeout = setTimeout(this.#onTimeout, this.#time);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells the ChangeController that a change should be notified
|
||||
* immediately.
|
||||
*/
|
||||
notifyImmediately() {
|
||||
clearTimeout(this.#timeout);
|
||||
this.#onTimeout();
|
||||
}
|
||||
|
||||
/**
|
||||
* Disposes the referenced resources.
|
||||
*/
|
||||
dispose() {
|
||||
if (this.hasPendingChanges) {
|
||||
this.notifyImmediately();
|
||||
}
|
||||
clearTimeout(this.#timeout);
|
||||
}
|
||||
}
|
||||
|
||||
export default ChangeController;
|
|
@ -0,0 +1,36 @@
|
|||
import { expect, describe, test, vi } from 'vitest'
|
||||
import ChangeController from './ChangeController'
|
||||
|
||||
describe("ChangeController", () => {
|
||||
test("Creating a ChangeController without a valid time should throw", () => {
|
||||
expect(() => new ChangeController(Infinity)).toThrowError('Invalid time')
|
||||
});
|
||||
|
||||
test("A ChangeController should dispatch an event when `notifyImmediately` is called", () => {
|
||||
const changeListener = vi.fn();
|
||||
const changeController = new ChangeController(10);
|
||||
changeController.addEventListener("change", changeListener)
|
||||
changeController.notifyImmediately();
|
||||
expect(changeController.hasPendingChanges).toBe(false);
|
||||
expect(changeListener).toBeCalled(1);
|
||||
});
|
||||
|
||||
test("A ChangeController should dispatch an event when `notifyDebounced` is called", async () => {
|
||||
return new Promise((resolve) => {
|
||||
const changeController = new ChangeController(10);
|
||||
changeController.addEventListener("change", () => resolve());
|
||||
changeController.notifyDebounced();
|
||||
expect(changeController.hasPendingChanges).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
test("A ChangeController should dispatch an event when `notifyDebounced` is called and disposed is called right after", async () => {
|
||||
return new Promise((resolve) => {
|
||||
const changeController = new ChangeController(10);
|
||||
changeController.addEventListener("change", () => resolve());
|
||||
changeController.notifyDebounced();
|
||||
expect(changeController.hasPendingChanges).toBe(true);
|
||||
changeController.dispose();
|
||||
});
|
||||
});
|
||||
});
|
34
frontend/text-editor/editor/controllers/SafeGuard.js
Normal file
34
frontend/text-editor/editor/controllers/SafeGuard.js
Normal file
|
@ -0,0 +1,34 @@
|
|||
/**
|
||||
* Max. amount of time we should allow.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
const SAFE_GUARD_TIME = 1000;
|
||||
|
||||
/**
|
||||
* Time at which the safeguard started.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
let startTime = Date.now();
|
||||
|
||||
/**
|
||||
* Marks the start of the safeguard.
|
||||
*/
|
||||
export function start() {
|
||||
startTime = Date.now();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the safeguard should throw.
|
||||
*/
|
||||
export function update() {
|
||||
if (Date.now - startTime >= SAFE_GUARD_TIME) {
|
||||
throw new Error('Safe guard timeout');
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
start,
|
||||
update,
|
||||
}
|
1740
frontend/text-editor/editor/controllers/SelectionController.js
Normal file
1740
frontend/text-editor/editor/controllers/SelectionController.js
Normal file
File diff suppressed because it is too large
Load diff
1166
frontend/text-editor/editor/controllers/SelectionController.test.js
Normal file
1166
frontend/text-editor/editor/controllers/SelectionController.test.js
Normal file
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,16 @@
|
|||
/**
|
||||
* Indicates the direction of the selection.
|
||||
*
|
||||
* @readonly
|
||||
* @enum {number}
|
||||
*/
|
||||
export const SelectionDirection = {
|
||||
/** The anchorNode is behind the focusNode */
|
||||
FORWARD: 1,
|
||||
/** The focusNode and the anchorNode are collapsed */
|
||||
NONE: 0,
|
||||
/** The focusNode is behind the anchorNode */
|
||||
BACKWARD: -1,
|
||||
};
|
||||
|
||||
export default SelectionDirection;
|
Loading…
Add table
Add a link
Reference in a new issue