From 6d3c273ffd1fccab9beacc3f5dfac1ed356c8882 Mon Sep 17 00:00:00 2001 From: Qi <474021214@qq.com> Date: Fri, 26 May 2023 14:52:36 +0800 Subject: [PATCH] feat: support bookmark (#2458) Co-authored-by: himself65 --- .github/workflows/nightly-build.yml | 1 + .github/workflows/release-desktop-app.yml | 1 + .../main/src/handlers/ui/getMetaData/index.ts | 125 ++++ .../main/src/handlers/ui/getMetaData/rules.ts | 690 ++++++++++++++++++ .../main/src/handlers/ui/getMetaData/types.ts | 40 + .../main/src/handlers/ui/getMetaData/utils.ts | 28 + .../layers/main/src/handlers/ui/index.ts | 8 +- apps/electron/package.json | 1 + apps/web/package.json | 10 +- apps/web/preset.config.mjs | 2 +- packages/component/package.json | 10 +- .../components/block-suite-editor/index.tsx | 31 +- .../block-suite-editor/plugins/bookmark.tsx | 210 ++++++ packages/component/src/ui/menu/menu-item.tsx | 1 + packages/component/src/ui/menu/styles.ts | 9 +- packages/env/package.json | 2 +- packages/jotai/package.json | 10 +- packages/y-indexeddb/package.json | 4 +- yarn.lock | 217 ++++-- 19 files changed, 1311 insertions(+), 89 deletions(-) create mode 100644 apps/electron/layers/main/src/handlers/ui/getMetaData/index.ts create mode 100644 apps/electron/layers/main/src/handlers/ui/getMetaData/rules.ts create mode 100644 apps/electron/layers/main/src/handlers/ui/getMetaData/types.ts create mode 100644 apps/electron/layers/main/src/handlers/ui/getMetaData/utils.ts create mode 100644 packages/component/src/components/block-suite-editor/plugins/bookmark.tsx diff --git a/.github/workflows/nightly-build.yml b/.github/workflows/nightly-build.yml index b7c16da426..a6e122adc1 100644 --- a/.github/workflows/nightly-build.yml +++ b/.github/workflows/nightly-build.yml @@ -67,6 +67,7 @@ jobs: ENABLE_TEST_PROPERTIES: false ENABLE_IMAGE_PREVIEW_MODAL: false RELEASE_VERSION: ${{ needs.set-build-version.outputs.version }} + ENABLE_BOOKMARK_OPERATION: true - name: Upload Artifact (web-static) uses: actions/upload-artifact@v3 diff --git a/.github/workflows/release-desktop-app.yml b/.github/workflows/release-desktop-app.yml index 1c06854685..ffc0ef20ad 100644 --- a/.github/workflows/release-desktop-app.yml +++ b/.github/workflows/release-desktop-app.yml @@ -68,6 +68,7 @@ jobs: ENABLE_TEST_PROPERTIES: false ENABLE_IMAGE_PREVIEW_MODAL: false RELEASE_VERSION: ${{ github.event.inputs.version }} + ENABLE_BOOKMARK_OPERATION: false - name: Upload Artifact (web-static) uses: actions/upload-artifact@v3 diff --git a/apps/electron/layers/main/src/handlers/ui/getMetaData/index.ts b/apps/electron/layers/main/src/handlers/ui/getMetaData/index.ts new file mode 100644 index 0000000000..b4a367c315 --- /dev/null +++ b/apps/electron/layers/main/src/handlers/ui/getMetaData/index.ts @@ -0,0 +1,125 @@ +import type { CheerioAPI, Element } from 'cheerio'; +import { load } from 'cheerio'; +import got from 'got'; + +import type { Context, MetaData, Options, RuleSet } from './types'; + +export * from './types'; + +import { metaDataRules } from './rules'; + +const defaultOptions = { + maxRedirects: 5, + ua: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36', + lang: '*', + timeout: 10000, + forceImageHttps: true, + customRules: {}, +}; + +const runRule = function (ruleSet: RuleSet, $: CheerioAPI, context: Context) { + let maxScore = 0; + let value; + + for (let currRule = 0; currRule < ruleSet.rules.length; currRule++) { + const [query, handler] = ruleSet.rules[currRule]; + const elements = Array.from($(query)); + + if (elements.length) { + for (const element of elements) { + let score = ruleSet.rules.length - currRule; + + if (ruleSet.scorer) { + const newScore = ruleSet.scorer(element as Element, score); + + if (newScore) { + score = newScore; + } + } + + if (score > maxScore) { + maxScore = score; + value = handler(element as Element); + } + } + } + } + + if (value) { + if (ruleSet.processor) { + value = ruleSet.processor(value, context); + } + + return value; + } + + if (ruleSet.defaultValue) { + return ruleSet.defaultValue(context); + } + + return undefined; +}; + +const getMetaData = async function ( + input: string | Partial, + inputOptions: Partial = {} +) { + let url; + if (typeof input === 'object') { + inputOptions = input; + url = input.url || ''; + } else { + url = input; + } + + const options = Object.assign({}, defaultOptions, inputOptions); + + const rules: Record = { ...metaDataRules }; + Object.keys(options.customRules).forEach((key: string) => { + rules[key] = { + rules: [...metaDataRules[key].rules, ...options.customRules[key].rules], + defaultValue: + options.customRules[key].defaultValue || + metaDataRules[key].defaultValue, + processor: + options.customRules[key].processor || metaDataRules[key].processor, + }; + }); + + let html; + if (!options.html) { + const response = await got(url, { + headers: { + 'User-Agent': options.ua, + 'Accept-Language': options.lang, + }, + timeout: options.timeout, + ...(options.maxRedirects === 0 + ? { followRedirect: false } + : { maxRedirects: options.maxRedirects }), + }); + html = response.body; + } else { + html = options.html; + } + + const metadata: MetaData = {}; + const context: Context = { + url, + options, + }; + + const $ = load(html); + // console.log('==============================='); + // console.log('html'); + // console.log(doc); + + Object.keys(rules).forEach((key: string) => { + const ruleSet = rules[key]; + metadata[key] = runRule(ruleSet, $, context) || undefined; + }); + + return metadata; +}; + +export default getMetaData; diff --git a/apps/electron/layers/main/src/handlers/ui/getMetaData/rules.ts b/apps/electron/layers/main/src/handlers/ui/getMetaData/rules.ts new file mode 100644 index 0000000000..7800d67a9a --- /dev/null +++ b/apps/electron/layers/main/src/handlers/ui/getMetaData/rules.ts @@ -0,0 +1,690 @@ +import type { RuleSet } from './types'; +import { getProvider, makeUrlAbsolute, makeUrlSecure, parseUrl } from './utils'; + +export const metaDataRules: Record = { + title: { + rules: [ + [ + 'meta[property="og:title"][content]', + element => element.attribs['content'], + ], + ['meta[name="og:title"][content]', element => element.attribs['content']], + [ + 'meta[property="twitter:title"][content]', + element => element.attribs['content'], + ], + [ + 'meta[name="twitter:title"][content]', + element => element.attribs['content'], + ], + [ + 'meta[property="parsely-title"][content]', + element => element.attribs['content'], + ], + [ + 'meta[name="parsely-title"][content]', + element => element.attribs['content'], + ], + [ + 'meta[property="sailthru.title"][content]', + element => element.attribs['content'], + ], + [ + 'meta[name="sailthru.title"][content]', + element => element.attribs['content'], + ], + ['title', (element: any) => element.text], + ], + }, + description: { + rules: [ + [ + 'meta[property="og:description"][content]', + element => element.attribs['content'], + ], + [ + 'meta[name="og:description"][content]', + element => element.attribs['content'], + ], + [ + 'meta[property="description" i][content]', + element => element.attribs['content'], + ], + [ + 'meta[name="description" i][content]', + element => element.attribs['content'], + ], + [ + 'meta[property="sailthru.description"][content]', + element => element.attribs['content'], + ], + [ + 'meta[name="sailthru.description"][content]', + element => element.attribs['content'], + ], + [ + 'meta[property="twitter:description"][content]', + element => element.attribs['content'], + ], + [ + 'meta[name="twitter:description"][content]', + element => element.attribs['content'], + ], + [ + 'meta[property="summary" i][content]', + element => element.attribs['content'], + ], + [ + 'meta[name="summary" i][content]', + element => element.attribs['content'], + ], + ], + }, + language: { + rules: [ + ['html[lang]', element => element.attribs['lang']], + [ + 'meta[property="language" i][content]', + element => element.attribs['content'], + ], + [ + 'meta[name="language" i][content]', + element => element.attribs['content'], + ], + [ + 'meta[property="og:locale"][content]', + element => element.attribs['content'], + ], + [ + 'meta[name="og:locale"][content]', + element => element.attribs['content'], + ], + ], + processor: (language: any) => language.split('-')[0], + }, + type: { + rules: [ + [ + 'meta[property="og:type"][content]', + element => element.attribs['content'], + ], + ['meta[name="og:type"][content]', element => element.attribs['content']], + [ + 'meta[property="parsely-type"][content]', + element => element.attribs['content'], + ], + [ + 'meta[name="parsely-type"][content]', + element => element.attribs['content'], + ], + [ + 'meta[property="medium"][content]', + element => element.attribs['content'], + ], + ['meta[name="medium"][content]', element => element.attribs['content']], + ], + }, + url: { + rules: [ + [ + 'meta[property="og:url"][content]', + element => element.attribs['content'], + ], + ['meta[name="og:url"][content]', element => element.attribs['content']], + [ + 'meta[property="al:web:url"][content]', + element => element.attribs['content'], + ], + [ + 'meta[name="al:web:url"][content]', + element => element.attribs['content'], + ], + [ + 'meta[property="parsely-link"][content]', + element => element.attribs['content'], + ], + [ + 'meta[name="parsely-link"][content]', + element => element.attribs['content'], + ], + ['a.amp-canurl', element => element.attribs['href']], + ['link[rel="canonical"][href]', element => element.attribs['href']], + ], + defaultValue: context => context.url, + processor: (url: any, context) => makeUrlAbsolute(context.url, url), + }, + provider: { + rules: [ + [ + 'meta[property="og:site_name"][content]', + element => element.attribs['content'], + ], + [ + 'meta[name="og:site_name"][content]', + element => element.attribs['content'], + ], + [ + 'meta[property="publisher" i][content]', + element => element.attribs['content'], + ], + [ + 'meta[name="publisher" i][content]', + element => element.attribs['content'], + ], + [ + 'meta[property="application-name" i][content]', + element => element.attribs['content'], + ], + [ + 'meta[name="application-name" i][content]', + element => element.attribs['content'], + ], + [ + 'meta[property="al:android:app_name"][content]', + element => element.attribs['content'], + ], + [ + 'meta[name="al:android:app_name"][content]', + element => element.attribs['content'], + ], + [ + 'meta[property="al:iphone:app_name"][content]', + element => element.attribs['content'], + ], + [ + 'meta[name="al:iphone:app_name"][content]', + element => element.attribs['content'], + ], + [ + 'meta[property="al:ipad:app_name"][content]', + element => element.attribs['content'], + ], + [ + 'meta[name="al:ipad:app_name"][content]', + element => element.attribs['content'], + ], + [ + 'meta[property="al:ios:app_name"][content]', + element => element.attribs['content'], + ], + [ + 'meta[name="al:ios:app_name"][content]', + element => element.attribs['content'], + ], + [ + 'meta[property="twitter:app:name:iphone"][content]', + element => element.attribs['content'], + ], + [ + 'meta[name="twitter:app:name:iphone"][content]', + element => element.attribs['content'], + ], + [ + 'meta[property="twitter:app:name:ipad"][content]', + element => element.attribs['content'], + ], + [ + 'meta[name="twitter:app:name:ipad"][content]', + element => element.attribs['content'], + ], + [ + 'meta[property="twitter:app:name:googleplay"][content]', + element => element.attribs['content'], + ], + [ + 'meta[name="twitter:app:name:googleplay"][content]', + element => element.attribs['content'], + ], + ], + defaultValue: context => getProvider(parseUrl(context.url)), + }, + keywords: { + rules: [ + [ + 'meta[property="keywords" i][content]', + element => element.attribs['content'], + ], + [ + 'meta[name="keywords" i][content]', + element => element.attribs['content'], + ], + [ + 'meta[property="parsely-tags"][content]', + element => element.attribs['content'], + ], + [ + 'meta[name="parsely-tags"][content]', + element => element.attribs['content'], + ], + [ + 'meta[property="sailthru.tags"][content]', + element => element.attribs['content'], + ], + [ + 'meta[name="sailthru.tags"][content]', + element => element.attribs['content'], + ], + [ + 'meta[property="article:tag" i][content]', + element => element.attribs['content'], + ], + [ + 'meta[name="article:tag" i][content]', + element => element.attribs['content'], + ], + [ + 'meta[property="book:tag" i][content]', + element => element.attribs['content'], + ], + [ + 'meta[name="book:tag" i][content]', + element => element.attribs['content'], + ], + [ + 'meta[property="topic" i][content]', + element => element.attribs['content'], + ], + ['meta[name="topic" i][content]', element => element.attribs['content']], + ], + processor: (keywords: any) => + keywords.split(',').map((keyword: string) => keyword.trim()), + }, + section: { + rules: [ + [ + 'meta[property="article:section"][content]', + element => element.attribs['content'], + ], + [ + 'meta[name="article:section"][content]', + element => element.attribs['content'], + ], + [ + 'meta[property="category"][content]', + element => element.attribs['content'], + ], + ['meta[name="category"][content]', element => element.attribs['content']], + ], + }, + author: { + rules: [ + [ + 'meta[property="author" i][content]', + element => element.attribs['content'], + ], + ['meta[name="author" i][content]', element => element.attribs['content']], + [ + 'meta[property="article:author"][content]', + element => element.attribs['content'], + ], + [ + 'meta[name="article:author"][content]', + element => element.attribs['content'], + ], + [ + 'meta[property="book:author"][content]', + element => element.attribs['content'], + ], + [ + 'meta[name="book:author"][content]', + element => element.attribs['content'], + ], + [ + 'meta[property="parsely-author"][content]', + element => element.attribs['content'], + ], + [ + 'meta[name="parsely-author"][content]', + element => element.attribs['content'], + ], + [ + 'meta[property="sailthru.author"][content]', + element => element.attribs['content'], + ], + [ + 'meta[name="sailthru.author"][content]', + element => element.attribs['content'], + ], + ['a[class*="author" i]', (element: any) => element.text], + ['[rel="author"]', (element: any) => element.text], + [ + 'meta[property="twitter:creator"][content]', + element => element.attribs['content'], + ], + [ + 'meta[name="twitter:creator"][content]', + element => element.attribs['content'], + ], + [ + 'meta[property="profile:username"][content]', + element => element.attribs['content'], + ], + [ + 'meta[name="profile:username"][content]', + element => element.attribs['content'], + ], + ], + }, + published: { + rules: [ + [ + 'meta[property="article:published_time"][content]', + element => element.attribs['content'], + ], + [ + 'meta[name="article:published_time"][content]', + element => element.attribs['content'], + ], + [ + 'meta[property="published_time"][content]', + element => element.attribs['content'], + ], + [ + 'meta[name="published_time"][content]', + element => element.attribs['content'], + ], + [ + 'meta[property="parsely-pub-date"][content]', + element => element.attribs['content'], + ], + [ + 'meta[name="parsely-pub-date"][content]', + element => element.attribs['content'], + ], + [ + 'meta[property="sailthru.date"][content]', + element => element.attribs['content'], + ], + [ + 'meta[name="sailthru.date"][content]', + element => element.attribs['content'], + ], + [ + 'meta[property="date" i][content]', + element => element.attribs['content'], + ], + ['meta[name="date" i][content]', element => element.attribs['content']], + [ + 'meta[property="release_date" i][content]', + element => element.attribs['content'], + ], + [ + 'meta[name="release_date" i][content]', + element => element.attribs['content'], + ], + ['time[datetime]', element => element.attribs['datetime']], + ['time[datetime][pubdate]', element => element.attribs['datetime']], + ], + processor: (value: any) => + Date.parse(value.toString()) + ? new Date(value.toString()).toISOString() + : undefined, + }, + modified: { + rules: [ + [ + 'meta[property="og:updated_time"][content]', + element => element.attribs['content'], + ], + [ + 'meta[name="og:updated_time"][content]', + element => element.attribs['content'], + ], + [ + 'meta[property="article:modified_time"][content]', + element => element.attribs['content'], + ], + [ + 'meta[name="article:modified_time"][content]', + element => element.attribs['content'], + ], + [ + 'meta[property="updated_time" i][content]', + element => element.attribs['content'], + ], + [ + 'meta[name="updated_time" i][content]', + element => element.attribs['content'], + ], + [ + 'meta[property="modified_time"][content]', + element => element.attribs['content'], + ], + [ + 'meta[name="modified_time"][content]', + element => element.attribs['content'], + ], + [ + 'meta[property="revised"][content]', + element => element.attribs['content'], + ], + ['meta[name="revised"][content]', element => element.attribs['content']], + ], + processor: (value: any) => + Date.parse(value.toString()) + ? new Date(value.toString()).toISOString() + : undefined, + }, + robots: { + rules: [ + [ + 'meta[property="robots" i][content]', + element => element.attribs['content'], + ], + ['meta[name="robots" i][content]', element => element.attribs['content']], + ], + processor: (keywords: any) => + keywords.split(',').map((keyword: string) => keyword.trim()), + }, + copyright: { + rules: [ + [ + 'meta[property="copyright" i][content]', + element => element.attribs['content'], + ], + [ + 'meta[name="copyright" i][content]', + element => element.attribs['content'], + ], + ], + }, + email: { + rules: [ + [ + 'meta[property="email" i][content]', + element => element.attribs['content'], + ], + ['meta[name="email" i][content]', element => element.attribs['content']], + [ + 'meta[property="reply-to" i][content]', + element => element.attribs['content'], + ], + [ + 'meta[name="reply-to" i][content]', + element => element.attribs['content'], + ], + ], + }, + twitter: { + rules: [ + [ + 'meta[property="twitter:site"][content]', + element => element.attribs['content'], + ], + [ + 'meta[name="twitter:site"][content]', + element => element.attribs['content'], + ], + ], + }, + facebook: { + rules: [ + [ + 'meta[property="fb:pages"][content]', + element => element.attribs['content'], + ], + ['meta[name="fb:pages"][content]', element => element.attribs['content']], + ], + }, + image: { + rules: [ + [ + 'meta[property="og:image:secure_url"][content]', + element => element.attribs['content'], + ], + [ + 'meta[name="og:image:secure_url"][content]', + element => element.attribs['content'], + ], + [ + 'meta[property="og:image:url"][content]', + element => element.attribs['content'], + ], + [ + 'meta[name="og:image:url"][content]', + element => element.attribs['content'], + ], + [ + 'meta[property="og:image"][content]', + element => element.attribs['content'], + ], + ['meta[name="og:image"][content]', element => element.attribs['content']], + [ + 'meta[property="twitter:image"][content]', + element => element.attribs['content'], + ], + [ + 'meta[name="twitter:image"][content]', + element => element.attribs['content'], + ], + [ + 'meta[property="twitter:image:src"][content]', + element => element.attribs['content'], + ], + [ + 'meta[name="twitter:image:src"][content]', + element => element.attribs['content'], + ], + [ + 'meta[property="thumbnail"][content]', + element => element.attribs['content'], + ], + [ + 'meta[name="thumbnail"][content]', + element => element.attribs['content'], + ], + [ + 'meta[property="parsely-image-url"][content]', + element => element.attribs['content'], + ], + [ + 'meta[name="parsely-image-url"][content]', + element => element.attribs['content'], + ], + [ + 'meta[property="sailthru.image.full"][content]', + element => element.attribs['content'], + ], + [ + 'meta[name="sailthru.image.full"][content]', + element => element.attribs['content'], + ], + ], + processor: (imageUrl: any, context) => + context.options.forceImageHttps === true + ? makeUrlSecure(makeUrlAbsolute(context.url, imageUrl)) + : makeUrlAbsolute(context.url, imageUrl), + }, + icon: { + rules: [ + [ + 'link[rel="apple-touch-icon"][href]', + element => element.attribs['href'], + ], + [ + 'link[rel="apple-touch-icon-precomposed"][href]', + element => element.attribs['href'], + ], + ['link[rel="icon" i][href]', element => element.attribs['href']], + ['link[rel="fluid-icon"][href]', element => element.attribs['href']], + ['link[rel="shortcut icon"][href]', element => element.attribs['href']], + ['link[rel="Shortcut Icon"][href]', element => element.attribs['href']], + ['link[rel="mask-icon"][href]', element => element.attribs['href']], + ], + scorer: element => { + const sizes = element.attribs['sizes']; + if (sizes) { + const sizeMatches = sizes.match(/\d+/g); + if (sizeMatches) { + const parsed = parseInt(sizeMatches[0]); + if (!isNaN(parsed)) { + return parsed; + } + } + } + }, + defaultValue: context => makeUrlAbsolute(context.url, '/favicon.ico'), + processor: (iconUrl, context) => + context.options.forceImageHttps === true + ? makeUrlSecure(makeUrlAbsolute(context.url, iconUrl)) + : makeUrlAbsolute(context.url, iconUrl), + }, + video: { + rules: [ + [ + 'meta[property="og:video:secure_url"][content]', + element => element.attribs['content'], + ], + [ + 'meta[name="og:video:secure_url"][content]', + element => element.attribs['content'], + ], + [ + 'meta[property="og:video:url"][content]', + element => element.attribs['content'], + ], + [ + 'meta[name="og:video:url"][content]', + element => element.attribs['content'], + ], + [ + 'meta[property="og:video"][content]', + element => element.attribs['content'], + ], + ['meta[name="og:video"][content]', element => element.attribs['content']], + ], + processor: (imageUrl: any, context) => + context.options.forceImageHttps === true + ? makeUrlSecure(makeUrlAbsolute(context.url, imageUrl)) + : makeUrlAbsolute(context.url, imageUrl), + }, + audio: { + rules: [ + [ + 'meta[property="og:audio:secure_url"][content]', + element => element.attribs['content'], + ], + [ + 'meta[name="og:audio:secure_url"][content]', + element => element.attribs['content'], + ], + [ + 'meta[property="og:audio:url"][content]', + element => element.attribs['content'], + ], + [ + 'meta[name="og:audio:url"][content]', + element => element.attribs['content'], + ], + [ + 'meta[property="og:audio"][content]', + element => element.attribs['content'], + ], + ['meta[name="og:audio"][content]', element => element.attribs['content']], + ], + processor: (imageUrl: any, context) => + context.options.forceImageHttps === true + ? makeUrlSecure(makeUrlAbsolute(context.url, imageUrl)) + : makeUrlAbsolute(context.url, imageUrl), + }, +}; diff --git a/apps/electron/layers/main/src/handlers/ui/getMetaData/types.ts b/apps/electron/layers/main/src/handlers/ui/getMetaData/types.ts new file mode 100644 index 0000000000..4dc7171d59 --- /dev/null +++ b/apps/electron/layers/main/src/handlers/ui/getMetaData/types.ts @@ -0,0 +1,40 @@ +import type { Element } from 'cheerio'; + +export interface MetaData { + title?: string; + description?: string; + icon?: string; + image?: string; + keywords?: string[]; + language?: string; + type?: string; + url?: string; + provider?: string; + + [x: string]: string | string[] | undefined; +} + +export type MetadataRule = [string, (el: Element) => string | null]; + +export interface Context { + url: string; + options: Options; +} + +export interface RuleSet { + rules: MetadataRule[]; + defaultValue?: (context: Context) => string | string[]; + scorer?: (el: Element, score: any) => any; + processor?: (input: any, context: Context) => any; +} + +export interface Options { + maxRedirects?: number; + ua?: string; + lang?: string; + timeout?: number; + forceImageHttps?: boolean; + html?: string; + url?: string; + customRules?: Record; +} diff --git a/apps/electron/layers/main/src/handlers/ui/getMetaData/utils.ts b/apps/electron/layers/main/src/handlers/ui/getMetaData/utils.ts new file mode 100644 index 0000000000..6f9bb8948a --- /dev/null +++ b/apps/electron/layers/main/src/handlers/ui/getMetaData/utils.ts @@ -0,0 +1,28 @@ +import urlparse from 'url'; + +export function makeUrlAbsolute(base: string, relative: string): string { + const relativeParsed = urlparse.parse(relative); + + if (relativeParsed.host === null) { + return urlparse.resolve(base, relative); + } + + return relative; +} + +export function makeUrlSecure(url: string): string { + return url.replace(/^http:/, 'https:'); +} + +export function parseUrl(url: string): string { + return urlparse.parse(url).hostname || ''; +} + +export function getProvider(host: string): string { + return host + .replace(/www[a-zA-Z0-9]*\./, '') + .replace('.co.', '.') + .split('.') + .slice(0, -1) + .join(' '); +} diff --git a/apps/electron/layers/main/src/handlers/ui/index.ts b/apps/electron/layers/main/src/handlers/ui/index.ts index fc61e991d4..354ce50de6 100644 --- a/apps/electron/layers/main/src/handlers/ui/index.ts +++ b/apps/electron/layers/main/src/handlers/ui/index.ts @@ -1,7 +1,8 @@ -import { app, BrowserWindow, nativeTheme } from 'electron'; +import { app, BrowserWindow, nativeTheme, session } from 'electron'; import { isMacOS } from '../../../../utils'; import type { NamespaceHandlers } from '../type'; +import getMetaData from './getMetaData'; import { getGoogleOauthCode } from './google-auth'; export const uiHandlers = { @@ -39,4 +40,9 @@ export const uiHandlers = { getGoogleOauthCode: async () => { return getGoogleOauthCode(); }, + getBookmarkDataByLink: async (_, url: string) => { + return getMetaData(url, { + ua: session.defaultSession.getUserAgent(), + }); + }, } satisfies NamespaceHandlers; diff --git a/apps/electron/package.json b/apps/electron/package.json index 12c394eb5b..639bca70c9 100644 --- a/apps/electron/package.json +++ b/apps/electron/package.json @@ -57,6 +57,7 @@ }, "dependencies": { "better-sqlite3": "^8.3.0", + "cheerio": "^1.0.0-rc.12", "chokidar": "^3.5.3", "electron-updater": "^5.3.0", "nanoid": "^4.0.2", diff --git a/apps/web/package.json b/apps/web/package.json index a8422f6986..d886f5cf2c 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -18,12 +18,12 @@ "@affine/jotai": "workspace:*", "@affine/templates": "workspace:*", "@affine/workspace": "workspace:*", - "@blocksuite/blocks": "0.0.0-20230525011821-20259c76-nightly", - "@blocksuite/editor": "0.0.0-20230525011821-20259c76-nightly", - "@blocksuite/global": "0.0.0-20230525011821-20259c76-nightly", + "@blocksuite/blocks": "0.0.0-20230526010315-e1d4bf97-nightly", + "@blocksuite/editor": "0.0.0-20230526010315-e1d4bf97-nightly", + "@blocksuite/global": "0.0.0-20230526010315-e1d4bf97-nightly", "@blocksuite/icons": "^2.1.19", - "@blocksuite/lit": "0.0.0-20230525011821-20259c76-nightly", - "@blocksuite/store": "0.0.0-20230525011821-20259c76-nightly", + "@blocksuite/lit": "0.0.0-20230526010315-e1d4bf97-nightly", + "@blocksuite/store": "0.0.0-20230526010315-e1d4bf97-nightly", "@dnd-kit/core": "^6.0.8", "@dnd-kit/sortable": "^7.0.2", "@emotion/cache": "^11.11.0", diff --git a/apps/web/preset.config.mjs b/apps/web/preset.config.mjs index a7c1c3dbfc..beb9477f4c 100644 --- a/apps/web/preset.config.mjs +++ b/apps/web/preset.config.mjs @@ -1,6 +1,5 @@ // @ts-check import 'dotenv/config'; - /** * @type {import('@affine/env').BlockSuiteFeatureFlags} */ @@ -12,6 +11,7 @@ export const blockSuiteFeatureFlags = { enable_drag_handle: true, enable_surface: true, enable_linked_page: true, + enable_bookmark_operation: process.env.ENABLE_BOOKMARK_OPERATION === 'true', }; /** diff --git a/packages/component/package.json b/packages/component/package.json index b2de2a535b..b3784bf4e4 100644 --- a/packages/component/package.json +++ b/packages/component/package.json @@ -52,12 +52,12 @@ "rxjs": "^7.8.1" }, "devDependencies": { - "@blocksuite/blocks": "0.0.0-20230525011821-20259c76-nightly", - "@blocksuite/editor": "0.0.0-20230525011821-20259c76-nightly", - "@blocksuite/global": "0.0.0-20230525011821-20259c76-nightly", + "@blocksuite/blocks": "0.0.0-20230526010315-e1d4bf97-nightly", + "@blocksuite/editor": "0.0.0-20230526010315-e1d4bf97-nightly", + "@blocksuite/global": "0.0.0-20230526010315-e1d4bf97-nightly", "@blocksuite/icons": "^2.1.19", - "@blocksuite/lit": "0.0.0-20230525011821-20259c76-nightly", - "@blocksuite/store": "0.0.0-20230525011821-20259c76-nightly", + "@blocksuite/lit": "0.0.0-20230526010315-e1d4bf97-nightly", + "@blocksuite/store": "0.0.0-20230526010315-e1d4bf97-nightly", "@storybook/addon-actions": "^7.0.12", "@storybook/addon-coverage": "^0.0.8", "@storybook/addon-essentials": "^7.0.12", diff --git a/packages/component/src/components/block-suite-editor/index.tsx b/packages/component/src/components/block-suite-editor/index.tsx index a9744cb7a4..7e7cbc1db3 100644 --- a/packages/component/src/components/block-suite-editor/index.tsx +++ b/packages/component/src/components/block-suite-editor/index.tsx @@ -16,6 +16,14 @@ import { blockSuiteEditorHeaderStyle, blockSuiteEditorStyle, } from './index.css'; +import { bookmarkPlugin } from './plugins/bookmark'; + +export type EditorPlugin = { + flavour: string; + onInit?: (page: Page, editor: Readonly) => void; + onLoad?: (page: Page, editor: EditorContainer) => () => void; + render?: (props: { page: Page }) => ReactElement | null; +}; export type EditorProps = { page: Page; @@ -42,6 +50,9 @@ const ImagePreviewModal = lazy(() => })) ); +// todo(himself65): plugin-infra should support this +const plugins = [bookmarkPlugin]; + const BlockSuiteEditorImpl = (props: EditorProps): ReactElement => { const { onLoad, page, mode, style, onInit } = props; const JotaiEditorContainer = useAtomValue( @@ -66,13 +77,23 @@ const BlockSuiteEditorImpl = (props: EditorProps): ReactElement => { editor.page = page; if (page.root === null) { onInit(page, editor); + plugins.forEach(plugin => { + plugin.onInit?.(page, editor); + }); } } - }, [onInit, editor, props, page]); + }, [editor, page, onInit]); useEffect(() => { if (editor.page && onLoad) { - return onLoad(page, editor); + const disposes = [] as ((() => void) | undefined)[]; + disposes.push(onLoad?.(page, editor)); + disposes.push(...plugins.map(plugin => plugin.onLoad?.(page, editor))); + return () => { + disposes + .filter((dispose): dispose is () => void => !!dispose) + .forEach(dispose => dispose()); + }; } }, [editor, editor.page, page, onLoad]); @@ -180,6 +201,12 @@ export const BlockSuiteEditor = memo(function BlockSuiteEditor( )} )} + {plugins.map(plugin => { + const Renderer = plugin.render; + return Renderer ? ( + + ) : null; + })} ); }); diff --git a/packages/component/src/components/block-suite-editor/plugins/bookmark.tsx b/packages/component/src/components/block-suite-editor/plugins/bookmark.tsx new file mode 100644 index 0000000000..a9197cfc47 --- /dev/null +++ b/packages/component/src/components/block-suite-editor/plugins/bookmark.tsx @@ -0,0 +1,210 @@ +import { MenuItem, MuiClickAwayListener, PureMenu } from '@affine/component'; +import type { EditorPlugin } from '@affine/component/block-suite-editor'; +import { + getCurrentBlockRange, + getCurrentNativeRange, + getVirgoByModel, + hasNativeSelection, +} from '@blocksuite/blocks/std'; +import type { Page } from '@blocksuite/store'; +import { assertExists } from '@blocksuite/store'; +import { useEffect, useMemo, useRef, useState } from 'react'; + +const isCursorInLink = (page: Page) => { + if (!hasNativeSelection()) return false; + const blockRange = getCurrentBlockRange(page); + if ( + !blockRange || + blockRange.type !== 'Native' || + blockRange.startOffset !== blockRange.endOffset + ) { + return false; + } + const { + models: [model], + } = blockRange; + const vEditor = getVirgoByModel(model); + const delta = vEditor?.getDeltaByRangeIndex(blockRange.startOffset); + + return delta?.attributes?.link; +}; + +type ShortcutMap = { + [key: string]: (e: KeyboardEvent, page: Page) => void; +}; +const menuOptions = [ + { + id: 'dismiss', + label: 'Dismiss', + }, + { + id: 'bookmark', + label: 'Create bookmark', + }, +]; + +const handleEnter = ({ + page, + selectedOption, + callback, +}: { + page: Page; + selectedOption: keyof ShortcutMap; + callback: () => void; +}) => { + if (selectedOption === 'dismiss') { + return callback(); + } + const blockRange = getCurrentBlockRange(page) as Exclude< + ReturnType, + null + >; + const vEditor = getVirgoByModel(blockRange.models[0]); + const linkInfo = vEditor! + .getDeltasByVRange({ + index: blockRange.startOffset, + length: 0, + }) + .find(delta => delta[0]?.attributes?.link); + if (!linkInfo) { + return; + } + const [, { index, length }] = linkInfo; + const link = linkInfo[0]?.attributes?.link as string; + + const model = blockRange.models[0]; + const parent = page.getParent(model); + assertExists(parent); + const currentBlockIndex = parent.children.indexOf(model); + page.addBlock( + 'affine:bookmark', + { url: link }, + parent, + currentBlockIndex + 1 + ); + + vEditor!.deleteText({ + index, + length, + }); + + if (model.isEmpty()) { + page.deleteBlock(model); + } + return callback(); +}; + +const BookMarkMenu: EditorPlugin['render'] = ({ page }) => { + const [anchor, setAnchor] = useState(null); + const [selectedOption, setSelectedOption] = useState(''); + const shouldHijack = useRef(false); + const shortcutMap = useMemo( + () => ({ + ArrowUp: () => { + const curIndex = menuOptions.findIndex( + ({ id }) => id === selectedOption + ); + if (menuOptions[curIndex - 1]) { + setSelectedOption(menuOptions[curIndex - 1].id); + } else if (curIndex === -1) { + setSelectedOption(menuOptions[0].id); + } else { + setSelectedOption(menuOptions[menuOptions.length - 1].id); + } + }, + ArrowDown: () => { + const curIndex = menuOptions.findIndex( + ({ id }) => id === selectedOption + ); + if (curIndex !== -1 && menuOptions[curIndex + 1]) { + setSelectedOption(menuOptions[curIndex + 1].id); + } else { + setSelectedOption(menuOptions[0].id); + } + }, + Enter: () => + handleEnter({ + page, + selectedOption, + callback: () => { + shouldHijack.current = false; + setAnchor(null); + }, + }), + }), + [page, selectedOption] + ); + + useEffect(() => { + // TODO: textUpdated slot is not working + // const disposer = page.slots.textUpdated.on(() => { + // console.log('text Updated', page); + // }); + const disposer = page.slots.historyUpdated.on(() => { + if (!isCursorInLink(page)) { + return; + } + setAnchor(getCurrentNativeRange()); + shouldHijack.current = true; + }); + + return () => { + // disposer.dispose(); + disposer.dispose(); + }; + }, [page, shortcutMap]); + + useEffect(() => { + const keydown = (e: KeyboardEvent) => { + if (!shouldHijack.current) { + return; + } + const shortcut = shortcutMap[e.key]; + if (shortcut) { + e.stopPropagation(); + e.preventDefault(); + shortcut(e, page); + } + }; + document.addEventListener('keydown', keydown, { capture: true }); + + return () => { + document.removeEventListener('keydown', keydown, { capture: true }); + }; + }, [page, shortcutMap]); + + return anchor ? ( + { + setAnchor(null); + setSelectedOption(''); + }} + > +
+ + {menuOptions.map(({ id, label }) => { + return ( + {}} + > + {label} + + ); + })} + +
+
+ ) : null; +}; + +const Defender: EditorPlugin['render'] = props => { + const flag = props.page.awarenessStore.getFlag('enable_bookmark_operation'); + return flag ? : null; +}; + +export const bookmarkPlugin: EditorPlugin = { + flavour: 'bookmark', + render: Defender, +}; diff --git a/packages/component/src/ui/menu/menu-item.tsx b/packages/component/src/ui/menu/menu-item.tsx index 23b485399d..b8c61f06f2 100644 --- a/packages/component/src/ui/menu/menu-item.tsx +++ b/packages/component/src/ui/menu/menu-item.tsx @@ -13,6 +13,7 @@ export type IconMenuProps = PropsWithChildren<{ endIcon?: ReactElement; iconSize?: [number, number]; disabled?: boolean; + active?: boolean; }> & HTMLAttributes; diff --git a/packages/component/src/ui/menu/styles.ts b/packages/component/src/ui/menu/styles.ts index a06c96ca83..e2c1ce026c 100644 --- a/packages/component/src/ui/menu/styles.ts +++ b/packages/component/src/ui/menu/styles.ts @@ -51,7 +51,8 @@ export const StyledContent = styled('div')(() => { export const StyledMenuItem = styled('button')<{ isDir?: boolean; disabled?: boolean; -}>(({ isDir = false, disabled = false }) => { + active?: boolean; +}>(({ isDir = false, disabled = false, active = false }) => { return { width: '100%', borderRadius: '5px', @@ -82,5 +83,11 @@ export const StyledMenuItem = styled('button')<{ : { backgroundColor: 'var(--affine-hover-color)', }, + + ...(active && !disabled + ? { + backgroundColor: 'var(--affine-hover-color)', + } + : {}), }; }); diff --git a/packages/env/package.json b/packages/env/package.json index 03c650cccb..3aa58f8102 100644 --- a/packages/env/package.json +++ b/packages/env/package.json @@ -4,7 +4,7 @@ "main": "./src/index.ts", "module": "./src/index.ts", "devDependencies": { - "@blocksuite/global": "0.0.0-20230525011821-20259c76-nightly", + "@blocksuite/global": "0.0.0-20230526010315-e1d4bf97-nightly", "next": "^13.4.2", "react": "18.3.0-canary-16d053d59-20230506", "react-dom": "18.3.0-canary-16d053d59-20230506", diff --git a/packages/jotai/package.json b/packages/jotai/package.json index 224fca94b7..87c1703882 100644 --- a/packages/jotai/package.json +++ b/packages/jotai/package.json @@ -7,11 +7,11 @@ "jotai": "^2.1.0" }, "devDependencies": { - "@blocksuite/blocks": "0.0.0-20230525011821-20259c76-nightly", - "@blocksuite/editor": "0.0.0-20230525011821-20259c76-nightly", - "@blocksuite/global": "0.0.0-20230525011821-20259c76-nightly", - "@blocksuite/lit": "0.0.0-20230525011821-20259c76-nightly", - "@blocksuite/store": "0.0.0-20230525011821-20259c76-nightly", + "@blocksuite/blocks": "0.0.0-20230526010315-e1d4bf97-nightly", + "@blocksuite/editor": "0.0.0-20230526010315-e1d4bf97-nightly", + "@blocksuite/global": "0.0.0-20230526010315-e1d4bf97-nightly", + "@blocksuite/lit": "0.0.0-20230526010315-e1d4bf97-nightly", + "@blocksuite/store": "0.0.0-20230526010315-e1d4bf97-nightly", "lottie-web": "^5.11.0" }, "peerDependencies": { diff --git a/packages/y-indexeddb/package.json b/packages/y-indexeddb/package.json index 9431d6a7a7..fdfdc61c56 100644 --- a/packages/y-indexeddb/package.json +++ b/packages/y-indexeddb/package.json @@ -28,8 +28,8 @@ "idb": "^7.1.1" }, "devDependencies": { - "@blocksuite/blocks": "0.0.0-20230525011821-20259c76-nightly", - "@blocksuite/store": "0.0.0-20230525011821-20259c76-nightly", + "@blocksuite/blocks": "0.0.0-20230526010315-e1d4bf97-nightly", + "@blocksuite/store": "0.0.0-20230526010315-e1d4bf97-nightly", "vite": "^4.3.8", "vite-plugin-dts": "^2.3.0", "y-indexeddb": "^9.0.11" diff --git a/yarn.lock b/yarn.lock index d9d3e888e7..a1413d169f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -50,12 +50,12 @@ __metadata: "@affine/i18n": "workspace:*" "@affine/jotai": "workspace:*" "@affine/workspace": "workspace:*" - "@blocksuite/blocks": 0.0.0-20230525011821-20259c76-nightly - "@blocksuite/editor": 0.0.0-20230525011821-20259c76-nightly - "@blocksuite/global": 0.0.0-20230525011821-20259c76-nightly + "@blocksuite/blocks": 0.0.0-20230526010315-e1d4bf97-nightly + "@blocksuite/editor": 0.0.0-20230526010315-e1d4bf97-nightly + "@blocksuite/global": 0.0.0-20230526010315-e1d4bf97-nightly "@blocksuite/icons": ^2.1.19 - "@blocksuite/lit": 0.0.0-20230525011821-20259c76-nightly - "@blocksuite/store": 0.0.0-20230525011821-20259c76-nightly + "@blocksuite/lit": 0.0.0-20230526010315-e1d4bf97-nightly + "@blocksuite/store": 0.0.0-20230526010315-e1d4bf97-nightly "@dnd-kit/core": ^6.0.8 "@dnd-kit/sortable": ^7.0.2 "@emotion/cache": ^11.11.0 @@ -148,6 +148,7 @@ __metadata: "@types/fs-extra": ^11.0.1 "@types/uuid": ^9.0.1 better-sqlite3: ^8.3.0 + cheerio: ^1.0.0-rc.12 chokidar: ^3.5.3 cross-env: 7.0.3 electron: 24.4.0 @@ -175,7 +176,7 @@ __metadata: version: 0.0.0-use.local resolution: "@affine/env@workspace:packages/env" dependencies: - "@blocksuite/global": 0.0.0-20230525011821-20259c76-nightly + "@blocksuite/global": 0.0.0-20230526010315-e1d4bf97-nightly lit: ^2.7.4 next: ^13.4.2 react: 18.3.0-canary-16d053d59-20230506 @@ -221,11 +222,11 @@ __metadata: resolution: "@affine/jotai@workspace:packages/jotai" dependencies: "@affine/env": "workspace:*" - "@blocksuite/blocks": 0.0.0-20230525011821-20259c76-nightly - "@blocksuite/editor": 0.0.0-20230525011821-20259c76-nightly - "@blocksuite/global": 0.0.0-20230525011821-20259c76-nightly - "@blocksuite/lit": 0.0.0-20230525011821-20259c76-nightly - "@blocksuite/store": 0.0.0-20230525011821-20259c76-nightly + "@blocksuite/blocks": 0.0.0-20230526010315-e1d4bf97-nightly + "@blocksuite/editor": 0.0.0-20230526010315-e1d4bf97-nightly + "@blocksuite/global": 0.0.0-20230526010315-e1d4bf97-nightly + "@blocksuite/lit": 0.0.0-20230526010315-e1d4bf97-nightly + "@blocksuite/store": 0.0.0-20230526010315-e1d4bf97-nightly jotai: ^2.1.0 lottie-web: ^5.11.0 peerDependencies: @@ -310,12 +311,12 @@ __metadata: "@affine/jotai": "workspace:*" "@affine/templates": "workspace:*" "@affine/workspace": "workspace:*" - "@blocksuite/blocks": 0.0.0-20230525011821-20259c76-nightly - "@blocksuite/editor": 0.0.0-20230525011821-20259c76-nightly - "@blocksuite/global": 0.0.0-20230525011821-20259c76-nightly + "@blocksuite/blocks": 0.0.0-20230526010315-e1d4bf97-nightly + "@blocksuite/editor": 0.0.0-20230526010315-e1d4bf97-nightly + "@blocksuite/global": 0.0.0-20230526010315-e1d4bf97-nightly "@blocksuite/icons": ^2.1.19 - "@blocksuite/lit": 0.0.0-20230525011821-20259c76-nightly - "@blocksuite/store": 0.0.0-20230525011821-20259c76-nightly + "@blocksuite/lit": 0.0.0-20230526010315-e1d4bf97-nightly + "@blocksuite/store": 0.0.0-20230526010315-e1d4bf97-nightly "@dnd-kit/core": ^6.0.8 "@dnd-kit/sortable": ^7.0.2 "@emotion/cache": ^11.11.0 @@ -2144,14 +2145,14 @@ __metadata: languageName: node linkType: hard -"@blocksuite/blocks@npm:0.0.0-20230525011821-20259c76-nightly": - version: 0.0.0-20230525011821-20259c76-nightly - resolution: "@blocksuite/blocks@npm:0.0.0-20230525011821-20259c76-nightly" +"@blocksuite/blocks@npm:0.0.0-20230526010315-e1d4bf97-nightly": + version: 0.0.0-20230526010315-e1d4bf97-nightly + resolution: "@blocksuite/blocks@npm:0.0.0-20230526010315-e1d4bf97-nightly" dependencies: - "@blocksuite/connector": 0.0.0-20230525011821-20259c76-nightly - "@blocksuite/global": 0.0.0-20230525011821-20259c76-nightly - "@blocksuite/phasor": 0.0.0-20230525011821-20259c76-nightly - "@blocksuite/virgo": 0.0.0-20230525011821-20259c76-nightly + "@blocksuite/connector": 0.0.0-20230526010315-e1d4bf97-nightly + "@blocksuite/global": 0.0.0-20230526010315-e1d4bf97-nightly + "@blocksuite/phasor": 0.0.0-20230526010315-e1d4bf97-nightly + "@blocksuite/virgo": 0.0.0-20230526010315-e1d4bf97-nightly "@popperjs/core": ^2.11.6 hotkeys-js: ^3.10.1 lit: ^2.7.3 @@ -2160,40 +2161,40 @@ __metadata: turndown: ^7.1.1 zod: ^3.21.4 peerDependencies: - "@blocksuite/lit": 0.0.0-20230525011821-20259c76-nightly - "@blocksuite/store": 0.0.0-20230525011821-20259c76-nightly + "@blocksuite/lit": 0.0.0-20230526010315-e1d4bf97-nightly + "@blocksuite/store": 0.0.0-20230526010315-e1d4bf97-nightly yjs: ^13 - checksum: d17ffb4194ca0ea3431bc7b3abef34f0d265c143a9a6ca446b64761d993b1f7c7348afd83bb9a75be5b09f74773ddc94c3c9f1e81ba6d3e110ed2f9b73a727cb + checksum: 68fca65f12d7109c362b8c29b076149463ad11c892e7db243ea0c2dd33bfb65233a14339d716897562bdbb6da98e1d52f33a403a6af11685f8dd4f9182e185d2 languageName: node linkType: hard -"@blocksuite/connector@npm:0.0.0-20230525011821-20259c76-nightly": - version: 0.0.0-20230525011821-20259c76-nightly - resolution: "@blocksuite/connector@npm:0.0.0-20230525011821-20259c76-nightly" - checksum: e7e0a1bcc8d02816e8885858dc7a7a3b81bfd5dd24d55e0c5a0f75f399fddc4f2491d25d3afa0f95e9c6cd23db464577bc884f6acd7c4a06f102f370ad95b2f2 +"@blocksuite/connector@npm:0.0.0-20230526010315-e1d4bf97-nightly": + version: 0.0.0-20230526010315-e1d4bf97-nightly + resolution: "@blocksuite/connector@npm:0.0.0-20230526010315-e1d4bf97-nightly" + checksum: dbee999f2ffd9784e9c57df397bf1ecc3d9e88a9a640585c6e63ede3a3e66f44bf9907db800f0a64ce79c9ba6a697bee28e74a0c8b4383b073f3be61209a6816 languageName: node linkType: hard -"@blocksuite/editor@npm:0.0.0-20230525011821-20259c76-nightly": - version: 0.0.0-20230525011821-20259c76-nightly - resolution: "@blocksuite/editor@npm:0.0.0-20230525011821-20259c76-nightly" +"@blocksuite/editor@npm:0.0.0-20230526010315-e1d4bf97-nightly": + version: 0.0.0-20230526010315-e1d4bf97-nightly + resolution: "@blocksuite/editor@npm:0.0.0-20230526010315-e1d4bf97-nightly" dependencies: - "@blocksuite/global": 0.0.0-20230525011821-20259c76-nightly + "@blocksuite/global": 0.0.0-20230526010315-e1d4bf97-nightly "@toeverything/theme": 0.5.9 lit: ^2.7.3 marked: ^4.2.12 turndown: ^7.1.1 peerDependencies: - "@blocksuite/blocks": 0.0.0-20230525011821-20259c76-nightly - "@blocksuite/lit": 0.0.0-20230525011821-20259c76-nightly - "@blocksuite/store": 0.0.0-20230525011821-20259c76-nightly - checksum: db7dbf81b428b1eeac50cdce4212890beb8fef7511eae4d38670012435f9d9d11b8cd268a54f977fe6659256d2df70d60f56c266ac5335cc8aa8a0012db440cf + "@blocksuite/blocks": 0.0.0-20230526010315-e1d4bf97-nightly + "@blocksuite/lit": 0.0.0-20230526010315-e1d4bf97-nightly + "@blocksuite/store": 0.0.0-20230526010315-e1d4bf97-nightly + checksum: 1de0b76a10eaa5fe8b8a7803cbe0b4817c39b8e9351bda2dec2c7859042ab0124c4f5649097c085080c574cf2b9cfd3560cc9f966297ad775b4179bf95775000 languageName: node linkType: hard -"@blocksuite/global@npm:0.0.0-20230525011821-20259c76-nightly": - version: 0.0.0-20230525011821-20259c76-nightly - resolution: "@blocksuite/global@npm:0.0.0-20230525011821-20259c76-nightly" +"@blocksuite/global@npm:0.0.0-20230526010315-e1d4bf97-nightly": + version: 0.0.0-20230526010315-e1d4bf97-nightly + resolution: "@blocksuite/global@npm:0.0.0-20230526010315-e1d4bf97-nightly" dependencies: ansi-colors: ^4.1.3 zod: ^3.21.4 @@ -2202,7 +2203,7 @@ __metadata: peerDependenciesMeta: lit: optional: true - checksum: 0370200a36cec7c12346e274d3c06485b529dff36d1622e1de9d8833c0c516f802abcfdc81aafa42e75eecdb6edd7bda6ee24dbad2e1c0531e6e6e4027d9862e + checksum: 76f321e5e7f76011628784bc084cf724c67e357bbb286c3f843388d9a35903e963bb89fea8a7ed39c29990b6ccde8ab9fde1532da9eb47d97ff481a2950d34ce languageName: node linkType: hard @@ -2216,38 +2217,38 @@ __metadata: languageName: node linkType: hard -"@blocksuite/lit@npm:0.0.0-20230525011821-20259c76-nightly": - version: 0.0.0-20230525011821-20259c76-nightly - resolution: "@blocksuite/lit@npm:0.0.0-20230525011821-20259c76-nightly" +"@blocksuite/lit@npm:0.0.0-20230526010315-e1d4bf97-nightly": + version: 0.0.0-20230526010315-e1d4bf97-nightly + resolution: "@blocksuite/lit@npm:0.0.0-20230526010315-e1d4bf97-nightly" dependencies: - "@blocksuite/global": 0.0.0-20230525011821-20259c76-nightly + "@blocksuite/global": 0.0.0-20230526010315-e1d4bf97-nightly lit: ^2.7.3 peerDependencies: - "@blocksuite/store": 0.0.0-20230525011821-20259c76-nightly - checksum: 029a462a7cdfda2d370a2df57bba690f72d9033516af221a449bedcd59b29d8dd59a1a4e024340756498f9f45c51ee1ca977176df6e46be347ce002199f2a5ae + "@blocksuite/store": 0.0.0-20230526010315-e1d4bf97-nightly + checksum: 7d1ce7a49758dab759af36fa5b234ac1eeb557cdae72d517b392c49ece7e15a79044951648363fce03f9b54150c39eca8b130349dfcd1e3d4a3e1b0f17c42b21 languageName: node linkType: hard -"@blocksuite/phasor@npm:0.0.0-20230525011821-20259c76-nightly": - version: 0.0.0-20230525011821-20259c76-nightly - resolution: "@blocksuite/phasor@npm:0.0.0-20230525011821-20259c76-nightly" +"@blocksuite/phasor@npm:0.0.0-20230526010315-e1d4bf97-nightly": + version: 0.0.0-20230526010315-e1d4bf97-nightly + resolution: "@blocksuite/phasor@npm:0.0.0-20230526010315-e1d4bf97-nightly" dependencies: - "@blocksuite/global": 0.0.0-20230525011821-20259c76-nightly + "@blocksuite/global": 0.0.0-20230526010315-e1d4bf97-nightly fractional-indexing: ^3.2.0 roughjs: ^4.5.2 peerDependencies: nanoid: ^4 yjs: ^13 - checksum: 52979810f3594486e78c109829ffd6ca5a41c7ac6981ebef617c4947c8d7084337b042e0c40fda119a148204c9e0c09e66ee39a0da43f7b1fab1a1f9e561a795 + checksum: 2708d5d0cb41ba5279871f5f9b57b5fcbf72ebf29d034b6aa36465b82a34aa029dc6ed16b3f1cb92baee0b8574594bfcdd8cdd895c5f174d13cddf357f3ef46a languageName: node linkType: hard -"@blocksuite/store@npm:0.0.0-20230525011821-20259c76-nightly": - version: 0.0.0-20230525011821-20259c76-nightly - resolution: "@blocksuite/store@npm:0.0.0-20230525011821-20259c76-nightly" +"@blocksuite/store@npm:0.0.0-20230526010315-e1d4bf97-nightly": + version: 0.0.0-20230526010315-e1d4bf97-nightly + resolution: "@blocksuite/store@npm:0.0.0-20230526010315-e1d4bf97-nightly" dependencies: - "@blocksuite/global": 0.0.0-20230525011821-20259c76-nightly - "@blocksuite/virgo": 0.0.0-20230525011821-20259c76-nightly + "@blocksuite/global": 0.0.0-20230526010315-e1d4bf97-nightly + "@blocksuite/virgo": 0.0.0-20230526010315-e1d4bf97-nightly "@types/flexsearch": ^0.7.3 buffer: ^6.0.3 flexsearch: 0.7.21 @@ -2262,20 +2263,20 @@ __metadata: zod: ^3.21.4 peerDependencies: yjs: ^13 - checksum: 9e6df21af3dc654fee45d150e1a139f9d9349711eaa3751e82154b0f4de74ae7a4b2b0741535ce34e675974cff9da6a72cefa57aa9c11fce96aa60aa5a12f112 + checksum: 92b14fcbafefba25dc95a3820df73715fd211464a2742ac7124420a4a393075fe1dd699e4f218a257291622f1f076c974d37b4ca00d5d0b40073a0b68490264f languageName: node linkType: hard -"@blocksuite/virgo@npm:0.0.0-20230525011821-20259c76-nightly": - version: 0.0.0-20230525011821-20259c76-nightly - resolution: "@blocksuite/virgo@npm:0.0.0-20230525011821-20259c76-nightly" +"@blocksuite/virgo@npm:0.0.0-20230526010315-e1d4bf97-nightly": + version: 0.0.0-20230526010315-e1d4bf97-nightly + resolution: "@blocksuite/virgo@npm:0.0.0-20230526010315-e1d4bf97-nightly" dependencies: - "@blocksuite/global": 0.0.0-20230525011821-20259c76-nightly + "@blocksuite/global": 0.0.0-20230526010315-e1d4bf97-nightly zod: ^3.21.4 peerDependencies: lit: ^2.7 yjs: ^13 - checksum: aa14f5d16682689feab13e2723314d86257fa47a4ce6e55c7367289bed22b10df5158f49d77ccd04bedae4d8009a80b3c7143a6c379492cba18c96025b92891f + checksum: 3eea9e391e005ff63b527cfb3950a212acd762dcce27c59ca397d07d302b7dfb5c1b36371d8127204a2e04cfb7b88f00aa4ddb24d8495419665f4fb86d1c14f9 languageName: node linkType: hard @@ -8677,8 +8678,8 @@ __metadata: version: 0.0.0-use.local resolution: "@toeverything/y-indexeddb@workspace:packages/y-indexeddb" dependencies: - "@blocksuite/blocks": 0.0.0-20230525011821-20259c76-nightly - "@blocksuite/store": 0.0.0-20230525011821-20259c76-nightly + "@blocksuite/blocks": 0.0.0-20230526010315-e1d4bf97-nightly + "@blocksuite/store": 0.0.0-20230526010315-e1d4bf97-nightly idb: ^7.1.1 vite: ^4.3.8 vite-plugin-dts: ^2.3.0 @@ -11323,6 +11324,13 @@ __metadata: languageName: node linkType: hard +"boolbase@npm:^1.0.0": + version: 1.0.0 + resolution: "boolbase@npm:1.0.0" + checksum: 3e25c80ef626c3a3487c73dbfc70ac322ec830666c9ad915d11b701142fab25ec1e63eff2c450c74347acfd2de854ccde865cd79ef4db1683f7c7b046ea43bb0 + languageName: node + linkType: hard + "boolean@npm:^3.0.1": version: 3.2.0 resolution: "boolean@npm:3.2.0" @@ -11914,6 +11922,35 @@ __metadata: languageName: node linkType: hard +"cheerio-select@npm:^2.1.0": + version: 2.1.0 + resolution: "cheerio-select@npm:2.1.0" + dependencies: + boolbase: ^1.0.0 + css-select: ^5.1.0 + css-what: ^6.1.0 + domelementtype: ^2.3.0 + domhandler: ^5.0.3 + domutils: ^3.0.1 + checksum: 843d6d479922f28a6c5342c935aff1347491156814de63c585a6eb73baf7bb4185c1b4383a1195dca0f12e3946d737c7763bcef0b9544c515d905c5c44c5308b + languageName: node + linkType: hard + +"cheerio@npm:^1.0.0-rc.12": + version: 1.0.0-rc.12 + resolution: "cheerio@npm:1.0.0-rc.12" + dependencies: + cheerio-select: ^2.1.0 + dom-serializer: ^2.0.0 + domhandler: ^5.0.3 + domutils: ^3.0.1 + htmlparser2: ^8.0.1 + parse5: ^7.0.0 + parse5-htmlparser2-tree-adapter: ^7.0.0 + checksum: 5d4c1b7a53cf22d3a2eddc0aff70cf23cbb30d01a4c79013e703a012475c02461aa1fcd99127e8d83a02216386ed6942b2c8103845fd0812300dd199e6e7e054 + languageName: node + linkType: hard + "chokidar@npm:3.5.3, chokidar@npm:^3.4.2, chokidar@npm:^3.5.2, chokidar@npm:^3.5.3": version: 3.5.3 resolution: "chokidar@npm:3.5.3" @@ -12764,6 +12801,19 @@ __metadata: languageName: node linkType: hard +"css-select@npm:^5.1.0": + version: 5.1.0 + resolution: "css-select@npm:5.1.0" + dependencies: + boolbase: ^1.0.0 + css-what: ^6.1.0 + domhandler: ^5.0.2 + domutils: ^3.0.1 + nth-check: ^2.0.1 + checksum: 2772c049b188d3b8a8159907192e926e11824aea525b8282981f72ba3f349cf9ecd523fdf7734875ee2cb772246c22117fc062da105b6d59afe8dcd5c99c9bda + languageName: node + linkType: hard + "css-spring@npm:^4.1.0": version: 4.1.0 resolution: "css-spring@npm:4.1.0" @@ -12780,6 +12830,13 @@ __metadata: languageName: node linkType: hard +"css-what@npm:^6.1.0": + version: 6.1.0 + resolution: "css-what@npm:6.1.0" + checksum: b975e547e1e90b79625918f84e67db5d33d896e6de846c9b584094e529f0c63e2ab85ee33b9daffd05bff3a146a1916bec664e18bb76dd5f66cbff9fc13b2bbe + languageName: node + linkType: hard + "css.escape@npm:^1.5.1": version: 1.5.1 resolution: "css.escape@npm:1.5.1" @@ -20958,6 +21015,15 @@ __metadata: languageName: node linkType: hard +"nth-check@npm:^2.0.1": + version: 2.1.1 + resolution: "nth-check@npm:2.1.1" + dependencies: + boolbase: ^1.0.0 + checksum: 5afc3dafcd1573b08877ca8e6148c52abd565f1d06b1eb08caf982e3fa289a82f2cae697ffb55b5021e146d60443f1590a5d6b944844e944714a5b549675bcd3 + languageName: node + linkType: hard + "nullthrows@npm:^1.1.1": version: 1.1.1 resolution: "nullthrows@npm:1.1.1" @@ -21499,6 +21565,25 @@ __metadata: languageName: node linkType: hard +"parse5-htmlparser2-tree-adapter@npm:^7.0.0": + version: 7.0.0 + resolution: "parse5-htmlparser2-tree-adapter@npm:7.0.0" + dependencies: + domhandler: ^5.0.2 + parse5: ^7.0.0 + checksum: fc5d01e07733142a1baf81de5c2a9c41426c04b7ab29dd218acb80cd34a63177c90aff4a4aee66cf9f1d0aeecff1389adb7452ad6f8af0a5888e3e9ad6ef733d + languageName: node + linkType: hard + +"parse5@npm:^7.0.0": + version: 7.1.2 + resolution: "parse5@npm:7.1.2" + dependencies: + entities: ^4.4.0 + checksum: 59465dd05eb4c5ec87b76173d1c596e152a10e290b7abcda1aecf0f33be49646ea74840c69af975d7887543ea45564801736356c568d6b5e71792fd0f4055713 + languageName: node + linkType: hard + "parseurl@npm:~1.3.2, parseurl@npm:~1.3.3": version: 1.3.3 resolution: "parseurl@npm:1.3.3"