mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-05 03:25:10 +08:00
Compare commits
52 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 486e047b6d | |||
| a7db5c5027 | |||
| 5bb67e66c2 | |||
| 2b9ccf8fcf | |||
| aaa81e7d12 | |||
| b70a0dfbc7 | |||
| 594e896f74 | |||
| 760a689c07 | |||
| b8967a8a7b | |||
| 1a2d6f25be | |||
| e9d04de399 | |||
| e68947c792 | |||
| 2cd0e75810 | |||
| 6457c979f5 | |||
| 7aa87de5f7 | |||
| efecce9bf2 | |||
| ee15b364d1 | |||
| 2db7dea46f | |||
| 2fd2c149a5 | |||
| be759dc118 | |||
| 00bd05897e | |||
| 08dbaae19b | |||
| 1ab9f1376d | |||
| d494394c8d | |||
| 11d1b2fae5 | |||
| 1fc51bf95e | |||
| d6b4ee8172 | |||
| e1cb8198f1 | |||
| 076c5ba044 | |||
| 26d2ed8afb | |||
| f860f77a24 | |||
| cac817c896 | |||
| ac8464068d | |||
| 4da00eba0d | |||
| afdc40b510 | |||
| aabb09b31f | |||
| e1e5e8fc14 | |||
| e73d68cac4 | |||
| 2f5647ac77 | |||
| 9c6d94f597 | |||
| 883b1d50f2 | |||
| 4e1fcca876 | |||
| 74ebec007d | |||
| 4f9a4e739a | |||
| 149433b8d0 | |||
| a5ab66d6cd | |||
| 714f2e79dc | |||
| bcd1cd2629 | |||
| 5670eb4d02 | |||
| cb7f15296a | |||
| 16d5b0df95 | |||
| a2f879066f |
@@ -218,7 +218,43 @@ jobs:
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: test-results-e2e-legacy-bs-${{ matrix.shard }}
|
||||
name: test-results-e2e-bs-${{ matrix.shard }}
|
||||
path: ./test-results
|
||||
if-no-files-found: ignore
|
||||
|
||||
e2e-blocksuite-cross-browser-test:
|
||||
name: E2E BlockSuite Cross Browser Test
|
||||
runs-on: ubuntu-latest
|
||||
needs: optimize_ci
|
||||
if: needs.optimize_ci.outputs.skip == 'false'
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
shard: [1, 2]
|
||||
browser: ['chromium', 'firefox', 'webkit']
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
playwright-install: true
|
||||
playwright-platform: ${{ matrix.browser }}
|
||||
electron-install: false
|
||||
full-cache: true
|
||||
|
||||
- name: Run playground build
|
||||
run: yarn workspace @blocksuite/playground build
|
||||
|
||||
- name: Run playwright tests
|
||||
env:
|
||||
BROWSER: ${{ matrix.browser }}
|
||||
run: yarn workspace @affine-test/blocksuite test "cross-platform/" --forbid-only --shard=${{ matrix.shard }}/${{ strategy.job-total }}
|
||||
|
||||
- name: Upload test results
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: test-results-e2e-bs-cross-browser-${{ matrix.browser }}-${{ matrix.shard }}
|
||||
path: ./test-results
|
||||
if-no-files-found: ignore
|
||||
|
||||
@@ -1141,6 +1177,7 @@ jobs:
|
||||
- check-yarn-binary
|
||||
- e2e-test
|
||||
- e2e-blocksuite-test
|
||||
- e2e-blocksuite-cross-browser-test
|
||||
- e2e-mobile-test
|
||||
- unit-test
|
||||
- build-native
|
||||
|
||||
@@ -263,5 +263,5 @@ jobs:
|
||||
packageName: app.affine.pro
|
||||
releaseFiles: packages/frontend/apps/android/App/app/build/outputs/bundle/${{ env.BUILD_TYPE }}Release/app-${{ env.BUILD_TYPE }}-release-signed.aab
|
||||
track: internal
|
||||
status: draft
|
||||
status: complete
|
||||
existingEditId: ${{ steps.bump.outputs.EDIT_ID }}
|
||||
|
||||
@@ -85,3 +85,6 @@ packages/frontend/core/public/static/templates
|
||||
af
|
||||
af.cmd
|
||||
*.resolved
|
||||
|
||||
# playwright
|
||||
storageState.json
|
||||
|
||||
+169
-169
File diff suppressed because one or more lines are too long
+1
-1
@@ -12,4 +12,4 @@ npmPublishAccess: public
|
||||
|
||||
npmPublishRegistry: "https://registry.npmjs.org"
|
||||
|
||||
yarnPath: .yarn/releases/yarn-4.9.0.cjs
|
||||
yarnPath: .yarn/releases/yarn-4.9.1.cjs
|
||||
|
||||
Generated
+23
-84
@@ -286,9 +286,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.97"
|
||||
version = "1.0.98"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f"
|
||||
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
|
||||
|
||||
[[package]]
|
||||
name = "arbitrary"
|
||||
@@ -601,9 +601,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.18"
|
||||
version = "1.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "525046617d8376e3db1deffb079e91cef90a89fc3ca5c185bbf8c9ecdd15cd5c"
|
||||
checksum = "8e3a13707ac958681c13b39b458c073d0d9bc8a22cb1b2f4c8e55eb72c13f362"
|
||||
dependencies = [
|
||||
"shlex",
|
||||
]
|
||||
@@ -1669,7 +1669,7 @@ dependencies = [
|
||||
"js-sys",
|
||||
"log",
|
||||
"wasm-bindgen",
|
||||
"windows-core 0.61.0",
|
||||
"windows-core 0.58.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1986,7 +1986,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-targets 0.48.5",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3101,15 +3101,6 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pemfile"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50"
|
||||
dependencies = [
|
||||
"rustls-pki-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pki-types"
|
||||
version = "1.11.0"
|
||||
@@ -3393,9 +3384,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sqlx"
|
||||
version = "0.8.3"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4410e73b3c0d8442c5f99b425d7a435b5ee0ae4167b3196771dd3f7a01be745f"
|
||||
checksum = "14e22987355fbf8cfb813a0cf8cd97b1b4ec834b94dbd759a9e8679d41fabe83"
|
||||
dependencies = [
|
||||
"sqlx-core",
|
||||
"sqlx-macros",
|
||||
@@ -3406,10 +3397,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-core"
|
||||
version = "0.8.3"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a007b6936676aa9ab40207cde35daab0a04b823be8ae004368c0793b96a61e0"
|
||||
checksum = "55c4720d7d4cd3d5b00f61d03751c685ad09c33ae8290c8a2c11335e0604300b"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
"chrono",
|
||||
"crc",
|
||||
@@ -3428,7 +3420,6 @@ dependencies = [
|
||||
"once_cell",
|
||||
"percent-encoding",
|
||||
"rustls",
|
||||
"rustls-pemfile",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
@@ -3443,9 +3434,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-macros"
|
||||
version = "0.8.3"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3112e2ad78643fef903618d78cf0aec1cb3134b019730edb039b69eaf531f310"
|
||||
checksum = "175147fcb75f353ac7675509bc58abb2cb291caf0fd24a3623b8f7e3eb0a754b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3456,9 +3447,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-macros-core"
|
||||
version = "0.8.3"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e9f90acc5ab146a99bf5061a7eb4976b573f560bc898ef3bf8435448dd5e7ad"
|
||||
checksum = "1cde983058e53bfa75998e1982086c5efe3c370f3250bf0357e344fa3352e32b"
|
||||
dependencies = [
|
||||
"dotenvy",
|
||||
"either",
|
||||
@@ -3482,9 +3473,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-mysql"
|
||||
version = "0.8.3"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4560278f0e00ce64938540546f59f590d60beee33fffbd3b9cd47851e5fff233"
|
||||
checksum = "847d2e5393a4f39e47e4f36cab419709bc2b83cbe4223c60e86e1471655be333"
|
||||
dependencies = [
|
||||
"atoi",
|
||||
"base64 0.22.1",
|
||||
@@ -3525,9 +3516,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-postgres"
|
||||
version = "0.8.3"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c5b98a57f363ed6764d5b3a12bfedf62f07aa16e1856a7ddc2a0bb190a959613"
|
||||
checksum = "cc35947a541b9e0a2e3d85da444f1c4137c13040267141b208395a0d0ca4659f"
|
||||
dependencies = [
|
||||
"atoi",
|
||||
"base64 0.22.1",
|
||||
@@ -3563,9 +3554,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-sqlite"
|
||||
version = "0.8.3"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f85ca71d3a5b24e64e1d08dd8fe36c6c95c339a896cc33068148906784620540"
|
||||
checksum = "6c48291dac4e5ed32da0927a0b981788be65674aeb62666d19873ab4289febde"
|
||||
dependencies = [
|
||||
"atoi",
|
||||
"chrono",
|
||||
@@ -3581,6 +3572,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_urlencoded",
|
||||
"sqlx-core",
|
||||
"thiserror 2.0.12",
|
||||
"tracing",
|
||||
"url",
|
||||
]
|
||||
@@ -4773,23 +4765,10 @@ dependencies = [
|
||||
"windows-implement 0.58.0",
|
||||
"windows-interface 0.58.0",
|
||||
"windows-result 0.2.0",
|
||||
"windows-strings 0.1.0",
|
||||
"windows-strings",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.61.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980"
|
||||
dependencies = [
|
||||
"windows-implement 0.60.0",
|
||||
"windows-interface 0.59.1",
|
||||
"windows-link",
|
||||
"windows-result 0.3.2",
|
||||
"windows-strings 0.4.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-implement"
|
||||
version = "0.57.0"
|
||||
@@ -4812,17 +4791,6 @@ dependencies = [
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-implement"
|
||||
version = "0.60.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-interface"
|
||||
version = "0.57.0"
|
||||
@@ -4845,17 +4813,6 @@ dependencies = [
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-interface"
|
||||
version = "0.59.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.1.1"
|
||||
@@ -4880,15 +4837,6 @@ dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-strings"
|
||||
version = "0.1.0"
|
||||
@@ -4899,15 +4847,6 @@ dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-strings"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.48.0"
|
||||
|
||||
@@ -55,6 +55,7 @@
|
||||
"@blocksuite/affine-widget-edgeless-auto-connect": "workspace:*",
|
||||
"@blocksuite/affine-widget-edgeless-toolbar": "workspace:*",
|
||||
"@blocksuite/affine-widget-frame-title": "workspace:*",
|
||||
"@blocksuite/affine-widget-linked-doc": "workspace:*",
|
||||
"@blocksuite/affine-widget-remote-selection": "workspace:*",
|
||||
"@blocksuite/affine-widget-scroll-anchoring": "workspace:*",
|
||||
"@blocksuite/affine-widget-slash-menu": "workspace:*",
|
||||
@@ -115,6 +116,7 @@
|
||||
"./widgets/edgeless-auto-connect": "./src/widgets/edgeless-auto-connect.ts",
|
||||
"./widgets/edgeless-toolbar": "./src/widgets/edgeless-toolbar.ts",
|
||||
"./widgets/frame-title": "./src/widgets/frame-title.ts",
|
||||
"./widgets/linked-doc": "./src/widgets/linked-doc.ts",
|
||||
"./widgets/remote-selection": "./src/widgets/remote-selection.ts",
|
||||
"./widgets/scroll-anchoring": "./src/widgets/scroll-anchoring.ts",
|
||||
"./widgets/slash-menu": "./src/widgets/slash-menu.ts",
|
||||
|
||||
@@ -4091,4 +4091,55 @@ hhh
|
||||
});
|
||||
expect(nanoidReplacement(rawBlockSnapshot)).toEqual(blockSnapshot);
|
||||
});
|
||||
|
||||
test('should not wrap url with angle brackets if it is not a url', async () => {
|
||||
const markdown = 'prompt: How many people will live in the world in 2040?';
|
||||
const sliceSnapshot: SliceSnapshot = {
|
||||
type: 'slice',
|
||||
content: [
|
||||
{
|
||||
type: 'block',
|
||||
id: 'matchesReplaceMap[0]',
|
||||
flavour: 'affine:note',
|
||||
props: {
|
||||
xywh: '[0,0,800,95]',
|
||||
background: DefaultTheme.noteBackgrounColor,
|
||||
index: 'a0',
|
||||
hidden: false,
|
||||
displayMode: NoteDisplayMode.DocAndEdgeless,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
type: 'block',
|
||||
id: 'matchesReplaceMap[1]',
|
||||
flavour: 'affine:paragraph',
|
||||
props: {
|
||||
type: 'text',
|
||||
text: {
|
||||
'$blocksuite:internal:text$': true,
|
||||
delta: [
|
||||
{
|
||||
insert:
|
||||
'prompt: How many people will live in the world in 2040?',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
workspaceId: '',
|
||||
pageId: '',
|
||||
};
|
||||
|
||||
const mdAdapter = new MarkdownAdapter(createJob(), provider);
|
||||
const rawSliceSnapshot = await mdAdapter.toSliceSnapshot({
|
||||
file: markdown,
|
||||
workspaceId: '',
|
||||
pageId: '',
|
||||
});
|
||||
expect(nanoidReplacement(rawSliceSnapshot!)).toEqual(sliceSnapshot);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export * from '@blocksuite/affine-widget-linked-doc';
|
||||
@@ -52,6 +52,7 @@
|
||||
{ "path": "../widgets/edgeless-auto-connect" },
|
||||
{ "path": "../widgets/edgeless-toolbar" },
|
||||
{ "path": "../widgets/frame-title" },
|
||||
{ "path": "../widgets/linked-doc" },
|
||||
{ "path": "../widgets/remote-selection" },
|
||||
{ "path": "../widgets/scroll-anchoring" },
|
||||
{ "path": "../widgets/slash-menu" },
|
||||
|
||||
@@ -81,21 +81,10 @@ export class BookmarkCard extends SignalWatcher(
|
||||
const theme = this.bookmark.std.get(ThemeProvider).theme;
|
||||
const { LoadingIcon, EmbedCardBannerIcon } = getEmbedCardIcons(theme);
|
||||
|
||||
const titleIconType =
|
||||
!icon?.split('.').pop() || icon?.split('.').pop() === 'svg'
|
||||
? 'svg+xml'
|
||||
: icon?.split('.').pop();
|
||||
|
||||
const titleIcon = this.loading
|
||||
? LoadingIcon
|
||||
: icon
|
||||
? html`<object
|
||||
type="image/${titleIconType}"
|
||||
data=${icon}
|
||||
draggable="false"
|
||||
>
|
||||
${WebIcon16}
|
||||
</object>`
|
||||
? html`<img src=${icon} alt="icon" />`
|
||||
: WebIcon16;
|
||||
|
||||
const descriptionText = this.loading
|
||||
@@ -108,9 +97,7 @@ export class BookmarkCard extends SignalWatcher(
|
||||
|
||||
const bannerImage =
|
||||
!this.loading && image
|
||||
? html`<object type="image/webp" data=${image} draggable="false">
|
||||
${EmbedCardBannerIcon}
|
||||
</object>`
|
||||
? html`<img src=${image} alt="banner" />`
|
||||
: EmbedCardBannerIcon;
|
||||
|
||||
return html`
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@blocksuite/affine-components": "workspace:*",
|
||||
"@blocksuite/affine-gfx-turbo-renderer": "workspace:*",
|
||||
"@blocksuite/affine-inline-latex": "workspace:*",
|
||||
"@blocksuite/affine-inline-link": "workspace:*",
|
||||
"@blocksuite/affine-inline-preset": "workspace:*",
|
||||
@@ -35,7 +36,8 @@
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./effects": "./src/effects.ts"
|
||||
"./effects": "./src/effects.ts",
|
||||
"./turbo-painter": "./src/turbo/code-painter.worker.ts"
|
||||
},
|
||||
"files": [
|
||||
"src",
|
||||
|
||||
@@ -2,6 +2,7 @@ import {
|
||||
type MarkdownAdapterPreprocessor,
|
||||
MarkdownPreprocessorExtension,
|
||||
} from '@blocksuite/affine-shared/adapters';
|
||||
import { isValidUrl } from '@blocksuite/affine-shared/utils';
|
||||
|
||||
const codePreprocessor: MarkdownAdapterPreprocessor = {
|
||||
name: 'code',
|
||||
@@ -53,14 +54,9 @@ const codePreprocessor: MarkdownAdapterPreprocessor = {
|
||||
//
|
||||
// eg. /MuawcBMT1Mzvoar09-_66?mode=page&blockIds=rL2_GXbtLU2SsJVfCSmh_
|
||||
// https://www.markdownguide.org/basic-syntax/#urls-and-email-addresses
|
||||
try {
|
||||
const valid =
|
||||
URL.canParse?.(trimmedLine) ?? Boolean(new URL(trimmedLine));
|
||||
if (valid) {
|
||||
return `<${trimmedLine}>`;
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
const valid = isValidUrl(trimmedLine);
|
||||
if (valid) {
|
||||
return `<${trimmedLine}>`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { affineTextStyles } from '@blocksuite/affine-shared/styles';
|
||||
import type { AffineTextAttributes } from '@blocksuite/affine-shared/types';
|
||||
import { ShadowlessElement } from '@blocksuite/std';
|
||||
import { ZERO_WIDTH_SPACE } from '@blocksuite/std/inline';
|
||||
import { ZERO_WIDTH_FOR_EMPTY_LINE } from '@blocksuite/std/inline';
|
||||
import type { DeltaInsert } from '@blocksuite/store';
|
||||
import { html } from 'lit';
|
||||
import { property } from 'lit/decorators.js';
|
||||
@@ -111,7 +111,7 @@ export class AffineCodeUnit extends ShadowlessElement {
|
||||
|
||||
@property({ type: Object })
|
||||
accessor delta: DeltaInsert<AffineTextAttributes> = {
|
||||
insert: ZERO_WIDTH_SPACE,
|
||||
insert: ZERO_WIDTH_FOR_EMPTY_LINE,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -3,3 +3,5 @@ export * from './code-block';
|
||||
export * from './code-block-config';
|
||||
export * from './code-block-spec';
|
||||
export * from './code-toolbar';
|
||||
export * from './turbo/code-layout-handler';
|
||||
export * from './turbo/code-painter.worker';
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
import type { Rect } from '@blocksuite/affine-gfx-turbo-renderer';
|
||||
import {
|
||||
BlockLayoutHandlerExtension,
|
||||
BlockLayoutHandlersIdentifier,
|
||||
} from '@blocksuite/affine-gfx-turbo-renderer';
|
||||
import type { Container } from '@blocksuite/global/di';
|
||||
import type { EditorHost, GfxBlockComponent } from '@blocksuite/std';
|
||||
import { clientToModelCoord, type ViewportRecord } from '@blocksuite/std/gfx';
|
||||
import type { BlockModel } from '@blocksuite/store';
|
||||
|
||||
import type { CodeLayout } from './code-painter.worker';
|
||||
|
||||
export class CodeLayoutHandlerExtension extends BlockLayoutHandlerExtension<CodeLayout> {
|
||||
readonly blockType = 'affine:code';
|
||||
|
||||
static override setup(di: Container) {
|
||||
di.addImpl(
|
||||
BlockLayoutHandlersIdentifier('code'),
|
||||
CodeLayoutHandlerExtension
|
||||
);
|
||||
}
|
||||
|
||||
override queryLayout(
|
||||
model: BlockModel,
|
||||
host: EditorHost,
|
||||
viewportRecord: ViewportRecord
|
||||
): CodeLayout | null {
|
||||
const component = host.std.view.getBlock(model.id) as GfxBlockComponent;
|
||||
if (!component) return null;
|
||||
|
||||
const codeBlockElement = component.querySelector(
|
||||
'.affine-code-block-container'
|
||||
);
|
||||
if (!codeBlockElement) return null;
|
||||
|
||||
const { zoom, viewScale } = viewportRecord;
|
||||
const codeLayout: CodeLayout = {
|
||||
type: 'affine:code',
|
||||
blockId: model.id,
|
||||
rect: { x: 0, y: 0, w: 0, h: 0 },
|
||||
};
|
||||
|
||||
// Get the bounding rect of the code block
|
||||
const clientRect = codeBlockElement.getBoundingClientRect();
|
||||
if (!clientRect) return null;
|
||||
|
||||
// Convert client coordinates to model coordinates
|
||||
const [modelX, modelY] = clientToModelCoord(viewportRecord, [
|
||||
clientRect.x,
|
||||
clientRect.y,
|
||||
]);
|
||||
|
||||
codeLayout.rect = {
|
||||
x: modelX,
|
||||
y: modelY,
|
||||
w: clientRect.width / zoom / viewScale,
|
||||
h: clientRect.height / zoom / viewScale,
|
||||
};
|
||||
|
||||
return codeLayout;
|
||||
}
|
||||
|
||||
calculateBound(layout: CodeLayout) {
|
||||
const rect: Rect = layout.rect;
|
||||
|
||||
return {
|
||||
rect,
|
||||
subRects: [rect],
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import type {
|
||||
BlockLayout,
|
||||
BlockLayoutPainter,
|
||||
WorkerToHostMessage,
|
||||
} from '@blocksuite/affine-gfx-turbo-renderer';
|
||||
import { BlockLayoutPainterExtension } from '@blocksuite/affine-gfx-turbo-renderer/painter';
|
||||
|
||||
export interface CodeLayout extends BlockLayout {
|
||||
type: 'affine:code';
|
||||
}
|
||||
|
||||
function isCodeLayout(layout: BlockLayout): layout is CodeLayout {
|
||||
return layout.type === 'affine:code';
|
||||
}
|
||||
|
||||
class CodeLayoutPainter implements BlockLayoutPainter {
|
||||
paint(
|
||||
ctx: OffscreenCanvasRenderingContext2D,
|
||||
layout: BlockLayout,
|
||||
layoutBaseX: number,
|
||||
layoutBaseY: number
|
||||
): void {
|
||||
if (!isCodeLayout(layout)) {
|
||||
const message: WorkerToHostMessage = {
|
||||
type: 'paintError',
|
||||
error: 'Invalid layout format',
|
||||
blockType: 'affine:code',
|
||||
};
|
||||
self.postMessage(message);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the layout dimensions
|
||||
const x = layout.rect.x - layoutBaseX;
|
||||
const y = layout.rect.y - layoutBaseY;
|
||||
const width = layout.rect.w;
|
||||
const height = layout.rect.h;
|
||||
|
||||
// Simple white rectangle for now
|
||||
ctx.fillStyle = 'white';
|
||||
ctx.fillRect(x, y, width, height);
|
||||
|
||||
// Add a border to visualize the code block
|
||||
ctx.strokeStyle = 'rgba(0, 0, 0, 0.1)';
|
||||
ctx.lineWidth = 1;
|
||||
ctx.strokeRect(x, y, width, height);
|
||||
}
|
||||
}
|
||||
|
||||
export const CodeLayoutPainterExtension = BlockLayoutPainterExtension(
|
||||
'affine:code',
|
||||
CodeLayoutPainter
|
||||
);
|
||||
@@ -8,6 +8,7 @@
|
||||
"include": ["./src"],
|
||||
"references": [
|
||||
{ "path": "../../components" },
|
||||
{ "path": "../../gfx/turbo-renderer" },
|
||||
{ "path": "../../inlines/latex" },
|
||||
{ "path": "../../inlines/link" },
|
||||
{ "path": "../../inlines/preset" },
|
||||
|
||||
@@ -259,7 +259,7 @@ export class EdgelessTextBlockComponent extends GfxBlockComponent<EdgelessTextBl
|
||||
};
|
||||
}
|
||||
|
||||
override onSelected(context: SelectedContext) {
|
||||
override onSelected(context: SelectedContext): void | boolean {
|
||||
const { selected, multiSelect, event: e } = context;
|
||||
const { editing } = this.gfx.selection;
|
||||
const alreadySelected = this.gfx.selection.has(this.model.id);
|
||||
@@ -318,7 +318,7 @@ export class EdgelessTextBlockComponent extends GfxBlockComponent<EdgelessTextBl
|
||||
})
|
||||
.catch(console.error);
|
||||
} else {
|
||||
super.onSelected(context);
|
||||
return super.onSelected(context);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import { EmbedLinkedDocBlockSchema } from '@blocksuite/affine-model';
|
||||
import {
|
||||
getInlineEditorByModel,
|
||||
insertContent,
|
||||
} from '@blocksuite/affine-rich-text';
|
||||
import { insertContent } from '@blocksuite/affine-rich-text';
|
||||
import { REFERENCE_NODE } from '@blocksuite/affine-shared/consts';
|
||||
import { createDefaultDoc } from '@blocksuite/affine-shared/utils';
|
||||
import {
|
||||
@@ -68,22 +65,7 @@ const linkedDocSlashMenuConfig: SlashMenuConfig = {
|
||||
if (!linkedDocWidget) return;
|
||||
// TODO(@L-Sun): make linked-doc-widget as extension
|
||||
// @ts-expect-error same as above
|
||||
const triggerKey = linkedDocWidget.config.triggerKeys[0];
|
||||
|
||||
insertContent(std, model, triggerKey);
|
||||
|
||||
const inlineEditor = getInlineEditorByModel(std, model);
|
||||
if (inlineEditor) {
|
||||
// Wait for range to be updated
|
||||
const subscription = inlineEditor.slots.inlineRangeSync.subscribe(
|
||||
() => {
|
||||
// TODO(@L-Sun): make linked-doc-widget as extension
|
||||
subscription.unsubscribe();
|
||||
// @ts-expect-error same as above
|
||||
linkedDocWidget.show({ addTriggerKey: true });
|
||||
}
|
||||
);
|
||||
}
|
||||
linkedDocWidget.show({ addTriggerKey: true });
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
@@ -53,7 +53,7 @@ export class FrameBlockComponent extends GfxBlockComponent<FrameBlockModel> {
|
||||
};
|
||||
}
|
||||
|
||||
override onSelected(context: SelectedContext): void {
|
||||
override onSelected(context: SelectedContext): boolean | void {
|
||||
const { x, y } = context.position;
|
||||
|
||||
if (
|
||||
@@ -63,10 +63,10 @@ export class FrameBlockComponent extends GfxBlockComponent<FrameBlockModel> {
|
||||
// otherwise if the frame has title, then ignore it because in this case the frame cannot be selected by frame body
|
||||
this.model.props.title.length)
|
||||
) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
super.onSelected(context);
|
||||
return super.onSelected(context);
|
||||
}
|
||||
|
||||
override renderGfxBlock() {
|
||||
|
||||
@@ -80,7 +80,7 @@ const builtinSurfaceToolbarConfig = {
|
||||
|
||||
ctx.store.addBlock(
|
||||
SurfaceRefBlockSchema.model.flavour,
|
||||
{ reference: frameId, refFlavour: NoteBlockSchema.model.flavour },
|
||||
{ reference: frameId, refFlavour: FrameBlockSchema.model.flavour },
|
||||
lastNoteId
|
||||
);
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"@blocksuite/affine-block-note": "workspace:*",
|
||||
"@blocksuite/affine-block-surface": "workspace:*",
|
||||
"@blocksuite/affine-components": "workspace:*",
|
||||
"@blocksuite/affine-gfx-turbo-renderer": "workspace:*",
|
||||
"@blocksuite/affine-model": "workspace:*",
|
||||
"@blocksuite/affine-shared": "workspace:*",
|
||||
"@blocksuite/affine-widget-slash-menu": "workspace:*",
|
||||
@@ -32,7 +33,8 @@
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./effects": "./src/effects.ts"
|
||||
"./effects": "./src/effects.ts",
|
||||
"./turbo-painter": "./src/turbo/image-painter.worker.ts"
|
||||
},
|
||||
"files": [
|
||||
"src",
|
||||
|
||||
@@ -7,5 +7,7 @@ export { ImageProxyService } from './image-proxy-service';
|
||||
export * from './image-service';
|
||||
export * from './image-spec';
|
||||
export * from './styles';
|
||||
export * from './turbo/image-layout-handler';
|
||||
export * from './turbo/image-painter.worker';
|
||||
export { addImages, downloadImageBlob, uploadBlobForImage } from './utils';
|
||||
export { ImageSelection } from '@blocksuite/affine-shared/selection';
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
import type { Rect } from '@blocksuite/affine-gfx-turbo-renderer';
|
||||
import {
|
||||
BlockLayoutHandlerExtension,
|
||||
BlockLayoutHandlersIdentifier,
|
||||
} from '@blocksuite/affine-gfx-turbo-renderer';
|
||||
import type { Container } from '@blocksuite/global/di';
|
||||
import type { EditorHost, GfxBlockComponent } from '@blocksuite/std';
|
||||
import { clientToModelCoord, type ViewportRecord } from '@blocksuite/std/gfx';
|
||||
import type { BlockModel } from '@blocksuite/store';
|
||||
|
||||
import type { ImageLayout } from './image-painter.worker';
|
||||
|
||||
export class ImageLayoutHandlerExtension extends BlockLayoutHandlerExtension<ImageLayout> {
|
||||
readonly blockType = 'affine:image';
|
||||
|
||||
static override setup(di: Container) {
|
||||
di.addImpl(
|
||||
BlockLayoutHandlersIdentifier('image'),
|
||||
ImageLayoutHandlerExtension
|
||||
);
|
||||
}
|
||||
|
||||
override queryLayout(
|
||||
model: BlockModel,
|
||||
host: EditorHost,
|
||||
viewportRecord: ViewportRecord
|
||||
): ImageLayout | null {
|
||||
const component = host.std.view.getBlock(model.id) as GfxBlockComponent;
|
||||
if (!component) return null;
|
||||
|
||||
const imageContainer = component.querySelector('.affine-image-container');
|
||||
if (!imageContainer) return null;
|
||||
|
||||
const resizableImg = component.querySelector(
|
||||
'.resizable-img'
|
||||
) as HTMLElement;
|
||||
if (!resizableImg) return null;
|
||||
|
||||
const { zoom, viewScale } = viewportRecord;
|
||||
const rect = resizableImg.getBoundingClientRect();
|
||||
|
||||
const [modelX, modelY] = clientToModelCoord(viewportRecord, [
|
||||
rect.x,
|
||||
rect.y,
|
||||
]);
|
||||
|
||||
const imageLayout: ImageLayout = {
|
||||
type: 'affine:image',
|
||||
blockId: model.id,
|
||||
rect: {
|
||||
x: modelX,
|
||||
y: modelY,
|
||||
w: rect.width / zoom / viewScale,
|
||||
h: rect.height / zoom / viewScale,
|
||||
},
|
||||
};
|
||||
|
||||
return imageLayout;
|
||||
}
|
||||
|
||||
calculateBound(layout: ImageLayout) {
|
||||
const rect: Rect = layout.rect;
|
||||
|
||||
return {
|
||||
rect,
|
||||
subRects: [rect],
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import type {
|
||||
BlockLayout,
|
||||
BlockLayoutPainter,
|
||||
} from '@blocksuite/affine-gfx-turbo-renderer';
|
||||
import { BlockLayoutPainterExtension } from '@blocksuite/affine-gfx-turbo-renderer/painter';
|
||||
|
||||
export interface ImageLayout extends BlockLayout {
|
||||
type: 'affine:image';
|
||||
rect: {
|
||||
x: number;
|
||||
y: number;
|
||||
w: number;
|
||||
h: number;
|
||||
};
|
||||
}
|
||||
|
||||
function isImageLayout(layout: BlockLayout): layout is ImageLayout {
|
||||
return layout.type === 'affine:image';
|
||||
}
|
||||
|
||||
class ImageLayoutPainter implements BlockLayoutPainter {
|
||||
paint(
|
||||
ctx: OffscreenCanvasRenderingContext2D,
|
||||
layout: BlockLayout,
|
||||
layoutBaseX: number,
|
||||
layoutBaseY: number
|
||||
): void {
|
||||
if (!isImageLayout(layout)) {
|
||||
console.warn(
|
||||
'Expected image layout but received different format:',
|
||||
layout
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// For now, just paint a white rectangle
|
||||
const x = layout.rect.x - layoutBaseX;
|
||||
const y = layout.rect.y - layoutBaseY;
|
||||
const width = layout.rect.w;
|
||||
const height = layout.rect.h;
|
||||
|
||||
// Draw a white rectangle with border
|
||||
ctx.fillStyle = 'white';
|
||||
ctx.fillRect(x, y, width, height);
|
||||
|
||||
// Add a border
|
||||
ctx.strokeStyle = '#e0e0e0';
|
||||
ctx.lineWidth = 1;
|
||||
ctx.strokeRect(x, y, width, height);
|
||||
}
|
||||
}
|
||||
|
||||
export const ImageLayoutPainterExtension = BlockLayoutPainterExtension(
|
||||
'affine:image',
|
||||
ImageLayoutPainter
|
||||
);
|
||||
@@ -10,6 +10,7 @@
|
||||
{ "path": "../note" },
|
||||
{ "path": "../surface" },
|
||||
{ "path": "../../components" },
|
||||
{ "path": "../../gfx/turbo-renderer" },
|
||||
{ "path": "../../model" },
|
||||
{ "path": "../../shared" },
|
||||
{ "path": "../../widgets/slash-menu" },
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
"@blocksuite/affine-widget-edgeless-auto-connect": "workspace:*",
|
||||
"@blocksuite/affine-widget-edgeless-toolbar": "workspace:*",
|
||||
"@blocksuite/affine-widget-frame-title": "workspace:*",
|
||||
"@blocksuite/affine-widget-linked-doc": "workspace:*",
|
||||
"@blocksuite/affine-widget-remote-selection": "workspace:*",
|
||||
"@blocksuite/affine-widget-scroll-anchoring": "workspace:*",
|
||||
"@blocksuite/affine-widget-slash-menu": "workspace:*",
|
||||
|
||||
@@ -33,6 +33,7 @@ import {
|
||||
ToolbarRegistryExtension,
|
||||
} from '@blocksuite/affine-shared/services';
|
||||
import { dragHandleWidget } from '@blocksuite/affine-widget-drag-handle';
|
||||
import { linkedDocWidget } from '@blocksuite/affine-widget-linked-doc';
|
||||
import { docRemoteSelectionWidget } from '@blocksuite/affine-widget-remote-selection';
|
||||
import { scrollAnchoringWidget } from '@blocksuite/affine-widget-scroll-anchoring';
|
||||
import { SlashMenuExtension } from '@blocksuite/affine-widget-slash-menu';
|
||||
@@ -44,7 +45,7 @@ import { RootBlockAdapterExtensions } from '../adapters/extension';
|
||||
import { clipboardConfigs } from '../clipboard';
|
||||
import { builtinToolbarConfig } from '../configs/toolbar';
|
||||
import { fallbackKeymap } from '../keyboard/keymap';
|
||||
import { linkedDocWidget, modalWidget, viewportOverlayWidget } from './widgets';
|
||||
import { modalWidget, viewportOverlayWidget } from './widgets';
|
||||
|
||||
/**
|
||||
* Why do we add these extensions into CommonSpecs?
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { WidgetViewExtension } from '@blocksuite/std';
|
||||
import { literal, unsafeStatic } from 'lit/static-html.js';
|
||||
|
||||
import { AFFINE_LINKED_DOC_WIDGET } from '../widgets/linked-doc/config.js';
|
||||
import { AFFINE_MODAL_WIDGET } from '../widgets/modal/modal.js';
|
||||
import { AFFINE_VIEWPORT_OVERLAY_WIDGET } from '../widgets/viewport-overlay/viewport-overlay.js';
|
||||
|
||||
@@ -10,11 +9,6 @@ export const modalWidget = WidgetViewExtension(
|
||||
AFFINE_MODAL_WIDGET,
|
||||
literal`${unsafeStatic(AFFINE_MODAL_WIDGET)}`
|
||||
);
|
||||
export const linkedDocWidget = WidgetViewExtension(
|
||||
'affine:page',
|
||||
AFFINE_LINKED_DOC_WIDGET,
|
||||
literal`${unsafeStatic(AFFINE_LINKED_DOC_WIDGET)}`
|
||||
);
|
||||
export const viewportOverlayWidget = WidgetViewExtension(
|
||||
'affine:page',
|
||||
AFFINE_VIEWPORT_OVERLAY_WIDGET,
|
||||
|
||||
@@ -88,6 +88,9 @@ export class EdgelessPageKeyboardManager extends PageKeyboardManager {
|
||||
p: () => {
|
||||
this._setEdgelessTool('brush');
|
||||
},
|
||||
'Shift-p': () => {
|
||||
this._setEdgelessTool('highlighter');
|
||||
},
|
||||
e: () => {
|
||||
this._setEdgelessTool('eraser');
|
||||
},
|
||||
|
||||
@@ -45,7 +45,6 @@ import { css, html } from 'lit';
|
||||
import { query } from 'lit/decorators.js';
|
||||
import { repeat } from 'lit/directives/repeat.js';
|
||||
|
||||
import type { EdgelessRootBlockWidgetName } from '../types.js';
|
||||
import type { EdgelessSelectedRectWidget } from './components/rects/edgeless-selected-rect.js';
|
||||
import { EdgelessPageKeyboardManager } from './edgeless-keyboard.js';
|
||||
import type { EdgelessRootService } from './edgeless-root-service.js';
|
||||
@@ -53,8 +52,7 @@ import { isCanvasElement } from './utils/query.js';
|
||||
|
||||
export class EdgelessRootBlockComponent extends BlockComponent<
|
||||
RootBlockModel,
|
||||
EdgelessRootService,
|
||||
EdgelessRootBlockWidgetName
|
||||
EdgelessRootService
|
||||
> {
|
||||
static override styles = css`
|
||||
affine-edgeless-root {
|
||||
|
||||
@@ -25,14 +25,12 @@ import { css, html } from 'lit';
|
||||
import { query, state } from 'lit/decorators.js';
|
||||
import { type StyleInfo, styleMap } from 'lit/directives/style-map.js';
|
||||
|
||||
import type { EdgelessRootBlockWidgetName } from '../types.js';
|
||||
import type { EdgelessRootService } from './edgeless-root-service.js';
|
||||
import { isCanvasElement } from './utils/query.js';
|
||||
|
||||
export class EdgelessRootPreviewBlockComponent extends BlockComponent<
|
||||
RootBlockModel,
|
||||
EdgelessRootService,
|
||||
EdgelessRootBlockWidgetName
|
||||
EdgelessRootService
|
||||
> {
|
||||
static override styles = css`
|
||||
affine-edgeless-root-preview {
|
||||
|
||||
@@ -7,6 +7,7 @@ import { effects as gfxShapeEffects } from '@blocksuite/affine-gfx-shape/effects
|
||||
import { effects as gfxTemplateEffects } from '@blocksuite/affine-gfx-template/effects';
|
||||
import { effects as gfxCanvasTextEffects } from '@blocksuite/affine-gfx-text/effects';
|
||||
import { effects as widgetEdgelessToolbarEffects } from '@blocksuite/affine-widget-edgeless-toolbar/effects';
|
||||
import { effects as widgetLinkedDocEffects } from '@blocksuite/affine-widget-linked-doc/effects';
|
||||
|
||||
import { EdgelessAutoCompletePanel } from './edgeless/components/auto-complete/auto-complete-panel.js';
|
||||
import { EdgelessAutoComplete } from './edgeless/components/auto-complete/edgeless-auto-complete.js';
|
||||
@@ -44,8 +45,6 @@ import {
|
||||
import { ZoomBarToggleButton } from './widgets/edgeless-zoom-toolbar/zoom-bar-toggle-button.js';
|
||||
import { EdgelessZoomToolbar } from './widgets/edgeless-zoom-toolbar/zoom-toolbar.js';
|
||||
import { effects as widgetMobileToolbarEffects } from './widgets/keyboard-toolbar/effects.js';
|
||||
import { effects as widgetLinkedDocEffects } from './widgets/linked-doc/effects.js';
|
||||
import { Loader } from './widgets/linked-doc/import-doc/loader.js';
|
||||
import { AffineCustomModal } from './widgets/modal/custom-modal.js';
|
||||
import { AFFINE_MODAL_WIDGET } from './widgets/modal/modal.js';
|
||||
import {
|
||||
@@ -127,9 +126,6 @@ function registerMiscComponents() {
|
||||
// Modal and menu components
|
||||
customElements.define('affine-custom-modal', AffineCustomModal);
|
||||
|
||||
// Loading and preview components
|
||||
customElements.define('loader-element', Loader);
|
||||
|
||||
// Toolbar and UI components
|
||||
customElements.define('edgeless-zoom-toolbar', EdgelessZoomToolbar);
|
||||
customElements.define('zoom-bar-toggle-button', ZoomBarToggleButton);
|
||||
|
||||
@@ -10,7 +10,6 @@ export * from './page/page-root-spec.js';
|
||||
export * from './preview/preview-root-block.js';
|
||||
export * from './root-config.js';
|
||||
export { RootService } from './root-service.js';
|
||||
export * from './transformers/index.js';
|
||||
export * from './types.js';
|
||||
export * from './utils/index.js';
|
||||
export * from './widgets/index.js';
|
||||
|
||||
@@ -27,7 +27,6 @@ import { css, html } from 'lit';
|
||||
import { query } from 'lit/decorators.js';
|
||||
import { repeat } from 'lit/directives/repeat.js';
|
||||
|
||||
import type { PageRootBlockWidgetName } from '../index.js';
|
||||
import { PageKeyboardManager } from '../keyboard/keyboard-manager.js';
|
||||
import type { PageRootService } from './page-root-service.js';
|
||||
|
||||
@@ -52,8 +51,7 @@ function testClickOnBlankArea(
|
||||
|
||||
export class PageRootBlockComponent extends BlockComponent<
|
||||
RootBlockModel,
|
||||
PageRootService,
|
||||
PageRootBlockWidgetName
|
||||
PageRootService
|
||||
> {
|
||||
static override styles = css`
|
||||
editor-host:has(> affine-page-root, * > affine-page-root) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { ViewportElementExtension } from '@blocksuite/affine-shared/services';
|
||||
import { IS_MOBILE } from '@blocksuite/global/env';
|
||||
import { BlockViewExtension, WidgetViewExtension } from '@blocksuite/std';
|
||||
import type { ExtensionType } from '@blocksuite/store';
|
||||
import { literal, unsafeStatic } from 'lit/static-html.js';
|
||||
@@ -31,7 +32,7 @@ const PageCommonExtension: ExtensionType[] = [
|
||||
export const PageRootBlockSpec: ExtensionType[] = [
|
||||
...PageCommonExtension,
|
||||
BlockViewExtension('affine:page', literal`affine-page-root`),
|
||||
keyboardToolbarWidget,
|
||||
IS_MOBILE ? [keyboardToolbarWidget] : [],
|
||||
PageClipboard,
|
||||
].flat();
|
||||
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import { ConfigExtensionFactory } from '@blocksuite/std';
|
||||
|
||||
import type { KeyboardToolbarConfig } from './widgets/keyboard-toolbar/config.js';
|
||||
import type { LinkedWidgetConfig } from './widgets/linked-doc/index.js';
|
||||
|
||||
export interface RootBlockConfig {
|
||||
linkedWidget?: Partial<LinkedWidgetConfig>;
|
||||
keyboardToolbar?: Partial<KeyboardToolbarConfig>;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,40 +1,5 @@
|
||||
import type { AFFINE_DRAG_HANDLE_WIDGET } from '@blocksuite/affine-widget-drag-handle';
|
||||
import type { AFFINE_FRAME_TITLE_WIDGET } from '@blocksuite/affine-widget-frame-title';
|
||||
import type {
|
||||
AFFINE_DOC_REMOTE_SELECTION_WIDGET,
|
||||
AFFINE_EDGELESS_REMOTE_SELECTION_WIDGET,
|
||||
} from '@blocksuite/affine-widget-remote-selection';
|
||||
import type { AFFINE_SLASH_MENU_WIDGET } from '@blocksuite/affine-widget-slash-menu';
|
||||
|
||||
import type { EdgelessRootBlockComponent } from './edgeless/edgeless-root-block.js';
|
||||
import type { PageRootBlockComponent } from './page/page-root-block.js';
|
||||
import type { AFFINE_EDGELESS_ZOOM_TOOLBAR_WIDGET } from './widgets/edgeless-zoom-toolbar/index.js';
|
||||
import type { AFFINE_KEYBOARD_TOOLBAR_WIDGET } from './widgets/index.js';
|
||||
import type { AFFINE_LINKED_DOC_WIDGET } from './widgets/linked-doc/config.js';
|
||||
import type { AFFINE_MODAL_WIDGET } from './widgets/modal/modal.js';
|
||||
import type { AFFINE_PAGE_DRAGGING_AREA_WIDGET } from './widgets/page-dragging-area/page-dragging-area.js';
|
||||
import type { AFFINE_VIEWPORT_OVERLAY_WIDGET } from './widgets/viewport-overlay/viewport-overlay.js';
|
||||
|
||||
export type PageRootBlockWidgetName =
|
||||
| typeof AFFINE_KEYBOARD_TOOLBAR_WIDGET
|
||||
| typeof AFFINE_MODAL_WIDGET
|
||||
| typeof AFFINE_SLASH_MENU_WIDGET
|
||||
| typeof AFFINE_LINKED_DOC_WIDGET
|
||||
| typeof AFFINE_PAGE_DRAGGING_AREA_WIDGET
|
||||
| typeof AFFINE_DRAG_HANDLE_WIDGET
|
||||
| typeof AFFINE_DOC_REMOTE_SELECTION_WIDGET
|
||||
| typeof AFFINE_VIEWPORT_OVERLAY_WIDGET;
|
||||
|
||||
export type EdgelessRootBlockWidgetName =
|
||||
| typeof AFFINE_MODAL_WIDGET
|
||||
| typeof AFFINE_SLASH_MENU_WIDGET
|
||||
| typeof AFFINE_LINKED_DOC_WIDGET
|
||||
| typeof AFFINE_DRAG_HANDLE_WIDGET
|
||||
| typeof AFFINE_DOC_REMOTE_SELECTION_WIDGET
|
||||
| typeof AFFINE_EDGELESS_REMOTE_SELECTION_WIDGET
|
||||
| typeof AFFINE_EDGELESS_ZOOM_TOOLBAR_WIDGET
|
||||
| typeof AFFINE_VIEWPORT_OVERLAY_WIDGET
|
||||
| typeof AFFINE_FRAME_TITLE_WIDGET;
|
||||
|
||||
export type RootBlockComponent =
|
||||
| PageRootBlockComponent
|
||||
|
||||
@@ -1,17 +1,5 @@
|
||||
export { AffineEdgelessZoomToolbarWidget } from './edgeless-zoom-toolbar/index.js';
|
||||
export * from './keyboard-toolbar/index.js';
|
||||
export {
|
||||
type LinkedMenuAction,
|
||||
type LinkedMenuGroup,
|
||||
type LinkedMenuItem,
|
||||
type LinkedWidgetConfig,
|
||||
LinkedWidgetUtils,
|
||||
} from './linked-doc/config.js';
|
||||
export {
|
||||
// It's used in the AFFiNE!
|
||||
showImportModal,
|
||||
} from './linked-doc/import-doc/index.js';
|
||||
export { AffineLinkedDocWidget } from './linked-doc/index.js';
|
||||
export { AffineModalWidget } from './modal/modal.js';
|
||||
export { AffinePageDraggingAreaWidget } from './page-dragging-area/page-dragging-area.js';
|
||||
export * from './viewport-overlay/viewport-overlay.js';
|
||||
|
||||
@@ -34,10 +34,7 @@ import {
|
||||
toggleUnderline,
|
||||
} from '@blocksuite/affine-inline-preset';
|
||||
import type { FrameBlockModel } from '@blocksuite/affine-model';
|
||||
import {
|
||||
getInlineEditorByModel,
|
||||
insertContent,
|
||||
} from '@blocksuite/affine-rich-text';
|
||||
import { insertContent } from '@blocksuite/affine-rich-text';
|
||||
import {
|
||||
copySelectedModelsCommand,
|
||||
deleteSelectedModelsCommand,
|
||||
@@ -55,6 +52,7 @@ import {
|
||||
openFileOrFiles,
|
||||
type Signal,
|
||||
} from '@blocksuite/affine-shared/utils';
|
||||
import type { AffineLinkedDocWidget } from '@blocksuite/affine-widget-linked-doc';
|
||||
import { viewPresets } from '@blocksuite/data-view/view-presets';
|
||||
import { assertType } from '@blocksuite/global/utils';
|
||||
import {
|
||||
@@ -105,7 +103,6 @@ import { cssVarV2 } from '@toeverything/theme/v2';
|
||||
import type { TemplateResult } from 'lit';
|
||||
|
||||
import type { PageRootBlockComponent } from '../../page/page-root-block.js';
|
||||
import type { AffineLinkedDocWidget } from '../linked-doc/index.js';
|
||||
import {
|
||||
FigmaDuotoneIcon,
|
||||
HeadingIcon,
|
||||
@@ -348,35 +345,11 @@ const pageToolGroup: KeyboardToolPanelGroup = {
|
||||
);
|
||||
if (!linkedDocWidget) return;
|
||||
assertType<AffineLinkedDocWidget>(linkedDocWidget);
|
||||
|
||||
const triggerKey = linkedDocWidget.config.triggerKeys[0];
|
||||
|
||||
std.command
|
||||
.chain()
|
||||
.pipe(getSelectedModelsCommand)
|
||||
.pipe(ctx => {
|
||||
const { selectedModels } = ctx;
|
||||
if (!selectedModels?.length) return;
|
||||
|
||||
const currentModel = selectedModels[0];
|
||||
insertContent(std, currentModel, triggerKey);
|
||||
|
||||
const inlineEditor = getInlineEditorByModel(std, currentModel);
|
||||
// Wait for range to be updated
|
||||
if (inlineEditor) {
|
||||
const subscription = inlineEditor.slots.inlineRangeSync.subscribe(
|
||||
() => {
|
||||
subscription.unsubscribe();
|
||||
linkedDocWidget.show({
|
||||
mode: 'mobile',
|
||||
addTriggerKey: true,
|
||||
});
|
||||
closeToolPanel();
|
||||
}
|
||||
);
|
||||
}
|
||||
})
|
||||
.run();
|
||||
linkedDocWidget.show({
|
||||
mode: 'mobile',
|
||||
addTriggerKey: true,
|
||||
});
|
||||
closeToolPanel();
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -428,8 +401,7 @@ const contentMediaToolGroup: KeyboardToolPanelGroup = {
|
||||
{
|
||||
name: 'Attachment',
|
||||
icon: AttachmentIcon(),
|
||||
showWhen: ({ std }) =>
|
||||
std.store.schema.flavourSchemaMap.has('affine:attachment'),
|
||||
showWhen: () => false,
|
||||
action: async ({ std }) => {
|
||||
const [_, { selectedModels }] = std.command.exec(
|
||||
getSelectedModelsCommand
|
||||
@@ -1029,8 +1001,7 @@ export const defaultKeyboardToolbarConfig: KeyboardToolbarConfig = {
|
||||
{
|
||||
name: 'Attachment',
|
||||
icon: AttachmentIcon(),
|
||||
showWhen: ({ std }) =>
|
||||
std.store.schema.flavourSchemaMap.has('affine:attachment'),
|
||||
showWhen: () => false,
|
||||
action: async ({ std }) => {
|
||||
const [_, { selectedModels }] = std.command.exec(
|
||||
getSelectedModelsCommand
|
||||
|
||||
@@ -2,6 +2,7 @@ import { getDocTitleByEditorHost } from '@blocksuite/affine-fragment-doc-title';
|
||||
import type { RootBlockModel } from '@blocksuite/affine-model';
|
||||
import {
|
||||
FeatureFlagService,
|
||||
isVirtualKeyboardProviderWithAction,
|
||||
VirtualKeyboardProvider,
|
||||
type VirtualKeyboardProviderWithAction,
|
||||
} from '@blocksuite/affine-shared/services';
|
||||
@@ -34,7 +35,10 @@ export class AffineKeyboardToolbarWidget extends WidgetComponent<RootBlockModel>
|
||||
|
||||
private _initialInputMode: string = '';
|
||||
|
||||
get keyboard(): VirtualKeyboardProviderWithAction {
|
||||
get keyboard(): VirtualKeyboardProviderWithAction & { fallback?: boolean } {
|
||||
const provider = this.std.get(VirtualKeyboardProvider);
|
||||
if (isVirtualKeyboardProviderWithAction(provider)) return provider;
|
||||
|
||||
return {
|
||||
// fallback keyboard actions
|
||||
show: () => {
|
||||
@@ -49,7 +53,7 @@ export class AffineKeyboardToolbarWidget extends WidgetComponent<RootBlockModel>
|
||||
rootComponent.inputMode = 'none';
|
||||
}
|
||||
},
|
||||
...this.std.get(VirtualKeyboardProvider),
|
||||
...provider,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -70,10 +74,6 @@ export class AffineKeyboardToolbarWidget extends WidgetComponent<RootBlockModel>
|
||||
|
||||
const rootComponent = this.block?.rootComponent;
|
||||
if (rootComponent) {
|
||||
this._initialInputMode = rootComponent.inputMode;
|
||||
this.disposables.add(() => {
|
||||
rootComponent.inputMode = this._initialInputMode;
|
||||
});
|
||||
this.disposables.addFromEvent(rootComponent, 'focus', () => {
|
||||
this._show$.value = true;
|
||||
});
|
||||
@@ -81,14 +81,20 @@ export class AffineKeyboardToolbarWidget extends WidgetComponent<RootBlockModel>
|
||||
this._show$.value = false;
|
||||
});
|
||||
|
||||
this.disposables.add(
|
||||
effect(() => {
|
||||
// recover input mode when keyboard toolbar is hidden
|
||||
if (!this._show$.value) {
|
||||
rootComponent.inputMode = this._initialInputMode;
|
||||
}
|
||||
})
|
||||
);
|
||||
if (this.keyboard.fallback) {
|
||||
this._initialInputMode = rootComponent.inputMode;
|
||||
this.disposables.add(() => {
|
||||
rootComponent.inputMode = this._initialInputMode;
|
||||
});
|
||||
this.disposables.add(
|
||||
effect(() => {
|
||||
// recover input mode when keyboard toolbar is hidden
|
||||
if (!this._show$.value) {
|
||||
rootComponent.inputMode = this._initialInputMode;
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (this._docTitle) {
|
||||
|
||||
@@ -55,10 +55,8 @@ export class AffineKeyboardToolbar extends SignalWatcher(
|
||||
}
|
||||
|
||||
private readonly _closeToolPanel = () => {
|
||||
if (!this.panelOpened) return;
|
||||
|
||||
this._currentPanelIndex$.value = -1;
|
||||
this.keyboard.show();
|
||||
if (!this.keyboard.visible$.peek()) this.keyboard.show();
|
||||
};
|
||||
|
||||
private readonly _currentPanelIndex$ = signal(-1);
|
||||
@@ -255,6 +253,16 @@ export class AffineKeyboardToolbar extends SignalWatcher(
|
||||
})
|
||||
);
|
||||
|
||||
this.disposables.add(
|
||||
effect(() => {
|
||||
// sometime the keyboard will auto show when user click into different paragraph in Android,
|
||||
// so we need to close the tool panel explicitly when the keyboard is visible
|
||||
if (this.keyboard.visible$.value) {
|
||||
this._closeToolPanel();
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
this._watchAutoShow();
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
{ "path": "../../widgets/edgeless-auto-connect" },
|
||||
{ "path": "../../widgets/edgeless-toolbar" },
|
||||
{ "path": "../../widgets/frame-title" },
|
||||
{ "path": "../../widgets/linked-doc" },
|
||||
{ "path": "../../widgets/remote-selection" },
|
||||
{ "path": "../../widgets/scroll-anchoring" },
|
||||
{ "path": "../../widgets/slash-menu" },
|
||||
|
||||
@@ -38,11 +38,11 @@ const styles = css`
|
||||
transition: 0.1s;
|
||||
}
|
||||
|
||||
label.subscribe {
|
||||
label.on {
|
||||
background: var(--affine-primary-color);
|
||||
}
|
||||
|
||||
label.subscribe:after {
|
||||
label.on:after {
|
||||
left: calc(100% - 1px);
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { css, html, LitElement } from 'lit';
|
||||
import { property } from 'lit/decorators.js';
|
||||
import { repeat } from 'lit-html/directives/repeat.js';
|
||||
|
||||
export class TooltipContentWithShortcut extends LitElement {
|
||||
static override styles = css`
|
||||
@@ -9,6 +10,10 @@ export class TooltipContentWithShortcut extends LitElement {
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
.tooltip__shortcuts {
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
}
|
||||
.tooltip__shortcut {
|
||||
font-size: 12px;
|
||||
position: relative;
|
||||
@@ -28,19 +33,30 @@ export class TooltipContentWithShortcut extends LitElement {
|
||||
opacity: 0.2;
|
||||
}
|
||||
.tooltip__label {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
white-space: pre;
|
||||
}
|
||||
`;
|
||||
|
||||
get shortcuts() {
|
||||
let shortcut = this.shortcut;
|
||||
if (!shortcut) return [];
|
||||
return shortcut.split(' ');
|
||||
}
|
||||
|
||||
override render() {
|
||||
const { tip, shortcut, postfix } = this;
|
||||
const { tip, shortcuts, postfix } = this;
|
||||
|
||||
return html`
|
||||
<div class="tooltip-with-shortcut">
|
||||
<span class="tooltip__label">${tip}</span>
|
||||
${shortcut
|
||||
? html`<span class="tooltip__shortcut">${shortcut}</span>`
|
||||
: ''}
|
||||
<div class="tooltip__shortcuts">
|
||||
${repeat(
|
||||
shortcuts,
|
||||
shortcut => html`<span class="tooltip__shortcut">${shortcut}</span>`
|
||||
)}
|
||||
</div>
|
||||
${postfix ? html`<span class="tooltip__postfix">${postfix}</span>` : ''}
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import {
|
||||
EdgelessBrushDarkIcon,
|
||||
EdgelessBrushLightIcon,
|
||||
EdgelessHighlighterDarkIcon,
|
||||
EdgelessHighlighterLightIcon,
|
||||
} from './icons';
|
||||
import type { Pen } from './types';
|
||||
|
||||
export const penIconMap = {
|
||||
dark: {
|
||||
brush: EdgelessBrushDarkIcon,
|
||||
highlighter: EdgelessHighlighterDarkIcon,
|
||||
},
|
||||
light: {
|
||||
brush: EdgelessBrushLightIcon,
|
||||
highlighter: EdgelessHighlighterLightIcon,
|
||||
},
|
||||
};
|
||||
|
||||
export const penInfoMap: { [k in Pen]: { tip: string; shortcut: string } } = {
|
||||
brush: {
|
||||
tip: 'Pen',
|
||||
shortcut: 'P',
|
||||
},
|
||||
highlighter: {
|
||||
tip: 'Highlighter',
|
||||
shortcut: '⇧ P',
|
||||
},
|
||||
};
|
||||
@@ -7,11 +7,16 @@ import {
|
||||
import type { ColorEvent } from '@blocksuite/affine-shared/utils';
|
||||
import { EdgelessToolbarToolMixin } from '@blocksuite/affine-widget-edgeless-toolbar';
|
||||
import { SignalWatcher } from '@blocksuite/global/lit';
|
||||
import { computed, type Signal } from '@preact/signals-core';
|
||||
import {
|
||||
computed,
|
||||
type ReadonlySignal,
|
||||
type Signal,
|
||||
} from '@preact/signals-core';
|
||||
import { css, html, LitElement, type TemplateResult } from 'lit';
|
||||
import { property } from 'lit/decorators.js';
|
||||
import { styleMap } from 'lit/directives/style-map.js';
|
||||
|
||||
import { penInfoMap } from './consts';
|
||||
import type { Pen, PenMap } from './types';
|
||||
|
||||
export class EdgelessPenMenu extends EdgelessToolbarToolMixin(
|
||||
@@ -26,8 +31,14 @@ export class EdgelessPenMenu extends EdgelessToolbarToolMixin(
|
||||
|
||||
.pens {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
padding: 0 4px;
|
||||
align-items: center;
|
||||
align-items: flex-end;
|
||||
|
||||
edgeless-tool-icon-button {
|
||||
display: flex;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.pen-wrapper {
|
||||
display: flex;
|
||||
@@ -36,7 +47,7 @@ export class EdgelessPenMenu extends EdgelessToolbarToolMixin(
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
transform: translateY(10px);
|
||||
transform: translateY(-2px);
|
||||
transition-property: color, transform;
|
||||
transition-duration: 300ms;
|
||||
transition-timing-function: ease-in-out;
|
||||
@@ -46,7 +57,7 @@ export class EdgelessPenMenu extends EdgelessToolbarToolMixin(
|
||||
.pen-wrapper:hover,
|
||||
.pen-wrapper:active,
|
||||
.pen-wrapper[data-active] {
|
||||
transform: translateY(-10px);
|
||||
transform: translateY(-22px);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,6 +67,8 @@ export class EdgelessPenMenu extends EdgelessToolbarToolMixin(
|
||||
}
|
||||
|
||||
menu-divider {
|
||||
display: flex;
|
||||
align-self: center;
|
||||
height: 24px;
|
||||
margin: 0 9px;
|
||||
}
|
||||
@@ -83,42 +96,64 @@ export class EdgelessPenMenu extends EdgelessToolbarToolMixin(
|
||||
override render() {
|
||||
const {
|
||||
_theme$: { value: theme },
|
||||
color$: { value: currentColor },
|
||||
colors$: {
|
||||
value: { brush: brushColor, highlighter: highlighterColor },
|
||||
},
|
||||
pen$: { value: pen },
|
||||
penIconMap$: {
|
||||
value: { brush: brushIcon, highlighter: highlighterIcon },
|
||||
},
|
||||
penInfo$: {
|
||||
value: { type, color },
|
||||
},
|
||||
} = this;
|
||||
|
||||
return html`
|
||||
<edgeless-slide-menu>
|
||||
<div class="pens" slot="prefix">
|
||||
<div
|
||||
class="pen-wrapper edgeless-brush-button"
|
||||
?data-active="${pen === 'brush'}"
|
||||
style=${styleMap({ color: brushColor })}
|
||||
<edgeless-tool-icon-button
|
||||
class="edgeless-brush-button"
|
||||
.tooltip=${html`<affine-tooltip-content-with-shortcut
|
||||
data-tip="${penInfoMap.brush.tip}"
|
||||
data-shortcut="${penInfoMap.brush.shortcut}"
|
||||
></affine-tooltip-content-with-shortcut>`}
|
||||
.tooltipOffset=${20}
|
||||
.hover=${false}
|
||||
@click=${() => this._onPickPen('brush')}
|
||||
>
|
||||
${brushIcon}
|
||||
</div>
|
||||
<div
|
||||
class="pen-wrapper edgeless-highlighter-button"
|
||||
?data-active="${pen === 'highlighter'}"
|
||||
style=${styleMap({ color: highlighterColor })}
|
||||
<div
|
||||
class="pen-wrapper"
|
||||
style=${styleMap({ color: brushColor })}
|
||||
?data-active="${type === 'brush'}"
|
||||
>
|
||||
${brushIcon}
|
||||
</div>
|
||||
</edgeless-tool-icon-button>
|
||||
|
||||
<edgeless-tool-icon-button
|
||||
class="edgeless-highlighter-button"
|
||||
.tooltip=${html`<affine-tooltip-content-with-shortcut
|
||||
data-tip="${penInfoMap.highlighter.tip}"
|
||||
data-shortcut="${penInfoMap.highlighter.shortcut}"
|
||||
></affine-tooltip-content-with-shortcut>`}
|
||||
.tooltipOffset=${20}
|
||||
.hover=${false}
|
||||
@click=${() => this._onPickPen('highlighter')}
|
||||
>
|
||||
${highlighterIcon}
|
||||
</div>
|
||||
<div
|
||||
class="pen-wrapper"
|
||||
style=${styleMap({ color: highlighterColor })}
|
||||
?data-active="${type === 'highlighter'}"
|
||||
>
|
||||
${highlighterIcon}
|
||||
</div>
|
||||
</edgeless-tool-icon-button>
|
||||
<menu-divider .vertical=${true}></menu-divider>
|
||||
</div>
|
||||
<div class="menu-content">
|
||||
<edgeless-color-panel
|
||||
class="one-way"
|
||||
@select=${this._onPickColor}
|
||||
.value=${currentColor}
|
||||
.value=${color}
|
||||
.theme=${theme}
|
||||
.palettes=${DefaultTheme.StrokeColorShortPalettes}
|
||||
.shouldKeepColor=${true}
|
||||
@@ -135,14 +170,20 @@ export class EdgelessPenMenu extends EdgelessToolbarToolMixin(
|
||||
accessor onChange!: (props: Record<string, unknown>) => void;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor colors$!: Signal<PenMap<string>>;
|
||||
accessor colors$!: ReadonlySignal<PenMap<string>>;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor color$!: Signal<string>;
|
||||
accessor penIconMap$!: ReadonlySignal<PenMap<TemplateResult>>;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor pen$!: Signal<Pen>;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor penIconMap$!: Signal<PenMap<TemplateResult>>;
|
||||
accessor penInfo$!: ReadonlySignal<{
|
||||
type: Pen;
|
||||
color: string;
|
||||
icon: TemplateResult<1>;
|
||||
tip: string;
|
||||
shortcut: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import { css, html, LitElement, nothing } from 'lit';
|
||||
import { styleMap } from 'lit/directives/style-map.js';
|
||||
import { when } from 'lit/directives/when.js';
|
||||
|
||||
import { penIconMap } from './icons';
|
||||
import { penIconMap, penInfoMap } from './consts';
|
||||
import type { Pen } from './types';
|
||||
|
||||
export class EdgelessPenToolButton extends EdgelessToolbarToolMixin(
|
||||
@@ -81,6 +81,18 @@ export class EdgelessPenToolButton extends EdgelessToolbarToolMixin(
|
||||
return this.penIconMap$.value[pen];
|
||||
});
|
||||
|
||||
private readonly penInfo$ = computed(() => {
|
||||
const type = this.pen$.value;
|
||||
const icon = this.penIcon$.value;
|
||||
const color = this.color$.value;
|
||||
return {
|
||||
...penInfoMap[type],
|
||||
color,
|
||||
icon,
|
||||
type,
|
||||
};
|
||||
});
|
||||
|
||||
private readonly pen$ = signal<Pen>('brush');
|
||||
|
||||
override enableActiveBackground = true;
|
||||
@@ -89,9 +101,20 @@ export class EdgelessPenToolButton extends EdgelessToolbarToolMixin(
|
||||
|
||||
override firstUpdated() {
|
||||
this.disposables.add(
|
||||
this.gfx.tool.currentToolName$.subscribe(tool => {
|
||||
if (this.type.map(String).includes(tool)) return;
|
||||
this.tryDisposePopper();
|
||||
this.gfx.tool.currentToolName$.subscribe(name => {
|
||||
const tool = this.type.find(t => t === name);
|
||||
if (!tool) {
|
||||
this.tryDisposePopper();
|
||||
return;
|
||||
}
|
||||
|
||||
if (tool !== this.pen$.peek()) {
|
||||
this.pen$.value = tool;
|
||||
}
|
||||
|
||||
if (this.active) return;
|
||||
|
||||
this._togglePenMenu();
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -101,10 +124,10 @@ export class EdgelessPenToolButton extends EdgelessToolbarToolMixin(
|
||||
!this.active && this.setEdgelessTool(this.pen$.peek());
|
||||
const menu = this.createPopper('edgeless-pen-menu', this);
|
||||
Object.assign(menu.element, {
|
||||
color$: this.color$,
|
||||
colors$: this.colors$,
|
||||
pen$: this.pen$,
|
||||
penIconMap$: this.penIconMap$,
|
||||
pen$: this.pen$,
|
||||
penInfo$: this.penInfo$,
|
||||
edgeless: this.edgeless,
|
||||
onChange: (props: Record<string, unknown>) => {
|
||||
const pen = this.pen$.peek();
|
||||
@@ -117,20 +140,22 @@ export class EdgelessPenToolButton extends EdgelessToolbarToolMixin(
|
||||
override render() {
|
||||
const {
|
||||
active,
|
||||
penIcon$: { value: icon },
|
||||
color$: { value: color },
|
||||
penInfo$: {
|
||||
value: { type, color, icon, tip, shortcut },
|
||||
},
|
||||
} = this;
|
||||
|
||||
return html`
|
||||
<edgeless-toolbar-button
|
||||
class="edgeless-pen-button"
|
||||
data-drawing-tool="${type}"
|
||||
.tooltip=${when(
|
||||
this.popper,
|
||||
() => nothing,
|
||||
() =>
|
||||
html`<affine-tooltip-content-with-shortcut
|
||||
data-tip="${'Pen'}"
|
||||
data-shortcut="${'P'}"
|
||||
data-tip="${tip}"
|
||||
data-shortcut="${shortcut}"
|
||||
></affine-tooltip-content-with-shortcut>`
|
||||
)}
|
||||
.tooltipOffset=${4}
|
||||
|
||||
@@ -73,16 +73,20 @@ export const TurboRendererConfigFactory =
|
||||
export class ViewportTurboRendererExtension extends GfxExtension {
|
||||
static override key = 'viewportTurboRenderer';
|
||||
|
||||
private readonly state$ = new BehaviorSubject<RenderingState>('inactive');
|
||||
public readonly state$ = new BehaviorSubject<RenderingState>('inactive');
|
||||
public readonly canvas: HTMLCanvasElement = document.createElement('canvas');
|
||||
public layoutCacheData: ViewportLayoutTree | null = null;
|
||||
private readonly worker: Worker;
|
||||
private readonly disposables = new DisposableGroup();
|
||||
private layoutCacheData: ViewportLayoutTree | null = null;
|
||||
private layoutVersion = 0;
|
||||
private bitmap: ImageBitmap | null = null;
|
||||
private viewportElement: GfxViewportElement | null = null;
|
||||
private readonly refresh$ = new Subject<void>();
|
||||
|
||||
public get currentState(): RenderingState {
|
||||
return this.state$.value;
|
||||
}
|
||||
|
||||
constructor(gfx: GfxController) {
|
||||
super(gfx);
|
||||
|
||||
|
||||
@@ -12,8 +12,8 @@ import {
|
||||
import {
|
||||
INLINE_ROOT_ATTR,
|
||||
type InlineRootElement,
|
||||
ZERO_WIDTH_NON_JOINER,
|
||||
ZERO_WIDTH_SPACE,
|
||||
ZERO_WIDTH_FOR_EMBED_NODE,
|
||||
ZERO_WIDTH_FOR_EMPTY_LINE,
|
||||
} from '@blocksuite/std/inline';
|
||||
import type { DeltaInsert } from '@blocksuite/store';
|
||||
import { shift } from '@floating-ui/dom';
|
||||
@@ -186,7 +186,7 @@ export class AffineFootnoteNode extends WithDisposable(ShadowlessElement) {
|
||||
return html`<span
|
||||
${this.hidePopup ? '' : ref(this._whenHover.setReference)}
|
||||
class=${nodeClasses}
|
||||
>${node}<v-text .str=${ZERO_WIDTH_NON_JOINER}></v-text
|
||||
>${node}<v-text .str=${ZERO_WIDTH_FOR_EMBED_NODE}></v-text
|
||||
></span>`;
|
||||
}
|
||||
|
||||
@@ -195,7 +195,7 @@ export class AffineFootnoteNode extends WithDisposable(ShadowlessElement) {
|
||||
|
||||
@property({ type: Object })
|
||||
accessor delta: DeltaInsert<AffineTextAttributes> = {
|
||||
insert: ZERO_WIDTH_SPACE,
|
||||
insert: ZERO_WIDTH_FOR_EMPTY_LINE,
|
||||
attributes: {},
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { AffineTextAttributes } from '@blocksuite/affine-shared/types';
|
||||
import { ShadowlessElement } from '@blocksuite/std';
|
||||
import { ZERO_WIDTH_SPACE } from '@blocksuite/std/inline';
|
||||
import { ZERO_WIDTH_FOR_EMPTY_LINE } from '@blocksuite/std/inline';
|
||||
import type { DeltaInsert } from '@blocksuite/store';
|
||||
import { html } from 'lit';
|
||||
import { property } from 'lit/decorators.js';
|
||||
@@ -50,6 +50,6 @@ export class LatexEditorUnit extends ShadowlessElement {
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor delta: DeltaInsert<AffineTextAttributes> = {
|
||||
insert: ZERO_WIDTH_SPACE,
|
||||
insert: ZERO_WIDTH_FOR_EMPTY_LINE,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -9,8 +9,8 @@ import {
|
||||
} from '@blocksuite/std';
|
||||
import {
|
||||
type InlineEditor,
|
||||
ZERO_WIDTH_NON_JOINER,
|
||||
ZERO_WIDTH_SPACE,
|
||||
ZERO_WIDTH_FOR_EMBED_NODE,
|
||||
ZERO_WIDTH_FOR_EMPTY_LINE,
|
||||
} from '@blocksuite/std/inline';
|
||||
import type { DeltaInsert } from '@blocksuite/store';
|
||||
import { signal } from '@preact/signals-core';
|
||||
@@ -178,7 +178,7 @@ export class AffineLatexNode extends SignalWatcher(
|
||||
override render() {
|
||||
return html`<span class="affine-latex" data-selected=${this.selected}
|
||||
><div class="latex-container"></div>
|
||||
<v-text .str=${ZERO_WIDTH_NON_JOINER}></v-text
|
||||
<v-text .str=${ZERO_WIDTH_FOR_EMBED_NODE}></v-text
|
||||
></span>`;
|
||||
}
|
||||
|
||||
@@ -244,7 +244,7 @@ export class AffineLatexNode extends SignalWatcher(
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor delta: DeltaInsert<AffineTextAttributes> = {
|
||||
insert: ZERO_WIDTH_SPACE,
|
||||
insert: ZERO_WIDTH_FOR_EMPTY_LINE,
|
||||
};
|
||||
|
||||
@property({ attribute: false })
|
||||
|
||||
@@ -13,7 +13,7 @@ import { BLOCK_ID_ATTR, ShadowlessElement } from '@blocksuite/std';
|
||||
import {
|
||||
INLINE_ROOT_ATTR,
|
||||
type InlineRootElement,
|
||||
ZERO_WIDTH_SPACE,
|
||||
ZERO_WIDTH_FOR_EMPTY_LINE,
|
||||
} from '@blocksuite/std/inline';
|
||||
import type { DeltaInsert } from '@blocksuite/store';
|
||||
import { css, html } from 'lit';
|
||||
@@ -177,7 +177,7 @@ export class AffineLink extends WithDisposable(ShadowlessElement) {
|
||||
|
||||
@property({ type: Object })
|
||||
accessor delta: DeltaInsert<AffineTextAttributes> = {
|
||||
insert: ZERO_WIDTH_SPACE,
|
||||
insert: ZERO_WIDTH_FOR_EMPTY_LINE,
|
||||
};
|
||||
|
||||
@property({ attribute: false })
|
||||
|
||||
@@ -5,8 +5,8 @@ import { SignalWatcher, WithDisposable } from '@blocksuite/global/lit';
|
||||
import type { BlockStdScope } from '@blocksuite/std';
|
||||
import { ShadowlessElement } from '@blocksuite/std';
|
||||
import {
|
||||
ZERO_WIDTH_NON_JOINER,
|
||||
ZERO_WIDTH_SPACE,
|
||||
ZERO_WIDTH_FOR_EMBED_NODE,
|
||||
ZERO_WIDTH_FOR_EMPTY_LINE,
|
||||
} from '@blocksuite/std/inline';
|
||||
import type { DeltaInsert } from '@blocksuite/store';
|
||||
import { css, html } from 'lit';
|
||||
@@ -88,7 +88,7 @@ export class AffineMention extends SignalWatcher(
|
||||
data-selected=${this.selected}
|
||||
data-type="error"
|
||||
class="affine-mention"
|
||||
>@Unknown Member<v-text .str=${ZERO_WIDTH_NON_JOINER}></v-text
|
||||
>@Unknown Member<v-text .str=${ZERO_WIDTH_FOR_EMBED_NODE}></v-text
|
||||
></span>`;
|
||||
|
||||
const userService = this.std.getOptional(UserProvider);
|
||||
@@ -107,7 +107,7 @@ export class AffineMention extends SignalWatcher(
|
||||
data-selected=${this.selected}
|
||||
data-type="removed"
|
||||
class="affine-mention"
|
||||
>@Inactive Member<v-text .str=${ZERO_WIDTH_NON_JOINER}></v-text
|
||||
>@Inactive Member<v-text .str=${ZERO_WIDTH_FOR_EMBED_NODE}></v-text
|
||||
></span>`;
|
||||
} else {
|
||||
return html`<span
|
||||
@@ -115,7 +115,7 @@ export class AffineMention extends SignalWatcher(
|
||||
data-type="default"
|
||||
class="affine-mention"
|
||||
>@${userInfo$.value.name ?? 'Unknown'}<v-text
|
||||
.str=${ZERO_WIDTH_NON_JOINER}
|
||||
.str=${ZERO_WIDTH_FOR_EMBED_NODE}
|
||||
></v-text
|
||||
></span>`;
|
||||
}
|
||||
@@ -129,7 +129,7 @@ export class AffineMention extends SignalWatcher(
|
||||
>@loading<span class="dots"
|
||||
><span class="dot">.</span><span class="dot">.</span
|
||||
><span class="dot">.</span></span
|
||||
><v-text .str=${ZERO_WIDTH_NON_JOINER}></v-text
|
||||
><v-text .str=${ZERO_WIDTH_FOR_EMBED_NODE}></v-text
|
||||
></span>`;
|
||||
}
|
||||
|
||||
@@ -138,7 +138,7 @@ export class AffineMention extends SignalWatcher(
|
||||
|
||||
@property({ type: Object })
|
||||
accessor delta: DeltaInsert<AffineTextAttributes> = {
|
||||
insert: ZERO_WIDTH_SPACE,
|
||||
insert: ZERO_WIDTH_FOR_EMPTY_LINE,
|
||||
attributes: {},
|
||||
};
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { affineTextStyles } from '@blocksuite/affine-shared/styles';
|
||||
import type { AffineTextAttributes } from '@blocksuite/affine-shared/types';
|
||||
import { ShadowlessElement } from '@blocksuite/std';
|
||||
import { ZERO_WIDTH_SPACE } from '@blocksuite/std/inline';
|
||||
import { ZERO_WIDTH_FOR_EMPTY_LINE } from '@blocksuite/std/inline';
|
||||
import type { DeltaInsert } from '@blocksuite/store';
|
||||
import { html } from 'lit';
|
||||
import { property } from 'lit/decorators.js';
|
||||
@@ -30,6 +30,6 @@ export class AffineText extends ShadowlessElement {
|
||||
|
||||
@property({ type: Object })
|
||||
accessor delta: DeltaInsert<AffineTextAttributes> = {
|
||||
insert: ZERO_WIDTH_SPACE,
|
||||
insert: ZERO_WIDTH_FOR_EMPTY_LINE,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -22,8 +22,8 @@ import { BLOCK_ID_ATTR, ShadowlessElement } from '@blocksuite/std';
|
||||
import {
|
||||
INLINE_ROOT_ATTR,
|
||||
type InlineRootElement,
|
||||
ZERO_WIDTH_NON_JOINER,
|
||||
ZERO_WIDTH_SPACE,
|
||||
ZERO_WIDTH_FOR_EMBED_NODE,
|
||||
ZERO_WIDTH_FOR_EMPTY_LINE,
|
||||
} from '@blocksuite/std/inline';
|
||||
import type { DeltaInsert, DocMeta, Store } from '@blocksuite/store';
|
||||
import { css, html, nothing } from 'lit';
|
||||
@@ -274,14 +274,14 @@ export class AffineReference extends WithDisposable(ShadowlessElement) {
|
||||
>${title}</span
|
||||
>`;
|
||||
|
||||
// we need to add `<v-text .str=${ZERO_WIDTH_NON_JOINER}></v-text>` in an
|
||||
// we need to add `<v-text .str=${ZERO_WIDTH_FOR_EMBED_NODE}></v-text>` in an
|
||||
// embed element to make sure inline range calculation is correct
|
||||
return html`<span
|
||||
data-selected=${this.selected}
|
||||
class="affine-reference"
|
||||
style=${styleMap(style)}
|
||||
@click=${(event: MouseEvent) => this.open({ event })}
|
||||
>${content}<v-text .str=${ZERO_WIDTH_NON_JOINER}></v-text
|
||||
>${content}<v-text .str=${ZERO_WIDTH_FOR_EMBED_NODE}></v-text
|
||||
></span>`;
|
||||
}
|
||||
|
||||
@@ -299,7 +299,7 @@ export class AffineReference extends WithDisposable(ShadowlessElement) {
|
||||
|
||||
@property({ type: Object })
|
||||
accessor delta: DeltaInsert<AffineTextAttributes> = {
|
||||
insert: ZERO_WIDTH_SPACE,
|
||||
insert: ZERO_WIDTH_FOR_EMPTY_LINE,
|
||||
attributes: {},
|
||||
};
|
||||
|
||||
|
||||
@@ -11,12 +11,16 @@ export const surfaceRefToEmbed =
|
||||
}
|
||||
});
|
||||
slots.beforeImport.subscribe(payload => {
|
||||
// only handle surface-ref block snapshot
|
||||
if (
|
||||
pageId &&
|
||||
payload.type === 'block' &&
|
||||
payload.snapshot.flavour === 'affine:surface-ref' &&
|
||||
!std.store.hasBlock(payload.snapshot.id)
|
||||
) {
|
||||
payload.type !== 'block' ||
|
||||
payload.snapshot.flavour !== 'affine:surface-ref'
|
||||
)
|
||||
return;
|
||||
|
||||
// turn into embed-linked-doc if the current doc is different from the pageId of the surface-ref block
|
||||
const isNotSameDoc = pageId !== std.store.doc.id;
|
||||
if (pageId && isNotSameDoc) {
|
||||
// The blockId of the original surface-ref block
|
||||
const blockId = payload.snapshot.id;
|
||||
payload.snapshot.id = std.workspace.idGenerator();
|
||||
|
||||
@@ -15,3 +15,9 @@ export interface VirtualKeyboardProviderWithAction
|
||||
export const VirtualKeyboardProvider = createIdentifier<
|
||||
VirtualKeyboardProvider | VirtualKeyboardProviderWithAction
|
||||
>('VirtualKeyboardProvider');
|
||||
|
||||
export function isVirtualKeyboardProviderWithAction(
|
||||
provider: VirtualKeyboardProvider
|
||||
): provider is VirtualKeyboardProviderWithAction {
|
||||
return 'show' in provider && 'hide' in provider;
|
||||
}
|
||||
|
||||
@@ -99,9 +99,6 @@ export class AffineDragHandleWidget extends WidgetComponent<RootBlockModel> {
|
||||
}
|
||||
|
||||
this._anchorModelDisposables = new DisposableGroup();
|
||||
this._anchorModelDisposables.add(
|
||||
blockModel.propsUpdated.subscribe(() => this.hide())
|
||||
);
|
||||
|
||||
this._anchorModelDisposables.add(
|
||||
blockModel.deleted.subscribe(() => this.hide())
|
||||
|
||||
@@ -195,25 +195,25 @@ export class DragEventWatcher {
|
||||
dragPayload: DragBlockPayload,
|
||||
dropPayload: DropPayload
|
||||
): DropResult | null => {
|
||||
const model = dropBlock.model;
|
||||
const dropModel = dropBlock.model;
|
||||
|
||||
const snapshot = dragPayload?.bsEntity?.snapshot;
|
||||
if (
|
||||
!snapshot ||
|
||||
snapshot.content.length === 0 ||
|
||||
!dragPayload?.from ||
|
||||
matchModels(model, [DatabaseBlockModel])
|
||||
matchModels(dropModel, [DatabaseBlockModel])
|
||||
)
|
||||
return null;
|
||||
|
||||
const isDropOnNoteBlock = matchModels(model, [NoteBlockModel]);
|
||||
const isDropOnNoteBlock = matchModels(dropModel, [NoteBlockModel]);
|
||||
|
||||
const schema = this.std.store.schema;
|
||||
const edge = dropPayload.edge;
|
||||
const scale = this.widget.scale.peek();
|
||||
let result: DropResult | null = null;
|
||||
|
||||
if (edge === 'right' && matchModels(dropBlock.model, [ListBlockModel])) {
|
||||
if (edge === 'right' && matchModels(dropModel, [ListBlockModel])) {
|
||||
const domRect = getRectByBlockComponent(dropBlock);
|
||||
const placement = 'in';
|
||||
|
||||
@@ -279,7 +279,7 @@ export class DragEventWatcher {
|
||||
placement,
|
||||
rect: Rect.fromLWTH(domRect.left, domRect.width, y, 3 * scale),
|
||||
modelState: {
|
||||
model,
|
||||
model: dropModel,
|
||||
rect: domRect,
|
||||
element: dropBlock,
|
||||
},
|
||||
@@ -548,145 +548,6 @@ export class DragEventWatcher {
|
||||
|
||||
if (!parent) return;
|
||||
|
||||
if (dragPayload.bsEntity?.fromMode === 'gfx') {
|
||||
if (!matchModels(parent, [NoteBlockModel])) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if not all blocks can be dropped in note block, merge the snapshot to the current doc
|
||||
if (
|
||||
!snapshot.content.every(block =>
|
||||
schema.safeValidate(block.flavour, 'affine:note')
|
||||
) &&
|
||||
// if all blocks are note blocks, merge it to the current parent note
|
||||
!snapshot.content.every(block => block.flavour === 'affine:note')
|
||||
) {
|
||||
// merge the snapshot to the current doc if the snapshot comes from other doc
|
||||
if (dragPayload.from?.docId !== this.widget.doc.id) {
|
||||
this._mergeSnapshotToCurDoc(snapshot)
|
||||
.then(idRemap => {
|
||||
let largestElem!: {
|
||||
size: number;
|
||||
id: string;
|
||||
flavour: string;
|
||||
};
|
||||
|
||||
idRemap.forEach(val => {
|
||||
const gfxElement = this.gfx.getElementById(val) as GfxModel;
|
||||
|
||||
if (gfxElement?.elementBound) {
|
||||
const elemBound = gfxElement.elementBound;
|
||||
const flavour = isPrimitiveModel(gfxElement)
|
||||
? gfxElement.type
|
||||
: gfxElement.flavour;
|
||||
|
||||
largestElem =
|
||||
(largestElem?.size ?? 0) < elemBound.w * elemBound.h
|
||||
? { size: elemBound.w * elemBound.h, id: val, flavour }
|
||||
: largestElem;
|
||||
}
|
||||
});
|
||||
|
||||
if (!largestElem) {
|
||||
store.addBlock(
|
||||
'affine:embed-linked-doc',
|
||||
{
|
||||
pageId: store.doc.id,
|
||||
},
|
||||
parent.id,
|
||||
index
|
||||
);
|
||||
} else {
|
||||
store.addBlock(
|
||||
'affine:surface-ref',
|
||||
{
|
||||
reference: largestElem.id,
|
||||
refFlavour: largestElem.flavour,
|
||||
},
|
||||
parent.id,
|
||||
index
|
||||
);
|
||||
}
|
||||
})
|
||||
.catch(console.error);
|
||||
}
|
||||
// otherwise, just to create a surface-ref block
|
||||
else {
|
||||
let largestElem!: {
|
||||
size: number;
|
||||
id: string;
|
||||
flavour: string;
|
||||
};
|
||||
|
||||
const walk = (block: BlockSnapshot) => {
|
||||
if (block.flavour === 'affine:surface') {
|
||||
Object.values(
|
||||
block.props.elements as Record<
|
||||
string,
|
||||
{ id: string; xywh: SerializedXYWH; type: string }
|
||||
>
|
||||
).forEach(elem => {
|
||||
if (elem.xywh) {
|
||||
const bound = Bound.deserialize(elem.xywh);
|
||||
const size = bound.w * bound.h;
|
||||
if ((largestElem?.size ?? 0) < size) {
|
||||
largestElem = { size, id: elem.id, flavour: elem.type };
|
||||
}
|
||||
}
|
||||
});
|
||||
block.children.forEach(walk);
|
||||
} else {
|
||||
if (block.props.xywh) {
|
||||
const bound = Bound.deserialize(
|
||||
block.props.xywh as SerializedXYWH
|
||||
);
|
||||
const size = bound.w * bound.h;
|
||||
if ((largestElem?.size ?? 0) < size) {
|
||||
largestElem = { size, id: block.id, flavour: block.flavour };
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
snapshot.content.forEach(walk);
|
||||
|
||||
if (largestElem) {
|
||||
store.addBlock(
|
||||
'affine:surface-ref',
|
||||
{
|
||||
reference: largestElem.id,
|
||||
refFlavour: largestElem.flavour,
|
||||
},
|
||||
parent.id,
|
||||
index
|
||||
);
|
||||
} else {
|
||||
store.addBlock(
|
||||
'affine:embed-linked-doc',
|
||||
{
|
||||
pageId: store.doc.id,
|
||||
},
|
||||
parent.id,
|
||||
index
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// drop a note on other note
|
||||
if (matchModels(parent, [NoteBlockModel])) {
|
||||
const [first] = snapshot.content;
|
||||
if (first.flavour === 'affine:note') {
|
||||
if (parent.id !== first.id) {
|
||||
this._onDropNoteOnNote(snapshot, parent.id, index);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// drop on the same place, do nothing
|
||||
if (
|
||||
(dragPayload.from?.docId === this.widget.doc.id &&
|
||||
@@ -698,7 +559,148 @@ export class DragEventWatcher {
|
||||
return;
|
||||
}
|
||||
|
||||
this._dropToModel(snapshot, parent.id, index).catch(console.error);
|
||||
// drop a note on other note
|
||||
if (
|
||||
matchModels(parent, [NoteBlockModel]) &&
|
||||
snapshot.content.every(block => block.flavour === 'affine:note')
|
||||
) {
|
||||
snapshot.content = snapshot.content.filter(
|
||||
block =>
|
||||
dragPayload.from?.docId !== this.widget.doc.id ||
|
||||
block.id !== parent.id
|
||||
);
|
||||
if (snapshot.content.length) {
|
||||
this._onDropNoteOnNote(snapshot, parent.id, index);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// all blocks can be safely dropped in the target parent
|
||||
if (
|
||||
snapshot.content.every(block =>
|
||||
schema.safeValidate(block.flavour, parent.flavour)
|
||||
)
|
||||
) {
|
||||
this._dropToModel(snapshot, parent.id, index).catch(console.error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
dragPayload.bsEntity?.fromMode === 'gfx' &&
|
||||
matchModels(parent, [NoteBlockModel])
|
||||
) {
|
||||
// if the snapshot comes from the same doc, just create a surface-ref block
|
||||
if (dragPayload.from?.docId === this.widget.doc.id) {
|
||||
let largestElem!: {
|
||||
size: number;
|
||||
id: string;
|
||||
flavour: string;
|
||||
};
|
||||
|
||||
const walk = (block: BlockSnapshot) => {
|
||||
if (block.flavour === 'affine:surface') {
|
||||
Object.values(
|
||||
block.props.elements as Record<
|
||||
string,
|
||||
{ id: string; xywh: SerializedXYWH; type: string }
|
||||
>
|
||||
).forEach(elem => {
|
||||
if (elem.xywh) {
|
||||
const bound = Bound.deserialize(elem.xywh);
|
||||
const size = bound.w * bound.h;
|
||||
if ((largestElem?.size ?? 0) < size) {
|
||||
largestElem = { size, id: elem.id, flavour: elem.type };
|
||||
}
|
||||
}
|
||||
});
|
||||
block.children.forEach(walk);
|
||||
} else {
|
||||
if (block.props.xywh) {
|
||||
const bound = Bound.deserialize(
|
||||
block.props.xywh as SerializedXYWH
|
||||
);
|
||||
const size = bound.w * bound.h;
|
||||
if ((largestElem?.size ?? 0) < size) {
|
||||
largestElem = { size, id: block.id, flavour: block.flavour };
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
snapshot.content.forEach(walk);
|
||||
|
||||
if (largestElem) {
|
||||
store.addBlock(
|
||||
'affine:surface-ref',
|
||||
{
|
||||
reference: largestElem.id,
|
||||
refFlavour: largestElem.flavour,
|
||||
},
|
||||
parent.id,
|
||||
index
|
||||
);
|
||||
} else {
|
||||
store.addBlock(
|
||||
'affine:embed-linked-doc',
|
||||
{
|
||||
pageId: store.doc.id,
|
||||
},
|
||||
parent.id,
|
||||
index
|
||||
);
|
||||
}
|
||||
}
|
||||
// otherwise, merge the snapshot into the current doc
|
||||
// and create a surface-ref block or embed-linked-doc block
|
||||
else {
|
||||
this._mergeSnapshotToCurDoc(snapshot)
|
||||
.then(idRemap => {
|
||||
let largestElem!: {
|
||||
size: number;
|
||||
id: string;
|
||||
flavour: string;
|
||||
};
|
||||
|
||||
idRemap.forEach(val => {
|
||||
const gfxElement = this.gfx.getElementById(val) as GfxModel;
|
||||
|
||||
if (gfxElement?.elementBound) {
|
||||
const elemBound = gfxElement.elementBound;
|
||||
const flavour = isPrimitiveModel(gfxElement)
|
||||
? gfxElement.type
|
||||
: gfxElement.flavour;
|
||||
|
||||
largestElem =
|
||||
(largestElem?.size ?? 0) < elemBound.w * elemBound.h
|
||||
? { size: elemBound.w * elemBound.h, id: val, flavour }
|
||||
: largestElem;
|
||||
}
|
||||
});
|
||||
|
||||
if (!largestElem) {
|
||||
store.addBlock(
|
||||
'affine:embed-linked-doc',
|
||||
{
|
||||
pageId: store.doc.id,
|
||||
},
|
||||
parent.id,
|
||||
index
|
||||
);
|
||||
} else {
|
||||
store.addBlock(
|
||||
'affine:surface-ref',
|
||||
{
|
||||
reference: largestElem.id,
|
||||
refFlavour: largestElem.flavour,
|
||||
},
|
||||
parent.id,
|
||||
index
|
||||
);
|
||||
}
|
||||
})
|
||||
.catch(console.error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private readonly _onDrop = (
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import {
|
||||
EdgelessLegacySlotIdentifier,
|
||||
type SurfaceBlockComponent,
|
||||
} from '@blocksuite/affine-block-surface';
|
||||
import { EdgelessLegacySlotIdentifier } from '@blocksuite/affine-block-surface';
|
||||
import { getSelectedRect } from '@blocksuite/affine-shared/utils';
|
||||
import { type IVec, Rect } from '@blocksuite/global/gfx';
|
||||
import {
|
||||
@@ -54,20 +51,14 @@ export class EdgelessWatcher {
|
||||
}
|
||||
|
||||
if (this.widget.isGfxDragHandleVisible) {
|
||||
this._showDragHandle().catch(console.error);
|
||||
this._showDragHandle();
|
||||
this._updateDragHoverRectTopLevelBlock();
|
||||
} else if (this.widget.activeDragHandle) {
|
||||
this.widget.hide();
|
||||
}
|
||||
};
|
||||
|
||||
private readonly _showDragHandle = async () => {
|
||||
const surfaceModel = this.widget.doc.getModelsByFlavour('affine:surface');
|
||||
const surface = this.widget.std.view.getBlock(
|
||||
surfaceModel[0]!.id
|
||||
) as SurfaceBlockComponent;
|
||||
await surface.updateComplete;
|
||||
|
||||
private readonly _showDragHandle = () => {
|
||||
if (!this.widget.anchorBlockId) return;
|
||||
|
||||
const container = this.widget.dragHandleContainer;
|
||||
@@ -119,7 +110,7 @@ export class EdgelessWatcher {
|
||||
|
||||
this.widget.anchorBlockId.value = selectedElement.id;
|
||||
|
||||
this._showDragHandle().catch(console.error);
|
||||
this._showDragHandle();
|
||||
};
|
||||
|
||||
get hoveredElemAreaRect() {
|
||||
@@ -212,11 +203,27 @@ export class EdgelessWatcher {
|
||||
})
|
||||
);
|
||||
|
||||
disposables.add(
|
||||
std.store.slots.blockUpdated.subscribe(payload => {
|
||||
if (
|
||||
this.widget.isGfxDragHandleVisible &&
|
||||
payload.id === this.widget.anchorBlockId.peek()
|
||||
) {
|
||||
if (payload.type === 'delete') {
|
||||
this.widget.hide();
|
||||
}
|
||||
if (payload.type === 'update') {
|
||||
this._showDragHandle();
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
if (surface) {
|
||||
disposables.add(
|
||||
surface.elementUpdated.subscribe(() => {
|
||||
if (this.widget.isGfxDragHandleVisible) {
|
||||
this._showDragHandle().catch(console.error);
|
||||
this._showDragHandle();
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"name": "@blocksuite/affine-widget-linked-doc",
|
||||
"description": "Affine linked doc widget.",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "tsc"
|
||||
},
|
||||
"sideEffects": false,
|
||||
"keywords": [],
|
||||
"author": "toeverything",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@blocksuite/affine-block-image": "workspace:*",
|
||||
"@blocksuite/affine-components": "workspace:*",
|
||||
"@blocksuite/affine-inline-reference": "workspace:*",
|
||||
"@blocksuite/affine-model": "workspace:*",
|
||||
"@blocksuite/affine-rich-text": "workspace:*",
|
||||
"@blocksuite/affine-shared": "workspace:*",
|
||||
"@blocksuite/global": "workspace:*",
|
||||
"@blocksuite/icons": "^2.2.10",
|
||||
"@blocksuite/std": "workspace:*",
|
||||
"@blocksuite/store": "workspace:*",
|
||||
"@lit/context": "^1.1.2",
|
||||
"@preact/signals-core": "^1.8.0",
|
||||
"@toeverything/theme": "^1.1.12",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"fflate": "^0.8.2",
|
||||
"lit": "^3.2.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"rxjs": "^7.8.1"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./effects": "./src/effects.ts"
|
||||
},
|
||||
"files": [
|
||||
"src",
|
||||
"dist",
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.21.0"
|
||||
}
|
||||
+80
-66
@@ -16,7 +16,12 @@ import {
|
||||
isFuzzyMatch,
|
||||
type Signal,
|
||||
} from '@blocksuite/affine-shared/utils';
|
||||
import type { BlockStdScope, EditorHost } from '@blocksuite/std';
|
||||
import { IS_MOBILE } from '@blocksuite/global/env';
|
||||
import {
|
||||
type BlockStdScope,
|
||||
ConfigExtensionFactory,
|
||||
type EditorHost,
|
||||
} from '@blocksuite/std';
|
||||
import type { InlineRange } from '@blocksuite/std/inline';
|
||||
import type { TemplateResult } from 'lit';
|
||||
|
||||
@@ -107,6 +112,7 @@ export type LinkedDocContext = {
|
||||
std: BlockStdScope;
|
||||
inlineEditor: AffineInlineEditor;
|
||||
startRange: InlineRange;
|
||||
startNativeRange: Range;
|
||||
triggerKey: string;
|
||||
config: LinkedWidgetConfig;
|
||||
close: () => void;
|
||||
@@ -171,73 +177,77 @@ export function createNewDocMenuGroup(
|
||||
docName.slice(0, DISPLAY_NAME_LENGTH) +
|
||||
(docName.length > DISPLAY_NAME_LENGTH ? '..' : '');
|
||||
|
||||
const items: LinkedMenuItem[] = [
|
||||
{
|
||||
key: 'create',
|
||||
name: `Create "${displayDocName}" doc`,
|
||||
icon: NewDocIcon,
|
||||
action: () => {
|
||||
abort();
|
||||
const docName = query;
|
||||
const newDoc = createDefaultDoc(doc.workspace, {
|
||||
title: docName,
|
||||
});
|
||||
insertLinkedNode({
|
||||
inlineEditor,
|
||||
docId: newDoc.id,
|
||||
});
|
||||
const telemetryService = editorHost.std.getOptional(TelemetryProvider);
|
||||
telemetryService?.track('LinkedDocCreated', {
|
||||
control: 'new doc',
|
||||
module: 'inline @',
|
||||
type: 'doc',
|
||||
other: 'new doc',
|
||||
});
|
||||
telemetryService?.track('DocCreated', {
|
||||
control: 'new doc',
|
||||
module: 'inline @',
|
||||
type: 'doc',
|
||||
});
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
if (!IS_MOBILE) {
|
||||
items.push({
|
||||
key: 'import',
|
||||
name: 'Import',
|
||||
icon: ImportIcon,
|
||||
action: () => {
|
||||
abort();
|
||||
const onSuccess = (
|
||||
docIds: string[],
|
||||
options: {
|
||||
importedCount: number;
|
||||
}
|
||||
) => {
|
||||
toast(
|
||||
editorHost,
|
||||
`Successfully imported ${options.importedCount} Doc${options.importedCount > 1 ? 's' : ''}.`
|
||||
);
|
||||
for (const docId of docIds) {
|
||||
insertLinkedNode({
|
||||
inlineEditor,
|
||||
docId,
|
||||
});
|
||||
}
|
||||
};
|
||||
const onFail = (message: string) => {
|
||||
toast(editorHost, message);
|
||||
};
|
||||
showImportModal({
|
||||
collection: doc.workspace,
|
||||
schema: doc.schema,
|
||||
onSuccess,
|
||||
onFail,
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
name: 'New Doc',
|
||||
items: [
|
||||
{
|
||||
key: 'create',
|
||||
name: `Create "${displayDocName}" doc`,
|
||||
icon: NewDocIcon,
|
||||
action: () => {
|
||||
abort();
|
||||
const docName = query;
|
||||
const newDoc = createDefaultDoc(doc.workspace, {
|
||||
title: docName,
|
||||
});
|
||||
insertLinkedNode({
|
||||
inlineEditor,
|
||||
docId: newDoc.id,
|
||||
});
|
||||
const telemetryService =
|
||||
editorHost.std.getOptional(TelemetryProvider);
|
||||
telemetryService?.track('LinkedDocCreated', {
|
||||
control: 'new doc',
|
||||
module: 'inline @',
|
||||
type: 'doc',
|
||||
other: 'new doc',
|
||||
});
|
||||
telemetryService?.track('DocCreated', {
|
||||
control: 'new doc',
|
||||
module: 'inline @',
|
||||
type: 'doc',
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'import',
|
||||
name: 'Import',
|
||||
icon: ImportIcon,
|
||||
action: () => {
|
||||
abort();
|
||||
const onSuccess = (
|
||||
docIds: string[],
|
||||
options: {
|
||||
importedCount: number;
|
||||
}
|
||||
) => {
|
||||
toast(
|
||||
editorHost,
|
||||
`Successfully imported ${options.importedCount} Doc${options.importedCount > 1 ? 's' : ''}.`
|
||||
);
|
||||
for (const docId of docIds) {
|
||||
insertLinkedNode({
|
||||
inlineEditor,
|
||||
docId,
|
||||
});
|
||||
}
|
||||
};
|
||||
const onFail = (message: string) => {
|
||||
toast(editorHost, message);
|
||||
};
|
||||
showImportModal({
|
||||
collection: doc.workspace,
|
||||
schema: doc.schema,
|
||||
onSuccess,
|
||||
onFail,
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
items,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -260,3 +270,7 @@ export const LinkedWidgetUtils = {
|
||||
};
|
||||
|
||||
export const AFFINE_LINKED_DOC_WIDGET = 'affine-linked-doc-widget';
|
||||
|
||||
export const LinkedWidgetConfigExtension = ConfigExtensionFactory<
|
||||
Partial<LinkedWidgetConfig>
|
||||
>('affine:widget-linked-doc');
|
||||
+2
-1
@@ -1,5 +1,6 @@
|
||||
import { AFFINE_LINKED_DOC_WIDGET } from './config.js';
|
||||
import { ImportDoc } from './import-doc/import-doc.js';
|
||||
import { Loader } from './import-doc/loader.js';
|
||||
import { AffineLinkedDocWidget } from './index.js';
|
||||
import { LinkedDocPopover } from './linked-doc-popover.js';
|
||||
import { AffineMobileLinkedDocMenu } from './mobile-linked-doc-menu.js';
|
||||
@@ -8,9 +9,9 @@ export function effects() {
|
||||
customElements.define('affine-linked-doc-popover', LinkedDocPopover);
|
||||
customElements.define(AFFINE_LINKED_DOC_WIDGET, AffineLinkedDocWidget);
|
||||
customElements.define('import-doc', ImportDoc);
|
||||
|
||||
customElements.define(
|
||||
'affine-mobile-linked-doc-menu',
|
||||
AffineMobileLinkedDocMenu
|
||||
);
|
||||
customElements.define('loader-element', Loader);
|
||||
}
|
||||
+3
-3
@@ -12,9 +12,9 @@ import type { Schema, Workspace } from '@blocksuite/store';
|
||||
import { html, LitElement, type PropertyValues } from 'lit';
|
||||
import { query, state } from 'lit/decorators.js';
|
||||
|
||||
import { HtmlTransformer } from '../../../transformers/html.js';
|
||||
import { MarkdownTransformer } from '../../../transformers/markdown.js';
|
||||
import { NotionHtmlTransformer } from '../../../transformers/notion-html.js';
|
||||
import { HtmlTransformer } from '../transformers/html.js';
|
||||
import { MarkdownTransformer } from '../transformers/markdown.js';
|
||||
import { NotionHtmlTransformer } from '../transformers/notion-html.js';
|
||||
import { styles } from './styles.js';
|
||||
|
||||
export type OnSuccessHandler = (
|
||||
@@ -0,0 +1,4 @@
|
||||
export * from './config';
|
||||
export * from './import-doc';
|
||||
export * from './transformers';
|
||||
export * from './widget';
|
||||
+7
-7
@@ -7,7 +7,6 @@ import {
|
||||
import { unsafeCSSVar } from '@blocksuite/affine-shared/theme';
|
||||
import {
|
||||
createKeydownObserver,
|
||||
getCurrentNativeRange,
|
||||
getPopperPosition,
|
||||
getViewportElement,
|
||||
} from '@blocksuite/affine-shared/utils';
|
||||
@@ -160,11 +159,15 @@ export class LinkedDocPopover extends SignalWatcher(
|
||||
|
||||
// init
|
||||
this._updateLinkedDocGroup().catch(console.error);
|
||||
this._disposables.addFromEvent(this, 'mousedown', e => {
|
||||
this._disposables.addFromEvent(this, 'pointerdown', e => {
|
||||
// Prevent input from losing focus
|
||||
e.preventDefault();
|
||||
});
|
||||
this._disposables.addFromEvent(window, 'mousedown', e => {
|
||||
this._disposables.addFromEvent(this, 'mousedown', e => {
|
||||
// Prevent input from losing focus in electron
|
||||
e.preventDefault();
|
||||
});
|
||||
this._disposables.addFromEvent(window, 'pointerdown', e => {
|
||||
if (e.target === this) return;
|
||||
// We don't clear the query when clicking outside the popover
|
||||
this.context.close();
|
||||
@@ -338,11 +341,8 @@ export class LinkedDocPopover extends SignalWatcher(
|
||||
|
||||
override willUpdate() {
|
||||
if (!this.hasUpdated) {
|
||||
const curRange = getCurrentNativeRange();
|
||||
if (!curRange) return;
|
||||
|
||||
const updatePosition = throttle(() => {
|
||||
this._position = getPopperPosition(this, curRange);
|
||||
this._position = getPopperPosition(this, this.context.startNativeRange);
|
||||
}, 10);
|
||||
|
||||
this.disposables.addFromEvent(window, 'resize', updatePosition);
|
||||
+8
-48
@@ -3,10 +3,7 @@ import {
|
||||
getTextContentFromInlineRange,
|
||||
} from '@blocksuite/affine-rich-text';
|
||||
import { VirtualKeyboardProvider } from '@blocksuite/affine-shared/services';
|
||||
import {
|
||||
createKeydownObserver,
|
||||
getViewportElement,
|
||||
} from '@blocksuite/affine-shared/utils';
|
||||
import { getViewportElement } from '@blocksuite/affine-shared/utils';
|
||||
import { SignalWatcher, WithDisposable } from '@blocksuite/global/lit';
|
||||
import { MoreHorizontalIcon } from '@blocksuite/icons/lit';
|
||||
import { PropTypes, requiredProperties } from '@blocksuite/std';
|
||||
@@ -16,7 +13,6 @@ import { property } from 'lit/decorators.js';
|
||||
import { join } from 'lit/directives/join.js';
|
||||
import { repeat } from 'lit/directives/repeat.js';
|
||||
|
||||
import { PageRootBlockComponent } from '../../index.js';
|
||||
import type {
|
||||
LinkedDocContext,
|
||||
LinkedMenuGroup,
|
||||
@@ -29,7 +25,6 @@ export const AFFINE_MOBILE_LINKED_DOC_MENU = 'affine-mobile-linked-doc-menu';
|
||||
|
||||
@requiredProperties({
|
||||
context: PropTypes.object,
|
||||
rootComponent: PropTypes.instanceOf(PageRootBlockComponent),
|
||||
})
|
||||
export class AffineMobileLinkedDocMenu extends SignalWatcher(
|
||||
WithDisposable(LitElement)
|
||||
@@ -38,8 +33,6 @@ export class AffineMobileLinkedDocMenu extends SignalWatcher(
|
||||
|
||||
private readonly _expand = new Set<string>();
|
||||
|
||||
private _firstActionItem: LinkedMenuItem | null = null;
|
||||
|
||||
private readonly _linkedDocGroup$ = signal<LinkedMenuGroup[]>([]);
|
||||
|
||||
private readonly _renderGroup = (group: LinkedMenuGroup) => {
|
||||
@@ -189,42 +182,14 @@ export class AffineMobileLinkedDocMenu extends SignalWatcher(
|
||||
const keydownObserverAbortController = new AbortController();
|
||||
this._disposables.add(() => keydownObserverAbortController.abort());
|
||||
|
||||
createKeydownObserver({
|
||||
target: eventSource,
|
||||
signal: keydownObserverAbortController.signal,
|
||||
onInput: isComposition => {
|
||||
if (isComposition) {
|
||||
this._updateLinkedDocGroup().catch(console.error);
|
||||
} else {
|
||||
const subscription = inlineEditor.slots.renderComplete.subscribe(
|
||||
() => {
|
||||
subscription.unsubscribe();
|
||||
this._updateLinkedDocGroup().catch(console.error);
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
onDelete: () => {
|
||||
const subscription = inlineEditor.slots.renderComplete.subscribe(
|
||||
() => {
|
||||
subscription.unsubscribe();
|
||||
const curRange = inlineEditor.getInlineRange();
|
||||
|
||||
if (!this.context.startRange || !curRange) return;
|
||||
|
||||
if (curRange.index < this.context.startRange.index) {
|
||||
this.context.close();
|
||||
}
|
||||
this._updateLinkedDocGroup().catch(console.error);
|
||||
}
|
||||
);
|
||||
},
|
||||
onConfirm: () => {
|
||||
this._firstActionItem?.action()?.catch(console.error);
|
||||
},
|
||||
onAbort: () => {
|
||||
// we need use beforeinput because the event.key of keypress event usually is `Unidentified` in Android
|
||||
this.disposables.addFromEvent(eventSource, 'beforeinput', () => {
|
||||
const curRange = inlineEditor.getInlineRange();
|
||||
if (curRange && curRange.index < this.context.startRange.index) {
|
||||
this.context.close();
|
||||
},
|
||||
return;
|
||||
}
|
||||
this._updateLinkedDocGroup().catch(console.error);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -239,8 +204,6 @@ export class AffineMobileLinkedDocMenu extends SignalWatcher(
|
||||
return nothing;
|
||||
}
|
||||
|
||||
this._firstActionItem = resolveSignal(groups[0].items)[0];
|
||||
|
||||
this.style.bottom = `${this.keyboard.height$.value}px`;
|
||||
|
||||
return html`
|
||||
@@ -250,7 +213,4 @@ export class AffineMobileLinkedDocMenu extends SignalWatcher(
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor context!: LinkedDocContext;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor rootComponent!: PageRootBlockComponent;
|
||||
}
|
||||
+1
-1
@@ -6,7 +6,7 @@ import { sha } from '@blocksuite/global/utils';
|
||||
import type { DocSnapshot, Schema, Store, Workspace } from '@blocksuite/store';
|
||||
import { extMimeMap, getAssetName, Transformer } from '@blocksuite/store';
|
||||
|
||||
import { download, Unzip, Zip } from '../transformers/utils.js';
|
||||
import { download, Unzip, Zip } from './utils.js';
|
||||
|
||||
async function exportDocs(
|
||||
collection: Workspace,
|
||||
+43
-22
@@ -7,7 +7,11 @@ import { FeatureFlagService } from '@blocksuite/affine-shared/services';
|
||||
import { getViewportElement } from '@blocksuite/affine-shared/utils';
|
||||
import { IS_MOBILE } from '@blocksuite/global/env';
|
||||
import type { BlockComponent } from '@blocksuite/std';
|
||||
import { BLOCK_ID_ATTR, WidgetComponent } from '@blocksuite/std';
|
||||
import {
|
||||
BLOCK_ID_ATTR,
|
||||
WidgetComponent,
|
||||
WidgetViewExtension,
|
||||
} from '@blocksuite/std';
|
||||
import { GfxControllerIdentifier } from '@blocksuite/std/gfx';
|
||||
import {
|
||||
INLINE_ROOT_ATTR,
|
||||
@@ -19,22 +23,18 @@ import { html, nothing } from 'lit';
|
||||
import { choose } from 'lit/directives/choose.js';
|
||||
import { repeat } from 'lit/directives/repeat.js';
|
||||
import { styleMap } from 'lit/directives/style-map.js';
|
||||
import { literal, unsafeStatic } from 'lit/static-html.js';
|
||||
|
||||
import type { PageRootBlockComponent } from '../../page/page-root-block.js';
|
||||
import { RootBlockConfigExtension } from '../../root-config.js';
|
||||
import {
|
||||
type AFFINE_LINKED_DOC_WIDGET,
|
||||
AFFINE_LINKED_DOC_WIDGET,
|
||||
getMenus,
|
||||
type LinkedDocContext,
|
||||
type LinkedWidgetConfig,
|
||||
LinkedWidgetConfigExtension,
|
||||
} from './config.js';
|
||||
import { linkedDocWidgetStyles } from './styles.js';
|
||||
export { type LinkedWidgetConfig } from './config.js';
|
||||
|
||||
export class AffineLinkedDocWidget extends WidgetComponent<
|
||||
RootBlockModel,
|
||||
PageRootBlockComponent
|
||||
> {
|
||||
export class AffineLinkedDocWidget extends WidgetComponent<RootBlockModel> {
|
||||
static override styles = linkedDocWidgetStyles;
|
||||
|
||||
private _context: LinkedDocContext | null = null;
|
||||
@@ -43,6 +43,19 @@ export class AffineLinkedDocWidget extends WidgetComponent<
|
||||
|
||||
private readonly _mode$ = signal<'desktop' | 'mobile' | 'none'>('none');
|
||||
|
||||
private _addTriggerKey(inlineEditor: InlineEditor, triggerKey: string) {
|
||||
const inlineRange = inlineEditor.getInlineRange();
|
||||
if (!inlineRange) return;
|
||||
inlineEditor.insertText(
|
||||
{ index: inlineRange.index, length: 0 },
|
||||
triggerKey
|
||||
);
|
||||
inlineEditor.setInlineRange({
|
||||
index: inlineRange.index + triggerKey.length,
|
||||
length: 0,
|
||||
});
|
||||
}
|
||||
|
||||
private _updateInputRects() {
|
||||
if (!this._context) return;
|
||||
const { inlineEditor, startRange, triggerKey } = this._context;
|
||||
@@ -217,8 +230,7 @@ export class AffineLinkedDocWidget extends WidgetComponent<
|
||||
scrollContainer: getViewportElement(this.std.host) ?? window,
|
||||
scrollTopOffset: 46,
|
||||
},
|
||||
...this.std.getOptional(RootBlockConfigExtension.identifier)
|
||||
?.linkedWidget,
|
||||
...this.std.getOptional(LinkedWidgetConfigExtension.identifier),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -259,27 +271,30 @@ export class AffineLinkedDocWidget extends WidgetComponent<
|
||||
inlineEditor = props.inlineEditor;
|
||||
}
|
||||
|
||||
const inlineRange = inlineEditor.getInlineRange();
|
||||
if (!inlineRange) return;
|
||||
|
||||
if (addTriggerKey) {
|
||||
inlineEditor.insertText(
|
||||
{ index: inlineRange.index, length: 0 },
|
||||
primaryTriggerKey
|
||||
);
|
||||
inlineEditor.setInlineRange({
|
||||
index: inlineRange.index + primaryTriggerKey.length,
|
||||
length: 0,
|
||||
this._addTriggerKey(inlineEditor, primaryTriggerKey);
|
||||
// we need to wait the range sync to get the correct startNativeRange
|
||||
const subscription = inlineEditor.slots.inlineRangeSync.subscribe(() => {
|
||||
this.show({ ...props, addTriggerKey: false });
|
||||
subscription.unsubscribe();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const startRange = inlineEditor.getInlineRange();
|
||||
if (!startRange) return;
|
||||
|
||||
const startNativeRange = inlineEditor.getNativeRange();
|
||||
if (!startNativeRange) return;
|
||||
|
||||
const disposable = inlineEditor.slots.renderComplete.subscribe(() => {
|
||||
this._updateInputRects();
|
||||
});
|
||||
this._context = {
|
||||
std: this.std,
|
||||
inlineEditor,
|
||||
startRange: inlineRange,
|
||||
startRange,
|
||||
startNativeRange,
|
||||
triggerKey: primaryTriggerKey,
|
||||
config: this.config,
|
||||
close: () => {
|
||||
@@ -316,6 +331,12 @@ export class AffineLinkedDocWidget extends WidgetComponent<
|
||||
}
|
||||
}
|
||||
|
||||
export const linkedDocWidget = WidgetViewExtension(
|
||||
'affine:page',
|
||||
AFFINE_LINKED_DOC_WIDGET,
|
||||
literal`${unsafeStatic(AFFINE_LINKED_DOC_WIDGET)}`
|
||||
);
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
[AFFINE_LINKED_DOC_WIDGET]: AffineLinkedDocWidget;
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"extends": "../../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist",
|
||||
"tsBuildInfoFile": "./dist/tsconfig.tsbuildinfo"
|
||||
},
|
||||
"include": ["./src"],
|
||||
"references": [
|
||||
{ "path": "../../blocks/image" },
|
||||
{ "path": "../../components" },
|
||||
{ "path": "../../inlines/reference" },
|
||||
{ "path": "../../model" },
|
||||
{ "path": "../../rich-text" },
|
||||
{ "path": "../../shared" },
|
||||
{ "path": "../../../framework/global" },
|
||||
{ "path": "../../../framework/std" },
|
||||
{ "path": "../../../framework/store" }
|
||||
]
|
||||
}
|
||||
@@ -115,8 +115,8 @@ export class ElementTransformManager extends GfxExtension {
|
||||
fallback: lockedElement !== picked,
|
||||
};
|
||||
|
||||
view?.onSelected(context);
|
||||
return true;
|
||||
const selected = view?.onSelected(context);
|
||||
return selected ?? true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
@@ -193,13 +193,15 @@ export class GfxElementModelView<
|
||||
this.model.xywh = currentBound.moveDelta(dx, dy).serialize();
|
||||
}
|
||||
|
||||
onSelected(context: SelectedContext) {
|
||||
onSelected(context: SelectedContext): void | boolean {
|
||||
if (this.model instanceof GfxPrimitiveElementModel) {
|
||||
if (context.multiSelect) {
|
||||
this.gfx.selection.toggle(this.model);
|
||||
} else {
|
||||
this.gfx.selection.set({ elements: [this.model.id] });
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import { html, LitElement } from 'lit';
|
||||
import { property } from 'lit/decorators.js';
|
||||
import { styleMap } from 'lit/directives/style-map.js';
|
||||
|
||||
import { ZERO_WIDTH_SPACE } from '../consts.js';
|
||||
import { ZERO_WIDTH_FOR_EMPTY_LINE } from '../consts.js';
|
||||
import type { InlineEditor } from '../inline-editor.js';
|
||||
import { isInlineRangeIntersect } from '../utils/inline-range.js';
|
||||
|
||||
@@ -90,7 +90,7 @@ export class VElement<
|
||||
|
||||
@property({ type: Object })
|
||||
accessor delta: DeltaInsert<T> = {
|
||||
insert: ZERO_WIDTH_SPACE,
|
||||
insert: ZERO_WIDTH_FOR_EMPTY_LINE,
|
||||
};
|
||||
|
||||
@property({ attribute: false })
|
||||
|
||||
@@ -4,7 +4,7 @@ import { html, LitElement, type TemplateResult } from 'lit';
|
||||
import { property } from 'lit/decorators.js';
|
||||
import { styleMap } from 'lit/directives/style-map.js';
|
||||
|
||||
import { INLINE_ROOT_ATTR, ZERO_WIDTH_SPACE } from '../consts.js';
|
||||
import { INLINE_ROOT_ATTR, ZERO_WIDTH_FOR_EMPTY_LINE } from '../consts.js';
|
||||
import type { InlineRootElement } from '../inline-editor.js';
|
||||
import { EmbedGap } from './embed-gap.js';
|
||||
|
||||
@@ -89,7 +89,9 @@ export class VLine extends LitElement {
|
||||
renderVElements() {
|
||||
if (this.elements.length === 0) {
|
||||
// don't use v-element because it not correspond to the actual delta
|
||||
return html`<div><v-text .str=${ZERO_WIDTH_SPACE}></v-text></div>`;
|
||||
return html`
|
||||
<div><v-text .str=${ZERO_WIDTH_FOR_EMPTY_LINE}></v-text></div>
|
||||
`;
|
||||
}
|
||||
|
||||
const inlineEditor = this.inlineEditor;
|
||||
|
||||
@@ -2,7 +2,7 @@ import { html, LitElement } from 'lit';
|
||||
import { property } from 'lit/decorators.js';
|
||||
import { styleMap } from 'lit/directives/style-map.js';
|
||||
|
||||
import { ZERO_WIDTH_SPACE } from '../consts.js';
|
||||
import { ZERO_WIDTH_FOR_EMPTY_LINE } from '../consts.js';
|
||||
|
||||
export class VText extends LitElement {
|
||||
override createRenderRoot() {
|
||||
@@ -24,7 +24,7 @@ export class VText extends LitElement {
|
||||
}
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor str: string = ZERO_WIDTH_SPACE;
|
||||
accessor str: string = ZERO_WIDTH_FOR_EMPTY_LINE;
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export const ZERO_WIDTH_SPACE = '\u200C';
|
||||
// see https://en.wikipedia.org/wiki/Zero-width_non-joiner
|
||||
export const ZERO_WIDTH_NON_JOINER = '\u200B';
|
||||
import { IS_SAFARI } from '@blocksuite/global/env';
|
||||
|
||||
export const ZERO_WIDTH_FOR_EMPTY_LINE = IS_SAFARI ? '\u200C' : '\u200B';
|
||||
export const ZERO_WIDTH_FOR_EMBED_NODE = IS_SAFARI ? '\u200B' : '\u200C';
|
||||
|
||||
export const INLINE_ROOT_ATTR = 'data-v-root';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
|
||||
|
||||
import type { VElement, VLine } from '../components/index.js';
|
||||
import { INLINE_ROOT_ATTR, ZERO_WIDTH_SPACE } from '../consts.js';
|
||||
import { INLINE_ROOT_ATTR, ZERO_WIDTH_FOR_EMPTY_LINE } from '../consts.js';
|
||||
import type { DomPoint, TextPoint } from '../types.js';
|
||||
import {
|
||||
isInlineRoot,
|
||||
@@ -76,7 +76,7 @@ export function textPointToDomPoint(
|
||||
index += calculateTextLength(text);
|
||||
}
|
||||
|
||||
if (text.wholeText !== ZERO_WIDTH_SPACE) {
|
||||
if (text.wholeText !== ZERO_WIDTH_FOR_EMPTY_LINE) {
|
||||
index += offset;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ZERO_WIDTH_SPACE } from '../consts.js';
|
||||
import { ZERO_WIDTH_FOR_EMPTY_LINE } from '../consts.js';
|
||||
|
||||
export function calculateTextLength(text: Text): number {
|
||||
if (text.wholeText === ZERO_WIDTH_SPACE) {
|
||||
if (text.wholeText === ZERO_WIDTH_FOR_EMPTY_LINE) {
|
||||
return 0;
|
||||
} else {
|
||||
return text.wholeText.length;
|
||||
|
||||
@@ -103,12 +103,14 @@ export abstract class GfxBlockComponent<
|
||||
this.model.pop('xywh');
|
||||
}
|
||||
|
||||
onSelected(context: SelectedContext) {
|
||||
onSelected(context: SelectedContext): void | boolean {
|
||||
if (context.multiSelect) {
|
||||
this.gfx.selection.toggle(this.model);
|
||||
} else {
|
||||
this.gfx.selection.set({ elements: [this.model.id] });
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
onRotate() {}
|
||||
@@ -219,12 +221,14 @@ export function toGfxBlockComponent<
|
||||
}
|
||||
|
||||
// eslint-disable-next-line sonarjs/no-identical-functions
|
||||
onSelected(context: SelectedContext) {
|
||||
onSelected(context: SelectedContext): void | boolean {
|
||||
if (context.multiSelect) {
|
||||
this.gfx.selection.toggle(this.model);
|
||||
} else {
|
||||
this.gfx.selection.set({ elements: [this.model.id] });
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
onRotate() {}
|
||||
|
||||
@@ -1,3 +1,30 @@
|
||||
# `@blocksuite/integration-test`
|
||||
|
||||
Integration test for BlockSuite.
|
||||
|
||||
## Running Tests
|
||||
|
||||
You can run all integration tests using:
|
||||
|
||||
```bash
|
||||
cd blocksuite/integration-test
|
||||
yarn test:unit
|
||||
```
|
||||
|
||||
To run a specific test or test file, use the `-t` flag with a test name pattern:
|
||||
|
||||
```bash
|
||||
# Run a specific test
|
||||
yarn test:unit -t "should access turbo renderer instance"
|
||||
|
||||
# Run all tests in a specific file
|
||||
yarn test:unit src/__tests__/edgeless/viewport-renderer.spec.ts
|
||||
```
|
||||
|
||||
For debugging tests with the Playwright debugger:
|
||||
|
||||
```bash
|
||||
yarn test:debug
|
||||
|
||||
yarn test:debug -t "should access turbo renderer instance"
|
||||
```
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* Please refer to integration-test/README.md for commands to run tests.
|
||||
*/
|
||||
import { ParagraphLayoutHandlerExtension } from '@blocksuite/affine/blocks/paragraph';
|
||||
import { noop } from '@blocksuite/affine/global/utils';
|
||||
import {
|
||||
TurboRendererConfigFactory,
|
||||
ViewportTurboRendererExtension,
|
||||
} from '@blocksuite/affine-gfx-turbo-renderer';
|
||||
import { beforeEach, describe, expect, test } from 'vitest';
|
||||
|
||||
import { wait } from '../utils/common.js';
|
||||
import { addSampleNotes } from '../utils/doc-generator.js';
|
||||
import {
|
||||
createPainterWorker,
|
||||
getRenderer,
|
||||
setupEditor,
|
||||
} from '../utils/setup.js';
|
||||
|
||||
describe('viewport turbo renderer', () => {
|
||||
beforeEach(async () => {
|
||||
const cleanup = await setupEditor('edgeless', [
|
||||
ParagraphLayoutHandlerExtension,
|
||||
TurboRendererConfigFactory({
|
||||
painterWorkerEntry: createPainterWorker,
|
||||
}),
|
||||
ViewportTurboRendererExtension,
|
||||
]);
|
||||
return cleanup;
|
||||
});
|
||||
|
||||
test('should render 6 notes in viewport', async () => {
|
||||
addSampleNotes(doc, 6);
|
||||
await wait();
|
||||
|
||||
const notes = document.querySelectorAll('affine-edgeless-note');
|
||||
expect(notes.length).toBe(6);
|
||||
});
|
||||
|
||||
test('should access turbo renderer instance', async () => {
|
||||
const renderer = getRenderer();
|
||||
expect(renderer).toBeDefined();
|
||||
expect(renderer instanceof ViewportTurboRendererExtension).toBe(true);
|
||||
expect(renderer.canvas).toBeInstanceOf(HTMLCanvasElement);
|
||||
});
|
||||
|
||||
test('initial state should be pending', async () => {
|
||||
const renderer = getRenderer();
|
||||
expect(renderer.state$.value).toBe('pending');
|
||||
});
|
||||
|
||||
test('zooming should change state to zooming', async () => {
|
||||
const renderer = getRenderer();
|
||||
renderer.viewport.zooming$.next(true);
|
||||
await wait();
|
||||
expect(renderer.state$.value).toBe('zooming');
|
||||
renderer.viewport.zooming$.next(false);
|
||||
await wait();
|
||||
expect(renderer.state$.value).not.toBe('zooming');
|
||||
});
|
||||
|
||||
test('state transitions between pending and ready', async () => {
|
||||
const renderer = getRenderer();
|
||||
|
||||
// Initial state should be pending after adding content
|
||||
addSampleNotes(doc, 1);
|
||||
await wait(100); // Short wait for initial processing
|
||||
expect(renderer.state$.value).toBe('pending');
|
||||
|
||||
// Ensure zooming is off and wait for debounce + buffer
|
||||
renderer.viewport.zooming$.next(false);
|
||||
await wait(renderer.options.debounceTime + 500);
|
||||
expect(renderer.state$.value).toBe('ready');
|
||||
});
|
||||
|
||||
test('initial layout cache data should be null', () => {
|
||||
const renderer = getRenderer();
|
||||
expect(renderer.layoutCacheData).toBeNull();
|
||||
});
|
||||
|
||||
test('invalidation should reset layout cache data to null', async () => {
|
||||
const renderer = getRenderer();
|
||||
addSampleNotes(doc, 1);
|
||||
await wait(100);
|
||||
|
||||
// Access getter to populate cache
|
||||
const _cache = renderer.layoutCache;
|
||||
noop(_cache);
|
||||
expect(renderer.layoutCacheData).not.toBeNull();
|
||||
|
||||
// Invalidate
|
||||
addSampleNotes(doc, 1);
|
||||
await wait(100);
|
||||
|
||||
expect(renderer.layoutCacheData).toBeNull();
|
||||
});
|
||||
|
||||
test('accessing layoutCache getter should populate cache data', async () => {
|
||||
const renderer = getRenderer();
|
||||
addSampleNotes(doc, 1);
|
||||
await wait();
|
||||
expect(renderer.layoutCacheData).toBeNull(); // Check internal state before access
|
||||
|
||||
const _cache = renderer.layoutCache; // Access getter to populate cache
|
||||
noop(_cache);
|
||||
expect(renderer.layoutCacheData).not.toBeNull(); // Check internal state after access
|
||||
expect(renderer.layoutCache?.roots.length).toBeGreaterThan(0); // Check public getter result
|
||||
});
|
||||
});
|
||||
@@ -1,31 +0,0 @@
|
||||
import { ParagraphLayoutHandlerExtension } from '@blocksuite/affine/blocks/paragraph';
|
||||
import {
|
||||
TurboRendererConfigFactory,
|
||||
ViewportTurboRendererExtension,
|
||||
} from '@blocksuite/affine-gfx-turbo-renderer';
|
||||
import { beforeEach, describe, expect, test } from 'vitest';
|
||||
|
||||
import { wait } from '../utils/common.js';
|
||||
import { addSampleNotes } from '../utils/doc-generator.js';
|
||||
import { createPainterWorker, setupEditor } from '../utils/setup.js';
|
||||
|
||||
describe('viewport turbo renderer', () => {
|
||||
beforeEach(async () => {
|
||||
const cleanup = await setupEditor('edgeless', [
|
||||
ParagraphLayoutHandlerExtension,
|
||||
TurboRendererConfigFactory({
|
||||
painterWorkerEntry: createPainterWorker,
|
||||
}),
|
||||
ViewportTurboRendererExtension,
|
||||
]);
|
||||
return cleanup;
|
||||
});
|
||||
|
||||
test('should render 6 notes in viewport', async () => {
|
||||
addSampleNotes(doc, 6);
|
||||
await wait();
|
||||
|
||||
const notes = document.querySelectorAll('affine-edgeless-note');
|
||||
expect(notes.length).toBe(6);
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ZipTransformer } from '@blocksuite/affine/blocks/root';
|
||||
import type { SurfaceBlockModel } from '@blocksuite/affine/blocks/surface';
|
||||
import { AffineSchemas } from '@blocksuite/affine/schemas';
|
||||
import { ZipTransformer } from '@blocksuite/affine/widgets/linked-doc';
|
||||
import type { PointLocation } from '@blocksuite/global/gfx';
|
||||
import { Schema } from '@blocksuite/store';
|
||||
import { beforeEach, expect, test } from 'vitest';
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ImageLayoutHandlerExtension } from '@blocksuite/affine/blocks/image';
|
||||
import { ListLayoutHandlerExtension } from '@blocksuite/affine/blocks/list';
|
||||
import { ParagraphLayoutHandlerExtension } from '@blocksuite/affine/blocks/paragraph';
|
||||
import {
|
||||
@@ -13,6 +14,7 @@ async function init() {
|
||||
setupEditor('edgeless', [
|
||||
ParagraphLayoutHandlerExtension,
|
||||
ListLayoutHandlerExtension,
|
||||
ImageLayoutHandlerExtension,
|
||||
TurboRendererConfigFactory({
|
||||
painterWorkerEntry: createPainterWorker,
|
||||
}),
|
||||
|
||||
@@ -8,29 +8,36 @@ import {
|
||||
registerStoreSpecs,
|
||||
StoreExtensions,
|
||||
} from '@blocksuite/affine/extensions';
|
||||
import type { ExtensionType, Store, Transformer } from '@blocksuite/store';
|
||||
|
||||
import { effects } from '../../effects.js';
|
||||
|
||||
registerStoreSpecs();
|
||||
blocksEffects();
|
||||
effects();
|
||||
|
||||
import type { DocMode } from '@blocksuite/affine/model';
|
||||
import { AffineSchemas } from '@blocksuite/affine/schemas';
|
||||
import {
|
||||
CommunityCanvasTextFonts,
|
||||
FontConfigExtension,
|
||||
} from '@blocksuite/affine/shared/services';
|
||||
import type { ViewportTurboRendererExtension } from '@blocksuite/affine-gfx-turbo-renderer';
|
||||
import {
|
||||
type ViewportTurboRendererExtension,
|
||||
ViewportTurboRendererIdentifier,
|
||||
} from '@blocksuite/affine-gfx-turbo-renderer';
|
||||
import type { ExtensionType, Store, Transformer } from '@blocksuite/store';
|
||||
import { Schema, Text } from '@blocksuite/store';
|
||||
import {
|
||||
createAutoIncrementIdGenerator,
|
||||
TestWorkspace,
|
||||
} from '@blocksuite/store/test';
|
||||
|
||||
import { effects } from '../../effects.js';
|
||||
import { TestAffineEditorContainer } from '../../index.js';
|
||||
|
||||
registerStoreSpecs();
|
||||
blocksEffects();
|
||||
effects();
|
||||
|
||||
export function getRenderer() {
|
||||
return editor.std.get(
|
||||
ViewportTurboRendererIdentifier
|
||||
) as ViewportTurboRendererExtension;
|
||||
}
|
||||
|
||||
function createCollectionOptions() {
|
||||
const schema = new Schema();
|
||||
const room = Math.random().toString(16).slice(2, 8);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ImageLayoutPainterExtension } from '@blocksuite/affine-block-image/turbo-painter';
|
||||
import { ListLayoutPainterExtension } from '@blocksuite/affine-block-list/turbo-painter';
|
||||
import { NoteLayoutPainterExtension } from '@blocksuite/affine-block-note/turbo-painter';
|
||||
import { ParagraphLayoutPainterExtension } from '@blocksuite/affine-block-paragraph/turbo-painter';
|
||||
@@ -7,4 +8,5 @@ new ViewportLayoutPainter([
|
||||
ParagraphLayoutPainterExtension,
|
||||
ListLayoutPainterExtension,
|
||||
NoteLayoutPainterExtension,
|
||||
ImageLayoutPainterExtension,
|
||||
]);
|
||||
|
||||
@@ -18,10 +18,9 @@ export default defineConfig(_configEnv =>
|
||||
browser: {
|
||||
enabled: true,
|
||||
headless: process.env.CI === 'true',
|
||||
name: 'chromium',
|
||||
instances: [{ browser: 'chromium' }],
|
||||
provider: 'playwright',
|
||||
isolate: false,
|
||||
providerOptions: {},
|
||||
viewport: {
|
||||
width: 1024,
|
||||
height: 768,
|
||||
|
||||
@@ -17,14 +17,6 @@ import '@shoelace-style/shoelace/dist/themes/dark.css';
|
||||
import './left-side-panel.js';
|
||||
|
||||
import { defaultImageProxyMiddleware } from '@blocksuite/affine/blocks/image';
|
||||
import {
|
||||
createAssetsArchive,
|
||||
download,
|
||||
HtmlTransformer,
|
||||
MarkdownTransformer,
|
||||
NotionHtmlTransformer,
|
||||
ZipTransformer,
|
||||
} from '@blocksuite/affine/blocks/root';
|
||||
import { ExportManager } from '@blocksuite/affine/blocks/surface';
|
||||
import { toast } from '@blocksuite/affine/components/toast';
|
||||
import {
|
||||
@@ -55,6 +47,14 @@ import {
|
||||
Text,
|
||||
type Workspace,
|
||||
} from '@blocksuite/affine/store';
|
||||
import {
|
||||
createAssetsArchive,
|
||||
download,
|
||||
HtmlTransformer,
|
||||
MarkdownTransformer,
|
||||
NotionHtmlTransformer,
|
||||
ZipTransformer,
|
||||
} from '@blocksuite/affine/widgets/linked-doc';
|
||||
import { NotionHtmlAdapter } from '@blocksuite/affine-shared/adapters';
|
||||
import type { AffineTextAttributes } from '@blocksuite/affine-shared/types';
|
||||
import { TestAffineEditorContainer } from '@blocksuite/integration-test';
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user