diff --git a/.github/workflows/auto-merge.yml b/.github/workflows/auto-merge.yml index 48c46cae4..908f1ea93 100644 --- a/.github/workflows/auto-merge.yml +++ b/.github/workflows/auto-merge.yml @@ -1,4 +1,4 @@ -name: Auto Merge or Create PR on Push +name: Create PR on Sync on: workflow_dispatch: @@ -56,30 +56,14 @@ jobs: sudo apt update sudo apt install gh -y - - name: Check for merge conflicts - id: conflicts - run: | - git fetch origin $TARGET_BRANCH - git checkout $TARGET_BRANCH - # Attempt to merge the main branch into the current branch - if $(git merge --no-commit --no-ff $SOURCE_BRANCH); then - echo "No merge conflicts detected." - echo "HAS_CONFLICTS=false" >> $GITHUB_ENV - else - echo "Merge conflicts detected." - echo "HAS_CONFLICTS=true" >> $GITHUB_ENV - git merge --abort - fi - - - name: Merge Change to Target Branch - if: env.HAS_CONFLICTS == 'false' - run: | - git commit -m "Merge branch '$SOURCE_BRANCH' into $TARGET_BRANCH" - git push origin $TARGET_BRANCH - - name: Create PR to Target Branch - if: env.HAS_CONFLICTS == 'true' run: | - # Replace 'username' with the actual GitHub username of the reviewer. - PR_URL=$(gh pr create --base $TARGET_BRANCH --head $SOURCE_BRANCH --title "sync: merge conflicts need to be resolved" --body "" --reviewer $REVIEWER) - echo "Pull Request created: $PR_URL" + # get all pull requests and check if there is already a PR + PR_EXISTS=$(gh pr list --base $TARGET_BRANCH --head $SOURCE_BRANCH --json number | jq '.[] | .number') + if [ -n "$PR_EXISTS" ]; then + echo "Pull Request already exists: $PR_EXISTS" + else + echo "Creating new pull request" + PR_URL=$(gh pr create --base $TARGET_BRANCH --head $SOURCE_BRANCH --title "sync: merge conflicts need to be resolved" --body "") + echo "Pull Request created: $PR_URL" + fi diff --git a/deploy/coolify/coolify-docker-compose.yml b/deploy/coolify/coolify-docker-compose.yml index 58e00a7a7..8ac5f44f0 100644 --- a/deploy/coolify/coolify-docker-compose.yml +++ b/deploy/coolify/coolify-docker-compose.yml @@ -180,7 +180,7 @@ services: plane-redis: container_name: plane-redis - image: redis:6.2.7-alpine + image: redis:7.2.4-alpine restart: always volumes: - redisdata:/data diff --git a/docker-compose-local.yml b/docker-compose-local.yml index a68a045dd..d79fa54d3 100644 --- a/docker-compose-local.yml +++ b/docker-compose-local.yml @@ -10,7 +10,7 @@ volumes: services: plane-redis: - image: redis:6.2.7-alpine + image: redis:7.2.4-alpine restart: unless-stopped networks: - dev_env diff --git a/docker-compose.yml b/docker-compose.yml index 03e7424df..bf8066055 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -118,7 +118,7 @@ services: plane-redis: container_name: plane-redis - image: redis:6.2.7-alpine + image: redis:7.2.4-alpine restart: always volumes: - redisdata:/data diff --git a/packages/editor/core/src/hooks/use-editor.tsx b/packages/editor/core/src/hooks/use-editor.tsx index 647b79929..78d252c81 100644 --- a/packages/editor/core/src/hooks/use-editor.tsx +++ b/packages/editor/core/src/hooks/use-editor.tsx @@ -142,11 +142,11 @@ export const useEditor = ({ executeMenuItemCommand: (itemName: EditorMenuItemNames) => { const editorItems = getEditorMenuItems(editorRef.current, uploadFile); - const getEditorMenuItem = (itemName: EditorMenuItemNames) => editorItems.find((item) => item.name === itemName); + const getEditorMenuItem = (itemName: EditorMenuItemNames) => editorItems.find((item) => item.key === itemName); const item = getEditorMenuItem(itemName); if (item) { - if (item.name === "image") { + if (item.key === "image") { item.command(savedSelection); } else { item.command(); @@ -158,7 +158,7 @@ export const useEditor = ({ isMenuItemActive: (itemName: EditorMenuItemNames): boolean => { const editorItems = getEditorMenuItems(editorRef.current, uploadFile); - const getEditorMenuItem = (itemName: EditorMenuItemNames) => editorItems.find((item) => item.name === itemName); + const getEditorMenuItem = (itemName: EditorMenuItemNames) => editorItems.find((item) => item.key === itemName); const item = getEditorMenuItem(itemName); return item ? item.isActive() : false; }, diff --git a/packages/editor/core/src/lib/editor-commands.ts b/packages/editor/core/src/lib/editor-commands.ts index f0c6c85e0..ce2cf3ad6 100644 --- a/packages/editor/core/src/lib/editor-commands.ts +++ b/packages/editor/core/src/lib/editor-commands.ts @@ -4,6 +4,11 @@ import { findTableAncestor } from "src/lib/utils"; import { Selection } from "@tiptap/pm/state"; import { UploadImage } from "src/types/upload-image"; +export const setText = (editor: Editor, range?: Range) => { + if (range) editor.chain().focus().deleteRange(range).clearNodes().run(); + else editor.chain().focus().clearNodes().run(); +}; + export const toggleHeadingOne = (editor: Editor, range?: Range) => { if (range) editor.chain().focus().deleteRange(range).setNode("heading", { level: 1 }).run(); else editor.chain().focus().toggleHeading({ level: 1 }).run(); @@ -19,6 +24,21 @@ export const toggleHeadingThree = (editor: Editor, range?: Range) => { else editor.chain().focus().toggleHeading({ level: 3 }).run(); }; +export const toggleHeadingFour = (editor: Editor, range?: Range) => { + if (range) editor.chain().focus().deleteRange(range).setNode("heading", { level: 4 }).run(); + else editor.chain().focus().toggleHeading({ level: 4 }).run(); +}; + +export const toggleHeadingFive = (editor: Editor, range?: Range) => { + if (range) editor.chain().focus().deleteRange(range).setNode("heading", { level: 5 }).run(); + else editor.chain().focus().toggleHeading({ level: 5 }).run(); +}; + +export const toggleHeadingSix = (editor: Editor, range?: Range) => { + if (range) editor.chain().focus().deleteRange(range).setNode("heading", { level: 6 }).run(); + else editor.chain().focus().toggleHeading({ level: 6 }).run(); +}; + export const toggleBold = (editor: Editor, range?: Range) => { if (range) editor.chain().focus().deleteRange(range).toggleBold().run(); else editor.chain().focus().toggleBold().run(); diff --git a/packages/editor/core/src/styles/editor.css b/packages/editor/core/src/styles/editor.css index 5868fce91..d602a1ddf 100644 --- a/packages/editor/core/src/styles/editor.css +++ b/packages/editor/core/src/styles/editor.css @@ -330,6 +330,29 @@ ul[data-type="taskList"] ul[data-type="taskList"] { line-height: 1.3; } +.prose :where(h4):not(:where([class~="not-prose"], [class~="not-prose"] *)) { + margin-top: 1rem; + margin-bottom: 1px; + font-size: 1rem; + line-height: 1.5; +} + +.prose :where(h5):not(:where([class~="not-prose"], [class~="not-prose"] *)) { + margin-top: 1rem; + margin-bottom: 1px; + font-size: 0.9rem; + font-weight: 600; + line-height: 1.5; +} + +.prose :where(h6):not(:where([class~="not-prose"], [class~="not-prose"] *)) { + margin-top: 1rem; + margin-bottom: 1px; + font-size: 0.83rem; + font-weight: 600; + line-height: 1.5; +} + .prose :where(p):not(:where([class~="not-prose"], [class~="not-prose"] *)) { margin-top: 0.25rem; margin-bottom: 1px; diff --git a/packages/editor/core/src/ui/menus/menu-items/index.tsx b/packages/editor/core/src/ui/menus/menu-items/index.tsx index 66736e0ea..46b1ed92a 100644 --- a/packages/editor/core/src/ui/menus/menu-items/index.tsx +++ b/packages/editor/core/src/ui/menus/menu-items/index.tsx @@ -13,16 +13,24 @@ import { UnderlineIcon, StrikethroughIcon, CodeIcon, + Heading4, + Heading5, + Heading6, + CaseSensitive, } from "lucide-react"; import { Editor } from "@tiptap/react"; import { insertImageCommand, insertTableCommand, + setText, toggleBlockquote, toggleBold, toggleBulletList, toggleCodeBlock, + toggleHeadingFive, + toggleHeadingFour, toggleHeadingOne, + toggleHeadingSix, toggleHeadingThree, toggleHeadingTwo, toggleItalic, @@ -36,15 +44,26 @@ import { UploadImage } from "src/types/upload-image"; import { Selection } from "@tiptap/pm/state"; export interface EditorMenuItem { + key: string; name: string; isActive: () => boolean; command: () => void; icon: LucideIconType; } +export const TextItem = (editor: Editor) => + ({ + key: "text", + name: "Text", + isActive: () => editor.isActive("paragraph"), + command: () => setText(editor), + icon: CaseSensitive, + }) as const satisfies EditorMenuItem; + export const HeadingOneItem = (editor: Editor) => ({ - name: "H1", + key: "h1", + name: "Heading 1", isActive: () => editor.isActive("heading", { level: 1 }), command: () => toggleHeadingOne(editor), icon: Heading1, @@ -52,7 +71,8 @@ export const HeadingOneItem = (editor: Editor) => export const HeadingTwoItem = (editor: Editor) => ({ - name: "H2", + key: "h2", + name: "Heading 2", isActive: () => editor.isActive("heading", { level: 2 }), command: () => toggleHeadingTwo(editor), icon: Heading2, @@ -60,15 +80,44 @@ export const HeadingTwoItem = (editor: Editor) => export const HeadingThreeItem = (editor: Editor) => ({ - name: "H3", + key: "h3", + name: "Heading 3", isActive: () => editor.isActive("heading", { level: 3 }), command: () => toggleHeadingThree(editor), icon: Heading3, }) as const satisfies EditorMenuItem; +export const HeadingFourItem = (editor: Editor) => + ({ + key: "h4", + name: "Heading 4", + isActive: () => editor.isActive("heading", { level: 4 }), + command: () => toggleHeadingFour(editor), + icon: Heading4, + }) as const satisfies EditorMenuItem; + +export const HeadingFiveItem = (editor: Editor) => + ({ + key: "h5", + name: "Heading 5", + isActive: () => editor.isActive("heading", { level: 5 }), + command: () => toggleHeadingFive(editor), + icon: Heading5, + }) as const satisfies EditorMenuItem; + +export const HeadingSixItem = (editor: Editor) => + ({ + key: "h6", + name: "Heading 6", + isActive: () => editor.isActive("heading", { level: 6 }), + command: () => toggleHeadingSix(editor), + icon: Heading6, + }) as const satisfies EditorMenuItem; + export const BoldItem = (editor: Editor) => ({ - name: "bold", + key: "bold", + name: "Bold", isActive: () => editor?.isActive("bold"), command: () => toggleBold(editor), icon: BoldIcon, @@ -76,7 +125,8 @@ export const BoldItem = (editor: Editor) => export const ItalicItem = (editor: Editor) => ({ - name: "italic", + key: "italic", + name: "Italic", isActive: () => editor?.isActive("italic"), command: () => toggleItalic(editor), icon: ItalicIcon, @@ -84,7 +134,8 @@ export const ItalicItem = (editor: Editor) => export const UnderLineItem = (editor: Editor) => ({ - name: "underline", + key: "underline", + name: "Underline", isActive: () => editor?.isActive("underline"), command: () => toggleUnderline(editor), icon: UnderlineIcon, @@ -92,7 +143,8 @@ export const UnderLineItem = (editor: Editor) => export const StrikeThroughItem = (editor: Editor) => ({ - name: "strike", + key: "strikethrough", + name: "Strikethrough", isActive: () => editor?.isActive("strike"), command: () => toggleStrike(editor), icon: StrikethroughIcon, @@ -100,47 +152,53 @@ export const StrikeThroughItem = (editor: Editor) => export const BulletListItem = (editor: Editor) => ({ - name: "bullet-list", + key: "bulleted-list", + name: "Bulleted list", isActive: () => editor?.isActive("bulletList"), command: () => toggleBulletList(editor), icon: ListIcon, }) as const satisfies EditorMenuItem; -export const TodoListItem = (editor: Editor) => - ({ - name: "To-do List", - isActive: () => editor.isActive("taskItem"), - command: () => toggleTaskList(editor), - icon: CheckSquare, - }) as const satisfies EditorMenuItem; - -export const CodeItem = (editor: Editor) => - ({ - name: "code", - isActive: () => editor?.isActive("code") || editor?.isActive("codeBlock"), - command: () => toggleCodeBlock(editor), - icon: CodeIcon, - }) as const satisfies EditorMenuItem; - export const NumberedListItem = (editor: Editor) => ({ - name: "ordered-list", + key: "numbered-list", + name: "Numbered list", isActive: () => editor?.isActive("orderedList"), command: () => toggleOrderedList(editor), icon: ListOrderedIcon, }) as const satisfies EditorMenuItem; +export const TodoListItem = (editor: Editor) => + ({ + key: "to-do-list", + name: "To-do list", + isActive: () => editor.isActive("taskItem"), + command: () => toggleTaskList(editor), + icon: CheckSquare, + }) as const satisfies EditorMenuItem; + export const QuoteItem = (editor: Editor) => ({ - name: "quote", + key: "quote", + name: "Quote", isActive: () => editor?.isActive("blockquote"), command: () => toggleBlockquote(editor), icon: QuoteIcon, }) as const satisfies EditorMenuItem; +export const CodeItem = (editor: Editor) => + ({ + key: "code", + name: "Code", + isActive: () => editor?.isActive("code") || editor?.isActive("codeBlock"), + command: () => toggleCodeBlock(editor), + icon: CodeIcon, + }) as const satisfies EditorMenuItem; + export const TableItem = (editor: Editor) => ({ - name: "table", + key: "table", + name: "Table", isActive: () => editor?.isActive("table"), command: () => insertTableCommand(editor), icon: TableIcon, @@ -148,7 +206,8 @@ export const TableItem = (editor: Editor) => export const ImageItem = (editor: Editor, uploadFile: UploadImage) => ({ - name: "image", + key: "image", + name: "Image", isActive: () => editor?.isActive("image"), command: (savedSelection: Selection | null) => insertImageCommand(editor, uploadFile, savedSelection), icon: ImageIcon, @@ -159,9 +218,13 @@ export function getEditorMenuItems(editor: Editor | null, uploadFile: UploadImag return []; } return [ + TextItem(editor), HeadingOneItem(editor), HeadingTwoItem(editor), HeadingThreeItem(editor), + HeadingFourItem(editor), + HeadingFiveItem(editor), + HeadingSixItem(editor), BoldItem(editor), ItalicItem(editor), UnderLineItem(editor), @@ -177,7 +240,7 @@ export function getEditorMenuItems(editor: Editor | null, uploadFile: UploadImag } export type EditorMenuItemNames = ReturnType extends (infer U)[] - ? U extends { name: infer N } + ? U extends { key: infer N } ? N : never : never; diff --git a/packages/editor/document-editor/src/ui/menu/block-menu.tsx b/packages/editor/document-editor/src/ui/menu/block-menu.tsx index 6fc9a87fe..8a303809c 100644 --- a/packages/editor/document-editor/src/ui/menu/block-menu.tsx +++ b/packages/editor/document-editor/src/ui/menu/block-menu.tsx @@ -67,11 +67,13 @@ export default function BlockMenu(props: BlockMenuProps) { popup.current?.hide(); }; document.addEventListener("click", handleClickDragHandle); + document.addEventListener("contextmenu", handleClickDragHandle); document.addEventListener("keydown", handleKeyDown); document.addEventListener("scroll", handleScroll, true); // Using capture phase return () => { document.removeEventListener("click", handleClickDragHandle); + document.removeEventListener("contextmenu", handleClickDragHandle); document.removeEventListener("keydown", handleKeyDown); document.removeEventListener("scroll", handleScroll, true); }; diff --git a/packages/editor/extensions/src/extensions/drag-drop.tsx b/packages/editor/extensions/src/extensions/drag-drop.tsx index e9ef9c06e..ab2df31ad 100644 --- a/packages/editor/extensions/src/extensions/drag-drop.tsx +++ b/packages/editor/extensions/src/extensions/drag-drop.tsx @@ -225,6 +225,9 @@ function DragHandle(options: DragHandleOptions) { dragHandleElement.addEventListener("click", (e) => { handleClick(e, view); }); + dragHandleElement.addEventListener("contextmenu", (e) => { + handleClick(e, view); + }); dragHandleElement.addEventListener("drag", (e) => { hideDragHandle(); diff --git a/packages/editor/rich-text-editor/src/ui/menus/bubble-menu/index.tsx b/packages/editor/rich-text-editor/src/ui/menus/bubble-menu/index.tsx index 2dbc86cec..c11f0593d 100644 --- a/packages/editor/rich-text-editor/src/ui/menus/bubble-menu/index.tsx +++ b/packages/editor/rich-text-editor/src/ui/menus/bubble-menu/index.tsx @@ -15,6 +15,7 @@ import { } from "@plane/editor-core"; export interface BubbleMenuItem { + key: string; name: string; isActive: () => boolean; command: () => void; diff --git a/packages/editor/rich-text-editor/src/ui/menus/bubble-menu/node-selector.tsx b/packages/editor/rich-text-editor/src/ui/menus/bubble-menu/node-selector.tsx index 1bb8c38bd..5c1c8479f 100644 --- a/packages/editor/rich-text-editor/src/ui/menus/bubble-menu/node-selector.tsx +++ b/packages/editor/rich-text-editor/src/ui/menus/bubble-menu/node-selector.tsx @@ -8,9 +8,13 @@ import { QuoteItem, CodeItem, TodoListItem, + TextItem, + HeadingFourItem, + HeadingFiveItem, + HeadingSixItem, } from "@plane/editor-core"; import { Editor } from "@tiptap/react"; -import { Check, ChevronDown, TextIcon } from "lucide-react"; +import { Check, ChevronDown } from "lucide-react"; import { Dispatch, FC, SetStateAction } from "react"; import { BubbleMenuItem } from "."; @@ -23,18 +27,16 @@ interface NodeSelectorProps { export const NodeSelector: FC = ({ editor, isOpen, setIsOpen }) => { const items: BubbleMenuItem[] = [ - { - name: "Text", - icon: TextIcon, - command: () => editor.chain().focus().clearNodes().run(), - isActive: () => editor.isActive("paragraph") && !editor.isActive("bulletList") && !editor.isActive("orderedList"), - }, + TextItem(editor), HeadingOneItem(editor), HeadingTwoItem(editor), HeadingThreeItem(editor), - TodoListItem(editor), + HeadingFourItem(editor), + HeadingFiveItem(editor), + HeadingSixItem(editor), BulletListItem(editor), NumberedListItem(editor), + TodoListItem(editor), QuoteItem(editor), CodeItem(editor), ]; @@ -58,7 +60,7 @@ export const NodeSelector: FC = ({ editor, isOpen, setIsOpen {isOpen && ( -
+
{items.map((item) => ( ))}
diff --git a/packages/ui/src/button/toggle-switch.tsx b/packages/ui/src/button/toggle-switch.tsx index 2cb0b0a93..6f76f2e7d 100644 --- a/packages/ui/src/button/toggle-switch.tsx +++ b/packages/ui/src/button/toggle-switch.tsx @@ -38,8 +38,10 @@ const ToggleSwitch: React.FC = (props) => { "inline-block self-center h-4 w-4 transform rounded-full shadow ring-0 transition duration-200 ease-in-out", { "translate-x-5 bg-white": value, - "h-2 w-2 translate-x-3": value && size === "sm", - "h-3 w-3 translate-x-4": value && size === "md", + "h-2 w-2": size === "sm", + "h-3 w-3": size === "md", + "translate-x-3": value && size === "sm", + "translate-x-4": value && size === "md", "translate-x-0.5 bg-custom-background-90": !value, "cursor-not-allowed": disabled, } diff --git a/space/constants/editor.ts b/space/constants/editor.ts index bdd07f0c5..eb8b99495 100644 --- a/space/constants/editor.ts +++ b/space/constants/editor.ts @@ -4,6 +4,9 @@ import { Heading1, Heading2, Heading3, + Heading4, + Heading5, + Heading6, Image, Italic, List, @@ -29,14 +32,17 @@ export type ToolbarMenuItem = { }; export const BASIC_MARK_ITEMS: ToolbarMenuItem[] = [ - { key: "H1", name: "Heading 1", icon: Heading1, editors: ["document"] }, - { key: "H2", name: "Heading 2", icon: Heading2, editors: ["document"] }, - { key: "H3", name: "Heading 3", icon: Heading3, editors: ["document"] }, + { key: "h1", name: "Heading 1", icon: Heading1, editors: ["document"] }, + { key: "h2", name: "Heading 2", icon: Heading2, editors: ["document"] }, + { key: "h3", name: "Heading 3", icon: Heading3, editors: ["document"] }, + { key: "h4", name: "Heading 4", icon: Heading4, editors: ["document"] }, + { key: "h5", name: "Heading 5", icon: Heading5, editors: ["document"] }, + { key: "h6", name: "Heading 6", icon: Heading6, editors: ["document"] }, { key: "bold", name: "Bold", icon: Bold, shortcut: ["Cmd", "B"], editors: ["lite", "document"] }, { key: "italic", name: "Italic", icon: Italic, shortcut: ["Cmd", "I"], editors: ["lite", "document"] }, { key: "underline", name: "Underline", icon: Underline, shortcut: ["Cmd", "U"], editors: ["lite", "document"] }, { - key: "strike", + key: "strikethrough", name: "Strikethrough", icon: Strikethrough, shortcut: ["Cmd", "Shift", "S"], @@ -46,21 +52,21 @@ export const BASIC_MARK_ITEMS: ToolbarMenuItem[] = [ export const LIST_ITEMS: ToolbarMenuItem[] = [ { - key: "bullet-list", + key: "bulleted-list", name: "Bulleted list", icon: List, shortcut: ["Cmd", "Shift", "7"], editors: ["lite", "document"], }, { - key: "ordered-list", + key: "numbered-list", name: "Numbered list", icon: ListOrdered, shortcut: ["Cmd", "Shift", "8"], editors: ["lite", "document"], }, { - key: "To-do List", + key: "to-do-list", name: "To-do list", icon: ListTodo, shortcut: ["Cmd", "Shift", "9"], diff --git a/web/components/api-token/modal/form.tsx b/web/components/api-token/modal/form.tsx index 93a92c9ef..7ba0ace71 100644 --- a/web/components/api-token/modal/form.tsx +++ b/web/components/api-token/modal/form.tsx @@ -146,7 +146,7 @@ export const CreateApiTokenForm: React.FC = (props) => { onChange={onChange} hasError={Boolean(errors.description)} placeholder="Token description" - className="h-24 w-full text-sm" + className="min-h-24 w-full text-sm" /> )} /> @@ -170,8 +170,8 @@ export const CreateApiTokenForm: React.FC = (props) => { {value === "custom" ? "Custom date" : selectedOption - ? selectedOption.label - : "Set expiration date"} + ? selectedOption.label + : "Set expiration date"} } value={value} @@ -207,8 +207,8 @@ export const CreateApiTokenForm: React.FC = (props) => { ? `Expires ${renderFormattedDate(customDate)}` : null : watch("expired_at") - ? `Expires ${getExpiryDate(watch("expired_at") ?? "")}` - : null} + ? `Expires ${getExpiryDate(watch("expired_at") ?? "")}` + : null} )} diff --git a/web/components/estimates/create-update-estimate-modal.tsx b/web/components/estimates/create-update-estimate-modal.tsx index aaf0f9e47..ede508c91 100644 --- a/web/components/estimates/create-update-estimate-modal.tsx +++ b/web/components/estimates/create-update-estimate-modal.tsx @@ -256,7 +256,7 @@ export const CreateUpdateEstimateModal: React.FC = observer((props) => { value={value} placeholder="Description" onChange={onChange} - className="mt-3 h-32 resize-none text-sm" + className="mt-3 min-h-32 resize-none text-sm" hasError={Boolean(errors?.description)} /> )} diff --git a/web/components/headers/global-issues.tsx b/web/components/headers/global-issues.tsx index 63259d5f6..b936c0793 100644 --- a/web/components/headers/global-issues.tsx +++ b/web/components/headers/global-issues.tsx @@ -1,35 +1,23 @@ import { useCallback, useState } from "react"; -import { observer } from "mobx-react"; -import Link from "next/link"; +import { observer } from "mobx-react-lite"; import { useRouter } from "next/router"; -// hooks -import { List, PlusIcon, Sheet } from "lucide-react"; +// icons +import { PlusIcon } from "lucide-react"; +// types import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types"; -import { Breadcrumbs, Button, LayersIcon, PhotoFilterIcon, Tooltip } from "@plane/ui"; +// ui +import { Breadcrumbs, Button, LayersIcon } from "@plane/ui"; +// components import { BreadcrumbLink } from "@/components/common"; import { DisplayFiltersSelection, FiltersDropdown, FilterSelection } from "@/components/issues"; -// components import { CreateUpdateWorkspaceViewModal } from "@/components/workspace"; -// ui -// icons -// types // constants import { EIssueFilterType, EIssuesStoreType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue"; import { EUserWorkspaceRoles } from "@/constants/workspace"; +// hooks import { useLabel, useMember, useUser, useIssues } from "@/hooks/store"; -import { usePlatformOS } from "@/hooks/use-platform-os"; -const GLOBAL_VIEW_LAYOUTS = [ - { key: "list", title: "List", link: "workspace-views", icon: List }, - { key: "spreadsheet", title: "Spreadsheet", link: "workspace-views/all-issues", icon: Sheet }, -]; - -type Props = { - activeLayout: "list" | "spreadsheet"; -}; - -export const GlobalIssuesHeader: React.FC = observer((props) => { - const { activeLayout } = props; +export const GlobalIssuesHeader: React.FC = observer(() => { // states const [createViewModal, setCreateViewModal] = useState(false); // router @@ -46,7 +34,6 @@ export const GlobalIssuesHeader: React.FC = observer((props) => { const { workspace: { workspaceMemberIds }, } = useMember(); - const { isMobile } = usePlatformOS(); const issueFilters = globalViewId ? filters[globalViewId.toString()] : undefined; @@ -116,65 +103,32 @@ export const GlobalIssuesHeader: React.FC = observer((props) => { - ) : ( - - ) - } - /> + } /> } />
-
- {GLOBAL_VIEW_LAYOUTS.map((layout) => ( - - - -
- -
-
-
- - ))} -
- - {activeLayout === "spreadsheet" && ( - <> - - - - - - - - )} + <> + + + + + + + {isAuthorizedUser && (
diff --git a/web/components/inbox/content/inbox-issue-mobile-header.tsx b/web/components/inbox/content/inbox-issue-mobile-header.tsx index f817657f3..cd26bfccc 100644 --- a/web/components/inbox/content/inbox-issue-mobile-header.tsx +++ b/web/components/inbox/content/inbox-issue-mobile-header.tsx @@ -38,8 +38,8 @@ type Props = { setIsSnoozeDateModalOpen: (value: boolean) => void; setSelectDuplicateIssue: (value: boolean) => void; handleCopyIssueLink: () => void; - toggleMobileSidebar: boolean; - setToggleMobileSidebar: (value: boolean) => void; + isMobileSidebar: boolean; + setIsMobileSidebar: (value: boolean) => void; }; export const InboxIssueActionsMobileHeader: React.FC = observer((props) => { @@ -59,8 +59,8 @@ export const InboxIssueActionsMobileHeader: React.FC = observer((props) = setIsSnoozeDateModalOpen, setSelectDuplicateIssue, handleCopyIssueLink, - toggleMobileSidebar, - setToggleMobileSidebar, + isMobileSidebar, + setIsMobileSidebar, } = props; const router = useRouter(); const issue = inboxIssue?.issue; @@ -71,10 +71,10 @@ export const InboxIssueActionsMobileHeader: React.FC = observer((props) = return (
setToggleMobileSidebar(!toggleMobileSidebar)} + onClick={() => setIsMobileSidebar(!isMobileSidebar)} className={cn( "w-4 h-4 flex-shrink-0 mr-2", - toggleMobileSidebar ? "text-custom-primary-100" : "text-custom-text-200" + isMobileSidebar ? "text-custom-primary-100" : "text-custom-text-200" )} />
diff --git a/web/components/inbox/content/root.tsx b/web/components/inbox/content/root.tsx index b3c97c11a..1ffe35a46 100644 --- a/web/components/inbox/content/root.tsx +++ b/web/components/inbox/content/root.tsx @@ -9,12 +9,12 @@ type TInboxContentRoot = { workspaceSlug: string; projectId: string; inboxIssueId: string; - toggleMobileSidebar: boolean; - setToggleMobileSidebar: (value: boolean) => void; + isMobileSidebar: boolean; + setIsMobileSidebar: (value: boolean) => void; }; export const InboxContentRoot: FC = observer((props) => { - const { workspaceSlug, projectId, inboxIssueId, toggleMobileSidebar, setToggleMobileSidebar } = props; + const { workspaceSlug, projectId, inboxIssueId, isMobileSidebar, setIsMobileSidebar } = props; // states const [isSubmitting, setIsSubmitting] = useState<"submitting" | "submitted" | "saved">("saved"); // hooks @@ -45,8 +45,8 @@ export const InboxContentRoot: FC = observer((props) => {
= observer((props) => { const { workspaceSlug, projectId, inboxIssueId, inboxAccessible } = props; // states - const [toggleMobileSidebar, setToggleMobileSidebar] = useState(false); + const [isMobileSidebar, setIsMobileSidebar] = useState(true); // hooks const { loader, error, fetchInboxIssues } = useProjectInbox(); @@ -60,8 +60,8 @@ export const InboxIssueRoot: FC = observer((props) => { {!inboxIssueId && (
setToggleMobileSidebar(!toggleMobileSidebar)} - className={cn("w-4 h-4 ", toggleMobileSidebar ? "text-custom-primary-100" : " text-custom-text-200")} + onClick={() => setIsMobileSidebar(!isMobileSidebar)} + className={cn("w-4 h-4 ", isMobileSidebar ? "text-custom-primary-100" : " text-custom-text-200")} />
)} @@ -69,11 +69,11 @@ export const InboxIssueRoot: FC = observer((props) => {
@@ -81,8 +81,8 @@ export const InboxIssueRoot: FC = observer((props) => { {inboxIssueId ? ( void; + setIsMobileSidebar: (value: boolean) => void; }; export const InboxIssueListItem: FC = observer((props) => { - const { workspaceSlug, projectId, inboxIssue, projectIdentifier,setToggleMobileSidebar } = props; + const { workspaceSlug, projectId, inboxIssue, projectIdentifier, setIsMobileSidebar } = props; // router const router = useRouter(); const { inboxIssueId } = router.query; @@ -35,7 +35,7 @@ export const InboxIssueListItem: FC = observer((props) const handleIssueRedirection = (event: MouseEvent, currentIssueId: string | undefined) => { if (inboxIssueId === currentIssueId) event.preventDefault(); - setToggleMobileSidebar(false); + setIsMobileSidebar(false); }; if (!issue) return <>; diff --git a/web/components/inbox/sidebar/inbox-list.tsx b/web/components/inbox/sidebar/inbox-list.tsx index a7a19eff1..be435cd77 100644 --- a/web/components/inbox/sidebar/inbox-list.tsx +++ b/web/components/inbox/sidebar/inbox-list.tsx @@ -10,18 +10,18 @@ export type InboxIssueListProps = { projectId: string; projectIdentifier?: string; inboxIssues: IInboxIssueStore[]; - setToggleMobileSidebar: (value: boolean) => void; + setIsMobileSidebar: (value: boolean) => void; }; export const InboxIssueList: FC = observer((props) => { - const { workspaceSlug, projectId, projectIdentifier, inboxIssues, setToggleMobileSidebar } = props; + const { workspaceSlug, projectId, projectIdentifier, inboxIssues, setIsMobileSidebar } = props; return ( <> {inboxIssues.map((inboxIssue) => ( void; + setIsMobileSidebar: (value: boolean) => void; }; const tabNavigationOptions: { key: TInboxIssueCurrentTab; label: string }[] = [ @@ -34,7 +34,7 @@ const tabNavigationOptions: { key: TInboxIssueCurrentTab; label: string }[] = [ ]; export const InboxSidebar: FC = observer((props) => { - const { workspaceSlug, projectId, setToggleMobileSidebar } = props; + const { workspaceSlug, projectId, setIsMobileSidebar } = props; // ref const containerRef = useRef(null); const elementRef = useRef(null); @@ -110,7 +110,7 @@ export const InboxSidebar: FC = observer((props) => { > {inboxIssuesArray.length > 0 ? ( { }); }; - const areFiltersEqual = isEqual(appliedFilters, viewDetails?.filters); + const areFiltersEqual = isEqual(appliedFilters ?? {}, viewDetails?.filters ?? {}); const isAuthorizedUser = !!currentWorkspaceRole && currentWorkspaceRole >= EUserWorkspaceRoles.MEMBER; const isDefaultView = DEFAULT_GLOBAL_VIEWS_LIST.map((view) => view.key).includes(globalViewId as TStaticViewTypes); // return if no filters are applied - if (!appliedFilters && areFiltersEqual) return null; + + if (isEmpty(appliedFilters) && areFiltersEqual) return null; return (
diff --git a/web/components/issues/issue-layouts/filters/applied-filters/roots/project-view-root.tsx b/web/components/issues/issue-layouts/filters/applied-filters/roots/project-view-root.tsx index 9d647873a..214f93386 100644 --- a/web/components/issues/issue-layouts/filters/applied-filters/roots/project-view-root.tsx +++ b/web/components/issues/issue-layouts/filters/applied-filters/roots/project-view-root.tsx @@ -1,3 +1,4 @@ +import isEmpty from "lodash/isEmpty"; import isEqual from "lodash/isEqual"; import { observer } from "mobx-react"; import { useRouter } from "next/router"; @@ -78,9 +79,10 @@ export const ProjectViewAppliedFiltersRoot: React.FC = observer(() => { ); }; - const areFiltersEqual = isEqual(appliedFilters, viewDetails?.filters); + const areFiltersEqual = isEqual(appliedFilters ?? {}, viewDetails?.filters ?? {}); + // return if no filters are applied - if (!appliedFilters && areFiltersEqual) return null; + if (isEmpty(appliedFilters) && areFiltersEqual) return null; const handleUpdateView = () => { if (!workspaceSlug || !projectId || !viewId || !viewDetails) return; diff --git a/web/components/pages/editor/header/toolbar.tsx b/web/components/pages/editor/header/toolbar.tsx index a23d57e77..b92e1eac9 100644 --- a/web/components/pages/editor/header/toolbar.tsx +++ b/web/components/pages/editor/header/toolbar.tsx @@ -1,10 +1,11 @@ import React, { useEffect, useState, useCallback } from "react"; +import { Check, ChevronDown } from "lucide-react"; // editor import { EditorMenuItemNames, EditorRefApi } from "@plane/document-editor"; // ui -import { Tooltip } from "@plane/ui"; +import { CustomMenu, Tooltip } from "@plane/ui"; // constants -import { TOOLBAR_ITEMS, ToolbarMenuItem } from "@/constants/editor"; +import { TOOLBAR_ITEMS, TYPOGRAPHY_ITEMS, ToolbarMenuItem } from "@/constants/editor"; // helpers import { cn } from "@/helpers/common.helper"; @@ -34,12 +35,12 @@ const ToolbarButton: React.FC = React.memo((props) => { key={item.key} type="button" onClick={() => executeCommand(item.key)} - className={cn("grid h-7 w-7 place-items-center rounded text-custom-text-300 hover:bg-custom-background-80", { + className={cn("grid size-7 place-items-center rounded text-custom-text-300 hover:bg-custom-background-80", { "bg-custom-background-80 text-custom-text-100": isActive, })} > @@ -71,8 +72,36 @@ export const PageToolbar: React.FC = ({ editorRef }) => { return () => unsubscribe(); }, [editorRef, updateActiveStates]); + const activeTypography = TYPOGRAPHY_ITEMS.find((item) => editorRef.isMenuItemActive(item.key)); + return (
+ + {activeTypography?.name || "Text"} + + + } + className="pr-2" + placement="bottom-start" + closeOnSelect + maxHeight="lg" + > + {TYPOGRAPHY_ITEMS.map((item) => ( + editorRef.executeMenuItemCommand(item.key)} + > + + + {item.name} + + {activeTypography?.key === item.key && } + + ))} + {Object.keys(toolbarItems).map((key) => (
{toolbarItems[key].map((item) => ( diff --git a/web/components/views/form.tsx b/web/components/views/form.tsx index 5bbdb5d49..0c6e01425 100644 --- a/web/components/views/form.tsx +++ b/web/components/views/form.tsx @@ -148,7 +148,7 @@ export const ProjectViewForm: React.FC = observer((props) => { id="description" name="description" placeholder="Description" - className="h-24 w-full resize-none text-sm" + className="min-h-24 w-full resize-none text-sm" hasError={Boolean(errors?.description)} value={value} onChange={onChange} diff --git a/web/components/workspace/views/form.tsx b/web/components/workspace/views/form.tsx index 00dcc4e75..27a22381c 100644 --- a/web/components/workspace/views/form.tsx +++ b/web/components/workspace/views/form.tsx @@ -137,7 +137,7 @@ export const WorkspaceViewForm: React.FC = observer((props) => { value={value} placeholder="Description" onChange={onChange} - className="h-24 w-full resize-none text-sm" + className="min-h-24 w-full resize-none text-sm" hasError={Boolean(errors?.description)} /> )} diff --git a/web/constants/editor.ts b/web/constants/editor.ts index d80da201c..2247d9f5c 100644 --- a/web/constants/editor.ts +++ b/web/constants/editor.ts @@ -1,9 +1,13 @@ import { Bold, + CaseSensitive, Code2, Heading1, Heading2, Heading3, + Heading4, + Heading5, + Heading6, Image, Italic, List, @@ -28,15 +32,22 @@ export type ToolbarMenuItem = { editors: TEditorTypes[]; }; -export const BASIC_MARK_ITEMS: ToolbarMenuItem[] = [ - { key: "H1", name: "Heading 1", icon: Heading1, editors: ["document"] }, - { key: "H2", name: "Heading 2", icon: Heading2, editors: ["document"] }, - { key: "H3", name: "Heading 3", icon: Heading3, editors: ["document"] }, +export const TYPOGRAPHY_ITEMS: ToolbarMenuItem[] = [ + { key: "text", name: "Text", icon: CaseSensitive, editors: ["document"] }, + { key: "h1", name: "Heading 1", icon: Heading1, editors: ["document"] }, + { key: "h2", name: "Heading 2", icon: Heading2, editors: ["document"] }, + { key: "h3", name: "Heading 3", icon: Heading3, editors: ["document"] }, + { key: "h4", name: "Heading 4", icon: Heading4, editors: ["document"] }, + { key: "h5", name: "Heading 5", icon: Heading5, editors: ["document"] }, + { key: "h6", name: "Heading 6", icon: Heading6, editors: ["document"] }, +]; + +const BASIC_MARK_ITEMS: ToolbarMenuItem[] = [ { key: "bold", name: "Bold", icon: Bold, shortcut: ["Cmd", "B"], editors: ["lite", "document"] }, { key: "italic", name: "Italic", icon: Italic, shortcut: ["Cmd", "I"], editors: ["lite", "document"] }, { key: "underline", name: "Underline", icon: Underline, shortcut: ["Cmd", "U"], editors: ["lite", "document"] }, { - key: "strike", + key: "strikethrough", name: "Strikethrough", icon: Strikethrough, shortcut: ["Cmd", "Shift", "S"], @@ -44,23 +55,23 @@ export const BASIC_MARK_ITEMS: ToolbarMenuItem[] = [ }, ]; -export const LIST_ITEMS: ToolbarMenuItem[] = [ +const LIST_ITEMS: ToolbarMenuItem[] = [ { - key: "bullet-list", + key: "bulleted-list", name: "Bulleted list", icon: List, shortcut: ["Cmd", "Shift", "7"], editors: ["lite", "document"], }, { - key: "ordered-list", + key: "numbered-list", name: "Numbered list", icon: ListOrdered, shortcut: ["Cmd", "Shift", "8"], editors: ["lite", "document"], }, { - key: "To-do List", + key: "to-do-list", name: "To-do list", icon: ListTodo, shortcut: ["Cmd", "Shift", "9"], @@ -68,12 +79,12 @@ export const LIST_ITEMS: ToolbarMenuItem[] = [ }, ]; -export const USER_ACTION_ITEMS: ToolbarMenuItem[] = [ +const USER_ACTION_ITEMS: ToolbarMenuItem[] = [ { key: "quote", name: "Quote", icon: Quote, editors: ["lite", "document"] }, { key: "code", name: "Code", icon: Code2, editors: ["lite", "document"] }, ]; -export const COMPLEX_ITEMS: ToolbarMenuItem[] = [ +const COMPLEX_ITEMS: ToolbarMenuItem[] = [ { key: "table", name: "Table", icon: Table, editors: ["document"] }, { key: "image", name: "Image", icon: Image, editors: ["lite", "document"] }, ]; diff --git a/web/constants/page.ts b/web/constants/page.ts index 04b95c163..f196b6152 100644 --- a/web/constants/page.ts +++ b/web/constants/page.ts @@ -23,7 +23,6 @@ export const PAGE_SORTING_KEY_OPTIONS: { { key: "name", label: "Name" }, { key: "created_at", label: "Date created" }, { key: "updated_at", label: "Date modified" }, - { key: "opened_at", label: "Last opened" }, ]; export const PAGE_SORT_BY_OPTIONS: { diff --git a/web/pages/[workspaceSlug]/workspace-views/[globalViewId].tsx b/web/pages/[workspaceSlug]/workspace-views/[globalViewId].tsx index 5979755a9..4e04fe7fa 100644 --- a/web/pages/[workspaceSlug]/workspace-views/[globalViewId].tsx +++ b/web/pages/[workspaceSlug]/workspace-views/[globalViewId].tsx @@ -29,8 +29,8 @@ const GlobalViewIssuesPage: NextPageWithLayout = observer(() => { currentWorkspace?.name && defaultView?.label ? `${currentWorkspace?.name} - ${defaultView?.label}` : currentWorkspace?.name && globalViewDetails?.name - ? `${currentWorkspace?.name} - ${globalViewDetails?.name}` - : undefined; + ? `${currentWorkspace?.name} - ${globalViewDetails?.name}` + : undefined; return ( <> @@ -46,7 +46,7 @@ const GlobalViewIssuesPage: NextPageWithLayout = observer(() => { }); GlobalViewIssuesPage.getLayout = function getLayout(page: ReactElement) { - return }>{page}; + return }>{page}; }; export default GlobalViewIssuesPage; diff --git a/web/pages/[workspaceSlug]/workspace-views/index.tsx b/web/pages/[workspaceSlug]/workspace-views/index.tsx index c7f57e912..4a1ceddd7 100644 --- a/web/pages/[workspaceSlug]/workspace-views/index.tsx +++ b/web/pages/[workspaceSlug]/workspace-views/index.tsx @@ -52,7 +52,7 @@ const WorkspaceViewsPage: NextPageWithLayout = observer(() => { }); WorkspaceViewsPage.getLayout = function getLayout(page: ReactElement) { - return }>{page}; + return }>{page}; }; export default WorkspaceViewsPage;