Compare commits

...

29 Commits

Author SHA1 Message Date
sriram veeraghanta
dd250eb3da fix: adding more build commands 2023-11-28 19:04:04 +05:30
sriram veeraghanta
578893df18 fix: adding pnpm action setup 2023-11-28 18:57:15 +05:30
sriram veeraghanta
84cc2fafd2 fix: moving towards pnpm 2023-11-28 18:42:13 +05:30
sriram veeraghanta
191949657d Merge branch 'develop' of github.com:makeplane/plane into fix/yarn-upgrade 2023-11-28 18:16:14 +05:30
rahulramesha
db510dcfcd fix: all functionalities for profile, archived and draft issues (#2922)
* fix profile issue filters and kanban

* fix profile draft and archived issues
2023-11-28 18:15:46 +05:30
Manish Gupta
d661026e3f cleanup 2023-11-28 17:39:39 +05:30
Manish Gupta
d3194ac595 cache enabled 2023-11-28 17:35:35 +05:30
Manish Gupta
ce70aa7349 wip 2023-11-28 17:34:32 +05:30
Manish Gupta
66cfb7b205 wip 2023-11-28 17:31:13 +05:30
Manish Gupta
b0688a060a wip 2023-11-28 17:30:23 +05:30
Manish Gupta
d062415bf0 wip 2023-11-28 17:27:27 +05:30
Manish Gupta
e81ea00fbf wip 2023-11-28 17:25:40 +05:30
Manish Gupta
4c3c20dacd yarn version 2023-11-28 17:22:51 +05:30
Manish Gupta
5875cf7fd7 testing yarn step 2023-11-28 17:21:25 +05:30
Lakhan Baheti
62c0615012 style: member role visibility (#2919)
* style: member role visibility

* fix: build errors
2023-11-28 17:11:04 +05:30
Bavisetti Narayan
3914a75334 chore: deactivated user workflow change (#2888)
* chore: deactivated user workflow change

* chore: removed archived and draft from v3 by default

* chore: draft and archive update

* chore: bool field

* chore: fall back workspace updated

* chore: workspace member active
2023-11-28 17:08:05 +05:30
Aaryan Khandelwal
0cbb201348 fix: workspace settings pages authorization (#2915)
* fix: workspace settings pages authorization

* chore: user cannot add a member with a higher role than theirs

* chore: update workspace general settings auth
2023-11-28 17:05:42 +05:30
sriram veeraghanta
495da01073 fix: using setup node v3 2023-11-28 16:58:31 +05:30
sriram veeraghanta
e5bb1e80cf fix: adding engines settings for docker setup 2023-11-28 16:49:59 +05:30
sriram veeraghanta
2eba0f8cdc fix: adding new yarn version in build script 2023-11-28 15:57:46 +05:30
sriram veeraghanta
efd2db5bf9 fix: enable corepack 2023-11-28 15:55:34 +05:30
sriram veeraghanta
e0e78903cd fix: updating setup-node v4 2023-11-28 15:47:52 +05:30
sriram veeraghanta
b0e0ab30e5 fix: upgraded package manager and implemented tsup config for ui package 2023-11-28 15:22:57 +05:30
rahulramesha
f7264364bd add functionality for addition of existing issues to modules and cycles (#2913)
Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>
2023-11-28 14:50:37 +05:30
Manish Gupta
35f8ffa5ab migration script added (#2914) 2023-11-28 13:29:08 +05:30
M. Palanikannan
6607caade7 fix: Image restoration fixed (marks/unmarks an image to be deleted after a week) (#2859)
* image restoration fixed (marks an image to be deleted after a week)

* removed clgs

* added image constraints

* formatted editor-core package using yarn format

* lite-text-editor nothing to format

* rich-text-editor nothing to format

* formatted document-editor with prettier

* modified file service to follow api change

* fixed more formatting in document editor

* fixed all instances of types with that from the package

* fixed delete to work consistently (minor optimizations turned off)

* stop duplicate images inside editor

* restore image on editor creation

say if user A deletes image number 2, user B was also in the same issue and in their screen the image was there, if user B makes certain changes and that gets saved in backend, according to user B image 2 should exist but since user A deleted it, it'll not get restored and get deleted in 7 days, hence I've added a check such that whenever a issue loads we restore all images by default

* added restore image function with types

* replaced all instances to have restore image logic

* fixed issue detail for peek view

* disabled option to insert table inside a table
2023-11-28 11:34:20 +05:30
Manish Gupta
8ee8270697 removed container names for selfhosting (#2907) 2023-11-28 11:31:04 +05:30
Aaryan Khandelwal
41ab962dd7 chore: update get invitation details endpoint (#2902) 2023-11-28 11:30:03 +05:30
Prateek Shourya
c22c6bb9b2 Fix: bug fixes and UI / UX improvements (#2906)
* Fix: issue with project publish modal data not updating immediately.

* fix: issue with workspace list not scrollable in profile settings.

* fix: update redirect workspace slug logic to redirect to prev workspace instead of `/`.

* style: update API tokens and webhooks empty state designs.
2023-11-28 11:29:01 +05:30
147 changed files with 13357 additions and 10040 deletions

View File

@@ -1,6 +1,6 @@
name: Build Pull Request Contents
on:
on:
pull_request:
types: ["opened", "synchronize"]
@@ -16,11 +16,15 @@ jobs:
uses: actions/checkout@v3.3.0
- name: Setup Node.js 18.x
uses: actions/setup-node@v2
uses: actions/setup-node@v4
with:
node-version: 18.x
cache: 'yarn'
- name: Install Pnpm
uses: pnpm/action-setup@v2
with:
version: 8.11.0
- name: Get changed files
id: changed-files
uses: tj-actions/changed-files@v38
@@ -44,5 +48,3 @@ jobs:
run: |
yarn
yarn build --filter=space

14
.gitignore vendored
View File

@@ -69,14 +69,18 @@ package-lock.json
# Sentry
.sentryclirc
# lock files
package-lock.json
pnpm-lock.yaml
pnpm-workspace.yaml
# yarn
.yarn
.yarnrc.yml
yarn.lock
# secrets
.npmrc
.secrets
# temp
tmp/
.temp/
## packages
dist
.temp/

View File

@@ -80,9 +80,18 @@ class UserMeSettingsSerializer(BaseSerializer):
workspace_invites = WorkspaceMemberInvite.objects.filter(
email=obj.email
).count()
if obj.last_workspace_id is not None:
if (
obj.last_workspace_id is not None
and Workspace.objects.filter(
pk=obj.last_workspace_id,
workspace_member__member=obj.id,
workspace_member__is_active=True,
).exists()
):
workspace = Workspace.objects.filter(
pk=obj.last_workspace_id, workspace_member__member=obj.id
pk=obj.last_workspace_id,
workspace_member__member=obj.id,
workspace_member__is_active=True,
).first()
return {
"last_workspace_id": obj.last_workspace_id,
@@ -95,7 +104,9 @@ class UserMeSettingsSerializer(BaseSerializer):
}
else:
fallback_workspace = (
Workspace.objects.filter(workspace_member__member_id=obj.id)
Workspace.objects.filter(
workspace_member__member_id=obj.id, workspace_member__is_active=True
)
.order_by("created_at")
.first()
)
@@ -159,10 +170,14 @@ class ChangePasswordSerializer(serializers.Serializer):
def validate(self, data):
if data.get("old_password") == data.get("new_password"):
raise serializers.ValidationError({"error": "New password cannot be same as old password."})
raise serializers.ValidationError(
{"error": "New password cannot be same as old password."}
)
if data.get("new_password") != data.get("confirm_password"):
raise serializers.ValidationError({"error": "Confirm password should be same as the new password."})
raise serializers.ValidationError(
{"error": "Confirm password should be same as the new password."}
)
return data

View File

@@ -4,6 +4,7 @@ import random
from itertools import chain
# Django imports
from django.db import models
from django.utils import timezone
from django.db.models import (
Prefetch,
@@ -367,6 +368,8 @@ class IssueListGroupedEndpoint(BaseAPIView):
def get(self, request, slug, project_id):
filters = issue_filters(request.query_params, "GET")
archive = request.GET.get("archived", False)
draft = request.GET.get("draft", False)
fields = [field for field in request.GET.get("fields", "").split(",") if field]
issue_queryset = (
@@ -384,6 +387,14 @@ class IssueListGroupedEndpoint(BaseAPIView):
)
)
.filter(**filters)
.filter(is_draft=bool(draft))
.filter(~Q(archived_at__isnull=bool(archive)))
.filter(
models.Q(issue_inbox__status=1)
| models.Q(issue_inbox__status=-1)
| models.Q(issue_inbox__status=2)
| models.Q(issue_inbox__isnull=True)
)
.annotate(cycle_id=F("issue_cycle__cycle_id"))
.annotate(module_id=F("issue_module__module_id"))
.annotate(

View File

@@ -113,6 +113,15 @@ class UserEndpoint(BaseViewSet):
# Deactivate the user
user.is_active = False
user.last_workspace_id = None
user.is_tour_completed = False
user.is_onboarded = False
user.onboarding_step = {
"workspace_join": False,
"profile_complete": False,
"workspace_create": False,
"workspace_invite": False,
}
user.save()
return Response(status=status.HTTP_204_NO_CONTENT)
@@ -135,9 +144,9 @@ class UpdateUserTourCompletedEndpoint(BaseAPIView):
class UserActivityEndpoint(BaseAPIView, BasePaginator):
def get(self, request):
queryset = IssueActivity.objects.filter(
actor=request.user
).select_related("actor", "workspace", "issue", "project")
queryset = IssueActivity.objects.filter(actor=request.user).select_related(
"actor", "workspace", "issue", "project"
)
return self.paginate(
request=request,

View File

@@ -113,7 +113,10 @@ class WorkSpaceViewSet(BaseViewSet):
return (
self.filter_queryset(super().get_queryset().select_related("owner"))
.order_by("name")
.filter(workspace_member__member=self.request.user)
.filter(
workspace_member__member=self.request.user,
workspace_member__is_active=True,
)
.annotate(total_members=member_count)
.annotate(total_issues=issue_count)
.select_related("owner")
@@ -189,17 +192,21 @@ class UserWorkSpacesEndpoint(BaseAPIView):
)
workspace = (
(
Workspace.objects.prefetch_related(
Prefetch("workspace_member", queryset=WorkspaceMember.objects.all())
Workspace.objects.prefetch_related(
Prefetch(
"workspace_member",
queryset=WorkspaceMember.objects.filter(
member=request.user, is_active=True
),
)
.filter(
workspace_member__member=request.user,
)
.select_related("owner")
)
.select_related("owner")
.annotate(total_members=member_count)
.annotate(total_issues=issue_count)
.filter(
workspace_member__member=request.user, workspace_member__is_active=True
)
.distinct()
)
serializer = WorkSpaceSerializer(self.filter_queryset(workspace), many=True)
@@ -319,7 +326,7 @@ class WorkspaceInvitationsViewset(BaseViewSet):
workspace_invitations, batch_size=10, ignore_conflicts=True
)
current_site = request.META.get('HTTP_ORIGIN')
current_site = request.META.get("HTTP_ORIGIN")
# Send invitations
for invitation in workspace_invitations:
@@ -418,7 +425,9 @@ class WorkspaceJoinEndpoint(BaseAPIView):
)
def get(self, request, slug, pk):
workspace_invitation = WorkspaceMemberInvite.objects.get(workspace__slug=slug, pk=pk)
workspace_invitation = WorkspaceMemberInvite.objects.get(
workspace__slug=slug, pk=pk
)
serializer = WorkSpaceMemberInviteSerializer(workspace_invitation)
return Response(serializer.data, status=status.HTTP_200_OK)
@@ -1313,9 +1322,7 @@ class WorkspaceUserProfileIssuesEndpoint(BaseAPIView):
return Response(issues, status=status.HTTP_200_OK)
class WorkspaceUserProfileIssuesGroupedEndpoint(BaseAPIView):
permission_classes = [
WorkspaceViewerPermission,
]
@@ -1362,13 +1369,16 @@ class WorkspaceUserProfileIssuesGroupedEndpoint(BaseAPIView):
)
).distinct()
issues = IssueLiteSerializer(issue_queryset, many=True, fields=fields if fields else None).data
issues = IssueLiteSerializer(
issue_queryset, many=True, fields=fields if fields else None
).data
issue_dict = {str(issue["id"]): issue for issue in issues}
return Response(
issue_dict,
status=status.HTTP_200_OK,
)
class WorkspaceLabelsEndpoint(BaseAPIView):
permission_classes = [
WorkspaceViewerPermission,

View File

@@ -102,7 +102,6 @@ services:
worker:
<<: *app-env
container_name: bgworker
platform: linux/amd64
image: makeplane/plane-backend:${APP_RELEASE:-latest}
restart: unless-stopped
@@ -114,7 +113,6 @@ services:
beat-worker:
<<: *app-env
container_name: beatworker
platform: linux/amd64
image: makeplane/plane-backend:${APP_RELEASE:-latest}
restart: unless-stopped
@@ -126,7 +124,6 @@ services:
plane-db:
<<: *app-env
container_name: plane-db
image: postgres:15.2-alpine
restart: unless-stopped
command: postgres -c 'max_connections=1000'
@@ -135,7 +132,6 @@ services:
plane-redis:
<<: *app-env
container_name: plane-redis
image: redis:6.2.7-alpine
restart: unless-stopped
volumes:
@@ -143,7 +139,6 @@ services:
plane-minio:
<<: *app-env
container_name: plane-minio
image: minio/minio
restart: unless-stopped
command: server /export --console-address ":9090"
@@ -153,7 +148,6 @@ services:
# Comment this if you already have a reverse proxy running
proxy:
<<: *app-env
container_name: proxy
platform: linux/amd64
image: makeplane/plane-proxy:${APP_RELEASE:-latest}
ports:

View File

@@ -0,0 +1,118 @@
#!/bin/bash
echo '
******************************************************************
This script is solely for the migration purpose only.
This is a 1 time migration of volume data from v0.13.2 => v0.14.x
Assumption:
1. Postgres data volume name ends with _pgdata
2. Minio data volume name ends with _uploads
3. Redis data volume name ends with _redisdata
Any changes to this script can break the migration.
Before you proceed, make sure you run the below command
to know the docker volumes
docker volume ls -q | grep -i "_pgdata"
docker volume ls -q | grep -i "_uploads"
docker volume ls -q | grep -i "_redisdata"
*******************************************************
'
DOWNLOAD_FOL=./download
rm -rf ${DOWNLOAD_FOL}
mkdir -p ${DOWNLOAD_FOL}
function volumeExists {
if [ "$(docker volume ls -f name=$1 | awk '{print $NF}' | grep -E '^'$1'$')" ]; then
return 0
else
return 1
fi
}
function readPrefixes(){
echo ''
echo 'Given below list of REDIS volumes, identify the prefix of source and destination volumes leaving "_redisdata" '
echo '---------------------'
docker volume ls -q | grep -i "_redisdata"
echo ''
read -p "Provide the Source Volume Prefix : " SRC_VOL_PREFIX
until [ "$SRC_VOL_PREFIX" ]; do
read -p "Provide the Source Volume Prefix : " SRC_VOL_PREFIX
done
read -p "Provide the Destination Volume Prefix : " DEST_VOL_PREFIX
until [ "$DEST_VOL_PREFIX" ]; do
read -p "Provide the Source Volume Prefix : " DEST_VOL_PREFIX
done
echo ''
echo 'Prefix Provided '
echo " Source : ${SRC_VOL_PREFIX}"
echo " Destination : ${DEST_VOL_PREFIX}"
echo '---------------------------------------'
}
function migrate(){
SRC_VOLUME=${SRC_VOL_PREFIX}_${VOL_NAME_SUFFIX}
DEST_VOLUME=${DEST_VOL_PREFIX}_${VOL_NAME_SUFFIX}
if volumeExists $SRC_VOLUME; then
if volumeExists $DEST_VOLUME; then
GOOD_TO_GO=1
else
echo "Destination Volume '$DEST_VOLUME' does not exist"
echo ''
fi
else
echo "Source Volume '$SRC_VOLUME' does not exist"
echo ''
fi
if [ $GOOD_TO_GO = 1 ]; then
echo "MIGRATING ${VOL_NAME_SUFFIX} FROM ${SRC_VOLUME} => ${DEST_VOLUME}"
TEMP_CONTAINER=$(docker run -d -v $SRC_VOLUME:$CONTAINER_VOL_FOLDER busybox true)
docker cp -q $TEMP_CONTAINER:$CONTAINER_VOL_FOLDER ${DOWNLOAD_FOL}/${VOL_NAME_SUFFIX}
docker rm $TEMP_CONTAINER &> /dev/null
TEMP_CONTAINER=$(docker run -d -v $DEST_VOLUME:$CONTAINER_VOL_FOLDER busybox true)
if [ "$VOL_NAME_SUFFIX" = "pgdata" ]; then
docker cp -q ${DOWNLOAD_FOL}/${VOL_NAME_SUFFIX} $TEMP_CONTAINER:$CONTAINER_VOL_FOLDER/_temp
docker run --rm -v $DEST_VOLUME:$CONTAINER_VOL_FOLDER \
-e DATA_FOLDER="${CONTAINER_VOL_FOLDER}" \
busybox /bin/sh -c 'cp -Rf $DATA_FOLDER/_temp/* $DATA_FOLDER '
else
docker cp -q ${DOWNLOAD_FOL}/${VOL_NAME_SUFFIX} $TEMP_CONTAINER:$CONTAINER_VOL_FOLDER
fi
docker rm $TEMP_CONTAINER &> /dev/null
echo ''
fi
}
readPrefixes
# MIGRATE DB
CONTAINER_VOL_FOLDER=/var/lib/postgresql/data
VOL_NAME_SUFFIX=pgdata
migrate
# MIGRATE REDIS
CONTAINER_VOL_FOLDER=/data
VOL_NAME_SUFFIX=redisdata
migrate
# MIGRATE MINIO
CONTAINER_VOL_FOLDER=/export
VOL_NAME_SUFFIX=uploads
migrate

View File

@@ -3,18 +3,13 @@
"version": "0.13.2",
"license": "AGPL-3.0",
"private": true,
"workspaces": [
"web",
"space",
"packages/editor/*",
"packages/eslint-config-custom",
"packages/tailwind-config-custom",
"packages/tsconfig",
"packages/ui"
],
"scripts": {
"build": "turbo run build",
"build:web": "turbo run build --filter=web",
"build:space": "turbo run build --filter=space",
"dev": "turbo run dev",
"dev:web": "turbo run dev --filter=web",
"dev:space": "turbo run dev --filter=space",
"start": "turbo run start",
"lint": "turbo run lint",
"clean": "turbo run clean",
@@ -22,15 +17,18 @@
},
"devDependencies": {
"autoprefixer": "^10.4.15",
"eslint-config-custom": "*",
"postcss": "^8.4.29",
"eslint-config-custom": "workspace:*",
"postcss": "^8.4.31",
"prettier": "latest",
"prettier-plugin-tailwindcss": "^0.5.4",
"tailwindcss": "^3.3.3",
"tailwindcss": "^3.3.5",
"turbo": "^1.10.16"
},
"resolutions": {
"@types/react": "18.2.0"
"@types/react": "^18.2.39"
},
"packageManager": "yarn@1.22.19"
"engines": {
"node": ">=18"
},
"packageManager": "pnpm@8.11.0"
}

View File

@@ -19,7 +19,8 @@
"scripts": {
"build": "tsup",
"dev": "tsup --watch",
"check-types": "tsc --noEmit"
"check-types": "tsc --noEmit",
"format": "prettier --write \"**/*.{ts,tsx,md}\""
},
"peerDependencies": {
"next": "12.3.2",
@@ -27,6 +28,7 @@
"react-dom": "18.2.0"
},
"dependencies": {
"@plane/editor-types": "workspace:*",
"@tiptap/core": "^2.1.7",
"@tiptap/extension-code-block-lowlight": "^2.1.12",
"@tiptap/extension-color": "^2.1.11",
@@ -59,14 +61,14 @@
"tiptap-markdown": "^0.8.2"
},
"devDependencies": {
"eslint": "^7.32.0",
"postcss": "^8.4.29",
"eslint-config-next": "13.2.4",
"@types/node": "18.15.3",
"@types/react": "^18.2.5",
"@types/react-dom": "18.0.11",
"tailwind-config-custom": "*",
"tsconfig": "*",
"eslint": "^7.32.0",
"eslint-config-next": "13.2.4",
"postcss": "^8.4.31",
"tailwind-config-custom": "workspace:*",
"tsconfig": "workspace:*",
"tsup": "^7.2.0",
"typescript": "4.9.5"
},

View File

@@ -1,6 +1,7 @@
import { UploadImage } from "@plane/editor-types";
import { Editor, Range } from "@tiptap/core";
import { UploadImage } from "../types/upload-image";
import { startImageUpload } from "../ui/plugins/upload-image";
import { findTableAncestor } from "./utils";
export const toggleHeadingOne = (editor: Editor, range?: Range) => {
if (range)
@@ -95,6 +96,15 @@ export const toggleBlockquote = (editor: Editor, range?: Range) => {
};
export const insertTableCommand = (editor: Editor, range?: Range) => {
if (typeof window !== "undefined") {
const selection: any = window?.getSelection();
if (selection.rangeCount !== 0) {
const range = selection.getRangeAt(0);
if (findTableAncestor(range.startContainer)) {
return;
}
}
}
if (range)
editor
.chain()

View File

@@ -1 +0,0 @@
export type DeleteImage = (assetUrlWithWorkspaceId: string) => Promise<any>;

View File

@@ -1,10 +0,0 @@
export type IMentionSuggestion = {
id: string;
type: string;
avatar: string;
title: string;
subtitle: string;
redirect_uri: string;
};
export type IMentionHighlight = string;

View File

@@ -1 +0,0 @@
export type UploadImage = (file: File) => Promise<string>;

View File

@@ -1,30 +1,33 @@
import { getNodeType } from '@tiptap/core'
import { NodeType } from '@tiptap/pm/model'
import { EditorState } from '@tiptap/pm/state'
import { getNodeType } from "@tiptap/core";
import { NodeType } from "@tiptap/pm/model";
import { EditorState } from "@tiptap/pm/state";
export const findListItemPos = (typeOrName: string | NodeType, state: EditorState) => {
const { $from } = state.selection
const nodeType = getNodeType(typeOrName, state.schema)
export const findListItemPos = (
typeOrName: string | NodeType,
state: EditorState,
) => {
const { $from } = state.selection;
const nodeType = getNodeType(typeOrName, state.schema);
let currentNode = null
let currentDepth = $from.depth
let currentPos = $from.pos
let targetDepth: number | null = null
let currentNode = null;
let currentDepth = $from.depth;
let currentPos = $from.pos;
let targetDepth: number | null = null;
while (currentDepth > 0 && targetDepth === null) {
currentNode = $from.node(currentDepth)
currentNode = $from.node(currentDepth);
if (currentNode.type === nodeType) {
targetDepth = currentDepth
targetDepth = currentDepth;
} else {
currentDepth -= 1
currentPos -= 1
currentDepth -= 1;
currentPos -= 1;
}
}
if (targetDepth === null) {
return null
return null;
}
return { $pos: state.doc.resolve(currentPos), depth: targetDepth }
}
return { $pos: state.doc.resolve(currentPos), depth: targetDepth };
};

View File

@@ -1,15 +1,19 @@
import { EditorState } from '@tiptap/pm/state'
import { EditorState } from "@tiptap/pm/state";
export const hasListBefore = (editorState: EditorState, name: string, parentListTypes: string[]) => {
const { $anchor } = editorState.selection
export const hasListBefore = (
editorState: EditorState,
name: string,
parentListTypes: string[],
) => {
const { $anchor } = editorState.selection;
const previousNodePos = Math.max(0, $anchor.pos - 2)
const previousNodePos = Math.max(0, $anchor.pos - 2);
const previousNode = editorState.doc.resolve(previousNodePos).node()
const previousNode = editorState.doc.resolve(previousNodePos).node();
if (!previousNode || !parentListTypes.includes(previousNode.type.name)) {
return false
return false;
}
return true
}
return true;
};

View File

@@ -1,17 +1,20 @@
import { EditorState } from '@tiptap/pm/state'
import { EditorState } from "@tiptap/pm/state";
export const hasListItemAfter = (typeOrName: string, state: EditorState): boolean => {
const { $anchor } = state.selection
export const hasListItemAfter = (
typeOrName: string,
state: EditorState,
): boolean => {
const { $anchor } = state.selection;
const $targetPos = state.doc.resolve($anchor.pos - $anchor.parentOffset - 2)
const $targetPos = state.doc.resolve($anchor.pos - $anchor.parentOffset - 2);
if ($targetPos.index() === $targetPos.parent.childCount - 1) {
return false
return false;
}
if ($targetPos.nodeAfter?.type.name !== typeOrName) {
return false
return false;
}
return true
}
return true;
};

View File

@@ -1,17 +1,20 @@
import { EditorState } from '@tiptap/pm/state'
import { EditorState } from "@tiptap/pm/state";
export const hasListItemBefore = (typeOrName: string, state: EditorState): boolean => {
const { $anchor } = state.selection
export const hasListItemBefore = (
typeOrName: string,
state: EditorState,
): boolean => {
const { $anchor } = state.selection;
const $targetPos = state.doc.resolve($anchor.pos - 2)
const $targetPos = state.doc.resolve($anchor.pos - 2);
if ($targetPos.index() === 0) {
return false
return false;
}
if ($targetPos.nodeBefore?.type.name !== typeOrName) {
return false
return false;
}
return true
}
return true;
};

View File

@@ -1,19 +1,135 @@
import Image from "@tiptap/extension-image";
import TrackImageDeletionPlugin from "../../plugins/delete-image";
import { EditorState, Plugin, PluginKey, Transaction } from "@tiptap/pm/state";
import { Node as ProseMirrorNode } from "@tiptap/pm/model";
import UploadImagesPlugin from "../../plugins/upload-image";
import { DeleteImage } from "../../../types/delete-image";
import ImageExt from "@tiptap/extension-image";
import { onNodeDeleted, onNodeRestored } from "../../plugins/delete-image";
import { DeleteImage, RestoreImage } from "@plane/editor-types";
interface ImageNode extends ProseMirrorNode {
attrs: {
src: string;
id: string;
};
}
const deleteKey = new PluginKey("delete-image");
const IMAGE_NODE_TYPE = "image";
const ImageExtension = (
deleteImage: DeleteImage,
restoreFile: RestoreImage,
cancelUploadImage?: () => any,
) =>
Image.extend({
ImageExt.extend({
addProseMirrorPlugins() {
return [
UploadImagesPlugin(cancelUploadImage),
TrackImageDeletionPlugin(deleteImage),
new Plugin({
key: deleteKey,
appendTransaction: (
transactions: readonly Transaction[],
oldState: EditorState,
newState: EditorState,
) => {
const newImageSources = new Set<string>();
newState.doc.descendants((node) => {
if (node.type.name === IMAGE_NODE_TYPE) {
newImageSources.add(node.attrs.src);
}
});
transactions.forEach((transaction) => {
// transaction could be a selection
if (!transaction.docChanged) return;
const removedImages: ImageNode[] = [];
// iterate through all the nodes in the old state
oldState.doc.descendants((oldNode, oldPos) => {
// if the node is not an image, then return as no point in checking
if (oldNode.type.name !== IMAGE_NODE_TYPE) return;
// Check if the node has been deleted or replaced
if (!newImageSources.has(oldNode.attrs.src)) {
removedImages.push(oldNode as ImageNode);
}
});
removedImages.forEach(async (node) => {
const src = node.attrs.src;
this.storage.images.set(src, true);
await onNodeDeleted(src, deleteImage);
});
});
return null;
},
}),
new Plugin({
key: new PluginKey("imageRestoration"),
appendTransaction: (
transactions: readonly Transaction[],
oldState: EditorState,
newState: EditorState,
) => {
const oldImageSources = new Set<string>();
oldState.doc.descendants((node) => {
if (node.type.name === IMAGE_NODE_TYPE) {
oldImageSources.add(node.attrs.src);
}
});
transactions.forEach((transaction) => {
if (!transaction.docChanged) return;
const addedImages: ImageNode[] = [];
newState.doc.descendants((node, pos) => {
if (node.type.name !== IMAGE_NODE_TYPE) return;
if (pos < 0 || pos > newState.doc.content.size) return;
if (oldImageSources.has(node.attrs.src)) return;
addedImages.push(node as ImageNode);
});
addedImages.forEach(async (image) => {
const wasDeleted = this.storage.images.get(image.attrs.src);
if (wasDeleted === undefined) {
this.storage.images.set(image.attrs.src, false);
} else if (wasDeleted === true) {
await onNodeRestored(image.attrs.src, restoreFile);
}
});
});
return null;
},
}),
];
},
onCreate(this) {
const imageSources = new Set<string>();
this.editor.state.doc.descendants((node) => {
if (node.type.name === IMAGE_NODE_TYPE) {
imageSources.add(node.attrs.src);
}
});
imageSources.forEach(async (src) => {
try {
const assetUrlWithWorkspaceId = new URL(src).pathname.substring(1);
await restoreFile(assetUrlWithWorkspaceId);
} catch (error) {
console.error("Error restoring image: ", error);
}
});
},
// storage to keep track of image states Map<src, isDeleted>
addStorage() {
return {
images: new Map<string, boolean>(),
};
},
addAttributes() {
return {
...this.parent?.(),

View File

@@ -15,14 +15,17 @@ import HorizontalRule from "./horizontal-rule";
import ImageExtension from "./image";
import { DeleteImage } from "../../types/delete-image";
import { isValidHttpUrl } from "../../lib/utils";
import { IMentionSuggestion } from "../../types/mention-suggestion";
import { Mentions } from "../mentions";
import { CustomKeymap } from "./keymap";
import { CustomCodeBlock } from "./code";
import { ListKeymap } from "./custom-list-keymap";
import {
IMentionSuggestion,
DeleteImage,
RestoreImage,
} from "@plane/editor-types";
export const CoreEditorExtensions = (
mentionConfig: {
@@ -30,6 +33,7 @@ export const CoreEditorExtensions = (
mentionHighlights: string[];
},
deleteFile: DeleteImage,
restoreFile: RestoreImage,
cancelUploadImage?: () => any,
) => [
StarterKit.configure({
@@ -71,7 +75,7 @@ export const CoreEditorExtensions = (
"text-custom-primary-300 underline underline-offset-[3px] hover:text-custom-primary-500 transition-colors cursor-pointer",
},
}),
ImageExtension(deleteFile, cancelUploadImage).configure({
ImageExtension(deleteFile, restoreFile, cancelUploadImage).configure({
HTMLAttributes: {
class: "rounded-lg border border-custom-border-300",
},

View File

@@ -1,22 +1,27 @@
import { useEditor as useCustomEditor, Editor } from "@tiptap/react";
import { useImperativeHandle, useRef, MutableRefObject } from "react";
import { DeleteImage } from "../../types/delete-image";
import { CoreEditorProps } from "../props";
import { CoreEditorExtensions } from "../extensions";
import { EditorProps } from "@tiptap/pm/view";
import { getTrimmedHTML } from "../../lib/utils";
import { UploadImage } from "../../types/upload-image";
import { useInitializedContent } from "./useInitializedContent";
import { IMentionSuggestion } from "../../types/mention-suggestion";
import {
DeleteImage,
IMentionSuggestion,
RestoreImage,
UploadImage,
} from "@plane/editor-types";
interface CustomEditorProps {
uploadFile: UploadImage;
restoreFile: RestoreImage;
deleteFile: DeleteImage;
cancelUploadImage?: () => any;
setIsSubmitting?: (
isSubmitting: "submitting" | "submitted" | "saved",
) => void;
setShouldShowAlert?: (showAlert: boolean) => void;
value: string;
deleteFile: DeleteImage;
debouncedUpdatesEnabled?: boolean;
onStart?: (json: any, html: string) => void;
onChange?: (json: any, html: string) => void;
@@ -25,7 +30,6 @@ interface CustomEditorProps {
forwardedRef?: any;
mentionHighlights?: string[];
mentionSuggestions?: IMentionSuggestion[];
cancelUploadImage?: () => any;
}
export const useEditor = ({
@@ -39,6 +43,7 @@ export const useEditor = ({
onChange,
setIsSubmitting,
forwardedRef,
restoreFile,
setShouldShowAlert,
mentionHighlights,
mentionSuggestions,
@@ -56,6 +61,7 @@ export const useEditor = ({
mentionHighlights: mentionHighlights ?? [],
},
deleteFile,
restoreFile,
cancelUploadImage,
),
...extensions,
@@ -63,7 +69,7 @@ export const useEditor = ({
content:
typeof value === "string" && value.trim() !== "" ? value : "<p></p>",
onCreate: async ({ editor }) => {
onStart?.(editor.getJSON(), getTrimmedHTML(editor.getHTML()))
onStart?.(editor.getJSON(), getTrimmedHTML(editor.getHTML()));
},
onUpdate: async ({ editor }) => {
// for instant feedback loop

View File

@@ -8,7 +8,7 @@ import {
import { CoreReadOnlyEditorExtensions } from "../../ui/read-only/extensions";
import { CoreReadOnlyEditorProps } from "../../ui/read-only/props";
import { EditorProps } from "@tiptap/pm/view";
import { IMentionSuggestion } from "../../types/mention-suggestion";
import { IMentionSuggestion } from "@plane/editor-types";
interface CustomReadOnlyEditorProps {
value: string;

View File

@@ -1,14 +1,16 @@
"use client";
import * as React from "react";
import { Extension } from "@tiptap/react";
import { UploadImage } from "../types/upload-image";
import { DeleteImage } from "../types/delete-image";
import { getEditorClassNames } from "../lib/utils";
import { EditorProps } from "@tiptap/pm/view";
import { useEditor } from "./hooks/useEditor";
import { EditorContainer } from "../ui/components/editor-container";
import { EditorContentWrapper } from "../ui/components/editor-content";
import { IMentionSuggestion } from "../types/mention-suggestion";
import {
UploadImage,
DeleteImage,
IMentionSuggestion,
} from "@plane/editor-types";
interface ICoreEditor {
value: string;

View File

@@ -1,3 +1,4 @@
import { IMentionSuggestion } from "@plane/editor-types";
import { Editor } from "@tiptap/react";
import React, {
forwardRef,
@@ -7,8 +8,6 @@ import React, {
useState,
} from "react";
import { IMentionSuggestion } from "../../types/mention-suggestion";
interface MentionListProps {
items: IMentionSuggestion[];
command: (item: {

View File

@@ -2,7 +2,8 @@ import { Mention, MentionOptions } from "@tiptap/extension-mention";
import { mergeAttributes } from "@tiptap/core";
import { ReactNodeViewRenderer } from "@tiptap/react";
import mentionNodeView from "./mentionNodeView";
import { IMentionHighlight } from "../../types/mention-suggestion";
import { IMentionHighlight } from "@plane/editor-types";
export interface CustomMentionOptions extends MentionOptions {
mentionHighlights: IMentionHighlight[];
readonly?: boolean;

View File

@@ -2,10 +2,7 @@
import suggestion from "./suggestion";
import { CustomMention } from "./custom";
import {
IMentionHighlight,
IMentionSuggestion,
} from "../../types/mention-suggestion";
import { IMentionHighlight, IMentionSuggestion } from "@plane/editor-types";
export const Mentions = (
mentionSuggestions: IMentionSuggestion[],

View File

@@ -3,7 +3,7 @@
import { NodeViewWrapper } from "@tiptap/react";
import { cn } from "../../lib/utils";
import { useRouter } from "next/router";
import { IMentionHighlight } from "../../types/mention-suggestion";
import { IMentionHighlight } from "@plane/editor-types";
// eslint-disable-next-line import/no-anonymous-default-export
export default (props) => {

View File

@@ -3,7 +3,7 @@ import { Editor } from "@tiptap/core";
import tippy from "tippy.js";
import MentionList from "./MentionList";
import { IMentionSuggestion } from "../../types/mention-suggestion";
import { IMentionSuggestion } from "@plane/editor-types";
const Suggestion = (suggestions: IMentionSuggestion[]) => ({
items: ({ query }: { query: string }) =>

View File

@@ -15,7 +15,6 @@ import {
CodeIcon,
} from "lucide-react";
import { Editor } from "@tiptap/react";
import { UploadImage } from "../../../types/upload-image";
import {
insertImageCommand,
insertTableCommand,
@@ -32,6 +31,7 @@ import {
toggleTaskList,
toggleUnderline,
} from "../../../lib/editor-commands";
import { UploadImage } from "@plane/editor-types";
export interface EditorMenuItem {
name: string;

View File

@@ -1,6 +1,6 @@
import { EditorState, Plugin, PluginKey, Transaction } from "@tiptap/pm/state";
import { Node as ProseMirrorNode } from "@tiptap/pm/model";
import { DeleteImage } from "../../types/delete-image";
import { DeleteImage, RestoreImage } from "@plane/editor-types";
const deleteKey = new PluginKey("delete-image");
const IMAGE_NODE_TYPE = "image";
@@ -59,7 +59,7 @@ const TrackImageDeletionPlugin = (deleteImage: DeleteImage): Plugin =>
export default TrackImageDeletionPlugin;
async function onNodeDeleted(
export async function onNodeDeleted(
src: string,
deleteImage: DeleteImage,
): Promise<void> {
@@ -73,3 +73,18 @@ async function onNodeDeleted(
console.error("Error deleting image: ", error);
}
}
export async function onNodeRestored(
src: string,
restoreImage: RestoreImage,
): Promise<void> {
try {
const assetUrlWithWorkspaceId = new URL(src).pathname.substring(1);
const resStatus = await restoreImage(assetUrlWithWorkspaceId);
if (resStatus === 204) {
console.log("Image restored successfully");
}
} catch (error) {
console.error("Error restoring image: ", error);
}
}

View File

@@ -1,4 +1,4 @@
import { UploadImage } from "../../types/upload-image";
import { UploadImage } from "@plane/editor-types";
import { EditorState, Plugin, PluginKey } from "@tiptap/pm/state";
import { Decoration, DecorationSet, EditorView } from "@tiptap/pm/view";

View File

@@ -1,7 +1,7 @@
import { UploadImage } from "@plane/editor-types";
import { EditorProps } from "@tiptap/pm/view";
import { findTableAncestor } from "../lib/utils";
import { startImageUpload } from "./plugins/upload-image";
import { UploadImage } from "../types/upload-image";
export function CoreEditorProps(
uploadFile: UploadImage,
@@ -82,5 +82,8 @@ export function CoreEditorProps(
}
return false;
},
transformPastedHTML(html) {
return html.replace(/<img.*?>/g, "");
},
};
}

View File

@@ -16,7 +16,7 @@ import TableRow from "../extensions/table/table-row/table-row";
import ReadOnlyImageExtension from "../extensions/image/read-only-image";
import { isValidHttpUrl } from "../../lib/utils";
import { Mentions } from "../mentions";
import { IMentionSuggestion } from "../../types/mention-suggestion";
import { IMentionSuggestion } from "@plane/editor-types";
export const CoreReadOnlyEditorExtensions = (mentionConfig: {
mentionSuggestions: IMentionSuggestion[];

View File

@@ -1 +1 @@
# Document Editor
# Document Editor

View File

@@ -18,7 +18,8 @@
"scripts": {
"build": "tsup",
"dev": "tsup --watch",
"check-types": "tsc --noEmit"
"check-types": "tsc --noEmit",
"format": "prettier --write \"**/*.{ts,tsx,md}\""
},
"peerDependencies": {
"next": "12.3.2",
@@ -27,9 +28,10 @@
"react-dom": "18.2.0"
},
"dependencies": {
"@plane/ui": "*",
"@plane/editor-core": "*",
"@plane/editor-extensions": "*",
"@plane/editor-core": "workspace:*",
"@plane/editor-extensions": "workspace:*",
"@plane/editor-types": "workspace:*",
"@plane/ui": "workspace:*",
"@tiptap/core": "^2.1.7",
"@tiptap/extension-placeholder": "^2.1.11",
"@types/node": "18.15.3",
@@ -41,9 +43,9 @@
},
"devDependencies": {
"eslint": "^7.32.0",
"postcss": "^8.4.29",
"tailwind-config-custom": "*",
"tsconfig": "*",
"postcss": "^8.4.31",
"tailwind-config-custom": "workspace:*",
"tsconfig": "workspace:*",
"tsup": "^7.2.0",
"typescript": "4.9.5"
},

View File

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

View File

@@ -3,31 +3,34 @@ import { useState } from "react";
import { IMarking } from "..";
export const useEditorMarkings = () => {
const [markings, setMarkings] = useState<IMarking[]>([])
const [markings, setMarkings] = useState<IMarking[]>([]);
const updateMarkings = (json: any) => {
const nodes = json.content as any[]
const tempMarkings: IMarking[] = []
let h1Sequence: number = 0
let h2Sequence: number = 0
const nodes = json.content as any[];
const tempMarkings: IMarking[] = [];
let h1Sequence: number = 0;
let h2Sequence: number = 0;
if (nodes) {
nodes.forEach((node) => {
if (node.type === "heading" && (node.attrs.level === 1 || node.attrs.level === 2) && node.content) {
if (
node.type === "heading" &&
(node.attrs.level === 1 || node.attrs.level === 2) &&
node.content
) {
tempMarkings.push({
type: "heading",
level: node.attrs.level,
text: node.content[0].text,
sequence: node.attrs.level === 1 ? ++h1Sequence : ++h2Sequence
})
sequence: node.attrs.level === 1 ? ++h1Sequence : ++h2Sequence,
});
}
})
});
}
setMarkings(tempMarkings)
}
setMarkings(tempMarkings);
};
return {
updateMarkings,
markings,
}
}
return {
updateMarkings,
markings,
};
};

View File

@@ -14,15 +14,14 @@ import { DocumentDetails } from "./types/editor-types";
import { PageRenderer } from "./components/page-renderer";
import { getMenuOptions } from "./utils/menu-options";
import { useRouter } from "next/router";
export type UploadImage = (file: File) => Promise<string>;
export type DeleteImage = (assetUrlWithWorkspaceId: string) => Promise<any>;
import { UploadImage, DeleteImage, RestoreImage } from "@plane/editor-types";
interface IDocumentEditor {
documentDetails: DocumentDetails;
value: string;
uploadFile: UploadImage;
deleteFile: DeleteImage;
restoreFile: RestoreImage;
customClassName?: string;
editorContentCustomClassNames?: string;
onChange: (json: any, html: string) => void;
@@ -62,6 +61,7 @@ const DocumentEditor = ({
value,
uploadFile,
deleteFile,
restoreFile,
customClassName,
forwardedRef,
duplicationConfig,
@@ -82,6 +82,7 @@ const DocumentEditor = ({
updateMarkings(json);
},
debouncedUpdatesEnabled,
restoreFile,
setIsSubmitting,
setShouldShowAlert,
value,

View File

@@ -19,7 +19,7 @@ import {
HeadingThreeItem,
findTableAncestor,
} from "@plane/editor-core";
import { UploadImage } from "..";
import { UploadImage } from "@plane/editor-types";
export interface BubbleMenuItem {
name: string;

View File

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

View File

@@ -1 +1 @@
export { FixedMenu } from "./fixed-menu";
export { FixedMenu } from "./fixed-menu";

View File

@@ -1,4 +1,4 @@
import * as React from 'react';
import * as React from "react";
// next-themes
import { useTheme } from "next-themes";
@@ -69,8 +69,16 @@ export const Tooltip: React.FC<Props> = ({
</div>
}
position={position}
renderTarget={({ isOpen: isTooltipOpen, ref: eleReference, ...tooltipProps }) =>
React.cloneElement(children, { ref: eleReference, ...tooltipProps, ...children.props })
renderTarget={({
isOpen: isTooltipOpen,
ref: eleReference,
...tooltipProps
}) =>
React.cloneElement(children, {
ref: eleReference,
...tooltipProps,
...children.props,
})
}
/>
);

View File

@@ -1,8 +1,7 @@
export interface DocumentDetails {
title: string;
created_by: string;
created_on: Date;
last_updated_by: string;
last_updated_at: Date;
created_by: string;
created_on: Date;
last_updated_by: string;
last_updated_at: Date;
}

View File

@@ -1,14 +1,13 @@
export interface IDuplicationConfig {
action: () => Promise<void>
action: () => Promise<void>;
}
export interface IPageLockConfig {
is_locked: boolean,
action: () => Promise<void>
locked_by?: string,
is_locked: boolean;
action: () => Promise<void>;
locked_by?: string;
}
export interface IPageArchiveConfig {
is_archived: boolean,
archived_at?: Date,
action: () => Promise<void>
}
is_archived: boolean;
archived_at?: Date;
action: () => Promise<void>;
}

View File

@@ -2,34 +2,33 @@ import { Editor } from "@tiptap/react";
import { IMarking } from "..";
function findNthH1(editor: Editor, n: number, level: number): number {
let count = 0;
let pos = 0;
editor.state.doc.descendants((node, position) => {
if (node.type.name === 'heading' && node.attrs.level === level) {
count++;
if (count === n) {
pos = position;
return false;
}
}
});
return pos;
}
function scrollToNode(editor: Editor, pos: number): void {
const headingNode = editor.state.doc.nodeAt(pos);
if (headingNode) {
const headingDOM = editor.view.nodeDOM(pos);
if (headingDOM instanceof HTMLElement) {
headingDOM.scrollIntoView({ behavior: 'smooth' });
let count = 0;
let pos = 0;
editor.state.doc.descendants((node, position) => {
if (node.type.name === "heading" && node.attrs.level === level) {
count++;
if (count === n) {
pos = position;
return false;
}
}
}
});
return pos;
}
export function scrollSummary(editor: Editor, marking: IMarking) {
if (editor) {
const pos = findNthH1(editor, marking.sequence, marking.level)
scrollToNode(editor, pos)
function scrollToNode(editor: Editor, pos: number): void {
const headingNode = editor.state.doc.nodeAt(pos);
if (headingNode) {
const headingDOM = editor.view.nodeDOM(pos);
if (headingDOM instanceof HTMLElement) {
headingDOM.scrollIntoView({ behavior: "smooth" });
}
}
}
export function scrollSummary(editor: Editor, marking: IMarking) {
if (editor) {
const pos = findNthH1(editor, marking.sequence, marking.level);
scrollToNode(editor, pos);
}
}

View File

@@ -1,12 +1,12 @@
import { Editor } from "@tiptap/core"
import { Editor } from "@tiptap/core";
export const copyMarkdownToClipboard = (editor: Editor | null) => {
const markdownOutput = editor?.storage.markdown.getMarkdown();
navigator.clipboard.writeText(markdownOutput)
}
navigator.clipboard.writeText(markdownOutput);
};
export const CopyPageLink = () => {
if (window){
navigator.clipboard.writeText(window.location.toString())
if (window) {
navigator.clipboard.writeText(window.location.toString());
}
}
};

View File

@@ -28,25 +28,25 @@
"react-dom": "18.2.0"
},
"dependencies": {
"@tiptap/react": "^2.1.7",
"@plane/editor-core": "workspace:*",
"@plane/editor-types": "workspace:*",
"@tiptap/core": "^2.1.7",
"@tiptap/pm": "^2.1.7",
"@tiptap/react": "^2.1.7",
"@tiptap/suggestion": "^2.0.4",
"@plane/editor-types": "*",
"@plane/editor-core": "*",
"eslint": "8.36.0",
"eslint-config-next": "13.2.4",
"lucide-react": "^0.244.0",
"tippy.js": "^6.3.7",
"@tiptap/pm": "^2.1.7"
"tippy.js": "^6.3.7"
},
"devDependencies": {
"@types/node": "18.15.3",
"@types/react": "^18.2.35",
"@types/react-dom": "^18.2.14",
"eslint": "^7.32.0",
"postcss": "^8.4.29",
"tailwind-config-custom": "*",
"tsconfig": "*",
"postcss": "^8.4.31",
"tailwind-config-custom": "workspace:*",
"tsconfig": "workspace:*",
"tsup": "^7.2.0",
"typescript": "4.9.5"
},

View File

@@ -19,7 +19,8 @@
"scripts": {
"build": "tsup",
"dev": "tsup --watch",
"check-types": "tsc --noEmit"
"check-types": "tsc --noEmit",
"format": "prettier --write \"**/*.{ts,tsx,md}\""
},
"peerDependencies": {
"next": "12.3.2",
@@ -28,18 +29,19 @@
"react-dom": "18.2.0"
},
"dependencies": {
"@plane/editor-core": "*",
"@plane/ui": "*"
"@plane/editor-core": "workspace:*",
"@plane/editor-types": "workspace:*",
"@plane/ui": "workspace:*"
},
"devDependencies": {
"@types/node": "18.15.3",
"@types/react": "^18.2.35",
"@types/react-dom": "^18.2.14",
"eslint": "^7.32.0",
"postcss": "^8.4.29",
"tailwind-config-custom": "*",
"eslint-config-custom": "*",
"tsconfig": "*",
"eslint-config-custom": "workspace:*",
"postcss": "^8.4.31",
"tailwind-config-custom": "workspace:*",
"tsconfig": "workspace:*",
"tsup": "^7.2.0",
"typescript": "4.9.5"
},

View File

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

View File

@@ -7,24 +7,19 @@ import {
} from "@plane/editor-core";
import { FixedMenu } from "./menus/fixed-menu";
import { LiteTextEditorExtensions } from "./extensions";
export type UploadImage = (file: File) => Promise<string>;
export type DeleteImage = (assetUrlWithWorkspaceId: string) => Promise<any>;
export type IMentionSuggestion = {
id: string;
type: string;
avatar: string;
title: string;
subtitle: string;
redirect_uri: string;
};
export type IMentionHighlight = string;
import {
UploadImage,
DeleteImage,
IMentionSuggestion,
RestoreImage,
} from "@plane/editor-types";
interface ILiteTextEditor {
value: string;
uploadFile: UploadImage;
deleteFile: DeleteImage;
restoreFile: RestoreImage;
noBorder?: boolean;
borderOnFocus?: boolean;
customClassName?: string;
@@ -73,6 +68,7 @@ const LiteTextEditor = (props: LiteTextEditorProps) => {
value,
uploadFile,
deleteFile,
restoreFile,
noBorder,
borderOnFocus,
customClassName,
@@ -93,6 +89,7 @@ const LiteTextEditor = (props: LiteTextEditorProps) => {
value,
uploadFile,
deleteFile,
restoreFile,
forwardedRef,
extensions: LiteTextEditorExtensions(onEnterKeyPress),
mentionHighlights,

View File

@@ -16,11 +16,12 @@ import {
UnderLineItem,
} from "@plane/editor-core";
import { Tooltip } from "@plane/ui";
import { UploadImage } from "../../";
import type { SVGProps } from "react";
import { UploadImage } from "@plane/editor-types";
interface LucideProps extends Partial<SVGProps<SVGSVGElement>> {
size?: string | number
absoluteStrokeWidth?: boolean
size?: string | number;
absoluteStrokeWidth?: boolean;
}
type LucideIcon = (props: LucideProps) => JSX.Element;

View File

@@ -19,7 +19,8 @@
"scripts": {
"build": "tsup",
"dev": "tsup --watch",
"check-types": "tsc --noEmit"
"check-types": "tsc --noEmit",
"format": "prettier --write \"**/*.{ts,tsx,md}\""
},
"peerDependencies": {
"next": "12.3.2",
@@ -28,9 +29,10 @@
"react-dom": "18.2.0"
},
"dependencies": {
"@plane/editor-core": "*",
"@plane/editor-core": "workspace:*",
"@plane/editor-extensions": "workspace:*",
"@plane/editor-types": "workspace:*",
"@tiptap/core": "^2.1.11",
"@plane/editor-extensions": "*",
"@tiptap/extension-placeholder": "^2.1.11",
"lucide-react": "^0.244.0"
},
@@ -39,10 +41,10 @@
"@types/react": "^18.2.35",
"@types/react-dom": "^18.2.14",
"eslint": "^7.32.0",
"postcss": "^8.4.29",
"postcss": "^8.4.31",
"react": "^18.2.0",
"tailwind-config-custom": "*",
"tsconfig": "*",
"tailwind-config-custom": "workspace:*",
"tsconfig": "workspace:*",
"tsup": "^7.2.0",
"typescript": "4.9.5"
},

View File

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

View File

@@ -1,7 +1,7 @@
import { SlashCommand } from "@plane/editor-extensions";
import Placeholder from "@tiptap/extension-placeholder";
import { DragAndDrop } from "@plane/editor-extensions";
import { UploadImage } from "../";
import { UploadImage } from "@plane/editor-types";
export const RichTextEditorExtensions = (
uploadFile: UploadImage,

View File

@@ -8,25 +8,18 @@ import {
} from "@plane/editor-core";
import { EditorBubbleMenu } from "./menus/bubble-menu";
import { RichTextEditorExtensions } from "./extensions";
import {
DeleteImage,
IMentionSuggestion,
RestoreImage,
UploadImage,
} from "@plane/editor-types";
export type UploadImage = (file: File) => Promise<string>;
export type DeleteImage = (assetUrlWithWorkspaceId: string) => Promise<any>;
export type IMentionSuggestion = {
id: string;
type: string;
avatar: string;
title: string;
subtitle: string;
redirect_uri: string;
};
export type IMentionHighlight = string;
interface IRichTextEditor {
export type IRichTextEditor = {
value: string;
dragDropEnabled?: boolean;
uploadFile: UploadImage;
restoreFile: RestoreImage;
deleteFile: DeleteImage;
noBorder?: boolean;
borderOnFocus?: boolean;
@@ -42,9 +35,9 @@ interface IRichTextEditor {
debouncedUpdatesEnabled?: boolean;
mentionHighlights?: string[];
mentionSuggestions?: IMentionSuggestion[];
}
};
interface RichTextEditorProps extends IRichTextEditor {
export interface RichTextEditorProps extends IRichTextEditor {
forwardedRef?: React.Ref<EditorHandle>;
}
@@ -67,6 +60,7 @@ const RichTextEditor = ({
cancelUploadImage,
borderOnFocus,
customClassName,
restoreFile,
forwardedRef,
mentionHighlights,
mentionSuggestions,
@@ -80,6 +74,7 @@ const RichTextEditor = ({
uploadFile,
cancelUploadImage,
deleteFile,
restoreFile,
forwardedRef,
extensions: RichTextEditorExtensions(
uploadFile,

View File

@@ -36,7 +36,7 @@
"@types/react": "^18.2.35",
"@types/react-dom": "^18.2.14",
"eslint": "^7.32.0",
"tsconfig": "*",
"tsconfig": "workspace:*",
"tsup": "^7.2.0",
"typescript": "4.9.5"
},

View File

@@ -1,5 +1,6 @@
export type { DeleteImage } from "./types/delete-image";
export type { UploadImage } from "./types/upload-image";
export type { RestoreImage } from "./types/restore-image";
export type {
IMentionHighlight,
IMentionSuggestion,

View File

@@ -0,0 +1 @@
export type RestoreImage = (assetUrlWithWorkspaceId: string) => Promise<any>;

View File

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

View File

@@ -7,10 +7,10 @@
"devDependencies": {
"@tailwindcss/typography": "^0.5.9",
"autoprefixer": "^10.4.14",
"postcss": "^8.4.21",
"postcss": "^8.4.31",
"prettier": "^2.8.8",
"prettier-plugin-tailwindcss": "^0.3.0",
"tailwindcss": "^3.2.7",
"tailwindcss-animate": "^1.0.6"
"tailwindcss": "^3.3.5",
"tailwindcss-animate": "^1.0.7"
}
}

View File

@@ -12,8 +12,10 @@
"dist/**"
],
"scripts": {
"build": "tsup src/index.ts --format esm,cjs --dts --external react",
"dev": "tsup src/index.ts --format esm,cjs --watch --dts --external react",
"build": "tsup",
"dev": "tsup --watch",
"check-types": "tsc --noEmit",
"format": "prettier --write \"**/*.{ts,tsx,md}\"",
"lint": "eslint src/",
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist"
},
@@ -23,10 +25,10 @@
"@types/react-color": "^3.0.9",
"@types/react-dom": "^18.2.15",
"classnames": "^2.3.2",
"eslint-config-custom": "*",
"eslint-config-custom": "workspace:*",
"react": "^18.2.0",
"tailwind-config-custom": "*",
"tsconfig": "*",
"tailwind-config-custom": "workspace:*",
"tsconfig": "workspace:*",
"tsup": "^5.10.1",
"typescript": "4.7.4"
},

View File

@@ -0,0 +1,11 @@
import { defineConfig, Options } from "tsup";
export default defineConfig((options: Options) => ({
entry: ["src/index.ts"],
format: ["cjs", "esm"],
dts: true,
clean: false,
external: ["react"],
injectStyle: true,
...options,
}));

11602
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

8
pnpm-workspace.yaml Normal file
View File

@@ -0,0 +1,8 @@
packages:
- "web"
- "space"
- "packages/editor/*"
- "packages/eslint-config-custom"
- "packages/tailwind-config-custom"
- "packages/tsconfig"
- "packages/ui"

View File

@@ -79,6 +79,7 @@ export const AddComment: React.FC<Props> = observer((props) => {
cancelUploadImage={fileService.cancelUpload}
uploadFile={fileService.getUploadFileFunction(workspace_slug as string)}
deleteFile={fileService.deleteImage}
restoreFile={fileService.restoreImage}
ref={editorRef}
value={
!value || value === "" || (typeof value === "object" && Object.keys(value).length === 0)

View File

@@ -106,6 +106,7 @@ export const CommentCard: React.FC<Props> = observer((props) => {
cancelUploadImage={fileService.cancelUpload}
uploadFile={fileService.getUploadFileFunction(workspaceSlug)}
deleteFile={fileService.deleteImage}
restoreFile={fileService.restoreImage}
ref={editorRef}
value={value}
debouncedUpdatesEnabled={false}

View File

@@ -3,8 +3,7 @@
"version": "0.13.2",
"private": true,
"scripts": {
"dev": "turbo run develop",
"develop": "next dev -p 4000",
"dev": "next dev -p 4000",
"build": "next build",
"start": "next start -p 4000",
"lint": "next lint",
@@ -17,10 +16,10 @@
"@emotion/styled": "^11.11.0",
"@headlessui/react": "^1.7.13",
"@mui/material": "^5.14.1",
"@plane/lite-text-editor": "*",
"@plane/rich-text-editor": "*",
"@plane/ui": "*",
"@plane/document-editor": "*",
"@plane/document-editor": "workspace:*",
"@plane/lite-text-editor": "workspace:*",
"@plane/rich-text-editor": "workspace:*",
"@plane/ui": "workspace:*",
"axios": "^1.3.4",
"clsx": "^2.0.0",
"js-cookie": "^3.0.1",
@@ -49,9 +48,9 @@
"@types/uuid": "^9.0.1",
"@typescript-eslint/eslint-plugin": "^5.48.2",
"eslint": "8.34.0",
"eslint-config-custom": "*",
"eslint-config-custom": "workspace:*",
"eslint-config-next": "13.2.1",
"tailwind-config-custom": "*",
"tsconfig": "*"
"tailwind-config-custom": "workspace:*",
"tsconfig": "workspace:*"
}
}

View File

@@ -1,4 +1,6 @@
// services
import APIService from "services/api.service";
// helpers
import { API_BASE_URL } from "helpers/common.helper";
import axios from "axios";
@@ -33,6 +35,7 @@ class FileService extends APIService {
super(API_BASE_URL);
this.uploadFile = this.uploadFile.bind(this);
this.deleteImage = this.deleteImage.bind(this);
this.restoreImage = this.restoreImage.bind(this);
this.cancelUpload = this.cancelUpload.bind(this);
}
@@ -50,6 +53,7 @@ class FileService extends APIService {
if (axios.isCancel(error)) {
console.log(error.message);
} else {
console.log(error);
throw error?.response?.data;
}
});
@@ -58,6 +62,7 @@ class FileService extends APIService {
cancelUpload() {
this.cancelSource.cancel("Upload cancelled");
}
getUploadFileFunction(workspaceSlug: string): (file: File) => Promise<string> {
return async (file: File) => {
const formData = new FormData();
@@ -77,6 +82,17 @@ class FileService extends APIService {
});
}
async restoreImage(assetUrlWithWorkspaceId: string): Promise<any> {
return this.post(`/api/workspaces/file-assets/${assetUrlWithWorkspaceId}/restore/`, {
headers: this.getHeaders(),
"Content-Type": "application/json",
})
.then((response) => response?.status)
.catch((error) => {
throw error?.response?.data;
});
}
async deleteFile(workspaceId: string, assetUrl: string): Promise<any> {
const lastIndex = assetUrl.lastIndexOf("/");
const assetId = assetUrl.substring(lastIndex + 1);

View File

@@ -22,17 +22,11 @@
],
"pipeline": {
"build": {
"dependsOn": [
"^build"
],
"outputs": [
".next/**",
"dist/**"
]
"dependsOn": ["^build"],
"outputs": [".next/**", "dist/**"]
},
"web#develop": {
"cache": false,
"persistent": true,
"web#dev": {
"cache": true,
"dependsOn": [
"@plane/lite-text-editor#build",
"@plane/rich-text-editor#build",
@@ -40,9 +34,8 @@
"@plane/ui#build"
]
},
"space#develop": {
"cache": false,
"persistent": true,
"space#dev": {
"cache": true,
"dependsOn": [
"@plane/lite-text-editor#build",
"@plane/rich-text-editor#build",
@@ -93,9 +86,7 @@
]
},
"test": {
"dependsOn": [
"^build"
],
"dependsOn": ["^build"],
"outputs": []
},
"lint": {

View File

@@ -14,7 +14,7 @@ export const ApiTokenEmptyState: React.FC<Props> = (props) => {
return (
<div
className={`flex items-center justify-center mx-auto rounded-sm border border-custom-border-200 bg-custom-background-90 py-10 px-16 w-full`}
className={`flex items-center justify-center mx-auto rounded-sm border border-custom-border-200 bg-custom-background-90 py-10 px-16 w-full lg:w-3/4`}
>
<div className="text-center flex flex-col items-center w-full">
<Image src={emptyApiTokens} className="w-52 sm:w-60" alt="empty" />

View File

@@ -155,6 +155,7 @@ export const CreateInboxIssueModal: React.FC<Props> = observer((props) => {
cancelUploadImage={fileService.cancelUpload}
uploadFile={fileService.getUploadFileFunction(workspaceSlug as string)}
deleteFile={fileService.deleteImage}
restoreFile={fileService.restoreImage}
ref={editorRef}
debouncedUpdatesEnabled={false}
value={!value || value === "" ? "<p></p>" : value}

View File

@@ -87,6 +87,7 @@ export const AddComment: React.FC<Props> = ({ disabled = false, onSubmit, showAc
cancelUploadImage={fileService.cancelUpload}
uploadFile={fileService.getUploadFileFunction(workspaceSlug as string)}
deleteFile={fileService.deleteImage}
restoreFile={fileService.restoreImage}
ref={editorRef}
value={!commentValue || commentValue === "" ? "<p></p>" : commentValue}
customClassName="p-2 h-full"

View File

@@ -108,6 +108,7 @@ export const CommentCard: React.FC<Props> = ({
cancelUploadImage={fileService.cancelUpload}
uploadFile={fileService.getUploadFileFunction(workspaceSlug as string)}
deleteFile={fileService.deleteImage}
restoreFile={fileService.restoreImage}
ref={editorRef}
value={watch("comment_html")}
debouncedUpdatesEnabled={false}

View File

@@ -148,6 +148,7 @@ export const IssueDescriptionForm: FC<IssueDetailsProps> = (props) => {
cancelUploadImage={fileService.cancelUpload}
uploadFile={fileService.getUploadFileFunction(workspaceSlug)}
deleteFile={fileService.deleteImage}
restoreFile={fileService.restoreImage}
value={value}
setShouldShowAlert={setShowAlert}
setIsSubmitting={setIsSubmitting}

View File

@@ -425,6 +425,7 @@ export const DraftIssueForm: FC<IssueFormProps> = observer((props) => {
cancelUploadImage={fileService.cancelUpload}
uploadFile={fileService.getUploadFileFunction(workspaceSlug as string)}
deleteFile={fileService.deleteImage}
restoreFile={fileService.restoreImage}
ref={editorRef}
debouncedUpdatesEnabled={false}
value={

View File

@@ -379,6 +379,7 @@ export const IssueForm: FC<IssueFormProps> = observer((props) => {
cancelUploadImage={fileService.cancelUpload}
uploadFile={fileService.getUploadFileFunction(workspaceSlug as string)}
deleteFile={fileService.deleteImage}
restoreFile={fileService.restoreImage}
ref={editorRef}
debouncedUpdatesEnabled={false}
value={

View File

@@ -28,7 +28,7 @@ export const CycleEmptyState: React.FC<Props> = observer((props) => {
const [cycleIssuesListModal, setCycleIssuesListModal] = useState(false);
const {
cycleIssue: cycleIssueStore,
cycleIssues: cycleIssueStore,
commandPalette: commandPaletteStore,
trackEvent: { setTrackElement },
} = useMobxStore();
@@ -40,15 +40,13 @@ export const CycleEmptyState: React.FC<Props> = observer((props) => {
const issueIds = data.map((i) => i.id);
await cycleIssueStore
.addIssueToCycle(workspaceSlug.toString(), projectId.toString(), cycleId.toString(), issueIds)
.catch(() => {
setToastAlert({
type: "error",
title: "Error!",
message: "Selected issues could not be added to the cycle. Please try again.",
});
await cycleIssueStore.addIssueToCycle(workspaceSlug.toString(), cycleId.toString(), issueIds).catch(() => {
setToastAlert({
type: "error",
title: "Error!",
message: "Selected issues could not be added to the cycle. Please try again.",
});
});
};
return (

View File

@@ -22,7 +22,11 @@ export const ModuleEmptyState: React.FC<Props> = observer((props) => {
// states
const [moduleIssuesListModal, setModuleIssuesListModal] = useState(false);
const { moduleIssue: moduleIssueStore, commandPalette: commandPaletteStore, trackEvent: { setTrackElement } } = useMobxStore();
const {
moduleIssues: moduleIssueStore,
commandPalette: commandPaletteStore,
trackEvent: { setTrackElement },
} = useMobxStore();
const { setToastAlert } = useToast();
@@ -31,15 +35,13 @@ export const ModuleEmptyState: React.FC<Props> = observer((props) => {
const issueIds = data.map((i) => i.id);
await moduleIssueStore
.addIssueToModule(workspaceSlug.toString(), projectId.toString(), moduleId.toString(), issueIds)
.catch(() =>
setToastAlert({
type: "error",
title: "Error!",
message: "Selected issues could not be added to the module. Please try again.",
})
);
await moduleIssueStore.addIssueToModule(workspaceSlug.toString(), moduleId.toString(), issueIds).catch(() =>
setToastAlert({
type: "error",
title: "Error!",
message: "Selected issues could not be added to the module. Please try again.",
})
);
};
return (
@@ -60,8 +62,8 @@ export const ModuleEmptyState: React.FC<Props> = observer((props) => {
icon: <PlusIcon className="h-3 w-3" strokeWidth={2} />,
onClick: () => {
setTrackElement("MODULE_EMPTY_STATE");
commandPaletteStore.toggleCreateIssueModal(true)
}
commandPaletteStore.toggleCreateIssueModal(true);
},
}}
secondaryButton={
<Button

View File

@@ -18,6 +18,7 @@ export const ProfileIssuesAppliedFiltersRoot: React.FC = observer(() => {
const {
workspace: { workspaceLabels },
workspaceProfileIssuesFilter: { issueFilters, updateFilters },
projectMember: { projectMembers },
} = useMobxStore();
const userFilters = issueFilters?.filters;
@@ -64,7 +65,7 @@ export const ProfileIssuesAppliedFiltersRoot: React.FC = observer(() => {
handleClearAllFilters={handleClearAllFilters}
handleRemoveFilter={handleRemoveFilter}
labels={workspaceLabels ?? []}
members={[]}
members={projectMembers?.map((m) => m.member)}
states={[]}
/>
</div>

View File

@@ -25,7 +25,6 @@ import {
IViewIssuesFilterStore,
IViewIssuesStore,
} from "store/issues";
import { EUserWorkspaceRoles } from "layouts/settings-layout/workspace/sidebar";
import { TUnGroupedIssues } from "store/issues/types";
interface IBaseGanttRoot {
@@ -46,6 +45,10 @@ export const BaseGanttRoot: React.FC<IBaseGanttRoot> = observer((props: IBaseGan
const { projectDetails } = useProjectDetails();
const {
user: { currentProjectRole },
} = useMobxStore();
const appliedDisplayFilters = issueFiltersStore.issueFilters?.displayFilters;
const issuesResponse = issueStore.getIssues;
@@ -69,7 +72,7 @@ export const BaseGanttRoot: React.FC<IBaseGanttRoot> = observer((props: IBaseGan
);
};
const isAllowed = (projectDetails?.member_role || 0) >= EUserWorkspaceRoles.MEMBER;
const isAllowed = currentProjectRole && currentProjectRole >= 15;
return (
<>

View File

@@ -54,6 +54,7 @@ export interface IBaseKanBanLayout {
showLoader?: boolean;
viewId?: string;
currentStore?: EProjectStore;
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
}
export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBaseKanBanLayout) => {
@@ -66,6 +67,7 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
showLoader,
viewId,
currentStore,
addIssuesToView,
} = props;
const {
@@ -186,6 +188,7 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
disableIssueCreation={!enableIssueCreation}
isReadOnly={!enableInlineEditing}
currentStore={currentStore}
addIssuesToView={addIssuesToView}
/>
) : (
<KanBanSwimLanes
@@ -226,6 +229,11 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
enableQuickIssueCreate={enableQuickAdd}
isReadOnly={!enableInlineEditing}
currentStore={currentStore}
addIssuesToView={(issues) => {
console.log("kanban existingIds", issues);
return Promise.resolve({} as IIssue);
}}
/>
)}
</DragDropContext>

View File

@@ -43,6 +43,7 @@ export interface IGroupByKanBan {
viewId?: string;
disableIssueCreation?: boolean;
currentStore?: EProjectStore;
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
isReadOnly: boolean;
}
@@ -70,6 +71,7 @@ const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => {
disableIssueCreation,
isReadOnly,
currentStore,
addIssuesToView,
} = props;
const verticalAlignPosition = (_list: any) =>
@@ -95,6 +97,7 @@ const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => {
handleKanBanToggle={handleKanBanToggle}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
addIssuesToView={addIssuesToView}
/>
</div>
)}
@@ -201,6 +204,7 @@ export interface IKanBan {
viewId?: string;
disableIssueCreation?: boolean;
currentStore?: EProjectStore;
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
isReadOnly: boolean;
}
@@ -231,6 +235,7 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
disableIssueCreation,
isReadOnly,
currentStore,
addIssuesToView,
} = props;
const { issueKanBanView: issueKanBanViewStore } = useMobxStore();
@@ -262,6 +267,7 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
disableIssueCreation={disableIssueCreation}
isReadOnly={isReadOnly}
currentStore={currentStore}
addIssuesToView={addIssuesToView}
/>
)}
@@ -290,6 +296,7 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
disableIssueCreation={disableIssueCreation}
isReadOnly={isReadOnly}
currentStore={currentStore}
addIssuesToView={addIssuesToView}
/>
)}
@@ -318,6 +325,7 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
disableIssueCreation={disableIssueCreation}
isReadOnly={isReadOnly}
currentStore={currentStore}
addIssuesToView={addIssuesToView}
/>
)}
@@ -346,6 +354,7 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
disableIssueCreation={disableIssueCreation}
isReadOnly={isReadOnly}
currentStore={currentStore}
addIssuesToView={addIssuesToView}
/>
)}
@@ -374,6 +383,7 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
disableIssueCreation={disableIssueCreation}
isReadOnly={isReadOnly}
currentStore={currentStore}
addIssuesToView={addIssuesToView}
/>
)}
@@ -402,6 +412,7 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
disableIssueCreation={disableIssueCreation}
isReadOnly={isReadOnly}
currentStore={currentStore}
addIssuesToView={addIssuesToView}
/>
)}
@@ -430,6 +441,7 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
disableIssueCreation={disableIssueCreation}
isReadOnly={isReadOnly}
currentStore={currentStore}
addIssuesToView={addIssuesToView}
/>
)}
</div>

View File

@@ -6,6 +6,7 @@ import { HeaderSubGroupByCard } from "./sub-group-by-card";
// ui
import { Avatar } from "@plane/ui";
import { EProjectStore } from "store/command-palette.store";
import { IIssue } from "types";
export interface IAssigneesHeader {
column_id: string;
@@ -18,6 +19,7 @@ export interface IAssigneesHeader {
handleKanBanToggle: any;
disableIssueCreation?: boolean;
currentStore?: EProjectStore;
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
}
export const Icon = ({ user }: any) => <Avatar name={user.display_name} src={user.avatar} size="base" />;
@@ -34,6 +36,7 @@ export const AssigneesHeader: FC<IAssigneesHeader> = observer((props) => {
handleKanBanToggle,
disableIssueCreation,
currentStore,
addIssuesToView,
} = props;
const assignee = column_value ?? null;
@@ -63,6 +66,7 @@ export const AssigneesHeader: FC<IAssigneesHeader> = observer((props) => {
issuePayload={{ assignees: [assignee?.id] }}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
addIssuesToView={addIssuesToView}
/>
))}
</>

View File

@@ -5,6 +5,7 @@ import { HeaderGroupByCard } from "./group-by-card";
import { HeaderSubGroupByCard } from "./sub-group-by-card";
import { Icon } from "./assignee";
import { EProjectStore } from "store/command-palette.store";
import { IIssue } from "types";
export interface ICreatedByHeader {
column_id: string;
@@ -17,6 +18,7 @@ export interface ICreatedByHeader {
handleKanBanToggle: any;
disableIssueCreation?: boolean;
currentStore?: EProjectStore;
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
}
export const CreatedByHeader: FC<ICreatedByHeader> = observer((props) => {
@@ -31,6 +33,7 @@ export const CreatedByHeader: FC<ICreatedByHeader> = observer((props) => {
handleKanBanToggle,
disableIssueCreation,
currentStore,
addIssuesToView,
} = props;
const createdBy = column_value ?? null;
@@ -60,6 +63,7 @@ export const CreatedByHeader: FC<ICreatedByHeader> = observer((props) => {
issuePayload={{ created_by: createdBy?.id }}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
addIssuesToView={addIssuesToView}
/>
))}
</>

View File

@@ -29,11 +29,9 @@ interface IHeaderGroupByCard {
issuePayload: Partial<IIssue>;
disableIssueCreation?: boolean;
currentStore?: EProjectStore;
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
}
const moduleService = new ModuleService();
const issueService = new IssueService();
export const HeaderGroupByCard: FC<IHeaderGroupByCard> = observer((props) => {
const {
sub_group_by,
@@ -46,6 +44,7 @@ export const HeaderGroupByCard: FC<IHeaderGroupByCard> = observer((props) => {
issuePayload,
disableIssueCreation,
currentStore,
addIssuesToView,
} = props;
const verticalAlignPosition = kanBanToggle?.groupByHeaderMinMax.includes(column_id);
@@ -60,34 +59,13 @@ export const HeaderGroupByCard: FC<IHeaderGroupByCard> = observer((props) => {
const renderExistingIssueModal = moduleId || cycleId;
const ExistingIssuesListModalPayload = moduleId ? { module: true } : { cycle: true };
const handleAddIssuesToModule = async (data: ISearchIssueResponse[]) => {
const handleAddIssuesToView = async (data: ISearchIssueResponse[]) => {
if (!workspaceSlug || !projectId) return;
const payload = {
issues: data.map((i) => i.id),
};
const issues = data.map((i) => i.id);
await moduleService
.addIssuesToModule(workspaceSlug as string, projectId as string, moduleId as string, payload)
.catch(() =>
setToastAlert({
type: "error",
title: "Error!",
message: "Selected issues could not be added to the module. Please try again.",
})
);
};
const handleAddIssuesToCycle = async (data: ISearchIssueResponse[]) => {
if (!workspaceSlug || !projectId) return;
const payload = {
issues: data.map((i) => i.id),
};
await issueService
.addIssueToCycle(workspaceSlug as string, projectId as string, cycleId as string, payload)
.catch(() => {
addIssuesToView &&
addIssuesToView(issues)?.catch(() => {
setToastAlert({
type: "error",
title: "Error!",
@@ -109,7 +87,7 @@ export const HeaderGroupByCard: FC<IHeaderGroupByCard> = observer((props) => {
isOpen={openExistingIssueListModal}
handleClose={() => setOpenExistingIssueListModal(false)}
searchParams={ExistingIssuesListModalPayload}
handleOnSubmit={moduleId ? handleAddIssuesToModule : handleAddIssuesToCycle}
handleOnSubmit={handleAddIssuesToView}
/>
)}
<div

View File

@@ -9,6 +9,7 @@ import { CreatedByHeader } from "./created_by";
// mobx
import { observer } from "mobx-react-lite";
import { EProjectStore } from "store/command-palette.store";
import { IIssue } from "types";
export interface IKanBanGroupByHeaderRoot {
column_id: string;
@@ -20,6 +21,7 @@ export interface IKanBanGroupByHeaderRoot {
handleKanBanToggle: any;
disableIssueCreation?: boolean;
currentStore?: EProjectStore;
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
}
export const KanBanGroupByHeaderRoot: React.FC<IKanBanGroupByHeaderRoot> = observer(
@@ -33,6 +35,7 @@ export const KanBanGroupByHeaderRoot: React.FC<IKanBanGroupByHeaderRoot> = obser
disableIssueCreation,
handleKanBanToggle,
currentStore,
addIssuesToView,
}) => (
<>
{group_by && group_by === "project" && (
@@ -47,6 +50,7 @@ export const KanBanGroupByHeaderRoot: React.FC<IKanBanGroupByHeaderRoot> = obser
handleKanBanToggle={handleKanBanToggle}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
addIssuesToView={addIssuesToView}
/>
)}
@@ -62,6 +66,7 @@ export const KanBanGroupByHeaderRoot: React.FC<IKanBanGroupByHeaderRoot> = obser
handleKanBanToggle={handleKanBanToggle}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
addIssuesToView={addIssuesToView}
/>
)}
{group_by && group_by === "state_detail.group" && (
@@ -76,6 +81,7 @@ export const KanBanGroupByHeaderRoot: React.FC<IKanBanGroupByHeaderRoot> = obser
handleKanBanToggle={handleKanBanToggle}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
addIssuesToView={addIssuesToView}
/>
)}
{group_by && group_by === "priority" && (
@@ -90,6 +96,7 @@ export const KanBanGroupByHeaderRoot: React.FC<IKanBanGroupByHeaderRoot> = obser
handleKanBanToggle={handleKanBanToggle}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
addIssuesToView={addIssuesToView}
/>
)}
{group_by && group_by === "labels" && (
@@ -104,6 +111,7 @@ export const KanBanGroupByHeaderRoot: React.FC<IKanBanGroupByHeaderRoot> = obser
handleKanBanToggle={handleKanBanToggle}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
addIssuesToView={addIssuesToView}
/>
)}
{group_by && group_by === "assignees" && (
@@ -118,6 +126,7 @@ export const KanBanGroupByHeaderRoot: React.FC<IKanBanGroupByHeaderRoot> = obser
handleKanBanToggle={handleKanBanToggle}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
addIssuesToView={addIssuesToView}
/>
)}
{group_by && group_by === "created_by" && (
@@ -132,6 +141,7 @@ export const KanBanGroupByHeaderRoot: React.FC<IKanBanGroupByHeaderRoot> = obser
handleKanBanToggle={handleKanBanToggle}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
addIssuesToView={addIssuesToView}
/>
)}
</>

View File

@@ -4,6 +4,7 @@ import { observer } from "mobx-react-lite";
import { HeaderGroupByCard } from "./group-by-card";
import { HeaderSubGroupByCard } from "./sub-group-by-card";
import { EProjectStore } from "store/command-palette.store";
import { IIssue } from "types";
export interface ILabelHeader {
column_id: string;
@@ -16,6 +17,7 @@ export interface ILabelHeader {
handleKanBanToggle: any;
disableIssueCreation?: boolean;
currentStore?: EProjectStore;
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
}
const Icon = ({ color }: any) => (
@@ -34,6 +36,7 @@ export const LabelHeader: FC<ILabelHeader> = observer((props) => {
handleKanBanToggle,
disableIssueCreation,
currentStore,
addIssuesToView,
} = props;
const label = column_value ?? null;
@@ -63,6 +66,7 @@ export const LabelHeader: FC<ILabelHeader> = observer((props) => {
issuePayload={{ labels: [label?.id] }}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
addIssuesToView={addIssuesToView}
/>
))}
</>

View File

@@ -8,6 +8,7 @@ import { HeaderSubGroupByCard } from "./sub-group-by-card";
// Icons
import { PriorityIcon } from "@plane/ui";
import { EProjectStore } from "store/command-palette.store";
import { IIssue } from "types";
export interface IPriorityHeader {
column_id: string;
@@ -20,6 +21,7 @@ export interface IPriorityHeader {
handleKanBanToggle: any;
disableIssueCreation?: boolean;
currentStore?: EProjectStore;
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
}
export const PriorityHeader: FC<IPriorityHeader> = observer((props) => {
@@ -34,6 +36,7 @@ export const PriorityHeader: FC<IPriorityHeader> = observer((props) => {
handleKanBanToggle,
disableIssueCreation,
currentStore,
addIssuesToView,
} = props;
const priority = column_value || null;
@@ -63,6 +66,7 @@ export const PriorityHeader: FC<IPriorityHeader> = observer((props) => {
issuePayload={{ priority: priority?.key }}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
addIssuesToView={addIssuesToView}
/>
))}
</>

View File

@@ -6,6 +6,7 @@ import { HeaderSubGroupByCard } from "./sub-group-by-card";
// emoji helper
import { renderEmoji } from "helpers/emoji.helper";
import { EProjectStore } from "store/command-palette.store";
import { IIssue } from "types";
export interface IProjectHeader {
column_id: string;
@@ -18,6 +19,7 @@ export interface IProjectHeader {
handleKanBanToggle: any;
disableIssueCreation?: boolean;
currentStore?: EProjectStore;
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
}
const Icon = ({ emoji }: any) => <div className="w-6 h-6">{renderEmoji(emoji)}</div>;
@@ -34,6 +36,7 @@ export const ProjectHeader: FC<IProjectHeader> = observer((props) => {
handleKanBanToggle,
disableIssueCreation,
currentStore,
addIssuesToView,
} = props;
const project = column_value ?? null;
@@ -63,6 +66,7 @@ export const ProjectHeader: FC<IProjectHeader> = observer((props) => {
issuePayload={{ project: project?.id }}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
addIssuesToView={addIssuesToView}
/>
))}
</>

View File

@@ -5,6 +5,7 @@ import { HeaderGroupByCard } from "./group-by-card";
import { HeaderSubGroupByCard } from "./sub-group-by-card";
import { StateGroupIcon } from "@plane/ui";
import { EProjectStore } from "store/command-palette.store";
import { IIssue } from "types";
export interface IStateGroupHeader {
column_id: string;
@@ -17,6 +18,7 @@ export interface IStateGroupHeader {
handleKanBanToggle: any;
disableIssueCreation?: boolean;
currentStore?: EProjectStore;
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
}
export const Icon = ({ stateGroup, color }: { stateGroup: any; color?: any }) => (
@@ -37,6 +39,7 @@ export const StateGroupHeader: FC<IStateGroupHeader> = observer((props) => {
handleKanBanToggle,
disableIssueCreation,
currentStore,
addIssuesToView,
} = props;
const stateGroup = column_value || null;
@@ -66,6 +69,7 @@ export const StateGroupHeader: FC<IStateGroupHeader> = observer((props) => {
issuePayload={{}}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
addIssuesToView={addIssuesToView}
/>
))}
</>

View File

@@ -5,6 +5,7 @@ import { HeaderGroupByCard } from "./group-by-card";
import { HeaderSubGroupByCard } from "./sub-group-by-card";
import { Icon } from "./state-group";
import { EProjectStore } from "store/command-palette.store";
import { IIssue } from "types";
export interface IStateHeader {
column_id: string;
@@ -17,6 +18,7 @@ export interface IStateHeader {
handleKanBanToggle: any;
disableIssueCreation?: boolean;
currentStore?: EProjectStore;
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
}
export const StateHeader: FC<IStateHeader> = observer((props) => {
@@ -31,6 +33,7 @@ export const StateHeader: FC<IStateHeader> = observer((props) => {
handleKanBanToggle,
disableIssueCreation,
currentStore,
addIssuesToView,
} = props;
const state = column_value ?? null;
@@ -60,6 +63,7 @@ export const StateHeader: FC<IStateHeader> = observer((props) => {
issuePayload={{ state: state?.id }}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
addIssuesToView={addIssuesToView}
/>
))}
</>

View File

@@ -8,6 +8,7 @@ import { PriorityHeader } from "./priority";
import { LabelHeader } from "./label";
import { CreatedByHeader } from "./created_by";
import { EProjectStore } from "store/command-palette.store";
import { IIssue } from "types";
export interface IKanBanSubGroupByHeaderRoot {
column_id: string;
@@ -19,6 +20,7 @@ export interface IKanBanSubGroupByHeaderRoot {
handleKanBanToggle: any;
disableIssueCreation?: boolean;
currentStore?: EProjectStore;
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
}
export const KanBanSubGroupByHeaderRoot: React.FC<IKanBanSubGroupByHeaderRoot> = observer((props) => {
@@ -32,6 +34,7 @@ export const KanBanSubGroupByHeaderRoot: React.FC<IKanBanSubGroupByHeaderRoot> =
handleKanBanToggle,
disableIssueCreation,
currentStore,
addIssuesToView,
} = props;
return (
@@ -48,6 +51,7 @@ export const KanBanSubGroupByHeaderRoot: React.FC<IKanBanSubGroupByHeaderRoot> =
handleKanBanToggle={handleKanBanToggle}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
addIssuesToView={addIssuesToView}
/>
)}
{sub_group_by && sub_group_by === "state_detail.group" && (
@@ -62,6 +66,7 @@ export const KanBanSubGroupByHeaderRoot: React.FC<IKanBanSubGroupByHeaderRoot> =
handleKanBanToggle={handleKanBanToggle}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
addIssuesToView={addIssuesToView}
/>
)}
{sub_group_by && sub_group_by === "priority" && (
@@ -76,6 +81,7 @@ export const KanBanSubGroupByHeaderRoot: React.FC<IKanBanSubGroupByHeaderRoot> =
handleKanBanToggle={handleKanBanToggle}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
addIssuesToView={addIssuesToView}
/>
)}
{sub_group_by && sub_group_by === "labels" && (
@@ -90,6 +96,7 @@ export const KanBanSubGroupByHeaderRoot: React.FC<IKanBanSubGroupByHeaderRoot> =
handleKanBanToggle={handleKanBanToggle}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
addIssuesToView={addIssuesToView}
/>
)}
{sub_group_by && sub_group_by === "assignees" && (
@@ -104,6 +111,7 @@ export const KanBanSubGroupByHeaderRoot: React.FC<IKanBanSubGroupByHeaderRoot> =
handleKanBanToggle={handleKanBanToggle}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
addIssuesToView={addIssuesToView}
/>
)}
{sub_group_by && sub_group_by === "created_by" && (
@@ -118,6 +126,7 @@ export const KanBanSubGroupByHeaderRoot: React.FC<IKanBanSubGroupByHeaderRoot> =
handleKanBanToggle={handleKanBanToggle}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
addIssuesToView={addIssuesToView}
/>
)}
</>

View File

@@ -50,6 +50,7 @@ export const CycleKanBanLayout: React.FC = observer(() => {
QuickActions={CycleIssueQuickActions}
viewId={cycleId}
currentStore={EProjectStore.CYCLE}
addIssuesToView={(issues: string[]) => cycleIssueStore.addIssueToCycle(workspaceSlug, cycleId, issues)}
/>
);
});

View File

@@ -72,6 +72,7 @@ export const ModuleKanBanLayout: React.FC = observer(() => {
QuickActions={ModuleIssueQuickActions}
viewId={moduleId}
currentStore={EProjectStore.MODULE}
addIssuesToView={(issues: string[]) => moduleIssueStore.addIssueToModule(workspaceSlug, moduleId, issues)}
/>
);
});

View File

@@ -23,6 +23,7 @@ interface ISubGroupSwimlaneHeader {
handleKanBanToggle: any;
disableIssueCreation?: boolean;
currentStore?: EProjectStore;
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
}
const SubGroupSwimlaneHeader: React.FC<ISubGroupSwimlaneHeader> = ({
issueIds,
@@ -34,6 +35,7 @@ const SubGroupSwimlaneHeader: React.FC<ISubGroupSwimlaneHeader> = ({
handleKanBanToggle,
disableIssueCreation,
currentStore,
addIssuesToView,
}) => {
const calculateIssueCount = (column_id: string) => {
let issueCount = 0;
@@ -60,6 +62,7 @@ const SubGroupSwimlaneHeader: React.FC<ISubGroupSwimlaneHeader> = ({
handleKanBanToggle={handleKanBanToggle}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
addIssuesToView={addIssuesToView}
/>
</div>
))}
@@ -114,6 +117,7 @@ const SubGroupSwimlane: React.FC<ISubGroupSwimlane> = observer((props) => {
disableIssueCreation,
enableQuickIssueCreate,
isReadOnly,
addIssuesToView,
} = props;
const calculateIssueCount = (column_id: string) => {
@@ -142,6 +146,7 @@ const SubGroupSwimlane: React.FC<ISubGroupSwimlane> = observer((props) => {
kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle}
disableIssueCreation={disableIssueCreation}
addIssuesToView={addIssuesToView}
/>
</div>
<div className="w-full border-b border-custom-border-400 border-dashed" />
@@ -170,6 +175,7 @@ const SubGroupSwimlane: React.FC<ISubGroupSwimlane> = observer((props) => {
enableQuickIssueCreate={enableQuickIssueCreate}
isDragStarted={isDragStarted}
isReadOnly={isReadOnly}
addIssuesToView={addIssuesToView}
/>
</div>
)}
@@ -200,6 +206,7 @@ export interface IKanBanSwimLanes {
isDragStarted?: boolean;
disableIssueCreation?: boolean;
currentStore?: EProjectStore;
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
enableQuickIssueCreate: boolean;
isReadOnly: boolean;
}
@@ -228,6 +235,7 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
enableQuickIssueCreate,
isReadOnly,
currentStore,
addIssuesToView,
} = props;
return (
@@ -245,6 +253,7 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
handleKanBanToggle={handleKanBanToggle}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
addIssuesToView={addIssuesToView}
/>
)}
@@ -260,6 +269,7 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
handleKanBanToggle={handleKanBanToggle}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
addIssuesToView={addIssuesToView}
/>
)}
@@ -275,6 +285,7 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
handleKanBanToggle={handleKanBanToggle}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
addIssuesToView={addIssuesToView}
/>
)}
@@ -289,6 +300,7 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle}
currentStore={currentStore}
addIssuesToView={addIssuesToView}
/>
)}
@@ -304,6 +316,7 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
handleKanBanToggle={handleKanBanToggle}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
addIssuesToView={addIssuesToView}
/>
)}
@@ -319,6 +332,7 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
handleKanBanToggle={handleKanBanToggle}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
addIssuesToView={addIssuesToView}
/>
)}
@@ -334,6 +348,7 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
handleKanBanToggle={handleKanBanToggle}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
addIssuesToView={addIssuesToView}
/>
)}
</div>

View File

@@ -54,10 +54,20 @@ interface IBaseListRoot {
getProjects: (projectStore: IProjectStore) => IProject[] | null;
viewId?: string;
currentStore: EProjectStore;
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
}
export const BaseListRoot = observer((props: IBaseListRoot) => {
const { issueFilterStore, issueStore, QuickActions, issueActions, getProjects, viewId, currentStore } = props;
const {
issueFilterStore,
issueStore,
QuickActions,
issueActions,
getProjects,
viewId,
currentStore,
addIssuesToView,
} = props;
const {
project: projectStore,
@@ -130,6 +140,7 @@ export const BaseListRoot = observer((props: IBaseListRoot) => {
isReadonly={!enableInlineEditing}
disableIssueCreation={!enableIssueCreation}
currentStore={currentStore}
addIssuesToView={addIssuesToView}
/>
</div>
)}

View File

@@ -32,6 +32,7 @@ export interface IGroupByList {
) => Promise<IIssue | undefined>;
disableIssueCreation?: boolean;
currentStore: EProjectStore;
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
viewId?: string;
}
@@ -54,6 +55,7 @@ const GroupByList: React.FC<IGroupByList> = (props) => {
viewId,
disableIssueCreation,
currentStore,
addIssuesToView,
} = props;
const prePopulateQuickAddData = (groupByKey: string | null, value: any) => {
@@ -91,6 +93,7 @@ const GroupByList: React.FC<IGroupByList> = (props) => {
}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
addIssuesToView={addIssuesToView}
/>
</div>
@@ -147,6 +150,7 @@ export interface IList {
viewId?: string;
disableIssueCreation?: boolean;
currentStore: EProjectStore;
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
}
export const List: React.FC<IList> = (props) => {
@@ -170,6 +174,7 @@ export const List: React.FC<IList> = (props) => {
members,
projects,
currentStore,
addIssuesToView,
} = props;
return (
@@ -193,6 +198,7 @@ export const List: React.FC<IList> = (props) => {
viewId={viewId}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
addIssuesToView={addIssuesToView}
/>
)}
@@ -214,6 +220,7 @@ export const List: React.FC<IList> = (props) => {
viewId={viewId}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
addIssuesToView={addIssuesToView}
/>
)}
@@ -235,6 +242,7 @@ export const List: React.FC<IList> = (props) => {
viewId={viewId}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
addIssuesToView={addIssuesToView}
/>
)}
@@ -256,6 +264,7 @@ export const List: React.FC<IList> = (props) => {
viewId={viewId}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
addIssuesToView={addIssuesToView}
/>
)}
@@ -277,6 +286,7 @@ export const List: React.FC<IList> = (props) => {
viewId={viewId}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
addIssuesToView={addIssuesToView}
/>
)}
@@ -298,6 +308,7 @@ export const List: React.FC<IList> = (props) => {
viewId={viewId}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
addIssuesToView={addIssuesToView}
/>
)}
@@ -319,6 +330,7 @@ export const List: React.FC<IList> = (props) => {
viewId={viewId}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
addIssuesToView={addIssuesToView}
/>
)}
@@ -340,6 +352,7 @@ export const List: React.FC<IList> = (props) => {
viewId={viewId}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
addIssuesToView={addIssuesToView}
/>
)}
</div>

View File

@@ -5,6 +5,7 @@ import { HeaderGroupByCard } from "./group-by-card";
// ui
import { Avatar } from "@plane/ui";
import { EProjectStore } from "store/command-palette.store";
import { IIssue } from "types";
export interface IAssigneesHeader {
column_id: string;
@@ -12,12 +13,13 @@ export interface IAssigneesHeader {
issues_count: number;
disableIssueCreation?: boolean;
currentStore: EProjectStore;
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
}
export const Icon = ({ user }: any) => <Avatar name={user.display_name} src={user.avatar} size="md" />;
export const AssigneesHeader: FC<IAssigneesHeader> = observer((props) => {
const { column_value, issues_count, disableIssueCreation, currentStore } = props;
const { column_value, issues_count, disableIssueCreation, currentStore, addIssuesToView } = props;
const assignee = column_value ?? null;
@@ -31,6 +33,7 @@ export const AssigneesHeader: FC<IAssigneesHeader> = observer((props) => {
issuePayload={{ assignees: [assignee?.member?.id] }}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
addIssuesToView={addIssuesToView}
/>
)}
</>

View File

@@ -4,6 +4,7 @@ import { observer } from "mobx-react-lite";
import { HeaderGroupByCard } from "./group-by-card";
import { Icon } from "./assignee";
import { EProjectStore } from "store/command-palette.store";
import { IIssue } from "types";
export interface ICreatedByHeader {
column_id: string;
@@ -11,10 +12,11 @@ export interface ICreatedByHeader {
issues_count: number;
disableIssueCreation?: boolean;
currentStore: EProjectStore;
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
}
export const CreatedByHeader: FC<ICreatedByHeader> = observer((props) => {
const { column_value, issues_count, disableIssueCreation, currentStore } = props;
const { column_value, issues_count, disableIssueCreation, currentStore, addIssuesToView } = props;
const createdBy = column_value ?? null;
@@ -28,6 +30,7 @@ export const CreatedByHeader: FC<ICreatedByHeader> = observer((props) => {
issuePayload={{ created_by: createdBy?.member?.id }}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
addIssuesToView={addIssuesToView}
/>
)}
</>

View File

@@ -2,6 +2,7 @@ import { observer } from "mobx-react-lite";
// components
import { HeaderGroupByCard } from "./group-by-card";
import { EProjectStore } from "store/command-palette.store";
import { IIssue } from "types";
export interface IEmptyHeader {
column_id: string;
@@ -9,10 +10,11 @@ export interface IEmptyHeader {
issues_count: number;
disableIssueCreation?: boolean;
currentStore: EProjectStore;
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
}
export const EmptyHeader: React.FC<IEmptyHeader> = observer((props) => {
const { column_id, column_value, issues_count, disableIssueCreation, currentStore } = props;
const { column_id, column_value, issues_count, disableIssueCreation, currentStore, addIssuesToView } = props;
return (
<HeaderGroupByCard
@@ -21,6 +23,7 @@ export const EmptyHeader: React.FC<IEmptyHeader> = observer((props) => {
issuePayload={{}}
disableIssueCreation={disableIssueCreation}
currentStore={currentStore}
addIssuesToView={addIssuesToView}
/>
);
});

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