Compare commits
117
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
768b4abf22 | ||
|
|
6f0af4689b | ||
|
|
22bbdd5ab8 | ||
|
|
28a9c53202 | ||
|
|
33d88f56da | ||
|
|
23001d425f | ||
|
|
5de94c575a | ||
|
|
f200acc1e8 | ||
|
|
f132fe59ae | ||
|
|
e584259c04 | ||
|
|
a77ceb636f | ||
|
|
10ab081a0b | ||
|
|
1d5a3a02c1 | ||
|
|
62b9b259c0 | ||
|
|
91e84aede1 | ||
|
|
71bf049e89 | ||
|
|
685e62a72f | ||
|
|
d9bd43f43c | ||
|
|
10bdcc906c | ||
|
|
54964924f0 | ||
|
|
ff8008cbed | ||
|
|
78428fb564 | ||
|
|
b2824366a8 | ||
|
|
05eb728c40 | ||
|
|
ad2471c5a7 | ||
|
|
440cfc0f20 | ||
|
|
9a32d722dc | ||
|
|
37fe9a185c | ||
|
|
eabb31a764 | ||
|
|
1e27c7936d | ||
|
|
0a617eec26 | ||
|
|
ad20079e0c | ||
|
|
e14baf17a7 | ||
|
|
542b18a585 | ||
|
|
37d88cc05b | ||
|
|
b652d1a8f1 | ||
|
|
98b7a941f9 | ||
|
|
dc131ee05b | ||
|
|
0e3d15215d | ||
|
|
816b6abf3b | ||
|
|
6ecaa661a7 | ||
|
|
0b1efb173f | ||
|
|
aceee7d2e2 | ||
|
|
0a41eff435 | ||
|
|
5f681973a0 | ||
|
|
9c65657a66 | ||
|
|
6fab75f9ab | ||
|
|
111070bdb9 | ||
|
|
d76840ff6f | ||
|
|
d4b6f4faf1 | ||
|
|
fe9519314a | ||
|
|
e141091e99 | ||
|
|
40b8b0ac35 | ||
|
|
47d6b152a0 | ||
|
|
216a7c8fda | ||
|
|
e7468292c7 | ||
|
|
7bff8d2ec5 | ||
|
|
6f2cce081f | ||
|
|
a86dafc11c | ||
|
|
81256d6373 | ||
|
|
c9792da4a1 | ||
|
|
37df0bcdd8 | ||
|
|
18c86bd8cc | ||
|
|
0ee6c20272 | ||
|
|
184db0156c | ||
|
|
b7a0f3c693 | ||
|
|
e40f38e2e1 | ||
|
|
05e7afab8d | ||
|
|
969a51f425 | ||
|
|
849bc92aea | ||
|
|
a37dec45d9 | ||
|
|
e1793dda74 | ||
|
|
31fdaf2659 | ||
|
|
8a1a6c6f62 | ||
|
|
3d83101f69 | ||
|
|
d473ba9d0d | ||
|
|
e5902152ab | ||
|
|
f03a9a6de8 | ||
|
|
b7e2f1e57a | ||
|
|
885de6f679 | ||
|
|
ce9714ff12 | ||
|
|
74b141eea2 | ||
|
|
08425c9614 | ||
|
|
ecfcc03ef0 | ||
|
|
829c08f0ee | ||
|
|
2edd2d947e | ||
|
|
5b67f27345 | ||
|
|
7684a2c091 | ||
|
|
4e2bf24e8d | ||
|
|
aafac9ed1d | ||
|
|
3adf48e429 | ||
|
|
1c546e3cc5 | ||
|
|
5c7382d894 | ||
|
|
1f8ae3a5ad | ||
|
|
f1ed0c979c | ||
|
|
78b29eb81b | ||
|
|
8d3a0a2eec | ||
|
|
910d1a1de3 | ||
|
|
6004f29bbc | ||
|
|
9d0056cfee | ||
|
|
4bb99d5fbf | ||
|
|
6c61fbd102 | ||
|
|
2605b938f0 | ||
|
|
b4f51cb5af | ||
|
|
fe80ca3e1c | ||
|
|
b78e83d81b | ||
|
|
ee68c3ae86 | ||
|
|
239f68e260 | ||
|
|
f949d57fa0 | ||
|
|
dadd2cf39b | ||
|
|
13d7832d35 | ||
|
|
0f892d4670 | ||
|
|
e8945f244d | ||
|
|
a0588be405 | ||
|
|
29a0ba4ddc | ||
|
|
644073f063 | ||
|
|
63a15f2bf9 |
@@ -9,6 +9,7 @@ on:
|
||||
- preview
|
||||
- qa
|
||||
- develop
|
||||
- release-*
|
||||
release:
|
||||
types: [released, prereleased]
|
||||
|
||||
@@ -62,14 +63,14 @@ jobs:
|
||||
runs-on: ubuntu-20.04
|
||||
needs: [branch_build_setup]
|
||||
env:
|
||||
FRONTEND_TAG: ${{ secrets.DOCKERHUB_USERNAME }}/plane-frontend:${{ needs.branch_build_setup.outputs.gh_branch_name }}
|
||||
FRONTEND_TAG: ${{ secrets.DOCKERHUB_USERNAME }}/plane-frontend${{ secrets.DOCKER_REPO_SUFFIX || '' }}:${{ needs.branch_build_setup.outputs.gh_branch_name }}
|
||||
steps:
|
||||
- name: Set Frontend Docker Tag
|
||||
run: |
|
||||
if [ "${{ needs.branch_build_setup.outputs.gh_branch_name }}" == "master" ] && [ "${{ github.event_name }}" == "release" ]; then
|
||||
TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-frontend:latest,${{ secrets.DOCKERHUB_USERNAME }}/plane-frontend:${{ github.event.release.tag_name }}
|
||||
TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-frontend${{ secrets.DOCKER_REPO_SUFFIX || '' }}:latest,${{ secrets.DOCKERHUB_USERNAME }}/plane-frontend${{ secrets.DOCKER_REPO_SUFFIX || '' }}:${{ github.event.release.tag_name }}
|
||||
elif [ "${{ needs.branch_build_setup.outputs.gh_branch_name }}" == "master" ]; then
|
||||
TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-frontend:stable
|
||||
TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-frontend${{ secrets.DOCKER_REPO_SUFFIX || '' }}:stable
|
||||
else
|
||||
TAG=${{ env.FRONTEND_TAG }}
|
||||
fi
|
||||
@@ -104,14 +105,14 @@ jobs:
|
||||
runs-on: ubuntu-20.04
|
||||
needs: [branch_build_setup]
|
||||
env:
|
||||
SPACE_TAG: ${{ secrets.DOCKERHUB_USERNAME }}/plane-space:${{ needs.branch_build_setup.outputs.gh_branch_name }}
|
||||
SPACE_TAG: ${{ secrets.DOCKERHUB_USERNAME }}/plane-space${{ secrets.DOCKER_REPO_SUFFIX || '' }}:${{ needs.branch_build_setup.outputs.gh_branch_name }}
|
||||
steps:
|
||||
- name: Set Space Docker Tag
|
||||
run: |
|
||||
if [ "${{ needs.branch_build_setup.outputs.gh_branch_name }}" == "master" ] && [ "${{ github.event_name }}" == "release" ]; then
|
||||
TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-space:latest,${{ secrets.DOCKERHUB_USERNAME }}/plane-space:${{ github.event.release.tag_name }}
|
||||
TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-space${{ secrets.DOCKER_REPO_SUFFIX || '' }}:latest,${{ secrets.DOCKERHUB_USERNAME }}/plane-space${{ secrets.DOCKER_REPO_SUFFIX || '' }}:${{ github.event.release.tag_name }}
|
||||
elif [ "${{ needs.branch_build_setup.outputs.gh_branch_name }}" == "master" ]; then
|
||||
TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-space:stable
|
||||
TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-space${{ secrets.DOCKER_REPO_SUFFIX || '' }}:stable
|
||||
else
|
||||
TAG=${{ env.SPACE_TAG }}
|
||||
fi
|
||||
@@ -146,14 +147,14 @@ jobs:
|
||||
runs-on: ubuntu-20.04
|
||||
needs: [branch_build_setup]
|
||||
env:
|
||||
BACKEND_TAG: ${{ secrets.DOCKERHUB_USERNAME }}/plane-backend:${{ needs.branch_build_setup.outputs.gh_branch_name }}
|
||||
BACKEND_TAG: ${{ secrets.DOCKERHUB_USERNAME }}/plane-backend${{ secrets.DOCKER_REPO_SUFFIX || '' }}:${{ needs.branch_build_setup.outputs.gh_branch_name }}
|
||||
steps:
|
||||
- name: Set Backend Docker Tag
|
||||
run: |
|
||||
if [ "${{ needs.branch_build_setup.outputs.gh_branch_name }}" == "master" ] && [ "${{ github.event_name }}" == "release" ]; then
|
||||
TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-backend:latest,${{ secrets.DOCKERHUB_USERNAME }}/plane-backend:${{ github.event.release.tag_name }}
|
||||
TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-backend${{ secrets.DOCKER_REPO_SUFFIX || '' }}:latest,${{ secrets.DOCKERHUB_USERNAME }}/plane-backend${{ secrets.DOCKER_REPO_SUFFIX || '' }}:${{ github.event.release.tag_name }}
|
||||
elif [ "${{ needs.branch_build_setup.outputs.gh_branch_name }}" == "master" ]; then
|
||||
TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-backend:stable
|
||||
TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-backend${{ secrets.DOCKER_REPO_SUFFIX || '' }}:stable
|
||||
else
|
||||
TAG=${{ env.BACKEND_TAG }}
|
||||
fi
|
||||
@@ -188,14 +189,14 @@ jobs:
|
||||
runs-on: ubuntu-20.04
|
||||
needs: [branch_build_setup]
|
||||
env:
|
||||
PROXY_TAG: ${{ secrets.DOCKERHUB_USERNAME }}/plane-proxy:${{ needs.branch_build_setup.outputs.gh_branch_name }}
|
||||
PROXY_TAG: ${{ secrets.DOCKERHUB_USERNAME }}/plane-proxy${{ secrets.DOCKER_REPO_SUFFIX || '' }}:${{ needs.branch_build_setup.outputs.gh_branch_name }}
|
||||
steps:
|
||||
- name: Set Proxy Docker Tag
|
||||
run: |
|
||||
if [ "${{ needs.branch_build_setup.outputs.gh_branch_name }}" == "master" ] && [ "${{ github.event_name }}" == "release" ]; then
|
||||
TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-proxy:latest,${{ secrets.DOCKERHUB_USERNAME }}/plane-proxy:${{ github.event.release.tag_name }}
|
||||
TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-proxy${{ secrets.DOCKER_REPO_SUFFIX || '' }}:latest,${{ secrets.DOCKERHUB_USERNAME }}/plane-proxy${{ secrets.DOCKER_REPO_SUFFIX || '' }}:${{ github.event.release.tag_name }}
|
||||
elif [ "${{ needs.branch_build_setup.outputs.gh_branch_name }}" == "master" ]; then
|
||||
TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-proxy:stable
|
||||
TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-proxy${{ secrets.DOCKER_REPO_SUFFIX || '' }}:stable
|
||||
else
|
||||
TAG=${{ env.PROXY_TAG }}
|
||||
fi
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
name: Build Pull Request Contents
|
||||
|
||||
on:
|
||||
on:
|
||||
pull_request:
|
||||
types: ["opened", "synchronize"]
|
||||
|
||||
@@ -14,13 +14,15 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout Repository to Actions
|
||||
uses: actions/checkout@v3.3.0
|
||||
with:
|
||||
token: ${{ secrets.ACCESS_TOKEN }}
|
||||
|
||||
- name: Setup Node.js 18.x
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 18.x
|
||||
cache: 'yarn'
|
||||
|
||||
cache: "yarn"
|
||||
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@v38
|
||||
@@ -44,5 +46,3 @@ jobs:
|
||||
run: |
|
||||
yarn
|
||||
yarn build --filter=space
|
||||
|
||||
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
name: Create PR in Plane EE Repository to sync the changes
|
||||
name: Create Sync Action
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- preview
|
||||
types:
|
||||
- closed
|
||||
env:
|
||||
SOURCE_BRANCH_NAME: ${{github.event.pull_request.base.ref}}
|
||||
|
||||
jobs:
|
||||
create_pr:
|
||||
@@ -16,27 +18,13 @@ jobs:
|
||||
pull-requests: write
|
||||
contents: read
|
||||
steps:
|
||||
- name: Check SOURCE_REPO
|
||||
id: check_repo
|
||||
env:
|
||||
SOURCE_REPO: ${{ secrets.SOURCE_REPO_NAME }}
|
||||
run: |
|
||||
echo "::set-output name=is_correct_repo::$(if [[ "$SOURCE_REPO" == "makeplane/plane" ]]; then echo 'true'; else echo 'false'; fi)"
|
||||
|
||||
- name: Checkout Code
|
||||
if: steps.check_repo.outputs.is_correct_repo == 'true'
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Branch Name
|
||||
if: steps.check_repo.outputs.is_correct_repo == 'true'
|
||||
run: |
|
||||
echo "SOURCE_BRANCH_NAME=${{ github.head_ref }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Setup GH CLI
|
||||
if: steps.check_repo.outputs.is_correct_repo == 'true'
|
||||
run: |
|
||||
type -p curl >/dev/null || (sudo apt update && sudo apt install curl -y)
|
||||
curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg
|
||||
@@ -45,35 +33,14 @@ jobs:
|
||||
sudo apt update
|
||||
sudo apt install gh -y
|
||||
|
||||
- name: Create Pull Request
|
||||
if: steps.check_repo.outputs.is_correct_repo == 'true'
|
||||
- name: Push Changes to Target Repo
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.ACCESS_TOKEN }}
|
||||
run: |
|
||||
TARGET_REPO="${{ secrets.TARGET_REPO_NAME }}"
|
||||
TARGET_BRANCH="${{ secrets.TARGET_REPO_BRANCH }}"
|
||||
TARGET_REPO="${{ secrets.SYNC_TARGET_REPO_NAME }}"
|
||||
TARGET_BRANCH="${{ secrets.SYNC_TARGET_BRANCH_NAME }}"
|
||||
SOURCE_BRANCH="${{ env.SOURCE_BRANCH_NAME }}"
|
||||
|
||||
git checkout $SOURCE_BRANCH
|
||||
git remote add target "https://$GH_TOKEN@github.com/$TARGET_REPO.git"
|
||||
git push target $SOURCE_BRANCH:$SOURCE_BRANCH
|
||||
|
||||
PR_TITLE="${{ github.event.pull_request.title }}"
|
||||
PR_BODY="${{ github.event.pull_request.body }}"
|
||||
|
||||
# Remove double quotes
|
||||
PR_TITLE_CLEANED="${PR_TITLE//\"/}"
|
||||
PR_BODY_CLEANED="${PR_BODY//\"/}"
|
||||
|
||||
# Construct PR_BODY_CONTENT using a here-document
|
||||
PR_BODY_CONTENT=$(cat <<EOF
|
||||
$PR_BODY_CLEANED
|
||||
EOF
|
||||
)
|
||||
|
||||
gh pr create \
|
||||
--base $TARGET_BRANCH \
|
||||
--head $SOURCE_BRANCH \
|
||||
--title "[SYNC] $PR_TITLE_CLEANED" \
|
||||
--body "$PR_BODY_CONTENT" \
|
||||
--repo $TARGET_REPO
|
||||
git remote add target-origin "https://$GH_TOKEN@github.com/$TARGET_REPO.git"
|
||||
git push target-origin $SOURCE_BRANCH:$TARGET_BRANCH
|
||||
+4
-25
@@ -33,8 +33,8 @@ The backend is a django project which is kept inside apiserver
|
||||
1. Clone the repo
|
||||
|
||||
```bash
|
||||
git clone https://github.com/makeplane/plane
|
||||
cd plane
|
||||
git clone https://github.com/makeplane/plane.git [folder-name]
|
||||
cd [folder-name]
|
||||
chmod +x setup.sh
|
||||
```
|
||||
|
||||
@@ -44,33 +44,12 @@ chmod +x setup.sh
|
||||
./setup.sh
|
||||
```
|
||||
|
||||
3. Define `NEXT_PUBLIC_API_BASE_URL=http://localhost` in **web/.env** and **space/.env** file
|
||||
3. Start the containers
|
||||
|
||||
```bash
|
||||
echo "\nNEXT_PUBLIC_API_BASE_URL=http://localhost\n" >> ./web/.env
|
||||
docker compose -f docker-compose-local.yml up
|
||||
```
|
||||
|
||||
```bash
|
||||
echo "\nNEXT_PUBLIC_API_BASE_URL=http://localhost\n" >> ./space/.env
|
||||
```
|
||||
|
||||
4. Run Docker compose up
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
5. Install dependencies
|
||||
|
||||
```bash
|
||||
yarn install
|
||||
```
|
||||
|
||||
6. Run the web app in development mode
|
||||
|
||||
```bash
|
||||
yarn dev
|
||||
```
|
||||
|
||||
## Missing a Feature?
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ Meet [Plane](https://plane.so). An open-source software development tool to mana
|
||||
|
||||
> Plane is still in its early days, not everything will be perfect yet, and hiccups may happen. Please let us know of any suggestions, ideas, or bugs that you encounter on our [Discord](https://discord.com/invite/A92xrEGCge) or GitHub issues, and we will use your feedback to improve on our upcoming releases.
|
||||
|
||||
The easiest way to get started with Plane is by creating a [Plane Cloud](https://app.plane.so) account. Plane Cloud offers a hosted solution for Plane. If you prefer to self-host Plane, please refer to our [deployment documentation](https://docs.plane.so/self-hosting).
|
||||
The easiest way to get started with Plane is by creating a [Plane Cloud](https://app.plane.so) account. Plane Cloud offers a hosted solution for Plane. If you prefer to self-host Plane, please refer to our [deployment documentation](https://docs.plane.so/self-hosting/docker-compose).
|
||||
|
||||
## ⚡️ Contributors Quick Start
|
||||
|
||||
@@ -63,7 +63,7 @@ Thats it!
|
||||
|
||||
## 🍙 Self Hosting
|
||||
|
||||
For self hosting environment setup, visit the [Self Hosting](https://docs.plane.so/self-hosting) documentation page
|
||||
For self hosting environment setup, visit the [Self Hosting](https://docs.plane.so/self-hosting/docker-compose) documentation page
|
||||
|
||||
## 🚀 Features
|
||||
|
||||
|
||||
@@ -49,5 +49,5 @@ USER captain
|
||||
# Expose container port and run entry point script
|
||||
EXPOSE 8000
|
||||
|
||||
# CMD [ "./bin/takeoff" ]
|
||||
CMD [ "./bin/takeoff.local" ]
|
||||
|
||||
|
||||
Executable
+31
@@ -0,0 +1,31 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
python manage.py wait_for_db
|
||||
python manage.py migrate
|
||||
|
||||
# Create the default bucket
|
||||
#!/bin/bash
|
||||
|
||||
# Collect system information
|
||||
HOSTNAME=$(hostname)
|
||||
MAC_ADDRESS=$(ip link show | awk '/ether/ {print $2}' | head -n 1)
|
||||
CPU_INFO=$(cat /proc/cpuinfo)
|
||||
MEMORY_INFO=$(free -h)
|
||||
DISK_INFO=$(df -h)
|
||||
|
||||
# Concatenate information and compute SHA-256 hash
|
||||
SIGNATURE=$(echo "$HOSTNAME$MAC_ADDRESS$CPU_INFO$MEMORY_INFO$DISK_INFO" | sha256sum | awk '{print $1}')
|
||||
|
||||
# Export the variables
|
||||
export MACHINE_SIGNATURE=$SIGNATURE
|
||||
|
||||
# Register instance
|
||||
python manage.py register_instance $MACHINE_SIGNATURE
|
||||
# Load the configuration variable
|
||||
python manage.py configure_instance
|
||||
|
||||
# Create the default bucket
|
||||
python manage.py create_bucket
|
||||
|
||||
python manage.py runserver 0.0.0.0:8000 --settings=plane.settings.local
|
||||
|
||||
@@ -96,7 +96,7 @@ class IssueSerializer(BaseSerializer):
|
||||
if (
|
||||
data.get("state")
|
||||
and not State.objects.filter(
|
||||
project_id=self.context.get("project_id"), pk=data.get("state")
|
||||
project_id=self.context.get("project_id"), pk=data.get("state").id
|
||||
).exists()
|
||||
):
|
||||
raise serializers.ValidationError(
|
||||
@@ -107,7 +107,7 @@ class IssueSerializer(BaseSerializer):
|
||||
if (
|
||||
data.get("parent")
|
||||
and not Issue.objects.filter(
|
||||
workspace_id=self.context.get("workspace_id"), pk=data.get("parent")
|
||||
workspace_id=self.context.get("workspace_id"), pk=data.get("parent").id
|
||||
).exists()
|
||||
):
|
||||
raise serializers.ValidationError(
|
||||
|
||||
@@ -65,18 +65,18 @@ class ModuleSerializer(BaseSerializer):
|
||||
def create(self, validated_data):
|
||||
members = validated_data.pop("members", None)
|
||||
|
||||
project = self.context["project"]
|
||||
|
||||
module = Module.objects.create(**validated_data, project=project)
|
||||
project_id = self.context["project_id"]
|
||||
workspace_id = self.context["workspace_id"]
|
||||
|
||||
module = Module.objects.create(**validated_data, project_id=project_id)
|
||||
if members is not None:
|
||||
ModuleMember.objects.bulk_create(
|
||||
[
|
||||
ModuleMember(
|
||||
module=module,
|
||||
member=member,
|
||||
project=project,
|
||||
workspace=project.workspace,
|
||||
member_id=str(member),
|
||||
project_id=project_id,
|
||||
workspace_id=workspace_id,
|
||||
created_by=module.created_by,
|
||||
updated_by=module.updated_by,
|
||||
)
|
||||
@@ -97,7 +97,7 @@ class ModuleSerializer(BaseSerializer):
|
||||
[
|
||||
ModuleMember(
|
||||
module=instance,
|
||||
member=member,
|
||||
member_id=str(member),
|
||||
project=instance.project,
|
||||
workspace=instance.project.workspace,
|
||||
created_by=instance.created_by,
|
||||
|
||||
@@ -221,11 +221,20 @@ class IssueAPIEndpoint(WebhookMixin, BaseAPIView):
|
||||
|
||||
def patch(self, request, slug, project_id, pk=None):
|
||||
issue = Issue.objects.get(workspace__slug=slug, project_id=project_id, pk=pk)
|
||||
project = Project.objects.get(pk=project_id)
|
||||
current_instance = json.dumps(
|
||||
IssueSerializer(issue).data, cls=DjangoJSONEncoder
|
||||
)
|
||||
requested_data = json.dumps(self.request.data, cls=DjangoJSONEncoder)
|
||||
serializer = IssueSerializer(issue, data=request.data, partial=True)
|
||||
serializer = IssueSerializer(
|
||||
issue,
|
||||
data=request.data,
|
||||
context={
|
||||
"project_id": project_id,
|
||||
"workspace_id": project.workspace_id,
|
||||
},
|
||||
partial=True,
|
||||
)
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
issue_activity.delay(
|
||||
|
||||
@@ -121,8 +121,8 @@ class ModuleAPIEndpoint(WebhookMixin, BaseAPIView):
|
||||
)
|
||||
|
||||
def post(self, request, slug, project_id):
|
||||
project = Project.objects.get(workspace__slug=slug, pk=project_id)
|
||||
serializer = ModuleSerializer(data=request.data, context={"project": project})
|
||||
project = Project.objects.get(pk=project_id, workspace__slug=slug)
|
||||
serializer = ModuleSerializer(data=request.data, context={"project_id": project_id, "workspace_id": project.workspace_id})
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
module = Module.objects.get(pk=serializer.data["id"])
|
||||
@@ -132,7 +132,7 @@ class ModuleAPIEndpoint(WebhookMixin, BaseAPIView):
|
||||
|
||||
def patch(self, request, slug, project_id, pk):
|
||||
module = Module.objects.get(pk=pk, project_id=project_id, workspace__slug=slug)
|
||||
serializer = ModuleSerializer(module, data=request.data)
|
||||
serializer = ModuleSerializer(module, data=request.data, context={"project_id": project_id}, partial=True)
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
|
||||
@@ -40,6 +40,7 @@ class CycleSerializer(BaseSerializer):
|
||||
started_estimates = serializers.IntegerField(read_only=True)
|
||||
workspace_detail = WorkspaceLiteSerializer(read_only=True, source="workspace")
|
||||
project_detail = ProjectLiteSerializer(read_only=True, source="project")
|
||||
status = serializers.CharField(read_only=True)
|
||||
|
||||
def validate(self, data):
|
||||
if (
|
||||
|
||||
@@ -4,6 +4,7 @@ from .base import BaseSerializer
|
||||
from plane.db.models import Estimate, EstimatePoint
|
||||
from plane.app.serializers import WorkspaceLiteSerializer, ProjectLiteSerializer
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
class EstimateSerializer(BaseSerializer):
|
||||
workspace_detail = WorkspaceLiteSerializer(read_only=True, source="workspace")
|
||||
@@ -19,6 +20,15 @@ class EstimateSerializer(BaseSerializer):
|
||||
|
||||
|
||||
class EstimatePointSerializer(BaseSerializer):
|
||||
|
||||
def validate(self, data):
|
||||
if not data:
|
||||
raise serializers.ValidationError("Estimate points are required")
|
||||
value = data.get("value")
|
||||
if value and len(value) > 20:
|
||||
raise serializers.ValidationError("Value can't be more than 20 characters")
|
||||
return data
|
||||
|
||||
class Meta:
|
||||
model = EstimatePoint
|
||||
fields = "__all__"
|
||||
|
||||
@@ -11,6 +11,10 @@ from django.db.models import (
|
||||
Count,
|
||||
Prefetch,
|
||||
Sum,
|
||||
Case,
|
||||
When,
|
||||
Value,
|
||||
CharField
|
||||
)
|
||||
from django.core import serializers
|
||||
from django.utils import timezone
|
||||
@@ -157,6 +161,28 @@ class CycleViewSet(WebhookMixin, BaseViewSet):
|
||||
),
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
status=Case(
|
||||
When(
|
||||
Q(start_date__lte=timezone.now()) & Q(end_date__gte=timezone.now()),
|
||||
then=Value("CURRENT")
|
||||
),
|
||||
When(
|
||||
start_date__gt=timezone.now(),
|
||||
then=Value("UPCOMING")
|
||||
),
|
||||
When(
|
||||
end_date__lt=timezone.now(),
|
||||
then=Value("COMPLETED")
|
||||
),
|
||||
When(
|
||||
Q(start_date__isnull=True) & Q(end_date__isnull=True),
|
||||
then=Value("DRAFT")
|
||||
),
|
||||
default=Value("DRAFT"),
|
||||
output_field=CharField(),
|
||||
)
|
||||
)
|
||||
.prefetch_related(
|
||||
Prefetch(
|
||||
"issue_cycle__issue__assignees",
|
||||
@@ -177,7 +203,7 @@ class CycleViewSet(WebhookMixin, BaseViewSet):
|
||||
queryset = self.get_queryset()
|
||||
cycle_view = request.GET.get("cycle_view", "all")
|
||||
|
||||
queryset = queryset.order_by("-is_favorite","-created_at")
|
||||
queryset = queryset.order_by("-is_favorite", "-created_at")
|
||||
|
||||
# Current Cycle
|
||||
if cycle_view == "current":
|
||||
@@ -575,7 +601,9 @@ class CycleIssueViewSet(WebhookMixin, BaseViewSet):
|
||||
)
|
||||
)
|
||||
|
||||
issues = IssueStateSerializer(issues, many=True, fields=fields if fields else None).data
|
||||
issues = IssueStateSerializer(
|
||||
issues, 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)
|
||||
|
||||
@@ -805,4 +833,4 @@ class TransferCycleIssueEndpoint(BaseAPIView):
|
||||
updated_cycles, ["cycle_id"], batch_size=100
|
||||
)
|
||||
|
||||
return Response({"message": "Success"}, status=status.HTTP_200_OK)
|
||||
return Response({"message": "Success"}, status=status.HTTP_200_OK)
|
||||
|
||||
@@ -53,11 +53,11 @@ class BulkEstimatePointEndpoint(BaseViewSet):
|
||||
)
|
||||
|
||||
estimate_points = request.data.get("estimate_points", [])
|
||||
|
||||
if not len(estimate_points) or len(estimate_points) > 8:
|
||||
|
||||
serializer = EstimatePointSerializer(data=request.data.get("estimate_points"), many=True)
|
||||
if not serializer.is_valid():
|
||||
return Response(
|
||||
{"error": "Estimate points are required"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
serializer.errors, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
estimate_serializer = EstimateSerializer(data=request.data.get("estimate"))
|
||||
|
||||
@@ -145,6 +145,16 @@ class ProjectViewSet(WebhookMixin, BaseViewSet):
|
||||
)
|
||||
)
|
||||
)
|
||||
.prefetch_related(
|
||||
Prefetch(
|
||||
"project_projectmember",
|
||||
queryset=ProjectMember.objects.filter(
|
||||
workspace__slug=self.kwargs.get("slug"),
|
||||
is_active=True,
|
||||
).select_related("member"),
|
||||
to_attr="members_list",
|
||||
)
|
||||
)
|
||||
.distinct()
|
||||
)
|
||||
|
||||
@@ -160,16 +170,6 @@ class ProjectViewSet(WebhookMixin, BaseViewSet):
|
||||
projects = (
|
||||
self.get_queryset()
|
||||
.annotate(sort_order=Subquery(sort_order_query))
|
||||
.prefetch_related(
|
||||
Prefetch(
|
||||
"project_projectmember",
|
||||
queryset=ProjectMember.objects.filter(
|
||||
workspace__slug=slug,
|
||||
is_active=True,
|
||||
).select_related("member"),
|
||||
to_attr="members_list",
|
||||
)
|
||||
)
|
||||
.order_by("sort_order", "name")
|
||||
)
|
||||
if request.GET.get("per_page", False) and request.GET.get("cursor", False):
|
||||
@@ -679,6 +679,25 @@ class ProjectMemberViewSet(BaseViewSet):
|
||||
)
|
||||
)
|
||||
|
||||
# Check if the user is already a member of the project and is inactive
|
||||
if ProjectMember.objects.filter(
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
member_id=member.get("member_id"),
|
||||
is_active=False,
|
||||
).exists():
|
||||
member_detail = ProjectMember.objects.get(
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
member_id=member.get("member_id"),
|
||||
is_active=False,
|
||||
)
|
||||
# Check if the user has not deactivated the account
|
||||
user = User.objects.filter(pk=member.get("member_id")).first()
|
||||
if user.is_active:
|
||||
member_detail.is_active = True
|
||||
member_detail.save(update_fields=["is_active"])
|
||||
|
||||
project_members = ProjectMember.objects.bulk_create(
|
||||
bulk_project_members,
|
||||
batch_size=10,
|
||||
@@ -991,11 +1010,18 @@ class ProjectPublicCoverImagesEndpoint(BaseAPIView):
|
||||
|
||||
def get(self, request):
|
||||
files = []
|
||||
s3 = boto3.client(
|
||||
"s3",
|
||||
aws_access_key_id=settings.AWS_ACCESS_KEY_ID,
|
||||
aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY,
|
||||
)
|
||||
s3_client_params = {
|
||||
"service_name": "s3",
|
||||
"aws_access_key_id": settings.AWS_ACCESS_KEY_ID,
|
||||
"aws_secret_access_key": settings.AWS_SECRET_ACCESS_KEY,
|
||||
}
|
||||
|
||||
# Use AWS_S3_ENDPOINT_URL if it is present in the settings
|
||||
if hasattr(settings, "AWS_S3_ENDPOINT_URL") and settings.AWS_S3_ENDPOINT_URL:
|
||||
s3_client_params["endpoint_url"] = settings.AWS_S3_ENDPOINT_URL
|
||||
|
||||
s3 = boto3.client(**s3_client_params)
|
||||
|
||||
params = {
|
||||
"Bucket": settings.AWS_STORAGE_BUCKET_NAME,
|
||||
"Prefix": "static/project-cover/",
|
||||
@@ -1008,9 +1034,19 @@ class ProjectPublicCoverImagesEndpoint(BaseAPIView):
|
||||
if not content["Key"].endswith(
|
||||
"/"
|
||||
): # This line ensures we're only getting files, not "sub-folders"
|
||||
files.append(
|
||||
f"https://{settings.AWS_STORAGE_BUCKET_NAME}.s3.{settings.AWS_REGION}.amazonaws.com/{content['Key']}"
|
||||
)
|
||||
if (
|
||||
hasattr(settings, "AWS_S3_CUSTOM_DOMAIN")
|
||||
and settings.AWS_S3_CUSTOM_DOMAIN
|
||||
and hasattr(settings, "AWS_S3_URL_PROTOCOL")
|
||||
and settings.AWS_S3_URL_PROTOCOL
|
||||
):
|
||||
files.append(
|
||||
f"{settings.AWS_S3_URL_PROTOCOL}//{settings.AWS_S3_CUSTOM_DOMAIN}/{content['Key']}"
|
||||
)
|
||||
else:
|
||||
files.append(
|
||||
f"https://{settings.AWS_STORAGE_BUCKET_NAME}.s3.{settings.AWS_REGION}.amazonaws.com/{content['Key']}"
|
||||
)
|
||||
|
||||
return Response(files, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
@@ -50,7 +50,8 @@ class GlobalSearchEndpoint(BaseAPIView):
|
||||
q = Q()
|
||||
for field in fields:
|
||||
if field == "sequence_id":
|
||||
sequences = re.findall(r"\d+\.\d+|\d+", query)
|
||||
# Match whole integers only (exclude decimal numbers)
|
||||
sequences = re.findall(r"\b\d+\b", query)
|
||||
for sequence_id in sequences:
|
||||
q |= Q(**{"sequence_id": sequence_id})
|
||||
else:
|
||||
|
||||
@@ -70,6 +70,7 @@ from plane.app.permissions import (
|
||||
WorkSpaceAdminPermission,
|
||||
WorkspaceEntityPermission,
|
||||
WorkspaceViewerPermission,
|
||||
WorkspaceUserPermission,
|
||||
)
|
||||
from plane.bgtasks.workspace_invitation_task import workspace_invitation
|
||||
from plane.utils.issue_filters import issue_filters
|
||||
@@ -495,6 +496,18 @@ class WorkSpaceMemberViewSet(BaseViewSet):
|
||||
WorkspaceEntityPermission,
|
||||
]
|
||||
|
||||
def get_permissions(self):
|
||||
if self.action == "leave":
|
||||
self.permission_classes = [
|
||||
WorkspaceUserPermission,
|
||||
]
|
||||
else:
|
||||
self.permission_classes = [
|
||||
WorkspaceEntityPermission,
|
||||
]
|
||||
|
||||
return super(WorkSpaceMemberViewSet, self).get_permissions()
|
||||
|
||||
search_fields = [
|
||||
"member__display_name",
|
||||
"member__first_name",
|
||||
|
||||
@@ -65,7 +65,7 @@ def send_export_email(email, slug, csv_buffer, rows):
|
||||
port=int(EMAIL_PORT),
|
||||
username=EMAIL_HOST_USER,
|
||||
password=EMAIL_HOST_PASSWORD,
|
||||
use_tls=bool(EMAIL_USE_TLS),
|
||||
use_tls=EMAIL_USE_TLS == "1",
|
||||
)
|
||||
|
||||
msg = EmailMultiAlternatives(
|
||||
|
||||
@@ -51,7 +51,7 @@ def forgot_password(first_name, email, uidb64, token, current_site):
|
||||
port=int(EMAIL_PORT),
|
||||
username=EMAIL_HOST_USER,
|
||||
password=EMAIL_HOST_PASSWORD,
|
||||
use_tls=bool(EMAIL_USE_TLS),
|
||||
use_tls=EMAIL_USE_TLS == "1",
|
||||
)
|
||||
|
||||
msg = EmailMultiAlternatives(
|
||||
|
||||
@@ -112,8 +112,8 @@ def track_parent(
|
||||
epoch,
|
||||
):
|
||||
if current_instance.get("parent") != requested_data.get("parent"):
|
||||
old_parent = Issue.objects.filter(pk=current_instance.get("parent")).first()
|
||||
new_parent = Issue.objects.filter(pk=requested_data.get("parent")).first()
|
||||
old_parent = Issue.objects.filter(pk=current_instance.get("parent")).first() if current_instance.get("parent") is not None else None
|
||||
new_parent = Issue.objects.filter(pk=requested_data.get("parent")).first() if requested_data.get("parent") is not None else None
|
||||
|
||||
issue_activities.append(
|
||||
IssueActivity(
|
||||
|
||||
@@ -41,7 +41,7 @@ def magic_link(email, key, token, current_site):
|
||||
port=int(EMAIL_PORT),
|
||||
username=EMAIL_HOST_USER,
|
||||
password=EMAIL_HOST_PASSWORD,
|
||||
use_tls=bool(EMAIL_USE_TLS),
|
||||
use_tls=EMAIL_USE_TLS == "1",
|
||||
)
|
||||
|
||||
msg = EmailMultiAlternatives(
|
||||
|
||||
@@ -60,7 +60,7 @@ def project_invitation(email, project_id, token, current_site, invitor):
|
||||
port=int(EMAIL_PORT),
|
||||
username=EMAIL_HOST_USER,
|
||||
password=EMAIL_HOST_PASSWORD,
|
||||
use_tls=bool(EMAIL_USE_TLS),
|
||||
use_tls=EMAIL_USE_TLS == "1",
|
||||
)
|
||||
|
||||
msg = EmailMultiAlternatives(
|
||||
|
||||
@@ -70,7 +70,7 @@ def workspace_invitation(email, workspace_id, token, current_site, invitor):
|
||||
port=int(EMAIL_PORT),
|
||||
username=EMAIL_HOST_USER,
|
||||
password=EMAIL_HOST_PASSWORD,
|
||||
use_tls=bool(EMAIL_USE_TLS),
|
||||
use_tls=EMAIL_USE_TLS == "1",
|
||||
)
|
||||
|
||||
msg = EmailMultiAlternatives(
|
||||
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
# Generated by Django 4.2.7 on 2023-12-29 10:20
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('db', '0050_user_use_case_alter_workspace_organization_size'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='cycle',
|
||||
name='external_id',
|
||||
field=models.CharField(blank=True, max_length=255, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='cycle',
|
||||
name='external_source',
|
||||
field=models.CharField(blank=True, max_length=255, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='inboxissue',
|
||||
name='external_id',
|
||||
field=models.CharField(blank=True, max_length=255, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='inboxissue',
|
||||
name='external_source',
|
||||
field=models.CharField(blank=True, max_length=255, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='issue',
|
||||
name='external_id',
|
||||
field=models.CharField(blank=True, max_length=255, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='issue',
|
||||
name='external_source',
|
||||
field=models.CharField(blank=True, max_length=255, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='issuecomment',
|
||||
name='external_id',
|
||||
field=models.CharField(blank=True, max_length=255, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='issuecomment',
|
||||
name='external_source',
|
||||
field=models.CharField(blank=True, max_length=255, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='label',
|
||||
name='external_id',
|
||||
field=models.CharField(blank=True, max_length=255, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='label',
|
||||
name='external_source',
|
||||
field=models.CharField(blank=True, max_length=255, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='module',
|
||||
name='external_id',
|
||||
field=models.CharField(blank=True, max_length=255, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='module',
|
||||
name='external_source',
|
||||
field=models.CharField(blank=True, max_length=255, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='state',
|
||||
name='external_id',
|
||||
field=models.CharField(blank=True, max_length=255, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='state',
|
||||
name='external_source',
|
||||
field=models.CharField(blank=True, max_length=255, null=True),
|
||||
),
|
||||
]
|
||||
@@ -18,6 +18,8 @@ class Cycle(ProjectBaseModel):
|
||||
)
|
||||
view_props = models.JSONField(default=dict)
|
||||
sort_order = models.FloatField(default=65535)
|
||||
external_source = models.CharField(max_length=255, null=True, blank=True)
|
||||
external_id = models.CharField(max_length=255, blank=True, null=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Cycle"
|
||||
|
||||
@@ -39,6 +39,8 @@ class InboxIssue(ProjectBaseModel):
|
||||
"db.Issue", related_name="inbox_duplicate", on_delete=models.SET_NULL, null=True
|
||||
)
|
||||
source = models.TextField(blank=True, null=True)
|
||||
external_source = models.CharField(max_length=255, null=True, blank=True)
|
||||
external_id = models.CharField(max_length=255, blank=True, null=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "InboxIssue"
|
||||
|
||||
@@ -102,6 +102,8 @@ class Issue(ProjectBaseModel):
|
||||
completed_at = models.DateTimeField(null=True)
|
||||
archived_at = models.DateField(null=True)
|
||||
is_draft = models.BooleanField(default=False)
|
||||
external_source = models.CharField(max_length=255, null=True, blank=True)
|
||||
external_id = models.CharField(max_length=255, blank=True, null=True)
|
||||
|
||||
objects = models.Manager()
|
||||
issue_objects = IssueManager()
|
||||
@@ -366,6 +368,8 @@ class IssueComment(ProjectBaseModel):
|
||||
default="INTERNAL",
|
||||
max_length=100,
|
||||
)
|
||||
external_source = models.CharField(max_length=255, null=True, blank=True)
|
||||
external_id = models.CharField(max_length=255, blank=True, null=True)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
self.comment_stripped = (
|
||||
@@ -416,6 +420,8 @@ class Label(ProjectBaseModel):
|
||||
description = models.TextField(blank=True)
|
||||
color = models.CharField(max_length=255, blank=True)
|
||||
sort_order = models.FloatField(default=65535)
|
||||
external_source = models.CharField(max_length=255, null=True, blank=True)
|
||||
external_id = models.CharField(max_length=255, blank=True, null=True)
|
||||
|
||||
class Meta:
|
||||
unique_together = ["name", "project"]
|
||||
|
||||
@@ -41,6 +41,8 @@ class Module(ProjectBaseModel):
|
||||
)
|
||||
view_props = models.JSONField(default=dict)
|
||||
sort_order = models.FloatField(default=65535)
|
||||
external_source = models.CharField(max_length=255, null=True, blank=True)
|
||||
external_id = models.CharField(max_length=255, blank=True, null=True)
|
||||
|
||||
class Meta:
|
||||
unique_together = ["name", "project"]
|
||||
|
||||
@@ -24,6 +24,8 @@ class State(ProjectBaseModel):
|
||||
max_length=20,
|
||||
)
|
||||
default = models.BooleanField(default=False)
|
||||
external_source = models.CharField(max_length=255, null=True, blank=True)
|
||||
external_id = models.CharField(max_length=255, blank=True, null=True)
|
||||
|
||||
def __str__(self):
|
||||
"""Return name of the state"""
|
||||
|
||||
@@ -291,7 +291,9 @@ CELERY_IMPORTS = (
|
||||
|
||||
# Sentry Settings
|
||||
# Enable Sentry Settings
|
||||
if bool(os.environ.get("SENTRY_DSN", False)) and os.environ.get("SENTRY_DSN").startswith("https://"):
|
||||
if bool(os.environ.get("SENTRY_DSN", False)) and os.environ.get(
|
||||
"SENTRY_DSN"
|
||||
).startswith("https://"):
|
||||
sentry_sdk.init(
|
||||
dsn=os.environ.get("SENTRY_DSN", ""),
|
||||
integrations=[
|
||||
@@ -334,3 +336,5 @@ INSTANCE_KEY = os.environ.get(
|
||||
|
||||
# Skip environment variable configuration
|
||||
SKIP_ENV_VAR = os.environ.get("SKIP_ENV_VAR", "1") == "1"
|
||||
|
||||
DATA_UPLOAD_MAX_MEMORY_SIZE = int(os.environ.get("FILE_SIZE_LIMIT", 5242880))
|
||||
|
||||
@@ -30,7 +30,7 @@ openpyxl==3.1.2
|
||||
beautifulsoup4==4.12.2
|
||||
dj-database-url==2.1.0
|
||||
posthog==3.0.2
|
||||
cryptography==41.0.5
|
||||
cryptography==41.0.6
|
||||
lxml==4.9.3
|
||||
boto3==1.28.40
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ function download(){
|
||||
echo ""
|
||||
echo "Latest version is now available for you to use"
|
||||
echo ""
|
||||
echo "In case of Upgrade, your new setting file is availabe as 'variables-upgrade.env'. Please compare and set the required values in '.env 'file."
|
||||
echo "In case of Upgrade, your new setting file is available as 'variables-upgrade.env'. Please compare and set the required values in '.env 'file."
|
||||
echo ""
|
||||
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ volumes:
|
||||
|
||||
services:
|
||||
plane-redis:
|
||||
container_name: plane-redis
|
||||
image: redis:6.2.7-alpine
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
@@ -21,7 +20,6 @@ services:
|
||||
- redisdata:/data
|
||||
|
||||
plane-minio:
|
||||
container_name: plane-minio
|
||||
image: minio/minio
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
@@ -36,7 +34,6 @@ services:
|
||||
MINIO_ROOT_PASSWORD: ${AWS_SECRET_ACCESS_KEY}
|
||||
|
||||
plane-db:
|
||||
container_name: plane-db
|
||||
image: postgres:15.2-alpine
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
@@ -53,7 +50,6 @@ services:
|
||||
PGDATA: /var/lib/postgresql/data
|
||||
|
||||
web:
|
||||
container_name: web
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./web/Dockerfile.dev
|
||||
@@ -61,8 +57,7 @@ services:
|
||||
networks:
|
||||
- dev_env
|
||||
volumes:
|
||||
- .:/app
|
||||
command: yarn dev --filter=web
|
||||
- ./web:/app/web
|
||||
env_file:
|
||||
- ./web/.env
|
||||
depends_on:
|
||||
@@ -73,22 +68,17 @@ services:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./space/Dockerfile.dev
|
||||
container_name: space
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- dev_env
|
||||
volumes:
|
||||
- .:/app
|
||||
command: yarn dev --filter=space
|
||||
env_file:
|
||||
- ./space/.env
|
||||
- ./space:/app/space
|
||||
depends_on:
|
||||
- api
|
||||
- worker
|
||||
- web
|
||||
|
||||
api:
|
||||
container_name: api
|
||||
build:
|
||||
context: ./apiserver
|
||||
dockerfile: Dockerfile.dev
|
||||
@@ -99,7 +89,7 @@ services:
|
||||
- dev_env
|
||||
volumes:
|
||||
- ./apiserver:/code
|
||||
command: /bin/sh -c "python manage.py migrate && python manage.py runserver 0.0.0.0:8000 --settings=plane.settings.local"
|
||||
# command: /bin/sh -c "python manage.py migrate && python manage.py runserver 0.0.0.0:8000 --settings=plane.settings.local"
|
||||
env_file:
|
||||
- ./apiserver/.env
|
||||
depends_on:
|
||||
@@ -107,7 +97,6 @@ services:
|
||||
- plane-redis
|
||||
|
||||
worker:
|
||||
container_name: bgworker
|
||||
build:
|
||||
context: ./apiserver
|
||||
dockerfile: Dockerfile.dev
|
||||
@@ -127,7 +116,6 @@ services:
|
||||
- plane-redis
|
||||
|
||||
beat-worker:
|
||||
container_name: beatworker
|
||||
build:
|
||||
context: ./apiserver
|
||||
dockerfile: Dockerfile.dev
|
||||
@@ -147,10 +135,9 @@ services:
|
||||
- plane-redis
|
||||
|
||||
proxy:
|
||||
container_name: proxy
|
||||
build:
|
||||
context: ./nginx
|
||||
dockerfile: Dockerfile
|
||||
dockerfile: Dockerfile.dev
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- dev_env
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
FROM nginx:1.25.0-alpine
|
||||
|
||||
RUN rm /etc/nginx/conf.d/default.conf
|
||||
COPY nginx.conf.dev /etc/nginx/nginx.conf.template
|
||||
|
||||
COPY ./env.sh /docker-entrypoint.sh
|
||||
|
||||
RUN chmod +x /docker-entrypoint.sh
|
||||
# Update all environment variables
|
||||
CMD ["/docker-entrypoint.sh"]
|
||||
@@ -18,7 +18,7 @@ server {
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
}
|
||||
location /space/ {
|
||||
location /spaces/ {
|
||||
proxy_pass http://localhost:4000/;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
events {
|
||||
}
|
||||
|
||||
http {
|
||||
sendfile on;
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
root /www/data/;
|
||||
access_log /var/log/nginx/access.log;
|
||||
|
||||
client_max_body_size ${FILE_SIZE_LIMIT};
|
||||
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header Referrer-Policy "no-referrer-when-downgrade" always;
|
||||
add_header Permissions-Policy "interest-cohort=()" always;
|
||||
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
||||
|
||||
location / {
|
||||
proxy_pass http://web:3000/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
}
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://api:8000/api/;
|
||||
}
|
||||
|
||||
location /spaces/ {
|
||||
rewrite ^/spaces/?$ /spaces/login break;
|
||||
proxy_pass http://space:4000/spaces/;
|
||||
}
|
||||
|
||||
location /${BUCKET_NAME}/ {
|
||||
proxy_pass http://plane-minio:9000/uploads/;
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -27,7 +27,7 @@
|
||||
"prettier": "latest",
|
||||
"prettier-plugin-tailwindcss": "^0.5.4",
|
||||
"tailwindcss": "^3.3.3",
|
||||
"turbo": "^1.11.1"
|
||||
"turbo": "^1.11.2"
|
||||
},
|
||||
"resolutions": {
|
||||
"@types/react": "18.2.42"
|
||||
|
||||
@@ -28,28 +28,22 @@
|
||||
"react-dom": "18.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@plane/editor-types": "*",
|
||||
"@tiptap/core": "^2.1.7",
|
||||
"@tiptap/core": "^2.1.13",
|
||||
"@tiptap/extension-blockquote": "^2.1.13",
|
||||
"@tiptap/extension-code-block-lowlight": "^2.1.12",
|
||||
"@tiptap/extension-color": "^2.1.11",
|
||||
"@tiptap/extension-image": "^2.1.7",
|
||||
"@tiptap/extension-link": "^2.1.7",
|
||||
"@tiptap/extension-list-item": "^2.1.12",
|
||||
"@tiptap/extension-mention": "^2.1.12",
|
||||
"@tiptap/extension-table": "^2.1.6",
|
||||
"@tiptap/extension-table-cell": "^2.1.6",
|
||||
"@tiptap/extension-table-header": "^2.1.6",
|
||||
"@tiptap/extension-table-row": "^2.1.6",
|
||||
"@tiptap/extension-task-item": "^2.1.7",
|
||||
"@tiptap/extension-task-list": "^2.1.7",
|
||||
"@tiptap/extension-text-style": "^2.1.11",
|
||||
"@tiptap/extension-underline": "^2.1.7",
|
||||
"@tiptap/pm": "^2.1.7",
|
||||
"@tiptap/prosemirror-tables": "^1.1.4",
|
||||
"@tiptap/react": "^2.1.7",
|
||||
"@tiptap/starter-kit": "^2.1.10",
|
||||
"@tiptap/suggestion": "^2.0.4",
|
||||
"@tiptap/extension-code-block-lowlight": "^2.1.13",
|
||||
"@tiptap/extension-color": "^2.1.13",
|
||||
"@tiptap/extension-image": "^2.1.13",
|
||||
"@tiptap/extension-link": "^2.1.13",
|
||||
"@tiptap/extension-list-item": "^2.1.13",
|
||||
"@tiptap/extension-mention": "^2.1.13",
|
||||
"@tiptap/extension-task-item": "^2.1.13",
|
||||
"@tiptap/extension-task-list": "^2.1.13",
|
||||
"@tiptap/extension-text-style": "^2.1.13",
|
||||
"@tiptap/extension-underline": "^2.1.13",
|
||||
"@tiptap/pm": "^2.1.13",
|
||||
"@tiptap/react": "^2.1.13",
|
||||
"@tiptap/starter-kit": "^2.1.13",
|
||||
"@tiptap/suggestion": "^2.0.13",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^1.2.1",
|
||||
"highlight.js": "^11.8.0",
|
||||
|
||||
+7
-4
@@ -1,10 +1,13 @@
|
||||
import { useEditor as useCustomEditor, Editor } from "@tiptap/react";
|
||||
import { useImperativeHandle, useRef, MutableRefObject } from "react";
|
||||
import { CoreEditorProps } from "../props";
|
||||
import { CoreEditorExtensions } from "../extensions";
|
||||
import { CoreEditorProps } from "src/ui/props";
|
||||
import { CoreEditorExtensions } from "src/ui/extensions";
|
||||
import { EditorProps } from "@tiptap/pm/view";
|
||||
import { getTrimmedHTML } from "../../lib/utils";
|
||||
import { DeleteImage, IMentionSuggestion, RestoreImage, UploadImage } from "@plane/editor-types";
|
||||
import { getTrimmedHTML } from "src/lib/utils";
|
||||
import { DeleteImage } from "src/types/delete-image";
|
||||
import { IMentionSuggestion } from "src/types/mention-suggestion";
|
||||
import { RestoreImage } from "src/types/restore-image";
|
||||
import { UploadImage } from "src/types/upload-image";
|
||||
|
||||
interface CustomEditorProps {
|
||||
uploadFile: UploadImage;
|
||||
+3
-3
@@ -1,9 +1,9 @@
|
||||
import { useEditor as useCustomEditor, Editor } from "@tiptap/react";
|
||||
import { useImperativeHandle, useRef, MutableRefObject } from "react";
|
||||
import { CoreReadOnlyEditorExtensions } from "../read-only/extensions";
|
||||
import { CoreReadOnlyEditorProps } from "../read-only/props";
|
||||
import { CoreReadOnlyEditorExtensions } from "src/ui/read-only/extensions";
|
||||
import { CoreReadOnlyEditorProps } from "src/ui/read-only/props";
|
||||
import { EditorProps } from "@tiptap/pm/view";
|
||||
import { IMentionSuggestion } from "@plane/editor-types";
|
||||
import { IMentionSuggestion } from "src/types/mention-suggestion";
|
||||
|
||||
interface CustomReadOnlyEditorProps {
|
||||
value: string;
|
||||
@@ -1,23 +1,32 @@
|
||||
// styles
|
||||
// import "./styles/tailwind.css";
|
||||
// import "./styles/editor.css";
|
||||
import "./styles/github-dark.css";
|
||||
import "src/styles/editor.css";
|
||||
import "src/styles/table.css";
|
||||
import "src/styles/github-dark.css";
|
||||
|
||||
export { isCellSelection } from "./ui/extensions/table/table/utilities/is-cell-selection";
|
||||
export { isCellSelection } from "src/ui/extensions/table/table/utilities/is-cell-selection";
|
||||
|
||||
// utils
|
||||
export * from "./lib/utils";
|
||||
export * from "./ui/extensions/table/table";
|
||||
export { startImageUpload } from "./ui/plugins/upload-image";
|
||||
export * from "src/lib/utils";
|
||||
export * from "src/ui/extensions/table/table";
|
||||
export { startImageUpload } from "src/ui/plugins/upload-image";
|
||||
|
||||
// components
|
||||
export { EditorContainer } from "./ui/components/editor-container";
|
||||
export { EditorContentWrapper } from "./ui/components/editor-content";
|
||||
export { EditorContainer } from "src/ui/components/editor-container";
|
||||
export { EditorContentWrapper } from "src/ui/components/editor-content";
|
||||
|
||||
// hooks
|
||||
export { useEditor } from "./ui/hooks/use-editor";
|
||||
export { useReadOnlyEditor } from "./ui/hooks/use-read-only-editor";
|
||||
export { useEditor } from "src/hooks/use-editor";
|
||||
export { useReadOnlyEditor } from "src/hooks/use-read-only-editor";
|
||||
|
||||
// helper items
|
||||
export * from "./ui/menus/menu-items";
|
||||
export * from "./lib/editor-commands";
|
||||
export * from "src/ui/menus/menu-items";
|
||||
export * from "src/lib/editor-commands";
|
||||
|
||||
// types
|
||||
export type { DeleteImage } from "src/types/delete-image";
|
||||
export type { UploadImage } from "src/types/upload-image";
|
||||
export type { RestoreImage } from "src/types/restore-image";
|
||||
export type { IMentionHighlight, IMentionSuggestion } from "src/types/mention-suggestion";
|
||||
export type { ISlashCommandItem, CommandProps } from "src/types/slash-commands-suggestion";
|
||||
export type { LucideIconType } from "src/types/lucide-icon";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { UploadImage } from "@plane/editor-types";
|
||||
import { Editor, Range } from "@tiptap/core";
|
||||
import { startImageUpload } from "../ui/plugins/upload-image";
|
||||
import { findTableAncestor } from "./utils";
|
||||
import { startImageUpload } from "src/ui/plugins/upload-image";
|
||||
import { findTableAncestor } from "src/lib/utils";
|
||||
import { UploadImage } from "src/types/upload-image";
|
||||
|
||||
export const toggleHeadingOne = (editor: Editor, range?: Range) => {
|
||||
if (range) editor.chain().focus().deleteRange(range).setNode("heading", { level: 1 }).run();
|
||||
|
||||
@@ -6,6 +6,12 @@
|
||||
height: 0;
|
||||
}
|
||||
|
||||
/* block quotes */
|
||||
.ProseMirror blockquote p::before,
|
||||
.ProseMirror blockquote p::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ProseMirror .is-empty::before {
|
||||
content: attr(data-placeholder);
|
||||
float: left;
|
||||
@@ -15,9 +21,10 @@
|
||||
}
|
||||
|
||||
/* Custom image styles */
|
||||
|
||||
.ProseMirror img {
|
||||
transition: filter 0.1s ease-in-out;
|
||||
margin-top: 0 !important;
|
||||
margin-bottom: 0 !important;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
@@ -53,11 +60,12 @@ ul[data-type="taskList"] li > label input[type="checkbox"] {
|
||||
background-color: rgb(var(--color-background-100));
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
width: 1.2rem;
|
||||
height: 1.2rem;
|
||||
width: 0.8rem;
|
||||
height: 0.8rem;
|
||||
position: relative;
|
||||
border: 2px solid rgb(var(--color-text-100));
|
||||
margin-right: 0.3rem;
|
||||
border: 1.5px solid rgb(var(--color-text-100));
|
||||
margin-right: 0.2rem;
|
||||
margin-top: 0.15rem;
|
||||
display: grid;
|
||||
place-content: center;
|
||||
|
||||
@@ -71,8 +79,8 @@ ul[data-type="taskList"] li > label input[type="checkbox"] {
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
width: 0.65em;
|
||||
height: 0.65em;
|
||||
width: 0.5em;
|
||||
height: 0.5em;
|
||||
transform: scale(0);
|
||||
transition: 120ms transform ease-in-out;
|
||||
box-shadow: inset 1em 1em;
|
||||
@@ -133,6 +141,8 @@ ul[data-type="taskList"] li[data-checked="true"] > div > p {
|
||||
.img-placeholder {
|
||||
position: relative;
|
||||
width: 35%;
|
||||
margin-top: 0 !important;
|
||||
margin-bottom: 0 !important;
|
||||
|
||||
&:before {
|
||||
content: "";
|
||||
@@ -159,7 +169,8 @@ ul[data-type="taskList"] li[data-checked="true"] > div > p {
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
table-layout: fixed;
|
||||
margin: 0;
|
||||
margin: 0.5em 0 0.5em 0;
|
||||
|
||||
border: 1px solid rgb(var(--color-border-200));
|
||||
width: 100%;
|
||||
|
||||
@@ -229,3 +240,34 @@ ul[data-type="taskList"] li[data-checked="true"] > div > p {
|
||||
.ProseMirror table * .is-empty::before {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.ProseMirror pre {
|
||||
background: rgba(var(--color-background-80));
|
||||
border-radius: 0.5rem;
|
||||
color: rgba(var(--color-text-100));
|
||||
font-family: "JetBrainsMono", monospace;
|
||||
padding: 0.75rem 1rem;
|
||||
}
|
||||
|
||||
.ProseMirror pre code {
|
||||
background: none;
|
||||
color: inherit;
|
||||
font-size: 0.8rem;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div[data-type="horizontalRule"] {
|
||||
line-height: 0;
|
||||
padding: 0.25rem 0;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
|
||||
& > div {
|
||||
border-bottom: 1px solid rgb(var(--color-text-100));
|
||||
}
|
||||
}
|
||||
|
||||
/* image resizer */
|
||||
.moveable-control-box {
|
||||
z-index: 10 !important;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
import { Smile } from "lucide-react";
|
||||
|
||||
export type LucideIconType = typeof Smile;
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Editor, EditorContent } from "@tiptap/react";
|
||||
import { ReactNode } from "react";
|
||||
import { ImageResizer } from "../extensions/image/image-resize";
|
||||
import { ImageResizer } from "src/ui/extensions/image/image-resize";
|
||||
|
||||
interface EditorContentProps {
|
||||
editor: Editor | null;
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import { getNodeAtPosition } from "@tiptap/core";
|
||||
import { EditorState } from "@tiptap/pm/state";
|
||||
|
||||
import { findListItemPos } from "./find-list-item-pos";
|
||||
import { findListItemPos } from "src/ui/extensions/custom-list-keymap/list-helpers/find-list-item-pos";
|
||||
|
||||
export const getNextListDepth = (typeOrName: string, state: EditorState) => {
|
||||
const listItemPos = findListItemPos(typeOrName, state);
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
import { Editor, isAtStartOfNode, isNodeActive } from "@tiptap/core";
|
||||
import { Node } from "@tiptap/pm/model";
|
||||
|
||||
import { findListItemPos } from "./find-list-item-pos";
|
||||
import { hasListBefore } from "./has-list-before";
|
||||
import { findListItemPos } from "src/ui/extensions/custom-list-keymap/list-helpers/find-list-item-pos";
|
||||
import { hasListBefore } from "src/ui/extensions/custom-list-keymap/list-helpers/has-list-before";
|
||||
|
||||
export const handleBackspace = (editor: Editor, name: string, parentListTypes: string[]) => {
|
||||
// this is required to still handle the undo handling
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
import { Editor, isAtEndOfNode, isNodeActive } from "@tiptap/core";
|
||||
|
||||
import { nextListIsDeeper } from "./next-list-is-deeper";
|
||||
import { nextListIsHigher } from "./next-list-is-higher";
|
||||
import { nextListIsDeeper } from "src/ui/extensions/custom-list-keymap/list-helpers/next-list-is-deeper";
|
||||
import { nextListIsHigher } from "src/ui/extensions/custom-list-keymap/list-helpers/next-list-is-higher";
|
||||
|
||||
export const handleDelete = (editor: Editor, name: string) => {
|
||||
// if the cursor is not inside the current node type
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
import { EditorState } from "@tiptap/pm/state";
|
||||
|
||||
import { findListItemPos } from "./find-list-item-pos";
|
||||
import { getNextListDepth } from "./get-next-list-depth";
|
||||
import { findListItemPos } from "src/ui/extensions/custom-list-keymap/list-helpers/find-list-item-pos";
|
||||
import { getNextListDepth } from "src/ui/extensions/custom-list-keymap/list-helpers/get-next-list-depth";
|
||||
|
||||
export const nextListIsDeeper = (typeOrName: string, state: EditorState) => {
|
||||
const listDepth = getNextListDepth(typeOrName, state);
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
import { EditorState } from "@tiptap/pm/state";
|
||||
|
||||
import { findListItemPos } from "./find-list-item-pos";
|
||||
import { getNextListDepth } from "./get-next-list-depth";
|
||||
import { findListItemPos } from "src/ui/extensions/custom-list-keymap/list-helpers/find-list-item-pos";
|
||||
import { getNextListDepth } from "src/ui/extensions/custom-list-keymap/list-helpers/get-next-list-depth";
|
||||
|
||||
export const nextListIsHigher = (typeOrName: string, state: EditorState) => {
|
||||
const listDepth = getNextListDepth(typeOrName, state);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Extension } from "@tiptap/core";
|
||||
|
||||
import { handleBackspace, handleDelete } from "./list-helpers";
|
||||
import { handleBackspace, handleDelete } from "src/ui/extensions/custom-list-keymap/list-helpers";
|
||||
|
||||
export type ListKeymapOptions = {
|
||||
listTypes: Array<{
|
||||
|
||||
@@ -22,7 +22,7 @@ declare module "@tiptap/core" {
|
||||
}
|
||||
}
|
||||
|
||||
export default Node.create<HorizontalRuleOptions>({
|
||||
export const HorizontalRule = Node.create<HorizontalRuleOptions>({
|
||||
name: "horizontalRule",
|
||||
|
||||
addOptions() {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { EditorState, Plugin, PluginKey, Transaction } from "@tiptap/pm/state";
|
||||
import { Node as ProseMirrorNode } from "@tiptap/pm/model";
|
||||
import UploadImagesPlugin from "../../plugins/upload-image";
|
||||
import { UploadImagesPlugin } from "src/ui/plugins/upload-image";
|
||||
import ImageExt from "@tiptap/extension-image";
|
||||
import { onNodeDeleted, onNodeRestored } from "../../plugins/delete-image";
|
||||
import { DeleteImage, RestoreImage } from "@plane/editor-types";
|
||||
import { onNodeDeleted, onNodeRestored } from "src/ui/plugins/delete-image";
|
||||
import { DeleteImage } from "src/types/delete-image";
|
||||
import { RestoreImage } from "src/types/restore-image";
|
||||
|
||||
interface ImageNode extends ProseMirrorNode {
|
||||
attrs: {
|
||||
@@ -15,7 +16,7 @@ interface ImageNode extends ProseMirrorNode {
|
||||
const deleteKey = new PluginKey("delete-image");
|
||||
const IMAGE_NODE_TYPE = "image";
|
||||
|
||||
const ImageExtension = (deleteImage: DeleteImage, restoreFile: RestoreImage, cancelUploadImage?: () => any) =>
|
||||
export const ImageExtension = (deleteImage: DeleteImage, restoreFile: RestoreImage, cancelUploadImage?: () => any) =>
|
||||
ImageExt.extend({
|
||||
addProseMirrorPlugins() {
|
||||
return [
|
||||
@@ -130,5 +131,3 @@ const ImageExtension = (deleteImage: DeleteImage, restoreFile: RestoreImage, can
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default ImageExtension;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import Image from "@tiptap/extension-image";
|
||||
|
||||
const ReadOnlyImageExtension = Image.extend({
|
||||
export const ReadOnlyImageExtension = Image.extend({
|
||||
addAttributes() {
|
||||
return {
|
||||
...this.parent?.(),
|
||||
@@ -13,5 +13,3 @@ const ReadOnlyImageExtension = Image.extend({
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default ReadOnlyImageExtension;
|
||||
|
||||
@@ -7,22 +7,25 @@ import TaskItem from "@tiptap/extension-task-item";
|
||||
import TaskList from "@tiptap/extension-task-list";
|
||||
import { Markdown } from "tiptap-markdown";
|
||||
|
||||
import TableHeader from "./table/table-header/table-header";
|
||||
import Table from "./table/table";
|
||||
import TableCell from "./table/table-cell/table-cell";
|
||||
import TableRow from "./table/table-row/table-row";
|
||||
import HorizontalRule from "./horizontal-rule";
|
||||
import { TableHeader } from "src/ui/extensions/table/table-header/table-header";
|
||||
import { Table } from "src/ui/extensions/table/table";
|
||||
import { TableCell } from "src/ui/extensions/table/table-cell/table-cell";
|
||||
import { TableRow } from "src/ui/extensions/table/table-row/table-row";
|
||||
import { HorizontalRule } from "src/ui/extensions/horizontal-rule";
|
||||
|
||||
import ImageExtension from "./image";
|
||||
import { ImageExtension } from "src/ui/extensions/image";
|
||||
|
||||
import { isValidHttpUrl } from "../../lib/utils";
|
||||
import { Mentions } from "../mentions";
|
||||
import { isValidHttpUrl } from "src/lib/utils";
|
||||
import { Mentions } from "src/ui/mentions";
|
||||
|
||||
import { CustomKeymap } from "./keymap";
|
||||
import { CustomCodeBlock } from "./code";
|
||||
import { CustomQuoteExtension } from "./quote";
|
||||
import { ListKeymap } from "./custom-list-keymap";
|
||||
import { IMentionSuggestion, DeleteImage, RestoreImage } from "@plane/editor-types";
|
||||
import { CustomKeymap } from "src/ui/extensions/keymap";
|
||||
import { CustomCodeBlock } from "src/ui/extensions/code";
|
||||
import { CustomQuoteExtension } from "src/ui/extensions/quote";
|
||||
import { ListKeymap } from "src/ui/extensions/custom-list-keymap";
|
||||
|
||||
import { DeleteImage } from "src/types/delete-image";
|
||||
import { IMentionSuggestion } from "src/types/mention-suggestion";
|
||||
import { RestoreImage } from "src/types/restore-image";
|
||||
|
||||
export const CoreEditorExtensions = (
|
||||
mentionConfig: {
|
||||
@@ -49,12 +52,12 @@ export const CoreEditorExtensions = (
|
||||
class: "leading-normal -mb-2",
|
||||
},
|
||||
},
|
||||
// blockquote: {
|
||||
// HTMLAttributes: {
|
||||
// class: "border-l-4 border-custom-border-300",
|
||||
// },
|
||||
// },
|
||||
code: false,
|
||||
code: {
|
||||
HTMLAttributes: {
|
||||
class: "rounded-md bg-custom-primary-30 mx-1 px-1 py-1 font-mono font-medium text-custom-text-1000",
|
||||
spellcheck: "false",
|
||||
},
|
||||
},
|
||||
codeBlock: false,
|
||||
horizontalRule: false,
|
||||
dropcursor: {
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { default as default } from "./table-cell";
|
||||
export { TableCell } from "./table-cell";
|
||||
|
||||
@@ -4,7 +4,7 @@ export interface TableCellOptions {
|
||||
HTMLAttributes: Record<string, any>;
|
||||
}
|
||||
|
||||
export default Node.create<TableCellOptions>({
|
||||
export const TableCell = Node.create<TableCellOptions>({
|
||||
name: "tableCell",
|
||||
|
||||
addOptions() {
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { default as default } from "./table-header";
|
||||
export { TableHeader } from "./table-header";
|
||||
|
||||
@@ -3,7 +3,8 @@ import { mergeAttributes, Node } from "@tiptap/core";
|
||||
export interface TableHeaderOptions {
|
||||
HTMLAttributes: Record<string, any>;
|
||||
}
|
||||
export default Node.create<TableHeaderOptions>({
|
||||
|
||||
export const TableHeader = Node.create<TableHeaderOptions>({
|
||||
name: "tableHeader",
|
||||
|
||||
addOptions() {
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { default as default } from "./table-row";
|
||||
export { TableRow } from "./table-row";
|
||||
|
||||
@@ -4,7 +4,7 @@ export interface TableRowOptions {
|
||||
HTMLAttributes: Record<string, any>;
|
||||
}
|
||||
|
||||
export default Node.create<TableRowOptions>({
|
||||
export const TableRow = Node.create<TableRowOptions>({
|
||||
name: "tableRow",
|
||||
|
||||
addOptions() {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const icons = {
|
||||
export const icons = {
|
||||
colorPicker: `<svg xmlns="http://www.w3.org/2000/svg" length="24" viewBox="0 0 24 24" style="transform: ;msFilter:;"><path fill="rgb(var(--color-text-300))" d="M20 14c-.092.064-2 2.083-2 3.5 0 1.494.949 2.448 2 2.5.906.044 2-.891 2-2.5 0-1.5-1.908-3.436-2-3.5zM9.586 20c.378.378.88.586 1.414.586s1.036-.208 1.414-.586l7-7-.707-.707L11 4.586 8.707 2.293 7.293 3.707 9.586 6 4 11.586c-.378.378-.586.88-.586 1.414s.208 1.036.586 1.414L9.586 20zM11 7.414 16.586 13H5.414L11 7.414z"></path></svg>`,
|
||||
deleteColumn: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" length="24"><path fill="#e53e3e" d="M0 0H24V24H0z"/><path d="M12 3c.552 0 1 .448 1 1v8c.835-.628 1.874-1 3-1 2.761 0 5 2.239 5 5s-2.239 5-5 5c-1.032 0-1.99-.313-2.787-.848L13 20c0 .552-.448 1-1 1H6c-.552 0-1-.448-1-1V4c0-.552.448-1 1-1h6zm-1 2H7v14h4V5zm8 10h-6v2h6v-2z"/></svg>`,
|
||||
deleteRow: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" length="24"><path fill="#e53e3e" d="M0 0H24V24H0z"/><path d="M20 5c.552 0 1 .448 1 1v6c0 .552-.448 1-1 1 .628.835 1 1.874 1 3 0 2.761-2.239 5-5 5s-5-2.239-5-5c0-1.126.372-2.165 1-3H4c-.552 0-1-.448-1-1V6c0-.552.448-1 1-1h16zm-7 10v2h6v-2h-6zm6-8H5v4h14V7z"/></svg>`,
|
||||
@@ -47,5 +47,3 @@ const icons = {
|
||||
</svg>
|
||||
`,
|
||||
};
|
||||
|
||||
export default icons;
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { default as default } from "./table";
|
||||
export { Table } from "./table";
|
||||
|
||||
@@ -4,9 +4,9 @@ import { Decoration, NodeView } from "@tiptap/pm/view";
|
||||
import tippy, { Instance, Props } from "tippy.js";
|
||||
|
||||
import { Editor } from "@tiptap/core";
|
||||
import { CellSelection, TableMap, updateColumnsOnResize } from "@tiptap/prosemirror-tables";
|
||||
import { CellSelection, TableMap, updateColumnsOnResize } from "@tiptap/pm/tables";
|
||||
|
||||
import icons from "./icons";
|
||||
import { icons } from "src/ui/extensions/table/table/icons";
|
||||
|
||||
export function updateColumns(
|
||||
node: ProseMirrorNode,
|
||||
|
||||
@@ -19,12 +19,12 @@ import {
|
||||
tableEditing,
|
||||
toggleHeader,
|
||||
toggleHeaderCell,
|
||||
} from "@tiptap/prosemirror-tables";
|
||||
} from "@tiptap/pm/tables";
|
||||
|
||||
import { tableControls } from "./table-controls";
|
||||
import { TableView } from "./table-view";
|
||||
import { createTable } from "./utilities/create-table";
|
||||
import { deleteTableWhenAllCellsSelected } from "./utilities/delete-table-when-all-cells-selected";
|
||||
import { tableControls } from "src/ui/extensions/table/table/table-controls";
|
||||
import { TableView } from "src/ui/extensions/table/table/table-view";
|
||||
import { createTable } from "src/ui/extensions/table/table/utilities/create-table";
|
||||
import { deleteTableWhenAllCellsSelected } from "src/ui/extensions/table/table/utilities/delete-table-when-all-cells-selected";
|
||||
|
||||
export interface TableOptions {
|
||||
HTMLAttributes: Record<string, any>;
|
||||
@@ -72,7 +72,7 @@ declare module "@tiptap/core" {
|
||||
}
|
||||
}
|
||||
|
||||
export default Node.create({
|
||||
export const Table = Node.create({
|
||||
name: "table",
|
||||
|
||||
addOptions() {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Fragment, Node as ProsemirrorNode, Schema } from "@tiptap/pm/model";
|
||||
|
||||
import { createCell } from "./create-cell";
|
||||
import { getTableNodeTypes } from "./get-table-node-types";
|
||||
import { createCell } from "src/ui/extensions/table/table/utilities/create-cell";
|
||||
import { getTableNodeTypes } from "src/ui/extensions/table/table/utilities/get-table-node-types";
|
||||
|
||||
export function createTable(
|
||||
schema: Schema,
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import { findParentNodeClosestToPos, KeyboardShortcutCommand } from "@tiptap/core";
|
||||
|
||||
import { isCellSelection } from "./is-cell-selection";
|
||||
import { isCellSelection } from "src/ui/extensions/table/table/utilities/is-cell-selection";
|
||||
|
||||
export const deleteTableWhenAllCellsSelected: KeyboardShortcutCommand = ({ editor }) => {
|
||||
const { selection } = editor.state;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { CellSelection } from "@tiptap/prosemirror-tables";
|
||||
import { CellSelection } from "@tiptap/pm/tables";
|
||||
|
||||
export function isCellSelection(value: unknown): value is CellSelection {
|
||||
return value instanceof CellSelection;
|
||||
|
||||
@@ -1,8 +1,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 "@plane/editor-types";
|
||||
import { MentionNodeView } from "src/ui/mentions/mention-node-view";
|
||||
import { IMentionHighlight } from "src/types/mention-suggestion";
|
||||
|
||||
export interface CustomMentionOptions extends MentionOptions {
|
||||
mentionHighlights: IMentionHighlight[];
|
||||
@@ -31,7 +31,7 @@ export const CustomMention = Mention.extend<CustomMentionOptions>({
|
||||
},
|
||||
|
||||
addNodeView() {
|
||||
return ReactNodeViewRenderer(mentionNodeView);
|
||||
return ReactNodeViewRenderer(MentionNodeView);
|
||||
},
|
||||
|
||||
parseHTML() {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// @ts-nocheck
|
||||
|
||||
import suggestion from "./suggestion";
|
||||
import { CustomMention } from "./custom";
|
||||
import { IMentionHighlight, IMentionSuggestion } from "@plane/editor-types";
|
||||
import { Suggestion } from "src/ui/mentions/suggestion";
|
||||
import { CustomMention } from "src/ui/mentions/custom";
|
||||
import { IMentionHighlight } from "src/types/mention-suggestion";
|
||||
|
||||
export const Mentions = (mentionSuggestions: IMentionSuggestion[], mentionHighlights: IMentionHighlight[], readonly) =>
|
||||
CustomMention.configure({
|
||||
@@ -11,5 +11,5 @@ export const Mentions = (mentionSuggestions: IMentionSuggestion[], mentionHighli
|
||||
},
|
||||
readonly: readonly,
|
||||
mentionHighlights: mentionHighlights,
|
||||
suggestion: suggestion(mentionSuggestions),
|
||||
suggestion: Suggestion(mentionSuggestions),
|
||||
});
|
||||
|
||||
+3
-5
@@ -1,6 +1,6 @@
|
||||
import { IMentionSuggestion } from "@plane/editor-types";
|
||||
import { Editor } from "@tiptap/react";
|
||||
import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useState } from "react";
|
||||
import { forwardRef, useEffect, useImperativeHandle, useState } from "react";
|
||||
import { IMentionSuggestion } from "src/types/mention-suggestion";
|
||||
|
||||
interface MentionListProps {
|
||||
items: IMentionSuggestion[];
|
||||
@@ -9,7 +9,7 @@ interface MentionListProps {
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react/display-name
|
||||
const MentionList = forwardRef((props: MentionListProps, ref) => {
|
||||
export const MentionList = forwardRef((props: MentionListProps, ref) => {
|
||||
const [selectedIndex, setSelectedIndex] = useState(0);
|
||||
|
||||
const selectItem = (index: number) => {
|
||||
@@ -98,5 +98,3 @@ const MentionList = forwardRef((props: MentionListProps, ref) => {
|
||||
});
|
||||
|
||||
MentionList.displayName = "MentionList";
|
||||
|
||||
export default MentionList;
|
||||
+3
-3
@@ -1,12 +1,12 @@
|
||||
/* eslint-disable react/display-name */
|
||||
// @ts-nocheck
|
||||
import { NodeViewWrapper } from "@tiptap/react";
|
||||
import { cn } from "../../lib/utils";
|
||||
import { cn } from "src/lib/utils";
|
||||
import { useRouter } from "next/router";
|
||||
import { IMentionHighlight } from "@plane/editor-types";
|
||||
import { IMentionHighlight } from "src/types/mention-suggestion";
|
||||
|
||||
// eslint-disable-next-line import/no-anonymous-default-export
|
||||
export default (props) => {
|
||||
export const MentionNodeView = (props) => {
|
||||
const router = useRouter();
|
||||
const highlights = props.extension.options.mentionHighlights as IMentionHighlight[];
|
||||
|
||||
@@ -2,10 +2,10 @@ import { ReactRenderer } from "@tiptap/react";
|
||||
import { Editor } from "@tiptap/core";
|
||||
import tippy from "tippy.js";
|
||||
|
||||
import MentionList from "./MentionList";
|
||||
import { IMentionSuggestion } from "@plane/editor-types";
|
||||
import { MentionList } from "src/ui/mentions/mention-list";
|
||||
import { IMentionSuggestion } from "src/types/mention-suggestion";
|
||||
|
||||
const Suggestion = (suggestions: IMentionSuggestion[]) => ({
|
||||
export const Suggestion = (suggestions: IMentionSuggestion[]) => ({
|
||||
items: ({ query }: { query: string }) =>
|
||||
suggestions.filter((suggestion) => suggestion.title.toLowerCase().startsWith(query.toLowerCase())).slice(0, 5),
|
||||
render: () => {
|
||||
@@ -55,5 +55,3 @@ const Suggestion = (suggestions: IMentionSuggestion[]) => ({
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default Suggestion;
|
||||
|
||||
@@ -30,14 +30,15 @@ import {
|
||||
toggleStrike,
|
||||
toggleTaskList,
|
||||
toggleUnderline,
|
||||
} from "../../../lib/editor-commands";
|
||||
import { UploadImage } from "@plane/editor-types";
|
||||
} from "src/lib/editor-commands";
|
||||
import { LucideIconType } from "src/types/lucide-icon";
|
||||
import { UploadImage } from "src/types/upload-image";
|
||||
|
||||
export interface EditorMenuItem {
|
||||
name: string;
|
||||
isActive: () => boolean;
|
||||
command: () => void;
|
||||
icon: typeof BoldIcon;
|
||||
icon: LucideIconType;
|
||||
}
|
||||
|
||||
export const HeadingOneItem = (editor: Editor): EditorMenuItem => ({
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { EditorState, Plugin, PluginKey, Transaction } from "@tiptap/pm/state";
|
||||
import { Node as ProseMirrorNode } from "@tiptap/pm/model";
|
||||
import { DeleteImage, RestoreImage } from "@plane/editor-types";
|
||||
import { DeleteImage } from "src/types/delete-image";
|
||||
import { RestoreImage } from "src/types/restore-image";
|
||||
|
||||
const deleteKey = new PluginKey("delete-image");
|
||||
const IMAGE_NODE_TYPE = "image";
|
||||
@@ -12,7 +13,7 @@ interface ImageNode extends ProseMirrorNode {
|
||||
};
|
||||
}
|
||||
|
||||
const TrackImageDeletionPlugin = (deleteImage: DeleteImage): Plugin =>
|
||||
export const TrackImageDeletionPlugin = (deleteImage: DeleteImage): Plugin =>
|
||||
new Plugin({
|
||||
key: deleteKey,
|
||||
appendTransaction: (transactions: readonly Transaction[], oldState: EditorState, newState: EditorState) => {
|
||||
@@ -53,8 +54,6 @@ const TrackImageDeletionPlugin = (deleteImage: DeleteImage): Plugin =>
|
||||
},
|
||||
});
|
||||
|
||||
export default TrackImageDeletionPlugin;
|
||||
|
||||
export async function onNodeDeleted(src: string, deleteImage: DeleteImage): Promise<void> {
|
||||
try {
|
||||
const assetUrlWithWorkspaceId = new URL(src).pathname.substring(1);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { UploadImage } from "@plane/editor-types";
|
||||
import { EditorState, Plugin, PluginKey } from "@tiptap/pm/state";
|
||||
import { Decoration, DecorationSet, EditorView } from "@tiptap/pm/view";
|
||||
import { UploadImage } from "src/types/upload-image";
|
||||
|
||||
const uploadKey = new PluginKey("upload-image");
|
||||
|
||||
const UploadImagesPlugin = (cancelUploadImage?: () => any) =>
|
||||
export const UploadImagesPlugin = (cancelUploadImage?: () => any) =>
|
||||
new Plugin({
|
||||
key: uploadKey,
|
||||
state: {
|
||||
@@ -43,7 +43,7 @@ const UploadImagesPlugin = (cancelUploadImage?: () => any) =>
|
||||
|
||||
cancelButton.appendChild(svgElement);
|
||||
placeholder.appendChild(cancelButton);
|
||||
const deco = Decoration.widget(pos + 1, placeholder, {
|
||||
const deco = Decoration.widget(pos, placeholder, {
|
||||
id,
|
||||
});
|
||||
set = set.add(tr.doc, [deco]);
|
||||
@@ -60,8 +60,6 @@ const UploadImagesPlugin = (cancelUploadImage?: () => any) =>
|
||||
},
|
||||
});
|
||||
|
||||
export default UploadImagesPlugin;
|
||||
|
||||
function findPlaceholder(state: EditorState, id: {}) {
|
||||
const decos = uploadKey.getState(state);
|
||||
const found = decos.find(undefined, undefined, (spec: { id: number | undefined }) => spec.id == id);
|
||||
@@ -133,7 +131,8 @@ export async function startImageUpload(
|
||||
const imageSrc = typeof src === "object" ? reader.result : src;
|
||||
|
||||
const node = schema.nodes.image.create({ src: imageSrc });
|
||||
const transaction = view.state.tr.replaceWith(pos, pos, node).setMeta(uploadKey, { remove: { id } });
|
||||
const transaction = view.state.tr.insert(pos - 1, node).setMeta(uploadKey, { remove: { id } });
|
||||
|
||||
view.dispatch(transaction);
|
||||
} catch (error) {
|
||||
console.error("Upload error: ", error);
|
||||
|
||||
@@ -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 { findTableAncestor } from "src/lib/utils";
|
||||
import { UploadImage } from "src/types/upload-image";
|
||||
import { startImageUpload } from "src/ui/plugins/upload-image";
|
||||
|
||||
export function CoreEditorProps(
|
||||
uploadFile: UploadImage,
|
||||
|
||||
@@ -8,15 +8,16 @@ import TaskList from "@tiptap/extension-task-list";
|
||||
import { Markdown } from "tiptap-markdown";
|
||||
import Gapcursor from "@tiptap/extension-gapcursor";
|
||||
|
||||
import TableHeader from "../extensions/table/table-header/table-header";
|
||||
import Table from "../extensions/table/table";
|
||||
import TableCell from "../extensions/table/table-cell/table-cell";
|
||||
import TableRow from "../extensions/table/table-row/table-row";
|
||||
import { TableHeader } from "src/ui/extensions/table/table-header/table-header";
|
||||
import { Table } from "src/ui/extensions/table/table";
|
||||
import { TableCell } from "src/ui/extensions/table/table-cell/table-cell";
|
||||
import { TableRow } from "src/ui/extensions/table/table-row/table-row";
|
||||
import { HorizontalRule } from "src/ui/extensions/horizontal-rule";
|
||||
|
||||
import ReadOnlyImageExtension from "../extensions/image/read-only-image";
|
||||
import { isValidHttpUrl } from "../../lib/utils";
|
||||
import { Mentions } from "../mentions";
|
||||
import { IMentionSuggestion } from "@plane/editor-types";
|
||||
import { ReadOnlyImageExtension } from "src/ui/extensions/image/read-only-image";
|
||||
import { isValidHttpUrl } from "src/lib/utils";
|
||||
import { Mentions } from "src/ui/mentions";
|
||||
import { IMentionSuggestion } from "src/types/mention-suggestion";
|
||||
|
||||
export const CoreReadOnlyEditorExtensions = (mentionConfig: {
|
||||
mentionSuggestions: IMentionSuggestion[];
|
||||
@@ -71,6 +72,7 @@ export const CoreReadOnlyEditorExtensions = (mentionConfig: {
|
||||
class: "rounded-lg border border-custom-border-300",
|
||||
},
|
||||
}),
|
||||
HorizontalRule,
|
||||
TiptapUnderline,
|
||||
TextStyle,
|
||||
Color,
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
{
|
||||
"extends": "tsconfig/react-library.json",
|
||||
"include": ["src/**/*", "index.d.ts"],
|
||||
"exclude": ["dist", "build", "node_modules"]
|
||||
"include": [
|
||||
"src/**/*",
|
||||
"index.d.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"dist",
|
||||
"build",
|
||||
"node_modules"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"baseUrl": "."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,13 +30,11 @@
|
||||
"dependencies": {
|
||||
"@plane/editor-core": "*",
|
||||
"@plane/editor-extensions": "*",
|
||||
"@plane/editor-types": "*",
|
||||
"@plane/ui": "*",
|
||||
"@tiptap/core": "^2.1.7",
|
||||
"@tiptap/extension-placeholder": "^2.1.11",
|
||||
"@tiptap/pm": "^2.1.12",
|
||||
"@tiptap/suggestion": "^2.1.12",
|
||||
"eslint": "8.36.0",
|
||||
"@tiptap/core": "^2.1.13",
|
||||
"@tiptap/extension-placeholder": "^2.1.13",
|
||||
"@tiptap/pm": "^2.1.13",
|
||||
"@tiptap/suggestion": "^2.1.13",
|
||||
"eslint-config-next": "13.2.4",
|
||||
"react-popper": "^2.3.0",
|
||||
"tippy.js": "^6.3.7",
|
||||
@@ -46,7 +44,7 @@
|
||||
"@types/node": "18.15.3",
|
||||
"@types/react": "^18.2.42",
|
||||
"@types/react-dom": "^18.2.17",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint": "8.36.0",
|
||||
"postcss": "^8.4.29",
|
||||
"tailwind-config-custom": "*",
|
||||
"tsconfig": "*",
|
||||
|
||||
+1
-2
@@ -1,6 +1,5 @@
|
||||
import { Editor } from "@tiptap/react";
|
||||
import { useState } from "react";
|
||||
import { IMarking } from "..";
|
||||
import { IMarking } from "src/types/editor-types";
|
||||
|
||||
export const useEditorMarkings = () => {
|
||||
const [markings, setMarkings] = useState<IMarking[]>([]);
|
||||
@@ -1,3 +1,3 @@
|
||||
export { DocumentEditor, DocumentEditorWithRef } from "./ui";
|
||||
export { DocumentReadOnlyEditor, DocumentReadOnlyEditorWithRef } from "./ui/readonly";
|
||||
export { FixedMenu } from "./ui/menu/fixed-menu";
|
||||
export { DocumentEditor, DocumentEditorWithRef } from "src/ui";
|
||||
export { DocumentReadOnlyEditor, DocumentReadOnlyEditorWithRef } from "src/ui/readonly";
|
||||
export { FixedMenu } from "src/ui/menu/fixed-menu";
|
||||
|
||||
+6
@@ -5,3 +5,9 @@ export interface DocumentDetails {
|
||||
last_updated_by: string;
|
||||
last_updated_at: Date;
|
||||
}
|
||||
export interface IMarking {
|
||||
type: "heading";
|
||||
level: number;
|
||||
text: string;
|
||||
sequence: number;
|
||||
}
|
||||
@@ -1,12 +1,11 @@
|
||||
import { Icon } from "lucide-react";
|
||||
import { LucideIconType } from "@plane/editor-core";
|
||||
|
||||
interface IAlertLabelProps {
|
||||
Icon?: Icon;
|
||||
Icon?: LucideIconType;
|
||||
backgroundColor: string;
|
||||
textColor?: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
export const AlertLabel = (props: IAlertLabelProps) => {
|
||||
const { Icon, backgroundColor, textColor, label } = props;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { HeadingComp, HeadingThreeComp, SubheadingComp } from "./heading-component";
|
||||
import { IMarking } from "..";
|
||||
import { HeadingComp, HeadingThreeComp, SubheadingComp } from "src/ui/components/heading-component";
|
||||
import { IMarking } from "src/types/editor-types";
|
||||
import { Editor } from "@tiptap/react";
|
||||
import { scrollSummary } from "../utils/editor-summary-utils";
|
||||
import { scrollSummary } from "src/utils/editor-summary-utils";
|
||||
|
||||
interface ContentBrowserProps {
|
||||
editor: Editor;
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import { Editor } from "@tiptap/react";
|
||||
import { Archive, RefreshCw, Lock } from "lucide-react";
|
||||
import { IMarking } from "..";
|
||||
import { FixedMenu } from "../menu";
|
||||
import { UploadImage } from "@plane/editor-types";
|
||||
import { DocumentDetails } from "../types/editor-types";
|
||||
import { AlertLabel } from "./alert-label";
|
||||
import { IVerticalDropdownItemProps, VerticalDropdownMenu } from "./vertical-dropdown-menu";
|
||||
import { SummaryPopover } from "./summary-popover";
|
||||
import { InfoPopover } from "./info-popover";
|
||||
import { IMarking, DocumentDetails } from "src/types/editor-types";
|
||||
import { FixedMenu } from "src/ui/menu";
|
||||
import { UploadImage } from "@plane/editor-core";
|
||||
import { AlertLabel } from "src/ui/components/alert-label";
|
||||
import { IVerticalDropdownItemProps, VerticalDropdownMenu } from "src/ui/components/vertical-dropdown-menu";
|
||||
import { SummaryPopover } from "src/ui/components/summary-popover";
|
||||
import { InfoPopover } from "src/ui/components/info-popover";
|
||||
|
||||
interface IEditorHeader {
|
||||
editor: Editor;
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useState } from "react";
|
||||
import { usePopper } from "react-popper";
|
||||
import { Calendar, History, Info } from "lucide-react";
|
||||
// types
|
||||
import { DocumentDetails } from "../types/editor-types";
|
||||
import { DocumentDetails } from "src/types/editor-types";
|
||||
|
||||
type Props = {
|
||||
documentDetails: DocumentDetails;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { EditorContainer, EditorContentWrapper } from "@plane/editor-core";
|
||||
import { Editor } from "@tiptap/react";
|
||||
import { useState } from "react";
|
||||
import { DocumentDetails } from "../types/editor-types";
|
||||
import { DocumentDetails } from "src/types/editor-types";
|
||||
|
||||
type IPageRenderer = {
|
||||
documentDetails: DocumentDetails;
|
||||
|
||||
@@ -3,9 +3,9 @@ import { Editor } from "@tiptap/react";
|
||||
import { usePopper } from "react-popper";
|
||||
import { List } from "lucide-react";
|
||||
// components
|
||||
import { ContentBrowser } from "./content-browser";
|
||||
import { ContentBrowser } from "src/ui/components/content-browser";
|
||||
// types
|
||||
import { IMarking } from "..";
|
||||
import { IMarking } from "src/types/editor-types";
|
||||
|
||||
type Props = {
|
||||
editor: Editor;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Editor } from "@tiptap/react";
|
||||
import { IMarking } from "..";
|
||||
import { ContentBrowser } from "./content-browser";
|
||||
import { IMarking } from "src/types/editor-types";
|
||||
import { ContentBrowser } from "src/ui/components/content-browser";
|
||||
|
||||
interface ISummarySideBarProps {
|
||||
editor: Editor;
|
||||
@@ -8,14 +8,12 @@ interface ISummarySideBarProps {
|
||||
sidePeekVisible: boolean;
|
||||
}
|
||||
|
||||
export const SummarySideBar = ({ editor, markings, sidePeekVisible }: ISummarySideBarProps) => {
|
||||
return (
|
||||
<div
|
||||
className={`h-full transform overflow-hidden p-5 transition-all duration-200 ${
|
||||
sidePeekVisible ? "translate-x-0" : "-translate-x-full"
|
||||
}`}
|
||||
>
|
||||
<ContentBrowser editor={editor} markings={markings} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export const SummarySideBar = ({ editor, markings, sidePeekVisible }: ISummarySideBarProps) => (
|
||||
<div
|
||||
className={`h-full transform overflow-hidden p-5 transition-all duration-200 ${
|
||||
sidePeekVisible ? "translate-x-0" : "-translate-x-full"
|
||||
}`}
|
||||
>
|
||||
<ContentBrowser editor={editor} markings={markings} />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Button, CustomMenu } from "@plane/ui";
|
||||
import { ChevronUp, Icon, MoreVertical } from "lucide-react";
|
||||
import { LucideIconType } from "@plane/editor-core";
|
||||
import { CustomMenu } from "@plane/ui";
|
||||
import { MoreVertical } from "lucide-react";
|
||||
|
||||
type TMenuItems =
|
||||
| "archive_page"
|
||||
@@ -14,7 +15,7 @@ type TMenuItems =
|
||||
export interface IVerticalDropdownItemProps {
|
||||
key: number;
|
||||
type: TMenuItems;
|
||||
Icon: Icon;
|
||||
Icon: LucideIconType;
|
||||
label: string;
|
||||
action: () => Promise<void> | void;
|
||||
}
|
||||
@@ -23,27 +24,23 @@ export interface IVerticalDropdownMenuProps {
|
||||
items: IVerticalDropdownItemProps[];
|
||||
}
|
||||
|
||||
const VerticalDropdownItem = ({ Icon, label, action }: IVerticalDropdownItemProps) => {
|
||||
return (
|
||||
<CustomMenu.MenuItem onClick={action} className="flex items-center gap-2">
|
||||
<Icon className="h-3 w-3" />
|
||||
<div className="text-custom-text-300">{label}</div>
|
||||
</CustomMenu.MenuItem>
|
||||
);
|
||||
};
|
||||
const VerticalDropdownItem = ({ Icon, label, action }: IVerticalDropdownItemProps) => (
|
||||
<CustomMenu.MenuItem onClick={action} className="flex items-center gap-2">
|
||||
<Icon className="h-3 w-3" />
|
||||
<div className="text-custom-text-300">{label}</div>
|
||||
</CustomMenu.MenuItem>
|
||||
);
|
||||
|
||||
export const VerticalDropdownMenu = ({ items }: IVerticalDropdownMenuProps) => {
|
||||
return (
|
||||
<CustomMenu
|
||||
maxHeight={"md"}
|
||||
className={"h-4.5 mt-1"}
|
||||
placement={"bottom-start"}
|
||||
optionsClassName={"border-custom-border border-r border-solid transition-all duration-200 ease-in-out "}
|
||||
customButton={<MoreVertical size={14} />}
|
||||
>
|
||||
{items.map((item, index) => (
|
||||
<VerticalDropdownItem key={index} type={item.type} Icon={item.Icon} label={item.label} action={item.action} />
|
||||
))}
|
||||
</CustomMenu>
|
||||
);
|
||||
};
|
||||
export const VerticalDropdownMenu = ({ items }: IVerticalDropdownMenuProps) => (
|
||||
<CustomMenu
|
||||
maxHeight={"md"}
|
||||
className={"h-4.5 mt-1"}
|
||||
placement={"bottom-start"}
|
||||
optionsClassName={"border-custom-border border-r border-solid transition-all duration-200 ease-in-out "}
|
||||
customButton={<MoreVertical size={14} />}
|
||||
>
|
||||
{items.map((item) => (
|
||||
<VerticalDropdownItem key={item.key} type={item.type} Icon={item.Icon} label={item.label} action={item.action} />
|
||||
))}
|
||||
</CustomMenu>
|
||||
);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user