tangled
alpha
login
or
join now
leaflet.pub
/
leaflet
289
fork
atom
a tool for shared writing and social publishing
289
fork
atom
overview
issues
28
pulls
pipelines
add bulk block text decoration shortcuts
awarm.space
5 months ago
f54569c9
3035d0c7
+88
-7
2 changed files
expand all
collapse all
unified
split
components
Blocks
TextBlock
index.tsx
SelectionManager.tsx
+9
-7
components/Blocks/TextBlock/index.tsx
···
257
257
let oldEditorState = this.state;
258
258
let newState = this.state.apply(tr);
259
259
let addToHistory = tr.getMeta("addToHistory");
260
260
+
let isBulkOp = tr.getMeta("bulkOp");
260
261
let docHasChanges = tr.steps.length !== 0 || tr.docChanged;
261
262
if (addToHistory !== false && docHasChanges) {
262
263
if (actionTimeout.current) {
263
264
window.clearTimeout(actionTimeout.current);
264
265
} else {
265
265
-
rep.undoManager.startGroup();
266
266
+
if (!isBulkOp) rep.undoManager.startGroup();
266
267
}
267
268
268
268
-
actionTimeout.current = window.setTimeout(() => {
269
269
-
rep.undoManager.endGroup();
270
270
-
actionTimeout.current = null;
271
271
-
}, 200);
269
269
+
if (!isBulkOp)
270
270
+
actionTimeout.current = window.setTimeout(() => {
271
271
+
rep.undoManager.endGroup();
272
272
+
actionTimeout.current = null;
273
273
+
}, 200);
272
274
rep.undoManager.add({
273
275
redo: () => {
274
276
useEditorStates.setState((oldState) => {
275
277
let view = oldState.editorStates[props.entityID]?.view;
276
276
-
if (!view?.hasFocus()) view?.focus();
278
278
+
if (!view?.hasFocus() && !isBulkOp) view?.focus();
277
279
return {
278
280
editorStates: {
279
281
...oldState.editorStates,
···
288
290
undo: () => {
289
291
useEditorStates.setState((oldState) => {
290
292
let view = oldState.editorStates[props.entityID]?.view;
291
291
-
if (!view?.hasFocus()) view?.focus();
293
293
+
if (!view?.hasFocus() && !isBulkOp) view?.focus();
292
294
return {
293
295
editorStates: {
294
296
...oldState.editorStates,
+79
components/SelectionManager.tsx
···
19
19
import { useIsMobile } from "src/hooks/isMobile";
20
20
import { deleteBlock } from "./Blocks/DeleteBlock";
21
21
import { Replicache } from "replicache";
22
22
+
import { schema } from "./Blocks/TextBlock/schema";
23
23
+
import { TextSelection } from "prosemirror-state";
24
24
+
import { MarkType } from "prosemirror-model";
22
25
export const useSelectingMouse = create(() => ({
23
26
start: null as null | string,
24
27
}));
···
179
182
},
180
183
{
181
184
metaKey: true,
185
185
+
key: "u",
186
186
+
handler: async () => {
187
187
+
let [sortedBlocks] = await getSortedSelectionBound();
188
188
+
toggleMarkInBlocks(
189
189
+
schema.marks.underline,
190
190
+
sortedBlocks.filter((b) => b.type === "text").map((b) => b.value),
191
191
+
);
192
192
+
},
193
193
+
},
194
194
+
{
195
195
+
metaKey: true,
196
196
+
key: "i",
197
197
+
handler: async () => {
198
198
+
let [sortedBlocks] = await getSortedSelectionBound();
199
199
+
toggleMarkInBlocks(
200
200
+
schema.marks.em,
201
201
+
sortedBlocks.filter((b) => b.type === "text").map((b) => b.value),
202
202
+
);
203
203
+
},
204
204
+
},
205
205
+
{
206
206
+
metaKey: true,
207
207
+
key: "b",
208
208
+
handler: async () => {
209
209
+
let [sortedBlocks] = await getSortedSelectionBound();
210
210
+
toggleMarkInBlocks(
211
211
+
schema.marks.strong,
212
212
+
sortedBlocks.filter((b) => b.type === "text").map((b) => b.value),
213
213
+
);
214
214
+
},
215
215
+
},
216
216
+
{
217
217
+
metaKey: true,
218
218
+
shift: true,
219
219
+
key: "X",
220
220
+
handler: async () => {
221
221
+
let [sortedBlocks] = await getSortedSelectionBound();
222
222
+
toggleMarkInBlocks(
223
223
+
schema.marks.strikethrough,
224
224
+
sortedBlocks.filter((b) => b.type === "text").map((b) => b.value),
225
225
+
);
226
226
+
},
227
227
+
},
228
228
+
{
229
229
+
metaKey: true,
182
230
shift: true,
183
231
key: "Enter",
184
232
handler: async () => {
···
463
511
}
464
512
if ((e.key === "c" || e.key === "x") && (e.metaKey || e.ctrlKey)) {
465
513
if (!rep) return;
514
514
+
if (e.shiftKey) return;
466
515
let [, , selectionWithFoldedChildren] =
467
516
await getSortedSelectionBound();
468
517
if (!selectionWithFoldedChildren) return;
···
667
716
sortedBlocksWithChildren,
668
717
];
669
718
};
719
719
+
720
720
+
function toggleMarkInBlocks(mark: MarkType, blocks: string[]) {
721
721
+
let everyBlockHasMark = blocks.reduce((acc, block) => {
722
722
+
let editor = useEditorStates.getState().editorStates[block];
723
723
+
if (!editor) return acc;
724
724
+
let { view } = editor;
725
725
+
let from = 0;
726
726
+
let to = view.state.doc.content.size;
727
727
+
let hasMarkInRange = view.state.doc.rangeHasMark(from, to, mark);
728
728
+
return acc && hasMarkInRange;
729
729
+
}, true);
730
730
+
for (let block of blocks) {
731
731
+
let editor = useEditorStates.getState().editorStates[block];
732
732
+
if (!editor) return;
733
733
+
let { view } = editor;
734
734
+
let tr = view.state.tr;
735
735
+
736
736
+
let from = 0;
737
737
+
let to = view.state.doc.content.size;
738
738
+
739
739
+
tr.setMeta("bulkOp", true);
740
740
+
if (everyBlockHasMark) {
741
741
+
tr.removeMark(from, to, mark);
742
742
+
} else {
743
743
+
tr.addMark(from, to, mark.create());
744
744
+
}
745
745
+
746
746
+
view.dispatch(tr);
747
747
+
}
748
748
+
}