Merge branch 'develop' into fix/clipboard
28
.github/deployment/Caddyfile-affine
vendored
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
:3000 {
|
||||||
|
root /* ./dist
|
||||||
|
|
||||||
|
file_server {
|
||||||
|
precompressed br
|
||||||
|
}
|
||||||
|
|
||||||
|
encode {
|
||||||
|
zstd
|
||||||
|
gzip 9
|
||||||
|
}
|
||||||
|
|
||||||
|
@notStatic {
|
||||||
|
not path /*.css
|
||||||
|
not path /*.js
|
||||||
|
not path /*.png
|
||||||
|
not path /*.jpg
|
||||||
|
not path /*.svg
|
||||||
|
not path /*.ttf
|
||||||
|
not path /*.eot
|
||||||
|
not path /*.woff
|
||||||
|
not path /*.woff2
|
||||||
|
}
|
||||||
|
|
||||||
|
handle @notStatic {
|
||||||
|
try_files {path} /index.html
|
||||||
|
}
|
||||||
|
}
|
21
.github/deployment/Dockerfile-affine
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
FROM node:16-alpine as builder
|
||||||
|
WORKDIR /app
|
||||||
|
COPY . .
|
||||||
|
RUN apk add g++ make python3 git libpng-dev
|
||||||
|
RUN npm i -g pnpm@7 && pnpm i --frozen-lockfile --store=node_modules/.pnpm-store && pnpm run build:local
|
||||||
|
|
||||||
|
FROM node:16-alpine as relocate
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=builder /app/dist/apps/ligo-virgo ./dist
|
||||||
|
COPY --from=builder /app/.github/deployment/Caddyfile-affine ./Caddyfile
|
||||||
|
RUN rm ./dist/*.txt
|
||||||
|
|
||||||
|
# =============
|
||||||
|
# AFFiNE image
|
||||||
|
# =============
|
||||||
|
FROM caddy:2.4.6-alpine as AFFiNE
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=relocate /app .
|
||||||
|
|
||||||
|
EXPOSE 3000
|
||||||
|
CMD ["caddy", "run"]
|
@ -1,7 +1,7 @@
|
|||||||
FROM node:16-alpine as builder
|
FROM node:16-alpine as builder
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN apk add g++ make python3 git
|
RUN apk add g++ make python3 git libpng-dev
|
||||||
RUN npm i -g pnpm@7 && pnpm i --frozen-lockfile --store=node_modules/.pnpm-store && pnpm run build:keck
|
RUN npm i -g pnpm@7 && pnpm i --frozen-lockfile --store=node_modules/.pnpm-store && pnpm run build:keck
|
||||||
|
|
||||||
FROM node:16-alpine as node_modules
|
FROM node:16-alpine as node_modules
|
@ -1,13 +1,13 @@
|
|||||||
FROM node:16-alpine as builder
|
FROM node:16-alpine as builder
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN apk add g++ make python3 git
|
RUN apk add g++ make python3 git libpng-dev
|
||||||
RUN npm i -g pnpm@7 && pnpm i --frozen-lockfile --store=node_modules/.pnpm-store && pnpm run build
|
RUN npm i -g pnpm@7 && pnpm i --frozen-lockfile --store=node_modules/.pnpm-store && pnpm run build
|
||||||
|
|
||||||
FROM node:16-alpine as relocate
|
FROM node:16-alpine as relocate
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY --from=builder /app/dist/apps/ligo-virgo ./dist
|
COPY --from=builder /app/dist/apps/ligo-virgo ./dist
|
||||||
COPY --from=builder /app/Caddyfile ./
|
COPY --from=builder /app/.github/deployment/Caddyfile-lisa ./Caddyfile
|
||||||
RUN rm ./dist/*.txt
|
RUN rm ./dist/*.txt
|
||||||
|
|
||||||
# =============
|
# =============
|
@ -1,13 +1,13 @@
|
|||||||
FROM node:16-alpine as builder
|
FROM node:16-alpine as builder
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN apk add g++ make python3 git
|
RUN apk add g++ make python3 git libpng-dev
|
||||||
RUN npm i -g pnpm@7 && pnpm i --frozen-lockfile --store=node_modules/.pnpm-store && pnpm run build:venus
|
RUN npm i -g pnpm@7 && pnpm i --frozen-lockfile --store=node_modules/.pnpm-store && pnpm run build:venus
|
||||||
|
|
||||||
FROM node:16-alpine as relocate
|
FROM node:16-alpine as relocate
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY --from=builder /app/dist/apps/venus ./dist
|
COPY --from=builder /app/dist/apps/venus ./dist
|
||||||
COPY --from=builder /app/Caddyfile-venus ./Caddyfile
|
COPY --from=builder /app/.github/deployment/Caddyfile-venus ./Caddyfile
|
||||||
RUN rm ./dist/*.txt
|
RUN rm ./dist/*.txt
|
||||||
|
|
||||||
# =============
|
# =============
|
2
.github/env/.env.e2e
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
NX_LOCAL=true
|
||||||
|
NX_E2E=true
|
57
.github/workflows/affine.yml
vendored
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
name: Build AFFiNE-Local
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [master]
|
||||||
|
# pull_request:
|
||||||
|
# branches: [master]
|
||||||
|
|
||||||
|
# Cancels all previous workflow runs for pull requests that have not completed.
|
||||||
|
# See https://docs.github.com/en/actions/using-jobs/using-concurrency
|
||||||
|
concurrency:
|
||||||
|
# The concurrency group contains the workflow name and the branch name for
|
||||||
|
# pull requests or the commit hash for any other events.
|
||||||
|
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
env:
|
||||||
|
REGISTRY: ghcr.io
|
||||||
|
NAMESPACE: toeverything
|
||||||
|
AFFINE_IMAGE_NAME: AFFiNE
|
||||||
|
IMAGE_TAG_LATEST: nightly-latest
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
ligo-virgo:
|
||||||
|
runs-on: self-hosted
|
||||||
|
environment: development
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Log in to the Container registry
|
||||||
|
uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Extract metadata (tags, labels) for Docker (AFFiNE-Local)
|
||||||
|
id: meta_affine
|
||||||
|
uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
|
||||||
|
with:
|
||||||
|
images: ${{ env.REGISTRY }}/${{ env.NAMESPACE }}/${{ env.AFFINE_IMAGE_NAME }}
|
||||||
|
tags: ${{ env.IMAGE_TAG_LATEST }}
|
||||||
|
|
||||||
|
- name: Build and push Docker image (AFFINE-Local)
|
||||||
|
uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: ./.github/deployment/Dockerfile-affine
|
||||||
|
push: ${{ github.ref == 'refs/heads/master' && true || false }}
|
||||||
|
tags: ${{ steps.meta_affine.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta_affine.outputs.labels }}
|
||||||
|
target: AFFiNE
|
34
.github/workflows/check.yml
vendored
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
name: standard check
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "develop", "master" ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ "develop", "master" ]
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.event.number || github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
main:
|
||||||
|
name: Nx Cloud - Main Job
|
||||||
|
uses: ./.github/workflows/nx-cloud-main.yml
|
||||||
|
secrets: inherit
|
||||||
|
with:
|
||||||
|
main-branch-name: develop
|
||||||
|
# number-of-agents: 2
|
||||||
|
parallel-commands: |
|
||||||
|
pnpm exec nx-cloud record -- pnpm exec nx format:check
|
||||||
|
pnpm e2e:ci ${{ github.ref == 'refs/heads/develop' && '--record' || '' }}
|
||||||
|
pnpm exec nx affected --target=lint --parallel=2 --exclude=components-common,keck,theme
|
||||||
|
pnpm exec nx affected --target=build --parallel=2
|
||||||
|
# parallel-commands-on-agents: |
|
||||||
|
# pnpm exec nx affected --target=lint --parallel=2 --exclude=components-common,keck,theme
|
||||||
|
# pnpm exec nx affected --target=build --parallel=2
|
||||||
|
|
||||||
|
# agents:
|
||||||
|
# name: Nx Cloud - Agents
|
||||||
|
# uses: nrwl/ci/.github/workflows/nx-cloud-agents.yml@v0.6
|
||||||
|
# with:
|
||||||
|
# number-of-agents: 2
|
72
.github/workflows/codeql-analysis.yml
vendored
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
# For most projects, this workflow file will not need changing; you simply need
|
||||||
|
# to commit it to your repository.
|
||||||
|
#
|
||||||
|
# You may wish to alter this file to override the set of languages analyzed,
|
||||||
|
# or to provide custom queries or build logic.
|
||||||
|
#
|
||||||
|
# ******** NOTE ********
|
||||||
|
# We have attempted to detect the languages in your repository. Please check
|
||||||
|
# the `language` matrix defined below to confirm you have the correct set of
|
||||||
|
# supported CodeQL languages.
|
||||||
|
#
|
||||||
|
name: "CodeQL"
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "develop", master ]
|
||||||
|
pull_request:
|
||||||
|
# The branches below must be a subset of the branches above
|
||||||
|
branches: [ "develop" ]
|
||||||
|
schedule:
|
||||||
|
- cron: '27 1 * * 0'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
analyze:
|
||||||
|
name: Analyze
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
actions: read
|
||||||
|
contents: read
|
||||||
|
security-events: write
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
language: [ 'javascript' ]
|
||||||
|
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
|
||||||
|
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
# Initializes the CodeQL tools for scanning.
|
||||||
|
- name: Initialize CodeQL
|
||||||
|
uses: github/codeql-action/init@v2
|
||||||
|
with:
|
||||||
|
languages: ${{ matrix.language }}
|
||||||
|
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||||
|
# By default, queries listed here will override any specified in a config file.
|
||||||
|
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||||
|
|
||||||
|
# Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
||||||
|
# queries: security-extended,security-and-quality
|
||||||
|
|
||||||
|
|
||||||
|
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||||
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
|
- name: Autobuild
|
||||||
|
uses: github/codeql-action/autobuild@v2
|
||||||
|
|
||||||
|
# ℹ️ Command-line programs to run using the OS shell.
|
||||||
|
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||||
|
|
||||||
|
# If the Autobuild fails above, remove it and uncomment the following three lines.
|
||||||
|
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
|
||||||
|
|
||||||
|
# - run: |
|
||||||
|
# echo "Run, Build Application using script"
|
||||||
|
# ./location_of_script_within_repo/buildscript.sh
|
||||||
|
|
||||||
|
- name: Perform CodeQL Analysis
|
||||||
|
uses: github/codeql-action/analyze@v2
|
14
.github/workflows/keck.yml
vendored
@ -6,12 +6,14 @@ on:
|
|||||||
branches: [master]
|
branches: [master]
|
||||||
paths:
|
paths:
|
||||||
- 'apps/keck/**'
|
- 'apps/keck/**'
|
||||||
|
- '.github/deployment'
|
||||||
- '.github/workflows/keck.yml'
|
- '.github/workflows/keck.yml'
|
||||||
pull_request:
|
# pull_request:
|
||||||
branches: [master]
|
# branches: [master]
|
||||||
paths:
|
# paths:
|
||||||
- 'apps/keck/**'
|
# - 'apps/keck/**'
|
||||||
- '.github/workflows/keck.yml'
|
# - '.github/deployment'
|
||||||
|
# - '.github/workflows/keck.yml'
|
||||||
|
|
||||||
# Cancels all previous workflow runs for pull requests that have not completed.
|
# Cancels all previous workflow runs for pull requests that have not completed.
|
||||||
# See https://docs.github.com/en/actions/using-jobs/using-concurrency
|
# See https://docs.github.com/en/actions/using-jobs/using-concurrency
|
||||||
@ -60,7 +62,7 @@ jobs:
|
|||||||
uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
|
uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: ./Dockerfile-keck
|
file: ./.github/deployment/Dockerfile-keck
|
||||||
push: ${{ github.ref == 'refs/heads/field' && true || false }}
|
push: ${{ github.ref == 'refs/heads/field' && true || false }}
|
||||||
tags: ${{ steps.meta_keck.outputs.tags }}
|
tags: ${{ steps.meta_keck.outputs.tags }}
|
||||||
labels: ${{ steps.meta_keck.outputs.labels }}
|
labels: ${{ steps.meta_keck.outputs.labels }}
|
||||||
|
4
.github/workflows/lint.yml
vendored
@ -3,8 +3,8 @@ name: Lint
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [master]
|
branches: [master]
|
||||||
pull_request:
|
# pull_request:
|
||||||
branches: [master]
|
# branches: [master]
|
||||||
|
|
||||||
# Cancels all previous workflow runs for pull requests that have not completed.
|
# Cancels all previous workflow runs for pull requests that have not completed.
|
||||||
# See https://docs.github.com/en/actions/using-jobs/using-concurrency
|
# See https://docs.github.com/en/actions/using-jobs/using-concurrency
|
||||||
|
5
.github/workflows/lisa.yml
vendored
@ -3,8 +3,8 @@ name: Build Lisa
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [master]
|
branches: [master]
|
||||||
pull_request:
|
# pull_request:
|
||||||
branches: [master]
|
# branches: [master]
|
||||||
|
|
||||||
# Cancels all previous workflow runs for pull requests that have not completed.
|
# Cancels all previous workflow runs for pull requests that have not completed.
|
||||||
# See https://docs.github.com/en/actions/using-jobs/using-concurrency
|
# See https://docs.github.com/en/actions/using-jobs/using-concurrency
|
||||||
@ -53,6 +53,7 @@ jobs:
|
|||||||
uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
|
uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
|
file: ./.github/deployment/Dockerfile-lisa
|
||||||
push: ${{ github.ref == 'refs/heads/master' && true || false }}
|
push: ${{ github.ref == 'refs/heads/master' && true || false }}
|
||||||
tags: ${{ steps.meta_lisa.outputs.tags }}
|
tags: ${{ steps.meta_lisa.outputs.tags }}
|
||||||
labels: ${{ steps.meta_lisa.outputs.labels }}
|
labels: ${{ steps.meta_lisa.outputs.labels }}
|
||||||
|
300
.github/workflows/nx-cloud-main.yml
vendored
Normal file
@ -0,0 +1,300 @@
|
|||||||
|
name: Nx Cloud Main
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
secrets:
|
||||||
|
NX_CLOUD_ACCESS_TOKEN:
|
||||||
|
required: false
|
||||||
|
NX_CLOUD_AUTH_TOKEN:
|
||||||
|
required: false
|
||||||
|
NX_CYPRESS_KEY:
|
||||||
|
required: false
|
||||||
|
inputs:
|
||||||
|
number-of-agents:
|
||||||
|
required: false
|
||||||
|
type: number
|
||||||
|
environment-variables:
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
init-commands:
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
final-commands:
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
parallel-commands:
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
parallel-commands-on-agents:
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
node-version:
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
yarn-version:
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
npm-version:
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
pnpm-version:
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
install-commands:
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
main-branch-name:
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
default: main
|
||||||
|
runs-on:
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
default: ubuntu-latest
|
||||||
|
# We needed this input in order to be able to configure out integration tests for this repo, it is not documented
|
||||||
|
# so as to not cause confusion/add noise, but technically any consumer of the workflow can use it if they want to.
|
||||||
|
working-directory:
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
|
||||||
|
env:
|
||||||
|
NX_CLOUD_DISTRIBUTED_EXECUTION: true
|
||||||
|
NX_CLOUD_DISTRIBUTED_EXECUTION_AGENT_COUNT: ${{ inputs.number-of-agents }}
|
||||||
|
NX_BRANCH: ${{ github.event.number || github.ref_name }}
|
||||||
|
NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}
|
||||||
|
NX_CLOUD_AUTH_TOKEN: ${{ secrets.NX_CLOUD_AUTH_TOKEN }}
|
||||||
|
CYPRESS_RECORD_KEY: ${{ secrets.NX_CYPRESS_KEY }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
main:
|
||||||
|
runs-on: ${{ inputs.runs-on }}
|
||||||
|
# The name of the job which will invoke this one is expected to be "Nx Cloud - Main Job", and whatever we call this will be appended
|
||||||
|
# to that one after a forward slash, so we keep this one intentionally short to produce "Nx Cloud - Main Job / Run" in the Github UI
|
||||||
|
name: Run
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
working-directory: ${{ inputs.working-directory || github.workspace }}
|
||||||
|
# Specify shell to help normalize across different operating systems
|
||||||
|
shell: bash
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
name: Checkout [Pull Request]
|
||||||
|
if: ${{ github.event_name == 'pull_request' }}
|
||||||
|
with:
|
||||||
|
# By default, PRs will be checked-out based on the Merge Commit, but we want the actual branch HEAD.
|
||||||
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
|
# We need to fetch all branches and commits so that Nx affected has a base to compare against.
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
name: Checkout [Default Branch]
|
||||||
|
if: ${{ github.event_name != 'pull_request' }}
|
||||||
|
with:
|
||||||
|
# We need to fetch all branches and commits so that Nx affected has a base to compare against.
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Derive appropriate SHAs for base and head for `nx affected` commands
|
||||||
|
uses: nrwl/nx-set-shas@v2
|
||||||
|
with:
|
||||||
|
main-branch-name: ${{ inputs.main-branch-name }}
|
||||||
|
|
||||||
|
- name: Detect package manager
|
||||||
|
id: package_manager
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo "::set-output name=name::$([[ -f ./yarn.lock ]] && echo "yarn" || ([[ -f ./pnpm-lock.yaml ]] && echo "pnpm") || echo "npm")"
|
||||||
|
# Set node/npm/yarn versions using volta, with optional overrides provided by the consumer
|
||||||
|
- uses: volta-cli/action@fdf4cf319494429a105efaa71d0e5ec67f338c6e
|
||||||
|
with:
|
||||||
|
node-version: "${{ inputs.node-version }}"
|
||||||
|
npm-version: "${{ inputs.npm-version }}"
|
||||||
|
yarn-version: "${{ inputs.yarn-version }}"
|
||||||
|
|
||||||
|
# Install pnpm with exact version provided by consumer or fallback to latest
|
||||||
|
- name: Install PNPM
|
||||||
|
if: steps.package_manager.outputs.name == 'pnpm'
|
||||||
|
uses: pnpm/action-setup@v2.2.1
|
||||||
|
with:
|
||||||
|
version: ${{ inputs.pnpm-version || 'latest' }}
|
||||||
|
|
||||||
|
- name: Print node/npm/yarn versions
|
||||||
|
id: versions
|
||||||
|
run: |
|
||||||
|
node_ver=$( node --version )
|
||||||
|
yarn_ver=$( yarn --version || true )
|
||||||
|
pnpm_ver=$( pnpm --version || true )
|
||||||
|
echo "Node: ${node_ver:1}"
|
||||||
|
echo "NPM: $( npm --version )"
|
||||||
|
if [[ $yarn_ver != '' ]]; then echo "Yarn: $yarn_ver"; fi
|
||||||
|
if [[ $pnpm_ver != '' ]]; then echo "PNPM: $pnpm_ver"; fi
|
||||||
|
echo "::set-output name=node_version::${node_ver:1}"
|
||||||
|
- name: Use the node_modules cache if available [npm]
|
||||||
|
if: steps.package_manager.outputs.name == 'npm'
|
||||||
|
uses: actions/cache@v2
|
||||||
|
with:
|
||||||
|
path: ~/.npm
|
||||||
|
key: ${{ runner.os }}-node-${{ steps.versions.outputs.node_version }}-${{ hashFiles('**/package-lock.json') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-node-${{ steps.versions.outputs.node_version }}-
|
||||||
|
- name: Use the node_modules cache if available [pnpm]
|
||||||
|
if: steps.package_manager.outputs.name == 'pnpm'
|
||||||
|
uses: actions/cache@v2
|
||||||
|
with:
|
||||||
|
path: ~/.pnpm-store
|
||||||
|
key: ${{ runner.os }}-node-${{ steps.versions.outputs.node_version }}-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-node-${{ steps.versions.outputs.node_version }}-
|
||||||
|
- name: Get yarn cache directory path
|
||||||
|
if: steps.package_manager.outputs.name == 'yarn'
|
||||||
|
id: yarn-cache-dir-path
|
||||||
|
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||||
|
|
||||||
|
- name: Use the node_modules cache if available [yarn]
|
||||||
|
if: steps.package_manager.outputs.name == 'yarn'
|
||||||
|
uses: actions/cache@v2
|
||||||
|
with:
|
||||||
|
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||||
|
key: ${{ runner.os }}-node-${{ steps.versions.outputs.node_version }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-node-${{ steps.versions.outputs.node_version }}-yarn-
|
||||||
|
- name: Process environment-variables
|
||||||
|
if: ${{ inputs.environment-variables != '' }}
|
||||||
|
uses: actions/github-script@v6
|
||||||
|
env:
|
||||||
|
ENV_VARS: ${{ inputs.environment-variables }}
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const { appendFileSync } = require('fs');
|
||||||
|
// trim spaces and escape quotes
|
||||||
|
const cleanStr = str => str
|
||||||
|
.trim()
|
||||||
|
.replaceAll(/`/g, "\`");
|
||||||
|
// parse variable to correct type
|
||||||
|
const parseStr = str =>
|
||||||
|
str === 'true' || str === 'TRUE'
|
||||||
|
? true
|
||||||
|
: str === 'false' || str === 'FALSE'
|
||||||
|
? false
|
||||||
|
: isNaN(str)
|
||||||
|
? str
|
||||||
|
: parseFloat(str);
|
||||||
|
const varsStr = process.env.ENV_VARS || '';
|
||||||
|
const vars = varsStr
|
||||||
|
.split('\n')
|
||||||
|
.map(variable => variable.trim())
|
||||||
|
.filter(variable => variable.indexOf('=') > 0)
|
||||||
|
.map(variable => ({
|
||||||
|
name: cleanStr(variable.split('=')[0]),
|
||||||
|
value: cleanStr(variable.slice(variable.indexOf('=') + 1))
|
||||||
|
}));
|
||||||
|
for (const v of vars) {
|
||||||
|
console.log(`Appending environment variable \`${v.name}\` with value \`${v.value}\` to ${process.env.GITHUB_ENV}`);
|
||||||
|
appendFileSync(process.env.GITHUB_ENV, `${v.name}=${parseStr(v.value)}\n`);
|
||||||
|
}
|
||||||
|
- name: Run any configured install-commands
|
||||||
|
if: ${{ inputs.install-commands != '' }}
|
||||||
|
run: |
|
||||||
|
${{ inputs.install-commands }}
|
||||||
|
- name: Install dependencies
|
||||||
|
if: ${{ inputs.install-commands == '' }}
|
||||||
|
run: |
|
||||||
|
if [ "${{ steps.package_manager.outputs.name == 'yarn' }}" == "true" ]; then
|
||||||
|
echo "Running yarn install --frozen-lockfile"
|
||||||
|
yarn install --frozen-lockfile
|
||||||
|
elif [ "${{ steps.package_manager.outputs.name == 'pnpm' }}" == "true" ]; then
|
||||||
|
echo "Running pnpm install --frozen-lockfile"
|
||||||
|
pnpm install --frozen-lockfile
|
||||||
|
else
|
||||||
|
echo "Running npm ci"
|
||||||
|
npm ci
|
||||||
|
fi
|
||||||
|
# An unfortunate side-effect of the way reusable workflows work is that by the time they are pulled into the "caller"
|
||||||
|
# repo, they are effectively completely embedded in that context. This means that we cannot reference any files which
|
||||||
|
# are local to this repo which defines the workflow, and we therefore need to work around this by embedding the contents
|
||||||
|
# of the shell utilities for executing commands into the workflow directly.
|
||||||
|
- name: Create command utils
|
||||||
|
uses: actions/github-script@v6
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const { writeFileSync } = require('fs');
|
||||||
|
const runCommandsInParallelScript = `
|
||||||
|
# Extract the provided commands from the stringified JSON array.
|
||||||
|
IFS=$'\n' read -d '' -a userCommands < <((jq -c -r '.[]') <<<"$1")
|
||||||
|
# Invoke the provided commands in parallel and collect their exit codes.
|
||||||
|
pids=()
|
||||||
|
for userCommand in "\${userCommands[@]}"; do
|
||||||
|
eval "$userCommand" & pids+=($!)
|
||||||
|
done
|
||||||
|
# If any one of the invoked commands exited with a non-zero exit code, exit the whole thing with code 1.
|
||||||
|
for pid in \${pids[*]}; do
|
||||||
|
if ! wait $pid; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
# All the invoked commands must have exited with code zero.
|
||||||
|
exit 0
|
||||||
|
`;
|
||||||
|
writeFileSync('./.github/workflows/run-commands-in-parallel.sh', runCommandsInParallelScript);
|
||||||
|
- name: Prepare command utils
|
||||||
|
# We need to escape the workspace path to be consistent cross-platform: https://github.com/actions/runner/issues/1066
|
||||||
|
run: chmod +x ${GITHUB_WORKSPACE//\\//}/.github/workflows/run-commands-in-parallel.sh
|
||||||
|
|
||||||
|
- name: Initialize the Nx Cloud distributed CI run
|
||||||
|
run: npx nx-cloud start-ci-run
|
||||||
|
|
||||||
|
# The good thing about the multi-line string input for sequential commands is that we can simply forward it on as is to the bash shell and it will behave
|
||||||
|
# how we want it to in terms of quote escaping, variable assignment etc
|
||||||
|
- name: Run any configured init-commands sequentially
|
||||||
|
if: ${{ inputs.init-commands != '' }}
|
||||||
|
run: |
|
||||||
|
${{ inputs.init-commands }}
|
||||||
|
- name: Process parallel commands configuration
|
||||||
|
uses: actions/github-script@v6
|
||||||
|
id: parallel_commands_config
|
||||||
|
env:
|
||||||
|
PARALLEL_COMMANDS: ${{ inputs.parallel-commands }}
|
||||||
|
PARALLEL_COMMANDS_ON_AGENTS: ${{ inputs.parallel-commands-on-agents }}
|
||||||
|
with:
|
||||||
|
# For the ones configured for main, explicitly set NX_CLOUD_DISTRIBUTED_EXECUTION to false, taking into account commands chained with &&
|
||||||
|
# within the strings. In order to properly escape single quotes we need to do some manual replacing and escaping so that the commands
|
||||||
|
# are forwarded onto the run-commands-in-parallel.sh script appropriately.
|
||||||
|
script: |
|
||||||
|
const parallelCommandsOnMainStr = process.env.PARALLEL_COMMANDS || '';
|
||||||
|
const parallelCommandsOnAgentsStr = process.env.PARALLEL_COMMANDS_ON_AGENTS || '';
|
||||||
|
const parallelCommandsOnMain = parallelCommandsOnMainStr
|
||||||
|
.split('\n')
|
||||||
|
.map(command => command.trim())
|
||||||
|
.filter(command => command.length > 0)
|
||||||
|
.map(s => s.replace(/'/g, '%27'));
|
||||||
|
const parallelCommandsOnAgents = parallelCommandsOnAgentsStr
|
||||||
|
.split('\n')
|
||||||
|
.map(command => command.trim())
|
||||||
|
.filter(command => command.length > 0)
|
||||||
|
.map(s => s.replace(/'/g, '%27'));
|
||||||
|
const formattedArrayOfCommands = [
|
||||||
|
...parallelCommandsOnMain.map(s => s
|
||||||
|
.split(' && ')
|
||||||
|
.map(s => `NX_CLOUD_DISTRIBUTED_EXECUTION=false ${s}`)
|
||||||
|
.join(' && ')
|
||||||
|
),
|
||||||
|
...parallelCommandsOnAgents,
|
||||||
|
];
|
||||||
|
const stringifiedEncodedArrayOfCommands = JSON.stringify(formattedArrayOfCommands)
|
||||||
|
.replace(/%27/g, "'\\''");
|
||||||
|
return stringifiedEncodedArrayOfCommands
|
||||||
|
result-encoding: string
|
||||||
|
|
||||||
|
- name: Run any configured parallel commands on main and agent jobs
|
||||||
|
# We need to escape the workspace path to be consistent cross-platform: https://github.com/actions/runner/issues/1066
|
||||||
|
run: ${GITHUB_WORKSPACE//\\//}/.github/workflows/run-commands-in-parallel.sh '${{ steps.parallel_commands_config.outputs.result }}'
|
||||||
|
|
||||||
|
# The good thing about the multi-line string input for sequential commands is that we can simply forward it on as is to the bash shell and it will behave
|
||||||
|
# how we want it to in terms of quote escaping, variable assignment etc
|
||||||
|
- name: Run any configured final-commands sequentially
|
||||||
|
if: ${{ inputs.final-commands != '' }}
|
||||||
|
run: |
|
||||||
|
${{ inputs.final-commands }}
|
||||||
|
- name: Stop all running agents for this CI run
|
||||||
|
# It's important that we always run this step, otherwise in the case of any failures in preceding non-Nx steps, the agents will keep running and waste billable minutes
|
||||||
|
if: ${{ always() }}
|
||||||
|
run: npx nx-cloud stop-all-agents
|
13
.github/workflows/venus.yml
vendored
@ -5,12 +5,13 @@ on:
|
|||||||
branches: [master]
|
branches: [master]
|
||||||
paths:
|
paths:
|
||||||
- 'apps/venus/**'
|
- 'apps/venus/**'
|
||||||
|
- '.github/deployment'
|
||||||
- '.github/workflows/venus.yml'
|
- '.github/workflows/venus.yml'
|
||||||
pull_request:
|
# pull_request:
|
||||||
branches: [master]
|
# branches: [master]
|
||||||
paths:
|
# paths:
|
||||||
- 'apps/venus/**'
|
# - 'apps/venus/**'
|
||||||
- '.github/workflows/venus.yml'
|
# - '.github/workflows/venus.yml'
|
||||||
|
|
||||||
# Cancels all previous workflow runs for pull requests that have not completed.
|
# Cancels all previous workflow runs for pull requests that have not completed.
|
||||||
# See https://docs.github.com/en/actions/using-jobs/using-concurrency
|
# See https://docs.github.com/en/actions/using-jobs/using-concurrency
|
||||||
@ -59,7 +60,7 @@ jobs:
|
|||||||
uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
|
uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: ./Dockerfile-venus
|
file: ./.github/deployment/Dockerfile-venus
|
||||||
push: ${{ github.ref == 'refs/heads/master' && true || false }}
|
push: ${{ github.ref == 'refs/heads/master' && true || false }}
|
||||||
tags: ${{ steps.meta_venus.outputs.tags }}
|
tags: ${{ steps.meta_venus.outputs.tags }}
|
||||||
labels: ${{ steps.meta_venus.outputs.labels }}
|
labels: ${{ steps.meta_venus.outputs.labels }}
|
||||||
|
31
README.md
@ -40,13 +40,20 @@ See https://github.com/all-?/all-contributors/issues/361#issuecomment-637166066
|
|||||||
|
|
||||||
<p align="center"><img width="1920" alt="affine_screen" src="https://user-images.githubusercontent.com/21084335/182552060-972cac0e-6258-4ccb-85bd-3bb466c30ccd.png"><p/>
|
<p align="center"><img width="1920" alt="affine_screen" src="https://user-images.githubusercontent.com/21084335/182552060-972cac0e-6258-4ccb-85bd-3bb466c30ccd.png"><p/>
|
||||||
|
|
||||||
# Stay Up-to-Date
|
# Stay Up-to-Date and Support Us
|
||||||
|
|
||||||
![952cd7a5-70fe-48ab-b74f-23981d94d2c5](https://user-images.githubusercontent.com/79301703/182365526-df074c64-cee4-45f6-b8e0-b912f17332c6.gif)
|
![952cd7a5-70fe-48ab-b74f-23981d94d2c5](https://user-images.githubusercontent.com/79301703/182365526-df074c64-cee4-45f6-b8e0-b912f17332c6.gif)
|
||||||
|
|
||||||
|
# How to use
|
||||||
|
|
||||||
|
If you have experience in front-end development, please [refer to here](https://affine.gitbook.io/affine/basic-documentation/contribute-to-affine); if you want to experience our latest version, please wait a moment, we will launch a web version in the near future.
|
||||||
|
And, thanks to Lee who [made a desktop build with Tauri](https://github.com/m1911star/affine-client) for you to try out.
|
||||||
|
Please notice that AFFiNE is still under Alpha stage and is not ready for production use.
|
||||||
|
|
||||||
# Table of contents
|
# Table of contents
|
||||||
|
|
||||||
- [Stay Up-to-Date](#stay-up-to-date)
|
- [Stay Up-to-Date and Support Us](#stay-up-to-date-and-support-us)
|
||||||
|
- [How to Use](#how-to-use)
|
||||||
- [Table of contents](#table-of-contents)
|
- [Table of contents](#table-of-contents)
|
||||||
- [Shape your page](#shape-your-page)
|
- [Shape your page](#shape-your-page)
|
||||||
- [Plan your task](#plan-your-task)
|
- [Plan your task](#plan-your-task)
|
||||||
@ -84,11 +91,11 @@ Affine is fully built with web technologies so that consistency and accessibilit
|
|||||||
|
|
||||||
# Documentation
|
# Documentation
|
||||||
|
|
||||||
Please view the [documentation](https://affine.gitbook.io/affine/)
|
AFFiNE is not yet ready for production use. To install, you may check how to build or deploy the AFFiNE in [quick-start](https://affine.gitbook.io/affine/basic-documentation/contribute-to-affine/quick-start). For the full documentation, please view it [here](https://affine.gitbook.io/affine/).
|
||||||
|
|
||||||
## Getting Started with development
|
## Getting Started with development
|
||||||
|
|
||||||
Please view the path Contribute-to-AFFiNE/Software-Contributions/Quick-Start in documentation.
|
Please view the path Contribute-to-AFFiNE/Software-Contributions/Quick-Start in the documentation.
|
||||||
|
|
||||||
# Roadmap
|
# Roadmap
|
||||||
|
|
||||||
@ -116,39 +123,39 @@ It is all perfect... If there are not so many waste operations and redundant inf
|
|||||||
That's why we are making AFFiNE. Some of the most important features are:
|
That's why we are making AFFiNE. Some of the most important features are:
|
||||||
|
|
||||||
- Transformable
|
- Transformable
|
||||||
- Every block can be transformed equally as a database
|
- Every block can be transformed equally well as a database
|
||||||
- e.g. you can now set up a to-do with MarkDown in text view and edit it in kanban view.
|
- e.g. you can now set up a to-do with MarkDown in text view and edit it in kanban view.
|
||||||
- Every doc can be turned into a whiteboard
|
- Every doc can be turned into a whiteboard
|
||||||
- An always good-to-read, structured docs-form page is the best for your notes, but a boundless doodle surface is better for collaboration and creativity.
|
- An always good-to-read, structured docs-form page is the best for your notes, but a boundless doodle surface is better for collaboration and creativity.
|
||||||
- Atomic
|
- Atomic
|
||||||
- The basic element of affine are blocks, not pages.
|
- The basic element of affine are blocks, not pages.
|
||||||
- Blocks can be directly reuse and synced between pages.
|
- Blocks can be directly reused and synced between pages.
|
||||||
- Pages and blocks are searched and organized on the basis of connected graphs, not tree-like paths.
|
- Pages and blocks are searched and organized based on connected graphs, not tree-like paths.
|
||||||
- Dual-link and semantic search are fully supported.
|
- Dual-link and semantic search are fully supported.
|
||||||
- Collaborative and privacy-first
|
- Collaborative and privacy-first
|
||||||
- Data is always stored locally by default
|
- Data is always stored locally by default
|
||||||
- CRDTs are applied so that peer-to-peer collaboration is possible.
|
- CRDTs are applied so that peer-to-peer collaboration is possible.
|
||||||
|
|
||||||
We really appreciate the idea of Monday, airtable and notion database. They inspired what we think is right for task management. But we don't like the repeated works -- we don't want to set a todo easily with markdown but end up re-write it again in kanban or other databases.
|
We really appreciate the idea of Monday, Airtable and Notion databases. They inspired what we think is right for task management. But we don't like the repeated works -- we don't want to set a todo easily with markdown but end up re-write it again in kanban or other databases.
|
||||||
With AFFiNE, every block group has infinite views, for you to keep your single source of truth.
|
With AFFiNE, every block group has infinite views, for you to keep your single source of truth.
|
||||||
|
|
||||||
We would like to give special thanks to the innovators and pioneers who greatly inspired us:
|
We would like to give special thanks to the innovators and pioneers who greatly inspired us:
|
||||||
|
|
||||||
- Quip & Notion -- that docs can be organized as blocks
|
- Quip & Notion -- that docs can be organized as blocks
|
||||||
- Taskade & Monday -- brillant multi-demensional tables
|
- Taskade & Monday -- brilliant multi-dimensional tables
|
||||||
- Height & Linear -- beautiful task management tool
|
- Height & Linear -- beautiful task management tool
|
||||||
|
|
||||||
We would also like to give thanks to open-source projects that make affine possible:
|
We would also like to give thanks to open-source projects that make affine possible:
|
||||||
|
|
||||||
- [Yjs](https://github.com/yjs/yjs) & [Yrs](https://github.com/y-crdt/y-crdt) -- Fundamental support of CRDTs for our implements on state management and data sync.
|
- [Yjs](https://github.com/yjs/yjs) & [Yrs](https://github.com/y-crdt/y-crdt) -- Fundamental support of CRDTs for our implementation on state management and data sync.
|
||||||
- [React](https://github.com/facebook/react) -- View layer support and web GUI framework.
|
- [React](https://github.com/facebook/react) -- View layer support and web GUI framework.
|
||||||
- [Rust](https://github.com/rust-lang/rust) -- High performance language that extends the ability and availability of our real-time backend, JWST.
|
- [Rust](https://github.com/rust-lang/rust) -- High performance language that extends the ability and availability of our real-time backend, JWST.
|
||||||
- [Fossil](https://www2.fossil-scm.org/home/doc/trunk/www/index.wiki) -- Source code management tool made with CRDTs which inspired our design on block data structure.
|
- [Fossil](https://www2.fossil-scm.org/home/doc/trunk/www/index.wiki) -- Source code management tool made with CRDTs which inspired our design on block data structure.
|
||||||
- [slatejs](https://github.com/ianstormtaylor/slate) -- Customizable rich-text editor.
|
- [slatejs](https://github.com/ianstormtaylor/slate) -- Customizable rich-text editor.
|
||||||
- [Jotai](https://github.com/pmndrs/jotai) -- Minimal state management tool for frontend.
|
- [Jotai](https://github.com/pmndrs/jotai) -- Minimal state management tool for frontend.
|
||||||
- [Tldraw](https://github.com/tldraw/tldraw) -- Excellent drawing board.
|
- [Tldraw](https://github.com/tldraw/tldraw) -- Excellent drawing board.
|
||||||
- [MUI](https://github.com/mui/material-ui) -- Our most used graphic UI component library.
|
- [MUI](https://github.com/mui/material-ui) -- Our most used graphic UI component library.
|
||||||
- Other [dependancies](https://github.com/toeverything/AFFiNE/network/dependencies)
|
- Other [dependencies](https://github.com/toeverything/AFFiNE/network/dependencies)
|
||||||
|
|
||||||
Thanks a lot to the community for providing such powerful and simple libraries, so that we can focus more on the implementation of the product logic, and we hope that in the future our projects will also provide a more easy-to-use knowledge base for everyone.
|
Thanks a lot to the community for providing such powerful and simple libraries, so that we can focus more on the implementation of the product logic, and we hope that in the future our projects will also provide a more easy-to-use knowledge base for everyone.
|
||||||
|
|
||||||
|
@ -5,15 +5,15 @@
|
|||||||
"author": "DarkSky <darksky2048@gmail.com>",
|
"author": "DarkSky <darksky2048@gmail.com>",
|
||||||
"main": "jest.config.ts",
|
"main": "jest.config.ts",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"authing-js-sdk": "^4.23.33",
|
"authing-js-sdk": "^4.23.35",
|
||||||
"firebase-admin": "^11.0.0",
|
"firebase-admin": "^11.0.1",
|
||||||
"lib0": "^0.2.51",
|
"lib0": "^0.2.52",
|
||||||
"lru-cache": "^7.13.0",
|
"lru-cache": "^7.13.2",
|
||||||
"nanoid": "^4.0.0",
|
"nanoid": "^4.0.0",
|
||||||
"readable-stream": "^4.1.0",
|
"readable-stream": "^4.1.0",
|
||||||
"ws": "^8.8.0",
|
"ws": "^8.8.1",
|
||||||
"y-protocols": "^1.0.5",
|
"y-protocols": "^1.0.5",
|
||||||
"yjs": "^13.5.39"
|
"yjs": "^13.5.41"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/readable-stream": "^2.3.13",
|
"@types/readable-stream": "^2.3.13",
|
||||||
|
@ -34,34 +34,31 @@ const _checkAuth = async (
|
|||||||
response: http.ServerResponse,
|
response: http.ServerResponse,
|
||||||
callback: (response: http.OutgoingMessage, workspace: string) => boolean
|
callback: (response: http.OutgoingMessage, workspace: string) => boolean
|
||||||
) => {
|
) => {
|
||||||
|
const url = new URL(request.url, `http://${request.headers.host}`);
|
||||||
|
const workspace = _getWorkspace(url.pathname);
|
||||||
if (process.env.NODE_ENV === 'development') {
|
if (process.env.NODE_ENV === 'development') {
|
||||||
const url = new URL(request.url, `http://${request.headers.host}`);
|
|
||||||
const workspace = _getWorkspace(url.pathname);
|
|
||||||
if (workspace) return callback(response, workspace);
|
if (workspace) return callback(response, workspace);
|
||||||
return false;
|
return false;
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
const decodedToken = await firebaseAuth
|
|
||||||
.getAuth()
|
|
||||||
.verifyIdToken(request.headers.token as string);
|
|
||||||
const allowWorkspace = [AFFINE_COMMON_WORKSPACE, decodedToken.uid];
|
|
||||||
const url = new URL(request.url, `http://${request.headers.host}`);
|
|
||||||
const workspace = _getWorkspace(url.pathname);
|
|
||||||
if (allowWorkspace.includes(workspace)) {
|
|
||||||
return callback(response, workspace);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
|
const decodedToken = await firebaseAuth
|
||||||
|
.getAuth()
|
||||||
|
.verifyIdToken(request.headers.token as string);
|
||||||
|
const allowWorkspace = [AFFINE_COMMON_WORKSPACE, decodedToken.uid];
|
||||||
|
if (allowWorkspace.includes(workspace)) {
|
||||||
|
return callback(response, workspace);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const HOST = process.env.HOST || 'localhost';
|
const HOST = process.env.HOST || 'localhost';
|
||||||
const PORT = process.env.PORT || 3000;
|
const PORT = process.env.PORT || 3000;
|
||||||
|
|
||||||
const _tokens = new LRUCache<string, string>({
|
const _tokens = new LRUCache<string, string>({
|
||||||
max: 10240,
|
max: 1024 * 10,
|
||||||
ttl: 1000 * 60 * 5,
|
ttl: 1000 * 60 * 5,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
17
apps/ligo-virgo-e2e/.eslintrc.json
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"extends": ["plugin:cypress/recommended", "../../.eslintrc.json"],
|
||||||
|
"ignorePatterns": ["!**/*"],
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
|
||||||
|
"rules": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"files": ["src/plugins/index.js"],
|
||||||
|
"rules": {
|
||||||
|
"@typescript-eslint/no-var-requires": "off",
|
||||||
|
"no-undef": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
18
apps/ligo-virgo-e2e/cypress.config.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { defineConfig } from 'cypress';
|
||||||
|
|
||||||
|
module.exports = defineConfig({
|
||||||
|
projectId: 'r1wrqr',
|
||||||
|
e2e: {
|
||||||
|
supportFile: './src/support/index.ts',
|
||||||
|
specPattern: './src/integration',
|
||||||
|
setupNodeEvents(on, config) {
|
||||||
|
// implement node event listeners here
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fileServerFolder: '.',
|
||||||
|
fixturesFolder: './src/fixtures',
|
||||||
|
video: false,
|
||||||
|
// videosFolder: '../../dist/cypress/apps/ligo-virgo-e2e/videos',
|
||||||
|
screenshotsFolder: '../../dist/cypress/apps/ligo-virgo-e2e/screenshots',
|
||||||
|
chromeWebSecurity: false,
|
||||||
|
});
|
28
apps/ligo-virgo-e2e/project.json
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||||
|
"sourceRoot": "apps/ligo-virgo-e2e/src",
|
||||||
|
"projectType": "application",
|
||||||
|
"targets": {
|
||||||
|
"e2e": {
|
||||||
|
"executor": "@nrwl/cypress:cypress",
|
||||||
|
"options": {
|
||||||
|
"cypressConfig": "apps/ligo-virgo-e2e/cypress.config.ts",
|
||||||
|
"devServerTarget": "ligo-virgo:serve"
|
||||||
|
},
|
||||||
|
"configurations": {
|
||||||
|
"production": {
|
||||||
|
"devServerTarget": "ligo-virgo:serve:production"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lint": {
|
||||||
|
"executor": "@nrwl/linter:eslint",
|
||||||
|
"outputs": ["{options.outputFile}"],
|
||||||
|
"options": {
|
||||||
|
"lintFilePatterns": ["apps/ligo-virgo-e2e/**/*.{js,ts}"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [],
|
||||||
|
"implicitDependencies": ["ligo-virgo"]
|
||||||
|
}
|
4
apps/ligo-virgo-e2e/src/fixtures/example.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"name": "Using fixtures to represent data",
|
||||||
|
"email": "hello@cypress.io"
|
||||||
|
}
|
14
apps/ligo-virgo-e2e/src/integration/app.spec.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { getTitle, getBoard } from '../support/app.po';
|
||||||
|
|
||||||
|
describe('ligo-virgo', () => {
|
||||||
|
beforeEach(() => cy.visit('/'));
|
||||||
|
|
||||||
|
it('basic load check', () => {
|
||||||
|
getTitle().contains('Get Started with AFFiNE');
|
||||||
|
|
||||||
|
cy.get('.block_container').contains('The Essentials');
|
||||||
|
|
||||||
|
getBoard().click();
|
||||||
|
cy.get('.tl-inner-div').contains('Graduating');
|
||||||
|
});
|
||||||
|
});
|
3
apps/ligo-virgo-e2e/src/support/app.po.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export const getTitle = () => cy.get('span[title]');
|
||||||
|
export const getDoc = () => cy.contains('Doc');
|
||||||
|
export const getBoard = () => cy.contains('Board');
|
33
apps/ligo-virgo-e2e/src/support/commands.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
// ***********************************************
|
||||||
|
// This example commands.js shows you how to
|
||||||
|
// create various custom commands and overwrite
|
||||||
|
// existing commands.
|
||||||
|
//
|
||||||
|
// For more comprehensive examples of custom
|
||||||
|
// commands please read more here:
|
||||||
|
// https://on.cypress.io/custom-commands
|
||||||
|
// ***********************************************
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||||
|
declare namespace Cypress {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
interface Chainable<Subject> {
|
||||||
|
login(email: string, password: string): void;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//
|
||||||
|
// -- This is a parent command --
|
||||||
|
Cypress.Commands.add('login', (email, password) => {
|
||||||
|
console.log('Custom command example: Login', email, password);
|
||||||
|
});
|
||||||
|
//
|
||||||
|
// -- This is a child command --
|
||||||
|
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// -- This is a dual command --
|
||||||
|
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// -- This will overwrite an existing command --
|
||||||
|
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
|
17
apps/ligo-virgo-e2e/src/support/index.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// ***********************************************************
|
||||||
|
// This example support/index.js is processed and
|
||||||
|
// loaded automatically before your test files.
|
||||||
|
//
|
||||||
|
// This is a great place to put global configuration and
|
||||||
|
// behavior that modifies Cypress.
|
||||||
|
//
|
||||||
|
// You can change the location of this file or turn off
|
||||||
|
// automatically serving support files with the
|
||||||
|
// 'supportFile' configuration option.
|
||||||
|
//
|
||||||
|
// You can read more here:
|
||||||
|
// https://on.cypress.io/configuration
|
||||||
|
// ***********************************************************
|
||||||
|
|
||||||
|
// Import commands.js using ES2015 syntax:
|
||||||
|
import './commands';
|
10
apps/ligo-virgo-e2e/tsconfig.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"sourceMap": false,
|
||||||
|
"outDir": "../../dist/out-tsc",
|
||||||
|
"allowJs": true,
|
||||||
|
"types": ["cypress", "node"]
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts", "src/**/*.js"]
|
||||||
|
}
|
@ -13,8 +13,8 @@
|
|||||||
"@mui/icons-material": "^5.8.4"
|
"@mui/icons-material": "^5.8.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"firebase": "^9.8.4",
|
"firebase": "^9.9.2",
|
||||||
"mini-css-extract-plugin": "^2.6.1",
|
"mini-css-extract-plugin": "^2.6.1",
|
||||||
"webpack": "^5.73.0"
|
"webpack": "^5.74.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,7 +48,8 @@
|
|||||||
"configurations": {
|
"configurations": {
|
||||||
"production": {
|
"production": {
|
||||||
"buildTarget": "ligo-virgo:build:production",
|
"buildTarget": "ligo-virgo:build:production",
|
||||||
"hmr": false
|
"hmr": false,
|
||||||
|
"open": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -4,8 +4,8 @@
|
|||||||
<!-- local dev index.html -->
|
<!-- local dev index.html -->
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<link rel="icon" href="https://app.affine.pro/favicon.ico" />
|
<link rel="icon" href="/favicon.ico" />
|
||||||
<title>AFFiNE | Local Dev Environment</title>
|
<title>AFFiNE - All In One Workos</title>
|
||||||
<script>
|
<script>
|
||||||
window.global = window;
|
window.global = window;
|
||||||
</script>
|
</script>
|
||||||
|
@ -10,16 +10,13 @@ export function WorkspaceHome() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const navigate_to_user_initial_page = async () => {
|
const navigate_to_user_initial_page = async () => {
|
||||||
const recent_pages = await services.api.userConfig.getRecentPages(
|
const [recent_pages, user_initial_page_id] = await Promise.all([
|
||||||
workspace_id,
|
services.api.userConfig.getRecentPages(workspace_id, user.id),
|
||||||
user.id
|
services.api.userConfig.getUserInitialPage(
|
||||||
);
|
|
||||||
|
|
||||||
const user_initial_page_id =
|
|
||||||
await services.api.userConfig.getUserInitialPage(
|
|
||||||
workspace_id,
|
workspace_id,
|
||||||
user.id
|
user.id
|
||||||
);
|
),
|
||||||
|
]);
|
||||||
if (recent_pages.length === 0) {
|
if (recent_pages.length === 0) {
|
||||||
await services.api.editorBlock.copyTemplateToPage(
|
await services.api.editorBlock.copyTemplateToPage(
|
||||||
workspace_id,
|
workspace_id,
|
||||||
|
@ -1,16 +1,9 @@
|
|||||||
/* eslint-disable filename-rules/match */
|
/* eslint-disable filename-rules/match */
|
||||||
import {
|
import { useEffect, useRef, type UIEvent, useState } from 'react';
|
||||||
useEffect,
|
|
||||||
useRef,
|
|
||||||
type UIEvent,
|
|
||||||
useState,
|
|
||||||
useLayoutEffect,
|
|
||||||
} from 'react';
|
|
||||||
import { useParams } from 'react-router';
|
import { useParams } from 'react-router';
|
||||||
import {
|
import {
|
||||||
MuiBox as Box,
|
MuiBox as Box,
|
||||||
MuiCircularProgress as CircularProgress,
|
MuiCircularProgress as CircularProgress,
|
||||||
MuiDivider as Divider,
|
|
||||||
styled,
|
styled,
|
||||||
} from '@toeverything/components/ui';
|
} from '@toeverything/components/ui';
|
||||||
import { AffineEditor } from '@toeverything/components/affine-editor';
|
import { AffineEditor } from '@toeverything/components/affine-editor';
|
||||||
@ -40,30 +33,8 @@ export function Page(props: PageProps) {
|
|||||||
const { page_id } = useParams();
|
const { page_id } = useParams();
|
||||||
const { showSpaceSidebar, fixedDisplay, setSpaceSidebarVisible } =
|
const { showSpaceSidebar, fixedDisplay, setSpaceSidebarVisible } =
|
||||||
useShowSpaceSidebar();
|
useShowSpaceSidebar();
|
||||||
const { user } = useUserAndSpaces();
|
|
||||||
const dailyNotesFlag = useFlag('BooleanDailyNotes', false);
|
const dailyNotesFlag = useFlag('BooleanDailyNotes', false);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!user?.id || !page_id) return;
|
|
||||||
const updateRecentPages = async () => {
|
|
||||||
// TODO: deal with it temporarily
|
|
||||||
await services.api.editorBlock.getWorkspaceDbBlock(
|
|
||||||
props.workspace,
|
|
||||||
{
|
|
||||||
userId: user.id,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// await services.api.userConfig.addRecentPage(
|
|
||||||
// props.workspace,
|
|
||||||
// user.id,
|
|
||||||
// page_id
|
|
||||||
// );
|
|
||||||
await services.api.editorBlock.clearUndoRedo(props.workspace);
|
|
||||||
};
|
|
||||||
updateRecentPages();
|
|
||||||
}, [user, props.workspace, page_id]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LigoApp>
|
<LigoApp>
|
||||||
<LigoLeftContainer style={{ width: fixedDisplay ? '300px' : 0 }}>
|
<LigoLeftContainer style={{ width: fixedDisplay ? '300px' : 0 }}>
|
||||||
@ -94,14 +65,14 @@ export function Page(props: PageProps) {
|
|||||||
)}
|
)}
|
||||||
<div>
|
<div>
|
||||||
<CollapsibleTitle
|
<CollapsibleTitle
|
||||||
title="Activities"
|
title="ACTIVITIES"
|
||||||
initialOpen={false}
|
initialOpen={false}
|
||||||
>
|
>
|
||||||
<Activities />
|
<Activities />
|
||||||
</CollapsibleTitle>
|
</CollapsibleTitle>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<CollapsiblePageTree title="Page Tree">
|
<CollapsiblePageTree title="PAGES">
|
||||||
{page_id ? <PageTree /> : null}
|
{page_id ? <PageTree /> : null}
|
||||||
</CollapsiblePageTree>
|
</CollapsiblePageTree>
|
||||||
</div>
|
</div>
|
||||||
@ -122,38 +93,29 @@ const EditorContainer = ({
|
|||||||
workspace: string;
|
workspace: string;
|
||||||
}) => {
|
}) => {
|
||||||
const [lockScroll, setLockScroll] = useState(false);
|
const [lockScroll, setLockScroll] = useState(false);
|
||||||
const scrollContainerRef = useRef<HTMLDivElement>();
|
const [scrollContainer, setScrollContainer] = useState<HTMLElement>();
|
||||||
const editorRef = useRef<BlockEditor>();
|
const editorRef = useRef<BlockEditor>();
|
||||||
|
|
||||||
const onScroll = (event: UIEvent) => {
|
const onScroll = (event: UIEvent) => {
|
||||||
editorRef.current.getHooks().onRootNodeScroll(event);
|
editorRef.current.getHooks().onRootNodeScroll(event);
|
||||||
editorRef.current.scrollManager.emitScrollEvent(event);
|
editorRef.current.scrollManager.emitScrollEvent(event);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
editorRef.current.scrollManager.scrollContainer =
|
|
||||||
scrollContainerRef.current;
|
|
||||||
|
|
||||||
editorRef.current.scrollManager.scrollController = {
|
|
||||||
lockScroll: () => setLockScroll(true),
|
|
||||||
unLockScroll: () => setLockScroll(false),
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const { setPageClientWidth } = usePageClientWidth();
|
const { setPageClientWidth } = usePageClientWidth();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (scrollContainerRef.current) {
|
if (scrollContainer) {
|
||||||
const obv = new ResizeObserver(e => {
|
const obv = new ResizeObserver(e => {
|
||||||
setPageClientWidth(e[0].contentRect.width);
|
setPageClientWidth(e[0].contentRect.width);
|
||||||
});
|
});
|
||||||
obv.observe(scrollContainerRef.current);
|
obv.observe(scrollContainer);
|
||||||
return () => obv.disconnect();
|
return () => obv.disconnect();
|
||||||
}
|
}
|
||||||
});
|
}, [setPageClientWidth, scrollContainer]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledEditorContainer
|
<StyledEditorContainer
|
||||||
lockScroll={lockScroll}
|
lockScroll={lockScroll}
|
||||||
ref={scrollContainerRef}
|
ref={ref => setScrollContainer(ref)}
|
||||||
onScroll={onScroll}
|
onScroll={onScroll}
|
||||||
>
|
>
|
||||||
{pageId ? (
|
{pageId ? (
|
||||||
@ -161,6 +123,11 @@ const EditorContainer = ({
|
|||||||
workspace={workspace}
|
workspace={workspace}
|
||||||
rootBlockId={pageId}
|
rootBlockId={pageId}
|
||||||
ref={editorRef}
|
ref={editorRef}
|
||||||
|
scrollContainer={scrollContainer}
|
||||||
|
scrollController={{
|
||||||
|
lockScroll: () => setLockScroll(true),
|
||||||
|
unLockScroll: () => setLockScroll(false),
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Box
|
<Box
|
||||||
@ -200,7 +167,7 @@ const LigoLeftContainer = styled('div')({
|
|||||||
position: 'relative',
|
position: 'relative',
|
||||||
});
|
});
|
||||||
|
|
||||||
const WorkspaceSidebar = styled('div')(({ hidden }) => ({
|
const WorkspaceSidebar = styled('div')(({ theme }) => ({
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
bottom: '48px',
|
bottom: '48px',
|
||||||
top: '12px',
|
top: '12px',
|
||||||
@ -210,7 +177,7 @@ const WorkspaceSidebar = styled('div')(({ hidden }) => ({
|
|||||||
width: 300,
|
width: 300,
|
||||||
minWidth: 300,
|
minWidth: 300,
|
||||||
borderRadius: '0px 10px 10px 0px',
|
borderRadius: '0px 10px 10px 0px',
|
||||||
boxShadow: '0px 1px 10px rgba(152, 172, 189, 0.6)',
|
boxShadow: theme.affine.shadows.shadow1,
|
||||||
backgroundColor: '#FFFFFF',
|
backgroundColor: '#FFFFFF',
|
||||||
transitionProperty: 'left',
|
transitionProperty: 'left',
|
||||||
transitionDuration: '0.35s',
|
transitionDuration: '0.35s',
|
||||||
|
@ -1,34 +1,34 @@
|
|||||||
import { useCallback, useState } from 'react';
|
import React, { useCallback, useState } from 'react';
|
||||||
import { useNavigate, useParams } from 'react-router-dom';
|
import { useNavigate, useParams } from 'react-router-dom';
|
||||||
import clsx from 'clsx';
|
|
||||||
import style9 from 'style9';
|
|
||||||
import {
|
import {
|
||||||
MuiBox as Box,
|
MuiBox as Box,
|
||||||
MuiButton as Button,
|
MuiButton as Button,
|
||||||
MuiCollapse as Collapse,
|
MuiCollapse as Collapse,
|
||||||
MuiIconButton as IconButton,
|
MuiIconButton as IconButton,
|
||||||
|
styled,
|
||||||
} from '@toeverything/components/ui';
|
} from '@toeverything/components/ui';
|
||||||
import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';
|
import {
|
||||||
import ArrowRightIcon from '@mui/icons-material/ArrowRight';
|
ArrowDropDownIcon,
|
||||||
|
ArrowRightIcon,
|
||||||
|
} from '@toeverything/components/icons';
|
||||||
import { services } from '@toeverything/datasource/db-service';
|
import { services } from '@toeverything/datasource/db-service';
|
||||||
import { NewpageIcon } from '@toeverything/components/common';
|
|
||||||
import {
|
import {
|
||||||
usePageTree,
|
usePageTree,
|
||||||
useCalendarHeatmap,
|
useCalendarHeatmap,
|
||||||
} from '@toeverything/components/layout';
|
} from '@toeverything/components/layout';
|
||||||
|
import { AddIcon } from '@toeverything/components/icons';
|
||||||
|
|
||||||
const styles = style9.create({
|
const StyledContainer = styled('div')({
|
||||||
ligoButton: {
|
height: '32px',
|
||||||
textTransform: 'none',
|
display: 'flex',
|
||||||
},
|
alignItems: 'center',
|
||||||
newPage: {
|
});
|
||||||
color: '#B6C7D3',
|
|
||||||
width: '26px',
|
const StyledBtn = styled('div')({
|
||||||
fontSize: '18px',
|
color: '#98ACBD',
|
||||||
textAlign: 'center',
|
textTransform: 'none',
|
||||||
cursor: 'pointer',
|
fontSize: '12px',
|
||||||
},
|
fontWeight: '600',
|
||||||
});
|
});
|
||||||
|
|
||||||
export type CollapsiblePageTreeProps = {
|
export type CollapsiblePageTreeProps = {
|
||||||
@ -73,31 +73,35 @@ export function CollapsiblePageTree(props: CollapsiblePageTreeProps) {
|
|||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
paddingRight: 1,
|
paddingRight: 1,
|
||||||
|
'&:hover': {
|
||||||
|
background: '#f5f7f8',
|
||||||
|
borderRadius: '5px',
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
onMouseEnter={() => setNewPageBtnVisible(true)}
|
onMouseEnter={() => setNewPageBtnVisible(true)}
|
||||||
onMouseLeave={() => setNewPageBtnVisible(false)}
|
onMouseLeave={() => setNewPageBtnVisible(false)}
|
||||||
>
|
>
|
||||||
<Button
|
<StyledContainer>
|
||||||
startIcon={
|
{open ? (
|
||||||
open ? <ArrowDropDownIcon /> : <ArrowRightIcon />
|
<ArrowDropDownIcon sx={{ color: '#566B7D' }} />
|
||||||
}
|
) : (
|
||||||
onClick={() => setOpen(prev => !prev)}
|
<ArrowRightIcon sx={{ color: '#566B7D' }} />
|
||||||
sx={{ color: '#566B7D', textTransform: 'none' }}
|
)}
|
||||||
className={clsx(styles('ligoButton'), className)}
|
<StyledBtn onClick={() => setOpen(prev => !prev)}>
|
||||||
style={style}
|
{title}
|
||||||
disableElevation
|
</StyledBtn>
|
||||||
disableRipple
|
</StyledContainer>
|
||||||
>
|
|
||||||
{title}
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
{newPageBtnVisible && (
|
{newPageBtnVisible && (
|
||||||
<div
|
<AddIcon
|
||||||
|
style={{
|
||||||
|
width: '20px',
|
||||||
|
height: '20px',
|
||||||
|
color: '#98ACBD',
|
||||||
|
cursor: 'pointer',
|
||||||
|
}}
|
||||||
onClick={create_page}
|
onClick={create_page}
|
||||||
className={clsx(styles('newPage'), className)}
|
/>
|
||||||
>
|
|
||||||
+
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
{children ? (
|
{children ? (
|
||||||
|
@ -9,8 +9,8 @@ export const Logo = ({ color, style, ...props }) => {
|
|||||||
>
|
>
|
||||||
<rect width="52" height="52" rx="10" fill="#3E6FDB" />
|
<rect width="52" height="52" rx="10" fill="#3E6FDB" />
|
||||||
<path
|
<path
|
||||||
fill-rule="evenodd"
|
fillRule="evenodd"
|
||||||
clip-rule="evenodd"
|
clipRule="evenodd"
|
||||||
d="M24.2189 11.4392L14.6321 38.7943H20.2472L26.3453 19.8747L32.4461 38.7943H38.0423L28.454 11.4392H24.2189Z"
|
d="M24.2189 11.4392L14.6321 38.7943H20.2472L26.3453 19.8747L32.4461 38.7943H38.0423L28.454 11.4392H24.2189Z"
|
||||||
fill="white"
|
fill="white"
|
||||||
/>
|
/>
|
||||||
|
@ -11,15 +11,15 @@ const StyledTabs = styled('div')({
|
|||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
});
|
});
|
||||||
|
|
||||||
const StyledDivider = styled(Divider)<{ isActive?: boolean }>(
|
const StyledDivider = styled(Divider, {
|
||||||
({ isActive }) => {
|
shouldForwardProp: (prop: string) => !['isActive'].includes(prop),
|
||||||
return {
|
})<{ isActive?: boolean }>(({ isActive }) => {
|
||||||
flex: 1,
|
return {
|
||||||
backgroundColor: isActive ? '#3E6FDB' : '#ECF1FB',
|
flex: 1,
|
||||||
borderWidth: '2px',
|
backgroundColor: isActive ? '#3E6FDB' : '#ECF1FB',
|
||||||
};
|
borderWidth: '2px',
|
||||||
}
|
};
|
||||||
);
|
});
|
||||||
|
|
||||||
const TAB_TITLE = {
|
const TAB_TITLE = {
|
||||||
PAGES: 'pages',
|
PAGES: 'pages',
|
||||||
|
@ -95,15 +95,11 @@ export const WorkspaceName = () => {
|
|||||||
if (!currentSpaceId) {
|
if (!currentSpaceId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const [name, workspaceId] = await Promise.all([
|
||||||
const name = await services.api.userConfig.getWorkspaceName(
|
services.api.userConfig.getWorkspaceName(currentSpaceId),
|
||||||
currentSpaceId
|
services.api.userConfig.getWorkspaceId(currentSpaceId),
|
||||||
);
|
]);
|
||||||
setWorkspaceName(name);
|
setWorkspaceName(name);
|
||||||
|
|
||||||
const workspaceId = await services.api.userConfig.getWorkspaceId(
|
|
||||||
currentSpaceId
|
|
||||||
);
|
|
||||||
setWorkspaceId(workspaceId);
|
setWorkspaceId(workspaceId);
|
||||||
}, [currentSpaceId]);
|
}, [currentSpaceId]);
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ module.exports = function (webpackConfig) {
|
|||||||
const config = getNxWebpackConfig(webpackConfig);
|
const config = getNxWebpackConfig(webpackConfig);
|
||||||
|
|
||||||
const isProd = config.mode === 'production';
|
const isProd = config.mode === 'production';
|
||||||
|
const isE2E = process.env.NX_E2E;
|
||||||
|
|
||||||
const style9 = {
|
const style9 = {
|
||||||
test: /\.(tsx|ts|js|mjs|jsx)$/,
|
test: /\.(tsx|ts|js|mjs|jsx)$/,
|
||||||
@ -34,11 +35,6 @@ module.exports = function (webpackConfig) {
|
|||||||
|
|
||||||
if (isProd) {
|
if (isProd) {
|
||||||
config.module.rules.unshift(style9);
|
config.module.rules.unshift(style9);
|
||||||
} else {
|
|
||||||
config.module.rules.push(style9);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isProd) {
|
|
||||||
config.entry = {
|
config.entry = {
|
||||||
main: [...config.entry.main, ...config.entry.polyfills],
|
main: [...config.entry.main, ...config.entry.polyfills],
|
||||||
};
|
};
|
||||||
@ -133,6 +129,7 @@ module.exports = function (webpackConfig) {
|
|||||||
});
|
});
|
||||||
config.module.rules.splice(6);
|
config.module.rules.splice(6);
|
||||||
} else {
|
} else {
|
||||||
|
config.module.rules.push(style9);
|
||||||
config.output = {
|
config.output = {
|
||||||
...config.output,
|
...config.output,
|
||||||
publicPath: '/',
|
publicPath: '/',
|
||||||
@ -162,6 +159,7 @@ module.exports = function (webpackConfig) {
|
|||||||
global: {},
|
global: {},
|
||||||
}),
|
}),
|
||||||
isProd &&
|
isProd &&
|
||||||
|
!isE2E &&
|
||||||
new HtmlWebpackPlugin({
|
new HtmlWebpackPlugin({
|
||||||
title: 'AFFiNE - All In One Workos',
|
title: 'AFFiNE - All In One Workos',
|
||||||
favicon: path.resolve(
|
favicon: path.resolve(
|
||||||
|
@ -14,6 +14,9 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"mini-css-extract-plugin": "^2.6.1",
|
"mini-css-extract-plugin": "^2.6.1",
|
||||||
"webpack": "^5.73.0"
|
"image-minimizer-webpack-plugin": "^3.2.3",
|
||||||
|
"imagemin": "^8.0.1",
|
||||||
|
"imagemin-optipng": "^8.0.0",
|
||||||
|
"webpack": "^5.74.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Before Width: | Height: | Size: 1.5 MiB After Width: | Height: | Size: 1.5 MiB |
@ -6,13 +6,18 @@ import clsx from 'clsx';
|
|||||||
|
|
||||||
import { CssVarsProvider, styled } from '@mui/joy/styles';
|
import { CssVarsProvider, styled } from '@mui/joy/styles';
|
||||||
import { Box, Button, Container, Grid, SvgIcon, Typography } from '@mui/joy';
|
import { Box, Button, Container, Grid, SvgIcon, Typography } from '@mui/joy';
|
||||||
import Card from '@mui/joy/Card';
|
|
||||||
import GitHubIcon from '@mui/icons-material/GitHub';
|
import GitHubIcon from '@mui/icons-material/GitHub';
|
||||||
import RedditIcon from '@mui/icons-material/Reddit';
|
import RedditIcon from '@mui/icons-material/Reddit';
|
||||||
import TelegramIcon from '@mui/icons-material/Telegram';
|
import TelegramIcon from '@mui/icons-material/Telegram';
|
||||||
// eslint-disable-next-line no-restricted-imports
|
// eslint-disable-next-line no-restricted-imports
|
||||||
import { useMediaQuery } from '@mui/material';
|
import { useMediaQuery } from '@mui/material';
|
||||||
|
|
||||||
|
import LogoImage from './logo.png';
|
||||||
|
import CollaborationImage from './collaboration.png';
|
||||||
|
import PageImage from './page.png';
|
||||||
|
import ShapeImage from './shape.png';
|
||||||
|
import TaskImage from './task.png';
|
||||||
|
|
||||||
const DiscordIcon = (props: any) => {
|
const DiscordIcon = (props: any) => {
|
||||||
return (
|
return (
|
||||||
<SvgIcon
|
<SvgIcon
|
||||||
@ -407,10 +412,7 @@ export function App() {
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<AffineImage
|
<AffineImage src={PageImage} alt="AFFiNE main ui" />
|
||||||
src="/assets/page.png"
|
|
||||||
alt="AFFiNE main ui"
|
|
||||||
/>
|
|
||||||
</Box>
|
</Box>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid xs={12} sx={{ display: 'flex' }}>
|
<Grid xs={12} sx={{ display: 'flex' }}>
|
||||||
@ -524,7 +526,7 @@ export function App() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<AffineImage
|
<AffineImage
|
||||||
src="/assets/shape.png"
|
src={ShapeImage}
|
||||||
alt="AFFiNE Shape Your Page"
|
alt="AFFiNE Shape Your Page"
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
@ -606,7 +608,7 @@ export function App() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<AffineImage
|
<AffineImage
|
||||||
src="/assets/task.png"
|
src={TaskImage}
|
||||||
alt="AFFiNE Plan Your Task"
|
alt="AFFiNE Plan Your Task"
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
@ -667,7 +669,7 @@ export function App() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<AffineImage
|
<AffineImage
|
||||||
src="/assets/collaboration.png"
|
src={CollaborationImage}
|
||||||
alt="AFFiNE Privacy-first, and collaborative"
|
alt="AFFiNE Privacy-first, and collaborative"
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
@ -680,7 +682,7 @@ export function App() {
|
|||||||
margin: 'auto',
|
margin: 'auto',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<AffineImage src="/assets/logo.png" alt="AFFiNE Logo" />
|
<AffineImage src={LogoImage} alt="AFFiNE Logo" />
|
||||||
</Box>
|
</Box>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid xs={12} sx={{ display: 'flex' }}>
|
<Grid xs={12} sx={{ display: 'flex' }}>
|
||||||
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 1.0 MiB After Width: | Height: | Size: 1.0 MiB |
BIN
apps/venus/src/app/shape.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
Before Width: | Height: | Size: 1.0 MiB After Width: | Height: | Size: 1.0 MiB |
Before Width: | Height: | Size: 936 KiB |
Before Width: | Height: | Size: 15 KiB |
@ -6,7 +6,7 @@
|
|||||||
<base href="/" />
|
<base href="/" />
|
||||||
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<link rel="icon" type="image/x-icon" href="favicon.ico" />
|
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
@ -7,6 +7,7 @@ const HtmlWebpackPlugin = require('html-webpack-plugin');
|
|||||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||||
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
|
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
|
||||||
const CompressionPlugin = require('compression-webpack-plugin');
|
const CompressionPlugin = require('compression-webpack-plugin');
|
||||||
|
const ImageMinimizerPlugin = require('image-minimizer-webpack-plugin');
|
||||||
const TerserPlugin = require('terser-webpack-plugin');
|
const TerserPlugin = require('terser-webpack-plugin');
|
||||||
const Style9Plugin = require('style9/webpack');
|
const Style9Plugin = require('style9/webpack');
|
||||||
|
|
||||||
@ -34,11 +35,6 @@ module.exports = function (webpackConfig) {
|
|||||||
|
|
||||||
if (isProd) {
|
if (isProd) {
|
||||||
config.module.rules.unshift(style9);
|
config.module.rules.unshift(style9);
|
||||||
} else {
|
|
||||||
config.module.rules.push(style9);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isProd) {
|
|
||||||
config.entry = {
|
config.entry = {
|
||||||
main: [...config.entry.main, ...config.entry.polyfills],
|
main: [...config.entry.main, ...config.entry.polyfills],
|
||||||
};
|
};
|
||||||
@ -61,6 +57,14 @@ module.exports = function (webpackConfig) {
|
|||||||
parallel: true,
|
parallel: true,
|
||||||
}),
|
}),
|
||||||
new CssMinimizerPlugin(),
|
new CssMinimizerPlugin(),
|
||||||
|
new ImageMinimizerPlugin({
|
||||||
|
minimizer: {
|
||||||
|
implementation: ImageMinimizerPlugin.imageminMinify,
|
||||||
|
options: {
|
||||||
|
plugins: [['optipng', { optimizationLevel: 5 }]],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
splitChunks: {
|
splitChunks: {
|
||||||
chunks: 'all',
|
chunks: 'all',
|
||||||
@ -115,6 +119,7 @@ module.exports = function (webpackConfig) {
|
|||||||
});
|
});
|
||||||
config.module.rules.splice(6);
|
config.module.rules.splice(6);
|
||||||
} else {
|
} else {
|
||||||
|
config.module.rules.push(style9);
|
||||||
config.output = {
|
config.output = {
|
||||||
...config.output,
|
...config.output,
|
||||||
publicPath: '/',
|
publicPath: '/',
|
||||||
|
@ -3,10 +3,10 @@
|
|||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@authing/react-ui-components": "^3.1.23",
|
"@authing/react-ui-components": "^3.1.39",
|
||||||
"@emotion/styled": "^11.9.3"
|
"@emotion/styled": "^11.9.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"authing-js-sdk": "^4.23.33"
|
"authing-js-sdk": "^4.23.35"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,12 @@ interface AffineEditorProps {
|
|||||||
scrollBlank?: boolean;
|
scrollBlank?: boolean;
|
||||||
|
|
||||||
isWhiteboard?: boolean;
|
isWhiteboard?: boolean;
|
||||||
|
|
||||||
|
scrollContainer?: HTMLElement;
|
||||||
|
scrollController?: {
|
||||||
|
lockScroll: () => void;
|
||||||
|
unLockScroll: () => void;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function _useConstantWithDispose(
|
function _useConstantWithDispose(
|
||||||
@ -53,13 +59,32 @@ function _useConstantWithDispose(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const AffineEditor = forwardRef<BlockEditor, AffineEditorProps>(
|
export const AffineEditor = forwardRef<BlockEditor, AffineEditorProps>(
|
||||||
({ workspace, rootBlockId, scrollBlank = true, isWhiteboard }, ref) => {
|
(
|
||||||
|
{
|
||||||
|
workspace,
|
||||||
|
rootBlockId,
|
||||||
|
scrollBlank = true,
|
||||||
|
isWhiteboard,
|
||||||
|
scrollController,
|
||||||
|
scrollContainer,
|
||||||
|
},
|
||||||
|
ref
|
||||||
|
) => {
|
||||||
const editor = _useConstantWithDispose(
|
const editor = _useConstantWithDispose(
|
||||||
workspace,
|
workspace,
|
||||||
rootBlockId,
|
rootBlockId,
|
||||||
isWhiteboard
|
isWhiteboard
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (scrollContainer) {
|
||||||
|
editor.scrollManager.scrollContainer = scrollContainer;
|
||||||
|
}
|
||||||
|
if (scrollController) {
|
||||||
|
editor.scrollManager.scrollController = scrollController;
|
||||||
|
}
|
||||||
|
}, [editor, scrollContainer, scrollController]);
|
||||||
|
|
||||||
useImperativeHandle(ref, () => editor);
|
useImperativeHandle(ref, () => editor);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -29,6 +29,7 @@ import { ErrorBoundary } from 'react-error-boundary';
|
|||||||
import { ErrorFallback } from './components/error-fallback';
|
import { ErrorFallback } from './components/error-fallback';
|
||||||
import { ZoomBar } from './components/zoom-bar';
|
import { ZoomBar } from './components/zoom-bar';
|
||||||
import { CommandPanel } from './components/command-panel';
|
import { CommandPanel } from './components/command-panel';
|
||||||
|
import { usePageClientWidth } from '@toeverything/datasource/state';
|
||||||
|
|
||||||
export interface TldrawProps extends TldrawAppCtorProps {
|
export interface TldrawProps extends TldrawAppCtorProps {
|
||||||
/**
|
/**
|
||||||
@ -132,6 +133,9 @@ export function Tldraw({
|
|||||||
tools,
|
tools,
|
||||||
}: TldrawProps) {
|
}: TldrawProps) {
|
||||||
const [sId, set_sid] = React.useState(id);
|
const [sId, set_sid] = React.useState(id);
|
||||||
|
const { pageClientWidth } = usePageClientWidth();
|
||||||
|
// page padding left and right total 300px
|
||||||
|
const editorShapeInitSize = pageClientWidth - 300;
|
||||||
|
|
||||||
// Create a new app when the component mounts.
|
// Create a new app when the component mounts.
|
||||||
const [app, setApp] = React.useState(() => {
|
const [app, setApp] = React.useState(() => {
|
||||||
@ -140,6 +144,7 @@ export function Tldraw({
|
|||||||
callbacks,
|
callbacks,
|
||||||
commands,
|
commands,
|
||||||
getSession,
|
getSession,
|
||||||
|
editorShapeInitSize,
|
||||||
tools,
|
tools,
|
||||||
});
|
});
|
||||||
return app;
|
return app;
|
||||||
|
@ -21,9 +21,7 @@ export const ZoomBar: FC = () => {
|
|||||||
const zoom = app.useStore(zoomSelector);
|
const zoom = app.useStore(zoomSelector);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<ZoomBarContainer>
|
||||||
style={{ position: 'absolute', right: 10, bottom: 10, zIndex: 200 }}
|
|
||||||
>
|
|
||||||
<MiniMapContainer>
|
<MiniMapContainer>
|
||||||
<MiniMap />
|
<MiniMap />
|
||||||
</MiniMapContainer>
|
</MiniMapContainer>
|
||||||
@ -52,10 +50,18 @@ export const ZoomBar: FC = () => {
|
|||||||
<UnfoldMoreIcon style={{ transform: 'rotateZ(90deg)' }} />
|
<UnfoldMoreIcon style={{ transform: 'rotateZ(90deg)' }} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</ZoomBarContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const ZoomBarContainer = styled('div')({
|
||||||
|
position: 'absolute',
|
||||||
|
right: 10,
|
||||||
|
bottom: 10,
|
||||||
|
zIndex: 200,
|
||||||
|
userSelect: 'none',
|
||||||
|
});
|
||||||
|
|
||||||
const MiniMapContainer = styled('div')({
|
const MiniMapContainer = styled('div')({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'flex-end',
|
justifyContent: 'flex-end',
|
||||||
|
@ -6,6 +6,7 @@ import {
|
|||||||
TldrawPatch,
|
TldrawPatch,
|
||||||
TDShape,
|
TDShape,
|
||||||
TDStatus,
|
TDStatus,
|
||||||
|
TDShapeType,
|
||||||
} from '@toeverything/components/board-types';
|
} from '@toeverything/components/board-types';
|
||||||
import { TLDR } from '@toeverything/components/board-state';
|
import { TLDR } from '@toeverything/components/board-state';
|
||||||
import { BaseSession } from './base-session';
|
import { BaseSession } from './base-session';
|
||||||
@ -75,6 +76,10 @@ export class RotateSession extends BaseSession {
|
|||||||
app: { currentPageId, currentPoint, shiftKey },
|
app: { currentPageId, currentPoint, shiftKey },
|
||||||
} = this;
|
} = this;
|
||||||
|
|
||||||
|
const filteredShapes = initialShapes.filter(
|
||||||
|
shape => shape.shape.type !== TDShapeType.Editor
|
||||||
|
);
|
||||||
|
|
||||||
const shapes: Record<string, Partial<TDShape>> = {};
|
const shapes: Record<string, Partial<TDShape>> = {};
|
||||||
|
|
||||||
let directionDelta =
|
let directionDelta =
|
||||||
@ -85,7 +90,7 @@ export class RotateSession extends BaseSession {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update the shapes
|
// Update the shapes
|
||||||
initialShapes.forEach(({ center, shape }) => {
|
filteredShapes.forEach(({ center, shape }) => {
|
||||||
const { rotation = 0 } = shape;
|
const { rotation = 0 } = shape;
|
||||||
let shapeDelta = 0;
|
let shapeDelta = 0;
|
||||||
|
|
||||||
|
@ -133,6 +133,7 @@ export class EditorUtil extends TDShapeUtil<T, E> {
|
|||||||
<HTMLContainer ref={ref} {...events}>
|
<HTMLContainer ref={ref} {...events}>
|
||||||
<Container
|
<Container
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
|
editing={isEditing}
|
||||||
onPointerDown={stopPropagation}
|
onPointerDown={stopPropagation}
|
||||||
onMouseEnter={activateIfEditing}
|
onMouseEnter={activateIfEditing}
|
||||||
onDragEnter={activateIfEditing}
|
onDragEnter={activateIfEditing}
|
||||||
@ -248,15 +249,15 @@ export class EditorUtil extends TDShapeUtil<T, E> {
|
|||||||
const PADDING = 16;
|
const PADDING = 16;
|
||||||
// const MIN_CONTAINER_HEIGHT = 200;
|
// const MIN_CONTAINER_HEIGHT = 200;
|
||||||
|
|
||||||
const Container = styled('div')({
|
const Container = styled('div')<{ editing: boolean }>(({ editing }) => ({
|
||||||
pointerEvents: 'all',
|
pointerEvents: 'all',
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
});
|
userSelect: editing ? 'unset' : 'none',
|
||||||
|
}));
|
||||||
|
|
||||||
const Mask = styled('div')({
|
const Mask = styled('div')({
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
userSelect: 'none',
|
|
||||||
top: 0,
|
top: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
|
@ -73,6 +73,7 @@ import { StateManager } from './manager/state-manager';
|
|||||||
import { getClipboard, setClipboard } from './idb-clipboard';
|
import { getClipboard, setClipboard } from './idb-clipboard';
|
||||||
import type { Commands } from './types/commands';
|
import type { Commands } from './types/commands';
|
||||||
import type { BaseTool } from './types/tool';
|
import type { BaseTool } from './types/tool';
|
||||||
|
import { MIN_PAGE_WIDTH } from '@toeverything/components/editor-core';
|
||||||
|
|
||||||
const uuid = Utils.uniqueId();
|
const uuid = Utils.uniqueId();
|
||||||
|
|
||||||
@ -178,6 +179,7 @@ export interface TldrawAppCtorProps {
|
|||||||
getSession: (type: SessionType) => {
|
getSession: (type: SessionType) => {
|
||||||
new (app: TldrawApp, ...args: any[]): BaseSessionType;
|
new (app: TldrawApp, ...args: any[]): BaseSessionType;
|
||||||
};
|
};
|
||||||
|
editorShapeInitSize?: number;
|
||||||
commands: Commands;
|
commands: Commands;
|
||||||
tools: Record<string, { new (app: TldrawApp): BaseTool }>;
|
tools: Record<string, { new (app: TldrawApp): BaseTool }>;
|
||||||
}
|
}
|
||||||
@ -223,6 +225,8 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
|||||||
|
|
||||||
fileSystemHandle: FileSystemHandle | null = null;
|
fileSystemHandle: FileSystemHandle | null = null;
|
||||||
|
|
||||||
|
editorShapeInitSize = MIN_PAGE_WIDTH;
|
||||||
|
|
||||||
viewport = Utils.getBoundsFromPoints([
|
viewport = Utils.getBoundsFromPoints([
|
||||||
[0, 0],
|
[0, 0],
|
||||||
[100, 100],
|
[100, 100],
|
||||||
@ -285,6 +289,10 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
|||||||
return acc;
|
return acc;
|
||||||
}, {} as Record<string, BaseTool>);
|
}, {} as Record<string, BaseTool>);
|
||||||
this.currentTool = this.tools['select'];
|
this.currentTool = this.tools['select'];
|
||||||
|
|
||||||
|
if (props.editorShapeInitSize) {
|
||||||
|
this.editorShapeInitSize = props.editorShapeInitSize;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------- Internal -------------------- */
|
/* -------------------- Internal -------------------- */
|
||||||
|
@ -18,6 +18,7 @@ export class EditorTool extends BaseTool {
|
|||||||
const {
|
const {
|
||||||
currentPoint,
|
currentPoint,
|
||||||
currentGrid,
|
currentGrid,
|
||||||
|
editorShapeInitSize,
|
||||||
settings: { showGrid },
|
settings: { showGrid },
|
||||||
appState: { currentPageId, currentStyle },
|
appState: { currentPageId, currentStyle },
|
||||||
document: { id: workspace },
|
document: { id: workspace },
|
||||||
@ -47,6 +48,7 @@ export class EditorTool extends BaseTool {
|
|||||||
? Vec.snap(currentPoint, currentGrid)
|
? Vec.snap(currentPoint, currentGrid)
|
||||||
: currentPoint,
|
: currentPoint,
|
||||||
style: { ...currentStyle },
|
style: { ...currentStyle },
|
||||||
|
size: [editorShapeInitSize, 200],
|
||||||
workspace,
|
workspace,
|
||||||
});
|
});
|
||||||
// In order to make the cursor just positioned at the beginning of the first line, it needs to be adjusted according to the padding newShape.point = Vec.sub(newShape.point, [50, 30]);
|
// In order to make the cursor just positioned at the beginning of the first line, it needs to be adjusted according to the padding newShape.point = Vec.sub(newShape.point, [50, 30]);
|
||||||
|
@ -1,16 +1,21 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import clsx from 'clsx';
|
|
||||||
import style9 from 'style9';
|
|
||||||
import {
|
import {
|
||||||
MuiButton as Button,
|
MuiButton as Button,
|
||||||
MuiCollapse as Collapse,
|
MuiCollapse as Collapse,
|
||||||
|
styled,
|
||||||
} from '@toeverything/components/ui';
|
} from '@toeverything/components/ui';
|
||||||
import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';
|
import {
|
||||||
import ArrowRightIcon from '@mui/icons-material/ArrowRight';
|
ArrowDropDownIcon,
|
||||||
|
ArrowRightIcon,
|
||||||
|
} from '@toeverything/components/icons';
|
||||||
|
|
||||||
const styles = style9.create({
|
const StyledContainer = styled('div')({
|
||||||
ligoButton: {
|
display: 'flex',
|
||||||
textTransform: 'none',
|
alignItems: 'center',
|
||||||
|
cursor: 'pointer',
|
||||||
|
'&:hover': {
|
||||||
|
background: '#f5f7f8',
|
||||||
|
borderRadius: '5px',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -24,29 +29,32 @@ export type CollapsibleTitleProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function CollapsibleTitle(props: CollapsibleTitleProps) {
|
export function CollapsibleTitle(props: CollapsibleTitleProps) {
|
||||||
const { className, style, children, title, initialOpen = true } = props;
|
const { children, title, initialOpen = true } = props;
|
||||||
|
|
||||||
const [open, setOpen] = useState(initialOpen);
|
const [open, setOpen] = useState(initialOpen);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Button
|
<StyledContainer onClick={() => setOpen(prev => !prev)}>
|
||||||
startIcon={
|
{open ? (
|
||||||
open ? (
|
<ArrowDropDownIcon sx={{ color: '#566B7D' }} />
|
||||||
<ArrowDropDownIcon sx={{ color: '#566B7D' }} />
|
) : (
|
||||||
) : (
|
<ArrowRightIcon sx={{ color: '#566B7D' }} />
|
||||||
<ArrowRightIcon sx={{ color: '#566B7D' }} />
|
)}
|
||||||
)
|
<div
|
||||||
}
|
style={{
|
||||||
onClick={() => setOpen(prev => !prev)}
|
color: '#98ACBD',
|
||||||
sx={{ color: '#566B7D', textTransform: 'none' }}
|
textTransform: 'none',
|
||||||
className={clsx(styles('ligoButton'), className)}
|
fontSize: '12px',
|
||||||
style={style}
|
fontWeight: '600',
|
||||||
disableElevation
|
height: '32px',
|
||||||
disableRipple
|
display: 'flex',
|
||||||
>
|
alignItems: 'center',
|
||||||
{title}
|
}}
|
||||||
</Button>
|
>
|
||||||
|
{title}
|
||||||
|
</div>
|
||||||
|
</StyledContainer>
|
||||||
{children ? (
|
{children ? (
|
||||||
<Collapse in={open} timeout="auto" unmountOnExit>
|
<Collapse in={open} timeout="auto" unmountOnExit>
|
||||||
{children}
|
{children}
|
||||||
|
@ -121,11 +121,11 @@ const isLinkActive = (editor: ReactEditor) => {
|
|||||||
|
|
||||||
const LinkStyledTooltip = styled(({ className, ...props }: MuiTooltipProps) => (
|
const LinkStyledTooltip = styled(({ className, ...props }: MuiTooltipProps) => (
|
||||||
<Tooltip {...props} classes={{ popper: className }} />
|
<Tooltip {...props} classes={{ popper: className }} />
|
||||||
))(() => ({
|
))(({ theme }) => ({
|
||||||
[`& .${muiTooltipClasses.tooltip}`]: {
|
[`& .${muiTooltipClasses.tooltip}`]: {
|
||||||
backgroundColor: '#fff',
|
backgroundColor: '#fff',
|
||||||
color: '#4C6275',
|
color: '#4C6275',
|
||||||
boxShadow: '0px 1px 10px rgba(152, 172, 189, 0.6)',
|
boxShadow: theme.affine.shadows.shadow1,
|
||||||
fontSize: '14px',
|
fontSize: '14px',
|
||||||
},
|
},
|
||||||
[`& .MuiTooltip-tooltipPlacementBottom`]: {
|
[`& .MuiTooltip-tooltipPlacementBottom`]: {
|
||||||
@ -412,8 +412,7 @@ export const LinkModal = memo((props: LinkModalProps) => {
|
|||||||
visible && (
|
visible && (
|
||||||
<>
|
<>
|
||||||
<LinkBehavior onMousedown={handle_mouse_down} rects={rects} />
|
<LinkBehavior onMousedown={handle_mouse_down} rects={rects} />
|
||||||
<div
|
<LinkModalContainer
|
||||||
className={styles('linkModalContainer')}
|
|
||||||
style={{
|
style={{
|
||||||
top: top + height + GAP_BETWEEN_CONTENT_AND_MODAL,
|
top: top + height + GAP_BETWEEN_CONTENT_AND_MODAL,
|
||||||
left,
|
left,
|
||||||
@ -431,7 +430,7 @@ export const LinkModal = memo((props: LinkModalProps) => {
|
|||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
ref={inputEl}
|
ref={inputEl}
|
||||||
/>
|
/>
|
||||||
</div>
|
</LinkModalContainer>
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
body
|
body
|
||||||
@ -491,19 +490,20 @@ const LinkBehavior = (props: {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const LinkModalContainer = styled('div')(({ theme }) => ({
|
||||||
|
position: 'fixed',
|
||||||
|
width: '354px',
|
||||||
|
height: '40px',
|
||||||
|
padding: '12px',
|
||||||
|
display: 'flex',
|
||||||
|
borderRadius: '4px',
|
||||||
|
boxShadow: theme.affine.shadows.shadow1,
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
alignItems: 'center',
|
||||||
|
zIndex: '1',
|
||||||
|
}));
|
||||||
|
|
||||||
const styles = style9.create({
|
const styles = style9.create({
|
||||||
linkModalContainer: {
|
|
||||||
position: 'fixed',
|
|
||||||
width: '354px',
|
|
||||||
height: '40px',
|
|
||||||
padding: '12px',
|
|
||||||
display: 'flex',
|
|
||||||
borderRadius: '4px',
|
|
||||||
boxShadow: '0px 1px 10px rgba(152, 172, 189, 0.6)',
|
|
||||||
backgroundColor: '#fff',
|
|
||||||
alignItems: 'center',
|
|
||||||
zIndex: '1',
|
|
||||||
},
|
|
||||||
linkModalContainerIcon: {
|
linkModalContainerIcon: {
|
||||||
width: '16px',
|
width: '16px',
|
||||||
margin: '0 16px 0 4px',
|
margin: '0 16px 0 4px',
|
||||||
|
@ -8,21 +8,21 @@
|
|||||||
"@codemirror/lang-css": "^6.0.0",
|
"@codemirror/lang-css": "^6.0.0",
|
||||||
"@codemirror/lang-html": "~6.1.0",
|
"@codemirror/lang-html": "~6.1.0",
|
||||||
"@codemirror/lang-java": "~6.0.0",
|
"@codemirror/lang-java": "~6.0.0",
|
||||||
"@codemirror/lang-javascript": "~6.0.1",
|
"@codemirror/lang-javascript": "~6.0.2",
|
||||||
"@codemirror/lang-json": "~6.0.0",
|
"@codemirror/lang-json": "~6.0.0",
|
||||||
"@codemirror/lang-lezer": "~6.0.0",
|
"@codemirror/lang-lezer": "~6.0.0",
|
||||||
"@codemirror/lang-markdown": "~6.0.0",
|
"@codemirror/lang-markdown": "~6.0.1",
|
||||||
"@codemirror/lang-php": "~6.0.0",
|
"@codemirror/lang-php": "~6.0.0",
|
||||||
"@codemirror/lang-python": "~6.0.0",
|
"@codemirror/lang-python": "~6.0.1",
|
||||||
"@codemirror/lang-rust": "~6.0.0",
|
"@codemirror/lang-rust": "~6.0.0",
|
||||||
"@codemirror/lang-sql": "~6.0.0",
|
"@codemirror/lang-sql": "~6.1.0",
|
||||||
"@codemirror/lang-xml": "~6.0.0",
|
"@codemirror/lang-xml": "~6.0.0",
|
||||||
"@codemirror/language": "^6.2.0",
|
"@codemirror/language": "^6.2.1",
|
||||||
"@codemirror/legacy-modes": "~6.1.0",
|
"@codemirror/legacy-modes": "~6.1.0",
|
||||||
"@codemirror/next": "^0.16.0",
|
"@codemirror/next": "^0.16.0",
|
||||||
"@codemirror/state": "^6.1.0",
|
"@codemirror/state": "^6.1.1",
|
||||||
"@codemirror/theme-one-dark": "^6.0.0",
|
"@codemirror/theme-one-dark": "^6.0.0",
|
||||||
"@codemirror/view": "^6.0.2",
|
"@codemirror/view": "^6.2.0",
|
||||||
"@dnd-kit/core": "^6.0.5",
|
"@dnd-kit/core": "^6.0.5",
|
||||||
"@dnd-kit/sortable": "^7.0.1",
|
"@dnd-kit/sortable": "^7.0.1",
|
||||||
"@dnd-kit/utilities": "^3.2.0",
|
"@dnd-kit/utilities": "^3.2.0",
|
||||||
@ -31,6 +31,7 @@
|
|||||||
"@mui/system": "^5.8.6",
|
"@mui/system": "^5.8.6",
|
||||||
"code-example": "^3.3.6",
|
"code-example": "^3.3.6",
|
||||||
"codemirror": "6.0.1",
|
"codemirror": "6.0.1",
|
||||||
|
"codemirror-lang-elixir": "^3.0.0",
|
||||||
"keymap": "link:@codemirror/next/keymap",
|
"keymap": "link:@codemirror/next/keymap",
|
||||||
"nanoid": "^4.0.0",
|
"nanoid": "^4.0.0",
|
||||||
"react-resizable": "^3.0.4",
|
"react-resizable": "^3.0.4",
|
||||||
|
@ -17,7 +17,7 @@ import {
|
|||||||
supportChildren,
|
supportChildren,
|
||||||
RenderBlockChildren,
|
RenderBlockChildren,
|
||||||
useOnSelect,
|
useOnSelect,
|
||||||
WrapperWithPendantAndDragDrop,
|
BlockPendantProvider,
|
||||||
} from '@toeverything/components/editor-core';
|
} from '@toeverything/components/editor-core';
|
||||||
import { List } from '../../components/style-container';
|
import { List } from '../../components/style-container';
|
||||||
import { getChildrenType, BulletIcon, NumberType } from './data';
|
import { getChildrenType, BulletIcon, NumberType } from './data';
|
||||||
@ -188,7 +188,7 @@ export const BulletView: FC<CreateView> = ({ block, editor }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<BlockContainer editor={editor} block={block} selected={isSelect}>
|
<BlockContainer editor={editor} block={block} selected={isSelect}>
|
||||||
<WrapperWithPendantAndDragDrop editor={editor} block={block}>
|
<BlockPendantProvider block={block}>
|
||||||
<List>
|
<List>
|
||||||
<BulletLeft>
|
<BulletLeft>
|
||||||
<BulletIcon numberType={properties.numberType} />
|
<BulletIcon numberType={properties.numberType} />
|
||||||
@ -206,7 +206,7 @@ export const BulletView: FC<CreateView> = ({ block, editor }) => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</List>
|
</List>
|
||||||
</WrapperWithPendantAndDragDrop>
|
</BlockPendantProvider>
|
||||||
<IndentWrapper>
|
<IndentWrapper>
|
||||||
<RenderBlockChildren block={block} />
|
<RenderBlockChildren block={block} />
|
||||||
</IndentWrapper>
|
</IndentWrapper>
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
import { FC, useState, useMemo, useRef, useEffect } from 'react';
|
import { FC, useState, useRef, useEffect } from 'react';
|
||||||
import { StyleWithAtRules } from 'style9';
|
import { StyleWithAtRules } from 'style9';
|
||||||
|
|
||||||
import { CreateView } from '@toeverything/framework/virgo';
|
import { CreateView } from '@toeverything/framework/virgo';
|
||||||
import CodeMirror from './CodeMirror';
|
import CodeMirror, { ReactCodeMirrorRef } from './CodeMirror';
|
||||||
import { styled } from '@toeverything/components/ui';
|
import { styled } from '@toeverything/components/ui';
|
||||||
import DeleteSweepOutlinedIcon from '@mui/icons-material/DeleteSweepOutlined';
|
|
||||||
|
|
||||||
import { javascript } from '@codemirror/lang-javascript';
|
import { javascript } from '@codemirror/lang-javascript';
|
||||||
import { html } from '@codemirror/lang-html';
|
import { html } from '@codemirror/lang-html';
|
||||||
@ -32,6 +31,7 @@ import { powerShell } from '@codemirror/legacy-modes/mode/powershell';
|
|||||||
import { brainfuck } from '@codemirror/legacy-modes/mode/brainfuck';
|
import { brainfuck } from '@codemirror/legacy-modes/mode/brainfuck';
|
||||||
import { stylus } from '@codemirror/legacy-modes/mode/stylus';
|
import { stylus } from '@codemirror/legacy-modes/mode/stylus';
|
||||||
import { erlang } from '@codemirror/legacy-modes/mode/erlang';
|
import { erlang } from '@codemirror/legacy-modes/mode/erlang';
|
||||||
|
import { elixir } from 'codemirror-lang-elixir';
|
||||||
import { nginx } from '@codemirror/legacy-modes/mode/nginx';
|
import { nginx } from '@codemirror/legacy-modes/mode/nginx';
|
||||||
import { perl } from '@codemirror/legacy-modes/mode/perl';
|
import { perl } from '@codemirror/legacy-modes/mode/perl';
|
||||||
import { pascal } from '@codemirror/legacy-modes/mode/pascal';
|
import { pascal } from '@codemirror/legacy-modes/mode/pascal';
|
||||||
@ -45,12 +45,11 @@ import { dockerFile } from '@codemirror/legacy-modes/mode/dockerfile';
|
|||||||
import { julia } from '@codemirror/legacy-modes/mode/julia';
|
import { julia } from '@codemirror/legacy-modes/mode/julia';
|
||||||
import { r } from '@codemirror/legacy-modes/mode/r';
|
import { r } from '@codemirror/legacy-modes/mode/r';
|
||||||
import { Extension } from '@codemirror/state';
|
import { Extension } from '@codemirror/state';
|
||||||
// import { Select } from '../../components/select';
|
|
||||||
import { Option, Select } from '@toeverything/components/ui';
|
import { Option, Select } from '@toeverything/components/ui';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
useOnSelect,
|
useOnSelect,
|
||||||
WrapperWithPendantAndDragDrop,
|
BlockPendantProvider,
|
||||||
} from '@toeverything/components/editor-core';
|
} from '@toeverything/components/editor-core';
|
||||||
import { copyToClipboard } from '@toeverything/utils';
|
import { copyToClipboard } from '@toeverything/utils';
|
||||||
interface CreateCodeView extends CreateView {
|
interface CreateCodeView extends CreateView {
|
||||||
@ -87,6 +86,7 @@ const langs: Record<string, any> = {
|
|||||||
brainfuck: () => StreamLanguage.define(brainfuck),
|
brainfuck: () => StreamLanguage.define(brainfuck),
|
||||||
stylus: () => StreamLanguage.define(stylus),
|
stylus: () => StreamLanguage.define(stylus),
|
||||||
erlang: () => StreamLanguage.define(erlang),
|
erlang: () => StreamLanguage.define(erlang),
|
||||||
|
elixir: () => StreamLanguage.define(elixir),
|
||||||
nginx: () => StreamLanguage.define(nginx),
|
nginx: () => StreamLanguage.define(nginx),
|
||||||
perl: () => StreamLanguage.define(perl),
|
perl: () => StreamLanguage.define(perl),
|
||||||
ruby: () => StreamLanguage.define(ruby),
|
ruby: () => StreamLanguage.define(ruby),
|
||||||
@ -100,10 +100,8 @@ const langs: Record<string, any> = {
|
|||||||
julia: () => StreamLanguage.define(julia),
|
julia: () => StreamLanguage.define(julia),
|
||||||
dockerfile: () => StreamLanguage.define(dockerFile),
|
dockerfile: () => StreamLanguage.define(dockerFile),
|
||||||
r: () => StreamLanguage.define(r),
|
r: () => StreamLanguage.define(r),
|
||||||
// clike: () => StreamLanguage.define(clike),
|
|
||||||
// clike: () => clike({ }),
|
|
||||||
};
|
};
|
||||||
|
const DEFAULT_LANG = 'javascript';
|
||||||
const CodeBlock = styled('div')(({ theme }) => ({
|
const CodeBlock = styled('div')(({ theme }) => ({
|
||||||
backgroundColor: '#F2F5F9',
|
backgroundColor: '#F2F5F9',
|
||||||
padding: '8px 24px',
|
padding: '8px 24px',
|
||||||
@ -118,7 +116,7 @@ const CodeBlock = styled('div')(({ theme }) => ({
|
|||||||
flexWrap: 'wrap',
|
flexWrap: 'wrap',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
},
|
},
|
||||||
'.delete-block': {
|
'.copy-block': {
|
||||||
padding: '6px 10px',
|
padding: '6px 10px',
|
||||||
backgroundColor: '#fff',
|
backgroundColor: '#fff',
|
||||||
borderRadius: theme.affine.shape.borderRadius,
|
borderRadius: theme.affine.shape.borderRadius,
|
||||||
@ -130,29 +128,27 @@ const CodeBlock = styled('div')(({ theme }) => ({
|
|||||||
}));
|
}));
|
||||||
export const CodeView: FC<CreateCodeView> = ({ block, editor }) => {
|
export const CodeView: FC<CreateCodeView> = ({ block, editor }) => {
|
||||||
const initValue: string = block.getProperty('text')?.value?.[0]?.text;
|
const initValue: string = block.getProperty('text')?.value?.[0]?.text;
|
||||||
const langType: string = block.getProperty('lang')?.value?.[0]?.text;
|
const langType: string = block.getProperty('lang');
|
||||||
const [mode, setMode] = useState('javascript');
|
|
||||||
const [extensions, setExtensions] = useState<Extension[]>();
|
const [extensions, setExtensions] = useState<Extension[]>();
|
||||||
const codeMirror = useRef();
|
const codeMirror = useRef<ReactCodeMirrorRef>();
|
||||||
useOnSelect(block.id, (is_select: boolean) => {
|
useOnSelect(block.id, (_is_select: boolean) => {
|
||||||
if (codeMirror.current) {
|
if (codeMirror.current) {
|
||||||
//@ts-ignore
|
|
||||||
codeMirror?.current?.view?.focus();
|
codeMirror?.current?.view?.focus();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const onChange = (value: any, codeEditor: any) => {
|
const onChange = (value: string) => {
|
||||||
block.setProperty('text', {
|
block.setProperty('text', {
|
||||||
value: [{ text: value }],
|
value: [{ text: value }],
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
useEffect(() => {
|
const handleLangChange = (lang: string) => {
|
||||||
handleLangChange(langType ? langType : 'javascript');
|
|
||||||
}, []);
|
|
||||||
function handleLangChange(lang: string) {
|
|
||||||
block.setProperty('lang', lang);
|
block.setProperty('lang', lang);
|
||||||
setMode(lang);
|
|
||||||
setExtensions([langs[lang]()]);
|
setExtensions([langs[lang]()]);
|
||||||
}
|
};
|
||||||
|
useEffect(() => {
|
||||||
|
handleLangChange(langType ? langType : DEFAULT_LANG);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const copyCode = () => {
|
const copyCode = () => {
|
||||||
copyToClipboard(initValue);
|
copyToClipboard(initValue);
|
||||||
};
|
};
|
||||||
@ -163,7 +159,7 @@ export const CodeView: FC<CreateCodeView> = ({ block, editor }) => {
|
|||||||
editor.selectionManager.activePreviousNode(block.id, 'start');
|
editor.selectionManager.activePreviousNode(block.id, 'start');
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<WrapperWithPendantAndDragDrop editor={editor} block={block}>
|
<BlockPendantProvider block={block}>
|
||||||
<CodeBlock
|
<CodeBlock
|
||||||
onKeyDown={e => {
|
onKeyDown={e => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@ -171,19 +167,12 @@ export const CodeView: FC<CreateCodeView> = ({ block, editor }) => {
|
|||||||
>
|
>
|
||||||
<div className="operation">
|
<div className="operation">
|
||||||
<div className="select">
|
<div className="select">
|
||||||
{/* <Select
|
|
||||||
label="Lang"
|
|
||||||
options={Object.keys(langs)}
|
|
||||||
value={mode}
|
|
||||||
onChange={evn => handleLangChange(evn.target.value)}
|
|
||||||
/> */}
|
|
||||||
<Select
|
<Select
|
||||||
width={128}
|
width={128}
|
||||||
placeholder="Search for a field type"
|
placeholder="Search for a field type"
|
||||||
value={mode}
|
value={langType || DEFAULT_LANG}
|
||||||
listboxStyle={{ maxHeight: '400px' }}
|
listboxStyle={{ maxHeight: '400px' }}
|
||||||
onChange={(selectedValue: string) => {
|
onChange={(selectedValue: string) => {
|
||||||
// setSelectedOption(selectedValue);
|
|
||||||
handleLangChange(selectedValue);
|
handleLangChange(selectedValue);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -197,17 +186,8 @@ export const CodeView: FC<CreateCodeView> = ({ block, editor }) => {
|
|||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="delete-block" onClick={copyCode}>
|
<div className="copy-block" onClick={copyCode}>
|
||||||
Copy
|
Copy
|
||||||
{/* <DeleteSweepOutlinedIcon
|
|
||||||
className="delete-icon"
|
|
||||||
fontSize="small"
|
|
||||||
sx={{
|
|
||||||
color: 'rgba(0,0,0,.5)',
|
|
||||||
cursor: 'pointer',
|
|
||||||
'&:hover': { color: 'rgba(0,0,0,.9)' },
|
|
||||||
}}
|
|
||||||
/> */}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -222,6 +202,6 @@ export const CodeView: FC<CreateCodeView> = ({ block, editor }) => {
|
|||||||
handleKeyArrowUp={handleKeyArrowUp}
|
handleKeyArrowUp={handleKeyArrowUp}
|
||||||
/>
|
/>
|
||||||
</CodeBlock>
|
</CodeBlock>
|
||||||
</WrapperWithPendantAndDragDrop>
|
</BlockPendantProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { FC, useState } from 'react';
|
import { FC, useState } from 'react';
|
||||||
import { CreateView } from '@toeverything/framework/virgo';
|
import { CreateView } from '@toeverything/framework/virgo';
|
||||||
import {
|
import {
|
||||||
WrapperWithPendantAndDragDrop,
|
BlockPendantProvider,
|
||||||
useOnSelect,
|
useOnSelect,
|
||||||
} from '@toeverything/components/editor-core';
|
} from '@toeverything/components/editor-core';
|
||||||
import { Upload } from '../../components/upload/upload';
|
import { Upload } from '../../components/upload/upload';
|
||||||
@ -33,7 +33,7 @@ export const EmbedLinkView: FC<EmbedLinkView> = props => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WrapperWithPendantAndDragDrop editor={editor} block={block}>
|
<BlockPendantProvider block={block}>
|
||||||
<LinkContainer>
|
<LinkContainer>
|
||||||
{embedLinkUrl ? (
|
{embedLinkUrl ? (
|
||||||
<SourceView
|
<SourceView
|
||||||
@ -53,6 +53,6 @@ export const EmbedLinkView: FC<EmbedLinkView> = props => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</LinkContainer>
|
</LinkContainer>
|
||||||
</WrapperWithPendantAndDragDrop>
|
</BlockPendantProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -2,7 +2,7 @@ import { FC, useState } from 'react';
|
|||||||
import { CreateView } from '@toeverything/framework/virgo';
|
import { CreateView } from '@toeverything/framework/virgo';
|
||||||
import {
|
import {
|
||||||
useOnSelect,
|
useOnSelect,
|
||||||
WrapperWithPendantAndDragDrop,
|
BlockPendantProvider,
|
||||||
} from '@toeverything/components/editor-core';
|
} from '@toeverything/components/editor-core';
|
||||||
import { Upload } from '../../components/upload/upload';
|
import { Upload } from '../../components/upload/upload';
|
||||||
import { SourceView } from '../../components/source-view';
|
import { SourceView } from '../../components/source-view';
|
||||||
@ -30,7 +30,7 @@ export const FigmaView: FC<FigmaView> = ({ block, editor }) => {
|
|||||||
setIsSelect(isSelect);
|
setIsSelect(isSelect);
|
||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<WrapperWithPendantAndDragDrop editor={editor} block={block}>
|
<BlockPendantProvider block={block}>
|
||||||
<LinkContainer>
|
<LinkContainer>
|
||||||
{figmaUrl ? (
|
{figmaUrl ? (
|
||||||
<SourceView
|
<SourceView
|
||||||
@ -52,6 +52,6 @@ export const FigmaView: FC<FigmaView> = ({ block, editor }) => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</LinkContainer>
|
</LinkContainer>
|
||||||
</WrapperWithPendantAndDragDrop>
|
</BlockPendantProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -2,13 +2,13 @@ import { FC, useEffect, useLayoutEffect, useRef } from 'react';
|
|||||||
import { ChildrenView } from '@toeverything/framework/virgo';
|
import { ChildrenView } from '@toeverything/framework/virgo';
|
||||||
import { styled } from '@toeverything/components/ui';
|
import { styled } from '@toeverything/components/ui';
|
||||||
import { sleep } from '@toeverything/utils';
|
import { sleep } from '@toeverything/utils';
|
||||||
import { GRID_ITEM_MIN_WIDTH, GRID_PROPERTY_KEY, removePercent } from '../grid';
|
import { GRID_PROPERTY_KEY, removePercent } from '../grid';
|
||||||
|
|
||||||
export const GRID_ITEM_CLASS_NAME = 'grid-item';
|
export const GRID_ITEM_CLASS_NAME = 'grid-item';
|
||||||
export const GRID_ITEM_CONTENT_CLASS_NAME = `${GRID_ITEM_CLASS_NAME}-content`;
|
export const GRID_ITEM_CONTENT_CLASS_NAME = `${GRID_ITEM_CLASS_NAME}-content`;
|
||||||
|
|
||||||
export const GridItem: FC<ChildrenView> = function (props) {
|
export const GridItem: FC<ChildrenView> = function (props) {
|
||||||
const { children, block } = props;
|
const { children, block, editor } = props;
|
||||||
const RENDER_DELAY_TIME = 100;
|
const RENDER_DELAY_TIME = 100;
|
||||||
const ref = useRef<HTMLDivElement>();
|
const ref = useRef<HTMLDivElement>();
|
||||||
|
|
||||||
@ -25,6 +25,7 @@ export const GridItem: FC<ChildrenView> = function (props) {
|
|||||||
|
|
||||||
const checkAndRefreshWidth = async () => {
|
const checkAndRefreshWidth = async () => {
|
||||||
const currentWidth = block.getProperty(GRID_PROPERTY_KEY);
|
const currentWidth = block.getProperty(GRID_PROPERTY_KEY);
|
||||||
|
const gridItemMinWidth = editor.configManager.grid.gridItemMinWidth;
|
||||||
if (currentWidth) {
|
if (currentWidth) {
|
||||||
setWidth(currentWidth);
|
setWidth(currentWidth);
|
||||||
} else if (!block.dom?.style.width) {
|
} else if (!block.dom?.style.width) {
|
||||||
@ -64,26 +65,23 @@ export const GridItem: FC<ChildrenView> = function (props) {
|
|||||||
if new width less then min width,
|
if new width less then min width,
|
||||||
set min width and next block will be fix width
|
set min width and next block will be fix width
|
||||||
*/
|
*/
|
||||||
if (newWidth < GRID_ITEM_MIN_WIDTH) {
|
if (newWidth < gridItemMinWidth) {
|
||||||
needFixWidth += GRID_ITEM_MIN_WIDTH - newWidth;
|
needFixWidth += gridItemMinWidth - newWidth;
|
||||||
newWidth = GRID_ITEM_MIN_WIDTH;
|
newWidth = gridItemMinWidth;
|
||||||
}
|
}
|
||||||
// if can fix width, fix width
|
// if can fix width, fix width
|
||||||
if (
|
if (newWidth > gridItemMinWidth && needFixWidth) {
|
||||||
newWidth > GRID_ITEM_MIN_WIDTH &&
|
|
||||||
needFixWidth
|
|
||||||
) {
|
|
||||||
if (
|
if (
|
||||||
newWidth - needFixWidth >=
|
newWidth - needFixWidth >=
|
||||||
GRID_ITEM_MIN_WIDTH
|
gridItemMinWidth
|
||||||
) {
|
) {
|
||||||
newWidth = newWidth - needFixWidth;
|
newWidth = newWidth - needFixWidth;
|
||||||
needFixWidth = 0;
|
needFixWidth = 0;
|
||||||
} else {
|
} else {
|
||||||
needFixWidth =
|
needFixWidth =
|
||||||
needFixWidth -
|
needFixWidth -
|
||||||
(newWidth - GRID_ITEM_MIN_WIDTH);
|
(newWidth - gridItemMinWidth);
|
||||||
newWidth = GRID_ITEM_MIN_WIDTH;
|
newWidth = gridItemMinWidth;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (index === children.length - 2) {
|
if (index === children.length - 2) {
|
||||||
|
@ -12,10 +12,8 @@ import { debounce, domToRect, Point } from '@toeverything/utils';
|
|||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { Protocol } from '@toeverything/datasource/db-service';
|
import { Protocol } from '@toeverything/datasource/db-service';
|
||||||
|
|
||||||
const MAX_ITEM_COUNT = 6;
|
|
||||||
const DB_UPDATE_DELAY = 50;
|
const DB_UPDATE_DELAY = 50;
|
||||||
const GRID_ON_DRAG_CLASS = 'grid-layout-on-drag';
|
const GRID_ON_DRAG_CLASS = 'grid-layout-on-drag';
|
||||||
export const GRID_ITEM_MIN_WIDTH = 100 / MAX_ITEM_COUNT;
|
|
||||||
export const GRID_PROPERTY_KEY = 'gridItemWidth';
|
export const GRID_PROPERTY_KEY = 'gridItemWidth';
|
||||||
|
|
||||||
export function removePercent(str: string) {
|
export function removePercent(str: string) {
|
||||||
@ -24,13 +22,14 @@ export function removePercent(str: string) {
|
|||||||
|
|
||||||
export const Grid: FC<CreateView> = function (props) {
|
export const Grid: FC<CreateView> = function (props) {
|
||||||
const { block, editor } = props;
|
const { block, editor } = props;
|
||||||
|
const gridItemMinWidth = editor.configManager.grid.gridItemMinWidth;
|
||||||
const [isOnDrag, setIsOnDrag] = useState<boolean>(false);
|
const [isOnDrag, setIsOnDrag] = useState<boolean>(false);
|
||||||
const isSetMouseUp = useRef<boolean>(false);
|
const isSetMouseUp = useRef<boolean>(false);
|
||||||
const gridContainerRef = useRef<HTMLDivElement>();
|
const gridContainerRef = useRef<HTMLDivElement>();
|
||||||
const mouseStartPoint = useRef<Point>();
|
const mouseStartPoint = useRef<Point>();
|
||||||
const gridItemCountRef = useRef<number>();
|
const gridItemCountRef = useRef<number>();
|
||||||
const originalLeftWidth = useRef<number>(GRID_ITEM_MIN_WIDTH);
|
const originalLeftWidth = useRef<number>(gridItemMinWidth);
|
||||||
const originalRightWidth = useRef<number>(GRID_ITEM_MIN_WIDTH);
|
const originalRightWidth = useRef<number>(gridItemMinWidth);
|
||||||
const [alertHandleId, setAlertHandleId] = useState<string>(null);
|
const [alertHandleId, setAlertHandleId] = useState<string>(null);
|
||||||
|
|
||||||
const getLeftRightGridItemDomByIndex = (index: number) => {
|
const getLeftRightGridItemDomByIndex = (index: number) => {
|
||||||
@ -126,8 +125,8 @@ export const Grid: FC<CreateView> = function (props) {
|
|||||||
editor.mouseManager.onMouseupEventOnce(() => {
|
editor.mouseManager.onMouseupEventOnce(() => {
|
||||||
setIsOnDrag(false);
|
setIsOnDrag(false);
|
||||||
isSetMouseUp.current = false;
|
isSetMouseUp.current = false;
|
||||||
originalLeftWidth.current = GRID_ITEM_MIN_WIDTH;
|
originalLeftWidth.current = gridItemMinWidth;
|
||||||
originalRightWidth.current = GRID_ITEM_MIN_WIDTH;
|
originalRightWidth.current = gridItemMinWidth;
|
||||||
mouseStartPoint.current = null;
|
mouseStartPoint.current = null;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@ -153,12 +152,12 @@ export const Grid: FC<CreateView> = function (props) {
|
|||||||
const newLeftWidth = originalLeftWidth.current - xDistance;
|
const newLeftWidth = originalLeftWidth.current - xDistance;
|
||||||
let newLeftPercent = (newLeftWidth / containerWidth) * 100;
|
let newLeftPercent = (newLeftWidth / containerWidth) * 100;
|
||||||
let newRightPercent = Number(totalWidth) - newLeftPercent;
|
let newRightPercent = Number(totalWidth) - newLeftPercent;
|
||||||
if (newLeftPercent < GRID_ITEM_MIN_WIDTH) {
|
if (newLeftPercent < gridItemMinWidth) {
|
||||||
newLeftPercent = GRID_ITEM_MIN_WIDTH;
|
newLeftPercent = gridItemMinWidth;
|
||||||
newRightPercent = totalWidth - GRID_ITEM_MIN_WIDTH;
|
newRightPercent = totalWidth - gridItemMinWidth;
|
||||||
} else if (newRightPercent < GRID_ITEM_MIN_WIDTH) {
|
} else if (newRightPercent < gridItemMinWidth) {
|
||||||
newRightPercent = GRID_ITEM_MIN_WIDTH;
|
newRightPercent = gridItemMinWidth;
|
||||||
newLeftPercent = totalWidth - GRID_ITEM_MIN_WIDTH;
|
newLeftPercent = totalWidth - gridItemMinWidth;
|
||||||
}
|
}
|
||||||
//XXX first change dom style is for animation speed, maybe not a good idea
|
//XXX first change dom style is for animation speed, maybe not a good idea
|
||||||
const newLeft = `${newLeftPercent}%`;
|
const newLeft = `${newLeftPercent}%`;
|
||||||
@ -213,6 +212,7 @@ export const Grid: FC<CreateView> = function (props) {
|
|||||||
<GridContainer
|
<GridContainer
|
||||||
className={clsx({ [GRID_ON_DRAG_CLASS]: isOnDrag })}
|
className={clsx({ [GRID_ON_DRAG_CLASS]: isOnDrag })}
|
||||||
ref={gridContainerRef}
|
ref={gridContainerRef}
|
||||||
|
gridItemMinWidth={gridItemMinWidth}
|
||||||
isOnDrag={isOnDrag}
|
isOnDrag={isOnDrag}
|
||||||
>
|
>
|
||||||
{block.childrenIds.map((id, i) => {
|
{block.childrenIds.map((id, i) => {
|
||||||
@ -233,7 +233,8 @@ export const Grid: FC<CreateView> = function (props) {
|
|||||||
onMouseDown={event => handleMouseDown(event, i)}
|
onMouseDown={event => handleMouseDown(event, i)}
|
||||||
blockId={id}
|
blockId={id}
|
||||||
enabledAddItem={
|
enabledAddItem={
|
||||||
block.childrenIds.length < MAX_ITEM_COUNT
|
block.childrenIds.length <
|
||||||
|
editor.configManager.grid.maxGridItemCount
|
||||||
}
|
}
|
||||||
onMouseEnter={event =>
|
onMouseEnter={event =>
|
||||||
handleHandleMouseEnter(event, i)
|
handleHandleMouseEnter(event, i)
|
||||||
@ -252,24 +253,25 @@ export const Grid: FC<CreateView> = function (props) {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const GridContainer = styled('div')<{ isOnDrag: boolean }>(
|
const GridContainer = styled('div')<{
|
||||||
({ isOnDrag, theme }) => ({
|
isOnDrag: boolean;
|
||||||
position: 'relative',
|
gridItemMinWidth: number;
|
||||||
display: 'flex',
|
}>(({ isOnDrag, theme, gridItemMinWidth }) => ({
|
||||||
alignItems: 'stretch',
|
position: 'relative',
|
||||||
borderRadius: '10px',
|
display: 'flex',
|
||||||
border: '1px solid #FFF',
|
alignItems: 'stretch',
|
||||||
minWidth: `${GRID_ITEM_MIN_WIDTH}%`,
|
borderRadius: '10px',
|
||||||
[`&:hover .${GRID_ITEM_CONTENT_CLASS_NAME}`]: {
|
border: '1px solid #FFF',
|
||||||
|
minWidth: `${gridItemMinWidth}%`,
|
||||||
|
[`&:hover .${GRID_ITEM_CONTENT_CLASS_NAME}`]: {
|
||||||
|
borderColor: theme.affine.palette.borderColor,
|
||||||
|
},
|
||||||
|
...(isOnDrag && {
|
||||||
|
[`& .${GRID_ITEM_CONTENT_CLASS_NAME}`]: {
|
||||||
borderColor: theme.affine.palette.borderColor,
|
borderColor: theme.affine.palette.borderColor,
|
||||||
},
|
},
|
||||||
...(isOnDrag && {
|
}),
|
||||||
[`& .${GRID_ITEM_CONTENT_CLASS_NAME}`]: {
|
}));
|
||||||
borderColor: theme.affine.palette.borderColor,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
const GridMask = styled('div')({
|
const GridMask = styled('div')({
|
||||||
position: 'fixed',
|
position: 'fixed',
|
||||||
|
@ -3,7 +3,7 @@ import { Protocol } from '@toeverything/datasource/db-service';
|
|||||||
import { AsyncBlock, BaseView } from '@toeverything/framework/virgo';
|
import { AsyncBlock, BaseView } from '@toeverything/framework/virgo';
|
||||||
import { GridItem } from '../grid-item/GridItem';
|
import { GridItem } from '../grid-item/GridItem';
|
||||||
import { GridRender } from './GridRender';
|
import { GridRender } from './GridRender';
|
||||||
export { GRID_ITEM_MIN_WIDTH, GRID_PROPERTY_KEY, removePercent } from './Grid';
|
export { GRID_PROPERTY_KEY, removePercent } from './Grid';
|
||||||
|
|
||||||
export class GridBlock extends BaseView {
|
export class GridBlock extends BaseView {
|
||||||
public override selectable = false;
|
public override selectable = false;
|
||||||
|
@ -69,7 +69,7 @@ const GroupContainer = styled('div')<{ isSelect?: boolean }>(
|
|||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
boxShadow: '0px 1px 10px rgb(152 172 189 / 60%)',
|
boxShadow: theme.affine.shadows.shadow1,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
@ -2,11 +2,11 @@ import { styled } from '@toeverything/components/ui';
|
|||||||
import type { ComponentPropsWithRef, MouseEvent } from 'react';
|
import type { ComponentPropsWithRef, MouseEvent } from 'react';
|
||||||
import { forwardRef } from 'react';
|
import { forwardRef } from 'react';
|
||||||
|
|
||||||
const StyledPanel = styled('div')(() => ({
|
const StyledPanel = styled('div')(({ theme }) => ({
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: 50,
|
top: 50,
|
||||||
background: '#FFFFFF',
|
background: '#FFFFFF',
|
||||||
boxShadow: '0px 1px 10px rgba(152, 172, 189, 0.6)',
|
boxShadow: theme.affine.shadows.shadow1,
|
||||||
borderRadius: 10,
|
borderRadius: 10,
|
||||||
padding: '12px 24px',
|
padding: '12px 24px',
|
||||||
}));
|
}));
|
||||||
|
@ -41,6 +41,7 @@ const getKanbanColor = (
|
|||||||
return DEFAULT_COLOR;
|
return DEFAULT_COLOR;
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
|
group.type === PropertyType.Status ||
|
||||||
group.type === PropertyType.Select ||
|
group.type === PropertyType.Select ||
|
||||||
group.type === PropertyType.MultiSelect ||
|
group.type === PropertyType.MultiSelect ||
|
||||||
group.type === DEFAULT_GROUP_ID
|
group.type === DEFAULT_GROUP_ID
|
||||||
|
@ -25,7 +25,7 @@ const AddCard = ({ group }: { group: KanbanGroup }) => {
|
|||||||
const { addCard } = useKanban();
|
const { addCard } = useKanban();
|
||||||
const handleClick = useCallback(async () => {
|
const handleClick = useCallback(async () => {
|
||||||
await addCard(group);
|
await addCard(group);
|
||||||
}, [addCard]);
|
}, [addCard, group]);
|
||||||
return <AddCardWrapper onClick={handleClick}>+</AddCardWrapper>;
|
return <AddCardWrapper onClick={handleClick}>+</AddCardWrapper>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
import type { KanbanCard } from '@toeverything/components/editor-core';
|
import type { KanbanCard } from '@toeverything/components/editor-core';
|
||||||
import { RenderBlock, useKanban } from '@toeverything/components/editor-core';
|
import {
|
||||||
|
RenderBlock,
|
||||||
|
useKanban,
|
||||||
|
useRefPage,
|
||||||
|
} from '@toeverything/components/editor-core';
|
||||||
import { styled } from '@toeverything/components/ui';
|
import { styled } from '@toeverything/components/ui';
|
||||||
|
import { useFlag } from '@toeverything/datasource/feature-flags';
|
||||||
|
|
||||||
const CardContent = styled('div')({
|
const CardContent = styled('div')({
|
||||||
margin: '20px',
|
margin: '20px',
|
||||||
@ -58,18 +63,24 @@ export const CardItem = ({
|
|||||||
block: KanbanCard['block'];
|
block: KanbanCard['block'];
|
||||||
}) => {
|
}) => {
|
||||||
const { addSubItem } = useKanban();
|
const { addSubItem } = useKanban();
|
||||||
|
const { openSubPage } = useRefPage();
|
||||||
|
const showKanbanRefPageFlag = useFlag('ShowKanbanRefPage', false);
|
||||||
const onAddItem = async () => {
|
const onAddItem = async () => {
|
||||||
await addSubItem(block);
|
await addSubItem(block);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onClickCard = async () => {
|
||||||
|
showKanbanRefPageFlag && openSubPage(id);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CardContainer>
|
<CardContainer onClick={onClickCard}>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<RenderBlock blockId={id} />
|
<RenderBlock blockId={id} />
|
||||||
</CardContent>
|
</CardContent>
|
||||||
<CardActions onClick={onAddItem}>
|
<CardActions onClick={onAddItem}>
|
||||||
<PlusIcon />
|
<PlusIcon />
|
||||||
<span>Add item</span>
|
<span>Add a sub-block</span>
|
||||||
</CardActions>
|
</CardActions>
|
||||||
</CardContainer>
|
</CardContainer>
|
||||||
);
|
);
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
useCurrentView,
|
useCurrentView,
|
||||||
useOnSelect,
|
useOnSelect,
|
||||||
WrapperWithPendantAndDragDrop,
|
BlockPendantProvider,
|
||||||
} from '@toeverything/components/editor-core';
|
} from '@toeverything/components/editor-core';
|
||||||
import { styled } from '@toeverything/components/ui';
|
import { styled } from '@toeverything/components/ui';
|
||||||
import { services } from '@toeverything/datasource/db-service';
|
import { services } from '@toeverything/datasource/db-service';
|
||||||
@ -143,13 +143,13 @@ export const ImageView: FC<ImageView> = ({ block, editor }) => {
|
|||||||
type: 'link',
|
type: 'link',
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
const handle_click = (e: React.MouseEvent<HTMLDivElement>) => {
|
const handle_click = async (e: React.MouseEvent<HTMLDivElement>) => {
|
||||||
//TODO clear active selection
|
//TODO clear active selection
|
||||||
// document.getElementsByTagName('body')[0].click();
|
// document.getElementsByTagName('body')[0].click();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.nativeEvent.stopPropagation();
|
e.nativeEvent.stopPropagation();
|
||||||
editor.selectionManager.setSelectedNodesIds([block.id]);
|
await editor.selectionManager.setSelectedNodesIds([block.id]);
|
||||||
editor.selectionManager.activeNodeByNodeId(block.id);
|
await editor.selectionManager.activeNodeByNodeId(block.id, 'end');
|
||||||
};
|
};
|
||||||
const down_file = () => {
|
const down_file = () => {
|
||||||
if (down_ref) {
|
if (down_ref) {
|
||||||
@ -158,7 +158,7 @@ export const ImageView: FC<ImageView> = ({ block, editor }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WrapperWithPendantAndDragDrop editor={editor} block={block}>
|
<BlockPendantProvider block={block}>
|
||||||
<ImageBlock>
|
<ImageBlock>
|
||||||
<div ref={resize_box}>
|
<div ref={resize_box}>
|
||||||
{imgUrl ? (
|
{imgUrl ? (
|
||||||
@ -229,6 +229,6 @@ export const ImageView: FC<ImageView> = ({ block, editor }) => {
|
|||||||
</div> */}
|
</div> */}
|
||||||
</div>
|
</div>
|
||||||
</ImageBlock>
|
</ImageBlock>
|
||||||
</WrapperWithPendantAndDragDrop>
|
</BlockPendantProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -19,9 +19,8 @@ import {
|
|||||||
supportChildren,
|
supportChildren,
|
||||||
RenderBlockChildren,
|
RenderBlockChildren,
|
||||||
useOnSelect,
|
useOnSelect,
|
||||||
WrapperWithPendantAndDragDrop,
|
BlockPendantProvider,
|
||||||
} from '@toeverything/components/editor-core';
|
} from '@toeverything/components/editor-core';
|
||||||
import { styled } from '@toeverything/components/ui';
|
|
||||||
import { List } from '../../components/style-container';
|
import { List } from '../../components/style-container';
|
||||||
import { BlockContainer } from '../../components/BlockContainer';
|
import { BlockContainer } from '../../components/BlockContainer';
|
||||||
|
|
||||||
@ -185,7 +184,7 @@ export const NumberedView: FC<CreateView> = ({ block, editor }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<BlockContainer editor={editor} block={block} selected={isSelect}>
|
<BlockContainer editor={editor} block={block} selected={isSelect}>
|
||||||
<WrapperWithPendantAndDragDrop editor={editor} block={block}>
|
<BlockPendantProvider block={block}>
|
||||||
<List>
|
<List>
|
||||||
<div className={'checkBoxContainer'}>
|
<div className={'checkBoxContainer'}>
|
||||||
{getNumber(properties.numberType, number)}.
|
{getNumber(properties.numberType, number)}.
|
||||||
@ -203,7 +202,7 @@ export const NumberedView: FC<CreateView> = ({ block, editor }) => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</List>
|
</List>
|
||||||
</WrapperWithPendantAndDragDrop>
|
</BlockPendantProvider>
|
||||||
|
|
||||||
<IndentWrapper>
|
<IndentWrapper>
|
||||||
<RenderBlockChildren block={block} />
|
<RenderBlockChildren block={block} />
|
||||||
|
@ -8,7 +8,7 @@ import {
|
|||||||
supportChildren,
|
supportChildren,
|
||||||
unwrapGroup,
|
unwrapGroup,
|
||||||
useOnSelect,
|
useOnSelect,
|
||||||
WrapperWithPendantAndDragDrop,
|
BlockPendantProvider,
|
||||||
} from '@toeverything/components/editor-core';
|
} from '@toeverything/components/editor-core';
|
||||||
import { styled } from '@toeverything/components/ui';
|
import { styled } from '@toeverything/components/ui';
|
||||||
import { Protocol } from '@toeverything/datasource/db-service';
|
import { Protocol } from '@toeverything/datasource/db-service';
|
||||||
@ -99,7 +99,7 @@ export const TextView: FC<CreateTextView> = ({
|
|||||||
if (!parentBlock) {
|
if (!parentBlock) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
const preParent = await parentBlock.previousSibling();
|
||||||
if (Protocol.Block.Type.group === parentBlock.type) {
|
if (Protocol.Block.Type.group === parentBlock.type) {
|
||||||
const children = await block.children();
|
const children = await block.children();
|
||||||
const preNode = await block.physicallyPerviousSibling();
|
const preNode = await block.physicallyPerviousSibling();
|
||||||
@ -129,34 +129,19 @@ export const TextView: FC<CreateTextView> = ({
|
|||||||
'start'
|
'start'
|
||||||
);
|
);
|
||||||
if (block.blockProvider.isEmpty()) {
|
if (block.blockProvider.isEmpty()) {
|
||||||
block.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
// TODO remove timing problem
|
|
||||||
const prevGroupBlock = await parentBlock.previousSibling();
|
|
||||||
|
|
||||||
if (!prevGroupBlock) {
|
|
||||||
const childrenBlock = await parentBlock.children();
|
|
||||||
if (childrenBlock.length) {
|
|
||||||
if (children.length) {
|
|
||||||
await parentBlock.append(...children);
|
|
||||||
}
|
|
||||||
await block.remove();
|
await block.remove();
|
||||||
return true;
|
const parentChild = await parentBlock.children();
|
||||||
|
if (
|
||||||
|
parentBlock.type ===
|
||||||
|
Protocol.Block.Type.group &&
|
||||||
|
!parentChild.length
|
||||||
|
) {
|
||||||
|
await editor.selectionManager.setSelectedNodesIds(
|
||||||
|
[preParent?.id ?? editor.getRootBlockId()]
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
parentBlock.remove();
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
if (prevGroupBlock.type !== Protocol.Block.Type.group) {
|
|
||||||
unwrapGroup(parentBlock);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
mergeGroup(prevGroupBlock, parentBlock);
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -231,7 +216,7 @@ export const TextView: FC<CreateTextView> = ({
|
|||||||
selected={isSelect}
|
selected={isSelect}
|
||||||
className={containerClassName}
|
className={containerClassName}
|
||||||
>
|
>
|
||||||
<WrapperWithPendantAndDragDrop editor={editor} block={block}>
|
<BlockPendantProvider block={block}>
|
||||||
<TextBlock
|
<TextBlock
|
||||||
block={block}
|
block={block}
|
||||||
type={block.type}
|
type={block.type}
|
||||||
@ -242,7 +227,7 @@ export const TextView: FC<CreateTextView> = ({
|
|||||||
handleConvert={handleConvert}
|
handleConvert={handleConvert}
|
||||||
handleTab={onTab}
|
handleTab={onTab}
|
||||||
/>
|
/>
|
||||||
</WrapperWithPendantAndDragDrop>
|
</BlockPendantProvider>
|
||||||
<IndentWrapper>
|
<IndentWrapper>
|
||||||
<RenderBlockChildren block={block} />
|
<RenderBlockChildren block={block} />
|
||||||
</IndentWrapper>
|
</IndentWrapper>
|
||||||
|
@ -1,6 +1,18 @@
|
|||||||
import type { AsyncBlock } from '@toeverything/components/editor-core';
|
import {
|
||||||
|
AsyncBlock,
|
||||||
|
useCurrentView,
|
||||||
|
useLazyIframe,
|
||||||
|
} from '@toeverything/components/editor-core';
|
||||||
import { styled } from '@toeverything/components/ui';
|
import { styled } from '@toeverything/components/ui';
|
||||||
import type { FC } from 'react';
|
import {
|
||||||
|
FC,
|
||||||
|
ReactElement,
|
||||||
|
ReactNode,
|
||||||
|
useEffect,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
|
import { SCENE_CONFIG } from '../../blocks/group/config';
|
||||||
import { BlockPreview } from './BlockView';
|
import { BlockPreview } from './BlockView';
|
||||||
import { formatUrl } from './format-url';
|
import { formatUrl } from './format-url';
|
||||||
|
|
||||||
@ -15,7 +27,18 @@ export interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getHost = (url: string) => new URL(url).host;
|
const getHost = (url: string) => new URL(url).host;
|
||||||
|
const MouseMaskContainer = styled('div')({
|
||||||
|
position: 'absolute',
|
||||||
|
zIndex: 1,
|
||||||
|
top: '0px',
|
||||||
|
left: '0px',
|
||||||
|
right: '0px',
|
||||||
|
bottom: '0px',
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
'&:hover': {
|
||||||
|
pointerEvents: 'none',
|
||||||
|
},
|
||||||
|
});
|
||||||
const LinkContainer = styled('div')<{
|
const LinkContainer = styled('div')<{
|
||||||
isSelected: boolean;
|
isSelected: boolean;
|
||||||
}>(({ theme, isSelected }) => {
|
}>(({ theme, isSelected }) => {
|
||||||
@ -38,12 +61,28 @@ const LinkContainer = styled('div')<{
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
const _getLinkStyle = (scene: string) => {
|
||||||
|
switch (scene) {
|
||||||
|
case SCENE_CONFIG.PAGE:
|
||||||
|
return {
|
||||||
|
width: '420px',
|
||||||
|
height: '198px',
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
return {
|
||||||
|
width: '252px',
|
||||||
|
height: '126px',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
const SourceViewContainer = styled('div')<{
|
const SourceViewContainer = styled('div')<{
|
||||||
isSelected: boolean;
|
isSelected: boolean;
|
||||||
}>(({ theme, isSelected }) => {
|
scene: string;
|
||||||
|
}>(({ theme, isSelected, scene }) => {
|
||||||
return {
|
return {
|
||||||
|
..._getLinkStyle(scene),
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
|
position: 'relative',
|
||||||
borderRadius: theme.affine.shape.borderRadius,
|
borderRadius: theme.affine.shape.borderRadius,
|
||||||
background: isSelected ? 'rgba(152, 172, 189, 0.1)' : 'transparent',
|
background: isSelected ? 'rgba(152, 172, 189, 0.1)' : 'transparent',
|
||||||
padding: '8px',
|
padding: '8px',
|
||||||
@ -52,32 +91,96 @@ const SourceViewContainer = styled('div')<{
|
|||||||
height: '100%',
|
height: '100%',
|
||||||
border: '1px solid #EAEEF2',
|
border: '1px solid #EAEEF2',
|
||||||
borderRadius: theme.affine.shape.borderRadius,
|
borderRadius: theme.affine.shape.borderRadius,
|
||||||
|
userSelect: 'none',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const LazyIframe = ({
|
||||||
|
src,
|
||||||
|
delay = 3000,
|
||||||
|
fallback,
|
||||||
|
}: {
|
||||||
|
src: string;
|
||||||
|
delay?: number;
|
||||||
|
fallback?: ReactNode;
|
||||||
|
}) => {
|
||||||
|
const [show, setShow] = useState(false);
|
||||||
|
const timer = useRef<number>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Hide iframe when the src changed
|
||||||
|
setShow(false);
|
||||||
|
}, [src]);
|
||||||
|
|
||||||
|
const onLoad = () => {
|
||||||
|
clearTimeout(timer.current);
|
||||||
|
timer.current = window.setTimeout(() => {
|
||||||
|
// Prevent iframe scrolling parent container
|
||||||
|
// Remove the delay after the issue is resolved
|
||||||
|
// See W3C https://github.com/w3c/csswg-drafts/issues/7134
|
||||||
|
// See https://forum.figma.com/t/prevent-figmas-embed-code-from-automatically-scrolling-to-it-on-page-load/26029/6
|
||||||
|
setShow(true);
|
||||||
|
}, delay);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
onMouseDown={e => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
style={{ display: show ? 'block' : 'none', height: '100%' }}
|
||||||
|
>
|
||||||
|
<iframe src={src} onLoad={onLoad} />
|
||||||
|
</div>
|
||||||
|
{!show && fallback}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Loading = styled('div')(() => {
|
||||||
|
return {
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
display: 'flex',
|
||||||
|
lineHeight: '100%',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
border: '1px solid #EAEEF2',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const LoadingContiner = () => {
|
||||||
|
return <Loading>loading...</Loading>;
|
||||||
|
};
|
||||||
|
|
||||||
export const SourceView: FC<Props> = props => {
|
export const SourceView: FC<Props> = props => {
|
||||||
const { link, isSelected, block, editorElement } = props;
|
const { link, isSelected, block, editorElement } = props;
|
||||||
const src = formatUrl(link);
|
const src = formatUrl(link);
|
||||||
const openTabOnBrowser = () => {
|
// let iframeShow = useLazyIframe(src, 3000, iframeContainer);
|
||||||
window.open(link, '_blank');
|
const [currentView] = useCurrentView();
|
||||||
};
|
const { type } = currentView;
|
||||||
if (src?.startsWith('http')) {
|
if (src?.startsWith('http')) {
|
||||||
return (
|
return (
|
||||||
<LinkContainer
|
<div style={{ display: 'flex' }}>
|
||||||
isSelected={isSelected}
|
<SourceViewContainer isSelected={isSelected} scene={type}>
|
||||||
onMouseDown={e => e.preventDefault()}
|
<MouseMaskContainer />
|
||||||
onClick={openTabOnBrowser}
|
|
||||||
>
|
<LazyIframe
|
||||||
<p>{getHost(src)}</p>
|
src={src}
|
||||||
<p>{src}</p>
|
fallback={LoadingContiner()}
|
||||||
</LinkContainer>
|
></LazyIframe>
|
||||||
|
</SourceViewContainer>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
} else if (src?.startsWith('affine')) {
|
} else if (src?.startsWith('affine')) {
|
||||||
return (
|
return (
|
||||||
<SourceViewContainer
|
<SourceViewContainer
|
||||||
isSelected={isSelected}
|
isSelected={isSelected}
|
||||||
style={{ padding: '0' }}
|
style={{ padding: '0' }}
|
||||||
|
scene={type}
|
||||||
>
|
>
|
||||||
<BlockPreview
|
<BlockPreview
|
||||||
block={block}
|
block={block}
|
||||||
|
@ -126,7 +126,7 @@ export const TextManage = forwardRef<ExtendedTextUtils, CreateTextView>(
|
|||||||
|
|
||||||
// block = await editor.commands.blockCommands.createNextBlock(block.id,)
|
// block = await editor.commands.blockCommands.createNextBlock(block.id,)
|
||||||
const on_text_view_active = useCallback(
|
const on_text_view_active = useCallback(
|
||||||
(point: CursorTypes, rang_form?: 'up' | 'down') => {
|
(point: CursorTypes) => {
|
||||||
// TODO code to be optimized
|
// TODO code to be optimized
|
||||||
if (textRef.current) {
|
if (textRef.current) {
|
||||||
const end_selection = textRef.current.getEndSelection();
|
const end_selection = textRef.current.getEndSelection();
|
||||||
@ -146,7 +146,7 @@ export const TextManage = forwardRef<ExtendedTextUtils, CreateTextView>(
|
|||||||
.getElementsByClassName('text-paragraph')[0]
|
.getElementsByClassName('text-paragraph')[0]
|
||||||
.getBoundingClientRect();
|
.getBoundingClientRect();
|
||||||
|
|
||||||
if (rang_form === 'up') {
|
if (blockTop > blockDomStyle.top) {
|
||||||
blockTop = blockDomStyle.bottom - 5;
|
blockTop = blockDomStyle.bottom - 5;
|
||||||
} else {
|
} else {
|
||||||
blockTop = blockDomStyle.top + 5;
|
blockTop = blockDomStyle.top + 5;
|
||||||
@ -319,7 +319,7 @@ export const TextManage = forwardRef<ExtendedTextUtils, CreateTextView>(
|
|||||||
if (nowPosition.top === startPosition.top) {
|
if (nowPosition.top === startPosition.top) {
|
||||||
editor.selectionManager.activePreviousNode(
|
editor.selectionManager.activePreviousNode(
|
||||||
block.id,
|
block.id,
|
||||||
new Point(nowPosition.left, nowPosition.top - 20)
|
new Point(nowPosition.left, nowPosition.top)
|
||||||
);
|
);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -357,17 +357,14 @@ export const TextManage = forwardRef<ExtendedTextUtils, CreateTextView>(
|
|||||||
// The specific amount of TODO needs to be determined after subsequent padding
|
// The specific amount of TODO needs to be determined after subsequent padding
|
||||||
editor.selectionManager.activeNextNode(
|
editor.selectionManager.activeNextNode(
|
||||||
block.id,
|
block.id,
|
||||||
new Point(nowPosition.left, nowPosition.bottom + 20)
|
new Point(nowPosition.left, nowPosition.bottom)
|
||||||
);
|
);
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
if (prePosition?.bottom === endPosition.bottom) {
|
if (prePosition?.bottom === endPosition.bottom) {
|
||||||
editor.selectionManager.activeNextNode(
|
editor.selectionManager.activeNextNode(
|
||||||
block.id,
|
block.id,
|
||||||
new Point(
|
new Point(prePosition.left, prePosition?.bottom)
|
||||||
prePosition.left,
|
|
||||||
prePosition?.bottom + 20
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
|
@ -3,7 +3,7 @@ import {
|
|||||||
RenderBlock,
|
RenderBlock,
|
||||||
useCurrentView,
|
useCurrentView,
|
||||||
useOnSelect,
|
useOnSelect,
|
||||||
WrapperWithPendantAndDragDrop,
|
BlockPendantProvider,
|
||||||
} from '@toeverything/components/editor-core';
|
} from '@toeverything/components/editor-core';
|
||||||
import { styled } from '@toeverything/components/ui';
|
import { styled } from '@toeverything/components/ui';
|
||||||
import type {
|
import type {
|
||||||
@ -13,7 +13,6 @@ import type {
|
|||||||
ReactElement,
|
ReactElement,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import { forwardRef, useState } from 'react';
|
import { forwardRef, useState } from 'react';
|
||||||
import style9 from 'style9';
|
|
||||||
import { SCENE_CONFIG } from '../blocks/group/config';
|
import { SCENE_CONFIG } from '../blocks/group/config';
|
||||||
import { BlockContainer } from '../components/BlockContainer';
|
import { BlockContainer } from '../components/BlockContainer';
|
||||||
|
|
||||||
@ -30,29 +29,15 @@ const TreeView = forwardRef<
|
|||||||
{ lastItem?: boolean } & ComponentPropsWithRef<'div'>
|
{ lastItem?: boolean } & ComponentPropsWithRef<'div'>
|
||||||
>(({ lastItem, children, onClick, ...restProps }, ref) => {
|
>(({ lastItem, children, onClick, ...restProps }, ref) => {
|
||||||
return (
|
return (
|
||||||
<div ref={ref} className={treeStyles('treeWrapper')} {...restProps}>
|
<TreeWrapper ref={ref} {...restProps}>
|
||||||
<div className={treeStyles('treeView')}>
|
<StyledTreeView>
|
||||||
<div
|
<VerticalLine last={lastItem} onClick={onClick} />
|
||||||
className={treeStyles({
|
<HorizontalLine last={lastItem} onClick={onClick} />
|
||||||
line: true,
|
{lastItem && <LastItemRadius />}
|
||||||
verticalLine: true,
|
</StyledTreeView>
|
||||||
lastItemVerticalLine: lastItem,
|
|
||||||
})}
|
|
||||||
onClick={onClick}
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
className={treeStyles({
|
|
||||||
line: true,
|
|
||||||
horizontalLine: true,
|
|
||||||
lastItemHorizontalLine: lastItem,
|
|
||||||
})}
|
|
||||||
onClick={onClick}
|
|
||||||
/>
|
|
||||||
{lastItem && <div className={treeStyles('lastItemRadius')} />}
|
|
||||||
</div>
|
|
||||||
{/* maybe need a child wrapper */}
|
{/* maybe need a child wrapper */}
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</TreeWrapper>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -71,10 +56,7 @@ const ChildrenView = ({
|
|||||||
const isKanbanScene = currentView.type === SCENE_CONFIG.KANBAN;
|
const isKanbanScene = currentView.type === SCENE_CONFIG.KANBAN;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<Children style={{ ...(!isKanbanScene && { marginLeft: indent }) }}>
|
||||||
className={styles('children')}
|
|
||||||
style={{ ...(!isKanbanScene && { marginLeft: indent }) }}
|
|
||||||
>
|
|
||||||
{childrenIds.map((childId, idx) => {
|
{childrenIds.map((childId, idx) => {
|
||||||
if (isKanbanScene) {
|
if (isKanbanScene) {
|
||||||
return (
|
return (
|
||||||
@ -94,7 +76,7 @@ const ChildrenView = ({
|
|||||||
</TreeView>
|
</TreeView>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</Children>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -104,9 +86,7 @@ const CollapsedNode = forwardRef<
|
|||||||
>((props, ref) => {
|
>((props, ref) => {
|
||||||
return (
|
return (
|
||||||
<TreeView ref={ref} lastItem={true} {...props}>
|
<TreeView ref={ref} lastItem={true} {...props}>
|
||||||
<div className={treeStyles('collapsed')} onClick={props.onClick}>
|
<Collapsed onClick={props.onClick}>···</Collapsed>
|
||||||
···
|
|
||||||
</div>
|
|
||||||
</TreeView>
|
</TreeView>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -146,11 +126,11 @@ export const withTreeViewChildren = (
|
|||||||
editor={props.editor}
|
editor={props.editor}
|
||||||
block={block}
|
block={block}
|
||||||
selected={isSelect}
|
selected={isSelect}
|
||||||
className={styles('wrapper')}
|
className={Wrapper.toString()}
|
||||||
>
|
>
|
||||||
<WrapperWithPendantAndDragDrop editor={editor} block={block}>
|
<BlockPendantProvider block={block}>
|
||||||
<div className={styles('node')}>{creator(props)}</div>
|
<div>{creator(props)}</div>
|
||||||
</WrapperWithPendantAndDragDrop>
|
</BlockPendantProvider>
|
||||||
|
|
||||||
{collapsed && (
|
{collapsed && (
|
||||||
<CollapsedNode
|
<CollapsedNode
|
||||||
@ -170,93 +150,79 @@ export const withTreeViewChildren = (
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const styles = style9.create({
|
const Wrapper = styled('div')({ display: 'flex', flexDirection: 'column' });
|
||||||
wrapper: {
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
},
|
|
||||||
node: {},
|
|
||||||
|
|
||||||
children: {
|
const Children = Wrapper;
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
const TREE_COLOR = '#D5DFE6';
|
||||||
},
|
// TODO determine the position of the horizontal line by the type of the item
|
||||||
|
const ITEM_POINT_HEIGHT = '12.5px'; // '50%'
|
||||||
|
|
||||||
|
const TreeWrapper = styled('div')({
|
||||||
|
position: 'relative',
|
||||||
});
|
});
|
||||||
|
|
||||||
const treeColor = '#D5DFE6';
|
const StyledTreeView = styled('div')({
|
||||||
// TODO determine the position of the horizontal line by the type of the item
|
position: 'absolute',
|
||||||
const itemPointHeight = '12.5px'; // '50%'
|
left: '-21px',
|
||||||
|
height: '100%',
|
||||||
|
});
|
||||||
|
|
||||||
const treeStyles = style9.create({
|
const Line = styled('div')({
|
||||||
treeWrapper: {
|
position: 'absolute',
|
||||||
position: 'relative',
|
cursor: 'pointer',
|
||||||
},
|
backgroundColor: TREE_COLOR,
|
||||||
|
// somehow tldraw would override this
|
||||||
|
boxSizing: 'content-box!important' as any,
|
||||||
|
// See [Can I add background color only for padding?](https://stackoverflow.com/questions/14628601/can-i-add-background-color-only-for-padding)
|
||||||
|
backgroundClip: 'content-box',
|
||||||
|
backgroundOrigin: 'content-box',
|
||||||
|
// Increase click hot spot
|
||||||
|
padding: '10px',
|
||||||
|
});
|
||||||
|
|
||||||
treeView: {
|
const VerticalLine = styled(Line)<{ last: boolean }>(({ last }) => ({
|
||||||
position: 'absolute',
|
width: '1px',
|
||||||
left: '-21px',
|
height: last ? ITEM_POINT_HEIGHT : '100%',
|
||||||
height: '100%',
|
paddingTop: 0,
|
||||||
},
|
paddingBottom: 0,
|
||||||
line: {
|
transform: 'translate(-50%, 0)',
|
||||||
position: 'absolute',
|
|
||||||
cursor: 'pointer',
|
|
||||||
backgroundColor: treeColor,
|
|
||||||
boxSizing: 'content-box',
|
|
||||||
// See [Can I add background color only for padding?](https://stackoverflow.com/questions/14628601/can-i-add-background-color-only-for-padding)
|
|
||||||
backgroundClip: 'content-box',
|
|
||||||
backgroundOrigin: 'content-box',
|
|
||||||
// Increase click hot spot
|
|
||||||
padding: '10px',
|
|
||||||
},
|
|
||||||
verticalLine: {
|
|
||||||
width: '1px',
|
|
||||||
height: '100%',
|
|
||||||
paddingTop: 0,
|
|
||||||
paddingBottom: 0,
|
|
||||||
transform: 'translate(-50%, 0)',
|
|
||||||
},
|
|
||||||
horizontalLine: {
|
|
||||||
width: '16px',
|
|
||||||
height: '1px',
|
|
||||||
paddingLeft: 0,
|
|
||||||
paddingRight: 0,
|
|
||||||
top: itemPointHeight,
|
|
||||||
transform: 'translate(0, -50%)',
|
|
||||||
},
|
|
||||||
noItemHorizontalLine: {
|
|
||||||
display: 'none',
|
|
||||||
},
|
|
||||||
|
|
||||||
lastItemHorizontalLine: {
|
opacity: last ? 0 : 'unset',
|
||||||
opacity: 0,
|
}));
|
||||||
},
|
|
||||||
lastItemVerticalLine: {
|
|
||||||
height: itemPointHeight,
|
|
||||||
opacity: 0,
|
|
||||||
},
|
|
||||||
lastItemRadius: {
|
|
||||||
boxSizing: 'content-box',
|
|
||||||
position: 'absolute',
|
|
||||||
left: '-0.5px',
|
|
||||||
top: 0,
|
|
||||||
height: itemPointHeight,
|
|
||||||
bottom: '50%',
|
|
||||||
width: '16px',
|
|
||||||
borderWidth: '1px',
|
|
||||||
borderStyle: 'solid',
|
|
||||||
borderLeftColor: treeColor,
|
|
||||||
borderBottomColor: treeColor,
|
|
||||||
borderTop: 'none',
|
|
||||||
borderRight: 'none',
|
|
||||||
borderRadius: '0 0 0 3px',
|
|
||||||
pointerEvents: 'none',
|
|
||||||
},
|
|
||||||
|
|
||||||
collapsed: {
|
const HorizontalLine = styled(Line)<{ last: boolean }>(({ last }) => ({
|
||||||
cursor: 'pointer',
|
width: '16px',
|
||||||
display: 'inline-block',
|
height: '1px',
|
||||||
color: '#B9CAD5',
|
paddingLeft: 0,
|
||||||
},
|
paddingRight: 0,
|
||||||
|
top: ITEM_POINT_HEIGHT,
|
||||||
|
transform: 'translate(0, -50%)',
|
||||||
|
opacity: last ? 0 : 'unset',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const Collapsed = styled('div')({
|
||||||
|
cursor: 'pointer',
|
||||||
|
display: 'inline-block',
|
||||||
|
color: '#B9CAD5',
|
||||||
|
});
|
||||||
|
|
||||||
|
const LastItemRadius = styled('div')({
|
||||||
|
boxSizing: 'content-box',
|
||||||
|
position: 'absolute',
|
||||||
|
left: '-0.5px',
|
||||||
|
top: 0,
|
||||||
|
height: ITEM_POINT_HEIGHT,
|
||||||
|
bottom: '50%',
|
||||||
|
width: '16px',
|
||||||
|
borderWidth: '1px',
|
||||||
|
borderStyle: 'solid',
|
||||||
|
borderLeftColor: TREE_COLOR,
|
||||||
|
borderBottomColor: TREE_COLOR,
|
||||||
|
borderTop: 'none',
|
||||||
|
borderRight: 'none',
|
||||||
|
borderRadius: '0 0 0 3px',
|
||||||
|
pointerEvents: 'none',
|
||||||
});
|
});
|
||||||
|
|
||||||
const StyledBorder = styled('div')({
|
const StyledBorder = styled('div')({
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
import { createContext, useContext } from 'react';
|
import { createContext, useContext } from 'react';
|
||||||
import type { BlockEditor, AsyncBlock } from './editor';
|
import type { BlockEditor, AsyncBlock } from './editor';
|
||||||
import type { Column } from '@toeverything/datasource/db-service';
|
|
||||||
import { genErrorObj } from '@toeverything/utils';
|
import { genErrorObj } from '@toeverything/utils';
|
||||||
|
|
||||||
export const RootContext = createContext<{
|
const RootContext = createContext<{
|
||||||
editor: BlockEditor;
|
editor: BlockEditor;
|
||||||
// TODO: Temporary fix, dependencies in the new architecture are bottom-up, editors do not need to be passed down from the top
|
// TODO: Temporary fix, dependencies in the new architecture are bottom-up, editors do not need to be passed down from the top
|
||||||
editorElement: () => JSX.Element;
|
editorElement: () => JSX.Element;
|
||||||
@ -14,6 +13,8 @@ export const RootContext = createContext<{
|
|||||||
) as any
|
) as any
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const EditorProvider = RootContext.Provider;
|
||||||
|
|
||||||
export const useEditor = () => {
|
export const useEditor = () => {
|
||||||
return useContext(RootContext);
|
return useContext(RootContext);
|
||||||
};
|
};
|
||||||
@ -22,16 +23,3 @@ export const useEditor = () => {
|
|||||||
* @deprecated
|
* @deprecated
|
||||||
*/
|
*/
|
||||||
export const BlockContext = createContext<AsyncBlock>(null as any);
|
export const BlockContext = createContext<AsyncBlock>(null as any);
|
||||||
|
|
||||||
/**
|
|
||||||
* Context of column information
|
|
||||||
*
|
|
||||||
* @deprecated
|
|
||||||
*/
|
|
||||||
export const ColumnsContext = createContext<{
|
|
||||||
fromId: string;
|
|
||||||
columns: Column[];
|
|
||||||
}>({
|
|
||||||
fromId: '',
|
|
||||||
columns: [],
|
|
||||||
});
|
|
@ -2,14 +2,14 @@ import type { BlockEditor } from './editor';
|
|||||||
import { styled, usePatchNodes } from '@toeverything/components/ui';
|
import { styled, usePatchNodes } from '@toeverything/components/ui';
|
||||||
import type { FC, PropsWithChildren } from 'react';
|
import type { FC, PropsWithChildren } from 'react';
|
||||||
import React, { useEffect, useRef, useState, useCallback } from 'react';
|
import React, { useEffect, useRef, useState, useCallback } from 'react';
|
||||||
import { RootContext } from './contexts';
|
import { EditorProvider } from './Contexts';
|
||||||
import { SelectionRect, SelectionRef } from './Selection';
|
import { SelectionRect, SelectionRef } from './Selection';
|
||||||
import {
|
import {
|
||||||
Protocol,
|
Protocol,
|
||||||
services,
|
services,
|
||||||
type ReturnUnobserve,
|
type ReturnUnobserve,
|
||||||
} from '@toeverything/datasource/db-service';
|
} from '@toeverything/datasource/db-service';
|
||||||
import { addNewGroup } from './recast-block';
|
import { addNewGroup, appendNewGroup } from './recast-block';
|
||||||
import { useIsOnDrag } from './hooks';
|
import { useIsOnDrag } from './hooks';
|
||||||
|
|
||||||
interface RenderRootProps {
|
interface RenderRootProps {
|
||||||
@ -151,7 +151,7 @@ export const RenderRoot: FC<PropsWithChildren<RenderRootProps>> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RootContext.Provider value={{ editor, editorElement }}>
|
<EditorProvider value={{ editor, editorElement }}>
|
||||||
<Container
|
<Container
|
||||||
isWhiteboard={editor.isWhiteboard}
|
isWhiteboard={editor.isWhiteboard}
|
||||||
ref={ref => {
|
ref={ref => {
|
||||||
@ -183,7 +183,7 @@ export const RenderRoot: FC<PropsWithChildren<RenderRootProps>> = ({
|
|||||||
{editor.isWhiteboard ? null : <ScrollBlank editor={editor} />}
|
{editor.isWhiteboard ? null : <ScrollBlank editor={editor} />}
|
||||||
{patchedNodes}
|
{patchedNodes}
|
||||||
</Container>
|
</Container>
|
||||||
</RootContext.Provider>
|
</EditorProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -199,24 +199,32 @@ function ScrollBlank({ editor }: { editor: BlockEditor }) {
|
|||||||
mouseMoved.current = false;
|
mouseMoved.current = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const lastBlock = await editor.getRootLastChildrenBlock();
|
const rootBlock = await editor.getBlockById(
|
||||||
|
editor.getRootBlockId()
|
||||||
|
);
|
||||||
|
if (!rootBlock) {
|
||||||
|
throw new Error('root block is not found');
|
||||||
|
}
|
||||||
|
|
||||||
const lastGroupBlock = await editor.getRootLastChildrenBlock();
|
const lastRootChildren = await rootBlock.lastChild();
|
||||||
// If last block is not a group
|
// If last block is not a group
|
||||||
// create a group with a empty text
|
// create a group with a empty text
|
||||||
if (lastGroupBlock.type !== 'group') {
|
if (lastRootChildren == null) {
|
||||||
addNewGroup(editor, lastBlock, true);
|
appendNewGroup(editor, rootBlock, true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lastGroupBlock.childrenIds.length > 1) {
|
if (
|
||||||
addNewGroup(editor, lastBlock, true);
|
lastRootChildren.type !== Protocol.Block.Type.group ||
|
||||||
|
lastRootChildren.childrenIds.length > 1
|
||||||
|
) {
|
||||||
|
addNewGroup(editor, lastRootChildren, true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the **only** block in the group is text and is empty
|
// If the **only** block in the group is text and is empty
|
||||||
// active the text block
|
// active the text block
|
||||||
const theGroupChildBlock = await lastGroupBlock.firstChild();
|
const theGroupChildBlock = await lastRootChildren.firstChild();
|
||||||
|
|
||||||
if (
|
if (
|
||||||
theGroupChildBlock &&
|
theGroupChildBlock &&
|
||||||
@ -229,7 +237,7 @@ function ScrollBlank({ editor }: { editor: BlockEditor }) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// else create a new group
|
// else create a new group
|
||||||
addNewGroup(editor, lastBlock, true);
|
addNewGroup(editor, lastRootChildren, true);
|
||||||
},
|
},
|
||||||
[editor]
|
[editor]
|
||||||
);
|
);
|
||||||
|
@ -187,7 +187,6 @@ export const SelectionRect = forwardRef<SelectionRef, SelectionProps>(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
const scrollDirections = getScrollDirections(
|
const scrollDirections = getScrollDirections(
|
||||||
endPointRef.current,
|
endPointRef.current,
|
||||||
scrollManager.verticalScrollTriggerDistance,
|
scrollManager.verticalScrollTriggerDistance,
|
||||||
@ -204,6 +203,7 @@ export const SelectionRect = forwardRef<SelectionRef, SelectionProps>(
|
|||||||
mouseType.current = 'up';
|
mouseType.current = 'up';
|
||||||
startPointBlock.current = null;
|
startPointBlock.current = null;
|
||||||
setShow(false);
|
setShow(false);
|
||||||
|
setRect(Rect.fromLTRB(0, 0, 0, 0));
|
||||||
scrollManager.stopAutoScroll();
|
scrollManager.stopAutoScroll();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
import { AsyncBlock, BlockEditor } from '../editor';
|
|
||||||
import type { FC, ReactElement } from 'react';
|
|
||||||
import { BlockPendantProvider } from '../block-pendant';
|
|
||||||
import { DragDropWrapper } from '../drag-drop-wrapper';
|
|
||||||
|
|
||||||
type BlockContentWrapperProps = {
|
|
||||||
block: AsyncBlock;
|
|
||||||
editor: BlockEditor;
|
|
||||||
children: ReactElement | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: remove
|
|
||||||
export const WrapperWithPendantAndDragDrop: FC<BlockContentWrapperProps> =
|
|
||||||
function ({ block, children, editor }) {
|
|
||||||
return (
|
|
||||||
<DragDropWrapper block={block} editor={editor}>
|
|
||||||
<BlockPendantProvider block={block}>
|
|
||||||
{children}
|
|
||||||
</BlockPendantProvider>
|
|
||||||
</DragDropWrapper>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1 +0,0 @@
|
|||||||
export * from './BlockContentWrapper';
|
|
@ -1,5 +1,4 @@
|
|||||||
import React, { ReactNode, useRef, useEffect, useState } from 'react';
|
import React, { ReactNode, useRef, useEffect, useState } from 'react';
|
||||||
import { getPendantHistory } from '../utils';
|
|
||||||
import {
|
import {
|
||||||
getRecastItemValue,
|
getRecastItemValue,
|
||||||
RecastMetaProperty,
|
RecastMetaProperty,
|
||||||
@ -30,22 +29,22 @@ export const PendantHistoryPanel = ({
|
|||||||
|
|
||||||
const [history, setHistory] = useState<RecastBlockValue[]>([]);
|
const [history, setHistory] = useState<RecastBlockValue[]>([]);
|
||||||
const popoverHandlerRef = useRef<{ [key: string]: PopperHandler }>({});
|
const popoverHandlerRef = useRef<{ [key: string]: PopperHandler }>({});
|
||||||
|
const { getValueHistory } = getRecastItemValue(block);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const init = async () => {
|
const init = async () => {
|
||||||
const currentBlockValues = getRecastItemValue(block).getAllValue();
|
const currentBlockValues = getRecastItemValue(block).getAllValue();
|
||||||
const allProperties = getProperties();
|
const missValues = getProperties().filter(
|
||||||
const missProperties = allProperties.filter(
|
|
||||||
property => !currentBlockValues.find(v => v.id === property.id)
|
property => !currentBlockValues.find(v => v.id === property.id)
|
||||||
);
|
);
|
||||||
const pendantHistory = getPendantHistory({
|
const valueHistory = getValueHistory({
|
||||||
recastBlockId: recastBlock.id,
|
recastBlockId: recastBlock.id,
|
||||||
});
|
});
|
||||||
const historyMap = missProperties.reduce<{
|
const historyMap = missValues.reduce<{
|
||||||
[key: RecastPropertyId]: string;
|
[key: RecastPropertyId]: string[];
|
||||||
}>((history, property) => {
|
}>((history, property) => {
|
||||||
if (pendantHistory[property.id]) {
|
if (valueHistory[property.id]) {
|
||||||
history[property.id] = pendantHistory[property.id];
|
history[property.id] = valueHistory[property.id];
|
||||||
}
|
}
|
||||||
|
|
||||||
return history;
|
return history;
|
||||||
@ -54,18 +53,30 @@ export const PendantHistoryPanel = ({
|
|||||||
const blockHistory = (
|
const blockHistory = (
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
Object.entries(historyMap).map(
|
Object.entries(historyMap).map(
|
||||||
async ([propertyId, blockId]) => {
|
async ([propertyId, blockIds]) => {
|
||||||
const latestValueBlock = (
|
const blocks = await groupBlock.children();
|
||||||
await groupBlock.children()
|
const latestChangeBlock = blockIds
|
||||||
).find((block: AsyncBlock) => block.id === blockId);
|
.reverse()
|
||||||
|
.reduce<AsyncBlock>((block, id) => {
|
||||||
|
if (!block) {
|
||||||
|
return blocks.find(
|
||||||
|
block => block.id === id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return block;
|
||||||
|
}, null);
|
||||||
|
|
||||||
return getRecastItemValue(
|
if (latestChangeBlock) {
|
||||||
latestValueBlock
|
return getRecastItemValue(
|
||||||
).getValue(propertyId as RecastPropertyId);
|
latestChangeBlock
|
||||||
|
).getValue(propertyId as RecastPropertyId);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
).filter(v => v);
|
).filter(v => v);
|
||||||
|
|
||||||
setHistory(blockHistory);
|
setHistory(blockHistory);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ import { ModifyPanelContentProps } from './types';
|
|||||||
import { StyledDivider, StyledPopoverSubTitle } from '../StyledComponent';
|
import { StyledDivider, StyledPopoverSubTitle } from '../StyledComponent';
|
||||||
import { BasicSelect } from './Select';
|
import { BasicSelect } from './Select';
|
||||||
import { InformationProperty, InformationValue } from '../../recast-block';
|
import { InformationProperty, InformationValue } from '../../recast-block';
|
||||||
import { genInitialOptions, getPendantIconsConfigByName } from '../utils';
|
import { generateInitialOptions, getPendantIconsConfigByName } from '../utils';
|
||||||
|
|
||||||
export default (props: ModifyPanelContentProps) => {
|
export default (props: ModifyPanelContentProps) => {
|
||||||
const { onPropertyChange, onValueChange, initialValue, property } = props;
|
const { onPropertyChange, onValueChange, initialValue, property } = props;
|
||||||
@ -38,7 +38,7 @@ export default (props: ModifyPanelContentProps) => {
|
|||||||
}}
|
}}
|
||||||
initialOptions={
|
initialOptions={
|
||||||
propProperty?.emailOptions ||
|
propProperty?.emailOptions ||
|
||||||
genInitialOptions(
|
generateInitialOptions(
|
||||||
property?.type,
|
property?.type,
|
||||||
getPendantIconsConfigByName('Email')
|
getPendantIconsConfigByName('Email')
|
||||||
)
|
)
|
||||||
@ -66,7 +66,7 @@ export default (props: ModifyPanelContentProps) => {
|
|||||||
}}
|
}}
|
||||||
initialOptions={
|
initialOptions={
|
||||||
propProperty?.phoneOptions ||
|
propProperty?.phoneOptions ||
|
||||||
genInitialOptions(
|
generateInitialOptions(
|
||||||
property?.type,
|
property?.type,
|
||||||
getPendantIconsConfigByName('Phone')
|
getPendantIconsConfigByName('Phone')
|
||||||
)
|
)
|
||||||
@ -94,7 +94,7 @@ export default (props: ModifyPanelContentProps) => {
|
|||||||
}}
|
}}
|
||||||
initialOptions={
|
initialOptions={
|
||||||
propProperty?.locationOptions ||
|
propProperty?.locationOptions ||
|
||||||
genInitialOptions(
|
generateInitialOptions(
|
||||||
property?.type,
|
property?.type,
|
||||||
getPendantIconsConfigByName('Location')
|
getPendantIconsConfigByName('Location')
|
||||||
)
|
)
|
||||||
|
@ -18,7 +18,9 @@ export default ({
|
|||||||
user: { username, nickname, photo },
|
user: { username, nickname, photo },
|
||||||
} = useUserAndSpaces();
|
} = useUserAndSpaces();
|
||||||
|
|
||||||
const [selectedValue, setSelectedValue] = useState(initialValue?.value);
|
const [selectedValue, setSelectedValue] = useState(
|
||||||
|
initialValue?.value || ''
|
||||||
|
);
|
||||||
const [focus, setFocus] = useState(false);
|
const [focus, setFocus] = useState(false);
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
return (
|
return (
|
||||||
|
@ -21,7 +21,7 @@ import {
|
|||||||
} from '@toeverything/components/ui';
|
} from '@toeverything/components/ui';
|
||||||
import { HighLightIconInput } from './IconInput';
|
import { HighLightIconInput } from './IconInput';
|
||||||
import { PendantConfig, IconNames, OptionIdType, OptionType } from '../types';
|
import { PendantConfig, IconNames, OptionIdType, OptionType } from '../types';
|
||||||
import { genBasicOption } from '../utils';
|
import { generateBasicOption } from '../utils';
|
||||||
|
|
||||||
type OptionItemType = {
|
type OptionItemType = {
|
||||||
option: OptionType;
|
option: OptionType;
|
||||||
@ -66,7 +66,7 @@ export const BasicSelect = ({
|
|||||||
const [selectIds, setSelectIds] = useState<OptionIdType[]>(initialValue);
|
const [selectIds, setSelectIds] = useState<OptionIdType[]>(initialValue);
|
||||||
|
|
||||||
const insertOption = (insertId: OptionIdType) => {
|
const insertOption = (insertId: OptionIdType) => {
|
||||||
const newOption = genBasicOption({
|
const newOption = generateBasicOption({
|
||||||
index: options.length + 1,
|
index: options.length + 1,
|
||||||
iconConfig,
|
iconConfig,
|
||||||
});
|
});
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { nanoid } from 'nanoid';
|
|
||||||
import { Input, Option, Select, Tooltip } from '@toeverything/components/ui';
|
import { Input, Option, Select, Tooltip } from '@toeverything/components/ui';
|
||||||
import { HelpCenterIcon } from '@toeverything/components/icons';
|
import { HelpCenterIcon } from '@toeverything/components/icons';
|
||||||
import { AsyncBlock } from '../../editor';
|
import { AsyncBlock } from '../../editor';
|
||||||
@ -15,13 +14,13 @@ import {
|
|||||||
StyledPopoverSubTitle,
|
StyledPopoverSubTitle,
|
||||||
StyledPopoverWrapper,
|
StyledPopoverWrapper,
|
||||||
} from '../StyledComponent';
|
} from '../StyledComponent';
|
||||||
import { genInitialOptions, getPendantConfigByType } from '../utils';
|
import {
|
||||||
|
generateRandomFieldName,
|
||||||
|
generateInitialOptions,
|
||||||
|
getPendantConfigByType,
|
||||||
|
} from '../utils';
|
||||||
import { useOnCreateSure } from './hooks';
|
import { useOnCreateSure } from './hooks';
|
||||||
|
|
||||||
const upperFirst = (str: string) => {
|
|
||||||
return `${str[0].toUpperCase()}${str.slice(1)}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const CreatePendantPanel = ({
|
export const CreatePendantPanel = ({
|
||||||
block,
|
block,
|
||||||
onSure,
|
onSure,
|
||||||
@ -35,7 +34,7 @@ export const CreatePendantPanel = ({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
selectedOption &&
|
selectedOption &&
|
||||||
setFieldName(upperFirst(`${selectedOption.type}#${nanoid(4)}`));
|
setFieldName(generateRandomFieldName(selectedOption.type));
|
||||||
}, [selectedOption]);
|
}, [selectedOption]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -45,7 +44,7 @@ export const CreatePendantPanel = ({
|
|||||||
<Select
|
<Select
|
||||||
width={284}
|
width={284}
|
||||||
placeholder="Search for a field type"
|
placeholder="Search for a field type"
|
||||||
value={selectedOption}
|
value={selectedOption ?? null}
|
||||||
onChange={(selectedValue: PendantOptions) => {
|
onChange={(selectedValue: PendantOptions) => {
|
||||||
setSelectedOption(selectedValue);
|
setSelectedOption(selectedValue);
|
||||||
}}
|
}}
|
||||||
@ -93,7 +92,7 @@ export const CreatePendantPanel = ({
|
|||||||
<PendantModifyPanel
|
<PendantModifyPanel
|
||||||
type={selectedOption.type}
|
type={selectedOption.type}
|
||||||
// Select, MultiSelect, Status use this props as initial property
|
// Select, MultiSelect, Status use this props as initial property
|
||||||
initialOptions={genInitialOptions(
|
initialOptions={generateInitialOptions(
|
||||||
selectedOption.type,
|
selectedOption.type,
|
||||||
getPendantConfigByType(selectedOption.type)
|
getPendantConfigByType(selectedOption.type)
|
||||||
)}
|
)}
|
||||||
|
@ -4,11 +4,11 @@ import { HelpCenterIcon } from '@toeverything/components/icons';
|
|||||||
import { PendantModifyPanel } from '../pendant-modify-panel';
|
import { PendantModifyPanel } from '../pendant-modify-panel';
|
||||||
import type { AsyncBlock } from '../../editor';
|
import type { AsyncBlock } from '../../editor';
|
||||||
import {
|
import {
|
||||||
|
getRecastItemValue,
|
||||||
type RecastBlockValue,
|
type RecastBlockValue,
|
||||||
type RecastMetaProperty,
|
type RecastMetaProperty,
|
||||||
} from '../../recast-block';
|
} from '../../recast-block';
|
||||||
import { getPendantConfigByType } from '../utils';
|
import { getPendantConfigByType } from '../utils';
|
||||||
import { usePendant } from '../use-pendant';
|
|
||||||
import {
|
import {
|
||||||
StyledPopoverWrapper,
|
StyledPopoverWrapper,
|
||||||
StyledOperationLabel,
|
StyledOperationLabel,
|
||||||
@ -42,7 +42,8 @@ export const UpdatePendantPanel = ({
|
|||||||
}: Props) => {
|
}: Props) => {
|
||||||
const pendantOption = pendantOptions.find(v => v.type === property.type);
|
const pendantOption = pendantOptions.find(v => v.type === property.type);
|
||||||
const iconConfig = getPendantConfigByType(property.type);
|
const iconConfig = getPendantConfigByType(property.type);
|
||||||
const { removePendant } = usePendant(block);
|
const { removeValue } = getRecastItemValue(block);
|
||||||
|
|
||||||
const Icon = IconMap[iconConfig.iconName];
|
const Icon = IconMap[iconConfig.iconName];
|
||||||
const [fieldName, setFieldName] = useState(property.name);
|
const [fieldName, setFieldName] = useState(property.name);
|
||||||
const onUpdateSure = useOnUpdateSure({ block, property });
|
const onUpdateSure = useOnUpdateSure({ block, property });
|
||||||
@ -108,7 +109,7 @@ export const UpdatePendantPanel = ({
|
|||||||
onDelete={
|
onDelete={
|
||||||
hasDelete
|
hasDelete
|
||||||
? async () => {
|
? async () => {
|
||||||
await removePendant(property);
|
await removeValue(property.id);
|
||||||
}
|
}
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,23 @@
|
|||||||
import type { CSSProperties } from 'react';
|
import type { CSSProperties } from 'react';
|
||||||
import {
|
import {
|
||||||
genSelectOptionId,
|
genSelectOptionId,
|
||||||
|
getRecastItemValue,
|
||||||
type InformationProperty,
|
type InformationProperty,
|
||||||
type MultiSelectProperty,
|
type MultiSelectProperty,
|
||||||
type RecastMetaProperty,
|
type RecastMetaProperty,
|
||||||
type SelectOption,
|
type SelectOption,
|
||||||
type SelectProperty,
|
type SelectProperty,
|
||||||
|
useRecastBlock,
|
||||||
useRecastBlockMeta,
|
useRecastBlockMeta,
|
||||||
useSelectProperty,
|
useSelectProperty,
|
||||||
|
SelectValue,
|
||||||
|
MultiSelectValue,
|
||||||
|
StatusValue,
|
||||||
|
InformationValue,
|
||||||
|
TextValue,
|
||||||
|
DateValue,
|
||||||
} from '../../recast-block';
|
} from '../../recast-block';
|
||||||
import { type AsyncBlock } from '../../editor';
|
import { type AsyncBlock } from '../../editor';
|
||||||
import { usePendant } from '../use-pendant';
|
|
||||||
import {
|
import {
|
||||||
type OptionType,
|
type OptionType,
|
||||||
PendantTypes,
|
PendantTypes,
|
||||||
@ -41,8 +48,8 @@ const genOptionWithId = (options: OptionType[] = []) => {
|
|||||||
export const useOnCreateSure = ({ block }: { block: AsyncBlock }) => {
|
export const useOnCreateSure = ({ block }: { block: AsyncBlock }) => {
|
||||||
const { addProperty } = useRecastBlockMeta();
|
const { addProperty } = useRecastBlockMeta();
|
||||||
const { createSelect } = useSelectProperty();
|
const { createSelect } = useSelectProperty();
|
||||||
const { setPendant } = usePendant(block);
|
const recastBlock = useRecastBlock();
|
||||||
|
const { setValue } = getRecastItemValue(block);
|
||||||
return async ({
|
return async ({
|
||||||
type,
|
type,
|
||||||
fieldName,
|
fieldName,
|
||||||
@ -79,7 +86,14 @@ export const useOnCreateSure = ({ block }: { block: AsyncBlock }) => {
|
|||||||
tempSelectedId: newValue,
|
tempSelectedId: newValue,
|
||||||
});
|
});
|
||||||
|
|
||||||
await setPendant(newProperty, selectedId);
|
await setValue(
|
||||||
|
{
|
||||||
|
id: newProperty.id,
|
||||||
|
type: newProperty.type,
|
||||||
|
value: selectedId,
|
||||||
|
} as SelectValue | MultiSelectValue | StatusValue,
|
||||||
|
recastBlock.id
|
||||||
|
);
|
||||||
} else if (type === PendantTypes.Information) {
|
} else if (type === PendantTypes.Information) {
|
||||||
const emailOptions = genOptionWithId(newPropertyItem.emailOptions);
|
const emailOptions = genOptionWithId(newPropertyItem.emailOptions);
|
||||||
|
|
||||||
@ -97,26 +111,33 @@ export const useOnCreateSure = ({ block }: { block: AsyncBlock }) => {
|
|||||||
locationOptions,
|
locationOptions,
|
||||||
} as Omit<InformationProperty, 'id'>);
|
} as Omit<InformationProperty, 'id'>);
|
||||||
|
|
||||||
await setPendant(newProperty, {
|
await setValue(
|
||||||
email: getOfficialSelected({
|
{
|
||||||
isMulti: true,
|
id: newProperty.id,
|
||||||
options: emailOptions,
|
type: newProperty.type,
|
||||||
tempOptions: newPropertyItem.emailOptions,
|
value: {
|
||||||
tempSelectedId: newValue.email,
|
email: getOfficialSelected({
|
||||||
}),
|
isMulti: true,
|
||||||
phone: getOfficialSelected({
|
options: emailOptions,
|
||||||
isMulti: true,
|
tempOptions: newPropertyItem.emailOptions,
|
||||||
options: phoneOptions,
|
tempSelectedId: newValue.email,
|
||||||
tempOptions: newPropertyItem.phoneOptions,
|
}),
|
||||||
tempSelectedId: newValue.phone,
|
phone: getOfficialSelected({
|
||||||
}),
|
isMulti: true,
|
||||||
location: getOfficialSelected({
|
options: phoneOptions,
|
||||||
isMulti: true,
|
tempOptions: newPropertyItem.phoneOptions,
|
||||||
options: locationOptions,
|
tempSelectedId: newValue.phone,
|
||||||
tempOptions: newPropertyItem.locationOptions,
|
}),
|
||||||
tempSelectedId: newValue.location,
|
location: getOfficialSelected({
|
||||||
}),
|
isMulti: true,
|
||||||
});
|
options: locationOptions,
|
||||||
|
tempOptions: newPropertyItem.locationOptions,
|
||||||
|
tempSelectedId: newValue.location,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
} as InformationValue,
|
||||||
|
recastBlock.id
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
// TODO: Color and background should use pendant config, but ui is not design now
|
// TODO: Color and background should use pendant config, but ui is not design now
|
||||||
const iconConfig = getPendantConfigByType(type);
|
const iconConfig = getPendantConfigByType(type);
|
||||||
@ -129,8 +150,14 @@ export const useOnCreateSure = ({ block }: { block: AsyncBlock }) => {
|
|||||||
color: iconConfig.color as CSSProperties['color'],
|
color: iconConfig.color as CSSProperties['color'],
|
||||||
iconName: iconConfig.iconName,
|
iconName: iconConfig.iconName,
|
||||||
});
|
});
|
||||||
|
await setValue(
|
||||||
await setPendant(newProperty, newValue);
|
{
|
||||||
|
id: newProperty.id,
|
||||||
|
type: newProperty.type,
|
||||||
|
value: newValue,
|
||||||
|
} as TextValue | DateValue,
|
||||||
|
recastBlock.id
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -144,8 +171,9 @@ export const useOnUpdateSure = ({
|
|||||||
property: RecastMetaProperty;
|
property: RecastMetaProperty;
|
||||||
}) => {
|
}) => {
|
||||||
const { updateSelect } = useSelectProperty();
|
const { updateSelect } = useSelectProperty();
|
||||||
const { setPendant } = usePendant(block);
|
|
||||||
const { updateProperty } = useRecastBlockMeta();
|
const { updateProperty } = useRecastBlockMeta();
|
||||||
|
const { setValue } = getRecastItemValue(block);
|
||||||
|
const recastBlock = useRecastBlock();
|
||||||
|
|
||||||
return async ({
|
return async ({
|
||||||
type,
|
type,
|
||||||
@ -199,7 +227,14 @@ export const useOnUpdateSure = ({
|
|||||||
tempSelectedId: newValue,
|
tempSelectedId: newValue,
|
||||||
});
|
});
|
||||||
|
|
||||||
await setPendant(selectProperty, selectedId);
|
await setValue(
|
||||||
|
{
|
||||||
|
id: selectProperty.id,
|
||||||
|
type: selectProperty.type,
|
||||||
|
value: selectedId,
|
||||||
|
} as SelectValue | MultiSelectValue | StatusValue,
|
||||||
|
recastBlock.id
|
||||||
|
);
|
||||||
} else if (type === PendantTypes.Information) {
|
} else if (type === PendantTypes.Information) {
|
||||||
// const { emailOptions, phoneOptions, locationOptions } =
|
// const { emailOptions, phoneOptions, locationOptions } =
|
||||||
// property as InformationProperty;
|
// property as InformationProperty;
|
||||||
@ -231,28 +266,42 @@ export const useOnUpdateSure = ({
|
|||||||
locationOptions,
|
locationOptions,
|
||||||
} as InformationProperty);
|
} as InformationProperty);
|
||||||
|
|
||||||
await setPendant(newProperty, {
|
await setValue(
|
||||||
email: getOfficialSelected({
|
{
|
||||||
isMulti: true,
|
id: newProperty.id,
|
||||||
options: emailOptions as SelectOption[],
|
type: newProperty.type,
|
||||||
tempOptions: newPropertyItem.emailOptions,
|
value: {
|
||||||
tempSelectedId: newValue.email,
|
email: getOfficialSelected({
|
||||||
}),
|
isMulti: true,
|
||||||
phone: getOfficialSelected({
|
options: emailOptions as SelectOption[],
|
||||||
isMulti: true,
|
tempOptions: newPropertyItem.emailOptions,
|
||||||
options: phoneOptions as SelectOption[],
|
tempSelectedId: newValue.email,
|
||||||
tempOptions: newPropertyItem.phoneOptions,
|
}),
|
||||||
tempSelectedId: newValue.phone,
|
phone: getOfficialSelected({
|
||||||
}),
|
isMulti: true,
|
||||||
location: getOfficialSelected({
|
options: phoneOptions as SelectOption[],
|
||||||
isMulti: true,
|
tempOptions: newPropertyItem.phoneOptions,
|
||||||
options: locationOptions as SelectOption[],
|
tempSelectedId: newValue.phone,
|
||||||
tempOptions: newPropertyItem.locationOptions,
|
}),
|
||||||
tempSelectedId: newValue.location,
|
location: getOfficialSelected({
|
||||||
}),
|
isMulti: true,
|
||||||
});
|
options: locationOptions as SelectOption[],
|
||||||
|
tempOptions: newPropertyItem.locationOptions,
|
||||||
|
tempSelectedId: newValue.location,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
} as InformationValue,
|
||||||
|
recastBlock.id
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
await setPendant(property, newValue);
|
await setValue(
|
||||||
|
{
|
||||||
|
id: property.id,
|
||||||
|
type: property.type,
|
||||||
|
value: newValue,
|
||||||
|
} as TextValue | DateValue,
|
||||||
|
recastBlock.id
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fieldName !== property.name) {
|
if (fieldName !== property.name) {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
MuiZoom,
|
MuiFade,
|
||||||
Popover,
|
Popover,
|
||||||
PopperHandler,
|
PopperHandler,
|
||||||
styled,
|
styled,
|
||||||
@ -100,16 +100,15 @@ export const PendantRender = ({ block }: { block: AsyncBlock }) => {
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{hasAddBtn ? (
|
{hasAddBtn ? (
|
||||||
<MuiZoom in={showAddBtn}>
|
<MuiFade in={showAddBtn}>
|
||||||
<div>
|
<div>
|
||||||
<AddPendantPopover
|
<AddPendantPopover
|
||||||
block={block}
|
block={block}
|
||||||
iconStyle={{ marginTop: 4 }}
|
iconStyle={{ marginTop: 4 }}
|
||||||
container={blockRenderContainerRef.current}
|
container={blockRenderContainerRef.current}
|
||||||
trigger="click"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</MuiZoom>
|
</MuiFade>
|
||||||
) : null}
|
) : null}
|
||||||
</BlockPendantContainer>
|
</BlockPendantContainer>
|
||||||
);
|
);
|
||||||
|
@ -1,41 +0,0 @@
|
|||||||
import { removePropertyValueRecord, setPendantHistory } from './utils';
|
|
||||||
import { AsyncBlock } from '../editor';
|
|
||||||
import {
|
|
||||||
getRecastItemValue,
|
|
||||||
RecastMetaProperty,
|
|
||||||
useRecastBlock,
|
|
||||||
} from '../recast-block';
|
|
||||||
|
|
||||||
export const usePendant = (block: AsyncBlock) => {
|
|
||||||
// const { getProperties, removeProperty } = useRecastBlockMeta();
|
|
||||||
const recastBlock = useRecastBlock();
|
|
||||||
const { getValue, setValue, removeValue } = getRecastItemValue(block);
|
|
||||||
// const { updateSelect } = useSelectProperty();
|
|
||||||
|
|
||||||
const setPendant = async (property: RecastMetaProperty, newValue: any) => {
|
|
||||||
const nv = {
|
|
||||||
id: property.id,
|
|
||||||
type: property.type,
|
|
||||||
value: newValue,
|
|
||||||
};
|
|
||||||
await setValue(nv);
|
|
||||||
setPendantHistory({
|
|
||||||
recastBlockId: recastBlock.id,
|
|
||||||
blockId: block.id,
|
|
||||||
propertyId: property.id,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const removePendant = async (property: RecastMetaProperty) => {
|
|
||||||
await removeValue(property.id);
|
|
||||||
removePropertyValueRecord({
|
|
||||||
recastBlockId: block.id,
|
|
||||||
propertyId: property.id,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
setPendant,
|
|
||||||
removePendant,
|
|
||||||
};
|
|
||||||
};
|
|
@ -1,84 +1,7 @@
|
|||||||
import {
|
import { PropertyType, SelectOption } from '../recast-block';
|
||||||
PropertyType,
|
import { OptionIdType, OptionType, PendantConfig, PendantTypes } from './types';
|
||||||
RecastBlockValue,
|
|
||||||
RecastPropertyId,
|
|
||||||
SelectOption,
|
|
||||||
} from '../recast-block';
|
|
||||||
import { OptionIdType, OptionType } from './types';
|
|
||||||
import { pendantConfig } from './config';
|
import { pendantConfig } from './config';
|
||||||
import { PendantConfig, PendantTypes } from './types';
|
import { nanoid } from 'nanoid';
|
||||||
type Props = {
|
|
||||||
recastBlockId: string;
|
|
||||||
blockId: string;
|
|
||||||
propertyId: RecastPropertyId;
|
|
||||||
};
|
|
||||||
|
|
||||||
type StorageMap = {
|
|
||||||
[recastBlockId: string]: {
|
|
||||||
[propertyId: RecastPropertyId]: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const LOCAL_STORAGE_NAME = 'TEMPORARY_PENDANT_DATA';
|
|
||||||
|
|
||||||
const ensureLocalStorage = () => {
|
|
||||||
const data = localStorage.getItem(LOCAL_STORAGE_NAME);
|
|
||||||
if (!data) {
|
|
||||||
localStorage.setItem(LOCAL_STORAGE_NAME, JSON.stringify({}));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const setPendantHistory = ({
|
|
||||||
recastBlockId,
|
|
||||||
blockId,
|
|
||||||
propertyId,
|
|
||||||
}: Props) => {
|
|
||||||
ensureLocalStorage();
|
|
||||||
const data: StorageMap = JSON.parse(
|
|
||||||
localStorage.getItem(LOCAL_STORAGE_NAME) as string
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!data[recastBlockId]) {
|
|
||||||
data[recastBlockId] = {};
|
|
||||||
}
|
|
||||||
const propertyValueRecord = data[recastBlockId];
|
|
||||||
propertyValueRecord[propertyId] = blockId;
|
|
||||||
|
|
||||||
localStorage.setItem(LOCAL_STORAGE_NAME, JSON.stringify(data));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getPendantHistory = ({
|
|
||||||
recastBlockId,
|
|
||||||
}: {
|
|
||||||
recastBlockId: string;
|
|
||||||
}) => {
|
|
||||||
ensureLocalStorage();
|
|
||||||
const data: StorageMap = JSON.parse(
|
|
||||||
localStorage.getItem(LOCAL_STORAGE_NAME) as string
|
|
||||||
);
|
|
||||||
|
|
||||||
return data[recastBlockId] ?? {};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const removePropertyValueRecord = ({
|
|
||||||
recastBlockId,
|
|
||||||
propertyId,
|
|
||||||
}: {
|
|
||||||
recastBlockId: string;
|
|
||||||
propertyId: RecastPropertyId;
|
|
||||||
}) => {
|
|
||||||
ensureLocalStorage();
|
|
||||||
const data: StorageMap = JSON.parse(
|
|
||||||
localStorage.getItem(LOCAL_STORAGE_NAME) as string
|
|
||||||
);
|
|
||||||
if (!data[recastBlockId]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
delete data[recastBlockId][propertyId];
|
|
||||||
|
|
||||||
localStorage.setItem(LOCAL_STORAGE_NAME, JSON.stringify(data));
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* In select pendant panel, use mock options instead of use `createSelect` when add or delete option
|
* In select pendant panel, use mock options instead of use `createSelect` when add or delete option
|
||||||
@ -107,7 +30,7 @@ export const getOfficialSelected = ({
|
|||||||
.map(id => {
|
.map(id => {
|
||||||
return tempOptions.findIndex((o: OptionType) => o.id === id);
|
return tempOptions.findIndex((o: OptionType) => o.id === id);
|
||||||
})
|
})
|
||||||
.filter(index => index != -1);
|
.filter(index => index !== -1);
|
||||||
selectedId = selectedIndex.map((index: number) => {
|
selectedId = selectedIndex.map((index: number) => {
|
||||||
return options[index].id;
|
return options[index].id;
|
||||||
});
|
});
|
||||||
@ -130,7 +53,7 @@ export const getPendantIconsConfigByName = (
|
|||||||
return pendantConfig[pendantName];
|
return pendantConfig[pendantName];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const genBasicOption = ({
|
export const generateBasicOption = ({
|
||||||
index,
|
index,
|
||||||
iconConfig,
|
iconConfig,
|
||||||
name = '',
|
name = '',
|
||||||
@ -159,22 +82,22 @@ export const genBasicOption = ({
|
|||||||
/**
|
/**
|
||||||
* Status Pendant is a Select Pendant built-in some options
|
* Status Pendant is a Select Pendant built-in some options
|
||||||
* **/
|
* **/
|
||||||
export const genInitialOptions = (
|
export const generateInitialOptions = (
|
||||||
type: PendantTypes,
|
type: PendantTypes,
|
||||||
iconConfig: PendantConfig
|
iconConfig: PendantConfig
|
||||||
) => {
|
) => {
|
||||||
if (type === PendantTypes.Status) {
|
if (type === PendantTypes.Status) {
|
||||||
return [
|
return [
|
||||||
genBasicOption({ index: 0, iconConfig, name: 'No Started' }),
|
generateBasicOption({ index: 0, iconConfig, name: 'No Started' }),
|
||||||
genBasicOption({
|
generateBasicOption({
|
||||||
index: 1,
|
index: 1,
|
||||||
iconConfig,
|
iconConfig,
|
||||||
name: 'In Progress',
|
name: 'In Progress',
|
||||||
}),
|
}),
|
||||||
genBasicOption({ index: 2, iconConfig, name: 'Complete' }),
|
generateBasicOption({ index: 2, iconConfig, name: 'Complete' }),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
return [genBasicOption({ index: 0, iconConfig })];
|
return [generateBasicOption({ index: 0, iconConfig })];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const checkPendantForm = (
|
export const checkPendantForm = (
|
||||||
@ -222,3 +145,10 @@ export const checkPendantForm = (
|
|||||||
|
|
||||||
return { passed: true, message: 'Check passed !' };
|
return { passed: true, message: 'Check passed !' };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const upperFirst = (str: string) => {
|
||||||
|
return `${str[0].toUpperCase()}${str.slice(1)}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const generateRandomFieldName = (type: PendantTypes) =>
|
||||||
|
upperFirst(`${type}#${nanoid(4)}`);
|
||||||
|
@ -1,28 +0,0 @@
|
|||||||
import { AsyncBlock, BlockEditor } from '../editor';
|
|
||||||
import { ReactElement } from 'react';
|
|
||||||
|
|
||||||
interface DragDropWrapperProps {
|
|
||||||
editor: BlockEditor;
|
|
||||||
block: AsyncBlock;
|
|
||||||
children: ReactElement | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function DragDropWrapper({
|
|
||||||
children,
|
|
||||||
editor,
|
|
||||||
block,
|
|
||||||
}: DragDropWrapperProps) {
|
|
||||||
const handlerDragOver: React.DragEventHandler<HTMLDivElement> = event => {
|
|
||||||
event.preventDefault();
|
|
||||||
if (block.dom) {
|
|
||||||
editor.getHooks().afterOnNodeDragOver(event, {
|
|
||||||
blockId: block.id,
|
|
||||||
dom: block.dom,
|
|
||||||
rect: block.dom?.getBoundingClientRect(),
|
|
||||||
type: block.type,
|
|
||||||
properties: block.getProperties(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return <div onDragOver={handlerDragOver}>{children}</div>;
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
export * from './DragDropWrapper';
|
|
@ -159,6 +159,33 @@ export class BlockCommands {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async moveInNewGridItem(
|
||||||
|
blockId: string,
|
||||||
|
gridItemId: string,
|
||||||
|
isBefore = false
|
||||||
|
) {
|
||||||
|
const block = await this._editor.getBlockById(blockId);
|
||||||
|
if (block) {
|
||||||
|
const gridItemBlock = await this._editor.createBlock(
|
||||||
|
Protocol.Block.Type.gridItem
|
||||||
|
);
|
||||||
|
const targetGridItemBlock = await this._editor.getBlockById(
|
||||||
|
gridItemId
|
||||||
|
);
|
||||||
|
await block.remove();
|
||||||
|
await gridItemBlock.append(block);
|
||||||
|
if (targetGridItemBlock && gridItemBlock) {
|
||||||
|
if (isBefore) {
|
||||||
|
await targetGridItemBlock.before(gridItemBlock);
|
||||||
|
} else {
|
||||||
|
await targetGridItemBlock.after(gridItemBlock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return gridItemBlock;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
public async splitGroupFromBlock(blockId: string) {
|
public async splitGroupFromBlock(blockId: string) {
|
||||||
const block = await this._editor.getBlockById(blockId);
|
const block = await this._editor.getBlockById(blockId);
|
||||||
await splitGroup(this._editor, block);
|
await splitGroup(this._editor, block);
|
||||||
|
27
libs/components/editor-core/src/editor/config/grid.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { BlockEditor } from '../..';
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* the global config for the editor
|
||||||
|
* @class GridConfig
|
||||||
|
*/
|
||||||
|
export class GridConfig {
|
||||||
|
private _maxGridItemCount = 6;
|
||||||
|
private _editor: BlockEditor;
|
||||||
|
|
||||||
|
constructor(editor: BlockEditor) {
|
||||||
|
this._editor = editor;
|
||||||
|
}
|
||||||
|
|
||||||
|
get maxGridItemCount() {
|
||||||
|
return this._maxGridItemCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
set maxGridItemCount(value) {
|
||||||
|
this._maxGridItemCount = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
get gridItemMinWidth() {
|
||||||
|
return 100 / this.maxGridItemCount;
|
||||||
|
}
|
||||||
|
}
|
23
libs/components/editor-core/src/editor/config/index.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { BlockEditor } from '../..';
|
||||||
|
import { GridConfig } from './grid';
|
||||||
|
|
||||||
|
// TODO: if config be complex, add children config abstract
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* the global config for the editor
|
||||||
|
* @class EditorConfig
|
||||||
|
*/
|
||||||
|
export class EditorConfig {
|
||||||
|
private _maxGridItemCount = 6;
|
||||||
|
private _editor: BlockEditor;
|
||||||
|
private _grid: GridConfig;
|
||||||
|
|
||||||
|
constructor(editor: BlockEditor) {
|
||||||
|
this._editor = editor;
|
||||||
|
this._grid = new GridConfig(editor);
|
||||||
|
}
|
||||||
|
|
||||||
|
get grid() {
|
||||||
|
return this._grid;
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable max-lines */
|
||||||
import { domToRect, Point } from '@toeverything/utils';
|
import { domToRect, Point } from '@toeverything/utils';
|
||||||
import { AsyncBlock } from '../..';
|
import { AsyncBlock } from '../..';
|
||||||
import { GridDropType } from '../commands/types';
|
import { GridDropType } from '../commands/types';
|
||||||
@ -5,6 +6,7 @@ import { Editor } from '../editor';
|
|||||||
import { BlockDropPlacement, GroupDirection } from '../types';
|
import { BlockDropPlacement, GroupDirection } from '../types';
|
||||||
// TODO: Evaluate implementing custom events with Rxjs
|
// TODO: Evaluate implementing custom events with Rxjs
|
||||||
import EventEmitter from 'eventemitter3';
|
import EventEmitter from 'eventemitter3';
|
||||||
|
import { Protocol } from '@toeverything/datasource/db-service';
|
||||||
|
|
||||||
enum DragType {
|
enum DragType {
|
||||||
dragBlock = 'dragBlock',
|
dragBlock = 'dragBlock',
|
||||||
@ -86,6 +88,7 @@ export class DragDropManager {
|
|||||||
while (curr !== this._editor.getRootBlockId()) {
|
while (curr !== this._editor.getRootBlockId()) {
|
||||||
if (curr === blockId) return false;
|
if (curr === blockId) return false;
|
||||||
const block = await this._editor.getBlockById(curr);
|
const block = await this._editor.getBlockById(curr);
|
||||||
|
if (!block) return false;
|
||||||
curr = block.parentId;
|
curr = block.parentId;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@ -114,6 +117,48 @@ export class DragDropManager {
|
|||||||
: GridDropType.right
|
: GridDropType.right
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
[
|
||||||
|
BlockDropPlacement.outerLeft,
|
||||||
|
BlockDropPlacement.outerRight,
|
||||||
|
].includes(this._blockDragDirection)
|
||||||
|
) {
|
||||||
|
const targetBlock = await this._editor.getBlockById(
|
||||||
|
this._blockDragTargetId
|
||||||
|
);
|
||||||
|
if (targetBlock.type !== Protocol.Block.Type.grid) {
|
||||||
|
await this._editor.commands.blockCommands.createLayoutBlock(
|
||||||
|
blockId,
|
||||||
|
this._blockDragTargetId,
|
||||||
|
this._blockDragDirection ===
|
||||||
|
BlockDropPlacement.outerLeft
|
||||||
|
? GridDropType.left
|
||||||
|
: GridDropType.right
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (targetBlock.type === Protocol.Block.Type.grid) {
|
||||||
|
const gridItems = await targetBlock.children();
|
||||||
|
if (
|
||||||
|
BlockDropPlacement.outerRight ===
|
||||||
|
this._blockDragDirection
|
||||||
|
) {
|
||||||
|
await this._editor.commands.blockCommands.moveInNewGridItem(
|
||||||
|
blockId,
|
||||||
|
gridItems[gridItems.length - 1].id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
BlockDropPlacement.outerLeft ===
|
||||||
|
this._blockDragDirection
|
||||||
|
) {
|
||||||
|
await this._editor.commands.blockCommands.moveInNewGridItem(
|
||||||
|
blockId,
|
||||||
|
gridItems[0].id,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -209,6 +254,93 @@ export class DragDropManager {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* check if drag block is out of blocks and return direction
|
||||||
|
* @param {React.DragEvent<Element>} event
|
||||||
|
* @return {
|
||||||
|
* direction: BlockDropPlacement.none, // none, outerLeft, outerRight
|
||||||
|
* block: undefined, // the block in the same clientY
|
||||||
|
* isOuter: false, // if is drag over outer
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* @memberof DragDropManager
|
||||||
|
*/
|
||||||
|
public async checkOuterBlockDragTypes(event: React.DragEvent<Element>) {
|
||||||
|
const { clientX, clientY } = event;
|
||||||
|
const mousePoint = new Point(clientX, clientY);
|
||||||
|
const rootBlock = await this._editor.getBlockById(
|
||||||
|
this._editor.getRootBlockId()
|
||||||
|
);
|
||||||
|
let direction = BlockDropPlacement.none;
|
||||||
|
const rootBlockRect = domToRect(rootBlock.dom);
|
||||||
|
let targetBlock: AsyncBlock | undefined;
|
||||||
|
let typesInfo = {
|
||||||
|
direction: BlockDropPlacement.none,
|
||||||
|
block: undefined,
|
||||||
|
isOuter: false,
|
||||||
|
} as {
|
||||||
|
direction: BlockDropPlacement;
|
||||||
|
block: AsyncBlock | undefined;
|
||||||
|
isOuter: boolean;
|
||||||
|
};
|
||||||
|
if (rootBlockRect.isPointLeft(mousePoint)) {
|
||||||
|
direction = BlockDropPlacement.outerLeft;
|
||||||
|
typesInfo.isOuter = true;
|
||||||
|
}
|
||||||
|
if (rootBlockRect.isPointRight(mousePoint)) {
|
||||||
|
direction = BlockDropPlacement.outerRight;
|
||||||
|
typesInfo.isOuter = true;
|
||||||
|
}
|
||||||
|
if (direction !== BlockDropPlacement.none) {
|
||||||
|
const blockList = await this._editor.getBlockListByLevelOrder();
|
||||||
|
targetBlock = blockList.find(block => {
|
||||||
|
const domRect = domToRect(block.dom);
|
||||||
|
const pointChecker =
|
||||||
|
direction === BlockDropPlacement.outerLeft
|
||||||
|
? domRect.isPointLeft.bind(domRect)
|
||||||
|
: domRect.isPointRight.bind(domRect);
|
||||||
|
return (
|
||||||
|
block.type !== Protocol.Block.Type.page &&
|
||||||
|
block.type !== Protocol.Block.Type.group &&
|
||||||
|
pointChecker(mousePoint)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
if (targetBlock) {
|
||||||
|
if (targetBlock.type !== Protocol.Block.Type.grid) {
|
||||||
|
this._setBlockDragDirection(direction);
|
||||||
|
this._setBlockDragTargetId(targetBlock.id);
|
||||||
|
typesInfo = {
|
||||||
|
direction,
|
||||||
|
block: targetBlock,
|
||||||
|
isOuter: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (targetBlock.type === Protocol.Block.Type.grid) {
|
||||||
|
const children = await targetBlock.children();
|
||||||
|
if (
|
||||||
|
children.length <
|
||||||
|
this._editor.configManager.grid.maxGridItemCount
|
||||||
|
) {
|
||||||
|
typesInfo = {
|
||||||
|
direction,
|
||||||
|
block: targetBlock,
|
||||||
|
isOuter: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
typesInfo.direction !== BlockDropPlacement.none &&
|
||||||
|
typesInfo.block
|
||||||
|
) {
|
||||||
|
this._setBlockDragTargetId(targetBlock.id);
|
||||||
|
}
|
||||||
|
this._setBlockDragDirection(typesInfo.direction);
|
||||||
|
return typesInfo;
|
||||||
|
}
|
||||||
|
|
||||||
public async checkBlockDragTypes(
|
public async checkBlockDragTypes(
|
||||||
event: React.DragEvent<Element>,
|
event: React.DragEvent<Element>,
|
||||||
blockDom: HTMLElement,
|
blockDom: HTMLElement,
|
||||||
@ -216,10 +348,25 @@ export class DragDropManager {
|
|||||||
) {
|
) {
|
||||||
const { clientX, clientY } = event;
|
const { clientX, clientY } = event;
|
||||||
this._setBlockDragTargetId(blockId);
|
this._setBlockDragTargetId(blockId);
|
||||||
|
const path = await this._editor.getBlockPath(blockId);
|
||||||
const mousePoint = new Point(clientX, clientY);
|
const mousePoint = new Point(clientX, clientY);
|
||||||
const rect = domToRect(blockDom);
|
const rect = domToRect(blockDom);
|
||||||
|
/**
|
||||||
|
* IMP: compute the level of the target block
|
||||||
|
* future feature drag drop has level support do not delete
|
||||||
|
* const levelUnderGrid = Array.from(path)
|
||||||
|
.reverse()
|
||||||
|
.findIndex(block => block.type === Protocol.Block.Type.gridItem);
|
||||||
|
const levelUnderGroup = Array.from(path)
|
||||||
|
.reverse()
|
||||||
|
.findIndex(block => block.type === Protocol.Block.Type.group);
|
||||||
|
const blockLevel =
|
||||||
|
levelUnderGrid > 0 ? levelUnderGrid : levelUnderGroup;
|
||||||
|
console.log({ blockLevel, levelUnderGrid, levelUnderGroup });
|
||||||
|
*
|
||||||
|
*/
|
||||||
let direction = BlockDropPlacement.bottom;
|
let direction = BlockDropPlacement.bottom;
|
||||||
|
|
||||||
if (mousePoint.x - rect.left <= this._dragBlockHotDistance) {
|
if (mousePoint.x - rect.left <= this._dragBlockHotDistance) {
|
||||||
direction = BlockDropPlacement.left;
|
direction = BlockDropPlacement.left;
|
||||||
}
|
}
|
||||||
@ -236,9 +383,10 @@ export class DragDropManager {
|
|||||||
direction === BlockDropPlacement.left ||
|
direction === BlockDropPlacement.left ||
|
||||||
direction === BlockDropPlacement.right
|
direction === BlockDropPlacement.right
|
||||||
) {
|
) {
|
||||||
const path = await this._editor.getBlockPath(blockId);
|
const gridBlocks = path.filter(
|
||||||
const gridBlocks = path.filter(block => block.type === 'grid');
|
block => block.type === Protocol.Block.Type.grid
|
||||||
// limit grid block floor counts
|
);
|
||||||
|
// limit grid block floor counts, when drag block to init grid
|
||||||
if (gridBlocks.length >= MAX_GRID_BLOCK_FLOOR) {
|
if (gridBlocks.length >= MAX_GRID_BLOCK_FLOOR) {
|
||||||
direction = BlockDropPlacement.none;
|
direction = BlockDropPlacement.none;
|
||||||
}
|
}
|
||||||
|