Skip to content

Commit fdcb54f

Browse files
authored
Merge pull request #310 from lblod/feature/better-dom-event-flow
Transactional Edits
2 parents 6fc0178 + 740b13c commit fdcb54f

File tree

346 files changed

+11131
-11178
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

346 files changed

+11131
-11178
lines changed

addon/commands/add-mark-to-range-command.ts

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,35 @@
1-
import Command from '@lblod/ember-rdfa-editor/commands/command';
2-
import ModelRange from '@lblod/ember-rdfa-editor/model/model-range';
3-
import Model from '@lblod/ember-rdfa-editor/model/model';
1+
import Command, {
2+
CommandContext,
3+
} from '@lblod/ember-rdfa-editor/commands/command';
4+
import ModelRange from '@lblod/ember-rdfa-editor/core/model/model-range';
45
import { ModelError } from '@lblod/ember-rdfa-editor/utils/errors';
5-
import { Serializable } from '../model/util/render-spec';
6+
import { Serializable } from '../utils/render-spec';
67

7-
export default class AddMarkToRangeCommand extends Command<
8-
[ModelRange, string],
9-
void
10-
> {
11-
name = 'add-mark-to-range';
8+
declare module '@lblod/ember-rdfa-editor' {
9+
export interface Commands {
10+
addMarkToRange: AddMarkToRangeCommand;
11+
}
12+
}
13+
export interface AddMarkToRangeCommandArgs {
14+
range: ModelRange;
15+
markName: string;
16+
markAttributes?: Record<string, Serializable>;
17+
}
1218

13-
constructor(model: Model) {
14-
super(model);
19+
export default class AddMarkToRangeCommand
20+
implements Command<AddMarkToRangeCommandArgs, void>
21+
{
22+
canExecute(): boolean {
23+
return true;
1524
}
1625

1726
execute(
18-
range: ModelRange,
19-
markName: string,
20-
markAttributes: Record<string, Serializable> = {}
27+
{ transaction }: CommandContext,
28+
{ range, markName, markAttributes = {} }: AddMarkToRangeCommandArgs
2129
): void {
22-
const spec = this.model.marksRegistry.lookupMark(markName);
30+
const spec = transaction.workingCopy.marksRegistry.lookupMark(markName);
2331
if (spec) {
24-
this.model.change((mutator) => {
25-
const resultRange = mutator.addMark(range, spec, markAttributes);
26-
this.model.selectRange(resultRange);
27-
});
32+
transaction.addMark(range, spec, markAttributes);
2833
} else {
2934
throw new ModelError(`Unrecognized mark: ${markName}`);
3035
}

addon/commands/add-mark-to-selection-command.ts

Lines changed: 46 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,66 @@
1-
import Command from '@lblod/ember-rdfa-editor/commands/command';
2-
import Model from '@lblod/ember-rdfa-editor/model/model';
3-
import { Mark } from '@lblod/ember-rdfa-editor/model/mark';
1+
import Command, {
2+
CommandContext,
3+
} from '@lblod/ember-rdfa-editor/commands/command';
4+
import { Mark } from '@lblod/ember-rdfa-editor/core/model/marks/mark';
5+
import ModelSelection from '@lblod/ember-rdfa-editor/core/model/model-selection';
46
import {
57
MisbehavedSelectionError,
68
ModelError,
79
} from '@lblod/ember-rdfa-editor/utils/errors';
8-
import ModelSelection from '@lblod/ember-rdfa-editor/model/model-selection';
9-
import { AttributeSpec, Serializable } from '../model/util/render-spec';
10+
import { AttributeSpec, Serializable } from '../utils/render-spec';
11+
import { CORE_OWNER } from '../utils/constants';
12+
import { SelectionChangedEvent } from '../utils/editor-event';
1013

11-
export default class AddMarkToSelectionCommand extends Command<
12-
[string, Record<string, Serializable>],
13-
void
14-
> {
15-
name = 'add-mark-to-selection';
14+
declare module '@lblod/ember-rdfa-editor' {
15+
export interface Commands {
16+
addMarkToSelection: AddMarkToSelectionCommand;
17+
}
18+
}
1619

17-
constructor(model: Model) {
18-
super(model);
20+
export interface AddMarkToSelectionCommandArgs {
21+
markName: string;
22+
markAttributes?: Record<string, Serializable>;
23+
}
24+
25+
export default class AddMarkToSelectionCommand
26+
implements Command<AddMarkToSelectionCommandArgs, void>
27+
{
28+
canExecute(): boolean {
29+
return true;
1930
}
2031

2132
execute(
22-
markName: string,
23-
markAttributes: Record<string, Serializable> = {}
33+
{ transaction }: CommandContext,
34+
{ markName, markAttributes = {} }: AddMarkToSelectionCommandArgs
2435
): void {
25-
const selection = this.model.selection;
26-
const spec = this.model.marksRegistry.lookupMark(markName);
36+
const selection = transaction.workingCopy.selection;
37+
const spec = transaction.workingCopy.marksRegistry.lookupMark(markName);
2738
if (spec) {
2839
if (selection.isCollapsed) {
29-
selection.addMark(new Mark<AttributeSpec>(spec, markAttributes));
30-
this.model.rootNode.focus();
31-
this.model.emitSelectionChanged();
40+
transaction.addMarkToSelection(
41+
new Mark<AttributeSpec>(spec, markAttributes)
42+
);
43+
44+
// TODO
45+
// this.model.rootNode.focus();
46+
// this.model.emitSelectionChanged();
3247
} else {
3348
if (!ModelSelection.isWellBehaved(selection)) {
3449
throw new MisbehavedSelectionError();
3550
}
36-
this.model.change((mutator) => {
37-
const resultRange = mutator.addMark(
38-
selection.lastRange,
39-
spec,
40-
markAttributes
41-
);
42-
this.model.selectRange(resultRange);
43-
});
51+
const resultRange = transaction.addMark(
52+
selection.lastRange,
53+
spec,
54+
markAttributes
55+
);
56+
transaction.selectRange(resultRange);
4457
}
58+
transaction.workingCopy.eventBus.emit(
59+
new SelectionChangedEvent({
60+
owner: CORE_OWNER,
61+
payload: transaction.workingCopy.selection,
62+
})
63+
);
4564
} else {
4665
throw new ModelError(`Unrecognized mark: ${markName}`);
4766
}

addon/commands/command-manager.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import {
2+
CanExecuteArgs,
3+
CommandName,
4+
Commands,
5+
ExecuteArgs,
6+
ExecuteReturn,
7+
} from '@lblod/ember-rdfa-editor';
8+
import Transaction from '../core/state/transaction';
9+
export type WrappedCommand<N extends CommandName> = {
10+
(args: ExecuteArgs<N>): ExecuteReturn<N>;
11+
canExecute: (args: CanExecuteArgs<N>) => boolean;
12+
};
13+
export type CommandExecutor = {
14+
[key in keyof Commands]: WrappedCommand<key>;
15+
};
16+
export default class CommandManager {}
17+
export function wrapCommand<N extends CommandName>(
18+
name: N,
19+
command: Commands[N],
20+
transaction: Transaction
21+
) {
22+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
23+
const wrapped = function (args: any) {
24+
return command.execute({ transaction }, args);
25+
} as WrappedCommand<N>;
26+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
27+
wrapped.canExecute = function (args: any) {
28+
return command.canExecute(transaction.workingCopy, args);
29+
};
30+
return [name, wrapped];
31+
}
32+
33+
export function commandMapToCommandExecutor(
34+
commands: Partial<Commands>,
35+
transaction: Transaction
36+
): CommandExecutor {
37+
const entries = Object.entries(commands) as [
38+
CommandName,
39+
Commands[CommandName]
40+
][];
41+
const mapped = entries.map(([commandName, command]) =>
42+
wrapCommand(commandName, command, transaction)
43+
);
44+
return Object.fromEntries(mapped) as CommandExecutor;
45+
}

addon/commands/command.ts

Lines changed: 22 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,26 @@
1-
import Model from '@lblod/ember-rdfa-editor/model/model';
2-
import {
3-
createLogger,
4-
Logger,
5-
} from '@lblod/ember-rdfa-editor/utils/logging-utils';
1+
import State from '../core/state';
2+
import Transaction from '../core/state/transaction';
63

7-
/**
8-
* Commands are the only things that are allowed to modify the model.
9-
* TODO: Currently this restriction is not enforced yet.
10-
* They need to be registered with {@link RawEditor.registerCommand()} before they
11-
* can be executed with {@link RawEditor.executeCommand()}.
12-
*/
13-
export default abstract class Command<
14-
A extends unknown[] = unknown[],
15-
R = void
16-
> {
17-
abstract name: string;
18-
protected model: Model;
19-
protected logger: Logger;
20-
createSnapshot: boolean;
21-
22-
protected constructor(model: Model, createSnapshot = true) {
23-
this.model = model;
24-
this.createSnapshot = createSnapshot;
25-
this.logger = createLogger(`command:${this.constructor.name}`);
26-
}
4+
export interface CommandContext {
5+
transaction: Transaction;
6+
}
277

28-
canExecute(..._args: A): boolean {
29-
return true;
30-
}
8+
/**
9+
* Represents an editing action at the highest level, and are built on top of the @link{Transaction} primitive.
10+
* You can think of it this way:
11+
* if a series of edits made with transactions would be a private script,
12+
* turning them into a command would be to turn that script into a real, published package
13+
*
14+
* Emacs users might also consider them "interactive" functions as opposed to non-interactive ones.
15+
*
16+
* Essentially commands are the answer to the question "Hey editor, what can you do?"
17+
* Whereas transactions are the answer to the followup question "Ah but what if I wanted to do _this other thing_?"
18+
*
19+
* You may encounter commands that do not follow this logic, that's because commands predate
20+
* this insight.
21+
* */
22+
export default interface Command<A, R> {
23+
canExecute(state: State, args: A): boolean;
3124

32-
abstract execute(...args: A): R;
25+
execute(context: CommandContext, args: A): R;
3326
}
Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import SelectionCommand from '@lblod/ember-rdfa-editor/commands/selection-command';
2-
import Model from '@lblod/ember-rdfa-editor/model/model';
32

3+
declare module '@lblod/ember-rdfa-editor' {
4+
export interface Commands {
5+
deleteSelection: DeleteSelectionCommand;
6+
}
7+
}
48
export default class DeleteSelectionCommand extends SelectionCommand {
5-
name = 'delete-selection';
6-
7-
constructor(model: Model) {
8-
super(model, true);
9+
constructor() {
10+
super(true);
911
}
1012
}

0 commit comments

Comments
 (0)