Merge pull request #3056 from makeplane/promote/develop-preview

Promote: Develop to Preview
This commit is contained in:
sriram veeraghanta 2023-12-10 15:51:41 +05:30 committed by GitHub
commit 0f28008fa5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
723 changed files with 3889 additions and 4795 deletions

View File

@ -1,8 +1,5 @@
# Helm Chart
Click on the below link to access the helm chart instructions.
[![Artifact Hub](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/makeplane)](https://artifacthub.io/packages/search?repo=makeplane)

View File

@ -7,12 +7,14 @@ We will cover two main options for setting up your self-hosted environment: usin
Let's get started!
## Setting up Docker Environment
<details>
<summary>Option 1 - Using Cloud Server</summary>
<p>Best way to start is to create EC2 maching on AWS. It must of minimum t3.medium/t3a/medium</p>
<p>Run the below command to install docker engine.</p>
```curl -fsSL https://get.docker.com -o install-docker.sh```
`curl -fsSL https://get.docker.com -o install-docker.sh`
</details>
---
@ -21,6 +23,7 @@ Let's get started!
<summary>Option 2 - Using Desktop</summary>
#### For Mac
<ol>
<li> Download Docker Desktop for Mac from the <a href="https://hub.docker.com/editions/community/docker-ce-desktop-mac/" target="_blank">Docker Hub</a>. </li>
<li> Double-click the downloaded `.dmg` file and drag the Docker app icon to the Applications folder. </li>
@ -28,6 +31,7 @@ Let's get started!
</ol>
#### For Windows:
<ol>
<li>Download Docker Desktop for Windows from the <a href="https://hub.docker.com/editions/community/docker-ce-desktop-windows/" target="_blank">Docker Hub</a>.</li>
<li>Run the installer and follow the instructions. You might be asked to enable Hyper-V and "Containers" Windows features.</li>
@ -35,6 +39,7 @@ Let's get started!
</ol>
After installation, you can verify the installation by opening a terminal (Command Prompt on Windows, Terminal app on Mac) and running the command `docker --version`. This should display the installed version of Docker.
</details>
---
@ -44,6 +49,7 @@ Let's get started!
Installing plane is a very easy and minimal step process.
### Prerequisite
- Docker installed and running
- OS with bash scripting enabled (Ubuntu, Linux AMI, macos). Windows systems need to have [gitbash](https://git-scm.com/download/win)
- User context used must have access to docker services. In most cases, use sudo su to switch as root user
@ -103,6 +109,7 @@ Action [2]: 1
For the 1st time setup, type "1" as action input.
This will create a create a folder `plane-app` or `plane-app-preview` (in case of preview deployment) and will download 2 files inside that
- `docker-compose.yaml`
- `.env`
@ -113,7 +120,7 @@ Again the `options [1-6]` will be popped up and this time hit `6` to exit.
### Continue with setup - Environment Settings
Before proceeding, we suggest used to review `.env` file and set the values.
Below are the most import keys you must refer to. *<span style="color: #fcba03">You can use any text editor to edit this file</span>*.
Below are the most import keys you must refer to. _<span style="color: #fcba03">You can use any text editor to edit this file</span>_.
> `NGINX_PORT` - This is default set to `80`. Make sure the port you choose to use is not preoccupied. (e.g `NGINX_PORT=8080`)
@ -232,7 +239,6 @@ Once done, choose `6` to exit from prompt.
Once done with making changes in `.env` file, jump on to `Start Server`
## Upgrading from v0.13.2 to v0.14.x
This is one time activity for users who are upgrading from v0.13.2 to v0.14.0
@ -289,9 +295,9 @@ For every command you must see 2 records something like shown in above example o
To move forward, you would need PREFIX of old setup and new setup. As per above example, `v0132` is the prefix of v0.13.2 and `plane-app` is the prefix of v0.14.0 setup
**Back to original terminal window**, *Provide the Source Volume Prefix* and hit ENTER.
**Back to original terminal window**, _Provide the Source Volume Prefix_ and hit ENTER.
Now you will be prompted to *Provide Destination Volume Prefix*. Provide the value and hit ENTER
Now you will be prompted to _Provide Destination Volume Prefix_. Provide the value and hit ENTER
```
Provide the Source Volume Prefix : v0132
@ -305,5 +311,3 @@ In case the suffixes are wrong or the mentioned volumes are not found, you will
In case of successful migration, it will be a silent exit without error.
Now its time to restart v0.14.0 setup.

View File

@ -27,7 +27,7 @@
"prettier": "latest",
"prettier-plugin-tailwindcss": "^0.5.4",
"tailwindcss": "^3.3.3",
"turbo": "^1.10.16"
"turbo": "^1.11.1"
},
"resolutions": {
"@types/react": "18.2.42"

View File

@ -0,0 +1,4 @@
module.exports = {
root: true,
extends: ["custom"],
};

View File

@ -0,0 +1,6 @@
.next
.vercel
.tubro
out/
dis/
build/

View File

@ -0,0 +1,5 @@
{
"printWidth": 120,
"tabWidth": 2,
"trailingComma": "es5"
}

View File

@ -4,35 +4,17 @@ import { startImageUpload } from "../ui/plugins/upload-image";
import { findTableAncestor } from "./utils";
export const toggleHeadingOne = (editor: Editor, range?: Range) => {
if (range)
editor
.chain()
.focus()
.deleteRange(range)
.setNode("heading", { level: 1 })
.run();
if (range) editor.chain().focus().deleteRange(range).setNode("heading", { level: 1 }).run();
else editor.chain().focus().toggleHeading({ level: 1 }).run();
};
export const toggleHeadingTwo = (editor: Editor, range?: Range) => {
if (range)
editor
.chain()
.focus()
.deleteRange(range)
.setNode("heading", { level: 2 })
.run();
if (range) editor.chain().focus().deleteRange(range).setNode("heading", { level: 2 }).run();
else editor.chain().focus().toggleHeading({ level: 2 }).run();
};
export const toggleHeadingThree = (editor: Editor, range?: Range) => {
if (range)
editor
.chain()
.focus()
.deleteRange(range)
.setNode("heading", { level: 3 })
.run();
if (range) editor.chain().focus().deleteRange(range).setNode("heading", { level: 3 }).run();
else editor.chain().focus().toggleHeading({ level: 3 }).run();
};
@ -57,8 +39,7 @@ export const toggleCodeBlock = (editor: Editor, range?: Range) => {
};
export const toggleOrderedList = (editor: Editor, range?: Range) => {
if (range)
editor.chain().focus().deleteRange(range).toggleOrderedList().run();
if (range) editor.chain().focus().deleteRange(range).toggleOrderedList().run();
else editor.chain().focus().toggleOrderedList().run();
};
@ -78,21 +59,8 @@ export const toggleStrike = (editor: Editor, range?: Range) => {
};
export const toggleBlockquote = (editor: Editor, range?: Range) => {
if (range)
editor
.chain()
.focus()
.deleteRange(range)
.toggleNode("paragraph", "paragraph")
.toggleBlockquote()
.run();
else
editor
.chain()
.focus()
.toggleNode("paragraph", "paragraph")
.toggleBlockquote()
.run();
if (range) editor.chain().focus().deleteRange(range).toggleNode("paragraph", "paragraph").toggleBlockquote().run();
else editor.chain().focus().toggleNode("paragraph", "paragraph").toggleBlockquote().run();
};
export const insertTableCommand = (editor: Editor, range?: Range) => {
@ -105,19 +73,8 @@ export const insertTableCommand = (editor: Editor, range?: Range) => {
}
}
}
if (range)
editor
.chain()
.focus()
.deleteRange(range)
.insertTable({ rows: 3, cols: 3, withHeaderRow: true })
.run();
else
editor
.chain()
.focus()
.insertTable({ rows: 3, cols: 3, withHeaderRow: true })
.run();
if (range) editor.chain().focus().deleteRange(range).insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run();
else editor.chain().focus().insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run();
};
export const unsetLinkEditor = (editor: Editor) => {
@ -131,10 +88,8 @@ export const setLinkEditor = (editor: Editor, url: string) => {
export const insertImageCommand = (
editor: Editor,
uploadFile: UploadImage,
setIsSubmitting?: (
isSubmitting: "submitting" | "submitted" | "saved",
) => void,
range?: Range,
setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void,
range?: Range
) => {
if (range) editor.chain().focus().deleteRange(range).run();
const input = document.createElement("input");

View File

@ -6,25 +6,19 @@ interface EditorClassNames {
customClassName?: string;
}
export const getEditorClassNames = ({
noBorder,
borderOnFocus,
customClassName,
}: EditorClassNames) =>
export const getEditorClassNames = ({ noBorder, borderOnFocus, customClassName }: EditorClassNames) =>
cn(
"relative w-full max-w-full sm:rounded-lg mt-2 p-3 relative focus:outline-none rounded-md",
noBorder ? "" : "border border-custom-border-200",
borderOnFocus ? "focus:border border-custom-border-300" : "focus:border-0",
customClassName,
customClassName
);
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
export const findTableAncestor = (
node: Node | null,
): HTMLTableElement | null => {
export const findTableAncestor = (node: Node | null): HTMLTableElement | null => {
while (node !== null && node.nodeName !== "TABLE") {
node = node.parentNode;
}

View File

@ -7,11 +7,7 @@ interface EditorContainerProps {
children: ReactNode;
}
export const EditorContainer = ({
editor,
editorClassNames,
children,
}: EditorContainerProps) => (
export const EditorContainer = ({ editor, editorClassNames, children }: EditorContainerProps) => (
<div
id="editor-container"
onClick={() => {

View File

@ -8,16 +8,10 @@ interface EditorContentProps {
children?: ReactNode;
}
export const EditorContentWrapper = ({
editor,
editorContentCustomClassNames = "",
children,
}: EditorContentProps) => (
export const EditorContentWrapper = ({ editor, editorContentCustomClassNames = "", children }: EditorContentProps) => (
<div className={`contentEditor ${editorContentCustomClassNames}`}>
<EditorContent editor={editor} />
{editor?.isActive("image") && editor?.isEditable && (
<ImageResizer editor={editor} />
)}
{editor?.isActive("image") && editor?.isEditable && <ImageResizer editor={editor} />}
{children}
</div>
);

View File

@ -2,10 +2,7 @@ import { getNodeType } from "@tiptap/core";
import { NodeType } from "@tiptap/pm/model";
import { EditorState } from "@tiptap/pm/state";
export const findListItemPos = (
typeOrName: string | NodeType,
state: EditorState,
) => {
export const findListItemPos = (typeOrName: string | NodeType, state: EditorState) => {
const { $from } = state.selection;
const nodeType = getNodeType(typeOrName, state.schema);

View File

@ -10,11 +10,7 @@ export const getNextListDepth = (typeOrName: string, state: EditorState) => {
return false;
}
const [, depth] = getNodeAtPosition(
state,
typeOrName,
listItemPos.$pos.pos + 4,
);
const [, depth] = getNodeAtPosition(state, typeOrName, listItemPos.$pos.pos + 4);
return depth;
};

View File

@ -4,11 +4,7 @@ import { Node } from "@tiptap/pm/model";
import { findListItemPos } from "./find-list-item-pos";
import { hasListBefore } from "./has-list-before";
export const handleBackspace = (
editor: Editor,
name: string,
parentListTypes: string[],
) => {
export const handleBackspace = (editor: Editor, name: string, parentListTypes: string[]) => {
// this is required to still handle the undo handling
if (editor.commands.undoInputRule()) {
return true;
@ -23,10 +19,7 @@ export const handleBackspace = (
// if the current item is NOT inside a list item &
// the previous item is a list (orderedList or bulletList)
// move the cursor into the list and delete the current item
if (
!isNodeActive(editor.state, name) &&
hasListBefore(editor.state, name, parentListTypes)
) {
if (!isNodeActive(editor.state, name) && hasListBefore(editor.state, name, parentListTypes)) {
const { $anchor } = editor.state.selection;
const $listPos = editor.state.doc.resolve($anchor.before() - 1);
@ -45,16 +38,11 @@ export const handleBackspace = (
return false;
}
const $lastItemPos = editor.state.doc.resolve(
$listPos.start() + lastItem.pos + 1,
);
const $lastItemPos = editor.state.doc.resolve($listPos.start() + lastItem.pos + 1);
return editor
.chain()
.cut(
{ from: $anchor.start() - 1, to: $anchor.end() + 1 },
$lastItemPos.end(),
)
.cut({ from: $anchor.start() - 1, to: $anchor.end() + 1 }, $lastItemPos.end())
.joinForward()
.run();
}

View File

@ -1,10 +1,6 @@
import { EditorState } from "@tiptap/pm/state";
export const hasListBefore = (
editorState: EditorState,
name: string,
parentListTypes: string[],
) => {
export const hasListBefore = (editorState: EditorState, name: string, parentListTypes: string[]) => {
const { $anchor } = editorState.selection;
const previousNodePos = Math.max(0, $anchor.pos - 2);

View File

@ -1,9 +1,6 @@
import { EditorState } from "@tiptap/pm/state";
export const hasListItemAfter = (
typeOrName: string,
state: EditorState,
): boolean => {
export const hasListItemAfter = (typeOrName: string, state: EditorState): boolean => {
const { $anchor } = state.selection;
const $targetPos = state.doc.resolve($anchor.pos - $anchor.parentOffset - 2);

View File

@ -1,9 +1,6 @@
import { EditorState } from "@tiptap/pm/state";
export const hasListItemBefore = (
typeOrName: string,
state: EditorState,
): boolean => {
export const hasListItemBefore = (typeOrName: string, state: EditorState): boolean => {
const { $anchor } = state.selection;
const $targetPos = state.doc.resolve($anchor.pos - 2);

View File

@ -1,12 +1,6 @@
import { TextSelection } from "prosemirror-state";
import {
InputRule,
mergeAttributes,
Node,
nodeInputRule,
wrappingInputRule,
} from "@tiptap/core";
import { InputRule, mergeAttributes, Node, nodeInputRule, wrappingInputRule } from "@tiptap/core";
/**
* Extension based on:
@ -83,8 +77,7 @@ export default Node.create<HorizontalRuleOptions>({
tr.setSelection(TextSelection.create(tr.doc, $to.pos));
} else {
// add node after horizontal rule if its the end of the document
const node =
$to.parent.type.contentMatch.defaultType?.create();
const node = $to.parent.type.contentMatch.defaultType?.create();
if (node) {
tr.insert(posAfter, node);

View File

@ -4,9 +4,7 @@ import Moveable from "react-moveable";
export const ImageResizer = ({ editor }: { editor: Editor }) => {
const updateMediaSize = () => {
const imageInfo = document.querySelector(
".ProseMirror-selectednode",
) as HTMLImageElement;
const imageInfo = document.querySelector(".ProseMirror-selectednode") as HTMLImageElement;
if (imageInfo) {
const selection = editor.state.selection;
editor.commands.setImage({
@ -32,9 +30,7 @@ export const ImageResizer = ({ editor }: { editor: Editor }) => {
resizable
throttleResize={0}
onResizeStart={() => {
const imageInfo = document.querySelector(
".ProseMirror-selectednode",
) as HTMLImageElement;
const imageInfo = document.querySelector(".ProseMirror-selectednode") as HTMLImageElement;
if (imageInfo) {
const originalWidth = Number(imageInfo.width);
const originalHeight = Number(imageInfo.height);

View File

@ -15,22 +15,14 @@ interface ImageNode extends ProseMirrorNode {
const deleteKey = new PluginKey("delete-image");
const IMAGE_NODE_TYPE = "image";
const ImageExtension = (
deleteImage: DeleteImage,
restoreFile: RestoreImage,
cancelUploadImage?: () => any,
) =>
const ImageExtension = (deleteImage: DeleteImage, restoreFile: RestoreImage, cancelUploadImage?: () => any) =>
ImageExt.extend({
addProseMirrorPlugins() {
return [
UploadImagesPlugin(cancelUploadImage),
new Plugin({
key: deleteKey,
appendTransaction: (
transactions: readonly Transaction[],
oldState: EditorState,
newState: EditorState,
) => {
appendTransaction: (transactions: readonly Transaction[], oldState: EditorState, newState: EditorState) => {
const newImageSources = new Set<string>();
newState.doc.descendants((node) => {
if (node.type.name === IMAGE_NODE_TYPE) {
@ -67,11 +59,7 @@ const ImageExtension = (
}),
new Plugin({
key: new PluginKey("imageRestoration"),
appendTransaction: (
transactions: readonly Transaction[],
oldState: EditorState,
newState: EditorState,
) => {
appendTransaction: (transactions: readonly Transaction[], oldState: EditorState, newState: EditorState) => {
const oldImageSources = new Set<string>();
oldState.doc.descendants((node) => {
if (node.type.name === IMAGE_NODE_TYPE) {

View File

@ -22,11 +22,7 @@ import { CustomKeymap } from "./keymap";
import { CustomCodeBlock } from "./code";
import { CustomQuoteExtension } from "./quote";
import { ListKeymap } from "./custom-list-keymap";
import {
IMentionSuggestion,
DeleteImage,
RestoreImage,
} from "@plane/editor-types";
import { IMentionSuggestion, DeleteImage, RestoreImage } from "@plane/editor-types";
export const CoreEditorExtensions = (
mentionConfig: {
@ -109,9 +105,5 @@ export const CoreEditorExtensions = (
TableHeader,
TableCell,
TableRow,
Mentions(
mentionConfig.mentionSuggestions,
mentionConfig.mentionHighlights,
false
),
Mentions(mentionConfig.mentionSuggestions, mentionConfig.mentionHighlights, false),
];

View File

@ -22,10 +22,6 @@ export default Node.create<TableRowOptions>({
},
renderHTML({ HTMLAttributes }) {
return [
"tr",
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
0,
];
return ["tr", mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0];
},
});

View File

@ -20,15 +20,12 @@ export function tableControls() {
mousemove: (view, event) => {
const pluginState = key.getState(view.state);
if (
!(event.target as HTMLElement).closest(".tableWrapper") &&
pluginState.values.hoveredTable
) {
if (!(event.target as HTMLElement).closest(".tableWrapper") && pluginState.values.hoveredTable) {
return view.dispatch(
view.state.tr.setMeta(key, {
setHoveredTable: null,
setHoveredCell: null,
}),
})
);
}
@ -40,13 +37,11 @@ export function tableControls() {
if (!pos) return;
const table = findParentNode((node) => node.type.name === "table")(
TextSelection.create(view.state.doc, pos.pos),
TextSelection.create(view.state.doc, pos.pos)
);
const cell = findParentNode((node) => node.type.name === "tableCell" || node.type.name === "tableHeader")(
TextSelection.create(view.state.doc, pos.pos)
);
const cell = findParentNode(
(node) =>
node.type.name === "tableCell" ||
node.type.name === "tableHeader",
)(TextSelection.create(view.state.doc, pos.pos));
if (!table || !cell) return;
@ -55,7 +50,7 @@ export function tableControls() {
view.state.tr.setMeta(key, {
setHoveredTable: table,
setHoveredCell: cell,
}),
})
);
}
},
@ -68,12 +63,7 @@ export function tableControls() {
const { hoveredTable, hoveredCell } = pluginState.values;
const docSize = state.doc.content.size;
if (
hoveredTable &&
hoveredCell &&
hoveredTable.pos < docSize &&
hoveredCell.pos < docSize
) {
if (hoveredTable && hoveredCell && hoveredTable.pos < docSize && hoveredCell.pos < docSize) {
const decorations = [
Decoration.node(
hoveredTable.pos,
@ -82,7 +72,7 @@ export function tableControls() {
{
hoveredTable,
hoveredCell,
},
}
),
];

View File

@ -4,11 +4,7 @@ import { Decoration, NodeView } from "@tiptap/pm/view";
import tippy, { Instance, Props } from "tippy.js";
import { Editor } from "@tiptap/core";
import {
CellSelection,
TableMap,
updateColumnsOnResize,
} from "@tiptap/prosemirror-tables";
import { CellSelection, TableMap, updateColumnsOnResize } from "@tiptap/prosemirror-tables";
import icons from "./icons";
@ -18,7 +14,7 @@ export function updateColumns(
table: HTMLElement,
cellMinWidth: number,
overrideCol?: number,
overrideValue?: any,
overrideValue?: any
) {
let totalWidth = 0;
let fixedWidth = true;
@ -31,8 +27,7 @@ export function updateColumns(
const { colspan, colwidth } = row.child(i).attrs;
for (let j = 0; j < colspan; j += 1, col += 1) {
const hasWidth =
overrideCol === col ? overrideValue : colwidth && colwidth[j];
const hasWidth = overrideCol === col ? overrideValue : colwidth && colwidth[j];
const cssWidth = hasWidth ? `${hasWidth}px` : "";
totalWidth += hasWidth || cellMinWidth;
@ -42,8 +37,7 @@ export function updateColumns(
}
if (!nextDOM) {
colgroup.appendChild(document.createElement("col")).style.width =
cssWidth;
colgroup.appendChild(document.createElement("col")).style.width = cssWidth;
} else {
if (nextDOM.style.width !== cssWidth) {
nextDOM.style.width = cssWidth;
@ -98,14 +92,12 @@ const columnsToolboxItems = [
{
label: "Add Column Before",
icon: icons.insertLeftTableIcon,
action: ({ editor }: { editor: Editor }) =>
editor.chain().focus().addColumnBefore().run(),
action: ({ editor }: { editor: Editor }) => editor.chain().focus().addColumnBefore().run(),
},
{
label: "Add Column After",
icon: icons.insertRightTableIcon,
action: ({ editor }: { editor: Editor }) =>
editor.chain().focus().addColumnAfter().run(),
action: ({ editor }: { editor: Editor }) => editor.chain().focus().addColumnAfter().run(),
},
{
label: "Pick Column Color",
@ -131,8 +123,7 @@ const columnsToolboxItems = [
{
label: "Delete Column",
icon: icons.deleteColumn,
action: ({ editor }: { editor: Editor }) =>
editor.chain().focus().deleteColumn().run(),
action: ({ editor }: { editor: Editor }) => editor.chain().focus().deleteColumn().run(),
},
];
@ -140,14 +131,12 @@ const rowsToolboxItems = [
{
label: "Add Row Above",
icon: icons.insertTopTableIcon,
action: ({ editor }: { editor: Editor }) =>
editor.chain().focus().addRowBefore().run(),
action: ({ editor }: { editor: Editor }) => editor.chain().focus().addRowBefore().run(),
},
{
label: "Add Row Below",
icon: icons.insertBottomTableIcon,
action: ({ editor }: { editor: Editor }) =>
editor.chain().focus().addRowAfter().run(),
action: ({ editor }: { editor: Editor }) => editor.chain().focus().addRowAfter().run(),
},
{
label: "Pick Row Color",
@ -159,11 +148,7 @@ const rowsToolboxItems = [
}: {
editor: Editor;
triggerButton: HTMLButtonElement;
controlsContainer:
| Element
| "parent"
| ((ref: Element) => Element)
| undefined;
controlsContainer: Element | "parent" | ((ref: Element) => Element) | undefined;
}) => {
createColorPickerToolbox({
triggerButton,
@ -177,8 +162,7 @@ const rowsToolboxItems = [
{
label: "Delete Row",
icon: icons.deleteRow,
action: ({ editor }: { editor: Editor }) =>
editor.chain().focus().deleteRow().run(),
action: ({ editor }: { editor: Editor }) => editor.chain().focus().deleteRow().run(),
},
];
@ -213,9 +197,9 @@ function createToolbox({
innerHTML: item.icon,
}),
h("div", { className: "label" }, item.label),
],
),
),
]
)
)
),
...tippyOptions,
});
@ -272,11 +256,11 @@ function createColorPickerToolbox({
{
className: "label",
},
key,
),
],
),
key
),
]
)
)
),
onHidden: (instance) => {
instance.destroy();
@ -319,7 +303,7 @@ export class TableView implements NodeView {
cellMinWidth: number,
decorations: Decoration[],
editor: Editor,
getPos: () => number,
getPos: () => number
) {
this.node = node;
this.cellMinWidth = cellMinWidth;
@ -337,7 +321,7 @@ export class TableView implements NodeView {
itemType: "button",
className: "rowsControlDiv",
onClick: () => this.selectRow(),
}),
})
);
this.columnsControl = h(
@ -347,14 +331,14 @@ export class TableView implements NodeView {
itemType: "button",
className: "columnsControlDiv",
onClick: () => this.selectColumn(),
}),
})
);
this.controls = h(
"div",
{ className: "tableControls", contentEditable: "false" },
this.rowsControl,
this.columnsControl,
this.columnsControl
);
this.columnsToolbox = createToolbox({
@ -397,7 +381,7 @@ export class TableView implements NodeView {
this.colgroup = h(
"colgroup",
null,
Array.from({ length: this.map.width }, () => 1).map(() => h("col")),
Array.from({ length: this.map.width }, () => 1).map(() => h("col"))
);
this.tbody = h("tbody");
this.table = h("table", null, this.colgroup, this.tbody);
@ -408,7 +392,7 @@ export class TableView implements NodeView {
className: "tableWrapper controls--disabled",
},
this.controls,
this.table,
this.table
);
this.render();
@ -434,18 +418,11 @@ export class TableView implements NodeView {
render() {
if (this.colgroup.children.length !== this.map.width) {
const cols = Array.from({ length: this.map.width }, () => 1).map(() =>
h("col"),
);
const cols = Array.from({ length: this.map.width }, () => 1).map(() => h("col"));
this.colgroup.replaceChildren(...cols);
}
updateColumnsOnResize(
this.node,
this.colgroup,
this.table,
this.cellMinWidth,
);
updateColumnsOnResize(this.node, this.colgroup, this.table, this.cellMinWidth);
}
ignoreMutation() {
@ -453,9 +430,7 @@ export class TableView implements NodeView {
}
updateControls() {
const { hoveredTable: table, hoveredCell: cell } = Object.values(
this.decorations,
).reduce(
const { hoveredTable: table, hoveredCell: cell } = Object.values(this.decorations).reduce(
(acc, curr) => {
if (curr.spec.hoveredCell !== undefined) {
acc["hoveredCell"] = curr.spec.hoveredCell;
@ -466,7 +441,7 @@ export class TableView implements NodeView {
}
return acc;
},
{} as Record<string, HTMLElement>,
{} as Record<string, HTMLElement>
) as any;
if (table === undefined || cell === undefined) {
@ -481,9 +456,7 @@ export class TableView implements NodeView {
const tableRect = this.table.getBoundingClientRect();
const cellRect = cellDom.getBoundingClientRect();
this.columnsControl.style.left = `${
cellRect.left - tableRect.left - this.table.parentElement!.scrollLeft
}px`;
this.columnsControl.style.left = `${cellRect.left - tableRect.left - this.table.parentElement!.scrollLeft}px`;
this.columnsControl.style.width = `${cellRect.width}px`;
this.rowsControl.style.top = `${cellRect.top - tableRect.top}px`;
@ -493,22 +466,14 @@ export class TableView implements NodeView {
selectColumn() {
if (!this.hoveredCell) return;
const colIndex = this.map.colCount(
this.hoveredCell.pos - (this.getPos() + 1),
);
const colIndex = this.map.colCount(this.hoveredCell.pos - (this.getPos() + 1));
const anchorCellPos = this.hoveredCell.pos;
const headCellPos =
this.map.map[colIndex + this.map.width * (this.map.height - 1)] +
(this.getPos() + 1);
const headCellPos = this.map.map[colIndex + this.map.width * (this.map.height - 1)] + (this.getPos() + 1);
const cellSelection = CellSelection.create(
this.editor.view.state.doc,
anchorCellPos,
headCellPos,
);
const cellSelection = CellSelection.create(this.editor.view.state.doc, anchorCellPos, headCellPos);
this.editor.view.dispatch(
// @ts-ignore
this.editor.state.tr.setSelection(cellSelection),
this.editor.state.tr.setSelection(cellSelection)
);
}
@ -516,21 +481,13 @@ export class TableView implements NodeView {
if (!this.hoveredCell) return;
const anchorCellPos = this.hoveredCell.pos;
const anchorCellIndex = this.map.map.indexOf(
anchorCellPos - (this.getPos() + 1),
);
const headCellPos =
this.map.map[anchorCellIndex + (this.map.width - 1)] +
(this.getPos() + 1);
const anchorCellIndex = this.map.map.indexOf(anchorCellPos - (this.getPos() + 1));
const headCellPos = this.map.map[anchorCellIndex + (this.map.width - 1)] + (this.getPos() + 1);
const cellSelection = CellSelection.create(
this.editor.state.doc,
anchorCellPos,
headCellPos,
);
const cellSelection = CellSelection.create(this.editor.state.doc, anchorCellPos, headCellPos);
this.editor.view.dispatch(
// @ts-ignore
this.editor.view.state.tr.setSelection(cellSelection),
this.editor.view.state.tr.setSelection(cellSelection)
);
}
}

View File

@ -1,12 +1,6 @@
import { TextSelection } from "@tiptap/pm/state";
import {
callOrReturn,
getExtensionField,
mergeAttributes,
Node,
ParentConfig,
} from "@tiptap/core";
import { callOrReturn, getExtensionField, mergeAttributes, Node, ParentConfig } from "@tiptap/core";
import {
addColumnAfter,
addColumnBefore,
@ -44,11 +38,7 @@ export interface TableOptions {
declare module "@tiptap/core" {
interface Commands<ReturnType> {
table: {
insertTable: (options?: {
rows?: number;
cols?: number;
withHeaderRow?: boolean;
}) => ReturnType;
insertTable: (options?: { rows?: number; cols?: number; withHeaderRow?: boolean }) => ReturnType;
addColumnBefore: () => ReturnType;
addColumnAfter: () => ReturnType;
deleteColumn: () => ReturnType;
@ -66,10 +56,7 @@ declare module "@tiptap/core" {
goToNextCell: () => ReturnType;
goToPreviousCell: () => ReturnType;
fixTables: () => ReturnType;
setCellSelection: (position: {
anchorCell: number;
headCell?: number;
}) => ReturnType;
setCellSelection: (position: { anchorCell: number; headCell?: number }) => ReturnType;
};
}
@ -114,11 +101,7 @@ export default Node.create({
},
renderHTML({ HTMLAttributes }) {
return [
"table",
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
["tbody", 0],
];
return ["table", mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), ["tbody", 0]];
},
addCommands() {
@ -220,11 +203,7 @@ export default Node.create({
(position) =>
({ tr, dispatch }) => {
if (dispatch) {
const selection = CellSelection.create(
tr.doc,
position.anchorCell,
position.headCell,
);
const selection = CellSelection.create(tr.doc, position.anchorCell, position.headCell);
// @ts-ignore
tr.setSelection(selection);
@ -260,13 +239,7 @@ export default Node.create({
return ({ editor, getPos, node, decorations }) => {
const { cellMinWidth } = this.options;
return new TableView(
node,
cellMinWidth,
decorations,
editor,
getPos as () => number,
);
return new TableView(node, cellMinWidth, decorations, editor, getPos as () => number);
};
},
@ -289,7 +262,7 @@ export default Node.create({
// @ts-ignore
lastColumnResizable: this.options.lastColumnResizable,
}),
})
);
}
@ -304,9 +277,7 @@ export default Node.create({
};
return {
tableRole: callOrReturn(
getExtensionField(extension, "tableRole", context),
),
tableRole: callOrReturn(getExtensionField(extension, "tableRole", context)),
};
},
});

View File

@ -2,7 +2,7 @@ import { Fragment, Node as ProsemirrorNode, NodeType } from "prosemirror-model";
export function createCell(
cellType: NodeType,
cellContent?: Fragment | ProsemirrorNode | Array<ProsemirrorNode>,
cellContent?: Fragment | ProsemirrorNode | Array<ProsemirrorNode>
): ProsemirrorNode | null | undefined {
if (cellContent) {
return cellType.createChecked(null, cellContent);

View File

@ -8,7 +8,7 @@ export function createTable(
rowsCount: number,
colsCount: number,
withHeaderRow: boolean,
cellContent?: Fragment | ProsemirrorNode | Array<ProsemirrorNode>,
cellContent?: Fragment | ProsemirrorNode | Array<ProsemirrorNode>
): ProsemirrorNode {
const types = getTableNodeTypes(schema);
const headerCells: ProsemirrorNode[] = [];
@ -33,12 +33,7 @@ export function createTable(
const rows: ProsemirrorNode[] = [];
for (let index = 0; index < rowsCount; index += 1) {
rows.push(
types.row.createChecked(
null,
withHeaderRow && index === 0 ? headerCells : cells,
),
);
rows.push(types.row.createChecked(null, withHeaderRow && index === 0 ? headerCells : cells));
}
return types.table.createChecked(null, rows);

View File

@ -1,13 +1,8 @@
import {
findParentNodeClosestToPos,
KeyboardShortcutCommand,
} from "@tiptap/core";
import { findParentNodeClosestToPos, KeyboardShortcutCommand } from "@tiptap/core";
import { isCellSelection } from "./is-cell-selection";
export const deleteTableWhenAllCellsSelected: KeyboardShortcutCommand = ({
editor,
}) => {
export const deleteTableWhenAllCellsSelected: KeyboardShortcutCommand = ({ editor }) => {
const { selection } = editor.state;
if (!isCellSelection(selection)) {
@ -15,10 +10,7 @@ export const deleteTableWhenAllCellsSelected: KeyboardShortcutCommand = ({
}
let cellCount = 0;
const table = findParentNodeClosestToPos(
selection.ranges[0].$from,
(node) => node.type.name === "table",
);
const table = findParentNodeClosestToPos(selection.ranges[0].$from, (node) => node.type.name === "table");
table?.node.descendants((node) => {
if (node.type.name === "table") {

View File

@ -4,12 +4,7 @@ import { CoreEditorProps } from "../props";
import { CoreEditorExtensions } from "../extensions";
import { EditorProps } from "@tiptap/pm/view";
import { getTrimmedHTML } from "../../lib/utils";
import {
DeleteImage,
IMentionSuggestion,
RestoreImage,
UploadImage,
} from "@plane/editor-types";
import { DeleteImage, IMentionSuggestion, RestoreImage, UploadImage } from "@plane/editor-types";
interface CustomEditorProps {
uploadFile: UploadImage;
@ -20,9 +15,7 @@ interface CustomEditorProps {
};
deleteFile: DeleteImage;
cancelUploadImage?: () => any;
setIsSubmitting?: (
isSubmitting: "submitting" | "submitted" | "saved",
) => void;
setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void;
setShouldShowAlert?: (showAlert: boolean) => void;
value: string;
debouncedUpdatesEnabled?: boolean;
@ -66,12 +59,11 @@ export const useEditor = ({
},
deleteFile,
restoreFile,
cancelUploadImage,
cancelUploadImage
),
...extensions,
],
content:
typeof value === "string" && value.trim() !== "" ? value : "<p></p>",
content: typeof value === "string" && value.trim() !== "" ? value : "<p></p>",
onCreate: async ({ editor }) => {
onStart?.(editor.getJSON(), getTrimmedHTML(editor.getHTML()));
},
@ -82,7 +74,7 @@ export const useEditor = ({
onChange?.(editor.getJSON(), getTrimmedHTML(editor.getHTML()));
},
},
[rerenderOnPropsChange],
[rerenderOnPropsChange]
);
const editorRef: MutableRefObject<Editor | null> = useRef(null);

View File

@ -30,8 +30,7 @@ export const useReadOnlyEditor = ({
const editor = useCustomEditor(
{
editable: false,
content:
typeof value === "string" && value.trim() !== "" ? value : "<p></p>",
content: typeof value === "string" && value.trim() !== "" ? value : "<p></p>",
editorProps: {
...CoreReadOnlyEditorProps,
...editorProps,
@ -44,7 +43,7 @@ export const useReadOnlyEditor = ({
...extensions,
],
},
[rerenderOnPropsChange],
[rerenderOnPropsChange]
);
const editorRef: MutableRefObject<Editor | null> = useRef(null);

View File

@ -1,21 +1,10 @@
import { IMentionSuggestion } from "@plane/editor-types";
import { Editor } from "@tiptap/react";
import React, {
forwardRef,
useCallback,
useEffect,
useImperativeHandle,
useState,
} from "react";
import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useState } from "react";
interface MentionListProps {
items: IMentionSuggestion[];
command: (item: {
id: string;
label: string;
target: string;
redirect_uri: string;
}) => void;
command: (item: { id: string; label: string; target: string; redirect_uri: string }) => void;
editor: Editor;
}
@ -37,9 +26,7 @@ const MentionList = forwardRef((props: MentionListProps, ref) => {
};
const upHandler = () => {
setSelectedIndex(
(selectedIndex + props.items.length - 1) % props.items.length,
);
setSelectedIndex((selectedIndex + props.items.length - 1) % props.items.length);
};
const downHandler = () => {
@ -76,31 +63,27 @@ const MentionList = forwardRef((props: MentionListProps, ref) => {
}));
return props.items && props.items.length !== 0 ? (
<div className="mentions absolute max-h-40 bg-custom-background-100 rounded-md shadow-custom-shadow-sm text-custom-text-300 text-sm overflow-y-auto w-48 p-1 space-y-0.5">
<div className="mentions absolute max-h-40 w-48 space-y-0.5 overflow-y-auto rounded-md bg-custom-background-100 p-1 text-sm text-custom-text-300 shadow-custom-shadow-sm">
{props.items.length ? (
props.items.map((item, index) => (
<div
key={item.id}
className={`flex items-center gap-2 rounded p-1 hover:bg-custom-background-80 cursor-pointer ${
className={`flex cursor-pointer items-center gap-2 rounded p-1 hover:bg-custom-background-80 ${
index === selectedIndex ? "bg-custom-background-80" : ""
}`}
onClick={() => selectItem(index)}
>
<div className="flex-shrink-0 h-4 w-4 grid place-items-center overflow-hidden">
<div className="grid h-4 w-4 flex-shrink-0 place-items-center overflow-hidden">
{item.avatar && item.avatar.trim() !== "" ? (
<img
src={item.avatar}
className="h-full w-full object-cover rounded-sm"
alt={item.title}
/>
<img src={item.avatar} className="h-full w-full rounded-sm object-cover" alt={item.title} />
) : (
<div className="h-full w-full grid place-items-center text-xs capitalize text-white rounded-sm bg-gray-700">
<div className="grid h-full w-full place-items-center rounded-sm bg-gray-700 text-xs capitalize text-white">
{item.title[0]}
</div>
)}
</div>
<div className="flex-grow space-y-1 truncate">
<p className="text-sm font-medium truncate">{item.title}</p>
<p className="truncate text-sm font-medium">{item.title}</p>
{/* <p className="text-xs text-gray-400">{item.subtitle}</p> */}
</div>
</div>

View File

@ -4,11 +4,7 @@ import suggestion from "./suggestion";
import { CustomMention } from "./custom";
import { IMentionHighlight, IMentionSuggestion } from "@plane/editor-types";
export const Mentions = (
mentionSuggestions: IMentionSuggestion[],
mentionHighlights: IMentionHighlight[],
readonly,
) =>
export const Mentions = (mentionSuggestions: IMentionSuggestion[], mentionHighlights: IMentionHighlight[], readonly) =>
CustomMention.configure({
HTMLAttributes: {
class: "mention",

View File

@ -8,8 +8,7 @@ import { IMentionHighlight } from "@plane/editor-types";
// eslint-disable-next-line import/no-anonymous-default-export
export default (props) => {
const router = useRouter();
const highlights = props.extension.options
.mentionHighlights as IMentionHighlight[];
const highlights = props.extension.options.mentionHighlights as IMentionHighlight[];
const handleClick = () => {
if (!props.extension.options.readonly) {
@ -18,18 +17,13 @@ export default (props) => {
};
return (
<NodeViewWrapper className="w-fit inline mention-component">
<NodeViewWrapper className="mention-component inline w-fit">
<span
className={cn(
"px-1 py-0.5 bg-custom-primary-100/20 text-custom-primary-100 rounded font-medium mention",
{
"text-yellow-500 bg-yellow-500/20": highlights
? highlights.includes(props.node.attrs.id)
: false,
className={cn("mention rounded bg-custom-primary-100/20 px-1 py-0.5 font-medium text-custom-primary-100", {
"bg-yellow-500/20 text-yellow-500": highlights ? highlights.includes(props.node.attrs.id) : false,
"cursor-pointer": !props.extension.options.readonly,
// "hover:bg-custom-primary-300" : !props.extension.options.readonly && !highlights.includes(props.node.attrs.id)
},
)}
})}
onClick={handleClick}
data-mention-target={props.node.attrs.target}
data-mention-id={props.node.attrs.id}

View File

@ -7,11 +7,7 @@ import { IMentionSuggestion } from "@plane/editor-types";
const Suggestion = (suggestions: IMentionSuggestion[]) => ({
items: ({ query }: { query: string }) =>
suggestions
.filter((suggestion) =>
suggestion.title.toLowerCase().startsWith(query.toLowerCase()),
)
.slice(0, 5),
suggestions.filter((suggestion) => suggestion.title.toLowerCase().startsWith(query.toLowerCase())).slice(0, 5),
render: () => {
let reactRenderer: ReactRenderer | null = null;
let popup: any | null = null;

View File

@ -134,9 +134,7 @@ export const TableItem = (editor: Editor): EditorMenuItem => ({
export const ImageItem = (
editor: Editor,
uploadFile: UploadImage,
setIsSubmitting?: (
isSubmitting: "submitting" | "submitted" | "saved",
) => void,
setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void
): EditorMenuItem => ({
name: "image",
isActive: () => editor?.isActive("image"),

View File

@ -15,11 +15,7 @@ interface ImageNode extends ProseMirrorNode {
const TrackImageDeletionPlugin = (deleteImage: DeleteImage): Plugin =>
new Plugin({
key: deleteKey,
appendTransaction: (
transactions: readonly Transaction[],
oldState: EditorState,
newState: EditorState,
) => {
appendTransaction: (transactions: readonly Transaction[], oldState: EditorState, newState: EditorState) => {
const newImageSources = new Set<string>();
newState.doc.descendants((node) => {
if (node.type.name === IMAGE_NODE_TYPE) {
@ -59,10 +55,7 @@ const TrackImageDeletionPlugin = (deleteImage: DeleteImage): Plugin =>
export default TrackImageDeletionPlugin;
export async function onNodeDeleted(
src: string,
deleteImage: DeleteImage,
): Promise<void> {
export async function onNodeDeleted(src: string, deleteImage: DeleteImage): Promise<void> {
try {
const assetUrlWithWorkspaceId = new URL(src).pathname.substring(1);
const resStatus = await deleteImage(assetUrlWithWorkspaceId);
@ -74,10 +67,7 @@ export async function onNodeDeleted(
}
}
export async function onNodeRestored(
src: string,
restoreImage: RestoreImage,
): Promise<void> {
export async function onNodeRestored(src: string, restoreImage: RestoreImage): Promise<void> {
try {
const assetUrlWithWorkspaceId = new URL(src).pathname.substring(1);
const resStatus = await restoreImage(assetUrlWithWorkspaceId);

View File

@ -21,10 +21,7 @@ const UploadImagesPlugin = (cancelUploadImage?: () => any) =>
const placeholder = document.createElement("div");
placeholder.setAttribute("class", "img-placeholder");
const image = document.createElement("img");
image.setAttribute(
"class",
"opacity-10 rounded-lg border border-custom-border-300",
);
image.setAttribute("class", "opacity-10 rounded-lg border border-custom-border-300");
image.src = src;
placeholder.appendChild(image);
@ -42,10 +39,7 @@ const UploadImagesPlugin = (cancelUploadImage?: () => any) =>
// Create an SVG element from the SVG string
const svgString = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-x-circle"><circle cx="12" cy="12" r="10"/><path d="m15 9-6 6"/><path d="m9 9 6 6"/></svg>`;
const parser = new DOMParser();
const svgElement = parser.parseFromString(
svgString,
"image/svg+xml",
).documentElement;
const svgElement = parser.parseFromString(svgString, "image/svg+xml").documentElement;
cancelButton.appendChild(svgElement);
placeholder.appendChild(cancelButton);
@ -54,13 +48,7 @@ const UploadImagesPlugin = (cancelUploadImage?: () => any) =>
});
set = set.add(tr.doc, [deco]);
} else if (action && action.remove) {
set = set.remove(
set.find(
undefined,
undefined,
(spec) => spec.id == action.remove.id,
),
);
set = set.remove(set.find(undefined, undefined, (spec) => spec.id == action.remove.id));
}
return set;
},
@ -76,11 +64,7 @@ export default UploadImagesPlugin;
function findPlaceholder(state: EditorState, id: {}) {
const decos = uploadKey.getState(state);
const found = decos.find(
undefined,
undefined,
(spec: { id: number | undefined }) => spec.id == id,
);
const found = decos.find(undefined, undefined, (spec: { id: number | undefined }) => spec.id == id);
return found.length ? found[0].from : null;
}
@ -96,9 +80,7 @@ export async function startImageUpload(
view: EditorView,
pos: number,
uploadFile: UploadImage,
setIsSubmitting?: (
isSubmitting: "submitting" | "submitted" | "saved",
) => void,
setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void
) {
if (!file) {
alert("No file selected. Please select a file to upload.");
@ -151,9 +133,7 @@ export async function startImageUpload(
const imageSrc = typeof src === "object" ? reader.result : src;
const node = schema.nodes.image.create({ src: imageSrc });
const transaction = view.state.tr
.replaceWith(pos, pos, node)
.setMeta(uploadKey, { remove: { id } });
const transaction = view.state.tr.replaceWith(pos, pos, node).setMeta(uploadKey, { remove: { id } });
view.dispatch(transaction);
} catch (error) {
console.error("Upload error: ", error);
@ -161,10 +141,7 @@ export async function startImageUpload(
}
}
const UploadImageHandler = (
file: File,
uploadFile: UploadImage,
): Promise<string> => {
const UploadImageHandler = (file: File, uploadFile: UploadImage): Promise<string> => {
try {
return new Promise(async (resolve, reject) => {
try {

View File

@ -5,9 +5,7 @@ import { startImageUpload } from "./plugins/upload-image";
export function CoreEditorProps(
uploadFile: UploadImage,
setIsSubmitting?: (
isSubmitting: "submitting" | "submitted" | "saved",
) => void,
setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void
): EditorProps {
return {
attributes: {
@ -34,11 +32,7 @@ export function CoreEditorProps(
}
}
}
if (
event.clipboardData &&
event.clipboardData.files &&
event.clipboardData.files[0]
) {
if (event.clipboardData && event.clipboardData.files && event.clipboardData.files[0]) {
event.preventDefault();
const file = event.clipboardData.files[0];
const pos = view.state.selection.from;
@ -57,12 +51,7 @@ export function CoreEditorProps(
}
}
}
if (
!moved &&
event.dataTransfer &&
event.dataTransfer.files &&
event.dataTransfer.files[0]
) {
if (!moved && event.dataTransfer && event.dataTransfer.files && event.dataTransfer.files[0]) {
event.preventDefault();
const file = event.dataTransfer.files[0];
const coordinates = view.posAtCoords({
@ -70,13 +59,7 @@ export function CoreEditorProps(
top: event.clientY,
});
if (coordinates) {
startImageUpload(
file,
view,
coordinates.pos - 1,
uploadFile,
setIsSubmitting,
);
startImageUpload(file, view, coordinates.pos - 1, uploadFile, setIsSubmitting);
}
return true;
}

View File

@ -45,8 +45,7 @@ export const CoreReadOnlyEditorExtensions = (mentionConfig: {
},
code: {
HTMLAttributes: {
class:
"rounded-md bg-custom-primary-30 mx-1 px-1 py-1 font-mono font-medium text-custom-text-1000",
class: "rounded-md bg-custom-primary-30 mx-1 px-1 py-1 font-mono font-medium text-custom-text-1000",
spellcheck: "false",
},
},
@ -94,9 +93,5 @@ export const CoreReadOnlyEditorExtensions = (mentionConfig: {
TableHeader,
TableCell,
TableRow,
Mentions(
mentionConfig.mentionSuggestions,
mentionConfig.mentionHighlights,
true,
),
Mentions(mentionConfig.mentionSuggestions, mentionConfig.mentionHighlights, true),
];

View File

@ -0,0 +1,4 @@
module.exports = {
root: true,
extends: ["custom"],
};

View File

@ -0,0 +1,6 @@
.next
.vercel
.tubro
out/
dis/
build/

View File

@ -0,0 +1,5 @@
{
"printWidth": 120,
"tabWidth": 2,
"trailingComma": "es5"
}

View File

@ -36,9 +36,6 @@
"@tiptap/extension-placeholder": "^2.1.11",
"@tiptap/pm": "^2.1.12",
"@tiptap/suggestion": "^2.1.12",
"@types/node": "18.15.3",
"@types/react": "^18.2.39",
"@types/react-dom": "18.0.11",
"eslint": "8.36.0",
"eslint-config-next": "13.2.4",
"react-popper": "^2.3.0",

View File

@ -1,6 +1,3 @@
export { DocumentEditor, DocumentEditorWithRef } from "./ui";
export {
DocumentReadOnlyEditor,
DocumentReadOnlyEditorWithRef,
} from "./ui/readonly";
export { DocumentReadOnlyEditor, DocumentReadOnlyEditorWithRef } from "./ui/readonly";
export { FixedMenu } from "./ui/menu/fixed-menu";

View File

@ -12,7 +12,7 @@ export const AlertLabel = (props: IAlertLabelProps) => {
return (
<div
className={`h-7 flex items-center gap-2 font-medium py-0.5 px-3 rounded-full text-xs ${backgroundColor} ${textColor}`}
className={`flex h-7 items-center gap-2 rounded-full px-3 py-0.5 text-xs font-medium ${backgroundColor} ${textColor}`}
>
{Icon && <Icon className="h-3 w-3" />}
<span>{label}</span>

View File

@ -1,8 +1,4 @@
import {
HeadingComp,
HeadingThreeComp,
SubheadingComp,
} from "./heading-component";
import { HeadingComp, HeadingThreeComp, SubheadingComp } from "./heading-component";
import { IMarking } from "..";
import { Editor } from "@tiptap/react";
import { scrollSummary } from "../utils/editor-summary-utils";
@ -16,32 +12,21 @@ export const ContentBrowser = (props: ContentBrowserProps) => {
const { editor, markings } = props;
return (
<div className="flex flex-col h-full overflow-hidden">
<div className="flex h-full flex-col overflow-hidden">
<h2 className="font-medium">Table of Contents</h2>
<div className="h-full overflow-y-auto">
{markings.length !== 0 ? (
markings.map((marking) =>
marking.level === 1 ? (
<HeadingComp
onClick={() => scrollSummary(editor, marking)}
heading={marking.text}
/>
<HeadingComp onClick={() => scrollSummary(editor, marking)} heading={marking.text} />
) : marking.level === 2 ? (
<SubheadingComp
onClick={() => scrollSummary(editor, marking)}
subHeading={marking.text}
/>
<SubheadingComp onClick={() => scrollSummary(editor, marking)} subHeading={marking.text} />
) : (
<HeadingThreeComp
heading={marking.text}
onClick={() => scrollSummary(editor, marking)}
/>
),
<HeadingThreeComp heading={marking.text} onClick={() => scrollSummary(editor, marking)} />
)
)
) : (
<p className="mt-3 text-xs text-custom-text-400">
Headings will be displayed here for navigation
</p>
<p className="mt-3 text-xs text-custom-text-400">Headings will be displayed here for navigation</p>
)}
</div>
</div>

View File

@ -5,10 +5,7 @@ import { FixedMenu } from "../menu";
import { UploadImage } from "@plane/editor-types";
import { DocumentDetails } from "../types/editor-types";
import { AlertLabel } from "./alert-label";
import {
IVerticalDropdownItemProps,
VerticalDropdownMenu,
} from "./vertical-dropdown-menu";
import { IVerticalDropdownItemProps, VerticalDropdownMenu } from "./vertical-dropdown-menu";
import { SummaryPopover } from "./summary-popover";
import { InfoPopover } from "./info-popover";
@ -23,9 +20,7 @@ interface IEditorHeader {
archivedAt?: Date;
readonly: boolean;
uploadFile?: UploadImage;
setIsSubmitting?: (
isSubmitting: "submitting" | "submitted" | "saved",
) => void;
setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void;
documentDetails: DocumentDetails;
isSubmitting?: "submitting" | "submitted" | "saved";
}
@ -48,8 +43,8 @@ export const EditorHeader = (props: IEditorHeader) => {
} = props;
return (
<div className="flex items-center border-b border-custom-border-200 py-2 px-5">
<div className="flex-shrink-0 w-56 lg:w-72">
<div className="flex items-center border-b border-custom-border-200 px-5 py-2">
<div className="w-56 flex-shrink-0 lg:w-72">
<SummaryPopover
editor={editor}
markings={markings}
@ -60,15 +55,11 @@ export const EditorHeader = (props: IEditorHeader) => {
<div className="flex-shrink-0">
{!readonly && uploadFile && (
<FixedMenu
editor={editor}
uploadFile={uploadFile}
setIsSubmitting={setIsSubmitting}
/>
<FixedMenu editor={editor} uploadFile={uploadFile} setIsSubmitting={setIsSubmitting} />
)}
</div>
<div className="flex-grow flex items-center justify-end gap-3">
<div className="flex flex-grow items-center justify-end gap-3">
{isLocked && (
<AlertLabel
Icon={Lock}
@ -88,7 +79,7 @@ export const EditorHeader = (props: IEditorHeader) => {
{!isLocked && !isArchived ? (
<div
className={`flex absolute right-[120px] transition-all duration-300 items-center gap-x-2 ${
className={`absolute right-[120px] flex items-center gap-x-2 transition-all duration-300 ${
isSubmitting === "saved" ? "fadeOut" : "fadeIn"
}`}
>

View File

@ -23,7 +23,7 @@ export const SubheadingComp = ({
}) => (
<p
onClick={onClick}
className="ml-6 mt-2 text-xs cursor-pointer font-medium tracking-tight text-gray-400 hover:text-custom-primary"
className="ml-6 mt-2 cursor-pointer text-xs font-medium tracking-tight text-gray-400 hover:text-custom-primary"
role="button"
>
{subHeading}
@ -39,7 +39,7 @@ export const HeadingThreeComp = ({
}) => (
<p
onClick={onClick}
className="ml-8 mt-2 text-xs cursor-pointer font-medium tracking-tight text-gray-400 hover:text-custom-primary"
className="ml-8 mt-2 cursor-pointer text-xs font-medium tracking-tight text-gray-400 hover:text-custom-primary"
role="button"
>
{heading}

View File

@ -19,10 +19,7 @@ const renderDate = (date: Date): string => {
hour12: true,
};
const formattedDate: string = new Intl.DateTimeFormat(
"en-US",
options,
).format(date);
const formattedDate: string = new Intl.DateTimeFormat("en-US", options).format(date);
return formattedDate;
};
@ -32,42 +29,35 @@ export const InfoPopover: React.FC<Props> = (props) => {
const [isPopoverOpen, setIsPopoverOpen] = useState<boolean>(false);
const [referenceElement, setReferenceElement] =
useState<HTMLButtonElement | null>(null);
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(
null,
);
const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | null>(null);
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
const { styles: infoPopoverStyles, attributes: infoPopoverAttributes } =
usePopper(referenceElement, popperElement, {
const { styles: infoPopoverStyles, attributes: infoPopoverAttributes } = usePopper(referenceElement, popperElement, {
placement: "bottom-start",
});
return (
<div
onMouseEnter={() => setIsPopoverOpen(true)}
onMouseLeave={() => setIsPopoverOpen(false)}
>
<div onMouseEnter={() => setIsPopoverOpen(true)} onMouseLeave={() => setIsPopoverOpen(false)}>
<button type="button" ref={setReferenceElement} className="block">
<Info className="h-3.5 w-3.5" />
</button>
{isPopoverOpen && (
<div
className="z-10 w-64 shadow-custom-shadow-rg rounded border-[0.5px] border-custom-border-200 bg-custom-background-100 p-3 space-y-2.5"
className="z-10 w-64 space-y-2.5 rounded border-[0.5px] border-custom-border-200 bg-custom-background-100 p-3 shadow-custom-shadow-rg"
ref={setPopperElement}
style={infoPopoverStyles.popper}
{...infoPopoverAttributes.popper}
>
<div className="space-y-1.5">
<h6 className="text-custom-text-400 text-xs">Last updated on</h6>
<h5 className="text-sm flex items-center gap-1">
<h6 className="text-xs text-custom-text-400">Last updated on</h6>
<h5 className="flex items-center gap-1 text-sm">
<History className="h-3 w-3" />
{renderDate(new Date(documentDetails.last_updated_at))}
</h5>
</div>
<div className="space-y-1.5">
<h6 className="text-custom-text-400 text-xs">Created on</h6>
<h5 className="text-sm flex items-center gap-1">
<h6 className="text-xs text-custom-text-400">Created on</h6>
<h5 className="flex items-center gap-1 text-sm">
<Calendar className="h-3 w-3" />
{renderDate(new Date(documentDetails.created_on))}
</h5>

View File

@ -25,14 +25,7 @@ const debounce = (func: (...args: any[]) => void, wait: number) => {
};
export const PageRenderer = (props: IPageRenderer) => {
const {
documentDetails,
editor,
editorClassNames,
editorContentCustomClassNames,
updatePageTitle,
readonly,
} = props;
const { documentDetails, editor, editorClassNames, editorContentCustomClassNames, updatePageTitle, readonly } = props;
const [pageTitle, setPagetitle] = useState(documentDetails.title);
@ -44,27 +37,24 @@ export const PageRenderer = (props: IPageRenderer) => {
};
return (
<div className="w-full pl-7 pt-5 pb-64">
<div className="w-full pb-64 pl-7 pt-5">
{!readonly ? (
<input
onChange={(e) => handlePageTitleChange(e.target.value)}
className="text-4xl bg-custom-background font-bold break-words pr-5 -mt-2 w-full border-none outline-none"
className="-mt-2 w-full break-words border-none bg-custom-background pr-5 text-4xl font-bold outline-none"
value={pageTitle}
/>
) : (
<input
onChange={(e) => handlePageTitleChange(e.target.value)}
className="text-4xl bg-custom-background font-bold break-words pr-5 -mt-2 w-full border-none outline-none overflow-x-clip"
className="-mt-2 w-full overflow-x-clip break-words border-none bg-custom-background pr-5 text-4xl font-bold outline-none"
value={pageTitle}
disabled
/>
)}
<div className="flex flex-col h-full w-full pr-5">
<div className="flex h-full w-full flex-col pr-5">
<EditorContainer editor={editor} editorClassNames={editorClassNames}>
<EditorContentWrapper
editor={editor}
editorContentCustomClassNames={editorContentCustomClassNames}
/>
<EditorContentWrapper editor={editor} editorContentCustomClassNames={editorContentCustomClassNames} />
</EditorContainer>
</div>
</div>

View File

@ -17,26 +17,24 @@ type Props = {
export const SummaryPopover: React.FC<Props> = (props) => {
const { editor, markings, sidePeekVisible, setSidePeekVisible } = props;
const [referenceElement, setReferenceElement] =
useState<HTMLButtonElement | null>(null);
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(
null,
);
const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | null>(null);
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
const { styles: summaryPopoverStyles, attributes: summaryPopoverAttributes } =
usePopper(referenceElement, popperElement, {
const { styles: summaryPopoverStyles, attributes: summaryPopoverAttributes } = usePopper(
referenceElement,
popperElement,
{
placement: "bottom-start",
});
}
);
return (
<div className="group/summary-popover w-min whitespace-nowrap">
<button
type="button"
ref={setReferenceElement}
className={`h-7 w-7 grid place-items-center rounded ${
sidePeekVisible
? "bg-custom-primary-100/20 text-custom-primary-100"
: "text-custom-text-300"
className={`grid h-7 w-7 place-items-center rounded ${
sidePeekVisible ? "bg-custom-primary-100/20 text-custom-primary-100" : "text-custom-text-300"
}`}
onClick={() => setSidePeekVisible(!sidePeekVisible)}
>
@ -44,7 +42,7 @@ export const SummaryPopover: React.FC<Props> = (props) => {
</button>
{!sidePeekVisible && (
<div
className="hidden group-hover/summary-popover:block z-10 max-h-80 w-64 shadow-custom-shadow-rg rounded border-[0.5px] border-custom-border-200 bg-custom-background-100 p-3 overflow-y-auto"
className="z-10 hidden max-h-80 w-64 overflow-y-auto rounded border-[0.5px] border-custom-border-200 bg-custom-background-100 p-3 shadow-custom-shadow-rg group-hover/summary-popover:block"
ref={setPopperElement}
style={summaryPopoverStyles.popper}
{...summaryPopoverAttributes.popper}

View File

@ -8,14 +8,10 @@ interface ISummarySideBarProps {
sidePeekVisible: boolean;
}
export const SummarySideBar = ({
editor,
markings,
sidePeekVisible,
}: ISummarySideBarProps) => {
export const SummarySideBar = ({ editor, markings, sidePeekVisible }: ISummarySideBarProps) => {
return (
<div
className={`h-full p-5 transition-all duration-200 transform overflow-hidden ${
className={`h-full transform overflow-hidden p-5 transition-all duration-200 ${
sidePeekVisible ? "translate-x-0" : "-translate-x-full"
}`}
>

View File

@ -23,11 +23,7 @@ export interface IVerticalDropdownMenuProps {
items: IVerticalDropdownItemProps[];
}
const VerticalDropdownItem = ({
Icon,
label,
action,
}: IVerticalDropdownItemProps) => {
const VerticalDropdownItem = ({ Icon, label, action }: IVerticalDropdownItemProps) => {
return (
<CustomMenu.MenuItem onClick={action} className="flex items-center gap-2">
<Icon className="h-3 w-3" />
@ -42,19 +38,11 @@ export const VerticalDropdownMenu = ({ items }: IVerticalDropdownMenuProps) => {
maxHeight={"md"}
className={"h-4.5 mt-1"}
placement={"bottom-start"}
optionsClassName={
"border-custom-border border-r border-solid transition-all duration-200 ease-in-out "
}
optionsClassName={"border-custom-border border-r border-solid transition-all duration-200 ease-in-out "}
customButton={<MoreVertical size={14} />}
>
{items.map((item, index) => (
<VerticalDropdownItem
key={index}
type={item.type}
Icon={item.Icon}
label={item.label}
action={item.action}
/>
<VerticalDropdownItem key={index} type={item.type} Icon={item.Icon} label={item.label} action={item.action} />
))}
</CustomMenu>
);

View File

@ -11,23 +11,22 @@ import { LayersIcon } from "@plane/ui";
export const DocumentEditorExtensions = (
uploadFile: UploadImage,
issueEmbedConfig?: IIssueEmbedConfig,
setIsSubmitting?: (
isSubmitting: "submitting" | "submitted" | "saved",
) => void,
setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void
) => {
const additonalOptions: ISlashCommandItem[] = [
const additionalOptions: ISlashCommandItem[] = [
{
title: "Issue Embed",
description: "Embed an issue from the project",
searchTerms: ["Issue", "Iss"],
icon: <LayersIcon height={"20px"} width={"20px"} />,
key: "issue_embed",
title: "Issue embed",
description: "Embed an issue from the project.",
searchTerms: ["issue", "link", "embed"],
icon: <LayersIcon className="h-3.5 w-3.5" />,
command: ({ editor, range }) => {
editor
.chain()
.focus()
.insertContentAt(
range,
"<p class='text-sm bg-gray-300 w-fit pl-3 pr-3 pt-1 pb-1 rounded shadow-sm'>#issue_</p>",
"<p class='text-sm bg-gray-300 w-fit pl-3 pr-3 pt-1 pb-1 rounded shadow-sm'>#issue_</p>"
)
.run();
},
@ -35,7 +34,7 @@ export const DocumentEditorExtensions = (
];
return [
SlashCommand(uploadFile, setIsSubmitting, additonalOptions),
SlashCommand(uploadFile, setIsSubmitting, additionalOptions),
DragAndDrop,
Placeholder.configure({
placeholder: ({ node }) => {

View File

@ -18,8 +18,7 @@ export interface IIssueListSuggestion {
}
export const IssueSuggestions = (suggestions: any[]) => {
const mappedSuggestions: IIssueListSuggestion[] = suggestions.map(
(suggestion): IIssueListSuggestion => {
const mappedSuggestions: IIssueListSuggestion[] = suggestions.map((suggestion): IIssueListSuggestion => {
let transactionId = uuidv4();
return {
title: suggestion.name,
@ -44,8 +43,7 @@ export const IssueSuggestions = (suggestions: any[]) => {
.run();
},
};
},
);
});
return IssueEmbedSuggestions.configure({
suggestion: {

View File

@ -9,15 +9,7 @@ export const IssueEmbedSuggestions = Extension.create({
addOptions() {
return {
suggestion: {
command: ({
editor,
range,
props,
}: {
editor: Editor;
range: Range;
props: any;
}) => {
command: ({ editor, range, props }: { editor: Editor; range: Range; props: any }) => {
props.command({ editor, range });
},
},

View File

@ -1,8 +1,6 @@
import { IIssueListSuggestion } from ".";
export const getIssueSuggestionItems = (
issueSuggestions: Array<IIssueListSuggestion>,
) => {
export const getIssueSuggestionItems = (issueSuggestions: Array<IIssueListSuggestion>) => {
return ({ query }: { query: string }) => {
const search = query.toLowerCase();
const filteredSuggestions = issueSuggestions.filter((item) => {

View File

@ -2,13 +2,7 @@ import { cn } from "@plane/editor-core";
import { Editor } from "@tiptap/core";
import tippy from "tippy.js";
import { ReactRenderer } from "@tiptap/react";
import {
useCallback,
useEffect,
useLayoutEffect,
useRef,
useState,
} from "react";
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
import { PriorityIcon } from "@plane/ui";
const updateScrollView = (container: HTMLElement, item: HTMLElement) => {
@ -62,9 +56,7 @@ const IssueSuggestionList = ({
let newDisplayedItems: { [key: string]: IssueSuggestionProps[] } = {};
let totalLength = 0;
sections.forEach((section) => {
newDisplayedItems[section] = items
.filter((item) => item.state === section)
.slice(0, 5);
newDisplayedItems[section] = items.filter((item) => item.state === section).slice(0, 5);
totalLength += newDisplayedItems[section].length;
});
@ -79,7 +71,7 @@ const IssueSuggestionList = ({
command(item);
}
},
[command, displayedItems, currentSection],
[command, displayedItems, currentSection]
);
useEffect(() => {
@ -93,22 +85,17 @@ const IssueSuggestionList = ({
// }
if (e.key === "ArrowUp") {
setSelectedIndex(
(selectedIndex + displayedItems[currentSection].length - 1) %
displayedItems[currentSection].length,
(selectedIndex + displayedItems[currentSection].length - 1) % displayedItems[currentSection].length
);
return true;
}
if (e.key === "ArrowDown") {
const nextIndex =
(selectedIndex + 1) % displayedItems[currentSection].length;
const nextIndex = (selectedIndex + 1) % displayedItems[currentSection].length;
setSelectedIndex(nextIndex);
if (nextIndex === 4) {
const nextItems = items
.filter((item) => item.state === currentSection)
.slice(
displayedItems[currentSection].length,
displayedItems[currentSection].length + 5,
);
.slice(displayedItems[currentSection].length, displayedItems[currentSection].length + 5);
setDisplayedItems((prevItems) => ({
...prevItems,
[currentSection]: [...prevItems[currentSection], ...nextItems],
@ -138,29 +125,17 @@ const IssueSuggestionList = ({
return () => {
document.removeEventListener("keydown", onKeyDown);
};
}, [
displayedItems,
selectedIndex,
setSelectedIndex,
selectItem,
currentSection,
]);
}, [displayedItems, selectedIndex, setSelectedIndex, selectItem, currentSection]);
useLayoutEffect(() => {
const container = commandListContainer?.current;
if (container) {
const sectionContainer = container?.querySelector(
`#${currentSection}-container`,
) as HTMLDivElement;
const sectionContainer = container?.querySelector(`#${currentSection}-container`) as HTMLDivElement;
if (sectionContainer) {
updateScrollView(container, sectionContainer);
}
const sectionScrollContainer = container?.querySelector(
`#${currentSection}`,
) as HTMLElement;
const item = sectionScrollContainer?.children[
selectedIndex
] as HTMLElement;
const sectionScrollContainer = container?.querySelector(`#${currentSection}`) as HTMLElement;
const item = sectionScrollContainer?.children[selectedIndex] as HTMLElement;
if (item && sectionScrollContainer) {
updateScrollView(sectionScrollContainer, item);
}
@ -171,56 +146,41 @@ const IssueSuggestionList = ({
<div
id="issue-list-container"
ref={commandListContainer}
className="z-[10] fixed max-h-80 w-60 overflow-y-auto overflow-x-hidden rounded-md border border-custom-border-100 bg-custom-background-100 px-1 shadow-custom-shadow-xs transition-all"
className="fixed z-[10] max-h-80 w-60 overflow-y-auto overflow-x-hidden rounded-md border border-custom-border-100 bg-custom-background-100 px-1 shadow-custom-shadow-xs transition-all"
>
{sections.map((section) => {
const sectionItems = displayedItems[section];
return (
sectionItems &&
sectionItems.length > 0 && (
<div
className={"h-full w-full flex flex-col"}
key={`${section}-container`}
id={`${section}-container`}
>
<div className={"flex h-full w-full flex-col"} key={`${section}-container`} id={`${section}-container`}>
<h6
className={
"sticky top-0 z-[10] bg-custom-background-100 text-xs text-custom-text-400 font-medium px-2 py-1"
"sticky top-0 z-[10] bg-custom-background-100 px-2 py-1 text-xs font-medium text-custom-text-400"
}
>
{section}
</h6>
<div
key={section}
id={section}
className={"max-h-[140px] overflow-y-scroll overflow-x-hidden"}
>
{sectionItems.map(
(item: IssueSuggestionProps, index: number) => (
<div key={section} id={section} className={"max-h-[140px] overflow-x-hidden overflow-y-scroll"}>
{sectionItems.map((item: IssueSuggestionProps, index: number) => (
<button
className={cn(
`flex w-full items-center space-x-2 rounded-md px-2 py-1 text-left text-sm text-custom-text-200 hover:bg-custom-primary-100/5 hover:text-custom-text-100`,
{
"bg-custom-primary-100/5 text-custom-text-100":
section === currentSection &&
index === selectedIndex,
},
section === currentSection && index === selectedIndex,
}
)}
key={index}
onClick={() => selectItem(index)}
>
<h5 className="text-xs text-custom-text-300 whitespace-nowrap">
{item.identifier}
</h5>
<h5 className="whitespace-nowrap text-xs text-custom-text-300">{item.identifier}</h5>
<PriorityIcon priority={item.priority} />
<div>
<p className="flex-grow text-xs truncate">
{item.title}
</p>
<p className="flex-grow truncate text-xs">{item.title}</p>
</div>
</button>
),
)}
))}
</div>
</div>
)

View File

@ -5,8 +5,7 @@ interface IssueWidgetExtensionProps {
issueEmbedConfig?: IIssueEmbedConfig;
}
export const IssueWidgetExtension = ({
issueEmbedConfig,
}: IssueWidgetExtensionProps) => IssueWidget.configure({
export const IssueWidgetExtension = ({ issueEmbedConfig }: IssueWidgetExtensionProps) =>
IssueWidget.configure({
issueEmbedConfig,
});

View File

@ -30,15 +30,13 @@ const IssueWidgetCard = (props) => {
{loading == 0 ? (
<div
onClick={completeIssueEmbedAction}
className="cursor-pointer w-full space-y-2 border-[0.5px] border-custom-border-200 rounded-md p-3 shadow-custom-shadow-2xs"
className="w-full cursor-pointer space-y-2 rounded-md border-[0.5px] border-custom-border-200 p-3 shadow-custom-shadow-2xs"
>
<h5 className="text-xs text-custom-text-300">
{issueDetails.project_detail.identifier}-{issueDetails.sequence_id}
</h5>
<h4 className="break-words text-sm font-medium">
{issueDetails.name}
</h4>
<div className="flex items-center flex-wrap gap-x-3 gap-y-2">
<h4 className="break-words text-sm font-medium">{issueDetails.name}</h4>
<div className="flex flex-wrap items-center gap-x-3 gap-y-2">
<div>
<PriorityIcon priority={issueDetails.priority} />
</div>
@ -46,18 +44,13 @@ const IssueWidgetCard = (props) => {
<AvatarGroup size="sm">
{issueDetails.assignee_details.map((assignee) => {
return (
<Avatar
key={assignee.id}
name={assignee.display_name}
src={assignee.avatar}
className={"m-0"}
/>
<Avatar key={assignee.id} name={assignee.display_name} src={assignee.avatar} className={"m-0"} />
);
})}
</AvatarGroup>
</div>
{issueDetails.target_date && (
<div className="rounded flex px-2.5 py-1 items-center border-[0.5px] border-custom-border-300 gap-1 text-custom-text-100 text-xs h-5">
<div className="flex h-5 items-center gap-1 rounded border-[0.5px] border-custom-border-300 px-2.5 py-1 text-xs text-custom-text-100">
<Calendar className="h-3 w-3" strokeWidth={1.5} />
{new Date(issueDetails.target_date).toLocaleDateString()}
</div>
@ -65,17 +58,15 @@ const IssueWidgetCard = (props) => {
</div>
</div>
) : loading == -1 ? (
<div className="flex gap-[8px] items-center pb-[10px] pt-[10px] pl-[13px] rounded border-[#D97706] border-2 bg-[#FFFBEB] text-[#D97706]">
<div className="flex items-center gap-[8px] rounded border-2 border-[#D97706] bg-[#FFFBEB] pb-[10px] pl-[13px] pt-[10px] text-[#D97706]">
<AlertTriangle color={"#D97706"} />
{
"This Issue embed is not found in any project. It can no longer be updated or accessed from here."
}
{"This Issue embed is not found in any project. It can no longer be updated or accessed from here."}
</div>
) : (
<div className="w-full space-y-2 border-[0.5px] border-custom-border-200 rounded-md p-3 shadow-custom-shadow-2xs">
<div className="w-full space-y-2 rounded-md border-[0.5px] border-custom-border-200 p-3 shadow-custom-shadow-2xs">
<Loader className={"px-6"}>
<Loader.Item height={"30px"} />
<div className={"space-y-2 mt-3"}>
<div className={"mt-3 space-y-2"}>
<Loader.Item height={"20px"} width={"70%"} />
<Loader.Item height={"20px"} width={"60%"} />
</div>

View File

@ -35,10 +35,7 @@ export const IssueWidget = Node.create({
addNodeView() {
return ReactNodeViewRenderer((props: Object) => (
<IssueWidgetCard
{...props}
issueEmbedConfig={this.options.issueEmbedConfig}
/>
<IssueWidgetCard {...props} issueEmbedConfig={this.options.issueEmbedConfig} />
));
},

View File

@ -15,21 +15,14 @@ export const useEditorMarkings = () => {
nodes.forEach((node) => {
if (
node.type === "heading" &&
(node.attrs.level === 1 ||
node.attrs.level === 2 ||
node.attrs.level === 3) &&
(node.attrs.level === 1 || node.attrs.level === 2 || node.attrs.level === 3) &&
node.content
) {
tempMarkings.push({
type: "heading",
level: node.attrs.level,
text: node.content[0].text,
sequence:
node.attrs.level === 1
? ++h1Sequence
: node.attrs.level === 2
? ++h2Sequence
: ++h3Sequence,
sequence: node.attrs.level === 1 ? ++h1Sequence : node.attrs.level === 2 ? ++h2Sequence : ++h3Sequence,
});
}
});

View File

@ -2,11 +2,7 @@
import React, { useState } from "react";
import { getEditorClassNames, useEditor } from "@plane/editor-core";
import { DocumentEditorExtensions } from "./extensions";
import {
IDuplicationConfig,
IPageArchiveConfig,
IPageLockConfig,
} from "./types/menu-actions";
import { IDuplicationConfig, IPageArchiveConfig, IPageLockConfig } from "./types/menu-actions";
import { EditorHeader } from "./components/editor-header";
import { useEditorMarkings } from "./hooks/use-editor-markings";
import { SummarySideBar } from "./components/summary-side-bar";
@ -41,9 +37,7 @@ interface IDocumentEditor {
customClassName?: string;
editorContentCustomClassNames?: string;
onChange: (json: any, html: string) => void;
setIsSubmitting?: (
isSubmitting: "submitting" | "submitted" | "saved",
) => void;
setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void;
setShouldShowAlert?: (showAlert: boolean) => void;
forwardedRef?: any;
updatePageTitle: (title: string) => Promise<void>;
@ -118,11 +112,7 @@ const DocumentEditor = ({
cancelUploadImage,
rerenderOnPropsChange,
forwardedRef,
extensions: DocumentEditorExtensions(
uploadFile,
embedConfig?.issueEmbedConfig,
setIsSubmitting,
),
extensions: DocumentEditorExtensions(uploadFile, embedConfig?.issueEmbedConfig, setIsSubmitting),
});
if (!editor) {
@ -147,7 +137,7 @@ const DocumentEditor = ({
if (!editor) return null;
return (
<div className="h-full w-full flex flex-col overflow-hidden">
<div className="flex h-full w-full flex-col overflow-hidden">
<EditorHeader
readonly={false}
KanbanMenuOptions={KanbanMenuOptions}
@ -163,13 +153,9 @@ const DocumentEditor = ({
documentDetails={documentDetails}
isSubmitting={isSubmitting}
/>
<div className="h-full w-full flex overflow-y-auto">
<div className="flex-shrink-0 h-full w-56 lg:w-72 sticky top-0">
<SummarySideBar
editor={editor}
markings={markings}
sidePeekVisible={sidePeekVisible}
/>
<div className="flex h-full w-full overflow-y-auto">
<div className="sticky top-0 h-full w-56 flex-shrink-0 lg:w-72">
<SummarySideBar editor={editor} markings={markings} sidePeekVisible={sidePeekVisible} />
</div>
<div className="h-full w-[calc(100%-14rem)] lg:w-[calc(100%-18rem-18rem)]">
<PageRenderer
@ -181,15 +167,15 @@ const DocumentEditor = ({
updatePageTitle={updatePageTitle}
/>
</div>
<div className="hidden lg:block flex-shrink-0 w-56 lg:w-72" />
<div className="hidden w-56 flex-shrink-0 lg:block lg:w-72" />
</div>
</div>
);
};
const DocumentEditorWithRef = React.forwardRef<EditorHandle, IDocumentEditor>(
(props, ref) => <DocumentEditor {...props} forwardedRef={ref} />,
);
const DocumentEditorWithRef = React.forwardRef<EditorHandle, IDocumentEditor>((props, ref) => (
<DocumentEditor {...props} forwardedRef={ref} />
));
DocumentEditorWithRef.displayName = "DocumentEditorWithRef";

View File

@ -1,6 +1,4 @@
import { Editor } from "@tiptap/react";
import { BoldIcon } from "lucide-react";
import {
BoldItem,
BulletListItem,
@ -18,22 +16,16 @@ import {
HeadingTwoItem,
HeadingThreeItem,
findTableAncestor,
EditorMenuItem,
} from "@plane/editor-core";
import { UploadImage } from "@plane/editor-types";
export interface BubbleMenuItem {
name: string;
isActive: () => boolean;
command: () => void;
icon: typeof BoldIcon;
}
export type BubbleMenuItem = EditorMenuItem;
type EditorBubbleMenuProps = {
editor: Editor;
uploadFile: UploadImage;
setIsSubmitting?: (
isSubmitting: "submitting" | "submitted" | "saved",
) => void;
setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void;
};
export const FixedMenu = (props: EditorBubbleMenuProps) => {
@ -49,15 +41,9 @@ export const FixedMenu = (props: EditorBubbleMenuProps) => {
StrikeThroughItem(editor),
];
const listItems: BubbleMenuItem[] = [
BulletListItem(editor),
NumberedListItem(editor),
];
const listItems: BubbleMenuItem[] = [BulletListItem(editor), NumberedListItem(editor)];
const userActionItems: BubbleMenuItem[] = [
QuoteItem(editor),
CodeItem(editor),
];
const userActionItems: BubbleMenuItem[] = [QuoteItem(editor), CodeItem(editor)];
function getComplexItems(): BubbleMenuItem[] {
const items: BubbleMenuItem[] = [TableItem(editor)];
@ -99,10 +85,10 @@ export const FixedMenu = (props: EditorBubbleMenuProps) => {
type="button"
onClick={item.command}
className={cn(
"h-7 w-7 grid place-items-center text-custom-text-300 hover:bg-custom-background-80 rounded",
"grid h-7 w-7 place-items-center rounded text-custom-text-300 hover:bg-custom-background-80",
{
"text-custom-text-100 bg-custom-background-80": item.isActive(),
},
"bg-custom-background-80 text-custom-text-100": item.isActive(),
}
)}
>
<item.icon className="h-4 w-4" />
@ -116,10 +102,10 @@ export const FixedMenu = (props: EditorBubbleMenuProps) => {
type="button"
onClick={item.command}
className={cn(
"h-7 w-7 grid place-items-center text-custom-text-300 hover:bg-custom-background-80 rounded",
"grid h-7 w-7 place-items-center rounded text-custom-text-300 hover:bg-custom-background-80",
{
"text-custom-text-100 bg-custom-background-80": item.isActive(),
},
"bg-custom-background-80 text-custom-text-100": item.isActive(),
}
)}
>
<item.icon
@ -137,10 +123,10 @@ export const FixedMenu = (props: EditorBubbleMenuProps) => {
type="button"
onClick={item.command}
className={cn(
"h-7 w-7 grid place-items-center text-custom-text-300 hover:bg-custom-background-80 rounded",
"grid h-7 w-7 place-items-center rounded text-custom-text-300 hover:bg-custom-background-80",
{
"text-custom-text-100 bg-custom-background-80": item.isActive(),
},
"bg-custom-background-80 text-custom-text-100": item.isActive(),
}
)}
>
<item.icon
@ -158,10 +144,10 @@ export const FixedMenu = (props: EditorBubbleMenuProps) => {
type="button"
onClick={item.command}
className={cn(
"h-7 w-7 grid place-items-center text-custom-text-300 hover:bg-custom-background-80 rounded",
"grid h-7 w-7 place-items-center rounded text-custom-text-300 hover:bg-custom-background-80",
{
"text-custom-text-100 bg-custom-background-80": item.isActive(),
},
"bg-custom-background-80 text-custom-text-100": item.isActive(),
}
)}
>
<item.icon

View File

@ -6,9 +6,5 @@ type Props = {
};
export const Icon: React.FC<Props> = ({ iconName, className = "" }) => (
<span
className={`material-symbols-rounded text-sm leading-5 font-light ${className}`}
>
{iconName}
</span>
<span className={`material-symbols-rounded text-sm font-light leading-5 ${className}`}>{iconName}</span>
);

View File

@ -8,11 +8,7 @@ import { IssueWidgetExtension } from "../extensions/widgets/IssueEmbedWidget";
import { IEmbedConfig } from "../extensions/widgets/IssueEmbedWidget/types";
import { useEditorMarkings } from "../hooks/use-editor-markings";
import { DocumentDetails } from "../types/editor-types";
import {
IPageArchiveConfig,
IPageLockConfig,
IDuplicationConfig,
} from "../types/menu-actions";
import { IPageArchiveConfig, IPageLockConfig, IDuplicationConfig } from "../types/menu-actions";
import { getMenuOptions } from "../utils/menu-options";
interface IDocumentReadOnlyEditor {
@ -67,9 +63,7 @@ const DocumentReadOnlyEditor = ({
value,
forwardedRef,
rerenderOnPropsChange,
extensions: [
IssueWidgetExtension({ issueEmbedConfig: embedConfig?.issueEmbedConfig }),
],
extensions: [IssueWidgetExtension({ issueEmbedConfig: embedConfig?.issueEmbedConfig })],
});
useEffect(() => {
@ -98,7 +92,7 @@ const DocumentReadOnlyEditor = ({
});
return (
<div className="h-full w-full flex flex-col overflow-hidden">
<div className="flex h-full w-full flex-col overflow-hidden">
<EditorHeader
isLocked={!pageLockConfig ? false : pageLockConfig.is_locked}
isArchived={!pageArchiveConfig ? false : pageArchiveConfig.is_archived}
@ -111,13 +105,9 @@ const DocumentReadOnlyEditor = ({
documentDetails={documentDetails}
archivedAt={pageArchiveConfig && pageArchiveConfig.archived_at}
/>
<div className="h-full w-full flex overflow-y-auto">
<div className="flex-shrink-0 h-full w-56 lg:w-80 sticky top-0">
<SummarySideBar
editor={editor}
markings={markings}
sidePeekVisible={sidePeekVisible}
/>
<div className="flex h-full w-full overflow-y-auto">
<div className="sticky top-0 h-full w-56 flex-shrink-0 lg:w-80">
<SummarySideBar editor={editor} markings={markings} sidePeekVisible={sidePeekVisible} />
</div>
<div className="h-full w-full">
<PageRenderer
@ -128,16 +118,15 @@ const DocumentReadOnlyEditor = ({
documentDetails={documentDetails}
/>
</div>
<div className="hidden lg:block flex-shrink-0 w-56 lg:w-80" />
<div className="hidden w-56 flex-shrink-0 lg:block lg:w-80" />
</div>
</div>
);
};
const DocumentReadOnlyEditorWithRef = forwardRef<
EditorHandle,
IDocumentReadOnlyEditor
>((props, ref) => <DocumentReadOnlyEditor {...props} forwardedRef={ref} />);
const DocumentReadOnlyEditorWithRef = forwardRef<EditorHandle, IDocumentReadOnlyEditor>((props, ref) => (
<DocumentReadOnlyEditor {...props} forwardedRef={ref} />
));
DocumentReadOnlyEditorWithRef.displayName = "DocumentReadOnlyEditorWithRef";

View File

@ -51,17 +51,11 @@ export const Tooltip: React.FC<Props> = ({
content={
<div
className={`relative z-50 max-w-xs gap-1 rounded-md p-2 text-xs shadow-md ${
theme === "custom"
? "bg-custom-background-100 text-custom-text-200"
: "bg-black text-gray-400"
} break-words overflow-hidden ${className}`}
theme === "custom" ? "bg-custom-background-100 text-custom-text-200" : "bg-black text-gray-400"
} overflow-hidden break-words ${className}`}
>
{tooltipHeading && (
<h5
className={`font-medium ${
theme === "custom" ? "text-custom-text-100" : "text-white"
}`}
>
<h5 className={`font-medium ${theme === "custom" ? "text-custom-text-100" : "text-white"}`}>
{tooltipHeading}
</h5>
)}
@ -69,11 +63,7 @@ export const Tooltip: React.FC<Props> = ({
</div>
}
position={position}
renderTarget={({
isOpen: isTooltipOpen,
ref: eleReference,
...tooltipProps
}) =>
renderTarget={({ isOpen: isTooltipOpen, ref: eleReference, ...tooltipProps }) =>
React.cloneElement(children, {
ref: eleReference,
...tooltipProps,

View File

@ -12,11 +12,7 @@ import {
} from "lucide-react";
import { NextRouter } from "next/router";
import { IVerticalDropdownItemProps } from "../components/vertical-dropdown-menu";
import {
IDuplicationConfig,
IPageArchiveConfig,
IPageLockConfig,
} from "../types/menu-actions";
import { IDuplicationConfig, IPageArchiveConfig, IPageLockConfig } from "../types/menu-actions";
import { copyMarkdownToClipboard, CopyPageLink } from "./menu-actions";
export interface MenuOptionsProps {
@ -90,8 +86,7 @@ export const getMenuOptions = ({
.then(() => {
onActionCompleteHandler({
title: "Page Copied",
message:
"Page has been copied as 'Copy of' followed by page title",
message: "Page has been copied as 'Copy of' followed by page title",
type: "success",
});
})

View File

@ -0,0 +1,4 @@
module.exports = {
root: true,
extends: ["custom"],
};

View File

@ -0,0 +1,6 @@
.next
.vercel
.tubro
out/
dis/
build/

View File

@ -0,0 +1,5 @@
{
"printWidth": 120,
"tabWidth": 2,
"trailingComma": "es5"
}

View File

@ -35,7 +35,7 @@
"@plane/editor-core": "*",
"eslint": "8.36.0",
"eslint-config-next": "13.2.4",
"lucide-react": "^0.244.0",
"lucide-react": "^0.293.0",
"tippy.js": "^6.3.7",
"@tiptap/pm": "^2.1.7"
},

View File

@ -43,9 +43,7 @@ function absoluteRect(node: Element) {
}
function nodeDOMAtCoords(coords: { x: number; y: number }) {
return document
.elementsFromPoint(coords.x, coords.y)
.find((elem: Element) => {
return document.elementsFromPoint(coords.x, coords.y).find((elem: Element) => {
return (
elem.parentElement?.matches?.(".ProseMirror") ||
elem.matches(
@ -57,7 +55,7 @@ function nodeDOMAtCoords(coords: { x: number; y: number }) {
"h1, h2, h3",
"[data-type=horizontalRule]",
".tableWrapper",
].join(", "),
].join(", ")
)
);
});
@ -104,9 +102,7 @@ function DragHandle(options: DragHandleOptions) {
const nodePos = nodePosAtDOM(node, view);
if (nodePos === null || nodePos === undefined || nodePos < 0) return;
view.dispatch(
view.state.tr.setSelection(NodeSelection.create(view.state.doc, nodePos)),
);
view.dispatch(view.state.tr.setSelection(NodeSelection.create(view.state.doc, nodePos)));
const slice = view.state.selection.content();
const { dom, text } = __serializeForClipboard(view, slice);
@ -137,9 +133,7 @@ function DragHandle(options: DragHandleOptions) {
if (nodePos === null || nodePos === undefined || nodePos < 0) return;
view.dispatch(
view.state.tr.setSelection(NodeSelection.create(view.state.doc, nodePos)),
);
view.dispatch(view.state.tr.setSelection(NodeSelection.create(view.state.doc, nodePos)));
}
let dragHandleElement: HTMLElement | null = null;

View File

@ -1,28 +1,21 @@
import {
useState,
useEffect,
useCallback,
ReactNode,
useRef,
useLayoutEffect,
} from "react";
import { useState, useEffect, useCallback, ReactNode, useRef, useLayoutEffect } from "react";
import { Editor, Range, Extension } from "@tiptap/core";
import Suggestion from "@tiptap/suggestion";
import { ReactRenderer } from "@tiptap/react";
import tippy from "tippy.js";
import type { UploadImage, ISlashCommandItem, CommandProps } from "@plane/editor-types";
import {
CaseSensitive,
Code2,
Heading1,
Heading2,
Heading3,
ImageIcon,
List,
ListOrdered,
Text,
TextQuote,
Code,
ListTodo,
MinusSquare,
CheckSquare,
ImageIcon,
Quote,
Table,
} from "lucide-react";
import {
@ -39,6 +32,7 @@ import {
} from "@plane/editor-core";
interface CommandItemProps {
key: string;
title: string;
description: string;
icon: ReactNode;
@ -50,15 +44,7 @@ const Command = Extension.create({
return {
suggestion: {
char: "/",
command: ({
editor,
range,
props,
}: {
editor: Editor;
range: Range;
props: any;
}) => {
command: ({ editor, range, props }: { editor: Editor; range: Range; props: any }) => {
props.command({ editor, range });
},
},
@ -80,132 +66,136 @@ const Command = Extension.create({
const getSuggestionItems =
(
uploadFile: UploadImage,
setIsSubmitting?: (
isSubmitting: "submitting" | "submitted" | "saved",
) => void,
additonalOptions?: Array<ISlashCommandItem>
setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void,
additionalOptions?: Array<ISlashCommandItem>
) =>
({ query }: { query: string }) => {
let slashCommands: ISlashCommandItem[] = [
{
key: "text",
title: "Text",
description: "Just start typing with plain text.",
searchTerms: ["p", "paragraph"],
icon: <Text size={18} />,
icon: <CaseSensitive className="h-3.5 w-3.5" />,
command: ({ editor, range }: CommandProps) => {
editor
.chain()
.focus()
.deleteRange(range)
.toggleNode("paragraph", "paragraph")
.run();
editor.chain().focus().deleteRange(range).toggleNode("paragraph", "paragraph").run();
},
},
{
key: "heading_1",
title: "Heading 1",
description: "Big section heading.",
searchTerms: ["title", "big", "large"],
icon: <Heading1 size={18} />,
icon: <Heading1 className="h-3.5 w-3.5" />,
command: ({ editor, range }: CommandProps) => {
toggleHeadingOne(editor, range);
},
},
{
key: "heading_2",
title: "Heading 2",
description: "Medium section heading.",
searchTerms: ["subtitle", "medium"],
icon: <Heading2 size={18} />,
icon: <Heading2 className="h-3.5 w-3.5" />,
command: ({ editor, range }: CommandProps) => {
toggleHeadingTwo(editor, range);
},
},
{
key: "heading_3",
title: "Heading 3",
description: "Small section heading.",
searchTerms: ["subtitle", "small"],
icon: <Heading3 size={18} />,
icon: <Heading3 className="h-3.5 w-3.5" />,
command: ({ editor, range }: CommandProps) => {
toggleHeadingThree(editor, range);
},
},
{
title: "To-do List",
key: "todo_list",
title: "To do",
description: "Track tasks with a to-do list.",
searchTerms: ["todo", "task", "list", "check", "checkbox"],
icon: <CheckSquare size={18} />,
icon: <ListTodo className="h-3.5 w-3.5" />,
command: ({ editor, range }: CommandProps) => {
toggleTaskList(editor, range);
},
},
{
title: "Bullet List",
key: "bullet_list",
title: "Bullet list",
description: "Create a simple bullet list.",
searchTerms: ["unordered", "point"],
icon: <List size={18} />,
icon: <List className="h-3.5 w-3.5" />,
command: ({ editor, range }: CommandProps) => {
toggleBulletList(editor, range);
},
},
{
title: "Divider",
description: "Visually divide blocks",
searchTerms: ["line", "divider", "horizontal", "rule", "separate"],
icon: <MinusSquare size={18} />,
command: ({ editor, range }: CommandProps) => {
// @ts-expect-error I have to move this to the core
editor.chain().focus().deleteRange(range).setHorizontalRule().run();
},
},
{
title: "Table",
description: "Create a Table",
searchTerms: ["table", "cell", "db", "data", "tabular"],
icon: <Table size={18} />,
command: ({ editor, range }: CommandProps) => {
insertTableCommand(editor, range);
},
},
{
title: "Numbered List",
key: "numbered_list",
title: "Numbered list",
description: "Create a list with numbering.",
searchTerms: ["ordered"],
icon: <ListOrdered size={18} />,
icon: <ListOrdered className="h-3.5 w-3.5" />,
command: ({ editor, range }: CommandProps) => {
toggleOrderedList(editor, range);
},
},
{
key: "table",
title: "Table",
description: "Create a table",
searchTerms: ["table", "cell", "db", "data", "tabular"],
icon: <Table className="h-3.5 w-3.5" />,
command: ({ editor, range }: CommandProps) => {
insertTableCommand(editor, range);
},
},
{
key: "quote_block",
title: "Quote",
description: "Capture a quote.",
searchTerms: ["blockquote"],
icon: <TextQuote size={18} />,
command: ({ editor, range }: CommandProps) =>
toggleBlockquote(editor, range),
icon: <Quote className="h-3.5 w-3.5" />,
command: ({ editor, range }: CommandProps) => toggleBlockquote(editor, range),
},
{
key: "code_block",
title: "Code",
description: "Capture a code snippet.",
searchTerms: ["codeblock"],
icon: <Code size={18} />,
icon: <Code2 className="h-3.5 w-3.5" />,
command: ({ editor, range }: CommandProps) =>
// @ts-expect-error I have to move this to the core
editor.chain().focus().deleteRange(range).toggleCodeBlock().run(),
},
{
key: "image",
title: "Image",
description: "Upload an image from your computer.",
searchTerms: ["photo", "picture", "media"],
icon: <ImageIcon size={18} />,
icon: <ImageIcon className="h-3.5 w-3.5" />,
command: ({ editor, range }: CommandProps) => {
insertImageCommand(editor, uploadFile, setIsSubmitting, range);
},
},
]
{
key: "divider",
title: "Divider",
description: "Visually divide blocks.",
searchTerms: ["line", "divider", "horizontal", "rule", "separate"],
icon: <MinusSquare className="h-3.5 w-3.5" />,
command: ({ editor, range }: CommandProps) => {
// @ts-expect-error I have to move this to the core
editor.chain().focus().deleteRange(range).setHorizontalRule().run();
},
},
];
if (additonalOptions) {
additonalOptions.map(item => {
slashCommands.push(item)
})
if (additionalOptions) {
additionalOptions.map((item) => {
slashCommands.push(item);
});
}
slashCommands = slashCommands.filter((item) => {
@ -214,14 +204,13 @@ const getSuggestionItems =
return (
item.title.toLowerCase().includes(search) ||
item.description.toLowerCase().includes(search) ||
(item.searchTerms &&
item.searchTerms.some((term: string) => term.includes(search)))
(item.searchTerms && item.searchTerms.some((term: string) => term.includes(search)))
);
}
return true;
})
});
return slashCommands
return slashCommands;
};
export const updateScrollView = (container: HTMLElement, item: HTMLElement) => {
@ -238,15 +227,7 @@ export const updateScrollView = (container: HTMLElement, item: HTMLElement) => {
}
};
const CommandList = ({
items,
command,
}: {
items: CommandItemProps[];
command: any;
editor: any;
range: any;
}) => {
const CommandList = ({ items, command }: { items: CommandItemProps[]; command: any; editor: any; range: any }) => {
const [selectedIndex, setSelectedIndex] = useState(0);
const selectItem = useCallback(
@ -256,7 +237,7 @@ const CommandList = ({
command(item);
}
},
[command, items],
[command, items]
);
useEffect(() => {
@ -303,27 +284,21 @@ const CommandList = ({
<div
id="slash-command"
ref={commandListContainer}
className="z-50 fixed h-auto max-h-[330px] w-72 overflow-y-auto rounded-md border border-custom-border-300 bg-custom-background-100 px-1 py-2 shadow-md transition-all"
className="fixed z-50 h-auto max-h-[330px] w-52 overflow-y-auto rounded-md border border-custom-border-300 bg-custom-background-100 px-1 py-2 shadow-md transition-all"
>
{items.map((item: CommandItemProps, index: number) => (
{items.map((item, index) => (
<button
key={item.key}
className={cn(
`flex w-full items-center space-x-2 rounded-md px-2 py-1 text-left text-sm text-custom-text-200 hover:bg-custom-primary-100/5 hover:text-custom-text-100`,
`flex w-full items-center gap-2 rounded-md px-2.5 py-1.5 text-sm text-custom-text-100 hover:bg-custom-primary-100/5`,
{
"bg-custom-primary-100/5 text-custom-text-100":
index === selectedIndex,
},
"bg-custom-primary-100/5": index === selectedIndex,
}
)}
key={index}
onClick={() => selectItem(index)}
>
<div className="flex h-10 w-10 items-center justify-center rounded-md border border-custom-border-300 bg-custom-background-100">
{item.icon}
</div>
<div>
<p className="font-medium">{item.title}</p>
<p className="text-xs text-custom-text-300">{item.description}</p>
</div>
<div className="grid flex-shrink-0 place-items-center">{item.icon}</div>
<p>{item.title}</p>
</button>
))}
</div>
@ -380,14 +355,12 @@ const renderItems = () => {
export const SlashCommand = (
uploadFile: UploadImage,
setIsSubmitting?: (
isSubmitting: "submitting" | "submitted" | "saved",
) => void,
additonalOptions?: Array<ISlashCommandItem>,
setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void,
additionalOptions?: Array<ISlashCommandItem>
) =>
Command.configure({
suggestion: {
items: getSuggestionItems(uploadFile, setIsSubmitting, additonalOptions),
items: getSuggestionItems(uploadFile, setIsSubmitting, additionalOptions),
render: renderItems,
},
});

View File

@ -0,0 +1,4 @@
module.exports = {
root: true,
extends: ["custom"],
};

View File

@ -0,0 +1,6 @@
.next
.vercel
.tubro
out/
dis/
build/

View File

@ -0,0 +1,5 @@
{
"printWidth": 120,
"tabWidth": 2,
"trailingComma": "es5"
}

View File

@ -1,6 +1,3 @@
export { LiteTextEditor, LiteTextEditorWithRef } from "./ui";
export { LiteReadOnlyEditor, LiteReadOnlyEditorWithRef } from "./ui/read-only";
export type {
IMentionSuggestion,
IMentionHighlight,
} from "@plane/editor-types";
export type { IMentionSuggestion, IMentionHighlight } from "@plane/editor-types";

View File

@ -1,18 +1,8 @@
import * as React from "react";
import {
EditorContainer,
EditorContentWrapper,
getEditorClassNames,
useEditor,
} from "@plane/editor-core";
import { EditorContainer, EditorContentWrapper, getEditorClassNames, useEditor } from "@plane/editor-core";
import { FixedMenu } from "./menus/fixed-menu";
import { LiteTextEditorExtensions } from "./extensions";
import {
UploadImage,
DeleteImage,
IMentionSuggestion,
RestoreImage,
} from "@plane/editor-types";
import { UploadImage, DeleteImage, IMentionSuggestion, RestoreImage } from "@plane/editor-types";
interface ILiteTextEditor {
value: string;
@ -25,9 +15,7 @@ interface ILiteTextEditor {
customClassName?: string;
editorContentCustomClassNames?: string;
onChange?: (json: any, html: string) => void;
setIsSubmitting?: (
isSubmitting: "submitting" | "submitted" | "saved",
) => void;
setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void;
setShouldShowAlert?: (showAlert: boolean) => void;
forwardedRef?: any;
debouncedUpdatesEnabled?: boolean;
@ -107,11 +95,8 @@ const LiteTextEditor = (props: LiteTextEditorProps) => {
return (
<EditorContainer editor={editor} editorClassNames={editorClassNames}>
<div className="flex flex-col">
<EditorContentWrapper
editor={editor}
editorContentCustomClassNames={editorContentCustomClassNames}
/>
<div className="w-full mt-4">
<EditorContentWrapper editor={editor} editorContentCustomClassNames={editorContentCustomClassNames} />
<div className="mt-4 w-full">
<FixedMenu
editor={editor}
uploadFile={uploadFile}
@ -125,9 +110,9 @@ const LiteTextEditor = (props: LiteTextEditorProps) => {
);
};
const LiteTextEditorWithRef = React.forwardRef<EditorHandle, ILiteTextEditor>(
(props, ref) => <LiteTextEditor {...props} forwardedRef={ref} />,
);
const LiteTextEditorWithRef = React.forwardRef<EditorHandle, ILiteTextEditor>((props, ref) => (
<LiteTextEditor {...props} forwardedRef={ref} />
));
LiteTextEditorWithRef.displayName = "LiteTextEditorWithRef";

View File

@ -6,9 +6,5 @@ type Props = {
};
export const Icon: React.FC<Props> = ({ iconName, className = "" }) => (
<span
className={`material-symbols-rounded text-sm leading-5 font-light ${className}`}
>
{iconName}
</span>
<span className={`material-symbols-rounded text-sm font-light leading-5 ${className}`}>{iconName}</span>
);

View File

@ -47,9 +47,7 @@ type EditorBubbleMenuProps = {
| undefined;
};
uploadFile: UploadImage;
setIsSubmitting?: (
isSubmitting: "submitting" | "submitted" | "saved",
) => void;
setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void;
submitButton: React.ReactNode;
};
@ -61,23 +59,15 @@ export const FixedMenu = (props: EditorBubbleMenuProps) => {
StrikeThroughItem(props.editor),
];
const listFormattingItems: BubbleMenuItem[] = [
BulletListItem(props.editor),
NumberedListItem(props.editor),
];
const listFormattingItems: BubbleMenuItem[] = [BulletListItem(props.editor), NumberedListItem(props.editor)];
const userActionItems: BubbleMenuItem[] = [
QuoteItem(props.editor),
CodeItem(props.editor),
];
const userActionItems: BubbleMenuItem[] = [QuoteItem(props.editor), CodeItem(props.editor)];
function getComplexItems(): BubbleMenuItem[] {
const items: BubbleMenuItem[] = [TableItem(props.editor)];
if (shouldShowImageItem()) {
items.push(
ImageItem(props.editor, props.uploadFile, props.setIsSubmitting),
);
items.push(ImageItem(props.editor, props.uploadFile, props.setIsSubmitting));
}
return items;
@ -109,22 +99,20 @@ export const FixedMenu = (props: EditorBubbleMenuProps) => {
};
return (
<div className="flex items-stretch gap-1.5 w-full h-9 overflow-x-scroll">
<div className="flex h-9 w-full items-stretch gap-1.5 overflow-x-scroll">
{props.commentAccessSpecifier && (
<div className="flex-shrink-0 flex items-stretch gap-0.5 border-[0.5px] border-custom-border-200 rounded p-1">
<div className="flex flex-shrink-0 items-stretch gap-0.5 rounded border-[0.5px] border-custom-border-200 p-1">
{props?.commentAccessSpecifier.commentAccess?.map((access) => (
<Tooltip key={access.key} tooltipContent={access.label}>
<button
type="button"
onClick={() => handleAccessChange(access.key)}
className={`aspect-square grid place-items-center p-1 rounded-sm hover:bg-custom-background-90 ${
props.commentAccessSpecifier?.accessValue === access.key
? "bg-custom-background-90"
: ""
className={`grid aspect-square place-items-center rounded-sm p-1 hover:bg-custom-background-90 ${
props.commentAccessSpecifier?.accessValue === access.key ? "bg-custom-background-90" : ""
}`}
>
<access.icon
className={`w-3.5 h-3.5 ${
className={`h-3.5 w-3.5 ${
props.commentAccessSpecifier?.accessValue === access.key
? "text-custom-text-100"
: "text-custom-text-400"
@ -136,23 +124,19 @@ export const FixedMenu = (props: EditorBubbleMenuProps) => {
))}
</div>
)}
<div className="flex items-stretch justify-between gap-2 w-full border-[0.5px] border-custom-border-200 bg-custom-background-90 rounded p-1">
<div className="flex w-full items-stretch justify-between gap-2 rounded border-[0.5px] border-custom-border-200 bg-custom-background-90 p-1">
<div className="flex items-stretch">
<div className="flex items-stretch gap-0.5 pr-2.5 border-r border-custom-border-200">
<div className="flex items-stretch gap-0.5 border-r border-custom-border-200 pr-2.5">
{basicTextFormattingItems.map((item, index) => (
<Tooltip
key={index}
tooltipContent={<span className="capitalize">{item.name}</span>}
>
<Tooltip key={index} tooltipContent={<span className="capitalize">{item.name}</span>}>
<button
type="button"
onClick={item.command}
className={cn(
"p-1 aspect-square text-custom-text-400 hover:bg-custom-background-80 rounded-sm grid place-items-center",
"grid aspect-square place-items-center rounded-sm p-1 text-custom-text-400 hover:bg-custom-background-80",
{
"text-custom-text-100 bg-custom-background-80":
item.isActive(),
},
"bg-custom-background-80 text-custom-text-100": item.isActive(),
}
)}
>
<item.icon
@ -165,21 +149,17 @@ export const FixedMenu = (props: EditorBubbleMenuProps) => {
</Tooltip>
))}
</div>
<div className="flex items-stretch gap-0.5 px-2.5 border-r border-custom-border-200">
<div className="flex items-stretch gap-0.5 border-r border-custom-border-200 px-2.5">
{listFormattingItems.map((item, index) => (
<Tooltip
key={index}
tooltipContent={<span className="capitalize">{item.name}</span>}
>
<Tooltip key={index} tooltipContent={<span className="capitalize">{item.name}</span>}>
<button
type="button"
onClick={item.command}
className={cn(
"p-1 aspect-square text-custom-text-400 hover:bg-custom-background-80 rounded-sm grid place-items-center",
"grid aspect-square place-items-center rounded-sm p-1 text-custom-text-400 hover:bg-custom-background-80",
{
"text-custom-text-100 bg-custom-background-80":
item.isActive(),
},
"bg-custom-background-80 text-custom-text-100": item.isActive(),
}
)}
>
<item.icon
@ -192,21 +172,17 @@ export const FixedMenu = (props: EditorBubbleMenuProps) => {
</Tooltip>
))}
</div>
<div className="flex items-stretch gap-0.5 px-2.5 border-r border-custom-border-200">
<div className="flex items-stretch gap-0.5 border-r border-custom-border-200 px-2.5">
{userActionItems.map((item, index) => (
<Tooltip
key={index}
tooltipContent={<span className="capitalize">{item.name}</span>}
>
<Tooltip key={index} tooltipContent={<span className="capitalize">{item.name}</span>}>
<button
type="button"
onClick={item.command}
className={cn(
"p-1 aspect-square text-custom-text-400 hover:bg-custom-background-80 rounded-sm grid place-items-center",
"grid aspect-square place-items-center rounded-sm p-1 text-custom-text-400 hover:bg-custom-background-80",
{
"text-custom-text-100 bg-custom-background-80":
item.isActive(),
},
"bg-custom-background-80 text-custom-text-100": item.isActive(),
}
)}
>
<item.icon
@ -221,19 +197,15 @@ export const FixedMenu = (props: EditorBubbleMenuProps) => {
</div>
<div className="flex items-stretch gap-0.5 pl-2.5">
{complexItems.map((item, index) => (
<Tooltip
key={index}
tooltipContent={<span className="capitalize">{item.name}</span>}
>
<Tooltip key={index} tooltipContent={<span className="capitalize">{item.name}</span>}>
<button
type="button"
onClick={item.command}
className={cn(
"p-1 aspect-square text-custom-text-400 hover:bg-custom-background-80 rounded-sm grid place-items-center",
"grid aspect-square place-items-center rounded-sm p-1 text-custom-text-400 hover:bg-custom-background-80",
{
"text-custom-text-100 bg-custom-background-80":
item.isActive(),
},
"bg-custom-background-80 text-custom-text-100": item.isActive(),
}
)}
>
<item.icon

View File

@ -1,10 +1,5 @@
import * as React from "react";
import {
EditorContainer,
EditorContentWrapper,
getEditorClassNames,
useReadOnlyEditor,
} from "@plane/editor-core";
import { EditorContainer, EditorContentWrapper, getEditorClassNames, useReadOnlyEditor } from "@plane/editor-core";
interface ICoreReadOnlyEditor {
value: string;
@ -50,19 +45,15 @@ const LiteReadOnlyEditor = ({
return (
<EditorContainer editor={editor} editorClassNames={editorClassNames}>
<div className="flex flex-col">
<EditorContentWrapper
editor={editor}
editorContentCustomClassNames={editorContentCustomClassNames}
/>
<EditorContentWrapper editor={editor} editorContentCustomClassNames={editorContentCustomClassNames} />
</div>
</EditorContainer>
);
};
const LiteReadOnlyEditorWithRef = React.forwardRef<
EditorHandle,
ICoreReadOnlyEditor
>((props, ref) => <LiteReadOnlyEditor {...props} forwardedRef={ref} />);
const LiteReadOnlyEditorWithRef = React.forwardRef<EditorHandle, ICoreReadOnlyEditor>((props, ref) => (
<LiteReadOnlyEditor {...props} forwardedRef={ref} />
));
LiteReadOnlyEditorWithRef.displayName = "LiteReadOnlyEditorWithRef";

View File

@ -50,17 +50,11 @@ export const Tooltip: React.FC<Props> = ({
content={
<div
className={`relative z-50 max-w-xs gap-1 rounded-md p-2 text-xs shadow-md ${
theme === "custom"
? "bg-custom-background-100 text-custom-text-200"
: "bg-black text-gray-400"
} break-words overflow-hidden ${className}`}
theme === "custom" ? "bg-custom-background-100 text-custom-text-200" : "bg-black text-gray-400"
} overflow-hidden break-words ${className}`}
>
{tooltipHeading && (
<h5
className={`font-medium ${
theme === "custom" ? "text-custom-text-100" : "text-white"
}`}
>
<h5 className={`font-medium ${theme === "custom" ? "text-custom-text-100" : "text-white"}`}>
{tooltipHeading}
</h5>
)}
@ -68,11 +62,7 @@ export const Tooltip: React.FC<Props> = ({
</div>
}
position={position}
renderTarget={({
isOpen: isTooltipOpen,
ref: eleReference,
...tooltipProps
}) =>
renderTarget={({ isOpen: isTooltipOpen, ref: eleReference, ...tooltipProps }) =>
React.cloneElement(children, {
ref: eleReference,
...tooltipProps,

View File

@ -0,0 +1,4 @@
module.exports = {
root: true,
extends: ["custom"],
};

View File

@ -0,0 +1,6 @@
.next
.vercel
.tubro
out/
dis/
build/

View File

@ -0,0 +1,5 @@
{
"printWidth": 120,
"tabWidth": 2,
"trailingComma": "es5"
}

View File

@ -41,9 +41,7 @@ The `@plane/rich-text-editor` package extends from the `editor-core` package, in
debouncedUpdatesEnabled={true}
setShouldShowAlert={setShowAlert}
setIsSubmitting={setIsSubmitting}
customClassName={
isAllowed ? "min-h-[150px] shadow-sm" : "!p-0 !pt-2 text-custom-text-200"
}
customClassName={isAllowed ? "min-h-[150px] shadow-sm" : "!p-0 !pt-2 text-custom-text-200"}
noBorder={!isAllowed}
onChange={(description: Object, description_html: string) => {
setShowAlert(true);
@ -96,8 +94,5 @@ return (
Here is an example of how to use the `RichReadOnlyEditor` component
```tsx
<RichReadOnlyEditor
value={issueDetails.description_html}
customClassName="p-3 min-h-[50px] shadow-sm"
/>
<RichReadOnlyEditor value={issueDetails.description_html} customClassName="p-3 min-h-[50px] shadow-sm" />
```

View File

@ -1,7 +1,4 @@
export { RichTextEditor, RichTextEditorWithRef } from "./ui";
export { RichReadOnlyEditor, RichReadOnlyEditorWithRef } from "./ui/read-only";
export type { RichTextEditorProps, IRichTextEditor } from "./ui";
export type {
IMentionHighlight,
IMentionSuggestion,
} from "@plane/editor-types";
export type { IMentionHighlight, IMentionSuggestion } from "@plane/editor-types";

View File

@ -5,10 +5,8 @@ import { UploadImage } from "@plane/editor-types";
export const RichTextEditorExtensions = (
uploadFile: UploadImage,
setIsSubmitting?: (
isSubmitting: "submitting" | "submitted" | "saved",
) => void,
dragDropEnabled?: boolean,
setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void,
dragDropEnabled?: boolean
) => [
SlashCommand(uploadFile, setIsSubmitting),
dragDropEnabled === true && DragAndDrop,

View File

@ -1,19 +1,9 @@
"use client";
import * as React from "react";
import {
EditorContainer,
EditorContentWrapper,
getEditorClassNames,
useEditor,
} from "@plane/editor-core";
import { EditorContainer, EditorContentWrapper, getEditorClassNames, useEditor } from "@plane/editor-core";
import { EditorBubbleMenu } from "./menus/bubble-menu";
import { RichTextEditorExtensions } from "./extensions";
import {
DeleteImage,
IMentionSuggestion,
RestoreImage,
UploadImage,
} from "@plane/editor-types";
import { DeleteImage, IMentionSuggestion, RestoreImage, UploadImage } from "@plane/editor-types";
export type IRichTextEditor = {
value: string;
@ -31,9 +21,7 @@ export type IRichTextEditor = {
customClassName?: string;
editorContentCustomClassNames?: string;
onChange?: (json: any, html: string) => void;
setIsSubmitting?: (
isSubmitting: "submitting" | "submitted" | "saved",
) => void;
setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void;
setShouldShowAlert?: (showAlert: boolean) => void;
forwardedRef?: any;
debouncedUpdatesEnabled?: boolean;
@ -82,11 +70,7 @@ const RichTextEditor = ({
restoreFile,
forwardedRef,
rerenderOnPropsChange,
extensions: RichTextEditorExtensions(
uploadFile,
setIsSubmitting,
dragDropEnabled,
),
extensions: RichTextEditorExtensions(uploadFile, setIsSubmitting, dragDropEnabled),
mentionHighlights,
mentionSuggestions,
});
@ -103,18 +87,15 @@ const RichTextEditor = ({
<EditorContainer editor={editor} editorClassNames={editorClassNames}>
{editor && <EditorBubbleMenu editor={editor} />}
<div className="flex flex-col">
<EditorContentWrapper
editor={editor}
editorContentCustomClassNames={editorContentCustomClassNames}
/>
<EditorContentWrapper editor={editor} editorContentCustomClassNames={editorContentCustomClassNames} />
</div>
</EditorContainer>
);
};
const RichTextEditorWithRef = React.forwardRef<EditorHandle, IRichTextEditor>(
(props, ref) => <RichTextEditor {...props} forwardedRef={ref} />,
);
const RichTextEditorWithRef = React.forwardRef<EditorHandle, IRichTextEditor>((props, ref) => (
<RichTextEditor {...props} forwardedRef={ref} />
));
RichTextEditorWithRef.displayName = "RichTextEditorWithRef";

View File

@ -123,11 +123,10 @@ export const EditorBubbleMenu: FC<EditorBubbleMenuProps> = (props: any) => {
type="button"
onClick={item.command}
className={cn(
"p-2 text-custom-text-300 hover:bg-custom-primary-100/5 active:bg-custom-primary-100/5 transition-colors",
"p-2 text-custom-text-300 transition-colors hover:bg-custom-primary-100/5 active:bg-custom-primary-100/5",
{
"text-custom-text-100 bg-custom-primary-100/5":
item.isActive(),
},
"bg-custom-primary-100/5 text-custom-text-100": item.isActive(),
}
)}
>
<item.icon

View File

@ -1,19 +1,7 @@
import { Editor } from "@tiptap/core";
import { Check, Trash } from "lucide-react";
import {
Dispatch,
FC,
SetStateAction,
useCallback,
useEffect,
useRef,
} from "react";
import {
cn,
isValidHttpUrl,
setLinkEditor,
unsetLinkEditor,
} from "@plane/editor-core";
import { Dispatch, FC, SetStateAction, useCallback, useEffect, useRef } from "react";
import { cn, isValidHttpUrl, setLinkEditor, unsetLinkEditor } from "@plane/editor-core";
interface LinkSelectorProps {
editor: Editor;
@ -21,11 +9,7 @@ interface LinkSelectorProps {
setIsOpen: Dispatch<SetStateAction<boolean>>;
}
export const LinkSelector: FC<LinkSelectorProps> = ({
editor,
isOpen,
setIsOpen,
}) => {
export const LinkSelector: FC<LinkSelectorProps> = ({ editor, isOpen, setIsOpen }) => {
const inputRef = useRef<HTMLInputElement>(null);
const onLinkSubmit = useCallback(() => {
@ -47,7 +31,7 @@ export const LinkSelector: FC<LinkSelectorProps> = ({
type="button"
className={cn(
"flex h-full items-center space-x-2 px-3 py-1.5 text-sm font-medium text-custom-text-300 hover:bg-custom-background-100 active:bg-custom-background-100",
{ "bg-custom-background-100": isOpen },
{ "bg-custom-background-100": isOpen }
)}
onClick={() => {
setIsOpen(!isOpen);
@ -64,7 +48,7 @@ export const LinkSelector: FC<LinkSelectorProps> = ({
</button>
{isOpen && (
<div
className="fixed top-full z-[99999] mt-1 flex w-60 overflow-hidden rounded border border-custom-border-300 bg-custom-background-100 dow-xl animate-in fade-in slide-in-from-top-1"
className="dow-xl fixed top-full z-[99999] mt-1 flex w-60 overflow-hidden rounded border border-custom-border-300 bg-custom-background-100 animate-in fade-in slide-in-from-top-1"
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();
@ -76,7 +60,7 @@ export const LinkSelector: FC<LinkSelectorProps> = ({
ref={inputRef}
type="url"
placeholder="Paste a link"
className="flex-1 bg-custom-background-100 border-r border-custom-border-300 p-1 text-sm outline-none placeholder:text-custom-text-400"
className="flex-1 border-r border-custom-border-300 bg-custom-background-100 p-1 text-sm outline-none placeholder:text-custom-text-400"
defaultValue={editor.getAttributes("link").href || ""}
/>
{editor.getAttributes("link").href ? (

View File

@ -21,21 +21,13 @@ interface NodeSelectorProps {
setIsOpen: Dispatch<SetStateAction<boolean>>;
}
export const NodeSelector: FC<NodeSelectorProps> = ({
editor,
isOpen,
setIsOpen,
}) => {
export const NodeSelector: FC<NodeSelectorProps> = ({ editor, isOpen, setIsOpen }) => {
const items: BubbleMenuItem[] = [
{
name: "Text",
icon: TextIcon,
command: () =>
editor.chain().focus().toggleNode("paragraph", "paragraph").run(),
isActive: () =>
editor.isActive("paragraph") &&
!editor.isActive("bulletList") &&
!editor.isActive("orderedList"),
command: () => editor.chain().focus().toggleNode("paragraph", "paragraph").run(),
isActive: () => editor.isActive("paragraph") && !editor.isActive("bulletList") && !editor.isActive("orderedList"),
},
HeadingOneItem(editor),
HeadingTwoItem(editor),
@ -75,9 +67,8 @@ export const NodeSelector: FC<NodeSelectorProps> = ({
className={cn(
"flex items-center justify-between rounded-sm px-2 py-1 text-sm text-custom-text-200 hover:bg-custom-primary-100/5 hover:text-custom-text-100",
{
"bg-custom-primary-100/5 text-custom-text-100":
activeItem.name === item.name,
},
"bg-custom-primary-100/5 text-custom-text-100": activeItem.name === item.name,
}
)}
>
<div className="flex items-center space-x-2">

View File

@ -1,10 +1,5 @@
"use client";
import {
EditorContainer,
EditorContentWrapper,
getEditorClassNames,
useReadOnlyEditor,
} from "@plane/editor-core";
import { EditorContainer, EditorContentWrapper, getEditorClassNames, useReadOnlyEditor } from "@plane/editor-core";
import * as React from "react";
interface IRichTextReadOnlyEditor {
@ -51,19 +46,15 @@ const RichReadOnlyEditor = ({
return (
<EditorContainer editor={editor} editorClassNames={editorClassNames}>
<div className="flex flex-col">
<EditorContentWrapper
editor={editor}
editorContentCustomClassNames={editorContentCustomClassNames}
/>
<EditorContentWrapper editor={editor} editorContentCustomClassNames={editorContentCustomClassNames} />
</div>
</EditorContainer>
);
};
const RichReadOnlyEditorWithRef = React.forwardRef<
EditorHandle,
IRichTextReadOnlyEditor
>((props, ref) => <RichReadOnlyEditor {...props} forwardedRef={ref} />);
const RichReadOnlyEditorWithRef = React.forwardRef<EditorHandle, IRichTextReadOnlyEditor>((props, ref) => (
<RichReadOnlyEditor {...props} forwardedRef={ref} />
));
RichReadOnlyEditorWithRef.displayName = "RichReadOnlyEditorWithRef";

View File

@ -0,0 +1,4 @@
module.exports = {
root: true,
extends: ["custom"],
};

View File

@ -0,0 +1,6 @@
.next
.vercel
.tubro
out/
dis/
build/

View File

@ -0,0 +1,5 @@
{
"printWidth": 120,
"tabWidth": 2,
"trailingComma": "es5"
}

View File

@ -1,8 +1,5 @@
export type { DeleteImage } from "./types/delete-image";
export type { UploadImage } from "./types/upload-image";
export type { RestoreImage } from "./types/restore-image";
export type {
IMentionHighlight,
IMentionSuggestion,
} from "./types/mention-suggestion";
export type { ISlashCommandItem, CommandProps } from "./types/slash-commands-suggestion"
export type { IMentionHighlight, IMentionSuggestion } from "./types/mention-suggestion";
export type { ISlashCommandItem, CommandProps } from "./types/slash-commands-suggestion";

View File

@ -1,15 +1,16 @@
import { ReactNode } from "react";
import { Editor, Range } from "@tiptap/core"
import { Editor, Range } from "@tiptap/core";
export type CommandProps = {
editor: Editor;
range: Range;
}
};
export type ISlashCommandItem = {
key: string;
title: string;
description: string;
searchTerms: string[];
icon: ReactNode;
command: ({ editor, range }: CommandProps) => void;
}
};

View File

@ -8,10 +8,11 @@
"eslint": "^7.23.0",
"eslint-config-next": "13.0.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-react": "7.31.8",
"eslint-config-turbo": "latest"
"eslint-config-turbo": "latest",
"eslint-plugin-react": "7.31.8"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^6.13.2",
"typescript": "^4.7.4"
},
"publishConfig": {

4
packages/ui/.eslintrc.js Normal file
View File

@ -0,0 +1,4 @@
module.exports = {
root: true,
extends: ["custom"],
};

Some files were not shown because too many files have changed in this diff Show More