Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
CarsonF committed Aug 2, 2023
2 parents 55b5ba3 + b4be487 commit 3910fd0
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 21 deletions.
4 changes: 2 additions & 2 deletions src/components/RichText/RichTextField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import { getHelperText, showError } from '../form/util';
import { FormattedNumber } from '../Formatters';
import { EditorJsTheme } from './EditorJsTheme';
import type { ToolKey } from './editorJsTools';
import { handleMsUnorderedList } from './ms-word-helpers';
import { handleMsPasteFormatting } from './ms-word-helpers';
import { RichTextView } from './RichTextView';

declare module '@editorjs/editorjs/types/data-formats/output-data' {
Expand Down Expand Up @@ -138,7 +138,7 @@ export function RichTextField({
'paste',
(event: ClipboardEvent) => {
const formattedUl: RichTextData | undefined =
handleMsUnorderedList(event);
handleMsPasteFormatting(event);
if (formattedUl) {
event.preventDefault();
input.onChange(formattedUl);
Expand Down
85 changes: 66 additions & 19 deletions src/components/RichText/ms-word-helpers.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,78 @@
import { OutputData as RichTextData } from '@editorjs/editorjs';

export const handleMsUnorderedList = (
export const handleMsPasteFormatting = (
event: ClipboardEvent
): RichTextData | undefined => {
const text = event.clipboardData?.getData('text');
// If there is no text, or the text has no list return undefined and continue native paste propagation
if (!text || !hasWordListMarkers(text)) {
return;
}

// Match any line that starts with "•\t" followed by anything until the end of the line
const matches = text?.match(/•\t(.*)/g);
const parsedLines = text.split('\n').map((line) => {
if (isUnorderedList(line)) {
return { type: 'ul', text: line.replace(/•\t/, '') };
}
if (isOrderedList(line)) {
return { type: 'ol', text: line.replace(/^\d+\.\s/, '') };
}
if (line === '\r') {
return { type: 'break', text: line };
}
return { type: 'p', text: line };
});

if (!matches) {
return undefined;
}
// If there are matches, prevent default actions, then create an array of strings without the "•\t" prefix
const listItems = matches.map((item) => item.replace(/•\t/, ''));
const groupedLines = groupSiblingsBy(parsedLines, (line) => line.type);

const blocks = groupedLines.map((lines) => {
const type = lines[0]!.type;
const textLines = lines.map((line) => line.text);

if (type === 'ol') {
return createListBlock(textLines, 'ordered');
}
if (type === 'ul') {
return createListBlock(textLines, 'unordered');
}
return createParagraphBlock(textLines);
});

// Now create a new object that conforms to the structure of RichTextData
// Assumption is that all the list items should be part of a single unordered list
return {
time: Date.now(),
blocks: [
{
type: 'list',
data: {
style: 'unordered',
items: listItems,
},
},
],
blocks,
};
};

const groupSiblingsBy = <T>(items: readonly T[], by: (item: T) => unknown) =>
items.reduce((acc: T[][], cur: T) => {
// If it's the first item or different from the last, start a new group
if (!acc.length || by(acc.at(-1)![0]!) !== by(cur)) {
acc.push([cur]);
} else {
// Otherwise, add it to the current group
acc.at(-1)!.push(cur);
}
return acc;
}, []);

const createParagraphBlock = (text: string[]) => ({
type: 'paragraph',
data: {
text: text.join(' '),
},
});

const createListBlock = (items: string[], style: 'unordered' | 'ordered') => ({
type: 'list',
data: {
style: style,
items: items,
},
});

const hasWordListMarkers = (text: string) =>
isUnorderedList(text) || isOrderedList(text);

const isUnorderedList = (text: string) => /•\t(.*)/.test(text);

const isOrderedList = (text: string) => /^\d+\.\s(.*)/.test(text);

0 comments on commit 3910fd0

Please sign in to comment.