forked from github/plane
Compare commits
29 Commits
style/empt
...
fix/pnpm-u
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dd250eb3da | ||
|
|
578893df18 | ||
|
|
84cc2fafd2 | ||
|
|
191949657d | ||
|
|
db510dcfcd | ||
|
|
d661026e3f | ||
|
|
d3194ac595 | ||
|
|
ce70aa7349 | ||
|
|
66cfb7b205 | ||
|
|
b0688a060a | ||
|
|
d062415bf0 | ||
|
|
e81ea00fbf | ||
|
|
4c3c20dacd | ||
|
|
5875cf7fd7 | ||
|
|
62c0615012 | ||
|
|
3914a75334 | ||
|
|
0cbb201348 | ||
|
|
495da01073 | ||
|
|
e5bb1e80cf | ||
|
|
2eba0f8cdc | ||
|
|
efd2db5bf9 | ||
|
|
e0e78903cd | ||
|
|
b0e0ab30e5 | ||
|
|
f7264364bd | ||
|
|
35f8ffa5ab | ||
|
|
6607caade7 | ||
|
|
8ee8270697 | ||
|
|
41ab962dd7 | ||
|
|
c22c6bb9b2 |
14
.github/workflows/build-test-pull-request.yml
vendored
14
.github/workflows/build-test-pull-request.yml
vendored
@@ -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
14
.gitignore
vendored
@@ -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/
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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:
|
||||
|
||||
118
deploy/selfhost/migration-0.13-0.14.sh
Executable file
118
deploy/selfhost/migration-0.13-0.14.sh
Executable 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
|
||||
|
||||
26
package.json
26
package.json
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export type DeleteImage = (assetUrlWithWorkspaceId: string) => Promise<any>;
|
||||
@@ -1,10 +0,0 @@
|
||||
export type IMentionSuggestion = {
|
||||
id: string;
|
||||
type: string;
|
||||
avatar: string;
|
||||
title: string;
|
||||
subtitle: string;
|
||||
redirect_uri: string;
|
||||
};
|
||||
|
||||
export type IMentionHighlight = string;
|
||||
@@ -1 +0,0 @@
|
||||
export type UploadImage = (file: File) => Promise<string>;
|
||||
@@ -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 };
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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?.(),
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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[],
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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 }) =>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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, "");
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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[];
|
||||
|
||||
@@ -1 +1 @@
|
||||
# Document Editor
|
||||
# Document Editor
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { FixedMenu } from "./fixed-menu";
|
||||
export { FixedMenu } from "./fixed-menu";
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
|
||||
1
packages/editor/types/src/types/restore-image.ts
Normal file
1
packages/editor/types/src/types/restore-image.ts
Normal file
@@ -0,0 +1 @@
|
||||
export type RestoreImage = (assetUrlWithWorkspaceId: string) => Promise<any>;
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
11
packages/ui/tsup.config.ts
Normal file
11
packages/ui/tsup.config.ts
Normal 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
11602
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
8
pnpm-workspace.yaml
Normal file
8
pnpm-workspace.yaml
Normal file
@@ -0,0 +1,8 @@
|
||||
packages:
|
||||
- "web"
|
||||
- "space"
|
||||
- "packages/editor/*"
|
||||
- "packages/eslint-config-custom"
|
||||
- "packages/tailwind-config-custom"
|
||||
- "packages/tsconfig"
|
||||
- "packages/ui"
|
||||
@@ -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)
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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:*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
23
turbo.json
23
turbo.json
@@ -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": {
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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={
|
||||
|
||||
@@ -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={
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -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)}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -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)}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user