Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,44 @@ interface TextEditorProps {
isDisabled?: boolean;
}

// Helper functions to convert between formats
const contentHelpers = {
// Convert content with newlines to ProseMirror nodes
createNodesFromContent: (content: string) => {
if (!content) return [];

const lines = content.split('\n');
const nodes = [];

for (let i = 0; i < lines.length; i++) {
if (lines[i] || i === 0) {
nodes.push(schema.text(lines[i] || ''));
}
if (i < lines.length - 1) {
const hardBreakNode = schema.nodes.hard_break;
if (hardBreakNode) {
nodes.push(hardBreakNode.create());
}
}
}

return nodes;
},

// Convert ProseMirror document to text with newlines
extractContentWithNewlines: (view: EditorView) => {
let content = '';
view.state.doc.descendants((node) => {
if (node.type.name === 'text' && node.text) {
content += node.text || '';
} else if (node.type.name === 'hard_break') {
content += '\n';
}
});
return content;
}
};

export const TextEditor: React.FC<TextEditorProps> = ({
rect,
content,
Expand Down Expand Up @@ -50,7 +88,8 @@ export const TextEditor: React.FC<TextEditorProps> = ({
const newState = view.state.apply(transaction);
view.updateState(newState);
if (onChange && transaction.docChanged) {
onChange(view.state.doc.textContent);
const textContent = contentHelpers.extractContentWithNewlines(view);
onChange(textContent);
}
},
attributes: {
Expand All @@ -60,8 +99,9 @@ export const TextEditor: React.FC<TextEditorProps> = ({

editorViewRef.current = view;

// Set initial content
const paragraph = schema.node('paragraph', null, content ? [schema.text(content)] : []);
// Set initial content with proper line break handling
const nodes = contentHelpers.createNodesFromContent(content);
const paragraph = schema.node('paragraph', null, nodes);
const newDoc = schema.node('doc', null, [paragraph]);
const tr = view.state.tr.replaceWith(0, view.state.doc.content.size, newDoc.content);
view.dispatch(tr);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,24 @@ import { baseKeymap } from 'prosemirror-commands';
import { history, redo, undo } from 'prosemirror-history';
import { keymap } from 'prosemirror-keymap';
import { Schema } from 'prosemirror-model';
import { Plugin } from 'prosemirror-state';
import { Plugin, EditorState } from 'prosemirror-state';
import { EditorView } from 'prosemirror-view';
import { adaptValueToCanvas } from '../utils';

export const schema = new Schema({
nodes: {
doc: { content: 'paragraph+' },
paragraph: {
content: 'text*',
content: '(text | hard_break)*',
toDOM: () => ['p', { style: 'margin: 0; padding: 0;' }, 0],
},
text: { inline: true },
hard_break: {
inline: true,
group: 'inline',
selectable: false,
toDOM: () => ['br'],
},
},
marks: {
style: {
Expand All @@ -34,15 +40,13 @@ export const schema = new Schema({

export function applyStylesToEditor(editorView: EditorView, styles: Record<string, string>) {
const { state, dispatch } = editorView;
const { tr } = state;
const styleMark = state.schema.marks?.style;
if (!styleMark) {
console.error('No style mark found');
return;
}
tr.addMark(0, state.doc.content.size, styleMark.create({ style: styles }));

// Apply container styles
const tr = state.tr.addMark(0, state.doc.content.size, styleMark.create({ style: styles }));
const fontSize = adaptValueToCanvas(parseFloat(styles.fontSize ?? ''));
const lineHeight = adaptValueToCanvas(parseFloat(styles.lineHeight ?? ''));

Expand All @@ -63,31 +67,38 @@ export function applyStylesToEditor(editorView: EditorView, styles: Record<strin
backgroundColor: styles.backgroundColor,
wordBreak: 'break-word',
overflow: 'visible',
height: '100%',
});
editorView.dom.style.height = '100%';
dispatch(tr);
}

// Export common plugins configuration
const createSmartEnterHandler = (onExit: () => void) => (state: EditorState, dispatch?: (tr: any) => void) => {
const { $from } = state.selection;

if ($from.parent.textContent.trim() === '') {
onExit();
return true;
}

if (dispatch) {
const hardBreakNode = state.schema.nodes.hard_break;
if (hardBreakNode) {
dispatch(state.tr.replaceSelectionWith(hardBreakNode.create()));
}
}
return true;
};

export const createEditorPlugins = (onEscape?: () => void, onEnter?: () => void): Plugin[] => [
history(),
keymap({
'Mod-z': undo,
'Mod-shift-z': redo,
Escape: () => {
if (onEscape) {
onEscape();
return true;
}
return false;
},
Enter: () => {
if (onEnter) {
onEnter();
return true;
}
return false;
onEscape?.();
return !!onEscape;
},
Enter: onEnter ? createSmartEnterHandler(onEnter) : () => false,
}),
keymap(baseKeymap),
];
16 changes: 11 additions & 5 deletions apps/web/preload/dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12460,7 +12460,8 @@ function createElement(element) {
newEl.setAttribute(key, value);
}
if (element.textContent !== null && element.textContent !== undefined) {
newEl.textContent = element.textContent;
const htmlContent = element.textContent.replace(/\n/g, "<br>");
newEl.innerHTML = htmlContent;
}
for (const [key, value] of Object.entries(element.styles)) {
newEl.style.setProperty(cssManager.jsToCssProperty(key), value);
Expand Down Expand Up @@ -12898,7 +12899,7 @@ function startEditingText(domId) {
console.warn("Start editing text failed. No target element found for selector:", domId);
return null;
}
const originalContent = el.textContent || "";
const originalContent = extractTextContent(el);
prepareElementForEditing(targetEl);
return { originalContent };
}
Expand All @@ -12920,7 +12921,7 @@ function stopEditingText(domId) {
}
cleanUpElementAfterEditing(el);
publishEditText(getDomElement(el, true));
return { newContent: el.textContent || "", domEl: getDomElement(el, true) };
return { newContent: extractTextContent(el), domEl: getDomElement(el, true) };
}
function prepareElementForEditing(el) {
el.setAttribute("data-onlook-editing-text" /* DATA_ONLOOK_EDITING_TEXT */, "true");
Expand All @@ -12933,7 +12934,12 @@ function removeEditingAttributes(el) {
el.removeAttribute("data-onlook-editing-text" /* DATA_ONLOOK_EDITING_TEXT */);
}
function updateTextContent(el, content) {
el.textContent = content;
const htmlContent = content.replace(/\n/g, "<br>");
el.innerHTML = htmlContent;
}
function extractTextContent(el) {
return el.innerHTML.replace(/<br\s*\/?>/gi, `
`).replace(/<[^>]*>/g, "");
}
function isChildTextEditable(oid) {
return true;
Expand Down Expand Up @@ -17359,5 +17365,5 @@ export {
penpalParent
};

//# debugId=7C5F5140D528C77364756E2164756E21
//# debugId=DDC523D775E2971564756E2164756E21
//# sourceMappingURL=index.js.map
4 changes: 3 additions & 1 deletion apps/web/preload/script/api/elements/dom/insert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,9 @@ export function createElement(element: ActionElement) {
}

if (element.textContent !== null && element.textContent !== undefined) {
newEl.textContent = element.textContent;
// Convert newlines to <br> tags for consistent handling
const htmlContent = element.textContent.replace(/\n/g, '<br>');
newEl.innerHTML = htmlContent;
}

for (const [key, value] of Object.entries(element.styles)) {
Expand Down
13 changes: 10 additions & 3 deletions apps/web/preload/script/api/elements/text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export function startEditingText(domId: string): EditTextResult | null {
console.warn('Start editing text failed. No target element found for selector:', domId);
return null;
}
const originalContent = el.textContent || '';
const originalContent = extractTextContent(el);
prepareElementForEditing(targetEl);

return { originalContent };
Expand All @@ -59,7 +59,7 @@ export function stopEditingText(domId: string): { newContent: string; domEl: Dom
}
cleanUpElementAfterEditing(el);
publishEditText(getDomElement(el, true));
return { newContent: el.textContent || '', domEl: getDomElement(el, true) };
return { newContent: extractTextContent(el), domEl: getDomElement(el, true) };
}

function prepareElementForEditing(el: HTMLElement) {
Expand All @@ -76,7 +76,14 @@ function removeEditingAttributes(el: HTMLElement) {
}

function updateTextContent(el: HTMLElement, content: string): void {
el.textContent = content;
// Convert newlines to <br> tags in the DOM
const htmlContent = content.replace(/\n/g, '<br>');
el.innerHTML = htmlContent;
}

function extractTextContent(el: HTMLElement): string {
// Convert <br> tags back to newlines for the editor
return el.innerHTML.replace(/<br\s*\/?>/gi, '\n').replace(/<[^>]*>/g, '');
}

export function isChildTextEditable(oid: string): boolean | null {
Expand Down