Compare commits

..

1 Commits

Author SHA1 Message Date
sriram veeraghanta 5f5ab2b75d fix: moving nginx to caddy 2025-03-09 16:19:09 +05:30
121 changed files with 547 additions and 959 deletions
+1 -3
View File
@@ -1,8 +1,6 @@
{
"name": "admin",
"description": "Admin UI for Plane",
"version": "0.25.1",
"license": "AGPL-3.0",
"version": "0.25.0",
"private": true,
"scripts": {
"dev": "turbo run develop",
+1 -4
View File
@@ -1,7 +1,4 @@
{
"name": "plane-api",
"version": "0.25.1",
"license": "AGPL-3.0",
"private": true,
"description": "API server powering Plane's backend"
"version": "0.25.0"
}
+2 -8
View File
@@ -80,7 +80,6 @@ class IssueSerializer(BaseSerializer):
data["assignees"] = ProjectMember.objects.filter(
project_id=self.context.get("project_id"),
is_active=True,
role__gte=15,
member_id__in=data["assignees"],
).values_list("member_id", flat=True)
@@ -159,13 +158,8 @@ class IssueSerializer(BaseSerializer):
pass
else:
try:
# Then assign it to default assignee, if it is a valid assignee
if default_assignee_id is not None and ProjectMember.objects.filter(
member_id=default_assignee_id,
project_id=project_id,
role__gte=15,
is_active=True
).exists():
# Then assign it to default assignee
if default_assignee_id is not None:
IssueAssignee.objects.create(
assignee_id=default_assignee_id,
issue=issue,
+8 -27
View File
@@ -36,7 +36,6 @@ from plane.db.models import (
State,
IssueVersion,
IssueDescriptionVersion,
ProjectMember,
)
@@ -120,17 +119,6 @@ class IssueCreateSerializer(BaseSerializer):
raise serializers.ValidationError("Start date cannot exceed target date")
return data
def get_valid_assignees(self, assignees, project_id):
if not assignees:
return []
return ProjectMember.objects.filter(
project_id=project_id,
role__gte=15,
is_active=True,
member_id__in=assignees
).values_list('member_id', flat=True)
def create(self, validated_data):
assignees = validated_data.pop("assignee_ids", None)
labels = validated_data.pop("label_ids", None)
@@ -146,33 +134,27 @@ class IssueCreateSerializer(BaseSerializer):
created_by_id = issue.created_by_id
updated_by_id = issue.updated_by_id
valid_assignee_ids = self.get_valid_assignees(assignees, project_id)
if valid_assignee_ids is not None and len(valid_assignee_ids):
if assignees is not None and len(assignees):
try:
IssueAssignee.objects.bulk_create(
[
IssueAssignee(
assignee_id=user_id,
assignee=user,
issue=issue,
project_id=project_id,
workspace_id=workspace_id,
created_by_id=created_by_id,
updated_by_id=updated_by_id,
)
for user_id in valid_assignee_ids
for user in assignees
],
batch_size=10,
)
except IntegrityError:
pass
else:
# Then assign it to default assignee, if it is a valid assignee
if default_assignee_id is not None and ProjectMember.objects.filter(
member_id=default_assignee_id,
project_id=project_id,
role__gte=15,
is_active=True
).exists():
# Then assign it to default assignee
if default_assignee_id is not None:
try:
IssueAssignee.objects.create(
assignee_id=default_assignee_id,
@@ -216,21 +198,20 @@ class IssueCreateSerializer(BaseSerializer):
created_by_id = instance.created_by_id
updated_by_id = instance.updated_by_id
valid_assignee_ids = self.get_valid_assignees(assignees, project_id)
if valid_assignee_ids is not None:
if assignees is not None:
IssueAssignee.objects.filter(issue=instance).delete()
try:
IssueAssignee.objects.bulk_create(
[
IssueAssignee(
assignee_id=user_id,
assignee=user,
issue=instance,
project_id=project_id,
workspace_id=workspace_id,
created_by_id=created_by_id,
updated_by_id=updated_by_id,
)
for user_id in valid_assignee_ids
for user in assignees
],
batch_size=10,
ignore_conflicts=True,
+3 -3
View File
@@ -1,8 +1,7 @@
{
"name": "live",
"version": "0.25.1",
"license": "AGPL-3.0",
"description": "A realtime collaborative server powers Plane's rich text editor",
"version": "0.25.0",
"description": "",
"main": "./src/server.ts",
"private": true,
"type": "module",
@@ -15,6 +14,7 @@
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@hocuspocus/extension-database": "^2.15.0",
"@hocuspocus/extension-logger": "^2.15.0",
+3 -4
View File
@@ -1,8 +1,6 @@
{
"name": "plane",
"description": "Open-source project management that unlocks customer value",
"repository": "https://github.com/makeplane/plane.git",
"version": "0.25.1",
"version": "0.25.0",
"license": "AGPL-3.0",
"private": true,
"workspaces": [
@@ -30,5 +28,6 @@
"nanoid": "3.3.8",
"esbuild": "0.25.0"
},
"packageManager": "yarn@1.22.22"
"packageManager": "yarn@1.22.22",
"name": "plane"
}
+2 -3
View File
@@ -1,7 +1,6 @@
{
"name": "@plane/constants",
"version": "0.25.1",
"version": "0.25.0",
"private": true,
"main": "./src/index.ts",
"license": "AGPL-3.0"
"main": "./src/index.ts"
}
+24 -160
View File
@@ -1,4 +1,8 @@
import { TIssueGroupByOptions, TIssueOrderByOptions, IIssueDisplayProperties } from "@plane/types";
import {
TIssueGroupByOptions,
TIssueOrderByOptions,
IIssueDisplayProperties,
} from "@plane/types";
export const ALL_ISSUES = "All Issues";
@@ -145,24 +149,25 @@ export const ISSUE_ORDER_BY_OPTIONS: {
{ key: "-priority", titleTranslationKey: "common.priority" },
];
export const ISSUE_DISPLAY_PROPERTIES_KEYS: (keyof IIssueDisplayProperties)[] = [
"assignee",
"start_date",
"due_date",
"labels",
"key",
"priority",
"state",
"sub_issue_count",
"link",
"attachment_count",
"estimate",
"created_on",
"updated_on",
"modules",
"cycle",
"issue_type",
];
export const ISSUE_DISPLAY_PROPERTIES_KEYS: (keyof IIssueDisplayProperties)[] =
[
"assignee",
"start_date",
"due_date",
"labels",
"key",
"priority",
"state",
"sub_issue_count",
"link",
"attachment_count",
"estimate",
"created_on",
"updated_on",
"modules",
"cycle",
"issue_type",
];
export const ISSUE_DISPLAY_PROPERTIES: {
key: keyof IIssueDisplayProperties;
@@ -210,144 +215,3 @@ export const ISSUE_DISPLAY_PROPERTIES: {
{ key: "modules", titleTranslationKey: "common.module" },
{ key: "cycle", titleTranslationKey: "common.cycle" },
];
export const SPREADSHEET_PROPERTY_LIST: (keyof IIssueDisplayProperties)[] = [
"state",
"priority",
"assignee",
"labels",
"modules",
"cycle",
"start_date",
"due_date",
"estimate",
"created_on",
"updated_on",
"link",
"attachment_count",
"sub_issue_count",
];
export const SPREADSHEET_PROPERTY_DETAILS: {
[key in keyof IIssueDisplayProperties]: {
i18n_title: string;
ascendingOrderKey: TIssueOrderByOptions;
ascendingOrderTitle: string;
descendingOrderKey: TIssueOrderByOptions;
descendingOrderTitle: string;
icon: string;
};
} = {
assignee: {
i18n_title: "common.assignees",
ascendingOrderKey: "assignees__first_name",
ascendingOrderTitle: "A",
descendingOrderKey: "-assignees__first_name",
descendingOrderTitle: "Z",
icon: "Users",
},
created_on: {
i18n_title: "common.sort.created_on",
ascendingOrderKey: "-created_at",
ascendingOrderTitle: "New",
descendingOrderKey: "created_at",
descendingOrderTitle: "Old",
icon: "CalendarDays",
},
due_date: {
i18n_title: "common.order_by.due_date",
ascendingOrderKey: "-target_date",
ascendingOrderTitle: "New",
descendingOrderKey: "target_date",
descendingOrderTitle: "Old",
icon: "CalendarCheck2",
},
estimate: {
i18n_title: "common.estimate",
ascendingOrderKey: "estimate_point__key",
ascendingOrderTitle: "Low",
descendingOrderKey: "-estimate_point__key",
descendingOrderTitle: "High",
icon: "Triangle",
},
labels: {
i18n_title: "common.labels",
ascendingOrderKey: "labels__name",
ascendingOrderTitle: "A",
descendingOrderKey: "-labels__name",
descendingOrderTitle: "Z",
icon: "Tag",
},
modules: {
i18n_title: "common.modules",
ascendingOrderKey: "issue_module__module__name",
ascendingOrderTitle: "A",
descendingOrderKey: "-issue_module__module__name",
descendingOrderTitle: "Z",
icon: "DiceIcon",
},
cycle: {
i18n_title: "common.cycle",
ascendingOrderKey: "issue_cycle__cycle__name",
ascendingOrderTitle: "A",
descendingOrderKey: "-issue_cycle__cycle__name",
descendingOrderTitle: "Z",
icon: "ContrastIcon",
},
priority: {
i18n_title: "common.priority",
ascendingOrderKey: "priority",
ascendingOrderTitle: "None",
descendingOrderKey: "-priority",
descendingOrderTitle: "Urgent",
icon: "Signal",
},
start_date: {
i18n_title: "common.order_by.start_date",
ascendingOrderKey: "-start_date",
ascendingOrderTitle: "New",
descendingOrderKey: "start_date",
descendingOrderTitle: "Old",
icon: "CalendarClock",
},
state: {
i18n_title: "common.state",
ascendingOrderKey: "state__name",
ascendingOrderTitle: "A",
descendingOrderKey: "-state__name",
descendingOrderTitle: "Z",
icon: "DoubleCircleIcon",
},
updated_on: {
i18n_title: "common.sort.updated_on",
ascendingOrderKey: "-updated_at",
ascendingOrderTitle: "New",
descendingOrderKey: "updated_at",
descendingOrderTitle: "Old",
icon: "CalendarDays",
},
link: {
i18n_title: "common.link",
ascendingOrderKey: "-link_count",
ascendingOrderTitle: "Most",
descendingOrderKey: "link_count",
descendingOrderTitle: "Least",
icon: "Link2",
},
attachment_count: {
i18n_title: "common.attachment",
ascendingOrderKey: "-attachment_count",
ascendingOrderTitle: "Most",
descendingOrderKey: "attachment_count",
descendingOrderTitle: "Least",
icon: "Paperclip",
},
sub_issue_count: {
i18n_title: "issue.display.properties.sub_issue",
ascendingOrderKey: "-sub_issues_count",
ascendingOrderTitle: "Most",
descendingOrderKey: "sub_issues_count",
descendingOrderTitle: "Least",
icon: "LayersIcon",
},
};
-1
View File
@@ -1,4 +1,3 @@
export * from "./common";
export * from "./filter";
export * from "./layout";
export * from "./modal";
-19
View File
@@ -1,19 +0,0 @@
// plane imports
import { TIssue } from "@plane/types";
export const DEFAULT_WORK_ITEM_FORM_VALUES: Partial<TIssue> = {
project_id: "",
type_id: null,
name: "",
description_html: "",
estimate_point: null,
state_id: "",
parent_id: null,
priority: "none",
assignee_ids: [],
label_ids: [],
cycle_id: null,
module_ids: null,
start_date: null,
target_date: null,
};
+1 -2
View File
@@ -1,8 +1,7 @@
{
"name": "@plane/editor",
"version": "0.25.1",
"version": "0.25.0",
"description": "Core Editor that powers Plane",
"license": "AGPL-3.0",
"private": true,
"main": "./dist/index.mjs",
"module": "./dist/index.mjs",
@@ -76,7 +76,7 @@ export const CustomImageNode = (props: CustomImageNodeProps) => {
failedToLoadImage={failedToLoadImage}
getPos={getPos}
loadImageFromFileSystem={setImageFromFileSystem}
maxFileSize={editor.storage.imageComponent?.maxFileSize}
maxFileSize={editor.storage.imageComponent.maxFileSize}
node={node}
setIsUploaded={setIsUploaded}
selected={selected}
@@ -16,7 +16,7 @@ export const ImageUploadStatus: React.FC<Props> = (props) => {
// subscribe to image upload status
const uploadStatus: number | undefined = useEditorState({
editor,
selector: ({ editor }) => editor.storage.imageComponent?.assetsUploadStatus[nodeId],
selector: ({ editor }) => editor.storage.imageComponent.assetsUploadStatus[nodeId],
});
useEffect(() => {
@@ -22,7 +22,7 @@ declare module "@tiptap/core" {
imageComponent: {
insertImageComponent: ({ file, pos, event }: InsertImageComponentProps) => ReturnType;
uploadImage: (blockId: string, file: File) => () => Promise<string> | undefined;
updateAssetsUploadStatus?: (updatedStatus: TFileHandler["assetsUploadStatus"]) => () => void;
updateAssetsUploadStatus: (updatedStatus: TFileHandler["assetsUploadStatus"]) => () => void;
getImageSource?: (path: string) => () => Promise<string>;
restoreImage: (src: string) => () => Promise<void>;
};
@@ -50,7 +50,8 @@ type TArguments = {
export const CoreEditorExtensions = (args: TArguments): Extensions => {
const { disabledExtensions, enableHistory, fileHandler, mentionHandler, placeholder, tabIndex } = args;
const extensions = [
return [
// @ts-expect-error tiptap types are incorrect
StarterKit.configure({
bulletList: {
HTMLAttributes: {
@@ -108,6 +109,12 @@ export const CoreEditorExtensions = (args: TArguments): Extensions => {
},
}),
CustomTypographyExtension,
ImageExtension(fileHandler).configure({
HTMLAttributes: {
class: "rounded-md",
},
}),
CustomImageExtension(fileHandler),
TiptapUnderline,
TextStyle,
TaskList.configure({
@@ -145,7 +152,7 @@ export const CoreEditorExtensions = (args: TArguments): Extensions => {
if (node.type.name === "heading") return `Heading ${node.attrs.level}`;
if (editor.storage.imageComponent?.uploadInProgress) return "";
if (editor.storage.imageComponent.uploadInProgress) return "";
const shouldHidePlaceholder =
editor.isActive("table") ||
@@ -172,18 +179,4 @@ export const CoreEditorExtensions = (args: TArguments): Extensions => {
disabledExtensions,
}),
];
if (!disabledExtensions.includes("image")) {
extensions.push(
ImageExtension(fileHandler).configure({
HTMLAttributes: {
class: "rounded-md",
},
}),
CustomImageExtension(fileHandler)
);
}
// @ts-expect-error tiptap types are incorrect
return extensions;
};
@@ -48,7 +48,6 @@ export const CustomImageComponentWithoutProps = () =>
return {
fileMap: new Map(),
deletedImageSet: new Map<string, boolean>(),
assetsUploadStatus: {},
};
},
});
@@ -41,7 +41,8 @@ type Props = {
export const CoreReadOnlyEditorExtensions = (props: Props): Extensions => {
const { disabledExtensions, fileHandler, mentionHandler } = props;
const extensions = [
return [
// @ts-expect-error tiptap types are incorrect
StarterKit.configure({
bulletList: {
HTMLAttributes: {
@@ -93,6 +94,12 @@ export const CoreReadOnlyEditorExtensions = (props: Props): Extensions => {
},
}),
CustomTypographyExtension,
ReadOnlyImageExtension(fileHandler).configure({
HTMLAttributes: {
class: "rounded-md",
},
}),
CustomReadOnlyImageExtension(fileHandler),
TiptapUnderline,
TextStyle,
TaskList.configure({
@@ -129,18 +136,4 @@ export const CoreReadOnlyEditorExtensions = (props: Props): Extensions => {
disabledExtensions,
}),
];
if (!disabledExtensions.includes("image")) {
extensions.push(
ReadOnlyImageExtension(fileHandler).configure({
HTMLAttributes: {
class: "rounded-md",
},
}),
CustomReadOnlyImageExtension(fileHandler)
);
}
// @ts-expect-error tiptap types are incorrect
return extensions;
};
@@ -43,7 +43,7 @@ import { CommandProps, ISlashCommandItem, TSlashCommandSectionKeys } from "@/typ
// plane editor extensions
import { coreEditorAdditionalSlashCommandOptions } from "@/plane-editor/extensions";
// local types
import { TExtensionProps, TSlashCommandAdditionalOption } from "./root";
import { TExtensionProps } from "./root";
export type TSlashCommandSection = {
key: TSlashCommandSectionKeys;
@@ -54,7 +54,7 @@ export type TSlashCommandSection = {
export const getSlashCommandFilteredSections =
(args: TExtensionProps) =>
({ query }: { query: string }): TSlashCommandSection[] => {
const { additionalOptions: externalAdditionalOptions, disabledExtensions } = args;
const { additionalOptions, disabledExtensions } = args;
const SLASH_COMMAND_SECTIONS: TSlashCommandSection[] = [
{
key: "general",
@@ -176,6 +176,15 @@ export const getSlashCommandFilteredSections =
icon: <Code2 className="size-3.5" />,
command: ({ editor, range }) => editor.chain().focus().deleteRange(range).toggleCodeBlock().run(),
},
{
commandKey: "image",
key: "image",
title: "Image",
icon: <ImageIcon className="size-3.5" />,
description: "Insert an image",
searchTerms: ["img", "photo", "picture", "media", "upload"],
command: ({ editor, range }: CommandProps) => insertImage({ editor, event: "insert", range }),
},
{
commandKey: "callout",
key: "callout",
@@ -275,24 +284,8 @@ export const getSlashCommandFilteredSections =
},
];
const internalAdditionalOptions: TSlashCommandAdditionalOption[] = [];
if (!disabledExtensions?.includes("image")) {
internalAdditionalOptions.push({
commandKey: "image",
key: "image",
title: "Image",
icon: <ImageIcon className="size-3.5" />,
description: "Insert an image",
searchTerms: ["img", "photo", "picture", "media", "upload"],
command: ({ editor, range }: CommandProps) => insertImage({ editor, event: "insert", range }),
section: "general",
pushAfter: "code",
});
}
[
...internalAdditionalOptions,
...(externalAdditionalOptions ?? []),
...(additionalOptions ?? []),
...coreEditorAdditionalSlashCommandOptions({
disabledExtensions,
}),
+3 -3
View File
@@ -111,7 +111,7 @@ export const useEditor = (props: CustomEditorProps) => {
// value is null when intentionally passed where syncing is not yet
// supported and value is undefined when the data from swr is not populated
if (value == null) return;
if (editor && !editor.isDestroyed && !editor.storage.imageComponent?.uploadInProgress) {
if (editor && !editor.isDestroyed && !editor.storage.imageComponent.uploadInProgress) {
try {
editor.commands.setContent(value, false, { preserveWhitespace: "full" });
if (editor.state.selection) {
@@ -129,7 +129,7 @@ export const useEditor = (props: CustomEditorProps) => {
useEffect(() => {
if (!editor) return;
const assetsUploadStatus = fileHandler.assetsUploadStatus;
editor.commands.updateAssetsUploadStatus?.(assetsUploadStatus);
editor.commands.updateAssetsUploadStatus(assetsUploadStatus);
}, [editor, fileHandler.assetsUploadStatus]);
useImperativeHandle(
@@ -221,7 +221,7 @@ export const useEditor = (props: CustomEditorProps) => {
if (!editor) return;
scrollSummary(editor, marking);
},
isEditorReadyToDiscard: () => editor?.storage.imageComponent?.uploadInProgress === false,
isEditorReadyToDiscard: () => editor?.storage.imageComponent.uploadInProgress === false,
setFocusAtPosition: (position: number) => {
if (!editor || editor.isDestroyed) {
console.error("Editor reference is not available or has been destroyed.");
@@ -21,9 +21,7 @@ export const useUploader = (args: TUploaderArgs) => {
const uploadFile = useCallback(
async (file: File) => {
const setImageUploadInProgress = (isUploading: boolean) => {
if (editor.storage.imageComponent) {
editor.storage.imageComponent.uploadInProgress = isUploading;
}
editor.storage.imageComponent.uploadInProgress = isUploading;
};
setImageUploadInProgress(true);
setUploading(true);
+1 -1
View File
@@ -1 +1 @@
export type TExtensions = "ai" | "collaboration-cursor" | "issue-embed" | "slash-commands" | "enter-key" | "image";
export type TExtensions = "ai" | "collaboration-cursor" | "issue-embed" | "slash-commands" | "enter-key";
+1 -2
View File
@@ -1,8 +1,7 @@
{
"name": "@plane/eslint-config",
"private": true,
"version": "0.25.1",
"license": "AGPL-3.0",
"version": "0.25.0",
"files": [
"library.js",
"next.js",
+1 -2
View File
@@ -1,7 +1,6 @@
{
"name": "@plane/hooks",
"version": "0.25.1",
"license": "AGPL-3.0",
"version": "0.25.0",
"description": "React hooks that are shared across multiple apps internally",
"private": true,
"main": "./dist/index.js",
+1 -2
View File
@@ -1,7 +1,6 @@
{
"name": "@plane/i18n",
"version": "0.25.1",
"license": "AGPL-3.0",
"version": "0.25.0",
"description": "I18n shared across multiple apps internally",
"private": true,
"main": "./src/index.ts",
@@ -687,7 +687,7 @@
"you": "You",
"upgrade_cta": {
"higher_subscription": "Upgrade to higher subscription",
"talk_to_sales": "Talk to Sales"
"talk_to_sales": "Talk to sales"
},
"category": "Category",
"categories": "Categories",
@@ -696,8 +696,7 @@
"delete": "Delete",
"deleting": "Deleting",
"pending": "Pending",
"invite": "Invite",
"view": "View"
"invite": "Invite"
},
"chart": {
@@ -815,8 +814,7 @@
"sub_issue_count": "Sub-work item count",
"attachment_count": "Attachment count",
"created_on": "Created on",
"sub_issue": "Sub-work item",
"work_item_count": "Work item count"
"sub_issue": "Sub-work item"
},
"extra": {
"show_sub_issues": "Show sub-work items",
@@ -867,8 +867,7 @@
"delete": "Eliminar",
"deleting": "Eliminando",
"pending": "Pendiente",
"invite": "Invitar",
"view": "Ver"
"invite": "Invitar"
},
"chart": {
@@ -986,8 +985,7 @@
"sub_issue_count": "Cantidad de sub-elementos",
"attachment_count": "Cantidad de archivos adjuntos",
"created_on": "Creado el",
"sub_issue": "Sub-elemento de trabajo",
"work_item_count": "Recuento de elementos de trabajo"
"sub_issue": "Sub-elemento de trabajo"
},
"extra": {
"show_sub_issues": "Mostrar sub-elementos",
@@ -856,7 +856,7 @@
"you": "Vous",
"upgrade_cta": {
"higher_subscription": "Passer à une abonnement plus élevé",
"talk_to_sales": "Parler aux ventes"
"talk_to_sales": "Parler à la vente"
},
"category": "Catégorie",
"categories": "Catégories",
@@ -865,8 +865,7 @@
"delete": "Supprimer",
"deleting": "Suppression",
"pending": "En attente",
"invite": "Inviter",
"view": "Afficher"
"invite": "Inviter"
},
"chart": {
@@ -984,8 +983,7 @@
"sub_issue_count": "Nombre de sous-éléments",
"attachment_count": "Nombre de pièces jointes",
"created_on": "Créé le",
"sub_issue": "Sous-élément de travail",
"work_item_count": "Nombre d'éléments de travail"
"sub_issue": "Sous-élément de travail"
},
"extra": {
"show_sub_issues": "Afficher les sous-éléments",
@@ -862,8 +862,7 @@
"delete": "Elimina",
"deleting": "Eliminazione in corso",
"pending": "In sospeso",
"invite": "Invita",
"view": "Visualizza"
"invite": "Invita"
},
"chart": {
@@ -981,8 +980,7 @@
"sub_issue_count": "Numero di sotto-elementi di lavoro",
"attachment_count": "Numero di allegati",
"created_on": "Creato il",
"sub_issue": "Sotto-elemento di lavoro",
"work_item_count": "Conteggio degli elementi di lavoro"
"sub_issue": "Sotto-elemento di lavoro"
},
"extra": {
"show_sub_issues": "Mostra sotto-elementi di lavoro",
@@ -856,7 +856,7 @@
"you": "あなた",
"upgrade_cta": {
"higher_subscription": "高いサブスクリプションにアップグレード",
"talk_to_sales": "トーク トゥ セールス"
"talk_to_sales": "セールスに連絡"
},
"category": "カテゴリー",
"categories": "カテゴリーズ",
@@ -865,8 +865,7 @@
"delete": "デリート",
"deleting": "デリーティング",
"pending": "保留中",
"invite": "招待",
"view": "ビュー"
"invite": "招待"
},
"chart": {
@@ -984,8 +983,7 @@
"sub_issue_count": "サブ作業項目数",
"attachment_count": "添付ファイル数",
"created_on": "作成日",
"sub_issue": "サブ作業項目",
"work_item_count": "作業項目数"
"sub_issue": "サブ作業項目"
},
"extra": {
"show_sub_issues": "サブ作業項目を表示",
@@ -864,8 +864,7 @@
"delete": "Удалить",
"deleting": "Удаление",
"pending": "Ожидание",
"invite": "Пригласить",
"view": "Просмотр"
"invite": "Пригласить"
},
"chart": {
@@ -983,8 +982,7 @@
"sub_issue_count": "Количество подэлементов",
"attachment_count": "Количество вложений",
"created_on": "Дата создания",
"sub_issue": "Подэлемент",
"work_item_count": "Количество рабочих элементов"
"sub_issue": "Подэлемент"
},
"extra": {
"show_sub_issues": "Показывать подэлементы",
@@ -1868,7 +1866,7 @@
}
},
"completed_no_issues": {
"title": "Нет рабочих элементов в цикле",
"title": "Нет рабочих элементов в цикле",
"description": "Нет рабочих элементов. Рабочие элементы были перенесены или скрыты. Для просмотра измените настройки отображения."
},
"active": {
@@ -2287,7 +2285,7 @@
"short_description": "Экспорт в csv"
},
"excel": {
"title": "Excel",
"title": "Excel",
"description": "Экспорт рабочих элементов в файл Excel.",
"short_description": "Экспорт в excel"
},
@@ -2305,7 +2303,7 @@
"default_global_view": {
"all_issues": "Все рабочие элементы",
"assigned": "Назначенные",
"created": "Созданные",
"created": "Созданные",
"subscribed": "Подписанные"
},
@@ -2334,7 +2332,7 @@
"project_modules": {
"status": {
"backlog": "Бэклог",
"planned": "Запланировано",
"planned": "Запланировано",
"in_progress": "В процессе",
"paused": "Приостановлено",
"completed": "Завершено",
@@ -865,8 +865,7 @@
"delete": "删除",
"deleting": "删除中",
"pending": "待处理",
"invite": "邀请",
"view": "查看"
"invite": "邀请"
},
"chart": {
@@ -984,8 +983,7 @@
"sub_issue_count": "子工作项数量",
"attachment_count": "附件数量",
"created_on": "创建于",
"sub_issue": "子工作项",
"work_item_count": "工作项数量"
"sub_issue": "子工作项"
},
"extra": {
"show_sub_issues": "显示子工作项",
+1 -2
View File
@@ -1,7 +1,6 @@
{
"name": "@plane/logger",
"version": "0.25.1",
"license": "AGPL-3.0",
"version": "0.25.0",
"description": "Logger shared across multiple apps internally",
"private": true,
"main": "./src/index.ts",
+1 -2
View File
@@ -1,8 +1,7 @@
{
"name": "@plane/propel",
"version": "0.25.1",
"version": "0.25.0",
"private": true,
"license": "AGPL-3.0",
"scripts": {
"lint": "eslint src --ext .ts,.tsx",
"lint:errors": "eslint src --ext .ts,.tsx --quiet"
+1 -2
View File
@@ -1,7 +1,6 @@
{
"name": "@plane/services",
"version": "0.25.1",
"license": "AGPL-3.0",
"version": "0.25.0",
"private": true,
"main": "./src/index.ts",
"scripts": {
+1 -2
View File
@@ -1,7 +1,6 @@
{
"name": "@plane/shared-state",
"version": "0.25.1",
"license": "AGPL-3.0",
"version": "0.25.0",
"description": "Shared state shared across multiple apps internally",
"private": true,
"main": "./src/index.ts",
+1 -2
View File
@@ -1,7 +1,6 @@
{
"name": "@plane/tailwind-config",
"version": "0.25.1",
"license": "AGPL-3.0",
"version": "0.25.0",
"description": "common tailwind configuration across monorepo",
"main": "tailwind.config.js",
"private": true,
+1 -2
View File
@@ -1,7 +1,6 @@
{
"name": "@plane/types",
"version": "0.25.1",
"license": "AGPL-3.0",
"version": "0.25.0",
"private": true,
"types": "./src/index.d.ts",
"main": "./src/index.d.ts"
-1
View File
@@ -40,4 +40,3 @@ export * from "./epics";
export * from "./charts";
export * from "./home";
export * from "./stickies";
export * from "./utils";
-5
View File
@@ -1,5 +0,0 @@
export type PartialDeep<K> = {
[attr in keyof K]?: K[attr] extends object ? PartialDeep<K[attr]> : K[attr];
};
export type CompleteOrEmpty<T> = T | Record<string, never>;
+6 -10
View File
@@ -1,6 +1,9 @@
import { TIssue } from "./issues/issue";
export type TIssueLayouts = "list" | "kanban" | "calendar" | "spreadsheet" | "gantt_chart";
export type TIssueLayouts =
| "list"
| "kanban"
| "calendar"
| "spreadsheet"
| "gantt_chart";
export type TIssueGroupByOptions =
| "state"
@@ -208,10 +211,3 @@ export interface IssuePaginationOptions {
subGroupedBy?: TIssueGroupByOptions;
orderBy?: TIssueOrderByOptions;
}
export type TSpreadsheetColumn = React.FC<{
issue: TIssue;
onClose: () => void;
onChange: (issue: TIssue, data: Partial<TIssue>, updates: any) => void;
disabled: boolean;
}>;
+1 -2
View File
@@ -1,7 +1,6 @@
{
"name": "@plane/typescript-config",
"version": "0.25.1",
"license": "AGPL-3.0",
"version": "0.25.0",
"private": true,
"files": [
"base.json",
+2 -2
View File
@@ -2,12 +2,12 @@
"name": "@plane/ui",
"description": "UI components shared across multiple apps internally",
"private": true,
"version": "0.25.1",
"version": "0.25.0",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"sideEffects": false,
"license": "AGPL-3.0",
"license": "MIT",
"files": [
"dist/**"
],
+1 -2
View File
@@ -1,8 +1,7 @@
{
"name": "@plane/utils",
"version": "0.25.1",
"version": "0.25.0",
"description": "Helper functions shared across multiple apps internally",
"license": "AGPL-3.0",
"private": true,
"main": "./dist/index.js",
"module": "./dist/index.mjs",
-34
View File
@@ -5,37 +5,3 @@ import { twMerge } from "tailwind-merge";
export const getSupportEmail = (defaultEmail: string = ""): string => defaultEmail;
export const cn = (...inputs: ClassValue[]) => twMerge(clsx(inputs));
/**
* Extracts IDs from an array of objects with ID property
*/
export const extractIds = <T extends { id: string }>(items: T[]): string[] => items.map((item) => item.id);
/**
* Checks if an ID exists and is valid within the provided list
*/
export const isValidId = (id: string | null | undefined, validIds: string[]): boolean => !!id && validIds.includes(id);
/**
* Filters an array to only include valid IDs
*/
export const filterValidIds = (ids: string[], validIds: string[]): string[] =>
ids.filter((id) => validIds.includes(id));
/**
* Filters an array to include only valid IDs, returning both valid and invalid IDs
*/
export const partitionValidIds = (ids: string[], validIds: string[]): { valid: string[]; invalid: string[] } => {
const valid: string[] = [];
const invalid: string[] = [];
ids.forEach((id) => {
if (validIds.includes(id)) {
valid.push(id);
} else {
invalid.push(id);
}
});
return { valid, invalid };
};
-1
View File
@@ -11,4 +11,3 @@ export * from "./state";
export * from "./string";
export * from "./theme";
export * from "./workspace";
export * from "./work-item";
-1
View File
@@ -1 +0,0 @@
export * from "./modal";
-33
View File
@@ -1,33 +0,0 @@
// plane imports
import { DEFAULT_WORK_ITEM_FORM_VALUES } from "@plane/constants";
import { IPartialProject, ISearchIssueResponse, IState, TIssue } from "@plane/types";
export const getUpdateFormDataForReset = (projectId: string | null | undefined, formData: Partial<TIssue>) => ({
...DEFAULT_WORK_ITEM_FORM_VALUES,
project_id: projectId,
name: formData.name,
description_html: formData.description_html,
priority: formData.priority,
start_date: formData.start_date,
target_date: formData.target_date,
});
export const convertWorkItemDataToSearchResponse = (
workspaceSlug: string,
workItem: TIssue,
project: IPartialProject | undefined,
state: IState | undefined
): ISearchIssueResponse => ({
id: workItem.id,
name: workItem.name,
project_id: workItem.project_id ?? "",
project__identifier: project?.identifier ?? "",
project__name: project?.name ?? "",
sequence_id: workItem.sequence_id,
type_id: workItem.type_id ?? "",
state__color: state?.color ?? "",
start_date: workItem.start_date,
state__group: state?.group ?? "backlog",
state__name: state?.name ?? "",
workspace__slug: workspaceSlug,
});
@@ -1,9 +1,9 @@
FROM nginx:1.25.0-alpine
RUN rm /etc/nginx/conf.d/default.conf
COPY nginx.conf.dev /etc/nginx/nginx.conf.template
COPY nginx/nginx.conf.dev /etc/nginx/nginx.conf.template
COPY ./env.sh /docker-entrypoint.sh
COPY nginx/env.sh /docker-entrypoint.sh
RUN chmod +x /docker-entrypoint.sh
# Update all environment variables
+2 -2
View File
@@ -1,9 +1,9 @@
FROM nginx:1.25.0-alpine
RUN rm /etc/nginx/conf.d/default.conf
COPY nginx.conf.template /etc/nginx/nginx.conf.template
COPY nginx/nginx.conf.template /etc/nginx/nginx.conf.template
COPY ./env.sh /docker-entrypoint.sh
COPY nginx/env.sh /docker-entrypoint.sh
RUN chmod +x /docker-entrypoint.sh
# Update all environment variables
+1
View File
@@ -0,0 +1 @@
nginx.conf.template
+39
View File
@@ -0,0 +1,39 @@
(plane_proxy) {
request_body {
max_size {$FILE_SIZE_LIMIT}
}
reverse_proxy /spaces/* space:3000
reverse_proxy /god-mode/* admin:3000
reverse_proxy /api/* api:8000
reverse_proxy /auth/* api:8000
reverse_proxy /live/* live:3000
reverse_proxy /{$BUCKET_NAME} plane-minio:9000
reverse_proxy /{$BUCKET_NAME}/* plane-minio:9000
reverse_proxy /* web:3000
}
{
email {$CERT_EMAIL:admin@example.com}
acme_ca {$CERT_ACME_CA}
{$CERT_ACME_DNS}
servers {
timeouts {
read_body 600s
read_header 30s
write 600s
idle 600s
}
max_header_size 25MB
client_ip_headers X-Forwarded-For X-Real-IP
trusted_proxies static {$TRUSTED_PROXIES:0.0.0.0/0}
}
log {
output {$LOG_OUTPUT:stdout}
level {$LOG_LEVEL:INFO}
}
}
{$SITE_ADDRESS} {
import plane_proxy
}
View File
+1 -2
View File
@@ -1,8 +1,7 @@
{
"name": "space",
"version": "0.25.1",
"version": "0.25.0",
"private": true,
"license": "AGPL-3.0",
"scripts": {
"dev": "turbo run develop",
"develop": "next dev -p 3002",
@@ -4,7 +4,7 @@ import { FC, ReactNode } from "react";
// components
import { AppHeader } from "@/components/core";
// local components
import { ProjectSettingHeader } from "../header";
import { ProjectSettingHeader } from "./header";
import { ProjectSettingsSidebar } from "./sidebar";
export interface IProjectSettingLayout {
@@ -10,7 +10,7 @@ import { AppHeader } from "@/components/core";
import { useUserPermissions } from "@/hooks/store";
// plane web constants
// local components
import { WorkspaceSettingHeader } from "../header";
import { WorkspaceSettingHeader } from "./header";
import { MobileWorkspaceSettingsTabs } from "./mobile-header-tabs";
import { WorkspaceSettingsSidebar } from "./sidebar";
@@ -1,9 +0,0 @@
import React, { FC } from "react";
import { IIssueDisplayProperties, TIssue } from "@plane/types";
export type TWorkItemLayoutAdditionalProperties = {
displayProperties: IIssueDisplayProperties;
issue: TIssue;
};
export const WorkItemLayoutAdditionalProperties: FC<TWorkItemLayoutAdditionalProperties> = (props) => <></>;
@@ -4,10 +4,9 @@ import React, { FC } from "react";
type Props = {
issueId: string;
className?: string;
size?: number;
showProgressText?: boolean;
showLabel?: boolean;
};
export const IssueStats: FC<Props> = (props) => <></>;
export const IssueStats: FC<Props> = (props) => {
const { issueId } = props;
return <></>;
};
@@ -1,69 +1,4 @@
import { FC } from "react";
import {
CalendarCheck2,
CalendarClock,
CalendarDays,
ContrastIcon,
LayersIcon,
Link2,
Paperclip,
Signal,
Tag,
Triangle,
Users,
} from "lucide-react";
// types
import { IGroupByColumn, IIssueDisplayProperties, TSpreadsheetColumn } from "@plane/types";
import { DiceIcon, DoubleCircleIcon, ISvgIcons } from "@plane/ui";
// components
import {
SpreadsheetAssigneeColumn,
SpreadsheetAttachmentColumn,
SpreadsheetCreatedOnColumn,
SpreadsheetDueDateColumn,
SpreadsheetEstimateColumn,
SpreadsheetLabelColumn,
SpreadsheetModuleColumn,
SpreadsheetCycleColumn,
SpreadsheetLinkColumn,
SpreadsheetPriorityColumn,
SpreadsheetStartDateColumn,
SpreadsheetStateColumn,
SpreadsheetSubIssueColumn,
SpreadsheetUpdatedOnColumn,
} from "@/components/issues/issue-layouts/spreadsheet";
import { IGroupByColumn } from "@plane/types";
export const getTeamProjectColumns = (): IGroupByColumn[] | undefined => undefined;
export const SpreadSheetPropertyIconMap: Record<string, FC<ISvgIcons>> = {
Users: Users,
CalenderDays: CalendarDays,
CalenderCheck2: CalendarCheck2,
Triangle: Triangle,
Tag: Tag,
DiceIcon: DiceIcon,
ContrastIcon: ContrastIcon,
Signal: Signal,
CalendarClock: CalendarClock,
DoubleCircleIcon: DoubleCircleIcon,
Link2: Link2,
Paperclip: Paperclip,
LayersIcon: LayersIcon,
};
export const SPREADSHEET_COLUMNS: { [key in keyof IIssueDisplayProperties]: TSpreadsheetColumn } = {
assignee: SpreadsheetAssigneeColumn,
created_on: SpreadsheetCreatedOnColumn,
due_date: SpreadsheetDueDateColumn,
estimate: SpreadsheetEstimateColumn,
labels: SpreadsheetLabelColumn,
modules: SpreadsheetModuleColumn,
cycle: SpreadsheetCycleColumn,
link: SpreadsheetLinkColumn,
priority: SpreadsheetPriorityColumn,
start_date: SpreadsheetStartDateColumn,
state: SpreadsheetStateColumn,
sub_issue_count: SpreadsheetSubIssueColumn,
updated_on: SpreadsheetUpdatedOnColumn,
attachment_count: SpreadsheetAttachmentColumn,
};
@@ -1,5 +1,3 @@
export * from "./provider";
export * from "./issue-type-select";
export * from "./additional-properties";
export * from "./template-select";
@@ -1,6 +1,4 @@
import { Control } from "react-hook-form";
// plane imports
import { EditorRefApi } from "@plane/editor";
// types
import { TBulkIssueProperties, TIssue } from "@plane/types";
@@ -11,7 +9,6 @@ export type TIssueTypeDropdownVariant = "xs" | "sm";
export type TIssueTypeSelectProps<T extends Partial<TIssueFields>> = {
control: Control<T>;
projectId: string | null;
editorRef?: React.MutableRefObject<EditorRefApi | null>;
disabled?: boolean;
variant?: TIssueTypeDropdownVariant;
placeholder?: string;
@@ -1,29 +1,17 @@
import React, { useState } from "react";
import React from "react";
import { observer } from "mobx-react-lite";
// plane imports
import { ISearchIssueResponse } from "@plane/types";
// components
import { IssueModalContext } from "@/components/issues";
export type TIssueModalProviderProps = {
templateId?: string;
type TIssueModalProviderProps = {
children: React.ReactNode;
};
export const IssueModalProvider = observer((props: TIssueModalProviderProps) => {
const { children } = props;
// states
const [selectedParentIssue, setSelectedParentIssue] = useState<ISearchIssueResponse | null>(null);
return (
<IssueModalContext.Provider
value={{
workItemTemplateId: null,
setWorkItemTemplateId: () => {},
isApplyingTemplate: false,
setIsApplyingTemplate: () => {},
selectedParentIssue,
setSelectedParentIssue,
issuePropertyValues: {},
setIssuePropertyValues: () => {},
issuePropertyValueErrors: {},
@@ -32,9 +20,6 @@ export const IssueModalProvider = observer((props: TIssueModalProviderProps) =>
getActiveAdditionalPropertiesLength: () => 0,
handlePropertyValuesValidation: () => true,
handleCreateUpdatePropertyValues: () => Promise.resolve(),
handleParentWorkItemDetails: () => Promise.resolve(undefined),
handleProjectEntitiesFetch: () => Promise.resolve(),
handleTemplateChange: () => Promise.resolve(),
}}
>
{children}
@@ -1,15 +0,0 @@
export type TWorkItemTemplateDropdownSize = "xs" | "sm";
export type TWorkItemTemplateSelect = {
projectId: string | null;
typeId: string | null;
disabled?: boolean;
size?: TWorkItemTemplateDropdownSize;
placeholder?: string;
renderChevron?: boolean;
dropDownContainerClassName?: string;
handleFormChange?: () => void;
};
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export const WorkItemTemplateSelect = (props: TWorkItemTemplateSelect) => <></>;
@@ -26,7 +26,7 @@ export const ChangeIssueAssignee: React.FC<Props> = observer((props) => {
} = useMember();
// derived values
const projectId = issue?.project_id ?? "";
const projectMemberIds = getProjectMemberIds(projectId, false);
const projectMemberIds = getProjectMemberIds(projectId);
const options =
projectMemberIds
@@ -24,15 +24,13 @@ import { IssueSearchModalEmptyState } from "./issue-search-modal-empty-state";
type Props = {
workspaceSlug: string | undefined;
projectId?: string;
projectId: string | undefined;
isOpen: boolean;
handleClose: () => void;
searchParams: Partial<TProjectIssuesSearchParams>;
handleOnSubmit: (data: ISearchIssueResponse[]) => Promise<void>;
workspaceLevelToggle?: boolean;
shouldHideIssue?: (issue: ISearchIssueResponse) => boolean;
selectedWorkItems?: ISearchIssueResponse[];
workItemSearchServiceCallback?: (params: TProjectIssuesSearchParams) => Promise<ISearchIssueResponse[]>;
};
const projectService = new ProjectService();
@@ -49,8 +47,6 @@ export const ExistingIssuesListModal: React.FC<Props> = (props) => {
handleOnSubmit,
workspaceLevelToggle = false,
shouldHideIssue,
selectedWorkItems,
workItemSearchServiceCallback,
} = props;
// states
const [isLoading, setIsLoading] = useState(false);
@@ -89,35 +85,20 @@ export const ExistingIssuesListModal: React.FC<Props> = (props) => {
handleClose();
};
const handleSearch = () => {
if (!isOpen || !workspaceSlug) return;
useEffect(() => {
if (!isOpen || !workspaceSlug || !projectId) return;
setIsLoading(true);
const searchService =
workItemSearchServiceCallback ??
(projectId
? projectService.projectIssuesSearch.bind(projectService, workspaceSlug?.toString(), projectId?.toString())
: undefined);
if (!searchService) return;
searchService({
search: debouncedSearchTerm,
...searchParams,
workspace_search: isWorkspaceLevel,
})
projectService
.projectIssuesSearch(workspaceSlug as string, projectId as string, {
search: debouncedSearchTerm,
...searchParams,
workspace_search: isWorkspaceLevel,
})
.then((res) => setIssues(res))
.finally(() => {
setIsSearching(false);
setIsLoading(false);
});
};
useEffect(() => {
if (selectedWorkItems) {
setSelectedIssues(selectedWorkItems);
}
}, [isOpen, selectedWorkItems]);
useEffect(() => {
handleSearch();
}, [debouncedSearchTerm, isOpen, isWorkspaceLevel, projectId, workspaceSlug]);
const filteredIssues = issues.filter((issue) => !shouldHideIssue?.(issue));
@@ -68,11 +68,7 @@ export const MemberOptions: React.FC<Props> = observer((props: Props) => {
}
}, [isOpen, isMobile]);
const memberIds = propsMemberIds
? propsMemberIds
: projectId
? getProjectMemberIds(projectId, true)
: workspaceMemberIds;
const memberIds = propsMemberIds ? propsMemberIds : projectId ? getProjectMemberIds(projectId) : workspaceMemberIds;
const onOpen = () => {
if (!memberIds && workspaceSlug && projectId) fetchProjectMembers(workspaceSlug.toString(), projectId);
};
@@ -52,7 +52,7 @@ export const RichTextEditor = forwardRef<EditorRefApi, RichTextEditorWrapperProp
renderComponent: (props) => <EditorMentionsRoot {...props} />,
}}
{...rest}
containerClassName={cn("relative pl-3 pb-3", containerClassName)}
containerClassName={cn("relative pl-3", containerClassName)}
/>
);
});
@@ -27,7 +27,6 @@ type Props = {
secondaryButton?: ButtonConfig;
customPrimaryButton?: React.ReactNode;
customSecondaryButton?: React.ReactNode;
className?: string;
};
const sizeClasses = {
@@ -67,18 +66,12 @@ export const DetailedEmptyState: React.FC<Props> = observer((props) => {
customPrimaryButton,
customSecondaryButton,
assetPath,
className,
} = props;
const hasButtons = primaryButton || secondaryButton || customPrimaryButton || customSecondaryButton;
return (
<div
className={cn(
"flex items-center justify-center min-h-full min-w-full overflow-y-auto py-10 md:px-20 px-5",
className
)}
>
<div className="flex items-center justify-center min-h-full min-w-full overflow-y-auto py-10 md:px-20 px-5">
<div className={cn("flex flex-col gap-5", sizeClasses[size])}>
<div className="flex flex-col gap-1.5 flex-shrink">
<h3 className={cn("text-xl font-semibold", { "font-medium": !description })}>{title}</h3>
@@ -66,7 +66,7 @@ export const IssueAttachmentsListItem: FC<TIssueAttachmentsListItem> = observer(
</div>
<div className="flex items-center gap-3">
{attachment?.created_by && (
{attachment?.updated_by && (
<>
<Tooltip
isMobile={isMobile}
@@ -53,7 +53,7 @@ export const FilterDisplayProperties: React.FC<Props> = observer((props) => {
}
}).map((property) => {
if (isEpic && property.key === "sub_issue_count") {
return { ...property, titleTranslationKey: "issue.display.properties.work_item_count" };
return { ...property, title: "Work item count" };
}
return property;
});
@@ -5,9 +5,9 @@ import { useParams } from "next/navigation";
// ui
import { Tooltip, ControlLink } from "@plane/ui";
// components
import { findTotalDaysInRange } from "@plane/utils";
import { SIDEBAR_WIDTH } from "@/components/gantt-chart/constants";
// helpers
import { renderFormattedDate } from "@/helpers/date-time.helper";
import { generateWorkItemLink } from "@/helpers/issue.helper";
// hooks
import { useIssueDetail, useIssues, useProject, useProjectState } from "@/hooks/store";
@@ -17,7 +17,6 @@ import { usePlatformOS } from "@/hooks/use-platform-os";
// plane web components
import { IssueIdentifier } from "@/plane-web/components/issues";
//
import { IssueStats } from "@/plane-web/components/issues/issue-layouts/issue-stats";
import { getBlockViewDetails } from "../utils";
import { GanttStoreType } from "./base-gantt-root";
@@ -49,8 +48,6 @@ export const IssueGanttBlock: React.FC<Props> = observer((props) => {
const handleIssuePeekOverview = () => handleRedirection(workspaceSlug, issueDetails, isMobile);
const duration = findTotalDaysInRange(issueDetails?.start_date, issueDetails?.target_date) || 0;
return (
<Tooltip
isMobile={isMobile}
@@ -65,24 +62,17 @@ export const IssueGanttBlock: React.FC<Props> = observer((props) => {
>
<div
id={`issue-${issueId}`}
className="relative flex h-full w-full cursor-pointer items-center rounded space-between"
className="relative flex h-full w-full cursor-pointer items-center rounded"
style={blockStyle}
onClick={handleIssuePeekOverview}
>
<div className="absolute left-0 top-0 h-full w-full bg-custom-background-100/50 " />
<div className="absolute left-0 top-0 h-full w-full bg-custom-background-100/50" />
<div
className="sticky w-auto overflow-hidden truncate px-2.5 py-1 text-sm text-custom-text-100 flex-1"
className="sticky w-auto overflow-hidden truncate px-2.5 py-1 text-sm text-custom-text-100"
style={{ left: `${SIDEBAR_WIDTH}px` }}
>
{issueDetails?.name}
</div>
{isEpic && (
<IssueStats
issueId={issueId}
className="sticky mx-2 font-medium text-custom-text-100 overflow-hidden truncate w-auto justify-end flex-shrink-0"
showProgressText={duration >= 2}
/>
)}
</div>
</Tooltip>
);
@@ -25,10 +25,8 @@ import { usePlatformOS } from "@/hooks/use-platform-os";
// plane web components
import { IssueIdentifier } from "@/plane-web/components/issues";
// local components
import { IssueStats } from "@/plane-web/components/issues/issue-layouts/issue-stats";
import { TRenderQuickActions } from "../list/list-view-types";
import { IssueProperties } from "../properties/all-properties";
import { WithDisplayPropertiesHOC } from "../properties/with-display-properties-HOC";
import { getIssueBlockId } from "../utils";
interface IssueBlockProps {
@@ -63,9 +61,6 @@ const KanbanIssueDetailsBlock: React.FC<IssueDetailsBlockProps> = observer((prop
// hooks
const { isMobile } = usePlatformOS();
// derived values
const subIssueCount = issue?.sub_issues_count ?? 0;
const handleEventPropagation = (e: React.MouseEvent) => {
e.stopPropagation();
e.preventDefault();
@@ -110,16 +105,6 @@ const KanbanIssueDetailsBlock: React.FC<IssueDetailsBlockProps> = observer((prop
isReadOnly={isReadOnly}
isEpic={isEpic}
/>
{isEpic && displayProperties && (
<WithDisplayPropertiesHOC
displayProperties={displayProperties}
displayPropertyKey="sub_issue_count"
shouldRenderProperty={(properties) => !!properties.sub_issue_count && !!subIssueCount}
>
<IssueStats issueId={issue.id} className="mt-2 font-medium text-custom-text-350" />
</WithDisplayPropertiesHOC>
)}
</>
);
});
@@ -25,7 +25,6 @@ import { usePlatformOS } from "@/hooks/use-platform-os";
import { IssueIdentifier } from "@/plane-web/components/issues";
import { IssueStats } from "@/plane-web/components/issues/issue-layouts/issue-stats";
// types
import { WithDisplayPropertiesHOC } from "../properties/with-display-properties-HOC";
import { TRenderQuickActions } from "./list-view-types";
interface IssueBlockProps {
@@ -270,15 +269,7 @@ export const IssueBlock = observer((props: IssueBlockProps) => {
>
<p className="truncate cursor-pointer text-sm text-custom-text-100">{issue.name}</p>
</Tooltip>
{isEpic && displayProperties && (
<WithDisplayPropertiesHOC
displayProperties={displayProperties}
displayPropertyKey="sub_issue_count"
shouldRenderProperty={(properties) => !!properties.sub_issue_count}
>
<IssueStats issueId={issue.id} className="ml-2 font-medium text-custom-text-350" />
</WithDisplayPropertiesHOC>
)}
{isEpic && <IssueStats issueId={issue.id} />}
</div>
{!issue?.tempId && (
<div
@@ -33,8 +33,6 @@ import { useEventTracker, useLabel, useIssues, useProjectState, useProject, useP
import { useAppRouter } from "@/hooks/use-app-router";
import { useIssueStoreType } from "@/hooks/use-issue-layout-store";
import { usePlatformOS } from "@/hooks/use-platform-os";
// plane web components
import { WorkItemLayoutAdditionalProperties } from "@/plane-web/components/issues/issue-layouts/additional-properties";
// local components
import { IssuePropertyLabels } from "./labels";
import { WithDisplayPropertiesHOC } from "./with-display-properties-HOC";
@@ -431,38 +429,36 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
{/* extra render properties */}
{/* sub-issues */}
{!isEpic && (
<WithDisplayPropertiesHOC
displayProperties={displayProperties}
displayPropertyKey="sub_issue_count"
shouldRenderProperty={(properties) => !!properties.sub_issue_count && !!subIssueCount}
<WithDisplayPropertiesHOC
displayProperties={displayProperties}
displayPropertyKey="sub_issue_count"
shouldRenderProperty={(properties) => !!properties.sub_issue_count && !!subIssueCount}
>
<Tooltip
tooltipHeading={isEpic ? t("issues.label", { count: 2 }) : t("common.sub_work_items")}
tooltipContent={`${subIssueCount}`}
isMobile={isMobile}
renderByDefault={false}
>
<Tooltip
tooltipHeading={t("common.sub_work_items")}
tooltipContent={`${subIssueCount}`}
isMobile={isMobile}
renderByDefault={false}
<div
onFocus={handleEventPropagation}
onClick={(e) => {
e.stopPropagation();
e.preventDefault();
if (subIssueCount) redirectToIssueDetail();
}}
className={cn(
"flex h-5 flex-shrink-0 items-center justify-center gap-2 overflow-hidden rounded border-[0.5px] border-custom-border-300 px-2.5 py-1",
{
"hover:bg-custom-background-80 cursor-pointer": subIssueCount,
}
)}
>
<div
onFocus={handleEventPropagation}
onClick={(e) => {
e.stopPropagation();
e.preventDefault();
if (subIssueCount) redirectToIssueDetail();
}}
className={cn(
"flex h-5 flex-shrink-0 items-center justify-center gap-2 overflow-hidden rounded border-[0.5px] border-custom-border-300 px-2.5 py-1",
{
"hover:bg-custom-background-80 cursor-pointer": subIssueCount,
}
)}
>
<Layers className="h-3 w-3 flex-shrink-0" strokeWidth={2} />
<div className="text-xs">{subIssueCount}</div>
</div>
</Tooltip>
</WithDisplayPropertiesHOC>
)}
<Layers className="h-3 w-3 flex-shrink-0" strokeWidth={2} />
<div className="text-xs">{subIssueCount}</div>
</div>
</Tooltip>
</WithDisplayPropertiesHOC>
{/* attachments */}
<WithDisplayPropertiesHOC
@@ -510,9 +506,6 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
</Tooltip>
</WithDisplayPropertiesHOC>
{/* Additional Properties */}
<WorkItemLayoutAdditionalProperties displayProperties={displayProperties} issue={issue} />
{/* label */}
<WithDisplayPropertiesHOC displayProperties={displayProperties} displayPropertyKey="labels">
<IssuePropertyLabels
@@ -1,15 +1,31 @@
"use client";
import { FC } from "react";
//ui
import { ArrowDownWideNarrow, ArrowUpNarrowWide, CheckIcon, ChevronDownIcon, Eraser, MoveRight } from "lucide-react";
// constants
import { SPREADSHEET_PROPERTY_DETAILS } from "@plane/constants";
import {
ArrowDownWideNarrow,
ArrowUpNarrowWide,
CheckIcon,
ChevronDownIcon,
Eraser,
MoveRight,
CalendarDays,
Link2,
Signal,
Tag,
Triangle,
Paperclip,
CalendarCheck2,
CalendarClock,
Users,
} from "lucide-react";
// i18n
import { useTranslation } from "@plane/i18n";
// types
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, TIssueOrderByOptions } from "@plane/types";
import { CustomMenu, Row } from "@plane/ui";
import { LayersIcon, DoubleCircleIcon, DiceIcon, ContrastIcon, CustomMenu, Row } from "@plane/ui";
// ui
import { ISvgIcons } from "@plane/ui/src/icons/type";
import useLocalStorage from "@/hooks/use-local-storage";
import { SpreadSheetPropertyIcon } from "../../utils";
interface Props {
property: keyof IIssueDisplayProperties;
@@ -19,6 +35,130 @@ interface Props {
isEpic?: boolean;
}
export const SPREADSHEET_PROPERTY_DETAILS: {
[key in keyof IIssueDisplayProperties]: {
i18n_title: string;
ascendingOrderKey: TIssueOrderByOptions;
ascendingOrderTitle: string;
descendingOrderKey: TIssueOrderByOptions;
descendingOrderTitle: string;
icon: FC<ISvgIcons>;
};
} = {
assignee: {
i18n_title: "common.assignees",
ascendingOrderKey: "assignees__first_name",
ascendingOrderTitle: "A",
descendingOrderKey: "-assignees__first_name",
descendingOrderTitle: "Z",
icon: Users,
},
created_on: {
i18n_title: "common.sort.created_on",
ascendingOrderKey: "-created_at",
ascendingOrderTitle: "New",
descendingOrderKey: "created_at",
descendingOrderTitle: "Old",
icon: CalendarDays,
},
due_date: {
i18n_title: "common.order_by.due_date",
ascendingOrderKey: "-target_date",
ascendingOrderTitle: "New",
descendingOrderKey: "target_date",
descendingOrderTitle: "Old",
icon: CalendarCheck2,
},
estimate: {
i18n_title: "common.estimate",
ascendingOrderKey: "estimate_point__key",
ascendingOrderTitle: "Low",
descendingOrderKey: "-estimate_point__key",
descendingOrderTitle: "High",
icon: Triangle,
},
labels: {
i18n_title: "common.labels",
ascendingOrderKey: "labels__name",
ascendingOrderTitle: "A",
descendingOrderKey: "-labels__name",
descendingOrderTitle: "Z",
icon: Tag,
},
modules: {
i18n_title: "common.modules",
ascendingOrderKey: "issue_module__module__name",
ascendingOrderTitle: "A",
descendingOrderKey: "-issue_module__module__name",
descendingOrderTitle: "Z",
icon: DiceIcon,
},
cycle: {
i18n_title: "common.cycle",
ascendingOrderKey: "issue_cycle__cycle__name",
ascendingOrderTitle: "A",
descendingOrderKey: "-issue_cycle__cycle__name",
descendingOrderTitle: "Z",
icon: ContrastIcon,
},
priority: {
i18n_title: "common.priority",
ascendingOrderKey: "priority",
ascendingOrderTitle: "None",
descendingOrderKey: "-priority",
descendingOrderTitle: "Urgent",
icon: Signal,
},
start_date: {
i18n_title: "common.order_by.start_date",
ascendingOrderKey: "-start_date",
ascendingOrderTitle: "New",
descendingOrderKey: "start_date",
descendingOrderTitle: "Old",
icon: CalendarClock,
},
state: {
i18n_title: "common.state",
ascendingOrderKey: "state__name",
ascendingOrderTitle: "A",
descendingOrderKey: "-state__name",
descendingOrderTitle: "Z",
icon: DoubleCircleIcon,
},
updated_on: {
i18n_title: "common.sort.updated_on",
ascendingOrderKey: "-updated_at",
ascendingOrderTitle: "New",
descendingOrderKey: "updated_at",
descendingOrderTitle: "Old",
icon: CalendarDays,
},
link: {
i18n_title: "common.link",
ascendingOrderKey: "-link_count",
ascendingOrderTitle: "Most",
descendingOrderKey: "link_count",
descendingOrderTitle: "Least",
icon: Link2,
},
attachment_count: {
i18n_title: "common.attachment",
ascendingOrderKey: "-attachment_count",
ascendingOrderTitle: "Most",
descendingOrderKey: "attachment_count",
descendingOrderTitle: "Least",
icon: Paperclip,
},
sub_issue_count: {
i18n_title: "issue.display.properties.sub_issue",
ascendingOrderKey: "-sub_issues_count",
ascendingOrderTitle: "Most",
descendingOrderKey: "sub_issues_count",
descendingOrderTitle: "Least",
icon: LayersIcon,
},
};
export const HeaderColumn = (props: Props) => {
const { displayFilters, handleDisplayFilterUpdate, property, onClose, isEpic = false } = props;
// i18n
@@ -50,7 +190,7 @@ export const HeaderColumn = (props: Props) => {
customButton={
<Row className="flex w-full cursor-pointer items-center justify-between gap-1.5 py-2 text-sm text-custom-text-200 hover:text-custom-text-100">
<div className="flex items-center gap-1.5">
{<SpreadSheetPropertyIcon iconKey={propertyDetails.icon} className="h-4 w-4 text-custom-text-400" />}
{<propertyDetails.icon className="h-4 w-4 text-custom-text-400" />}
{property === "sub_issue_count" && isEpic ? t("issue.label", { count: 2 }) : t(propertyDetails.i18n_title)}
</div>
<div className="ml-3 flex">
@@ -8,7 +8,6 @@ import { Row } from "@plane/ui";
import { cn } from "@/helpers/common.helper";
// hooks
import { useAppRouter } from "@/hooks/use-app-router";
import { IssueStats } from "@/plane-web/components/issues/issue-layouts/issue-stats";
type Props = {
issue: TIssue;
@@ -19,30 +18,30 @@ export const SpreadsheetSubIssueColumn: React.FC<Props> = observer((props: Props
// router
const router = useAppRouter();
// hooks
const { workspaceSlug } = useParams();
const { workspaceSlug, epicId } = useParams();
// derived values
const isEpic = issue?.is_epic;
const subIssueCount = issue?.sub_issues_count ?? 0;
const redirectToIssueDetail = () => {
router.push(
`/${workspaceSlug?.toString()}/projects/${issue.project_id}/${issue.archived_at ? "archives/" : ""}${isEpic ? "epics" : "issues"}/${issue.id}#sub-issues`
`/${workspaceSlug?.toString()}/projects/${issue.project_id}/${issue.archived_at ? "archives/" : ""}${epicId ? "epics" : "issues"}/${issue.id}#sub-issues`
);
};
const label = `${subIssueCount} sub-work item${subIssueCount !== 1 ? "s" : ""}`;
const issueLabel = epicId ? "work item" : "sub-work item";
const label = `${subIssueCount} ${issueLabel}${subIssueCount !== 1 ? "s" : ""}`;
return (
<Row
onClick={subIssueCount ? redirectToIssueDetail : () => {}}
className={cn(
"flex h-11 w-full items-center border-b-[0.5px] border-custom-border-200 py-1 text-xs hover:bg-custom-background-90 group-[.selected-issue-row]:bg-custom-primary-100/5 group-[.selected-issue-row]:hover:bg-custom-primary-90",
"flex h-11 w-full items-center border-b-[0.5px] border-custom-border-200 py-1 text-xs hover:bg-custom-background-80 group-[.selected-issue-row]:bg-custom-primary-100/5 group-[.selected-issue-row]:hover:bg-custom-primary-100/10",
{
"cursor-pointer": subIssueCount,
}
)}
>
{isEpic ? <IssueStats issueId={issue.id} /> : label}
{label}
</Row>
);
});
@@ -3,12 +3,27 @@ import { observer } from "mobx-react";
import { usePathname } from "next/navigation";
// types
import { IIssueDisplayProperties, TIssue } from "@plane/types";
// components
import {
SpreadsheetAssigneeColumn,
SpreadsheetAttachmentColumn,
SpreadsheetCreatedOnColumn,
SpreadsheetDueDateColumn,
SpreadsheetEstimateColumn,
SpreadsheetLabelColumn,
SpreadsheetModuleColumn,
SpreadsheetCycleColumn,
SpreadsheetLinkColumn,
SpreadsheetPriorityColumn,
SpreadsheetStartDateColumn,
SpreadsheetStateColumn,
SpreadsheetSubIssueColumn,
SpreadsheetUpdatedOnColumn,
} from "@/components/issues/issue-layouts/spreadsheet";
// hooks
import { useEventTracker } from "@/hooks/store";
// components
import { SPREADSHEET_COLUMNS } from "@/plane-web/components/issues/issue-layouts/utils";
import { WithDisplayPropertiesHOC } from "../properties/with-display-properties-HOC";
// utils
type Props = {
displayProperties: IIssueDisplayProperties;
@@ -19,6 +34,30 @@ type Props = {
isEstimateEnabled: boolean;
};
type TSpreadsheetColumn = React.FC<{
issue: TIssue;
onClose: () => void;
onChange: (issue: TIssue, data: Partial<TIssue>, updates: any) => void;
disabled: boolean;
}>;
const SPREADSHEET_COLUMNS: { [key in keyof IIssueDisplayProperties]: TSpreadsheetColumn } = {
assignee: SpreadsheetAssigneeColumn,
created_on: SpreadsheetCreatedOnColumn,
due_date: SpreadsheetDueDateColumn,
estimate: SpreadsheetEstimateColumn,
labels: SpreadsheetLabelColumn,
modules: SpreadsheetModuleColumn,
cycle: SpreadsheetCycleColumn,
link: SpreadsheetLinkColumn,
priority: SpreadsheetPriorityColumn,
start_date: SpreadsheetStartDateColumn,
state: SpreadsheetStateColumn,
sub_issue_count: SpreadsheetSubIssueColumn,
updated_on: SpreadsheetUpdatedOnColumn,
attachment_count: SpreadsheetAttachmentColumn,
};
export const IssueColumn = observer((props: Props) => {
const { displayProperties, issueDetail, disableUserActions, property, updateIssue, isEstimateEnabled } = props;
// router
@@ -1,7 +1,7 @@
import React, { useRef } from "react";
import { observer } from "mobx-react";
// plane constants
import { EIssueLayoutTypes, SPREADSHEET_SELECT_GROUP, SPREADSHEET_PROPERTY_LIST } from "@plane/constants";
import { EIssueLayoutTypes, SPREADSHEET_SELECT_GROUP } from "@plane/constants";
// types
import { TIssue, IIssueDisplayFilterOptions, IIssueDisplayProperties } from "@plane/types";
// components
@@ -18,6 +18,23 @@ import { useBulkOperationStatus } from "@/plane-web/hooks/use-bulk-operation-sta
import { TRenderQuickActions } from "../list/list-view-types";
import { SpreadsheetTable } from "./spreadsheet-table";
const SPREADSHEET_PROPERTY_LIST: (keyof IIssueDisplayProperties)[] = [
"state",
"priority",
"assignee",
"labels",
"modules",
"cycle",
"start_date",
"due_date",
"estimate",
"created_on",
"updated_on",
"link",
"attachment_count",
"sub_issue_count",
];
type Props = {
displayProperties: IIssueDisplayProperties;
displayFilters: IIssueDisplayFilterOptions;

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