Skip to content

Commit

Permalink
[WEB-1537] fix: inline code block size fixed for headers, etc (#4709)
Browse files Browse the repository at this point in the history
* fix: inline code block size fixed for headers, etc

* feat: persisting focus accurately post converting the code block into text

* fix: typo in error handling
  • Loading branch information
Palanikannan1437 authored Jun 7, 2024
1 parent 51758b7 commit f565611
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 63 deletions.
68 changes: 8 additions & 60 deletions packages/editor/core/src/lib/editor-commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { startImageUpload } from "src/ui/plugins/image/image-upload-handler";
import { findTableAncestor } from "src/lib/utils";
import { Selection } from "@tiptap/pm/state";
import { UploadImage } from "src/types/upload-image";
import { replaceCodeWithText } from "src/ui/extensions/code/utils/replace-code-block-with-text";

export const setText = (editor: Editor, range?: Range) => {
if (range) editor.chain().focus().deleteRange(range).clearNodes().run();
Expand Down Expand Up @@ -54,81 +55,28 @@ export const toggleUnderline = (editor: Editor, range?: Range) => {
else editor.chain().focus().toggleUnderline().run();
};

const replaceCodeBlockWithContent = (editor: Editor) => {
try {
const { schema } = editor.state;
const { paragraph } = schema.nodes;
let replaced = false;

const replaceCodeBlock = (from: number, to: number, textContent: string) => {
const docSize = editor.state.doc.content.size;

if (from < 0 || to > docSize || from > to) {
console.error("Invalid range for replacement: ", from, to, "in a document of size", docSize);
return;
}

// split the textContent by new lines to handle each line as a separate paragraph
const lines = textContent.split(/\r?\n/);

const tr = editor.state.tr;

// Calculate the position for inserting the first paragraph
let insertPos = from;

// Remove the code block first
tr.delete(from, to);

// For each line, create a paragraph node and insert it
lines.forEach((line) => {
const paragraphNode = paragraph.create({}, schema.text(line));
tr.insert(insertPos, paragraphNode);
// Update insertPos for the next insertion
insertPos += paragraphNode.nodeSize;
});

// Dispatch the transaction
editor.view.dispatch(tr);
replaced = true;
};

editor.state.doc.nodesBetween(editor.state.selection.from, editor.state.selection.to, (node, pos) => {
if (node.type === schema.nodes.codeBlock) {
const startPos = pos;
const endPos = pos + node.nodeSize;
const textContent = node.textContent;
if (textContent.length === 0) {
editor.chain().focus().toggleCodeBlock().run();
}
replaceCodeBlock(startPos, endPos, textContent);
return false;
}
});

if (!replaced) {
console.log("No code block to replace.");
}
} catch (error) {
console.error("An error occurred while replacing code block content:", error);
}
};

export const toggleCodeBlock = (editor: Editor, range?: Range) => {
try {
// if it's a code block, replace it with the code with paragraphs
if (editor.isActive("codeBlock")) {
replaceCodeBlockWithContent(editor);
replaceCodeWithText(editor);
return;
}

const { from, to } = range || editor.state.selection;
const text = editor.state.doc.textBetween(from, to, "\n");
const isMultiline = text.includes("\n");

// if the selection is not a range i.e. empty, then simply convert it into a code block
if (editor.state.selection.empty) {
editor.chain().focus().toggleCodeBlock().run();
} else if (isMultiline) {
// if the selection is multiline, then also replace the text content with
// a code block
editor.chain().focus().deleteRange({ from, to }).insertContentAt(from, `\`\`\`\n${text}\n\`\`\``).run();
} else {
// if the selection is single line, then simply convert it into inline
// code
editor.chain().focus().toggleCode().run();
}
} catch (error) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export const CustomCodeInlineExtension = Mark.create<CodeOptions>({
return {
HTMLAttributes: {
class:
"rounded bg-custom-background-80 px-1 py-[2px] font-mono font-medium text-orange-500 border-[0.5px] border-custom-border-200 text-sm",
"rounded bg-custom-background-80 px-1 py-[2px] font-mono font-medium text-orange-500 border-[0.5px] border-custom-border-200",
spellcheck: "false",
},
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import { Editor, findParentNode } from "@tiptap/core";

type ReplaceCodeBlockParams = {
editor: Editor;
from: number;
to: number;
textContent: string;
cursorPosInsideCodeblock: number;
};

export function replaceCodeWithText(editor: Editor): void {
try {
const { from, to } = editor.state.selection;
const cursorPosInsideCodeblock = from;
let replaced = false;

editor.state.doc.nodesBetween(from, to, (node, pos) => {
if (node.type === editor.state.schema.nodes.codeBlock) {
const startPos = pos;
const endPos = pos + node.nodeSize;
const textContent = node.textContent;

if (textContent.length === 0) {
editor.chain().focus().toggleCodeBlock().run();
} else {
transformCodeBlockToParagraphs({
editor,
from: startPos,
to: endPos,
textContent,
cursorPosInsideCodeblock,
});
}

replaced = true;
return false;
}
});

if (!replaced) {
console.log("No code block to replace.");
}
} catch (error) {
console.error("An error occurred while replacing code block content:", error);
}
}

function transformCodeBlockToParagraphs({
editor,
from,
to,
textContent,
cursorPosInsideCodeblock,
}: ReplaceCodeBlockParams): void {
const { schema } = editor.state;
const { paragraph } = schema.nodes;
const docSize = editor.state.doc.content.size;

if (from < 0 || to > docSize || from > to) {
console.error("Invalid range for replacement: ", from, to, "in a document of size", docSize);
return;
}

// Split the textContent by new lines to handle each line as a separate paragraph for Windows (\r\n) and Unix (\n)
const lines = textContent.split(/\r?\n/);
const tr = editor.state.tr;
let insertPos = from;

// Remove the code block first
tr.delete(from, to);

// For each line, create a paragraph node and insert it
lines.forEach((line) => {
// if the line is empty, create a paragraph node with no content
const paragraphNode = line.length === 0 ? paragraph.create({}) : paragraph.create({}, schema.text(line));
tr.insert(insertPos, paragraphNode);
insertPos += paragraphNode.nodeSize;
});

// Now persist the focus to the converted paragraph
const parentNodeOffset = findParentNode((node) => node.type === schema.nodes.codeBlock)(editor.state.selection)?.pos;

if (parentNodeOffset === undefined) throw new Error("Invalid code block offset");

const lineNumber = getLineNumber(textContent, cursorPosInsideCodeblock, parentNodeOffset);
const cursorPosOutsideCodeblock = cursorPosInsideCodeblock + (lineNumber - 1);

editor.view.dispatch(tr);
editor.chain().focus(cursorPosOutsideCodeblock).run();
}

/**
* Calculates the line number where the cursor is located inside the code block.
* Assumes the indexing of the content inside the code block is like ProseMirror's indexing.
*
* @param {string} textContent - The content of the code block.
* @param {number} cursorPosition - The absolute cursor position in the document.
* @param {number} codeBlockNodePos - The starting position of the code block node in the document.
* @returns {number} The 1-based line number where the cursor is located.
*/
function getLineNumber(textContent: string, cursorPosition: number, codeBlockNodePos: number): number {
// Split the text content into lines, handling both Unix and Windows newlines
const lines = textContent.split(/\r?\n/);
const cursorPosInsideCodeblockRelative = cursorPosition - codeBlockNodePos;

let startPosition = 0;
let lineNumber = 0;

for (let i = 0; i < lines.length; i++) {
// Calculate the end position of the current line
const endPosition = startPosition + lines[i].length + 1; // +1 for the newline character

// Check if the cursor position is within the current line
if (cursorPosInsideCodeblockRelative >= startPosition && cursorPosInsideCodeblockRelative <= endPosition) {
lineNumber = i + 1; // Line numbers are 1-based
break;
}

// Update the start position for the next line
startPosition = endPosition;
}

return lineNumber;
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export const ListKeymap = ({ tabIndex }: { tabIndex?: number }) =>

return handled;
} catch (e) {
console.log("error in handling Backspac:", e);
console.log("Error in handling Delete:", e);
return false;
}
},
Expand Down Expand Up @@ -104,7 +104,7 @@ export const ListKeymap = ({ tabIndex }: { tabIndex?: number }) =>

return handled;
} catch (e) {
console.log("error in handling Backspac:", e);
console.log("Error in handling Backspace:", e);
return false;
}
},
Expand Down

0 comments on commit f565611

Please sign in to comment.