mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
[WEB-1537] fix: inline code block size fixed for headers, etc (#4709)
* 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
This commit is contained in:
parent
51758b774e
commit
f5656111ee
@ -3,6 +3,7 @@ import { startImageUpload } from "src/ui/plugins/image/image-upload-handler";
|
|||||||
import { findTableAncestor } from "src/lib/utils";
|
import { findTableAncestor } from "src/lib/utils";
|
||||||
import { Selection } from "@tiptap/pm/state";
|
import { Selection } from "@tiptap/pm/state";
|
||||||
import { UploadImage } from "src/types/upload-image";
|
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) => {
|
export const setText = (editor: Editor, range?: Range) => {
|
||||||
if (range) editor.chain().focus().deleteRange(range).clearNodes().run();
|
if (range) editor.chain().focus().deleteRange(range).clearNodes().run();
|
||||||
@ -54,69 +55,11 @@ export const toggleUnderline = (editor: Editor, range?: Range) => {
|
|||||||
else editor.chain().focus().toggleUnderline().run();
|
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) => {
|
export const toggleCodeBlock = (editor: Editor, range?: Range) => {
|
||||||
try {
|
try {
|
||||||
|
// if it's a code block, replace it with the code with paragraphs
|
||||||
if (editor.isActive("codeBlock")) {
|
if (editor.isActive("codeBlock")) {
|
||||||
replaceCodeBlockWithContent(editor);
|
replaceCodeWithText(editor);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,11 +67,16 @@ export const toggleCodeBlock = (editor: Editor, range?: Range) => {
|
|||||||
const text = editor.state.doc.textBetween(from, to, "\n");
|
const text = editor.state.doc.textBetween(from, to, "\n");
|
||||||
const isMultiline = text.includes("\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) {
|
if (editor.state.selection.empty) {
|
||||||
editor.chain().focus().toggleCodeBlock().run();
|
editor.chain().focus().toggleCodeBlock().run();
|
||||||
} else if (isMultiline) {
|
} 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();
|
editor.chain().focus().deleteRange({ from, to }).insertContentAt(from, `\`\`\`\n${text}\n\`\`\``).run();
|
||||||
} else {
|
} else {
|
||||||
|
// if the selection is single line, then simply convert it into inline
|
||||||
|
// code
|
||||||
editor.chain().focus().toggleCode().run();
|
editor.chain().focus().toggleCode().run();
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -33,7 +33,7 @@ export const CustomCodeInlineExtension = Mark.create<CodeOptions>({
|
|||||||
return {
|
return {
|
||||||
HTMLAttributes: {
|
HTMLAttributes: {
|
||||||
class:
|
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",
|
spellcheck: "false",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -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;
|
||||||
|
}
|
@ -69,7 +69,7 @@ export const ListKeymap = ({ tabIndex }: { tabIndex?: number }) =>
|
|||||||
|
|
||||||
return handled;
|
return handled;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log("error in handling Backspac:", e);
|
console.log("Error in handling Delete:", e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -104,7 +104,7 @@ export const ListKeymap = ({ tabIndex }: { tabIndex?: number }) =>
|
|||||||
|
|
||||||
return handled;
|
return handled;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log("error in handling Backspac:", e);
|
console.log("Error in handling Backspace:", e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user