mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
Merge branch 'develop' of github.com:makeplane/plane into develop
This commit is contained in:
commit
247720b0d4
@ -746,7 +746,7 @@ class CycleIssueAPIEndpoint(WebhookMixin, BaseAPIView):
|
|||||||
|
|
||||||
class TransferCycleIssueAPIEndpoint(BaseAPIView):
|
class TransferCycleIssueAPIEndpoint(BaseAPIView):
|
||||||
"""
|
"""
|
||||||
This viewset provides `create` actions for transfering the issues into a particular cycle.
|
This viewset provides `create` actions for transferring the issues into a particular cycle.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ interface CustomEditorProps {
|
|||||||
uploadFile: UploadImage;
|
uploadFile: UploadImage;
|
||||||
restoreFile: RestoreImage;
|
restoreFile: RestoreImage;
|
||||||
deleteFile: DeleteImage;
|
deleteFile: DeleteImage;
|
||||||
cancelUploadImage?: () => any;
|
cancelUploadImage?: () => void;
|
||||||
initialValue: string;
|
initialValue: string;
|
||||||
editorClassName: string;
|
editorClassName: string;
|
||||||
// undefined when prop is not passed, null if intentionally passed to stop
|
// undefined when prop is not passed, null if intentionally passed to stop
|
||||||
@ -56,7 +56,7 @@ export const useEditor = ({
|
|||||||
}: CustomEditorProps) => {
|
}: CustomEditorProps) => {
|
||||||
const editor = useCustomEditor({
|
const editor = useCustomEditor({
|
||||||
editorProps: {
|
editorProps: {
|
||||||
...CoreEditorProps(uploadFile, editorClassName),
|
...CoreEditorProps(editorClassName),
|
||||||
...editorProps,
|
...editorProps,
|
||||||
},
|
},
|
||||||
extensions: [
|
extensions: [
|
||||||
@ -69,6 +69,7 @@ export const useEditor = ({
|
|||||||
deleteFile,
|
deleteFile,
|
||||||
restoreFile,
|
restoreFile,
|
||||||
cancelUploadImage,
|
cancelUploadImage,
|
||||||
|
uploadFile,
|
||||||
},
|
},
|
||||||
placeholder,
|
placeholder,
|
||||||
}),
|
}),
|
||||||
@ -89,19 +90,37 @@ export const useEditor = ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// for syncing swr data on tab refocus etc, can remove it once this is merged
|
const editorRef: MutableRefObject<Editor | null> = useRef(null);
|
||||||
// https://github.com/ueberdosis/tiptap/pull/4453
|
|
||||||
|
const [savedSelection, setSavedSelection] = useState<Selection | null>(null);
|
||||||
|
|
||||||
|
// Inside your component or hook
|
||||||
|
const savedSelectionRef = useRef(savedSelection);
|
||||||
|
|
||||||
|
// Update the ref whenever savedSelection changes
|
||||||
|
useEffect(() => {
|
||||||
|
savedSelectionRef.current = savedSelection;
|
||||||
|
}, [savedSelection]);
|
||||||
|
|
||||||
|
// Effect for syncing SWR data
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// value is null when intentionally passed where syncing is not yet
|
// value is null when intentionally passed where syncing is not yet
|
||||||
// supported and value is undefined when the data from swr is not populated
|
// supported and value is undefined when the data from swr is not populated
|
||||||
if (value === null || value === undefined) return;
|
if (value === null || value === undefined) return;
|
||||||
if (editor && !editor.isDestroyed) editor?.commands.setContent(value);
|
if (editor && !editor.isDestroyed && !editor.storage.image.uploadInProgress) {
|
||||||
|
editor.commands.setContent(value);
|
||||||
|
const currentSavedSelection = savedSelectionRef.current;
|
||||||
|
if (currentSavedSelection) {
|
||||||
|
editor.view.focus();
|
||||||
|
const docLength = editor.state.doc.content.size;
|
||||||
|
const relativePosition = Math.min(currentSavedSelection.from, docLength - 1);
|
||||||
|
editor.commands.setTextSelection(relativePosition);
|
||||||
|
} else {
|
||||||
|
editor.commands.focus("end");
|
||||||
|
}
|
||||||
|
}
|
||||||
}, [editor, value, id]);
|
}, [editor, value, id]);
|
||||||
|
|
||||||
const editorRef: MutableRefObject<Editor | null> = useRef(null);
|
|
||||||
|
|
||||||
const [savedSelection, setSavedSelection] = useState<Selection | null>(null);
|
|
||||||
|
|
||||||
useImperativeHandle(
|
useImperativeHandle(
|
||||||
forwardedRef,
|
forwardedRef,
|
||||||
() => ({
|
() => ({
|
||||||
|
@ -126,7 +126,7 @@ export const insertImageCommand = (
|
|||||||
if (input.files?.length) {
|
if (input.files?.length) {
|
||||||
const file = input.files[0];
|
const file = input.files[0];
|
||||||
const pos = savedSelection?.anchor ?? editor.view.state.selection.from;
|
const pos = savedSelection?.anchor ?? editor.view.state.selection.from;
|
||||||
startImageUpload(file, editor.view, pos, uploadFile);
|
startImageUpload(editor, file, editor.view, pos, uploadFile);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
input.click();
|
input.click();
|
||||||
|
@ -236,7 +236,7 @@ div[data-type="horizontalRule"] {
|
|||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
|
|
||||||
& > div {
|
& > div {
|
||||||
border-bottom: 1px solid rgb(var(--color-border-200));
|
border-bottom: 2px solid rgb(var(--color-border-200));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
45
packages/editor/core/src/ui/extensions/drop.tsx
Normal file
45
packages/editor/core/src/ui/extensions/drop.tsx
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import { Extension } from "@tiptap/core";
|
||||||
|
import { Plugin, PluginKey } from "prosemirror-state";
|
||||||
|
import { UploadImage } from "src/types/upload-image";
|
||||||
|
import { startImageUpload } from "../plugins/upload-image";
|
||||||
|
|
||||||
|
export const DropHandlerExtension = (uploadFile: UploadImage) =>
|
||||||
|
Extension.create({
|
||||||
|
name: "dropHandler",
|
||||||
|
priority: 1000,
|
||||||
|
|
||||||
|
addProseMirrorPlugins() {
|
||||||
|
return [
|
||||||
|
new Plugin({
|
||||||
|
key: new PluginKey("dropHandler"),
|
||||||
|
props: {
|
||||||
|
handlePaste: (view, event) => {
|
||||||
|
if (event.clipboardData && event.clipboardData.files && event.clipboardData.files[0]) {
|
||||||
|
event.preventDefault();
|
||||||
|
const file = event.clipboardData.files[0];
|
||||||
|
const pos = view.state.selection.from;
|
||||||
|
startImageUpload(this.editor, file, view, pos, uploadFile);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
handleDrop: (view, event, _slice, moved) => {
|
||||||
|
if (!moved && event.dataTransfer && event.dataTransfer.files && event.dataTransfer.files[0]) {
|
||||||
|
event.preventDefault();
|
||||||
|
const file = event.dataTransfer.files[0];
|
||||||
|
const coordinates = view.posAtCoords({
|
||||||
|
left: event.clientX,
|
||||||
|
top: event.clientY,
|
||||||
|
});
|
||||||
|
if (coordinates) {
|
||||||
|
startImageUpload(this.editor, file, view, coordinates.pos - 1, uploadFile);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
},
|
||||||
|
});
|
@ -28,11 +28,22 @@ export const CustomHorizontalRule = Node.create<HorizontalRuleOptions>({
|
|||||||
group: "block",
|
group: "block",
|
||||||
|
|
||||||
parseHTML() {
|
parseHTML() {
|
||||||
return [{ tag: "hr" }];
|
return [
|
||||||
|
{
|
||||||
|
tag: `div[data-type="${this.name}"]`,
|
||||||
|
},
|
||||||
|
{ tag: "hr" },
|
||||||
|
];
|
||||||
},
|
},
|
||||||
|
|
||||||
renderHTML({ HTMLAttributes }) {
|
renderHTML({ HTMLAttributes }) {
|
||||||
return ["hr", mergeAttributes(this.options.HTMLAttributes, HTMLAttributes)];
|
return [
|
||||||
|
"div",
|
||||||
|
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, {
|
||||||
|
"data-type": this.name,
|
||||||
|
}),
|
||||||
|
["div", {}],
|
||||||
|
];
|
||||||
},
|
},
|
||||||
|
|
||||||
addCommands() {
|
addCommands() {
|
||||||
|
@ -18,7 +18,7 @@ interface ImageNode extends ProseMirrorNode {
|
|||||||
const deleteKey = new PluginKey("delete-image");
|
const deleteKey = new PluginKey("delete-image");
|
||||||
const IMAGE_NODE_TYPE = "image";
|
const IMAGE_NODE_TYPE = "image";
|
||||||
|
|
||||||
export const ImageExtension = (deleteImage: DeleteImage, restoreFile: RestoreImage, cancelUploadImage?: () => any) =>
|
export const ImageExtension = (deleteImage: DeleteImage, restoreFile: RestoreImage, cancelUploadImage?: () => void) =>
|
||||||
ImageExt.extend({
|
ImageExt.extend({
|
||||||
addKeyboardShortcuts() {
|
addKeyboardShortcuts() {
|
||||||
return {
|
return {
|
||||||
@ -28,7 +28,7 @@ export const ImageExtension = (deleteImage: DeleteImage, restoreFile: RestoreIma
|
|||||||
},
|
},
|
||||||
addProseMirrorPlugins() {
|
addProseMirrorPlugins() {
|
||||||
return [
|
return [
|
||||||
UploadImagesPlugin(cancelUploadImage),
|
UploadImagesPlugin(this.editor, cancelUploadImage),
|
||||||
new Plugin({
|
new Plugin({
|
||||||
key: deleteKey,
|
key: deleteKey,
|
||||||
appendTransaction: (transactions: readonly Transaction[], oldState: EditorState, newState: EditorState) => {
|
appendTransaction: (transactions: readonly Transaction[], oldState: EditorState, newState: EditorState) => {
|
||||||
@ -124,6 +124,7 @@ export const ImageExtension = (deleteImage: DeleteImage, restoreFile: RestoreIma
|
|||||||
addStorage() {
|
addStorage() {
|
||||||
return {
|
return {
|
||||||
images: new Map<string, boolean>(),
|
images: new Map<string, boolean>(),
|
||||||
|
uploadInProgress: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -29,6 +29,8 @@ import { CustomCodeInlineExtension } from "src/ui/extensions/code-inline";
|
|||||||
import { CustomTypographyExtension } from "src/ui/extensions/typography";
|
import { CustomTypographyExtension } from "src/ui/extensions/typography";
|
||||||
import { CustomHorizontalRule } from "src/ui/extensions/horizontal-rule/horizontal-rule";
|
import { CustomHorizontalRule } from "src/ui/extensions/horizontal-rule/horizontal-rule";
|
||||||
import { CustomCodeMarkPlugin } from "./custom-code-inline/inline-code-plugin";
|
import { CustomCodeMarkPlugin } from "./custom-code-inline/inline-code-plugin";
|
||||||
|
import { UploadImage } from "src/types/upload-image";
|
||||||
|
import { DropHandlerExtension } from "./drop";
|
||||||
|
|
||||||
type TArguments = {
|
type TArguments = {
|
||||||
mentionConfig: {
|
mentionConfig: {
|
||||||
@ -38,14 +40,15 @@ type TArguments = {
|
|||||||
fileConfig: {
|
fileConfig: {
|
||||||
deleteFile: DeleteImage;
|
deleteFile: DeleteImage;
|
||||||
restoreFile: RestoreImage;
|
restoreFile: RestoreImage;
|
||||||
cancelUploadImage?: () => any;
|
cancelUploadImage?: () => void;
|
||||||
|
uploadFile: UploadImage;
|
||||||
};
|
};
|
||||||
placeholder?: string | ((isFocused: boolean) => string);
|
placeholder?: string | ((isFocused: boolean) => string);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CoreEditorExtensions = ({
|
export const CoreEditorExtensions = ({
|
||||||
mentionConfig,
|
mentionConfig,
|
||||||
fileConfig: { deleteFile, restoreFile, cancelUploadImage },
|
fileConfig: { deleteFile, restoreFile, cancelUploadImage, uploadFile },
|
||||||
placeholder,
|
placeholder,
|
||||||
}: TArguments) => [
|
}: TArguments) => [
|
||||||
StarterKit.configure({
|
StarterKit.configure({
|
||||||
@ -73,10 +76,8 @@ export const CoreEditorExtensions = ({
|
|||||||
width: 1,
|
width: 1,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
// BulletList,
|
|
||||||
// OrderedList,
|
|
||||||
// ListItem,
|
|
||||||
CustomQuoteExtension,
|
CustomQuoteExtension,
|
||||||
|
DropHandlerExtension(uploadFile),
|
||||||
CustomHorizontalRule.configure({
|
CustomHorizontalRule.configure({
|
||||||
HTMLAttributes: {
|
HTMLAttributes: {
|
||||||
class: "my-4 border-custom-border-400",
|
class: "my-4 border-custom-border-400",
|
||||||
|
@ -1,12 +1,22 @@
|
|||||||
|
import { Editor } from "@tiptap/core";
|
||||||
import { EditorState, Plugin, PluginKey } from "@tiptap/pm/state";
|
import { EditorState, Plugin, PluginKey } from "@tiptap/pm/state";
|
||||||
import { Decoration, DecorationSet, EditorView } from "@tiptap/pm/view";
|
import { Decoration, DecorationSet, EditorView } from "@tiptap/pm/view";
|
||||||
import { UploadImage } from "src/types/upload-image";
|
import { UploadImage } from "src/types/upload-image";
|
||||||
|
|
||||||
const uploadKey = new PluginKey("upload-image");
|
const uploadKey = new PluginKey("upload-image");
|
||||||
|
|
||||||
export const UploadImagesPlugin = (cancelUploadImage?: () => any) =>
|
export const UploadImagesPlugin = (editor: Editor, cancelUploadImage?: () => void) => {
|
||||||
new Plugin({
|
let currentView: EditorView | null = null;
|
||||||
|
return new Plugin({
|
||||||
key: uploadKey,
|
key: uploadKey,
|
||||||
|
view(editorView) {
|
||||||
|
currentView = editorView;
|
||||||
|
return {
|
||||||
|
destroy() {
|
||||||
|
currentView = null;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
state: {
|
state: {
|
||||||
init() {
|
init() {
|
||||||
return DecorationSet.empty;
|
return DecorationSet.empty;
|
||||||
@ -27,13 +37,17 @@ export const UploadImagesPlugin = (cancelUploadImage?: () => any) =>
|
|||||||
|
|
||||||
// Create cancel button
|
// Create cancel button
|
||||||
const cancelButton = document.createElement("button");
|
const cancelButton = document.createElement("button");
|
||||||
|
cancelButton.type = "button";
|
||||||
cancelButton.style.position = "absolute";
|
cancelButton.style.position = "absolute";
|
||||||
cancelButton.style.right = "3px";
|
cancelButton.style.right = "3px";
|
||||||
cancelButton.style.top = "3px";
|
cancelButton.style.top = "3px";
|
||||||
cancelButton.setAttribute("class", "opacity-90 rounded-lg");
|
cancelButton.setAttribute("class", "opacity-90 rounded-lg");
|
||||||
|
|
||||||
cancelButton.onclick = () => {
|
cancelButton.onclick = () => {
|
||||||
cancelUploadImage?.();
|
if (currentView) {
|
||||||
|
cancelUploadImage?.();
|
||||||
|
removePlaceholder(editor, currentView, id);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create an SVG element from the SVG string
|
// Create an SVG element from the SVG string
|
||||||
@ -59,6 +73,7 @@ export const UploadImagesPlugin = (cancelUploadImage?: () => any) =>
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
function findPlaceholder(state: EditorState, id: {}) {
|
function findPlaceholder(state: EditorState, id: {}) {
|
||||||
const decos = uploadKey.getState(state);
|
const decos = uploadKey.getState(state);
|
||||||
@ -66,26 +81,38 @@ function findPlaceholder(state: EditorState, id: {}) {
|
|||||||
return found.length ? found[0].from : null;
|
return found.length ? found[0].from : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const removePlaceholder = (view: EditorView, id: {}) => {
|
const removePlaceholder = (editor: Editor, view: EditorView, id: {}) => {
|
||||||
const removePlaceholderTr = view.state.tr.setMeta(uploadKey, {
|
const removePlaceholderTr = view.state.tr.setMeta(uploadKey, {
|
||||||
remove: { id },
|
remove: { id },
|
||||||
});
|
});
|
||||||
view.dispatch(removePlaceholderTr);
|
view.dispatch(removePlaceholderTr);
|
||||||
|
editor.storage.image.uploadInProgress = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function startImageUpload(file: File, view: EditorView, pos: number, uploadFile: UploadImage) {
|
export async function startImageUpload(
|
||||||
|
editor: Editor,
|
||||||
|
file: File,
|
||||||
|
view: EditorView,
|
||||||
|
pos: number,
|
||||||
|
uploadFile: UploadImage
|
||||||
|
) {
|
||||||
|
editor.storage.image.uploadInProgress = true;
|
||||||
|
|
||||||
if (!file) {
|
if (!file) {
|
||||||
alert("No file selected. Please select a file to upload.");
|
alert("No file selected. Please select a file to upload.");
|
||||||
|
editor.storage.image.uploadInProgress = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!file.type.includes("image/")) {
|
if (!file.type.includes("image/")) {
|
||||||
alert("Invalid file type. Please select an image file.");
|
alert("Invalid file type. Please select an image file.");
|
||||||
|
editor.storage.image.uploadInProgress = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (file.size > 5 * 1024 * 1024) {
|
if (file.size > 5 * 1024 * 1024) {
|
||||||
alert("File size too large. Please select a file smaller than 5MB.");
|
alert("File size too large. Please select a file smaller than 5MB.");
|
||||||
|
editor.storage.image.uploadInProgress = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,7 +137,7 @@ export async function startImageUpload(file: File, view: EditorView, pos: number
|
|||||||
// Handle FileReader errors
|
// Handle FileReader errors
|
||||||
reader.onerror = (error) => {
|
reader.onerror = (error) => {
|
||||||
console.error("FileReader error: ", error);
|
console.error("FileReader error: ", error);
|
||||||
removePlaceholder(view, id);
|
removePlaceholder(editor, view, id);
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -121,7 +148,10 @@ export async function startImageUpload(file: File, view: EditorView, pos: number
|
|||||||
const { schema } = view.state;
|
const { schema } = view.state;
|
||||||
pos = findPlaceholder(view.state, id);
|
pos = findPlaceholder(view.state, id);
|
||||||
|
|
||||||
if (pos == null) return;
|
if (pos == null) {
|
||||||
|
editor.storage.image.uploadInProgress = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
const imageSrc = typeof src === "object" ? reader.result : src;
|
const imageSrc = typeof src === "object" ? reader.result : src;
|
||||||
|
|
||||||
const node = schema.nodes.image.create({ src: imageSrc });
|
const node = schema.nodes.image.create({ src: imageSrc });
|
||||||
@ -129,9 +159,9 @@ export async function startImageUpload(file: File, view: EditorView, pos: number
|
|||||||
|
|
||||||
view.dispatch(transaction);
|
view.dispatch(transaction);
|
||||||
view.focus();
|
view.focus();
|
||||||
|
editor.storage.image.uploadInProgress = false;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Upload error: ", error);
|
removePlaceholder(editor, view, id);
|
||||||
removePlaceholder(view, id);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
import { EditorProps } from "@tiptap/pm/view";
|
import { EditorProps } from "@tiptap/pm/view";
|
||||||
import { cn, findTableAncestor } from "src/lib/utils";
|
import { cn } from "src/lib/utils";
|
||||||
import { UploadImage } from "src/types/upload-image";
|
|
||||||
import { startImageUpload } from "src/ui/plugins/upload-image";
|
|
||||||
|
|
||||||
export function CoreEditorProps(uploadFile: UploadImage, editorClassName: string): EditorProps {
|
export function CoreEditorProps(editorClassName: string): EditorProps {
|
||||||
return {
|
return {
|
||||||
attributes: {
|
attributes: {
|
||||||
class: cn(
|
class: cn(
|
||||||
@ -17,45 +15,12 @@ export function CoreEditorProps(uploadFile: UploadImage, editorClassName: string
|
|||||||
if (["ArrowUp", "ArrowDown", "Enter"].includes(event.key)) {
|
if (["ArrowUp", "ArrowDown", "Enter"].includes(event.key)) {
|
||||||
const slashCommand = document.querySelector("#slash-command");
|
const slashCommand = document.querySelector("#slash-command");
|
||||||
if (slashCommand) {
|
if (slashCommand) {
|
||||||
|
console.log("registered");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
handlePaste: (view, event) => {
|
|
||||||
if (typeof window !== "undefined") {
|
|
||||||
const selection: any = window?.getSelection();
|
|
||||||
if (selection.rangeCount !== 0) {
|
|
||||||
const range = selection.getRangeAt(0);
|
|
||||||
if (findTableAncestor(range.startContainer)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (event.clipboardData && event.clipboardData.files && event.clipboardData.files[0]) {
|
|
||||||
event.preventDefault();
|
|
||||||
const file = event.clipboardData.files[0];
|
|
||||||
const pos = view.state.selection.from;
|
|
||||||
startImageUpload(file, view, pos, uploadFile);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
handleDrop: (view, event, _slice, moved) => {
|
|
||||||
if (!moved && event.dataTransfer && event.dataTransfer.files && event.dataTransfer.files[0]) {
|
|
||||||
event.preventDefault();
|
|
||||||
const file = event.dataTransfer.files[0];
|
|
||||||
const coordinates = view.posAtCoords({
|
|
||||||
left: event.clientX,
|
|
||||||
top: event.clientY,
|
|
||||||
});
|
|
||||||
if (coordinates) {
|
|
||||||
startImageUpload(file, view, coordinates.pos - 1, uploadFile);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
transformPastedHTML(html) {
|
transformPastedHTML(html) {
|
||||||
return html.replace(/<img.*?>/g, "");
|
return html.replace(/<img.*?>/g, "");
|
||||||
},
|
},
|
||||||
|
@ -101,21 +101,44 @@ export default function BlockMenu(props: BlockMenuProps) {
|
|||||||
label: "Duplicate",
|
label: "Duplicate",
|
||||||
isDisabled: editor.state.selection.content().content.firstChild?.type.name === "image",
|
isDisabled: editor.state.selection.content().content.firstChild?.type.name === "image",
|
||||||
onClick: (e) => {
|
onClick: (e) => {
|
||||||
const { view } = editor;
|
|
||||||
const { state } = view;
|
|
||||||
const { selection } = state;
|
|
||||||
|
|
||||||
editor
|
|
||||||
.chain()
|
|
||||||
.insertContentAt(selection.to, selection.content().content.firstChild!.toJSON(), {
|
|
||||||
updateSelection: true,
|
|
||||||
})
|
|
||||||
.focus(selection.to + 1, { scrollIntoView: false })
|
|
||||||
.run();
|
|
||||||
|
|
||||||
popup.current?.hide();
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { state } = editor;
|
||||||
|
const { selection } = state;
|
||||||
|
const firstChild = selection.content().content.firstChild;
|
||||||
|
const docSize = state.doc.content.size;
|
||||||
|
|
||||||
|
if (!firstChild) {
|
||||||
|
throw new Error("No content selected or content is not duplicable.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Directly use selection.to as the insertion position
|
||||||
|
const insertPos = selection.to;
|
||||||
|
|
||||||
|
// Ensure the insertion position is within the document's bounds
|
||||||
|
if (insertPos < 0 || insertPos > docSize) {
|
||||||
|
throw new Error("The insertion position is invalid or outside the document.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const contentToInsert = firstChild.toJSON();
|
||||||
|
|
||||||
|
// Insert the content at the calculated position
|
||||||
|
editor
|
||||||
|
.chain()
|
||||||
|
.insertContentAt(insertPos, contentToInsert, {
|
||||||
|
updateSelection: true,
|
||||||
|
})
|
||||||
|
.focus(Math.min(insertPos + 1, docSize), { scrollIntoView: false })
|
||||||
|
.run();
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
console.error(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
popup.current?.hide();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@ -58,10 +58,10 @@ function nodeDOMAtCoords(coords: { x: number; y: number }) {
|
|||||||
[
|
[
|
||||||
"li",
|
"li",
|
||||||
"p:not(:first-child)",
|
"p:not(:first-child)",
|
||||||
"pre",
|
".code-block",
|
||||||
"blockquote",
|
"blockquote",
|
||||||
"h1, h2, h3",
|
"h1, h2, h3",
|
||||||
".table-wrapper",
|
"table",
|
||||||
"[data-type=horizontalRule]",
|
"[data-type=horizontalRule]",
|
||||||
].join(", ")
|
].join(", ")
|
||||||
)
|
)
|
||||||
@ -77,10 +77,25 @@ function nodePosAtDOM(node: Element, view: EditorView, options: DragHandleOption
|
|||||||
})?.inside;
|
})?.inside;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function nodePosAtDOMForBlockquotes(node: Element, view: EditorView) {
|
||||||
|
const boundingRect = node.getBoundingClientRect();
|
||||||
|
|
||||||
|
return view.posAtCoords({
|
||||||
|
left: boundingRect.left + 1,
|
||||||
|
top: boundingRect.top + 1,
|
||||||
|
})?.inside;
|
||||||
|
}
|
||||||
|
|
||||||
function calcNodePos(pos: number, view: EditorView) {
|
function calcNodePos(pos: number, view: EditorView) {
|
||||||
const $pos = view.state.doc.resolve(pos);
|
const maxPos = view.state.doc.content.size;
|
||||||
if ($pos.depth > 1) return $pos.before($pos.depth);
|
const safePos = Math.max(0, Math.min(pos, maxPos));
|
||||||
return pos;
|
const $pos = view.state.doc.resolve(safePos);
|
||||||
|
|
||||||
|
if ($pos.depth > 1) {
|
||||||
|
const newPos = $pos.before($pos.depth);
|
||||||
|
return Math.max(0, Math.min(newPos, maxPos));
|
||||||
|
}
|
||||||
|
return safePos;
|
||||||
}
|
}
|
||||||
|
|
||||||
function DragHandle(options: DragHandleOptions) {
|
function DragHandle(options: DragHandleOptions) {
|
||||||
@ -156,6 +171,20 @@ function DragHandle(options: DragHandleOptions) {
|
|||||||
|
|
||||||
if (!(node instanceof Element)) return;
|
if (!(node instanceof Element)) return;
|
||||||
|
|
||||||
|
if (node.matches("blockquote")) {
|
||||||
|
let nodePosForBlockquotes = nodePosAtDOMForBlockquotes(node, view);
|
||||||
|
if (nodePosForBlockquotes === null || nodePosForBlockquotes === undefined) return;
|
||||||
|
|
||||||
|
const docSize = view.state.doc.content.size;
|
||||||
|
nodePosForBlockquotes = Math.max(0, Math.min(nodePosForBlockquotes, docSize));
|
||||||
|
|
||||||
|
if (nodePosForBlockquotes >= 0 && nodePosForBlockquotes <= docSize) {
|
||||||
|
const nodeSelection = NodeSelection.create(view.state.doc, nodePosForBlockquotes);
|
||||||
|
view.dispatch(view.state.tr.setSelection(nodeSelection));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let nodePos = nodePosAtDOM(node, view, options);
|
let nodePos = nodePosAtDOM(node, view, options);
|
||||||
|
|
||||||
if (nodePos === null || nodePos === undefined) return;
|
if (nodePos === null || nodePos === undefined) return;
|
||||||
@ -244,11 +273,13 @@ function DragHandle(options: DragHandleOptions) {
|
|||||||
|
|
||||||
rect.top += (lineHeight - 20) / 2;
|
rect.top += (lineHeight - 20) / 2;
|
||||||
rect.top += paddingTop;
|
rect.top += paddingTop;
|
||||||
|
|
||||||
// Li markers
|
// Li markers
|
||||||
if (node.matches("ul:not([data-type=taskList]) li, ol li")) {
|
if (node.matches("ul:not([data-type=taskList]) li, ol li")) {
|
||||||
rect.top += 4;
|
rect.top += 4;
|
||||||
rect.left -= 18;
|
rect.left -= 18;
|
||||||
}
|
}
|
||||||
|
|
||||||
rect.width = options.dragHandleWidth;
|
rect.width = options.dragHandleWidth;
|
||||||
|
|
||||||
if (!dragHandleElement) return;
|
if (!dragHandleElement) return;
|
||||||
|
@ -35,7 +35,7 @@ export const OAuthOptions: React.FC<Props> = observer((props) => {
|
|||||||
const response = await authService.socialAuth(socialAuthPayload);
|
const response = await authService.socialAuth(socialAuthPayload);
|
||||||
|
|
||||||
if (response) handleSignInRedirection();
|
if (response) handleSignInRedirection();
|
||||||
} else throw Error("Cant find credentials");
|
} else throw Error("Can't find credentials");
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
title: "Error signing in!",
|
title: "Error signing in!",
|
||||||
@ -56,7 +56,7 @@ export const OAuthOptions: React.FC<Props> = observer((props) => {
|
|||||||
const response = await authService.socialAuth(socialAuthPayload);
|
const response = await authService.socialAuth(socialAuthPayload);
|
||||||
|
|
||||||
if (response) handleSignInRedirection();
|
if (response) handleSignInRedirection();
|
||||||
} else throw Error("Cant find credentials");
|
} else throw Error("Can't find credentials");
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
title: "Error signing in!",
|
title: "Error signing in!",
|
||||||
|
@ -36,7 +36,7 @@ export const OAuthOptions: React.FC<Props> = observer((props) => {
|
|||||||
const response = await authService.socialAuth(socialAuthPayload);
|
const response = await authService.socialAuth(socialAuthPayload);
|
||||||
|
|
||||||
if (response) handleSignInRedirection();
|
if (response) handleSignInRedirection();
|
||||||
} else throw Error("Cant find credentials");
|
} else throw Error("Can't find credentials");
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
setToast({
|
setToast({
|
||||||
type: TOAST_TYPE.ERROR,
|
type: TOAST_TYPE.ERROR,
|
||||||
@ -57,7 +57,7 @@ export const OAuthOptions: React.FC<Props> = observer((props) => {
|
|||||||
const response = await authService.socialAuth(socialAuthPayload);
|
const response = await authService.socialAuth(socialAuthPayload);
|
||||||
|
|
||||||
if (response) handleSignInRedirection();
|
if (response) handleSignInRedirection();
|
||||||
} else throw Error("Cant find credentials");
|
} else throw Error("Can't find credentials");
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
setToast({
|
setToast({
|
||||||
type: TOAST_TYPE.ERROR,
|
type: TOAST_TYPE.ERROR,
|
||||||
|
@ -24,7 +24,7 @@ const RenderIfVisible: React.FC<Props> = (props) => {
|
|||||||
as = "div",
|
as = "div",
|
||||||
children,
|
children,
|
||||||
classNames = "",
|
classNames = "",
|
||||||
alwaysRender = false, //render the children even if it is not visble in root
|
alwaysRender = false, //render the children even if it is not visible in root
|
||||||
placeholderChildren = null, //placeholder children
|
placeholderChildren = null, //placeholder children
|
||||||
pauseHeightUpdateWhileRendering = false, //while this is true the height of the blocks are maintained
|
pauseHeightUpdateWhileRendering = false, //while this is true the height of the blocks are maintained
|
||||||
changingReference, //This is to force render when this reference is changed
|
changingReference, //This is to force render when this reference is changed
|
||||||
@ -48,7 +48,7 @@ const RenderIfVisible: React.FC<Props> = (props) => {
|
|||||||
// } else {
|
// } else {
|
||||||
// setShouldVisible(entries[0].isIntersecting);
|
// setShouldVisible(entries[0].isIntersecting);
|
||||||
// }
|
// }
|
||||||
setShouldVisible(entries[0].isIntersecting);
|
setShouldVisible(entries[entries.length - 1].isIntersecting);
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
root: root?.current,
|
root: root?.current,
|
||||||
|
@ -166,7 +166,7 @@ export const KanbanGroup = (props: IKanbanGroup) => {
|
|||||||
<div
|
<div
|
||||||
id={`${groupId}__${sub_group_id}`}
|
id={`${groupId}__${sub_group_id}`}
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative h-full transition-all",
|
"relative h-full transition-all min-h-[50px]",
|
||||||
{ "bg-custom-background-80": isDraggingOverColumn },
|
{ "bg-custom-background-80": isDraggingOverColumn },
|
||||||
{ "vertical-scrollbar scrollbar-md": !sub_group_by }
|
{ "vertical-scrollbar scrollbar-md": !sub_group_by }
|
||||||
)}
|
)}
|
||||||
|
Loading…
Reference in New Issue
Block a user