Skip to content

Commit

Permalink
fixes export in plain text
Browse files Browse the repository at this point in the history
  • Loading branch information
Darginec05 committed Jul 20, 2024
1 parent 5d9793f commit 991cdfd
Show file tree
Hide file tree
Showing 14 changed files with 136 additions and 65 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
],
"private": true,
"scripts": {
"start": "yarn lerna run start --scope @yoopta/editor --scope @yoopta/starter-kit --scope @yoopta/image --scope @yoopta/video --scope @yoopta/file --parallel --ignore development",
"start": "yarn lerna run start --scope @yoopta/editor --scope @yoopta/starter-kit --scope @yoopta/action-menu-list --scope @yoopta/video --scope @yoopta/file --parallel --ignore development",
"build": "yarn clean && yarn lerna run build --parallel --ignore development",
"clean": "find ./packages -type d -name dist ! -path './packages/development/*' -exec rm -rf {} +",
"serve": "yarn lerna run dev --scope=development",
Expand Down
2 changes: 1 addition & 1 deletion packages/core/editor/src/UI/Overlay/Overlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ type Props = {

const Overlay = ({ className, onClick, children, lockScroll = true, ...rest }: Props) => {
return (
<FloatingOverlay lockScroll={lockScroll} className={className} onClick={onClick} {...rest}>
<FloatingOverlay lockScroll={lockScroll} className={`yoopta-portal ${className}`} onClick={onClick} {...rest}>
{children}
</FloatingOverlay>
);
Expand Down
19 changes: 13 additions & 6 deletions packages/core/editor/src/components/Editor/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import { ReactEditor } from 'slate-react';
import { YooptaBlockPath } from '../../editor/types';
import { useRectangeSelectionBox } from '../SelectionBox/hooks';
import { SelectionBox } from '../SelectionBox/SelectionBox';
import { serializeHTML } from '../../parsers/serializeHTML';
import { Blocks } from '../../editor/blocks';

type Props = {
Expand Down Expand Up @@ -123,7 +122,10 @@ const Editor = ({
};

const onMouseDown = (event: React.MouseEvent) => {
if (isReadOnly) return;
const isTargetInsidePortal =
event.target instanceof HTMLElement && !!event.target.closest('[data-floating-ui-portal]');

if (isReadOnly || isTargetInsidePortal) return;

// if (event.shiftKey) {
// const currentSelectionIndex = editor.selection;
Expand Down Expand Up @@ -183,12 +185,17 @@ const Editor = ({
if (Array.isArray(editor.selectedBlocks) && editor.selectedBlocks.length > 0) {
event.preventDefault();

const htmlString = serializeHTML(editor, editor.getEditorValue());
const blob = new Blob([htmlString], { type: 'text/html' });
const htmlString = editor.getHTML(editor.getEditorValue());
const textString = editor.getPlainText(editor.getEditorValue());
const htmlBlob = new Blob([htmlString], { type: 'text/html' });
const textBlob = new Blob([textString], { type: 'text/plain' });

const item = new ClipboardItem({ 'text/html': blob });
const clipboardItem = new ClipboardItem({
'text/html': htmlBlob,
'text/plain': textBlob,
});

navigator.clipboard.write([item]).then(() => {
navigator.clipboard.write([clipboardItem]).then(() => {
const html = new DOMParser().parseFromString(htmlString, 'text/html');
console.log('HTML copied\n', html.body);
});
Expand Down
9 changes: 8 additions & 1 deletion packages/core/editor/src/editor/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { moveBlock } from './blocks/moveBlock';
import { focusBlock } from './blocks/focusBlock';
import { splitBlock } from './blocks/splitBlock';
import { setSelection } from './selection/setSelection';
import { YooEditor } from './types';
import { YooEditor, YooptaContentValue } from './types';
import { increaseBlockDepth } from './blocks/increaseBlockDepth';
import { decreaseBlockDepth } from './blocks/decreaseBlockDepth';
import { getEditorValue } from './core/getEditorValue';
Expand All @@ -19,6 +19,9 @@ import { focus } from './core/focus';
import { isFocused } from './core/isFocused';
import { deleteBlocks } from './blocks/deleteBlocks';
import { getBlock } from './blocks/getBlock';
import { getHTML } from '../parsers/getHTML';
import { getMarkdown } from '../parsers/getMarkdown';
import { getPlainText } from '../parsers/getPlainText';

// export const YooEditor = {}
// export const BlockTransforms = {}
Expand Down Expand Up @@ -75,6 +78,10 @@ export const createYooptaEditor = (): YooEditor => {
isFocused: () => isFocused(editor),
focus: () => focus(editor),
blur: (...args) => blur(editor, ...args),

getHTML: (content: YooptaContentValue) => getHTML(editor, content),
getMarkdown: (content: YooptaContentValue) => getMarkdown(editor, content),
getPlainText: (content: YooptaContentValue) => getPlainText(editor, content),
};

return editor;
Expand Down
6 changes: 6 additions & 0 deletions packages/core/editor/src/editor/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,15 @@ export type YooEditor<TNodes = any> = {
emit: (event: YooEditorEvents, payload: any) => void;
readOnly: boolean;

// focus handlers
isFocused: () => boolean;
blur: (options?: EditorBlurOptions) => void;
focus: () => void;

// parser handlers
getHTML: (content: YooptaContentValue) => string;
getMarkdown: (content: YooptaContentValue) => string;
getPlainText: (content: YooptaContentValue) => string;
};

// types for slate values
Expand Down
3 changes: 0 additions & 3 deletions packages/core/editor/src/hooks/useForceRender.ts

This file was deleted.

1 change: 0 additions & 1 deletion packages/core/editor/src/parsers/deserializeHTML.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,6 @@ export function deserializeHTML(editor: YooEditor, html: HTMLElement) {
console.log('pasted html', html);

const PLUGINS_NODE_NAME_MATCHERS_MAP = getMappedPluginByNodeNames(editor);
console.log('PLUGINS_NODE_NAME_MATCHERS_MAP', PLUGINS_NODE_NAME_MATCHERS_MAP);

const blocks = deserialize(editor, PLUGINS_NODE_NAME_MATCHERS_MAP, html).filter(isYooptaBlock) as YooptaBlockData[];

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { YooEditor, YooptaContentValue } from '../editor/types';
import { BaseText, Descendant } from 'slate';
import { SlateElement, YooEditor, YooptaContentValue } from '../editor/types';
import { getPluginByInlineElement } from '../utils/blockElements';

const MARKS_NODE_NAME_MATCHERS_MAP = {
Expand Down Expand Up @@ -37,7 +38,7 @@ function serializeChildren(children, plugins) {
.join('');
}

export function serializeHTML(editor: YooEditor, content: YooptaContentValue) {
export function getHTML(editor: YooEditor, content: YooptaContentValue): string {
const blocks = Object.values(content)
.filter((item) => editor.selectedBlocks?.includes(item.meta.order))
.sort((a, b) => a.meta.order - b.meta.order);
Expand All @@ -47,7 +48,7 @@ export function serializeHTML(editor: YooEditor, content: YooptaContentValue) {

if (plugin && plugin.parsers?.html?.serialize) {
const content = serializeChildren(blockData.value[0].children, editor.plugins);
return plugin.parsers.html.serialize(blockData.value[0], content);
return plugin.parsers.html.serialize(blockData.value[0] as SlateElement, content);
}

return '';
Expand Down
30 changes: 30 additions & 0 deletions packages/core/editor/src/parsers/getMarkdown.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { SlateElement, YooEditor, YooptaBlockData, YooptaContentValue } from '@yoopta/editor';

export function serialize(editor: YooEditor, blocksData: YooptaBlockData[]) {
const blocks = blocksData.sort((a, b) => (a.meta.order > b.meta.order ? 1 : -1));

const markdown = blocks.map((blockData) => {
const plugin = editor.plugins[blockData.type];

if (plugin) {
const element = blockData.value[0] as SlateElement;

if (plugin.parsers?.markdown?.serialize) {
const serialized = plugin.parsers.markdown.serialize(
element,
element.children.map((child) => child.text).join(''),
);
if (serialized) return serialized;
}
}

return '';
});

return markdown.join('\n');
}

export function getMarkdown(editor: YooEditor, content: YooptaContentValue) {
const selectedBlocks = Object.values(content);
return serialize(editor, selectedBlocks);
}
10 changes: 10 additions & 0 deletions packages/core/editor/src/parsers/getPlainText.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { YooEditor, YooptaContentValue } from '@yoopta/editor';
import { getHTML } from './getHTML';

export function getPlainText(editor: YooEditor, content: YooptaContentValue) {
const htmlString = getHTML(editor, content);

const div = document.createElement('div');
div.innerHTML = htmlString;
return div.innerText;
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,16 @@ export type StarterKitProps = {
readOnly?: boolean;
autoFocus?: boolean;
className?: string;
placeholder?: string;
style?: CSSProperties;
selectionRef?: React.RefObject<HTMLDivElement> | false;
mediaUploadsFn?: MediaUploadsFn;
media?: MediaUploadsFn;
};

export type MediaUploadsFn = {
image?: (file: File) => Promise<ImageUploadResponse>;
video?: (file: File) => Promise<VideoUploadResponse>;
file?: (file: File) => Promise<FileUploadResponse>;
imageUpload?: (file: File) => Promise<ImageUploadResponse>;
videoUpload?: (file: File) => Promise<VideoUploadResponse>;
fileUpload?: (file: File) => Promise<FileUploadResponse>;
};

function StarterKit({
Expand All @@ -34,7 +35,8 @@ function StarterKit({
readOnly,
autoFocus,
className,
mediaUploadsFn,
placeholder,
media,
selectionRef = false,
}: StarterKitProps) {
const editor = useMemo(() => createYooptaEditor(), []);
Expand All @@ -54,14 +56,15 @@ function StarterKit({
id={id}
selectionBoxRoot={selectionRef}
editor={editor}
plugins={getPlugins({ mediaUploadsFn })}
plugins={getPlugins({ media })}
tools={TOOLS}
marks={MARKS}
value={value}
readOnly={readOnly}
autoFocus={autoFocus}
className={className}
style={style}
placeholder={placeholder}
/>
);
}
Expand Down
16 changes: 8 additions & 8 deletions packages/core/starter-kit/src/utilts/plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ import Code from '@yoopta/code';
import { type MediaUploadsFn } from '../components/StarterKit/StarterKit';

type PluginParams = {
mediaUploadsFn: MediaUploadsFn;
media?: MediaUploadsFn;
};

export const getPlugins = ({ mediaUploadsFn }: PluginParams) => {
export const getPlugins = ({ media }: PluginParams) => {
return [
Paragraph,
Accordion,
Expand All @@ -34,35 +34,35 @@ export const getPlugins = ({ mediaUploadsFn }: PluginParams) => {
Image.extend({
options: {
async onUpload(file) {
if (!mediaUploadsFn?.image) {
if (!media?.imageUpload) {
throw new Error('Image upload function is not provided');
}

const data = await mediaUploadsFn?.image(file);
const data = await media?.imageUpload(file);
return data;
},
},
}),
Video.extend({
options: {
onUpload: async (file) => {
if (!mediaUploadsFn?.video) {
if (!media?.videoUpload) {
throw new Error('Image upload function is not provided');
}

const data = await mediaUploadsFn?.video(file);
const data = await media?.videoUpload(file);
return data;
},
},
}),
File.extend({
options: {
onUpload: async (file) => {
if (!mediaUploadsFn?.file) {
if (!media?.fileUpload) {
throw new Error('Image upload function is not provided');
}

const data = await mediaUploadsFn?.file(file);
const data = await media?.fileUpload(file);
return data;
},
},
Expand Down
79 changes: 45 additions & 34 deletions packages/development/src/pages/dev/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { YooptaBlockData, YooptaContentValue } from '@yoopta/editor';
import { useState } from 'react';
import { useRef, useState } from 'react';

import YooptaEditor from '@yoopta/starter-kit';
import { uploadToCloudinary } from '../../utils/cloudinary';
Expand All @@ -8,42 +8,53 @@ export type YooptaChildrenValue = Record<string, YooptaBlockData>;

const BasicExample = () => {
const [value, setValue] = useState<YooptaContentValue>();
const selectionRef = useRef<HTMLDivElement | null>(null);

return (
<YooptaEditor
value={value}
onChange={(data) => setValue(data)}
style={{ width: 650 }}
mediaUploadsFn={{
image: async (file) => {
const data = await uploadToCloudinary(file, 'image');
<div ref={selectionRef} className="p-20">
<button type="button" onClick={() => console.log('value')}>
GET
</button>
<YooptaEditor
value={value}
onChange={(data) => {
console.log('data', data);
setValue(data);
}}
style={{ width: 650 }}
selectionRef={selectionRef}
placeholder="Start typing here..."
media={{
imageUpload: async (file) => {
const data = await uploadToCloudinary(file, 'image');

return {
src: data.secure_url,
alt: 'cloudinary',
sizes: {
width: data.width,
height: data.height,
},
};
},
file: async (file) => {
const response = await uploadToCloudinary(file, 'auto');
return { src: response.url, name: response.name };
},
video: async (file) => {
const data = await uploadToCloudinary(file, 'video');
return {
src: data.secure_url,
alt: 'cloudinary',
sizes: {
width: data.width,
height: data.height,
},
};
},
}}
/>
return {
src: data.secure_url,
alt: 'cloudinary',
sizes: {
width: data.width,
height: data.height,
},
};
},
fileUpload: async (file) => {
const response = await uploadToCloudinary(file, 'auto');
return { src: response.url, name: response.name };
},
videoUpload: async (file) => {
const data = await uploadToCloudinary(file, 'video');
return {
src: data.secure_url,
alt: 'cloudinary',
sizes: {
width: data.width,
height: data.height,
},
};
},
}}
/>
</div>
);
};

Expand Down
2 changes: 1 addition & 1 deletion packages/plugins/embed/src/plugin/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ const Embed = new YooptaPlugin<EmbedPluginElements, EmbedElementProps, EmbedPlug
}

return `<div style="display: flex; width: 100%; justify-content: center">
<iframe src="${url}" width="${element.props.sizes.width}" height="${element.props.sizes.height}" /> </div>`;
<iframe src="${url}" width="${element.props.sizes.width}" height="${element.props.sizes.height}"></iframe> </div>`;
},
},
markdown: {
Expand Down

0 comments on commit 991cdfd

Please sign in to comment.