diff --git a/.github/deployment/Caddyfile-affine b/.github/deployment/Caddyfile-affine new file mode 100644 index 0000000000..b6db6009e9 --- /dev/null +++ b/.github/deployment/Caddyfile-affine @@ -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 + } +} diff --git a/Caddyfile b/.github/deployment/Caddyfile-lisa similarity index 100% rename from Caddyfile rename to .github/deployment/Caddyfile-lisa diff --git a/Caddyfile-venus b/.github/deployment/Caddyfile-venus similarity index 100% rename from Caddyfile-venus rename to .github/deployment/Caddyfile-venus diff --git a/.github/deployment/Dockerfile-affine b/.github/deployment/Dockerfile-affine new file mode 100644 index 0000000000..46a46233f2 --- /dev/null +++ b/.github/deployment/Dockerfile-affine @@ -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"] \ No newline at end of file diff --git a/Dockerfile-keck b/.github/deployment/Dockerfile-keck similarity index 93% rename from Dockerfile-keck rename to .github/deployment/Dockerfile-keck index 598fded0ab..7918e49427 100644 --- a/Dockerfile-keck +++ b/.github/deployment/Dockerfile-keck @@ -1,7 +1,7 @@ FROM node:16-alpine as builder WORKDIR /app 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 FROM node:16-alpine as node_modules diff --git a/Dockerfile b/.github/deployment/Dockerfile-lisa similarity index 78% rename from Dockerfile rename to .github/deployment/Dockerfile-lisa index f6e2249faf..471e4d0d48 100644 --- a/Dockerfile +++ b/.github/deployment/Dockerfile-lisa @@ -1,13 +1,13 @@ FROM node:16-alpine as builder WORKDIR /app 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 FROM node:16-alpine as relocate WORKDIR /app 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 # ============= diff --git a/Dockerfile-venus b/.github/deployment/Dockerfile-venus similarity index 78% rename from Dockerfile-venus rename to .github/deployment/Dockerfile-venus index 55ece303c6..585ade8a20 100644 --- a/Dockerfile-venus +++ b/.github/deployment/Dockerfile-venus @@ -1,13 +1,13 @@ FROM node:16-alpine as builder WORKDIR /app 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 FROM node:16-alpine as relocate WORKDIR /app 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 # ============= diff --git a/.github/env/.env.e2e b/.github/env/.env.e2e new file mode 100644 index 0000000000..0c627a684c --- /dev/null +++ b/.github/env/.env.e2e @@ -0,0 +1,2 @@ +NX_LOCAL=true +NX_E2E=true \ No newline at end of file diff --git a/.env.local-dev b/.github/env/.env.local-dev similarity index 100% rename from .env.local-dev rename to .github/env/.env.local-dev diff --git a/.github/workflows/affine.yml b/.github/workflows/affine.yml new file mode 100644 index 0000000000..a5c0eafe70 --- /dev/null +++ b/.github/workflows/affine.yml @@ -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 diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml new file mode 100644 index 0000000000..a49efb4d99 --- /dev/null +++ b/.github/workflows/check.yml @@ -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 diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000000..75ff3a8010 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -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 diff --git a/.github/workflows/keck.yml b/.github/workflows/keck.yml index d7b915c100..32e1d56058 100644 --- a/.github/workflows/keck.yml +++ b/.github/workflows/keck.yml @@ -6,12 +6,14 @@ on: branches: [master] paths: - 'apps/keck/**' + - '.github/deployment' - '.github/workflows/keck.yml' - pull_request: - branches: [master] - paths: - - 'apps/keck/**' - - '.github/workflows/keck.yml' + # pull_request: + # branches: [master] + # paths: + # - 'apps/keck/**' + # - '.github/deployment' + # - '.github/workflows/keck.yml' # Cancels all previous workflow runs for pull requests that have not completed. # See https://docs.github.com/en/actions/using-jobs/using-concurrency @@ -60,7 +62,7 @@ jobs: uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc with: context: . - file: ./Dockerfile-keck + file: ./.github/deployment/Dockerfile-keck push: ${{ github.ref == 'refs/heads/field' && true || false }} tags: ${{ steps.meta_keck.outputs.tags }} labels: ${{ steps.meta_keck.outputs.labels }} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 8266d460df..8efce5cbd4 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -3,8 +3,8 @@ name: Lint on: push: branches: [master] - pull_request: - 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 diff --git a/.github/workflows/lisa.yml b/.github/workflows/lisa.yml index 878fad6274..f548ddeeed 100644 --- a/.github/workflows/lisa.yml +++ b/.github/workflows/lisa.yml @@ -3,8 +3,8 @@ name: Build Lisa on: push: branches: [master] - pull_request: - 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 @@ -53,6 +53,7 @@ jobs: uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc with: context: . + file: ./.github/deployment/Dockerfile-lisa push: ${{ github.ref == 'refs/heads/master' && true || false }} tags: ${{ steps.meta_lisa.outputs.tags }} labels: ${{ steps.meta_lisa.outputs.labels }} diff --git a/.github/workflows/nx-cloud-main.yml b/.github/workflows/nx-cloud-main.yml new file mode 100644 index 0000000000..31fca0fc67 --- /dev/null +++ b/.github/workflows/nx-cloud-main.yml @@ -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 \ No newline at end of file diff --git a/.github/workflows/venus.yml b/.github/workflows/venus.yml index 59bf758cdc..699cf44dd3 100644 --- a/.github/workflows/venus.yml +++ b/.github/workflows/venus.yml @@ -5,12 +5,13 @@ on: branches: [master] paths: - 'apps/venus/**' + - '.github/deployment' - '.github/workflows/venus.yml' - pull_request: - branches: [master] - paths: - - 'apps/venus/**' - - '.github/workflows/venus.yml' + # pull_request: + # branches: [master] + # paths: + # - 'apps/venus/**' + # - '.github/workflows/venus.yml' # Cancels all previous workflow runs for pull requests that have not completed. # See https://docs.github.com/en/actions/using-jobs/using-concurrency @@ -59,7 +60,7 @@ jobs: uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc with: context: . - file: ./Dockerfile-venus + file: ./.github/deployment/Dockerfile-venus push: ${{ github.ref == 'refs/heads/master' && true || false }} tags: ${{ steps.meta_venus.outputs.tags }} labels: ${{ steps.meta_venus.outputs.labels }} diff --git a/README.md b/README.md index 042802d1de..666d76fabc 100644 --- a/README.md +++ b/README.md @@ -40,13 +40,20 @@ See https://github.com/all-?/all-contributors/issues/361#issuecomment-637166066

affine_screen

-# 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) +# 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 -- [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) - [Shape your page](#shape-your-page) - [Plan your task](#plan-your-task) @@ -84,11 +91,11 @@ Affine is fully built with web technologies so that consistency and accessibilit # 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 -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 @@ -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: - 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. - 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. - Atomic - The basic element of affine are blocks, not pages. - - Blocks can be directly reuse and synced between pages. - - Pages and blocks are searched and organized on the basis of connected graphs, not tree-like paths. + - Blocks can be directly reused and synced between pages. + - Pages and blocks are searched and organized based on connected graphs, not tree-like paths. - Dual-link and semantic search are fully supported. - Collaborative and privacy-first - Data is always stored locally by default - 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. 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 -- Taskade & Monday -- brillant multi-demensional tables +- Taskade & Monday -- brilliant multi-dimensional tables - Height & Linear -- beautiful task management tool 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. - [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. - [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. - [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. diff --git a/apps/keck/package.json b/apps/keck/package.json index d0cc69ceed..d960ea7694 100644 --- a/apps/keck/package.json +++ b/apps/keck/package.json @@ -5,15 +5,15 @@ "author": "DarkSky ", "main": "jest.config.ts", "dependencies": { - "authing-js-sdk": "^4.23.33", - "firebase-admin": "^11.0.0", - "lib0": "^0.2.51", - "lru-cache": "^7.13.0", + "authing-js-sdk": "^4.23.35", + "firebase-admin": "^11.0.1", + "lib0": "^0.2.52", + "lru-cache": "^7.13.2", "nanoid": "^4.0.0", "readable-stream": "^4.1.0", - "ws": "^8.8.0", + "ws": "^8.8.1", "y-protocols": "^1.0.5", - "yjs": "^13.5.39" + "yjs": "^13.5.41" }, "devDependencies": { "@types/readable-stream": "^2.3.13", diff --git a/apps/keck/src/index.ts b/apps/keck/src/index.ts index d76df31b53..31dadb7893 100644 --- a/apps/keck/src/index.ts +++ b/apps/keck/src/index.ts @@ -34,34 +34,31 @@ const _checkAuth = async ( response: http.ServerResponse, 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') { - const url = new URL(request.url, `http://${request.headers.host}`); - const workspace = _getWorkspace(url.pathname); if (workspace) return callback(response, workspace); 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 PORT = process.env.PORT || 3000; const _tokens = new LRUCache({ - max: 10240, + max: 1024 * 10, ttl: 1000 * 60 * 5, }); diff --git a/apps/ligo-virgo-e2e/.eslintrc.json b/apps/ligo-virgo-e2e/.eslintrc.json new file mode 100644 index 0000000000..84a4be82b5 --- /dev/null +++ b/apps/ligo-virgo-e2e/.eslintrc.json @@ -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" + } + } + ] +} diff --git a/apps/ligo-virgo-e2e/cypress.config.ts b/apps/ligo-virgo-e2e/cypress.config.ts new file mode 100644 index 0000000000..46bcce8903 --- /dev/null +++ b/apps/ligo-virgo-e2e/cypress.config.ts @@ -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, +}); diff --git a/apps/ligo-virgo-e2e/project.json b/apps/ligo-virgo-e2e/project.json new file mode 100644 index 0000000000..3ddcf56e1c --- /dev/null +++ b/apps/ligo-virgo-e2e/project.json @@ -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"] +} diff --git a/apps/ligo-virgo-e2e/src/fixtures/example.json b/apps/ligo-virgo-e2e/src/fixtures/example.json new file mode 100644 index 0000000000..b132a94fea --- /dev/null +++ b/apps/ligo-virgo-e2e/src/fixtures/example.json @@ -0,0 +1,4 @@ +{ + "name": "Using fixtures to represent data", + "email": "hello@cypress.io" +} diff --git a/apps/ligo-virgo-e2e/src/integration/app.spec.ts b/apps/ligo-virgo-e2e/src/integration/app.spec.ts new file mode 100644 index 0000000000..651bf215f2 --- /dev/null +++ b/apps/ligo-virgo-e2e/src/integration/app.spec.ts @@ -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'); + }); +}); diff --git a/apps/ligo-virgo-e2e/src/support/app.po.ts b/apps/ligo-virgo-e2e/src/support/app.po.ts new file mode 100644 index 0000000000..4362b3e26b --- /dev/null +++ b/apps/ligo-virgo-e2e/src/support/app.po.ts @@ -0,0 +1,3 @@ +export const getTitle = () => cy.get('span[title]'); +export const getDoc = () => cy.contains('Doc'); +export const getBoard = () => cy.contains('Board'); diff --git a/apps/ligo-virgo-e2e/src/support/commands.ts b/apps/ligo-virgo-e2e/src/support/commands.ts new file mode 100644 index 0000000000..4200179bac --- /dev/null +++ b/apps/ligo-virgo-e2e/src/support/commands.ts @@ -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 { + 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) => { ... }) diff --git a/apps/ligo-virgo-e2e/src/support/index.ts b/apps/ligo-virgo-e2e/src/support/index.ts new file mode 100644 index 0000000000..3d469a6b6c --- /dev/null +++ b/apps/ligo-virgo-e2e/src/support/index.ts @@ -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'; diff --git a/apps/ligo-virgo-e2e/tsconfig.json b/apps/ligo-virgo-e2e/tsconfig.json new file mode 100644 index 0000000000..e20e2ba6e4 --- /dev/null +++ b/apps/ligo-virgo-e2e/tsconfig.json @@ -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"] +} diff --git a/apps/ligo-virgo/package.json b/apps/ligo-virgo/package.json index 54c68924c5..5bb0d78eaa 100644 --- a/apps/ligo-virgo/package.json +++ b/apps/ligo-virgo/package.json @@ -13,8 +13,8 @@ "@mui/icons-material": "^5.8.4" }, "devDependencies": { - "firebase": "^9.8.4", + "firebase": "^9.9.2", "mini-css-extract-plugin": "^2.6.1", - "webpack": "^5.73.0" + "webpack": "^5.74.0" } } diff --git a/apps/ligo-virgo/project.json b/apps/ligo-virgo/project.json index ad3444c4a4..8412a6e16a 100644 --- a/apps/ligo-virgo/project.json +++ b/apps/ligo-virgo/project.json @@ -48,7 +48,8 @@ "configurations": { "production": { "buildTarget": "ligo-virgo:build:production", - "hmr": false + "hmr": false, + "open": false } } }, diff --git a/apps/ligo-virgo/src/index.html b/apps/ligo-virgo/src/index.html index 94f01b6db7..2d1ed8fb85 100644 --- a/apps/ligo-virgo/src/index.html +++ b/apps/ligo-virgo/src/index.html @@ -4,8 +4,8 @@ - - AFFiNE | Local Dev Environment + + AFFiNE - All In One Workos diff --git a/apps/ligo-virgo/src/pages/workspace/Home.tsx b/apps/ligo-virgo/src/pages/workspace/Home.tsx index c8ce89b6d4..a424a2052d 100644 --- a/apps/ligo-virgo/src/pages/workspace/Home.tsx +++ b/apps/ligo-virgo/src/pages/workspace/Home.tsx @@ -10,16 +10,13 @@ export function WorkspaceHome() { useEffect(() => { const navigate_to_user_initial_page = async () => { - const recent_pages = await services.api.userConfig.getRecentPages( - workspace_id, - user.id - ); - - const user_initial_page_id = - await services.api.userConfig.getUserInitialPage( + const [recent_pages, user_initial_page_id] = await Promise.all([ + services.api.userConfig.getRecentPages(workspace_id, user.id), + services.api.userConfig.getUserInitialPage( workspace_id, user.id - ); + ), + ]); if (recent_pages.length === 0) { await services.api.editorBlock.copyTemplateToPage( workspace_id, diff --git a/apps/ligo-virgo/src/pages/workspace/docs/Page.tsx b/apps/ligo-virgo/src/pages/workspace/docs/Page.tsx index 556ca606a7..76c48efeb6 100644 --- a/apps/ligo-virgo/src/pages/workspace/docs/Page.tsx +++ b/apps/ligo-virgo/src/pages/workspace/docs/Page.tsx @@ -1,16 +1,9 @@ /* eslint-disable filename-rules/match */ -import { - useEffect, - useRef, - type UIEvent, - useState, - useLayoutEffect, -} from 'react'; +import { useEffect, useRef, type UIEvent, useState } from 'react'; import { useParams } from 'react-router'; import { MuiBox as Box, MuiCircularProgress as CircularProgress, - MuiDivider as Divider, styled, } from '@toeverything/components/ui'; import { AffineEditor } from '@toeverything/components/affine-editor'; @@ -40,30 +33,8 @@ export function Page(props: PageProps) { const { page_id } = useParams(); const { showSpaceSidebar, fixedDisplay, setSpaceSidebarVisible } = useShowSpaceSidebar(); - const { user } = useUserAndSpaces(); 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 ( @@ -94,14 +65,14 @@ export function Page(props: PageProps) { )}

- + {page_id ? : null}
@@ -122,38 +93,29 @@ const EditorContainer = ({ workspace: string; }) => { const [lockScroll, setLockScroll] = useState(false); - const scrollContainerRef = useRef(); + const [scrollContainer, setScrollContainer] = useState(); const editorRef = useRef(); + const onScroll = (event: UIEvent) => { editorRef.current.getHooks().onRootNodeScroll(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(); useEffect(() => { - if (scrollContainerRef.current) { + if (scrollContainer) { const obv = new ResizeObserver(e => { setPageClientWidth(e[0].contentRect.width); }); - obv.observe(scrollContainerRef.current); + obv.observe(scrollContainer); return () => obv.disconnect(); } - }); + }, [setPageClientWidth, scrollContainer]); return ( setScrollContainer(ref)} onScroll={onScroll} > {pageId ? ( @@ -161,6 +123,11 @@ const EditorContainer = ({ workspace={workspace} rootBlockId={pageId} ref={editorRef} + scrollContainer={scrollContainer} + scrollController={{ + lockScroll: () => setLockScroll(true), + unLockScroll: () => setLockScroll(false), + }} /> ) : ( {children ? ( diff --git a/apps/ligo-virgo/src/pages/workspace/docs/components/logo/Logo.tsx b/apps/ligo-virgo/src/pages/workspace/docs/components/logo/Logo.tsx index 8b33a276c4..d4926a57e5 100644 --- a/apps/ligo-virgo/src/pages/workspace/docs/components/logo/Logo.tsx +++ b/apps/ligo-virgo/src/pages/workspace/docs/components/logo/Logo.tsx @@ -9,8 +9,8 @@ export const Logo = ({ color, style, ...props }) => { > diff --git a/apps/ligo-virgo/src/pages/workspace/docs/components/tabs/Tabs.tsx b/apps/ligo-virgo/src/pages/workspace/docs/components/tabs/Tabs.tsx index 3722c5e162..fc06d826bc 100644 --- a/apps/ligo-virgo/src/pages/workspace/docs/components/tabs/Tabs.tsx +++ b/apps/ligo-virgo/src/pages/workspace/docs/components/tabs/Tabs.tsx @@ -11,15 +11,15 @@ const StyledTabs = styled('div')({ cursor: 'pointer', }); -const StyledDivider = styled(Divider)<{ isActive?: boolean }>( - ({ isActive }) => { - return { - flex: 1, - backgroundColor: isActive ? '#3E6FDB' : '#ECF1FB', - borderWidth: '2px', - }; - } -); +const StyledDivider = styled(Divider, { + shouldForwardProp: (prop: string) => !['isActive'].includes(prop), +})<{ isActive?: boolean }>(({ isActive }) => { + return { + flex: 1, + backgroundColor: isActive ? '#3E6FDB' : '#ECF1FB', + borderWidth: '2px', + }; +}); const TAB_TITLE = { PAGES: 'pages', diff --git a/apps/ligo-virgo/src/pages/workspace/docs/workspace-name.tsx b/apps/ligo-virgo/src/pages/workspace/docs/workspace-name.tsx index cf6f3a0313..6311497161 100644 --- a/apps/ligo-virgo/src/pages/workspace/docs/workspace-name.tsx +++ b/apps/ligo-virgo/src/pages/workspace/docs/workspace-name.tsx @@ -95,15 +95,11 @@ export const WorkspaceName = () => { if (!currentSpaceId) { return; } - - const name = await services.api.userConfig.getWorkspaceName( - currentSpaceId - ); + const [name, workspaceId] = await Promise.all([ + services.api.userConfig.getWorkspaceName(currentSpaceId), + services.api.userConfig.getWorkspaceId(currentSpaceId), + ]); setWorkspaceName(name); - - const workspaceId = await services.api.userConfig.getWorkspaceId( - currentSpaceId - ); setWorkspaceId(workspaceId); }, [currentSpaceId]); diff --git a/apps/ligo-virgo/webpack.config.js b/apps/ligo-virgo/webpack.config.js index edb5305b2b..3903284128 100644 --- a/apps/ligo-virgo/webpack.config.js +++ b/apps/ligo-virgo/webpack.config.js @@ -16,6 +16,7 @@ module.exports = function (webpackConfig) { const config = getNxWebpackConfig(webpackConfig); const isProd = config.mode === 'production'; + const isE2E = process.env.NX_E2E; const style9 = { test: /\.(tsx|ts|js|mjs|jsx)$/, @@ -34,11 +35,6 @@ module.exports = function (webpackConfig) { if (isProd) { config.module.rules.unshift(style9); - } else { - config.module.rules.push(style9); - } - - if (isProd) { config.entry = { main: [...config.entry.main, ...config.entry.polyfills], }; @@ -133,6 +129,7 @@ module.exports = function (webpackConfig) { }); config.module.rules.splice(6); } else { + config.module.rules.push(style9); config.output = { ...config.output, publicPath: '/', @@ -162,6 +159,7 @@ module.exports = function (webpackConfig) { global: {}, }), isProd && + !isE2E && new HtmlWebpackPlugin({ title: 'AFFiNE - All In One Workos', favicon: path.resolve( diff --git a/apps/venus/package.json b/apps/venus/package.json index 20c7374fdc..fe69da1b8e 100644 --- a/apps/venus/package.json +++ b/apps/venus/package.json @@ -14,6 +14,9 @@ }, "devDependencies": { "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" } } diff --git a/apps/venus/src/assets/collaboration.png b/apps/venus/src/app/collaboration.png similarity index 100% rename from apps/venus/src/assets/collaboration.png rename to apps/venus/src/app/collaboration.png diff --git a/apps/venus/src/app/index.tsx b/apps/venus/src/app/index.tsx index 011c7b9245..dea6dc0b7b 100644 --- a/apps/venus/src/app/index.tsx +++ b/apps/venus/src/app/index.tsx @@ -6,13 +6,18 @@ import clsx from 'clsx'; import { CssVarsProvider, styled } from '@mui/joy/styles'; import { Box, Button, Container, Grid, SvgIcon, Typography } from '@mui/joy'; -import Card from '@mui/joy/Card'; import GitHubIcon from '@mui/icons-material/GitHub'; import RedditIcon from '@mui/icons-material/Reddit'; import TelegramIcon from '@mui/icons-material/Telegram'; // eslint-disable-next-line no-restricted-imports 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) => { return ( - + @@ -524,7 +526,7 @@ export function App() { }} > @@ -606,7 +608,7 @@ export function App() { }} > @@ -667,7 +669,7 @@ export function App() { }} > @@ -680,7 +682,7 @@ export function App() { margin: 'auto', }} > - + diff --git a/apps/venus/src/assets/logo.png b/apps/venus/src/app/logo.png similarity index 100% rename from apps/venus/src/assets/logo.png rename to apps/venus/src/app/logo.png diff --git a/apps/venus/src/assets/page.png b/apps/venus/src/app/page.png similarity index 100% rename from apps/venus/src/assets/page.png rename to apps/venus/src/app/page.png diff --git a/apps/venus/src/app/shape.png b/apps/venus/src/app/shape.png new file mode 100644 index 0000000000..ad0ea8088c Binary files /dev/null and b/apps/venus/src/app/shape.png differ diff --git a/apps/venus/src/assets/task.png b/apps/venus/src/app/task.png similarity index 100% rename from apps/venus/src/assets/task.png rename to apps/venus/src/app/task.png diff --git a/apps/venus/src/assets/shape.png b/apps/venus/src/assets/shape.png deleted file mode 100644 index 0dc76b50fe..0000000000 Binary files a/apps/venus/src/assets/shape.png and /dev/null differ diff --git a/apps/venus/src/favicon.ico b/apps/venus/src/favicon.ico deleted file mode 100644 index 317ebcb233..0000000000 Binary files a/apps/venus/src/favicon.ico and /dev/null differ diff --git a/apps/venus/src/index.html b/apps/venus/src/index.html index 5dc4ff530d..232e6299d1 100644 --- a/apps/venus/src/index.html +++ b/apps/venus/src/index.html @@ -6,7 +6,7 @@ - +
diff --git a/apps/venus/webpack.config.js b/apps/venus/webpack.config.js index 8c980aef98..c325851149 100644 --- a/apps/venus/webpack.config.js +++ b/apps/venus/webpack.config.js @@ -7,6 +7,7 @@ const HtmlWebpackPlugin = require('html-webpack-plugin'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); const CompressionPlugin = require('compression-webpack-plugin'); +const ImageMinimizerPlugin = require('image-minimizer-webpack-plugin'); const TerserPlugin = require('terser-webpack-plugin'); const Style9Plugin = require('style9/webpack'); @@ -34,11 +35,6 @@ module.exports = function (webpackConfig) { if (isProd) { config.module.rules.unshift(style9); - } else { - config.module.rules.push(style9); - } - - if (isProd) { config.entry = { main: [...config.entry.main, ...config.entry.polyfills], }; @@ -61,6 +57,14 @@ module.exports = function (webpackConfig) { parallel: true, }), new CssMinimizerPlugin(), + new ImageMinimizerPlugin({ + minimizer: { + implementation: ImageMinimizerPlugin.imageminMinify, + options: { + plugins: [['optipng', { optimizationLevel: 5 }]], + }, + }, + }), ], splitChunks: { chunks: 'all', @@ -115,6 +119,7 @@ module.exports = function (webpackConfig) { }); config.module.rules.splice(6); } else { + config.module.rules.push(style9); config.output = { ...config.output, publicPath: '/', diff --git a/libs/components/account/package.json b/libs/components/account/package.json index a2d8289377..96fb8115d7 100644 --- a/libs/components/account/package.json +++ b/libs/components/account/package.json @@ -3,10 +3,10 @@ "version": "0.0.1", "license": "MIT", "dependencies": { - "@authing/react-ui-components": "^3.1.23", + "@authing/react-ui-components": "^3.1.39", "@emotion/styled": "^11.9.3" }, "devDependencies": { - "authing-js-sdk": "^4.23.33" + "authing-js-sdk": "^4.23.35" } } diff --git a/libs/components/affine-editor/src/Editor.tsx b/libs/components/affine-editor/src/Editor.tsx index 62f266b46c..4f842de2fd 100644 --- a/libs/components/affine-editor/src/Editor.tsx +++ b/libs/components/affine-editor/src/Editor.tsx @@ -18,6 +18,12 @@ interface AffineEditorProps { scrollBlank?: boolean; isWhiteboard?: boolean; + + scrollContainer?: HTMLElement; + scrollController?: { + lockScroll: () => void; + unLockScroll: () => void; + }; } function _useConstantWithDispose( @@ -53,13 +59,32 @@ function _useConstantWithDispose( } export const AffineEditor = forwardRef( - ({ workspace, rootBlockId, scrollBlank = true, isWhiteboard }, ref) => { + ( + { + workspace, + rootBlockId, + scrollBlank = true, + isWhiteboard, + scrollController, + scrollContainer, + }, + ref + ) => { const editor = _useConstantWithDispose( workspace, rootBlockId, isWhiteboard ); + useEffect(() => { + if (scrollContainer) { + editor.scrollManager.scrollContainer = scrollContainer; + } + if (scrollController) { + editor.scrollManager.scrollController = scrollController; + } + }, [editor, scrollContainer, scrollController]); + useImperativeHandle(ref, () => editor); return ( diff --git a/libs/components/board-draw/src/TlDraw.tsx b/libs/components/board-draw/src/TlDraw.tsx index 5ade114f4f..21044ad51d 100644 --- a/libs/components/board-draw/src/TlDraw.tsx +++ b/libs/components/board-draw/src/TlDraw.tsx @@ -29,6 +29,7 @@ import { ErrorBoundary } from 'react-error-boundary'; import { ErrorFallback } from './components/error-fallback'; import { ZoomBar } from './components/zoom-bar'; import { CommandPanel } from './components/command-panel'; +import { usePageClientWidth } from '@toeverything/datasource/state'; export interface TldrawProps extends TldrawAppCtorProps { /** @@ -132,6 +133,9 @@ export function Tldraw({ tools, }: TldrawProps) { 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. const [app, setApp] = React.useState(() => { @@ -140,6 +144,7 @@ export function Tldraw({ callbacks, commands, getSession, + editorShapeInitSize, tools, }); return app; diff --git a/libs/components/board-draw/src/components/zoom-bar/ZoomBar.tsx b/libs/components/board-draw/src/components/zoom-bar/ZoomBar.tsx index 97a82f3825..9a1693031d 100644 --- a/libs/components/board-draw/src/components/zoom-bar/ZoomBar.tsx +++ b/libs/components/board-draw/src/components/zoom-bar/ZoomBar.tsx @@ -21,9 +21,7 @@ export const ZoomBar: FC = () => { const zoom = app.useStore(zoomSelector); return ( -
+ @@ -52,10 +50,18 @@ export const ZoomBar: FC = () => {
- + ); }; +const ZoomBarContainer = styled('div')({ + position: 'absolute', + right: 10, + bottom: 10, + zIndex: 200, + userSelect: 'none', +}); + const MiniMapContainer = styled('div')({ display: 'flex', justifyContent: 'flex-end', diff --git a/libs/components/board-sessions/src/rotate-session.ts b/libs/components/board-sessions/src/rotate-session.ts index e9da6b6bcc..2de48ed120 100644 --- a/libs/components/board-sessions/src/rotate-session.ts +++ b/libs/components/board-sessions/src/rotate-session.ts @@ -6,6 +6,7 @@ import { TldrawPatch, TDShape, TDStatus, + TDShapeType, } from '@toeverything/components/board-types'; import { TLDR } from '@toeverything/components/board-state'; import { BaseSession } from './base-session'; @@ -75,6 +76,10 @@ export class RotateSession extends BaseSession { app: { currentPageId, currentPoint, shiftKey }, } = this; + const filteredShapes = initialShapes.filter( + shape => shape.shape.type !== TDShapeType.Editor + ); + const shapes: Record> = {}; let directionDelta = @@ -85,7 +90,7 @@ export class RotateSession extends BaseSession { } // Update the shapes - initialShapes.forEach(({ center, shape }) => { + filteredShapes.forEach(({ center, shape }) => { const { rotation = 0 } = shape; let shapeDelta = 0; diff --git a/libs/components/board-shapes/src/editor-util/EditorUtil.tsx b/libs/components/board-shapes/src/editor-util/EditorUtil.tsx index 430a3069f2..a7c56ebce1 100644 --- a/libs/components/board-shapes/src/editor-util/EditorUtil.tsx +++ b/libs/components/board-shapes/src/editor-util/EditorUtil.tsx @@ -133,6 +133,7 @@ export class EditorUtil extends TDShapeUtil { { const PADDING = 16; // const MIN_CONTAINER_HEIGHT = 200; -const Container = styled('div')({ +const Container = styled('div')<{ editing: boolean }>(({ editing }) => ({ pointerEvents: 'all', position: 'relative', width: '100%', -}); + userSelect: editing ? 'unset' : 'none', +})); const Mask = styled('div')({ position: 'absolute', - userSelect: 'none', top: 0, left: 0, bottom: 0, diff --git a/libs/components/board-state/src/tldraw-app.ts b/libs/components/board-state/src/tldraw-app.ts index c431a90bde..ab9492aabb 100644 --- a/libs/components/board-state/src/tldraw-app.ts +++ b/libs/components/board-state/src/tldraw-app.ts @@ -73,6 +73,7 @@ import { StateManager } from './manager/state-manager'; import { getClipboard, setClipboard } from './idb-clipboard'; import type { Commands } from './types/commands'; import type { BaseTool } from './types/tool'; +import { MIN_PAGE_WIDTH } from '@toeverything/components/editor-core'; const uuid = Utils.uniqueId(); @@ -178,6 +179,7 @@ export interface TldrawAppCtorProps { getSession: (type: SessionType) => { new (app: TldrawApp, ...args: any[]): BaseSessionType; }; + editorShapeInitSize?: number; commands: Commands; tools: Record; } @@ -223,6 +225,8 @@ export class TldrawApp extends StateManager { fileSystemHandle: FileSystemHandle | null = null; + editorShapeInitSize = MIN_PAGE_WIDTH; + viewport = Utils.getBoundsFromPoints([ [0, 0], [100, 100], @@ -285,6 +289,10 @@ export class TldrawApp extends StateManager { return acc; }, {} as Record); this.currentTool = this.tools['select']; + + if (props.editorShapeInitSize) { + this.editorShapeInitSize = props.editorShapeInitSize; + } } /* -------------------- Internal -------------------- */ diff --git a/libs/components/board-tools/src/editor-tool/editor-tool.ts b/libs/components/board-tools/src/editor-tool/editor-tool.ts index 4f1d20a2d6..f0da22a0c1 100644 --- a/libs/components/board-tools/src/editor-tool/editor-tool.ts +++ b/libs/components/board-tools/src/editor-tool/editor-tool.ts @@ -18,6 +18,7 @@ export class EditorTool extends BaseTool { const { currentPoint, currentGrid, + editorShapeInitSize, settings: { showGrid }, appState: { currentPageId, currentStyle }, document: { id: workspace }, @@ -47,6 +48,7 @@ export class EditorTool extends BaseTool { ? Vec.snap(currentPoint, currentGrid) : currentPoint, style: { ...currentStyle }, + size: [editorShapeInitSize, 200], 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]); diff --git a/libs/components/common/src/lib/collapsible-title/index.tsx b/libs/components/common/src/lib/collapsible-title/index.tsx index 2612eb0aa7..7dd59e613b 100644 --- a/libs/components/common/src/lib/collapsible-title/index.tsx +++ b/libs/components/common/src/lib/collapsible-title/index.tsx @@ -1,16 +1,21 @@ import { useState } from 'react'; -import clsx from 'clsx'; -import style9 from 'style9'; import { MuiButton as Button, MuiCollapse as Collapse, + styled, } from '@toeverything/components/ui'; -import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown'; -import ArrowRightIcon from '@mui/icons-material/ArrowRight'; +import { + ArrowDropDownIcon, + ArrowRightIcon, +} from '@toeverything/components/icons'; -const styles = style9.create({ - ligoButton: { - textTransform: 'none', +const StyledContainer = styled('div')({ + display: 'flex', + alignItems: 'center', + cursor: 'pointer', + '&:hover': { + background: '#f5f7f8', + borderRadius: '5px', }, }); @@ -24,29 +29,32 @@ export type 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); return ( <> - + setOpen(prev => !prev)}> + {open ? ( + + ) : ( + + )} +
+ {title} +
+
{children ? ( {children} diff --git a/libs/components/common/src/lib/text/plugins/link.tsx b/libs/components/common/src/lib/text/plugins/link.tsx index 18679b27d7..60193e38a0 100644 --- a/libs/components/common/src/lib/text/plugins/link.tsx +++ b/libs/components/common/src/lib/text/plugins/link.tsx @@ -121,11 +121,11 @@ const isLinkActive = (editor: ReactEditor) => { const LinkStyledTooltip = styled(({ className, ...props }: MuiTooltipProps) => ( -))(() => ({ +))(({ theme }) => ({ [`& .${muiTooltipClasses.tooltip}`]: { backgroundColor: '#fff', color: '#4C6275', - boxShadow: '0px 1px 10px rgba(152, 172, 189, 0.6)', + boxShadow: theme.affine.shadows.shadow1, fontSize: '14px', }, [`& .MuiTooltip-tooltipPlacementBottom`]: { @@ -412,8 +412,7 @@ export const LinkModal = memo((props: LinkModalProps) => { visible && ( <> -
{ autoComplete="off" ref={inputEl} /> -
+ ), 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({ - 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: { width: '16px', margin: '0 16px 0 4px', diff --git a/libs/components/editor-blocks/package.json b/libs/components/editor-blocks/package.json index d497e04e0f..d9b9c66f72 100644 --- a/libs/components/editor-blocks/package.json +++ b/libs/components/editor-blocks/package.json @@ -8,21 +8,21 @@ "@codemirror/lang-css": "^6.0.0", "@codemirror/lang-html": "~6.1.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-lezer": "~6.0.0", - "@codemirror/lang-markdown": "~6.0.0", + "@codemirror/lang-markdown": "~6.0.1", "@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-sql": "~6.0.0", + "@codemirror/lang-sql": "~6.1.0", "@codemirror/lang-xml": "~6.0.0", - "@codemirror/language": "^6.2.0", + "@codemirror/language": "^6.2.1", "@codemirror/legacy-modes": "~6.1.0", "@codemirror/next": "^0.16.0", - "@codemirror/state": "^6.1.0", + "@codemirror/state": "^6.1.1", "@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/sortable": "^7.0.1", "@dnd-kit/utilities": "^3.2.0", @@ -31,6 +31,7 @@ "@mui/system": "^5.8.6", "code-example": "^3.3.6", "codemirror": "6.0.1", + "codemirror-lang-elixir": "^3.0.0", "keymap": "link:@codemirror/next/keymap", "nanoid": "^4.0.0", "react-resizable": "^3.0.4", diff --git a/libs/components/editor-blocks/src/blocks/bullet/BulletView.tsx b/libs/components/editor-blocks/src/blocks/bullet/BulletView.tsx index 6f1970df15..dee9042e7a 100644 --- a/libs/components/editor-blocks/src/blocks/bullet/BulletView.tsx +++ b/libs/components/editor-blocks/src/blocks/bullet/BulletView.tsx @@ -17,7 +17,7 @@ import { supportChildren, RenderBlockChildren, useOnSelect, - WrapperWithPendantAndDragDrop, + BlockPendantProvider, } from '@toeverything/components/editor-core'; import { List } from '../../components/style-container'; import { getChildrenType, BulletIcon, NumberType } from './data'; @@ -188,7 +188,7 @@ export const BulletView: FC = ({ block, editor }) => { return ( - + @@ -206,7 +206,7 @@ export const BulletView: FC = ({ block, editor }) => { /> - + diff --git a/libs/components/editor-blocks/src/blocks/code/CodeView.tsx b/libs/components/editor-blocks/src/blocks/code/CodeView.tsx index 3a729e51f0..9ce542acf2 100644 --- a/libs/components/editor-blocks/src/blocks/code/CodeView.tsx +++ b/libs/components/editor-blocks/src/blocks/code/CodeView.tsx @@ -1,10 +1,9 @@ -import { FC, useState, useMemo, useRef, useEffect } from 'react'; +import { FC, useState, useRef, useEffect } from 'react'; import { StyleWithAtRules } from 'style9'; import { CreateView } from '@toeverything/framework/virgo'; -import CodeMirror from './CodeMirror'; +import CodeMirror, { ReactCodeMirrorRef } from './CodeMirror'; import { styled } from '@toeverything/components/ui'; -import DeleteSweepOutlinedIcon from '@mui/icons-material/DeleteSweepOutlined'; import { javascript } from '@codemirror/lang-javascript'; 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 { stylus } from '@codemirror/legacy-modes/mode/stylus'; import { erlang } from '@codemirror/legacy-modes/mode/erlang'; +import { elixir } from 'codemirror-lang-elixir'; import { nginx } from '@codemirror/legacy-modes/mode/nginx'; import { perl } from '@codemirror/legacy-modes/mode/perl'; 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 { r } from '@codemirror/legacy-modes/mode/r'; import { Extension } from '@codemirror/state'; -// import { Select } from '../../components/select'; import { Option, Select } from '@toeverything/components/ui'; import { useOnSelect, - WrapperWithPendantAndDragDrop, + BlockPendantProvider, } from '@toeverything/components/editor-core'; import { copyToClipboard } from '@toeverything/utils'; interface CreateCodeView extends CreateView { @@ -87,6 +86,7 @@ const langs: Record = { brainfuck: () => StreamLanguage.define(brainfuck), stylus: () => StreamLanguage.define(stylus), erlang: () => StreamLanguage.define(erlang), + elixir: () => StreamLanguage.define(elixir), nginx: () => StreamLanguage.define(nginx), perl: () => StreamLanguage.define(perl), ruby: () => StreamLanguage.define(ruby), @@ -100,10 +100,8 @@ const langs: Record = { julia: () => StreamLanguage.define(julia), dockerfile: () => StreamLanguage.define(dockerFile), r: () => StreamLanguage.define(r), - // clike: () => StreamLanguage.define(clike), - // clike: () => clike({ }), }; - +const DEFAULT_LANG = 'javascript'; const CodeBlock = styled('div')(({ theme }) => ({ backgroundColor: '#F2F5F9', padding: '8px 24px', @@ -118,7 +116,7 @@ const CodeBlock = styled('div')(({ theme }) => ({ flexWrap: 'wrap', justifyContent: 'space-between', }, - '.delete-block': { + '.copy-block': { padding: '6px 10px', backgroundColor: '#fff', borderRadius: theme.affine.shape.borderRadius, @@ -130,29 +128,27 @@ const CodeBlock = styled('div')(({ theme }) => ({ })); export const CodeView: FC = ({ block, editor }) => { const initValue: string = block.getProperty('text')?.value?.[0]?.text; - const langType: string = block.getProperty('lang')?.value?.[0]?.text; - const [mode, setMode] = useState('javascript'); + const langType: string = block.getProperty('lang'); const [extensions, setExtensions] = useState(); - const codeMirror = useRef(); - useOnSelect(block.id, (is_select: boolean) => { + const codeMirror = useRef(); + useOnSelect(block.id, (_is_select: boolean) => { if (codeMirror.current) { - //@ts-ignore codeMirror?.current?.view?.focus(); } }); - const onChange = (value: any, codeEditor: any) => { + const onChange = (value: string) => { block.setProperty('text', { value: [{ text: value }], }); }; - useEffect(() => { - handleLangChange(langType ? langType : 'javascript'); - }, []); - function handleLangChange(lang: string) { + const handleLangChange = (lang: string) => { block.setProperty('lang', lang); - setMode(lang); setExtensions([langs[lang]()]); - } + }; + useEffect(() => { + handleLangChange(langType ? langType : DEFAULT_LANG); + }, []); + const copyCode = () => { copyToClipboard(initValue); }; @@ -163,7 +159,7 @@ export const CodeView: FC = ({ block, editor }) => { editor.selectionManager.activePreviousNode(block.id, 'start'); }; return ( - + { e.stopPropagation(); @@ -171,19 +167,12 @@ export const CodeView: FC = ({ block, editor }) => { >
- {/* { - // setSelectedOption(selectedValue); handleLangChange(selectedValue); }} > @@ -197,17 +186,8 @@ export const CodeView: FC = ({ block, editor }) => {
-
+
Copy - {/* */}
@@ -222,6 +202,6 @@ export const CodeView: FC = ({ block, editor }) => { handleKeyArrowUp={handleKeyArrowUp} /> - + ); }; diff --git a/libs/components/editor-blocks/src/blocks/embed-link/EmbedLinkView.tsx b/libs/components/editor-blocks/src/blocks/embed-link/EmbedLinkView.tsx index ac073abdb0..da33c2be4a 100644 --- a/libs/components/editor-blocks/src/blocks/embed-link/EmbedLinkView.tsx +++ b/libs/components/editor-blocks/src/blocks/embed-link/EmbedLinkView.tsx @@ -1,7 +1,7 @@ import { FC, useState } from 'react'; import { CreateView } from '@toeverything/framework/virgo'; import { - WrapperWithPendantAndDragDrop, + BlockPendantProvider, useOnSelect, } from '@toeverything/components/editor-core'; import { Upload } from '../../components/upload/upload'; @@ -33,7 +33,7 @@ export const EmbedLinkView: FC = props => { }; return ( - + {embedLinkUrl ? ( = props => { /> )} - + ); }; diff --git a/libs/components/editor-blocks/src/blocks/figma/FigmaView.tsx b/libs/components/editor-blocks/src/blocks/figma/FigmaView.tsx index 1dbaef3e11..2d12315235 100644 --- a/libs/components/editor-blocks/src/blocks/figma/FigmaView.tsx +++ b/libs/components/editor-blocks/src/blocks/figma/FigmaView.tsx @@ -2,7 +2,7 @@ import { FC, useState } from 'react'; import { CreateView } from '@toeverything/framework/virgo'; import { useOnSelect, - WrapperWithPendantAndDragDrop, + BlockPendantProvider, } from '@toeverything/components/editor-core'; import { Upload } from '../../components/upload/upload'; import { SourceView } from '../../components/source-view'; @@ -30,7 +30,7 @@ export const FigmaView: FC = ({ block, editor }) => { setIsSelect(isSelect); }); return ( - + {figmaUrl ? ( = ({ block, editor }) => { /> )} - + ); }; diff --git a/libs/components/editor-blocks/src/blocks/grid-item/GridItem.tsx b/libs/components/editor-blocks/src/blocks/grid-item/GridItem.tsx index 491ac66628..4273e7c5d1 100644 --- a/libs/components/editor-blocks/src/blocks/grid-item/GridItem.tsx +++ b/libs/components/editor-blocks/src/blocks/grid-item/GridItem.tsx @@ -2,13 +2,13 @@ import { FC, useEffect, useLayoutEffect, useRef } from 'react'; import { ChildrenView } from '@toeverything/framework/virgo'; import { styled } from '@toeverything/components/ui'; 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_CONTENT_CLASS_NAME = `${GRID_ITEM_CLASS_NAME}-content`; export const GridItem: FC = function (props) { - const { children, block } = props; + const { children, block, editor } = props; const RENDER_DELAY_TIME = 100; const ref = useRef(); @@ -25,6 +25,7 @@ export const GridItem: FC = function (props) { const checkAndRefreshWidth = async () => { const currentWidth = block.getProperty(GRID_PROPERTY_KEY); + const gridItemMinWidth = editor.configManager.grid.gridItemMinWidth; if (currentWidth) { setWidth(currentWidth); } else if (!block.dom?.style.width) { @@ -64,26 +65,23 @@ export const GridItem: FC = function (props) { if new width less then min width, set min width and next block will be fix width */ - if (newWidth < GRID_ITEM_MIN_WIDTH) { - needFixWidth += GRID_ITEM_MIN_WIDTH - newWidth; - newWidth = GRID_ITEM_MIN_WIDTH; + if (newWidth < gridItemMinWidth) { + needFixWidth += gridItemMinWidth - newWidth; + newWidth = gridItemMinWidth; } // if can fix width, fix width - if ( - newWidth > GRID_ITEM_MIN_WIDTH && - needFixWidth - ) { + if (newWidth > gridItemMinWidth && needFixWidth) { if ( newWidth - needFixWidth >= - GRID_ITEM_MIN_WIDTH + gridItemMinWidth ) { newWidth = newWidth - needFixWidth; needFixWidth = 0; } else { needFixWidth = needFixWidth - - (newWidth - GRID_ITEM_MIN_WIDTH); - newWidth = GRID_ITEM_MIN_WIDTH; + (newWidth - gridItemMinWidth); + newWidth = gridItemMinWidth; } } if (index === children.length - 2) { diff --git a/libs/components/editor-blocks/src/blocks/grid/Grid.tsx b/libs/components/editor-blocks/src/blocks/grid/Grid.tsx index bc27ec4ac6..ec06c1049f 100644 --- a/libs/components/editor-blocks/src/blocks/grid/Grid.tsx +++ b/libs/components/editor-blocks/src/blocks/grid/Grid.tsx @@ -12,10 +12,8 @@ import { debounce, domToRect, Point } from '@toeverything/utils'; import clsx from 'clsx'; import { Protocol } from '@toeverything/datasource/db-service'; -const MAX_ITEM_COUNT = 6; const DB_UPDATE_DELAY = 50; 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 function removePercent(str: string) { @@ -24,13 +22,14 @@ export function removePercent(str: string) { export const Grid: FC = function (props) { const { block, editor } = props; + const gridItemMinWidth = editor.configManager.grid.gridItemMinWidth; const [isOnDrag, setIsOnDrag] = useState(false); const isSetMouseUp = useRef(false); const gridContainerRef = useRef(); const mouseStartPoint = useRef(); const gridItemCountRef = useRef(); - const originalLeftWidth = useRef(GRID_ITEM_MIN_WIDTH); - const originalRightWidth = useRef(GRID_ITEM_MIN_WIDTH); + const originalLeftWidth = useRef(gridItemMinWidth); + const originalRightWidth = useRef(gridItemMinWidth); const [alertHandleId, setAlertHandleId] = useState(null); const getLeftRightGridItemDomByIndex = (index: number) => { @@ -126,8 +125,8 @@ export const Grid: FC = function (props) { editor.mouseManager.onMouseupEventOnce(() => { setIsOnDrag(false); isSetMouseUp.current = false; - originalLeftWidth.current = GRID_ITEM_MIN_WIDTH; - originalRightWidth.current = GRID_ITEM_MIN_WIDTH; + originalLeftWidth.current = gridItemMinWidth; + originalRightWidth.current = gridItemMinWidth; mouseStartPoint.current = null; }); } else { @@ -153,12 +152,12 @@ export const Grid: FC = function (props) { const newLeftWidth = originalLeftWidth.current - xDistance; let newLeftPercent = (newLeftWidth / containerWidth) * 100; let newRightPercent = Number(totalWidth) - newLeftPercent; - if (newLeftPercent < GRID_ITEM_MIN_WIDTH) { - newLeftPercent = GRID_ITEM_MIN_WIDTH; - newRightPercent = totalWidth - GRID_ITEM_MIN_WIDTH; - } else if (newRightPercent < GRID_ITEM_MIN_WIDTH) { - newRightPercent = GRID_ITEM_MIN_WIDTH; - newLeftPercent = totalWidth - GRID_ITEM_MIN_WIDTH; + if (newLeftPercent < gridItemMinWidth) { + newLeftPercent = gridItemMinWidth; + newRightPercent = totalWidth - gridItemMinWidth; + } else if (newRightPercent < gridItemMinWidth) { + newRightPercent = gridItemMinWidth; + newLeftPercent = totalWidth - gridItemMinWidth; } //XXX first change dom style is for animation speed, maybe not a good idea const newLeft = `${newLeftPercent}%`; @@ -213,6 +212,7 @@ export const Grid: FC = function (props) { {block.childrenIds.map((id, i) => { @@ -233,7 +233,8 @@ export const Grid: FC = function (props) { onMouseDown={event => handleMouseDown(event, i)} blockId={id} enabledAddItem={ - block.childrenIds.length < MAX_ITEM_COUNT + block.childrenIds.length < + editor.configManager.grid.maxGridItemCount } onMouseEnter={event => handleHandleMouseEnter(event, i) @@ -252,24 +253,25 @@ export const Grid: FC = function (props) { ); }; -const GridContainer = styled('div')<{ isOnDrag: boolean }>( - ({ isOnDrag, theme }) => ({ - position: 'relative', - display: 'flex', - alignItems: 'stretch', - borderRadius: '10px', - border: '1px solid #FFF', - minWidth: `${GRID_ITEM_MIN_WIDTH}%`, - [`&:hover .${GRID_ITEM_CONTENT_CLASS_NAME}`]: { +const GridContainer = styled('div')<{ + isOnDrag: boolean; + gridItemMinWidth: number; +}>(({ isOnDrag, theme, gridItemMinWidth }) => ({ + position: 'relative', + display: 'flex', + alignItems: 'stretch', + borderRadius: '10px', + 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, }, - ...(isOnDrag && { - [`& .${GRID_ITEM_CONTENT_CLASS_NAME}`]: { - borderColor: theme.affine.palette.borderColor, - }, - }), - }) -); + }), +})); const GridMask = styled('div')({ position: 'fixed', diff --git a/libs/components/editor-blocks/src/blocks/grid/index.ts b/libs/components/editor-blocks/src/blocks/grid/index.ts index ec6c363819..808f54f0aa 100644 --- a/libs/components/editor-blocks/src/blocks/grid/index.ts +++ b/libs/components/editor-blocks/src/blocks/grid/index.ts @@ -3,7 +3,7 @@ import { Protocol } from '@toeverything/datasource/db-service'; import { AsyncBlock, BaseView } from '@toeverything/framework/virgo'; import { GridItem } from '../grid-item/GridItem'; 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 { public override selectable = false; diff --git a/libs/components/editor-blocks/src/blocks/group/GroupView.tsx b/libs/components/editor-blocks/src/blocks/group/GroupView.tsx index 1c2ef04605..193a97f076 100644 --- a/libs/components/editor-blocks/src/blocks/group/GroupView.tsx +++ b/libs/components/editor-blocks/src/blocks/group/GroupView.tsx @@ -69,7 +69,7 @@ const GroupContainer = styled('div')<{ isSelect?: boolean }>( } : { '&:hover': { - boxShadow: '0px 1px 10px rgb(152 172 189 / 60%)', + boxShadow: theme.affine.shadows.shadow1, }, }), }) diff --git a/libs/components/editor-blocks/src/blocks/group/components/Panel.tsx b/libs/components/editor-blocks/src/blocks/group/components/Panel.tsx index e8faba84a1..d6d4b79f02 100644 --- a/libs/components/editor-blocks/src/blocks/group/components/Panel.tsx +++ b/libs/components/editor-blocks/src/blocks/group/components/Panel.tsx @@ -2,11 +2,11 @@ import { styled } from '@toeverything/components/ui'; import type { ComponentPropsWithRef, MouseEvent } from 'react'; import { forwardRef } from 'react'; -const StyledPanel = styled('div')(() => ({ +const StyledPanel = styled('div')(({ theme }) => ({ position: 'absolute', top: 50, background: '#FFFFFF', - boxShadow: '0px 1px 10px rgba(152, 172, 189, 0.6)', + boxShadow: theme.affine.shadows.shadow1, borderRadius: 10, padding: '12px 24px', })); diff --git a/libs/components/editor-blocks/src/blocks/group/scene-kanban/CardContainer.tsx b/libs/components/editor-blocks/src/blocks/group/scene-kanban/CardContainer.tsx index 3616c0d206..cd71b72666 100644 --- a/libs/components/editor-blocks/src/blocks/group/scene-kanban/CardContainer.tsx +++ b/libs/components/editor-blocks/src/blocks/group/scene-kanban/CardContainer.tsx @@ -41,6 +41,7 @@ const getKanbanColor = ( return DEFAULT_COLOR; } if ( + group.type === PropertyType.Status || group.type === PropertyType.Select || group.type === PropertyType.MultiSelect || group.type === DEFAULT_GROUP_ID diff --git a/libs/components/editor-blocks/src/blocks/group/scene-kanban/CardContext.tsx b/libs/components/editor-blocks/src/blocks/group/scene-kanban/CardContext.tsx index cb313d14ee..f7f05b37d4 100644 --- a/libs/components/editor-blocks/src/blocks/group/scene-kanban/CardContext.tsx +++ b/libs/components/editor-blocks/src/blocks/group/scene-kanban/CardContext.tsx @@ -25,7 +25,7 @@ const AddCard = ({ group }: { group: KanbanGroup }) => { const { addCard } = useKanban(); const handleClick = useCallback(async () => { await addCard(group); - }, [addCard]); + }, [addCard, group]); return +; }; diff --git a/libs/components/editor-blocks/src/blocks/group/scene-kanban/CardItem.tsx b/libs/components/editor-blocks/src/blocks/group/scene-kanban/CardItem.tsx index f26374e4c3..76220d7ff0 100644 --- a/libs/components/editor-blocks/src/blocks/group/scene-kanban/CardItem.tsx +++ b/libs/components/editor-blocks/src/blocks/group/scene-kanban/CardItem.tsx @@ -1,6 +1,11 @@ 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 { useFlag } from '@toeverything/datasource/feature-flags'; const CardContent = styled('div')({ margin: '20px', @@ -58,18 +63,24 @@ export const CardItem = ({ block: KanbanCard['block']; }) => { const { addSubItem } = useKanban(); + const { openSubPage } = useRefPage(); + const showKanbanRefPageFlag = useFlag('ShowKanbanRefPage', false); const onAddItem = async () => { await addSubItem(block); }; + const onClickCard = async () => { + showKanbanRefPageFlag && openSubPage(id); + }; + return ( - + - Add item + Add a sub-block ); diff --git a/libs/components/editor-blocks/src/blocks/image/ImageView.tsx b/libs/components/editor-blocks/src/blocks/image/ImageView.tsx index 36b446d790..369dceac87 100644 --- a/libs/components/editor-blocks/src/blocks/image/ImageView.tsx +++ b/libs/components/editor-blocks/src/blocks/image/ImageView.tsx @@ -1,7 +1,7 @@ import { useCurrentView, useOnSelect, - WrapperWithPendantAndDragDrop, + BlockPendantProvider, } from '@toeverything/components/editor-core'; import { styled } from '@toeverything/components/ui'; import { services } from '@toeverything/datasource/db-service'; @@ -143,13 +143,13 @@ export const ImageView: FC = ({ block, editor }) => { type: 'link', }); }; - const handle_click = (e: React.MouseEvent) => { + const handle_click = async (e: React.MouseEvent) => { //TODO clear active selection // document.getElementsByTagName('body')[0].click(); e.stopPropagation(); e.nativeEvent.stopPropagation(); - editor.selectionManager.setSelectedNodesIds([block.id]); - editor.selectionManager.activeNodeByNodeId(block.id); + await editor.selectionManager.setSelectedNodesIds([block.id]); + await editor.selectionManager.activeNodeByNodeId(block.id, 'end'); }; const down_file = () => { if (down_ref) { @@ -158,7 +158,7 @@ export const ImageView: FC = ({ block, editor }) => { }; return ( - +
{imgUrl ? ( @@ -229,6 +229,6 @@ export const ImageView: FC = ({ block, editor }) => {
*/}
-
+ ); }; diff --git a/libs/components/editor-blocks/src/blocks/numbered/NumberedView.tsx b/libs/components/editor-blocks/src/blocks/numbered/NumberedView.tsx index 514ce335f6..2f8f29b706 100644 --- a/libs/components/editor-blocks/src/blocks/numbered/NumberedView.tsx +++ b/libs/components/editor-blocks/src/blocks/numbered/NumberedView.tsx @@ -19,9 +19,8 @@ import { supportChildren, RenderBlockChildren, useOnSelect, - WrapperWithPendantAndDragDrop, + BlockPendantProvider, } from '@toeverything/components/editor-core'; -import { styled } from '@toeverything/components/ui'; import { List } from '../../components/style-container'; import { BlockContainer } from '../../components/BlockContainer'; @@ -185,7 +184,7 @@ export const NumberedView: FC = ({ block, editor }) => { return ( - +
{getNumber(properties.numberType, number)}. @@ -203,7 +202,7 @@ export const NumberedView: FC = ({ block, editor }) => { />
-
+ diff --git a/libs/components/editor-blocks/src/blocks/text/TextView.tsx b/libs/components/editor-blocks/src/blocks/text/TextView.tsx index ced1a8745d..cb408cb189 100644 --- a/libs/components/editor-blocks/src/blocks/text/TextView.tsx +++ b/libs/components/editor-blocks/src/blocks/text/TextView.tsx @@ -8,7 +8,7 @@ import { supportChildren, unwrapGroup, useOnSelect, - WrapperWithPendantAndDragDrop, + BlockPendantProvider, } from '@toeverything/components/editor-core'; import { styled } from '@toeverything/components/ui'; import { Protocol } from '@toeverything/datasource/db-service'; @@ -99,7 +99,7 @@ export const TextView: FC = ({ if (!parentBlock) { return false; } - + const preParent = await parentBlock.previousSibling(); if (Protocol.Block.Type.group === parentBlock.type) { const children = await block.children(); const preNode = await block.physicallyPerviousSibling(); @@ -129,34 +129,19 @@ export const TextView: FC = ({ 'start' ); 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(); - 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; } } @@ -231,7 +216,7 @@ export const TextView: FC = ({ selected={isSelect} className={containerClassName} > - + = ({ handleConvert={handleConvert} handleTab={onTab} /> - + diff --git a/libs/components/editor-blocks/src/components/source-view/SourceView.tsx b/libs/components/editor-blocks/src/components/source-view/SourceView.tsx index 3835199d3f..4dbb488850 100644 --- a/libs/components/editor-blocks/src/components/source-view/SourceView.tsx +++ b/libs/components/editor-blocks/src/components/source-view/SourceView.tsx @@ -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 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 { formatUrl } from './format-url'; @@ -15,7 +27,18 @@ export interface Props { } 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')<{ isSelected: boolean; }>(({ 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')<{ isSelected: boolean; -}>(({ theme, isSelected }) => { + scene: string; +}>(({ theme, isSelected, scene }) => { return { + ..._getLinkStyle(scene), overflow: 'hidden', + position: 'relative', borderRadius: theme.affine.shape.borderRadius, background: isSelected ? 'rgba(152, 172, 189, 0.1)' : 'transparent', padding: '8px', @@ -52,32 +91,96 @@ const SourceViewContainer = styled('div')<{ height: '100%', border: '1px solid #EAEEF2', 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(); + + 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 ( + <> +
{ + e.preventDefault(); + e.stopPropagation(); + }} + style={{ display: show ? 'block' : 'none', height: '100%' }} + > +