mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-07 01:53:45 +00:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
22187f964a | ||
|
|
cf7b026832 | ||
|
|
e6818b4f14 | ||
|
|
aab9925aa1 | ||
|
|
86218d87c2 | ||
|
|
de4084495b |
@@ -75,7 +75,7 @@
|
||||
"@vitest/coverage-istanbul": "1.6.0",
|
||||
"@vitest/ui": "1.6.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"electron": "^31.1.0",
|
||||
"electron": "~30.1.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-import-x": "^0.5.0",
|
||||
|
||||
4
packages/common/env/package.json
vendored
4
packages/common/env/package.json
vendored
@@ -3,8 +3,8 @@
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"devDependencies": {
|
||||
"@blocksuite/global": "0.16.0-canary-202407040721-5bf36c3",
|
||||
"@blocksuite/store": "0.16.0-canary-202407040721-5bf36c3",
|
||||
"@blocksuite/global": "0.16.0-canary-202407050348-4620c21",
|
||||
"@blocksuite/store": "0.16.0-canary-202407050348-4620c21",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"vitest": "1.6.0"
|
||||
|
||||
1
packages/common/env/src/global.ts
vendored
1
packages/common/env/src/global.ts
vendored
@@ -24,6 +24,7 @@ export const runtimeFlagsSchema = z.object({
|
||||
enablePayment: z.boolean(),
|
||||
enablePageHistory: z.boolean(),
|
||||
enableExperimentalFeature: z.boolean(),
|
||||
enableInfoModal: z.boolean(),
|
||||
allowLocalWorkspace: z.boolean(),
|
||||
// this is for the electron app
|
||||
serverUrlPrefix: z.string(),
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
"@affine/debug": "workspace:*",
|
||||
"@affine/env": "workspace:*",
|
||||
"@affine/templates": "workspace:*",
|
||||
"@blocksuite/blocks": "0.16.0-canary-202407040721-5bf36c3",
|
||||
"@blocksuite/global": "0.16.0-canary-202407040721-5bf36c3",
|
||||
"@blocksuite/store": "0.16.0-canary-202407040721-5bf36c3",
|
||||
"@blocksuite/blocks": "0.16.0-canary-202407050348-4620c21",
|
||||
"@blocksuite/global": "0.16.0-canary-202407050348-4620c21",
|
||||
"@blocksuite/store": "0.16.0-canary-202407050348-4620c21",
|
||||
"@datastructures-js/binary-search-tree": "^5.3.2",
|
||||
"foxact": "^0.2.33",
|
||||
"fuse.js": "^7.0.0",
|
||||
@@ -33,8 +33,8 @@
|
||||
"devDependencies": {
|
||||
"@affine-test/fixtures": "workspace:*",
|
||||
"@affine/templates": "workspace:*",
|
||||
"@blocksuite/block-std": "0.16.0-canary-202407040721-5bf36c3",
|
||||
"@blocksuite/presets": "0.16.0-canary-202407040721-5bf36c3",
|
||||
"@blocksuite/block-std": "0.16.0-canary-202407050348-4620c21",
|
||||
"@blocksuite/presets": "0.16.0-canary-202407050348-4620c21",
|
||||
"@testing-library/react": "^16.0.0",
|
||||
"async-call-rpc": "^6.4.0",
|
||||
"fake-indexeddb": "^6.0.0",
|
||||
|
||||
@@ -299,9 +299,9 @@ export class FullTextInvertedIndex implements InvertedIndex {
|
||||
async insert(trx: DataStructRWTransaction, id: number, terms: string[]) {
|
||||
for (let i = 0; i < terms.length; i++) {
|
||||
const tokenMap = new Map<string, Token[]>();
|
||||
const term = terms[i];
|
||||
const originString = terms[i];
|
||||
|
||||
const tokens = new GeneralTokenizer().tokenize(term);
|
||||
const tokens = new GeneralTokenizer().tokenize(originString);
|
||||
|
||||
for (const token of tokens) {
|
||||
const tokens = tokenMap.get(token.term) || [];
|
||||
@@ -314,7 +314,7 @@ export class FullTextInvertedIndex implements InvertedIndex {
|
||||
key: InvertedIndexKey.forString(this.fieldKey, term).buffer(),
|
||||
nid: id,
|
||||
pos: {
|
||||
l: term.length,
|
||||
l: originString.length,
|
||||
i: i,
|
||||
rs: tokens.map(token => [token.start, token.end]),
|
||||
},
|
||||
|
||||
@@ -76,12 +76,12 @@
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@blocksuite/block-std": "0.16.0-canary-202407040721-5bf36c3",
|
||||
"@blocksuite/blocks": "0.16.0-canary-202407040721-5bf36c3",
|
||||
"@blocksuite/global": "0.16.0-canary-202407040721-5bf36c3",
|
||||
"@blocksuite/block-std": "0.16.0-canary-202407050348-4620c21",
|
||||
"@blocksuite/blocks": "0.16.0-canary-202407050348-4620c21",
|
||||
"@blocksuite/global": "0.16.0-canary-202407050348-4620c21",
|
||||
"@blocksuite/icons": "2.1.58",
|
||||
"@blocksuite/presets": "0.16.0-canary-202407040721-5bf36c3",
|
||||
"@blocksuite/store": "0.16.0-canary-202407040721-5bf36c3",
|
||||
"@blocksuite/presets": "0.16.0-canary-202407050348-4620c21",
|
||||
"@blocksuite/store": "0.16.0-canary-202407050348-4620c21",
|
||||
"@storybook/addon-actions": "^7.6.17",
|
||||
"@storybook/addon-essentials": "^7.6.17",
|
||||
"@storybook/addon-interactions": "^7.6.17",
|
||||
|
||||
@@ -19,13 +19,13 @@
|
||||
"@affine/graphql": "workspace:*",
|
||||
"@affine/i18n": "workspace:*",
|
||||
"@affine/templates": "workspace:*",
|
||||
"@blocksuite/block-std": "0.16.0-canary-202407040721-5bf36c3",
|
||||
"@blocksuite/blocks": "0.16.0-canary-202407040721-5bf36c3",
|
||||
"@blocksuite/global": "0.16.0-canary-202407040721-5bf36c3",
|
||||
"@blocksuite/block-std": "0.16.0-canary-202407050348-4620c21",
|
||||
"@blocksuite/blocks": "0.16.0-canary-202407050348-4620c21",
|
||||
"@blocksuite/global": "0.16.0-canary-202407050348-4620c21",
|
||||
"@blocksuite/icons": "2.1.58",
|
||||
"@blocksuite/inline": "0.16.0-canary-202407040721-5bf36c3",
|
||||
"@blocksuite/presets": "0.16.0-canary-202407040721-5bf36c3",
|
||||
"@blocksuite/store": "0.16.0-canary-202407040721-5bf36c3",
|
||||
"@blocksuite/inline": "0.16.0-canary-202407050348-4620c21",
|
||||
"@blocksuite/presets": "0.16.0-canary-202407050348-4620c21",
|
||||
"@blocksuite/store": "0.16.0-canary-202407050348-4620c21",
|
||||
"@dnd-kit/core": "^6.1.0",
|
||||
"@dnd-kit/modifiers": "^7.0.0",
|
||||
"@dnd-kit/sortable": "^8.0.0",
|
||||
|
||||
@@ -13,6 +13,7 @@ export const openQuotaModalAtom = atom(false);
|
||||
export const openStarAFFiNEModalAtom = atom(false);
|
||||
export const openIssueFeedbackModalAtom = atom(false);
|
||||
export const openHistoryTipsModalAtom = atom(false);
|
||||
export const openInfoModalAtom = atom(false);
|
||||
|
||||
export const rightSidebarWidthAtom = atom(320);
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './icons-mapping';
|
||||
export * from './info-modal/info-modal';
|
||||
export * from './page-properties-manager';
|
||||
export * from './table';
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import { cssVar } from '@toeverything/theme';
|
||||
import { globalStyle, style } from '@vanilla-extract/css';
|
||||
|
||||
export const title = style({
|
||||
fontSize: cssVar('fontSm'),
|
||||
fontWeight: '500',
|
||||
color: cssVar('textSecondaryColor'),
|
||||
padding: '6px',
|
||||
});
|
||||
|
||||
export const wrapper = style({
|
||||
width: '100%',
|
||||
borderRadius: 4,
|
||||
color: cssVar('textPrimaryColor'),
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 2,
|
||||
padding: '6px',
|
||||
':hover': {
|
||||
backgroundColor: cssVar('hoverColor'),
|
||||
},
|
||||
});
|
||||
|
||||
globalStyle(`${wrapper} svg`, {
|
||||
color: cssVar('iconSecondary'),
|
||||
fontSize: 16,
|
||||
transform: 'none',
|
||||
});
|
||||
globalStyle(`${wrapper} span`, {
|
||||
fontSize: cssVar('fontSm'),
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
borderBottom: 'none',
|
||||
});
|
||||
@@ -0,0 +1,33 @@
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { AffinePageReference } from '../../reference-link';
|
||||
import { managerContext } from '../common';
|
||||
import * as styles from './back-links-row.css';
|
||||
export const BackLinksRow = ({
|
||||
references,
|
||||
onClick,
|
||||
}: {
|
||||
references: { docId: string; title: string }[];
|
||||
onClick?: () => void;
|
||||
}) => {
|
||||
const manager = useContext(managerContext);
|
||||
const t = useI18n();
|
||||
return (
|
||||
<div>
|
||||
<div className={styles.title}>
|
||||
{t['com.affine.page-properties.backlinks']()} · {references.length}
|
||||
</div>
|
||||
{references.map(link => (
|
||||
<AffinePageReference
|
||||
key={link.docId}
|
||||
pageId={link.docId}
|
||||
wrapper={props => (
|
||||
<div className={styles.wrapper} onClick={onClick} {...props} />
|
||||
)}
|
||||
docCollection={manager.workspace.docCollection}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,39 @@
|
||||
import { cssVar } from '@toeverything/theme';
|
||||
import { style } from '@vanilla-extract/css';
|
||||
|
||||
export const container = style({
|
||||
maxWidth: 480,
|
||||
minWidth: 360,
|
||||
padding: '20px 0',
|
||||
alignSelf: 'start',
|
||||
marginTop: '120px',
|
||||
});
|
||||
|
||||
export const titleContainer = style({
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
flexDirection: 'column',
|
||||
});
|
||||
|
||||
export const titleStyle = style({
|
||||
fontSize: cssVar('fontH6'),
|
||||
fontWeight: '600',
|
||||
});
|
||||
|
||||
export const rowNameContainer = style({
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
gap: 6,
|
||||
padding: 6,
|
||||
width: '160px',
|
||||
});
|
||||
|
||||
export const viewport = style({
|
||||
maxHeight: 'calc(100vh - 220px)',
|
||||
padding: '0 24px',
|
||||
});
|
||||
|
||||
export const scrollBar = style({
|
||||
width: 6,
|
||||
transform: 'translateX(-4px)',
|
||||
});
|
||||
@@ -0,0 +1,156 @@
|
||||
import {
|
||||
Divider,
|
||||
type InlineEditHandle,
|
||||
Modal,
|
||||
Scrollable,
|
||||
} from '@affine/component';
|
||||
import { DocsSearchService } from '@affine/core/modules/docs-search';
|
||||
import type { Doc } from '@blocksuite/store';
|
||||
import {
|
||||
LiveData,
|
||||
useLiveData,
|
||||
useService,
|
||||
type Workspace,
|
||||
} from '@toeverything/infra';
|
||||
import { Suspense, useCallback, useContext, useMemo, useRef } from 'react';
|
||||
|
||||
import { BlocksuiteHeaderTitle } from '../../../blocksuite/block-suite-header/title';
|
||||
import { managerContext } from '../common';
|
||||
import {
|
||||
PagePropertiesAddProperty,
|
||||
PagePropertyRow,
|
||||
SortableProperties,
|
||||
usePagePropertiesManager,
|
||||
} from '../table';
|
||||
import { BackLinksRow } from './back-links-row';
|
||||
import * as styles from './info-modal.css';
|
||||
import { TagsRow } from './tags-row';
|
||||
import { TimeRow } from './time-row';
|
||||
|
||||
export const InfoModal = ({
|
||||
open,
|
||||
onOpenChange,
|
||||
page,
|
||||
workspace,
|
||||
}: {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
page: Doc;
|
||||
workspace: Workspace;
|
||||
}) => {
|
||||
const titleInputHandleRef = useRef<InlineEditHandle>(null);
|
||||
const manager = usePagePropertiesManager(page);
|
||||
const handleClose = useCallback(() => {
|
||||
onOpenChange(false);
|
||||
}, [onOpenChange]);
|
||||
|
||||
const docsSearchService = useService(DocsSearchService);
|
||||
const references = useLiveData(
|
||||
useMemo(
|
||||
() => LiveData.from(docsSearchService.watchRefsFrom(page.id), null),
|
||||
[docsSearchService, page.id]
|
||||
)
|
||||
);
|
||||
|
||||
if (!manager.page || manager.readonly) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
contentOptions={{
|
||||
className: styles.container,
|
||||
'aria-describedby': undefined,
|
||||
}}
|
||||
open={open}
|
||||
onOpenChange={onOpenChange}
|
||||
withoutCloseButton
|
||||
>
|
||||
<Scrollable.Root>
|
||||
<Scrollable.Viewport
|
||||
className={styles.viewport}
|
||||
data-testid="info-modal"
|
||||
>
|
||||
<div className={styles.titleContainer} data-testid="info-modal-title">
|
||||
<BlocksuiteHeaderTitle
|
||||
className={styles.titleStyle}
|
||||
inputHandleRef={titleInputHandleRef}
|
||||
pageId={page.id}
|
||||
docCollection={workspace.docCollection}
|
||||
/>
|
||||
</div>
|
||||
<managerContext.Provider value={manager}>
|
||||
<Suspense>
|
||||
<InfoTable
|
||||
docId={page.id}
|
||||
onClose={handleClose}
|
||||
references={references}
|
||||
readonly={manager.readonly}
|
||||
/>
|
||||
</Suspense>
|
||||
</managerContext.Provider>
|
||||
</Scrollable.Viewport>
|
||||
<Scrollable.Scrollbar className={styles.scrollBar} />
|
||||
</Scrollable.Root>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
const InfoTable = ({
|
||||
onClose,
|
||||
references,
|
||||
docId,
|
||||
readonly,
|
||||
}: {
|
||||
docId: string;
|
||||
onClose: () => void;
|
||||
readonly: boolean;
|
||||
references:
|
||||
| {
|
||||
docId: string;
|
||||
title: string;
|
||||
}[]
|
||||
| null;
|
||||
}) => {
|
||||
const manager = useContext(managerContext);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<TimeRow docId={docId} />
|
||||
<Divider size="thinner" />
|
||||
{references && references.length > 0 ? (
|
||||
<>
|
||||
<BackLinksRow references={references} onClick={onClose} />
|
||||
<Divider size="thinner" />
|
||||
</>
|
||||
) : null}
|
||||
<TagsRow docId={docId} readonly={readonly} />
|
||||
<SortableProperties>
|
||||
{properties =>
|
||||
properties.length ? (
|
||||
<div>
|
||||
{properties
|
||||
.filter(
|
||||
property =>
|
||||
manager.isPropertyRequired(property.id) ||
|
||||
(property.visibility !== 'hide' &&
|
||||
!(
|
||||
property.visibility === 'hide-if-empty' &&
|
||||
!property.value
|
||||
))
|
||||
)
|
||||
.map(property => (
|
||||
<PagePropertyRow
|
||||
key={property.id}
|
||||
property={property}
|
||||
rowNameClassName={styles.rowNameContainer}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
) : null
|
||||
}
|
||||
</SortableProperties>
|
||||
{manager.readonly ? null : <PagePropertiesAddProperty />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,102 @@
|
||||
import { cssVar } from '@toeverything/theme';
|
||||
import { style } from '@vanilla-extract/css';
|
||||
|
||||
export const icon = style({
|
||||
fontSize: 16,
|
||||
color: cssVar('iconSecondary'),
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
});
|
||||
|
||||
export const rowNameContainer = style({
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 6,
|
||||
padding: 6,
|
||||
width: '160px',
|
||||
});
|
||||
|
||||
export const rowName = style({
|
||||
flexGrow: 1,
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
fontSize: cssVar('fontSm'),
|
||||
color: cssVar('textSecondaryColor'),
|
||||
});
|
||||
|
||||
export const time = style({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
padding: '6px 8px',
|
||||
flexGrow: 1,
|
||||
fontSize: cssVar('fontSm'),
|
||||
});
|
||||
|
||||
export const rowCell = style({
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'start',
|
||||
gap: 4,
|
||||
});
|
||||
|
||||
export const container = style({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
marginTop: 20,
|
||||
marginBottom: 4,
|
||||
});
|
||||
|
||||
export const rowValueCell = style({
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'flex-start',
|
||||
position: 'relative',
|
||||
borderRadius: 4,
|
||||
fontSize: cssVar('fontSm'),
|
||||
lineHeight: '22px',
|
||||
userSelect: 'none',
|
||||
':focus-visible': {
|
||||
outline: 'none',
|
||||
},
|
||||
cursor: 'pointer',
|
||||
':hover': {
|
||||
backgroundColor: cssVar('hoverColor'),
|
||||
},
|
||||
padding: '6px 8px',
|
||||
border: `1px solid transparent`,
|
||||
color: cssVar('textPrimaryColor'),
|
||||
':focus': {
|
||||
backgroundColor: cssVar('hoverColor'),
|
||||
},
|
||||
'::placeholder': {
|
||||
color: cssVar('placeholderColor'),
|
||||
},
|
||||
selectors: {
|
||||
'&[data-empty="true"]': {
|
||||
color: cssVar('placeholderColor'),
|
||||
},
|
||||
'&[data-readonly=true]': {
|
||||
pointerEvents: 'none',
|
||||
},
|
||||
},
|
||||
flex: 1,
|
||||
});
|
||||
|
||||
export const tagsMenu = style({
|
||||
padding: 0,
|
||||
transform:
|
||||
'translate(-3.5px, calc(-3.5px + var(--radix-popper-anchor-height) * -1))',
|
||||
width: 'calc(var(--radix-popper-anchor-width) + 16px)',
|
||||
overflow: 'hidden',
|
||||
});
|
||||
|
||||
export const tagsInlineEditor = style({
|
||||
selectors: {
|
||||
'&[data-empty=true]': {
|
||||
color: cssVar('placeholderColor'),
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,58 @@
|
||||
import { Menu } from '@affine/component';
|
||||
import { TagService } from '@affine/core/modules/tag';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { TagsIcon } from '@blocksuite/icons/rc';
|
||||
import { useLiveData, useService } from '@toeverything/infra';
|
||||
import clsx from 'clsx';
|
||||
|
||||
import { InlineTagsList, TagsEditor } from '../tags-inline-editor';
|
||||
import * as styles from './tags-row.css';
|
||||
|
||||
export const TagsRow = ({
|
||||
docId,
|
||||
readonly,
|
||||
}: {
|
||||
docId: string;
|
||||
readonly: boolean;
|
||||
}) => {
|
||||
const t = useI18n();
|
||||
const tagList = useService(TagService).tagList;
|
||||
const tagIds = useLiveData(tagList.tagIdsByPageId$(docId));
|
||||
const empty = !tagIds || tagIds.length === 0;
|
||||
return (
|
||||
<div className={styles.rowCell} data-testid="info-modal-tags-row">
|
||||
<div className={styles.rowNameContainer}>
|
||||
<div className={styles.icon}>
|
||||
<TagsIcon />
|
||||
</div>
|
||||
<div className={styles.rowName}>{t['Tags']()}</div>
|
||||
</div>
|
||||
<Menu
|
||||
contentOptions={{
|
||||
side: 'bottom',
|
||||
align: 'start',
|
||||
sideOffset: 0,
|
||||
avoidCollisions: false,
|
||||
className: styles.tagsMenu,
|
||||
onClick(e) {
|
||||
e.stopPropagation();
|
||||
},
|
||||
}}
|
||||
items={<TagsEditor pageId={docId} readonly={readonly} />}
|
||||
>
|
||||
<div
|
||||
className={clsx(styles.tagsInlineEditor, styles.rowValueCell)}
|
||||
data-empty={empty}
|
||||
data-readonly={readonly}
|
||||
data-testid="info-modal-tags-value"
|
||||
>
|
||||
{empty ? (
|
||||
t['com.affine.page-properties.property-value-placeholder']()
|
||||
) : (
|
||||
<InlineTagsList pageId={docId} readonly />
|
||||
)}
|
||||
</div>
|
||||
</Menu>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,51 @@
|
||||
import { cssVar } from '@toeverything/theme';
|
||||
import { style } from '@vanilla-extract/css';
|
||||
|
||||
export const icon = style({
|
||||
fontSize: 16,
|
||||
color: cssVar('iconSecondary'),
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
});
|
||||
|
||||
export const rowNameContainer = style({
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
|
||||
gap: 6,
|
||||
padding: 6,
|
||||
width: '160px',
|
||||
});
|
||||
|
||||
export const rowName = style({
|
||||
flexGrow: 1,
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
fontSize: cssVar('fontSm'),
|
||||
color: cssVar('textSecondaryColor'),
|
||||
});
|
||||
|
||||
export const time = style({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
padding: '6px 8px',
|
||||
flexGrow: 1,
|
||||
fontSize: cssVar('fontSm'),
|
||||
});
|
||||
|
||||
export const rowCell = style({
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 4,
|
||||
});
|
||||
|
||||
export const container = style({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
marginTop: 20,
|
||||
marginBottom: 4,
|
||||
});
|
||||
@@ -0,0 +1,92 @@
|
||||
import { i18nTime, useI18n } from '@affine/i18n';
|
||||
import { DateTimeIcon, HistoryIcon } from '@blocksuite/icons/rc';
|
||||
import { useLiveData, useService, WorkspaceService } from '@toeverything/infra';
|
||||
import type { ConfigType } from 'dayjs';
|
||||
import { useDebouncedValue } from 'foxact/use-debounced-value';
|
||||
import { type ReactNode, useContext, useMemo } from 'react';
|
||||
|
||||
import { managerContext } from '../common';
|
||||
import * as styles from './time-row.css';
|
||||
|
||||
const RowComponent = ({
|
||||
name,
|
||||
icon,
|
||||
time,
|
||||
}: {
|
||||
name: string;
|
||||
icon: ReactNode;
|
||||
time?: string | null;
|
||||
}) => {
|
||||
return (
|
||||
<div className={styles.rowCell}>
|
||||
<div className={styles.rowNameContainer}>
|
||||
<div className={styles.icon}>{icon}</div>
|
||||
<span className={styles.rowName}>{name}</span>
|
||||
</div>
|
||||
<div className={styles.time}>{time ? time : 'unknown'}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const TimeRow = ({ docId }: { docId: string }) => {
|
||||
const t = useI18n();
|
||||
const manager = useContext(managerContext);
|
||||
const workspaceService = useService(WorkspaceService);
|
||||
const { syncing, retrying, serverClock } = useLiveData(
|
||||
workspaceService.workspace.engine.doc.docState$(docId)
|
||||
);
|
||||
|
||||
const timestampElement = useMemo(() => {
|
||||
const formatI18nTime = (time: ConfigType) =>
|
||||
i18nTime(time, {
|
||||
relative: {
|
||||
max: [1, 'day'],
|
||||
accuracy: 'minute',
|
||||
},
|
||||
absolute: {
|
||||
accuracy: 'day',
|
||||
},
|
||||
});
|
||||
const localizedCreateTime = manager.createDate
|
||||
? formatI18nTime(manager.createDate)
|
||||
: null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<RowComponent
|
||||
icon={<DateTimeIcon />}
|
||||
name={t['Created']()}
|
||||
time={
|
||||
manager.createDate
|
||||
? formatI18nTime(manager.createDate)
|
||||
: localizedCreateTime
|
||||
}
|
||||
/>
|
||||
{serverClock ? (
|
||||
<RowComponent
|
||||
icon={<HistoryIcon />}
|
||||
name={t[!syncing && !retrying ? 'Updated' : 'com.affine.syncing']()}
|
||||
time={!syncing && !retrying ? formatI18nTime(serverClock) : null}
|
||||
/>
|
||||
) : manager.updatedDate ? (
|
||||
<RowComponent
|
||||
icon={<HistoryIcon />}
|
||||
name={t['Updated']()}
|
||||
time={formatI18nTime(manager.updatedDate)}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
}, [
|
||||
manager.createDate,
|
||||
manager.updatedDate,
|
||||
retrying,
|
||||
serverClock,
|
||||
syncing,
|
||||
t,
|
||||
]);
|
||||
|
||||
const dTimestampElement = useDebouncedValue(timestampElement, 500);
|
||||
|
||||
return <div className={styles.container}>{dTimestampElement}</div>;
|
||||
};
|
||||
@@ -129,6 +129,16 @@ export const addPropertyButton = style({
|
||||
color: cssVar('textPrimaryColor'),
|
||||
backgroundColor: cssVar('hoverColor'),
|
||||
},
|
||||
gap: 2,
|
||||
fontWeight: 400,
|
||||
});
|
||||
|
||||
globalStyle(`${addPropertyButton} svg`, {
|
||||
fontSize: 16,
|
||||
color: cssVar('iconSecondary'),
|
||||
});
|
||||
globalStyle(`${addPropertyButton}:hover svg`, {
|
||||
color: cssVar('iconColor'),
|
||||
});
|
||||
|
||||
export const collapsedIcon = style({
|
||||
@@ -262,7 +272,7 @@ export const propertyRowIconContainer = style({
|
||||
justifyContent: 'center',
|
||||
borderRadius: '2px',
|
||||
fontSize: 16,
|
||||
color: 'inherit',
|
||||
color: cssVar('iconSecondary'),
|
||||
});
|
||||
|
||||
export const propertyRowNameContainer = style({
|
||||
|
||||
@@ -105,7 +105,7 @@ interface SortablePropertiesProps {
|
||||
children: (properties: PageInfoCustomProperty[]) => React.ReactNode;
|
||||
}
|
||||
|
||||
const SortableProperties = ({ children }: SortablePropertiesProps) => {
|
||||
export const SortableProperties = ({ children }: SortablePropertiesProps) => {
|
||||
const manager = useContext(managerContext);
|
||||
const properties = useMemo(() => manager.sorter.getOrderedItems(), [manager]);
|
||||
const editingItem = useAtomValue(editingPropertyAtom);
|
||||
@@ -735,9 +735,13 @@ export const PagePropertiesTableHeader = ({
|
||||
interface PagePropertyRowProps {
|
||||
property: PageInfoCustomProperty;
|
||||
style?: React.CSSProperties;
|
||||
rowNameClassName?: string;
|
||||
}
|
||||
|
||||
const PagePropertyRow = ({ property }: PagePropertyRowProps) => {
|
||||
export const PagePropertyRow = ({
|
||||
property,
|
||||
rowNameClassName,
|
||||
}: PagePropertyRowProps) => {
|
||||
const manager = useContext(managerContext);
|
||||
const meta = manager.getCustomPropertyMeta(property.id);
|
||||
|
||||
@@ -772,7 +776,10 @@ const PagePropertyRow = ({ property }: PagePropertyRowProps) => {
|
||||
{...attributes}
|
||||
{...listeners}
|
||||
data-testid="page-property-row-name"
|
||||
className={styles.sortablePropertyRowNameCell}
|
||||
className={clsx(
|
||||
styles.sortablePropertyRowNameCell,
|
||||
rowNameClassName
|
||||
)}
|
||||
onClick={handleEditMeta}
|
||||
>
|
||||
<div className={styles.propertyRowNameContainer}>
|
||||
@@ -790,7 +797,11 @@ const PagePropertyRow = ({ property }: PagePropertyRowProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
const PageTagsRow = () => {
|
||||
export const PageTagsRow = ({
|
||||
rowNameClassName,
|
||||
}: {
|
||||
rowNameClassName?: string;
|
||||
}) => {
|
||||
const t = useI18n();
|
||||
return (
|
||||
<div
|
||||
@@ -799,7 +810,7 @@ const PageTagsRow = () => {
|
||||
data-property="tags"
|
||||
>
|
||||
<div
|
||||
className={styles.propertyRowNameCell}
|
||||
className={clsx(styles.propertyRowNameCell, rowNameClassName)}
|
||||
data-testid="page-property-row-name"
|
||||
>
|
||||
<div className={styles.propertyRowNameContainer}>
|
||||
@@ -1074,7 +1085,7 @@ const PagePropertiesTableInner = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const usePagePropertiesManager = (page: Doc) => {
|
||||
export const usePagePropertiesManager = (page: Doc) => {
|
||||
// the workspace properties adapter adapter is reactive,
|
||||
// which means it's reference will change when any of the properties change
|
||||
// also it will trigger a re-render of the component
|
||||
|
||||
@@ -30,7 +30,7 @@ interface InlineTagsListProps
|
||||
onRemove?: () => void;
|
||||
}
|
||||
|
||||
const InlineTagsList = ({
|
||||
export const InlineTagsList = ({
|
||||
pageId,
|
||||
readonly,
|
||||
children,
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import { IconButton, Tooltip } from '@affine/component';
|
||||
import { openInfoModalAtom } from '@affine/core/atoms';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { InformationIcon } from '@blocksuite/icons/rc';
|
||||
import { useSetAtom } from 'jotai';
|
||||
|
||||
export const InfoButton = () => {
|
||||
const setOpenInfoModal = useSetAtom(openInfoModalAtom);
|
||||
const t = useI18n();
|
||||
const onOpenInfoModal = () => {
|
||||
setOpenInfoModal(true);
|
||||
};
|
||||
return (
|
||||
<Tooltip content={t['com.affine.page-properties.page-info.view']()}>
|
||||
<IconButton
|
||||
data-testid="header-info-button"
|
||||
onClick={onOpenInfoModal}
|
||||
icon={<InformationIcon />}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
@@ -6,7 +6,10 @@ import {
|
||||
MenuSeparator,
|
||||
MenuSub,
|
||||
} from '@affine/component/ui/menu';
|
||||
import { openHistoryTipsModalAtom } from '@affine/core/atoms';
|
||||
import {
|
||||
openHistoryTipsModalAtom,
|
||||
openInfoModalAtom,
|
||||
} from '@affine/core/atoms';
|
||||
import { PageHistoryModal } from '@affine/core/components/affine/page-history-modal';
|
||||
import { ShareMenuContent } from '@affine/core/components/affine/share-page-modal/share-menu';
|
||||
import { Export, MoveToTrash } from '@affine/core/components/page-list';
|
||||
@@ -27,6 +30,7 @@ import {
|
||||
FavoriteIcon,
|
||||
HistoryIcon,
|
||||
ImportIcon,
|
||||
InformationIcon,
|
||||
PageIcon,
|
||||
ShareIcon,
|
||||
} from '@blocksuite/icons/rc';
|
||||
@@ -83,6 +87,11 @@ export const PageHeaderMenuButton = ({
|
||||
return setOpenHistoryTipsModal(true);
|
||||
}, [setOpenHistoryTipsModal, workspace.flavour]);
|
||||
|
||||
const setOpenInfoModal = useSetAtom(openInfoModalAtom);
|
||||
const openInfoModal = () => {
|
||||
setOpenInfoModal(true);
|
||||
};
|
||||
|
||||
const handleOpenTrashModal = useCallback(() => {
|
||||
setTrashModal({
|
||||
open: true,
|
||||
@@ -236,6 +245,35 @@ export const PageHeaderMenuButton = ({
|
||||
{t['com.affine.header.option.add-tag']()}
|
||||
</MenuItem> */}
|
||||
<MenuSeparator />
|
||||
{runtimeConfig.enableInfoModal ? (
|
||||
<MenuItem
|
||||
preFix={
|
||||
<MenuIcon>
|
||||
<InformationIcon />
|
||||
</MenuIcon>
|
||||
}
|
||||
data-testid="editor-option-menu-info"
|
||||
onSelect={openInfoModal}
|
||||
style={menuItemStyle}
|
||||
>
|
||||
{t['com.affine.page-properties.page-info.view']()}
|
||||
</MenuItem>
|
||||
) : null}
|
||||
{runtimeConfig.enablePageHistory ? (
|
||||
<MenuItem
|
||||
preFix={
|
||||
<MenuIcon>
|
||||
<HistoryIcon />
|
||||
</MenuIcon>
|
||||
}
|
||||
data-testid="editor-option-menu-history"
|
||||
onSelect={openHistoryModal}
|
||||
style={menuItemStyle}
|
||||
>
|
||||
{t['com.affine.history.view-history-version']()}
|
||||
</MenuItem>
|
||||
) : null}
|
||||
<MenuSeparator />
|
||||
{!isJournal && (
|
||||
<MenuItem
|
||||
preFix={
|
||||
@@ -264,21 +302,6 @@ export const PageHeaderMenuButton = ({
|
||||
</MenuItem>
|
||||
<Export exportHandler={exportHandler} pageMode={currentMode} />
|
||||
|
||||
{runtimeConfig.enablePageHistory ? (
|
||||
<MenuItem
|
||||
preFix={
|
||||
<MenuIcon>
|
||||
<HistoryIcon />
|
||||
</MenuIcon>
|
||||
}
|
||||
data-testid="editor-option-menu-history"
|
||||
onSelect={openHistoryModal}
|
||||
style={menuItemStyle}
|
||||
>
|
||||
{t['com.affine.history.view-history-version']()}
|
||||
</MenuItem>
|
||||
) : null}
|
||||
|
||||
<MenuSeparator />
|
||||
<MoveToTrash
|
||||
data-testid="editor-option-menu-delete"
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
useDocMetaHelper,
|
||||
} from '@affine/core/hooks/use-block-suite-page-meta';
|
||||
import type { DocCollection } from '@affine/core/shared';
|
||||
import clsx from 'clsx';
|
||||
import type { HTMLAttributes } from 'react';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
@@ -16,6 +17,7 @@ export interface BlockSuiteHeaderTitleProps {
|
||||
/** if set, title cannot be edited */
|
||||
isPublic?: boolean;
|
||||
inputHandleRef?: InlineEditProps['handleRef'];
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const inputAttrs = {
|
||||
@@ -39,7 +41,7 @@ export const BlocksuiteHeaderTitle = (props: BlockSuiteHeaderTitleProps) => {
|
||||
|
||||
return (
|
||||
<InlineEdit
|
||||
className={styles.title}
|
||||
className={clsx(styles.title, props.className)}
|
||||
autoSelect
|
||||
value={title}
|
||||
onChange={onChange}
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
FavoriteIcon,
|
||||
FilterIcon,
|
||||
FilterMinusIcon,
|
||||
InformationIcon,
|
||||
MoreVerticalIcon,
|
||||
PlusIcon,
|
||||
ResetIcon,
|
||||
@@ -36,6 +37,7 @@ import { useCallback, useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import type { CollectionService } from '../../modules/collection';
|
||||
import { InfoModal } from '../affine/page-properties';
|
||||
import { usePageHelper } from '../blocksuite/block-suite-page-list/utils';
|
||||
import { FavoriteTag } from './components/favorite-tag';
|
||||
import * as styles from './list.css';
|
||||
@@ -65,6 +67,12 @@ export const PageOperationCell = ({
|
||||
const favourite = useLiveData(favAdapter.isFavorite$(page.id, 'doc'));
|
||||
const workbench = useService(WorkbenchService).workbench;
|
||||
const { duplicate } = useBlockSuiteMetaHelper(currentWorkspace.docCollection);
|
||||
const blocksuiteDoc = currentWorkspace.docCollection.getDoc(page.id);
|
||||
|
||||
const [openInfoModal, setOpenInfoModal] = useState(false);
|
||||
const onOpenInfoModal = () => {
|
||||
setOpenInfoModal(true);
|
||||
};
|
||||
|
||||
const onDisablePublicSharing = useCallback(() => {
|
||||
toast('Successfully disabled', {
|
||||
@@ -144,6 +152,18 @@ export const PageOperationCell = ({
|
||||
? t['com.affine.favoritePageOperation.remove']()
|
||||
: t['com.affine.favoritePageOperation.add']()}
|
||||
</MenuItem>
|
||||
{runtimeConfig.enableInfoModal ? (
|
||||
<MenuItem
|
||||
onClick={onOpenInfoModal}
|
||||
preFix={
|
||||
<MenuIcon>
|
||||
<InformationIcon />
|
||||
</MenuIcon>
|
||||
}
|
||||
>
|
||||
{t['com.affine.page-properties.page-info.view']()}
|
||||
</MenuItem>
|
||||
) : null}
|
||||
|
||||
{environment.isDesktop && appSettings.enableMultiView ? (
|
||||
<MenuItem
|
||||
@@ -215,6 +235,14 @@ export const PageOperationCell = ({
|
||||
</IconButton>
|
||||
</Menu>
|
||||
</ColWrapper>
|
||||
{blocksuiteDoc ? (
|
||||
<InfoModal
|
||||
open={openInfoModal}
|
||||
onOpenChange={setOpenInfoModal}
|
||||
page={blocksuiteDoc}
|
||||
workspace={currentWorkspace}
|
||||
/>
|
||||
) : null}
|
||||
<DisablePublicSharing.DisablePublicSharingModal
|
||||
onConfirm={onDisablePublicSharing}
|
||||
open={openDisableShared}
|
||||
|
||||
@@ -8,30 +8,9 @@ import {
|
||||
aiIslandAnimationBg,
|
||||
aiIslandBtn,
|
||||
aiIslandWrapper,
|
||||
borderAngle1,
|
||||
borderAngle2,
|
||||
borderAngle3,
|
||||
gradient,
|
||||
} from './styles.css';
|
||||
|
||||
if (
|
||||
typeof window !== 'undefined' &&
|
||||
window.CSS &&
|
||||
window.CSS.registerProperty
|
||||
) {
|
||||
const getName = (nameWithVar: string) => nameWithVar.slice(4, -1);
|
||||
const registerAngle = (varName: string, initialValue: number) => {
|
||||
window.CSS.registerProperty({
|
||||
name: getName(varName),
|
||||
syntax: '<angle>',
|
||||
inherits: false,
|
||||
initialValue: `${initialValue}deg`,
|
||||
});
|
||||
};
|
||||
registerAngle(borderAngle1, 0);
|
||||
registerAngle(borderAngle2, 90);
|
||||
registerAngle(borderAngle3, 180);
|
||||
}
|
||||
|
||||
export const AIIsland = () => {
|
||||
// to make sure ai island is hidden first and animate in
|
||||
const [hide, setHide] = useState(true);
|
||||
@@ -52,7 +31,13 @@ export const AIIsland = () => {
|
||||
data-hide={hide}
|
||||
data-animation={!aiChatHasEverOpened}
|
||||
>
|
||||
<div className={aiIslandAnimationBg} />
|
||||
{aiChatHasEverOpened ? null : (
|
||||
<div className={aiIslandAnimationBg}>
|
||||
<div className={gradient} />
|
||||
<div className={gradient} />
|
||||
<div className={gradient} />
|
||||
</div>
|
||||
)}
|
||||
<button
|
||||
className={aiIslandBtn}
|
||||
data-testid="ai-island"
|
||||
|
||||
@@ -51,14 +51,8 @@ const brightGreen = createVar('bright-green');
|
||||
const brightRed = createVar('bright-red');
|
||||
const borderWidth = createVar('border-width');
|
||||
|
||||
const rotateBg1 = keyframes({
|
||||
to: { [borderAngle1.slice(4, -1)]: '360deg' },
|
||||
});
|
||||
const rotateBg2 = keyframes({
|
||||
to: { [borderAngle2.slice(4, -1)]: '450deg' },
|
||||
});
|
||||
const rotateBg3 = keyframes({
|
||||
to: { [borderAngle3.slice(4, -1)]: '540deg' },
|
||||
const rotateBg = keyframes({
|
||||
to: { transform: 'rotate(360deg)' },
|
||||
});
|
||||
|
||||
export const aiIslandAnimationBg = style({
|
||||
@@ -68,6 +62,7 @@ export const aiIslandAnimationBg = style({
|
||||
left: 0,
|
||||
position: 'absolute',
|
||||
borderRadius: '50%',
|
||||
overflow: 'hidden',
|
||||
|
||||
vars: {
|
||||
[borderAngle1]: '0deg',
|
||||
@@ -79,21 +74,6 @@ export const aiIslandAnimationBg = style({
|
||||
[borderWidth]: '1.5px',
|
||||
},
|
||||
backgroundColor: 'transparent',
|
||||
backgroundImage: `conic-gradient(from ${borderAngle1} at 50% 50%,
|
||||
transparent,
|
||||
${brightBlue} 10%,
|
||||
transparent 30%,
|
||||
transparent),
|
||||
conic-gradient(from ${borderAngle2} at 50% 50%,
|
||||
transparent,
|
||||
${brightGreen} 10%,
|
||||
transparent 60%,
|
||||
transparent),
|
||||
conic-gradient(from ${borderAngle3} at 50% 50%,
|
||||
transparent,
|
||||
${brightRed} 10%,
|
||||
transparent 50%,
|
||||
transparent)`,
|
||||
|
||||
selectors: {
|
||||
[`${aiIslandWrapper}[data-animation="true"] &`]: {
|
||||
@@ -101,7 +81,44 @@ export const aiIslandAnimationBg = style({
|
||||
height: `calc(100% + 2 * ${borderWidth})`,
|
||||
top: `calc(-1 * ${borderWidth})`,
|
||||
left: `calc(-1 * ${borderWidth})`,
|
||||
animation: `${rotateBg1} 3s linear infinite, ${rotateBg2} 8s linear infinite, ${rotateBg3} 13s linear infinite`,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const gradient = style({
|
||||
position: 'absolute',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
borderRadius: 'inherit',
|
||||
animationName: rotateBg,
|
||||
animationIterationCount: 'infinite',
|
||||
animationTimingFunction: 'linear',
|
||||
pointerEvents: 'none',
|
||||
willChange: 'transform',
|
||||
selectors: {
|
||||
[`&:nth-of-type(1)`]: {
|
||||
animationDuration: '3s',
|
||||
backgroundImage: `conic-gradient(from ${borderAngle1} at 50% 50%,
|
||||
transparent, ${brightBlue} 10%,
|
||||
transparent 30%,
|
||||
transparent
|
||||
)`,
|
||||
},
|
||||
[`&:nth-of-type(2)`]: {
|
||||
animationDuration: '8s',
|
||||
backgroundImage: `conic-gradient(from ${borderAngle2} at 50% 50%,
|
||||
transparent, ${brightGreen} 10%,
|
||||
transparent 60%,
|
||||
transparent
|
||||
)`,
|
||||
},
|
||||
[`&:nth-of-type(3)`]: {
|
||||
animationDuration: '13s',
|
||||
backgroundImage: `conic-gradient(from ${borderAngle3} at 50% 50%,
|
||||
transparent, ${brightRed} 10%,
|
||||
transparent 50%,
|
||||
transparent
|
||||
)`,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
EditIcon,
|
||||
FavoriteIcon,
|
||||
FilterMinusIcon,
|
||||
InformationIcon,
|
||||
LinkedPageIcon,
|
||||
SplitViewIcon,
|
||||
} from '@blocksuite/icons/rc';
|
||||
@@ -24,6 +25,7 @@ type OperationItemsProps = {
|
||||
onRemoveFromFavourites?: () => void;
|
||||
onDelete: () => void;
|
||||
onOpenInSplitView: () => void;
|
||||
onOpenInfoModal: () => void;
|
||||
};
|
||||
|
||||
export const OperationItems = ({
|
||||
@@ -36,6 +38,7 @@ export const OperationItems = ({
|
||||
onRemoveFromFavourites,
|
||||
onDelete,
|
||||
onOpenInSplitView,
|
||||
onOpenInfoModal,
|
||||
}: OperationItemsProps) => {
|
||||
const { appSettings } = useAppSettingHelper();
|
||||
const t = useI18n();
|
||||
@@ -63,6 +66,19 @@ export const OperationItems = ({
|
||||
name: t['Rename'](),
|
||||
click: onRename,
|
||||
},
|
||||
...(runtimeConfig.enableInfoModal
|
||||
? [
|
||||
{
|
||||
icon: (
|
||||
<MenuIcon>
|
||||
<InformationIcon />
|
||||
</MenuIcon>
|
||||
),
|
||||
name: t['com.affine.page-properties.page-info.view'](),
|
||||
click: onOpenInfoModal,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
icon: (
|
||||
<MenuIcon>
|
||||
@@ -123,7 +139,7 @@ export const OperationItems = ({
|
||||
<DeleteIcon />
|
||||
</MenuIcon>
|
||||
),
|
||||
name: t['com.affine.trashOperation.delete'](),
|
||||
name: t['com.affine.moveToTrash.title'](),
|
||||
click: onDelete,
|
||||
type: 'danger',
|
||||
},
|
||||
@@ -139,6 +155,7 @@ export const OperationItems = ({
|
||||
onRemoveFromAllowList,
|
||||
appSettings.enableMultiView,
|
||||
onOpenInSplitView,
|
||||
onOpenInfoModal,
|
||||
onDelete,
|
||||
]
|
||||
);
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { toast } from '@affine/component';
|
||||
import { IconButton } from '@affine/component/ui/button';
|
||||
import { Menu } from '@affine/component/ui/menu';
|
||||
import { InfoModal } from '@affine/core/components/affine/page-properties';
|
||||
import { FavoriteItemsAdapter } from '@affine/core/modules/properties';
|
||||
import { WorkbenchService } from '@affine/core/modules/workbench';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { MoreHorizontalIcon } from '@blocksuite/icons/rc';
|
||||
import { useService, useServices, WorkspaceService } from '@toeverything/infra';
|
||||
import { useCallback } from 'react';
|
||||
import { useCallback, useState } from 'react';
|
||||
|
||||
import { useTrashModalHelper } from '../../../../hooks/affine/use-trash-modal-helper';
|
||||
import { usePageHelper } from '../../../blocksuite/block-suite-page-list/utils';
|
||||
@@ -33,9 +34,12 @@ export const OperationMenuButton = ({ ...props }: OperationMenuButtonProps) => {
|
||||
isReferencePage,
|
||||
} = props;
|
||||
const t = useI18n();
|
||||
const [openInfoModal, setOpenInfoModal] = useState(false);
|
||||
|
||||
const { workspaceService } = useServices({
|
||||
WorkspaceService,
|
||||
});
|
||||
const page = workspaceService.workspace.docCollection.getDoc(pageId);
|
||||
const { createLinkedPage } = usePageHelper(
|
||||
workspaceService.workspace.docCollection
|
||||
);
|
||||
@@ -76,30 +80,45 @@ export const OperationMenuButton = ({ ...props }: OperationMenuButtonProps) => {
|
||||
workbench.openDoc(pageId, { at: 'tail' });
|
||||
}, [pageId, workbench]);
|
||||
|
||||
const handleOpenInfoModal = useCallback(() => {
|
||||
setOpenInfoModal(true);
|
||||
}, [setOpenInfoModal]);
|
||||
|
||||
return (
|
||||
<Menu
|
||||
items={
|
||||
<OperationItems
|
||||
onAddLinkedPage={handleAddLinkedPage}
|
||||
onDelete={handleDelete}
|
||||
onRemoveFromAllowList={handleRemoveFromAllowList}
|
||||
onRemoveFromFavourites={handleRemoveFromFavourites}
|
||||
onRename={handleRename}
|
||||
onOpenInSplitView={handleOpenInSplitView}
|
||||
inAllowList={inAllowList}
|
||||
inFavorites={inFavorites}
|
||||
isReferencePage={isReferencePage}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<IconButton
|
||||
size="small"
|
||||
type="plain"
|
||||
data-testid="left-sidebar-page-operation-button"
|
||||
style={{ marginLeft: 4 }}
|
||||
<>
|
||||
<Menu
|
||||
items={
|
||||
<OperationItems
|
||||
onAddLinkedPage={handleAddLinkedPage}
|
||||
onDelete={handleDelete}
|
||||
onRemoveFromAllowList={handleRemoveFromAllowList}
|
||||
onRemoveFromFavourites={handleRemoveFromFavourites}
|
||||
onRename={handleRename}
|
||||
onOpenInSplitView={handleOpenInSplitView}
|
||||
onOpenInfoModal={handleOpenInfoModal}
|
||||
inAllowList={inAllowList}
|
||||
inFavorites={inFavorites}
|
||||
isReferencePage={isReferencePage}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<MoreHorizontalIcon />
|
||||
</IconButton>
|
||||
</Menu>
|
||||
<IconButton
|
||||
size="small"
|
||||
type="plain"
|
||||
data-testid="left-sidebar-page-operation-button"
|
||||
style={{ marginLeft: 4 }}
|
||||
>
|
||||
<MoreHorizontalIcon />
|
||||
</IconButton>
|
||||
</Menu>
|
||||
{page ? (
|
||||
<InfoModal
|
||||
open={openInfoModal}
|
||||
onOpenChange={setOpenInfoModal}
|
||||
page={page}
|
||||
workspace={workspaceService.workspace}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { toast } from '@affine/component';
|
||||
import { openInfoModalAtom } from '@affine/core/atoms';
|
||||
import {
|
||||
PreconditionStrategy,
|
||||
registerAffineCommand,
|
||||
@@ -36,6 +37,7 @@ export function useRegisterBlocksuiteEditorCommands() {
|
||||
const trash = useLiveData(doc.trash$);
|
||||
|
||||
const setPageHistoryModalState = useSetAtom(pageHistoryModalAtom);
|
||||
const setInfoModalState = useSetAtom(openInfoModalAtom);
|
||||
|
||||
const openHistoryModal = useCallback(() => {
|
||||
setPageHistoryModalState(() => ({
|
||||
@@ -44,8 +46,11 @@ export function useRegisterBlocksuiteEditorCommands() {
|
||||
}));
|
||||
}, [docId, setPageHistoryModalState]);
|
||||
|
||||
const { restoreFromTrash, duplicate } =
|
||||
useBlockSuiteMetaHelper(docCollection);
|
||||
const openInfoModal = useCallback(() => {
|
||||
setInfoModalState(true);
|
||||
}, [setInfoModalState]);
|
||||
|
||||
const { duplicate } = useBlockSuiteMetaHelper(docCollection);
|
||||
const exportHandler = useExportPage(doc.blockSuiteDoc);
|
||||
const { setTrashModal } = useTrashModalHelper(docCollection);
|
||||
const onClickDelete = useCallback(
|
||||
@@ -89,6 +94,22 @@ export function useRegisterBlocksuiteEditorCommands() {
|
||||
// })
|
||||
// );
|
||||
|
||||
unsubs.push(
|
||||
registerAffineCommand({
|
||||
id: `editor:${mode}-view-info`,
|
||||
preconditionStrategy: () =>
|
||||
PreconditionStrategy.InPaperOrEdgeless &&
|
||||
!trash &&
|
||||
runtimeConfig.enableInfoModal,
|
||||
category: `editor:${mode}`,
|
||||
icon: mode === 'page' ? <PageIcon /> : <EdgelessIcon />,
|
||||
label: t['com.affine.page-properties.page-info.view'](),
|
||||
run() {
|
||||
openInfoModal();
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
unsubs.push(
|
||||
registerAffineCommand({
|
||||
id: `editor:${mode}-${favorite ? 'remove-from' : 'add-to'}-favourites`,
|
||||
@@ -270,7 +291,6 @@ export function useRegisterBlocksuiteEditorCommands() {
|
||||
mode,
|
||||
onClickDelete,
|
||||
exportHandler,
|
||||
restoreFromTrash,
|
||||
t,
|
||||
trash,
|
||||
isCloudWorkspace,
|
||||
@@ -280,5 +300,6 @@ export function useRegisterBlocksuiteEditorCommands() {
|
||||
docId,
|
||||
doc,
|
||||
telemetry,
|
||||
openInfoModal,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -36,6 +36,8 @@ export class CollectionsQuickSearchSession
|
||||
keys: ['name'],
|
||||
includeMatches: true,
|
||||
includeScore: true,
|
||||
ignoreLocation: true,
|
||||
threshold: 0.0,
|
||||
});
|
||||
|
||||
const result = fuse.search(query);
|
||||
|
||||
@@ -151,7 +151,8 @@ export class CommandsQuickSearchSession
|
||||
keys: [{ name: 'label.title', weight: 2 }, 'label.subTitle'],
|
||||
includeMatches: true,
|
||||
includeScore: true,
|
||||
threshold: 0.4,
|
||||
ignoreLocation: true,
|
||||
threshold: 0.0,
|
||||
});
|
||||
|
||||
const result = query
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { GlobalState } from '@toeverything/infra';
|
||||
import { Entity, LiveData } from '@toeverything/infra';
|
||||
import { combineLatest } from 'rxjs';
|
||||
|
||||
import type { SidebarTabName } from '../../multi-tab-sidebar';
|
||||
import { RightSidebarView } from './right-sidebar-view';
|
||||
@@ -14,11 +15,13 @@ export class RightSidebar extends Entity {
|
||||
constructor(private readonly globalState: GlobalState) {
|
||||
super();
|
||||
|
||||
const sub = this.activeTabName$.subscribe(name => {
|
||||
if (name === 'chat') {
|
||||
this.globalState.set(RIGHT_SIDEBAR_AI_HAS_EVER_OPENED_KEY, true);
|
||||
const sub = combineLatest([this.activeTabName$, this.isOpen$]).subscribe(
|
||||
([name, open]) => {
|
||||
if (name === 'chat' && open) {
|
||||
this.globalState.set(RIGHT_SIDEBAR_AI_HAS_EVER_OPENED_KEY, true);
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
this._disposables.push(() => sub.unsubscribe());
|
||||
}
|
||||
|
||||
|
||||
@@ -18,3 +18,9 @@ export const journalWeekPicker = style({
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
});
|
||||
|
||||
export const iconButtonContainer = style({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 10,
|
||||
});
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { Divider, type InlineEditHandle } from '@affine/component';
|
||||
import { openInfoModalAtom } from '@affine/core/atoms';
|
||||
import { InfoModal } from '@affine/core/components/affine/page-properties';
|
||||
import { FavoriteButton } from '@affine/core/components/blocksuite/block-suite-header/favorite';
|
||||
import { InfoButton } from '@affine/core/components/blocksuite/block-suite-header/info';
|
||||
import { JournalWeekDatePicker } from '@affine/core/components/blocksuite/block-suite-header/journal/date-picker';
|
||||
import { JournalTodayButton } from '@affine/core/components/blocksuite/block-suite-header/journal/today-button';
|
||||
import { PageHeaderMenuButton } from '@affine/core/components/blocksuite/block-suite-header/menu';
|
||||
@@ -9,7 +12,7 @@ import { useRegisterCopyLinkCommands } from '@affine/core/hooks/affine/use-regis
|
||||
import { useJournalInfoHelper } from '@affine/core/hooks/use-journal';
|
||||
import type { Doc } from '@blocksuite/store';
|
||||
import { type Workspace } from '@toeverything/infra';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { useAtom, useAtomValue } from 'jotai';
|
||||
import { useCallback, useRef } from 'react';
|
||||
|
||||
import { SharePageButton } from '../../../components/affine/share-page-modal';
|
||||
@@ -90,8 +93,16 @@ export function NormalPageHeader({ page, workspace }: PageHeaderProps) {
|
||||
pageId={page?.id}
|
||||
docCollection={workspace.docCollection}
|
||||
/>
|
||||
{hideCollect ? null : <FavoriteButton pageId={page?.id} />}
|
||||
<PageHeaderMenuButton rename={onRename} page={page} />
|
||||
<div className={styles.iconButtonContainer}>
|
||||
{hideCollect ? null : (
|
||||
<>
|
||||
<FavoriteButton pageId={page?.id} />
|
||||
{runtimeConfig.enableInfoModal ? <InfoButton /> : null}
|
||||
</>
|
||||
)}
|
||||
<PageHeaderMenuButton rename={onRename} page={page} />
|
||||
</div>
|
||||
|
||||
<div className={styles.spacer} />
|
||||
|
||||
{!hidePresent ? <DetailPageHeaderPresentButton /> : null}
|
||||
@@ -111,15 +122,26 @@ export function DetailPageHeader(props: PageHeaderProps) {
|
||||
const { page, workspace } = props;
|
||||
const { isJournal } = useJournalInfoHelper(page.collection, page.id);
|
||||
const isInTrash = page.meta?.trash;
|
||||
const [openInfoModal, setOpenInfoModal] = useAtom(openInfoModalAtom);
|
||||
|
||||
useRegisterCopyLinkCommands({
|
||||
workspaceMeta: workspace.meta,
|
||||
docId: page.id,
|
||||
});
|
||||
|
||||
return isJournal && !isInTrash ? (
|
||||
<JournalPageHeader {...props} />
|
||||
) : (
|
||||
<NormalPageHeader {...props} />
|
||||
return (
|
||||
<>
|
||||
{isJournal && !isInTrash ? (
|
||||
<JournalPageHeader {...props} />
|
||||
) : (
|
||||
<NormalPageHeader {...props} />
|
||||
)}
|
||||
<InfoModal
|
||||
open={openInfoModal}
|
||||
onOpenChange={setOpenInfoModal}
|
||||
page={page}
|
||||
workspace={workspace}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,6 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@toeverything/infra": "workspace:*",
|
||||
"electron": "^31.1.0"
|
||||
"electron": "~30.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,10 +29,10 @@
|
||||
"@affine/env": "workspace:*",
|
||||
"@affine/i18n": "workspace:*",
|
||||
"@affine/native": "workspace:*",
|
||||
"@blocksuite/block-std": "0.16.0-canary-202407040721-5bf36c3",
|
||||
"@blocksuite/blocks": "0.16.0-canary-202407040721-5bf36c3",
|
||||
"@blocksuite/presets": "0.16.0-canary-202407040721-5bf36c3",
|
||||
"@blocksuite/store": "0.16.0-canary-202407040721-5bf36c3",
|
||||
"@blocksuite/block-std": "0.16.0-canary-202407050348-4620c21",
|
||||
"@blocksuite/blocks": "0.16.0-canary-202407050348-4620c21",
|
||||
"@blocksuite/presets": "0.16.0-canary-202407050348-4620c21",
|
||||
"@blocksuite/store": "0.16.0-canary-202407050348-4620c21",
|
||||
"@electron-forge/cli": "^7.3.0",
|
||||
"@electron-forge/core": "^7.3.0",
|
||||
"@electron-forge/core-utils": "^7.3.0",
|
||||
@@ -52,7 +52,7 @@
|
||||
"builder-util-runtime": "^9.2.5-alpha.2",
|
||||
"core-js": "^3.36.1",
|
||||
"cross-env": "^7.0.3",
|
||||
"electron": "^31.1.0",
|
||||
"electron": "~30.1.0",
|
||||
"electron-log": "^5.1.2",
|
||||
"electron-squirrel-startup": "1.0.1",
|
||||
"electron-window-state": "^5.0.3",
|
||||
|
||||
@@ -100,6 +100,23 @@ export enum CopilotModels {
|
||||
TextModerationStable = 'TextModerationStable',
|
||||
}
|
||||
|
||||
export interface CopilotPromptConfigInput {
|
||||
frequencyPenalty: InputMaybe<Scalars['Int']['input']>;
|
||||
jsonMode: InputMaybe<Scalars['Boolean']['input']>;
|
||||
presencePenalty: InputMaybe<Scalars['Int']['input']>;
|
||||
temperature: InputMaybe<Scalars['Int']['input']>;
|
||||
topP: InputMaybe<Scalars['Int']['input']>;
|
||||
}
|
||||
|
||||
export interface CopilotPromptConfigType {
|
||||
__typename?: 'CopilotPromptConfigType';
|
||||
frequencyPenalty: Maybe<Scalars['Int']['output']>;
|
||||
jsonMode: Maybe<Scalars['Boolean']['output']>;
|
||||
presencePenalty: Maybe<Scalars['Int']['output']>;
|
||||
temperature: Maybe<Scalars['Int']['output']>;
|
||||
topP: Maybe<Scalars['Int']['output']>;
|
||||
}
|
||||
|
||||
export interface CopilotPromptMessageInput {
|
||||
content: Scalars['String']['input'];
|
||||
params: InputMaybe<Scalars['JSON']['input']>;
|
||||
@@ -127,6 +144,7 @@ export interface CopilotPromptNotFoundDataType {
|
||||
export interface CopilotPromptType {
|
||||
__typename?: 'CopilotPromptType';
|
||||
action: Maybe<Scalars['String']['output']>;
|
||||
config: Maybe<CopilotPromptConfigType>;
|
||||
messages: Array<CopilotPromptMessageType>;
|
||||
model: CopilotModels;
|
||||
name: Scalars['String']['output'];
|
||||
@@ -170,6 +188,7 @@ export interface CreateCheckoutSessionInput {
|
||||
|
||||
export interface CreateCopilotPromptInput {
|
||||
action: InputMaybe<Scalars['String']['input']>;
|
||||
config: InputMaybe<CopilotPromptConfigInput>;
|
||||
messages: Array<CopilotPromptMessageInput>;
|
||||
model: CopilotModels;
|
||||
name: Scalars['String']['input'];
|
||||
|
||||
@@ -846,6 +846,7 @@
|
||||
"com.affine.page-properties.create-property.menu.header": "Type",
|
||||
"com.affine.page-properties.icons": "Icons",
|
||||
"com.affine.page-properties.page-info": "Info",
|
||||
"com.affine.page-properties.page-info.view": "View Info",
|
||||
"com.affine.page-properties.property-value-placeholder": "Empty",
|
||||
"com.affine.page-properties.property.always-hide": "Always hide",
|
||||
"com.affine.page-properties.property.always-show": "Always show",
|
||||
|
||||
139
tests/affine-local/e2e/doc-info-modal.spec.ts
Normal file
139
tests/affine-local/e2e/doc-info-modal.spec.ts
Normal file
@@ -0,0 +1,139 @@
|
||||
import { test } from '@affine-test/kit/playwright';
|
||||
import { openHomePage } from '@affine-test/kit/utils/load-page';
|
||||
import {
|
||||
clickNewPageButton,
|
||||
clickPageMoreActions,
|
||||
getBlockSuiteEditorTitle,
|
||||
getPageByTitle,
|
||||
getPageOperationButton,
|
||||
waitForEmptyEditor,
|
||||
} from '@affine-test/kit/utils/page-logic';
|
||||
import {
|
||||
addCustomProperty,
|
||||
closeTagsEditor,
|
||||
ensurePagePropertiesVisible,
|
||||
expectTagsVisible,
|
||||
filterTags,
|
||||
removeSelectedTag,
|
||||
} from '@affine-test/kit/utils/properties';
|
||||
import { expect, type Page } from '@playwright/test';
|
||||
|
||||
const searchAndCreateTag = async (page: Page, name: string) => {
|
||||
await filterTags(page, name);
|
||||
await page
|
||||
.locator(
|
||||
'[data-testid="tags-editor-popup"] [data-testid="tag-selector-item"]:has-text("Create ")'
|
||||
)
|
||||
.click();
|
||||
};
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await openHomePage(page);
|
||||
await clickNewPageButton(page);
|
||||
await waitForEmptyEditor(page);
|
||||
await ensurePagePropertiesVisible(page);
|
||||
await getBlockSuiteEditorTitle(page).click();
|
||||
await getBlockSuiteEditorTitle(page).fill('this is a new page');
|
||||
});
|
||||
|
||||
test('New a page and open it ,then open info modal in the title bar', async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.getByTestId('header-info-button').click();
|
||||
|
||||
const infoModal = page.getByTestId('info-modal');
|
||||
await expect(infoModal).toBeVisible();
|
||||
const tagRow = page.getByTestId('info-modal-tags-row');
|
||||
await expect(tagRow).toBeVisible();
|
||||
const title = page.getByTestId('info-modal-title');
|
||||
await expect(title).toHaveText('this is a new page');
|
||||
});
|
||||
|
||||
test('New a page and open it ,then open info modal in the title bar more action button', async ({
|
||||
page,
|
||||
}) => {
|
||||
await clickPageMoreActions(page);
|
||||
await page.getByTestId('editor-option-menu-info').click();
|
||||
|
||||
const infoModal = page.getByTestId('info-modal');
|
||||
await expect(infoModal).toBeVisible();
|
||||
const tagRow = page.getByTestId('info-modal-tags-row');
|
||||
await expect(tagRow).toBeVisible();
|
||||
const title = page.getByTestId('info-modal-title');
|
||||
await expect(title).toHaveText('this is a new page');
|
||||
});
|
||||
|
||||
test('New a page, then open info modal from all doc', async ({ page }) => {
|
||||
const newPageId = page.url().split('/').reverse()[0];
|
||||
|
||||
await page.getByTestId('all-pages').click();
|
||||
const cell = getPageByTitle(page, 'this is a new page');
|
||||
expect(cell).not.toBeUndefined();
|
||||
await getPageOperationButton(page, newPageId).click();
|
||||
await page.getByRole('menuitem', { name: 'View Info' }).click();
|
||||
|
||||
const infoModal = page.getByTestId('info-modal');
|
||||
await expect(infoModal).toBeVisible();
|
||||
const tagRow = page.getByTestId('info-modal-tags-row');
|
||||
await expect(tagRow).toBeVisible();
|
||||
const title = page.getByTestId('info-modal-title');
|
||||
await expect(title).toHaveText('this is a new page');
|
||||
});
|
||||
|
||||
test('New a page and add to favourites, then open info modal from sidebar', async ({
|
||||
page,
|
||||
}) => {
|
||||
const newPageId = page.url().split('/').reverse()[0];
|
||||
|
||||
await clickPageMoreActions(page);
|
||||
await page.getByTestId('editor-option-menu-favorite').click();
|
||||
|
||||
await page.getByTestId('all-pages').click();
|
||||
const favoriteListItemInSidebar = page.getByTestId(
|
||||
'favourite-page-' + newPageId
|
||||
);
|
||||
expect(await favoriteListItemInSidebar.textContent()).toBe(
|
||||
'this is a new page'
|
||||
);
|
||||
await favoriteListItemInSidebar.hover();
|
||||
await favoriteListItemInSidebar
|
||||
.getByTestId('left-sidebar-page-operation-button')
|
||||
.click();
|
||||
const infoBtn = page.getByText('View Info');
|
||||
await infoBtn.click();
|
||||
|
||||
const infoModal = page.getByTestId('info-modal');
|
||||
await expect(infoModal).toBeVisible();
|
||||
const tagRow = page.getByTestId('info-modal-tags-row');
|
||||
await expect(tagRow).toBeVisible();
|
||||
const title = page.getByTestId('info-modal-title');
|
||||
await expect(title).toHaveText('this is a new page');
|
||||
});
|
||||
|
||||
test('allow create tag', async ({ page }) => {
|
||||
await page.getByTestId('header-info-button').click();
|
||||
|
||||
const infoModal = page.getByTestId('info-modal');
|
||||
await expect(infoModal).toBeVisible();
|
||||
await page.getByTestId('info-modal-tags-value').click();
|
||||
await searchAndCreateTag(page, 'Test1');
|
||||
await searchAndCreateTag(page, 'Test2');
|
||||
await closeTagsEditor(page);
|
||||
await expectTagsVisible(page, ['Test1', 'Test2']);
|
||||
|
||||
await page.getByTestId('info-modal-tags-value').click();
|
||||
await removeSelectedTag(page, 'Test1');
|
||||
await closeTagsEditor(page);
|
||||
await expectTagsVisible(page, ['Test2']);
|
||||
});
|
||||
|
||||
test('add custom property', async ({ page }) => {
|
||||
await page.getByTestId('header-info-button').click();
|
||||
|
||||
const infoModal = page.getByTestId('info-modal');
|
||||
await expect(infoModal).toBeVisible();
|
||||
await addCustomProperty(page, 'Text');
|
||||
await addCustomProperty(page, 'Number');
|
||||
await addCustomProperty(page, 'Date');
|
||||
await addCustomProperty(page, 'Checkbox');
|
||||
});
|
||||
@@ -76,7 +76,7 @@ test('Show collections items in sidebar', async ({ page }) => {
|
||||
await collectionPage
|
||||
.getByTestId('left-sidebar-page-operation-button')
|
||||
.click();
|
||||
const deletePage = page.getByText('Delete');
|
||||
const deletePage = page.getByText('Move to Trash');
|
||||
await deletePage.click();
|
||||
await page.getByTestId('confirm-delete-page').click();
|
||||
expect(await collections.getByTestId('collection-page').count()).toBe(0);
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"@affine/env": "workspace:*",
|
||||
"@affine/templates": "workspace:*",
|
||||
"@aws-sdk/client-s3": "3.609.0",
|
||||
"@blocksuite/presets": "0.16.0-canary-202407040721-5bf36c3",
|
||||
"@blocksuite/presets": "0.16.0-canary-202407050348-4620c21",
|
||||
"@clack/core": "^0.3.4",
|
||||
"@clack/prompts": "^0.7.0",
|
||||
"@magic-works/i18n-codegen": "^0.6.0",
|
||||
|
||||
@@ -23,6 +23,7 @@ export function getRuntimeConfig(buildFlags: BuildFlags): RuntimeConfig {
|
||||
enablePayment: true,
|
||||
enablePageHistory: true,
|
||||
enableExperimentalFeature: false,
|
||||
enableInfoModal: false,
|
||||
allowLocalWorkspace: buildFlags.distribution === 'desktop' ? true : false,
|
||||
serverUrlPrefix: 'https://app.affine.pro',
|
||||
appVersion: packageJson.version,
|
||||
@@ -63,6 +64,7 @@ export function getRuntimeConfig(buildFlags: BuildFlags): RuntimeConfig {
|
||||
enablePayment: true,
|
||||
enablePageHistory: true,
|
||||
enableExperimentalFeature: true,
|
||||
enableInfoModal: true,
|
||||
allowLocalWorkspace: buildFlags.distribution === 'desktop' ? true : false,
|
||||
serverUrlPrefix: 'https://affine.fail',
|
||||
appVersion: packageJson.version,
|
||||
|
||||
150
yarn.lock
150
yarn.lock
@@ -226,7 +226,7 @@ __metadata:
|
||||
"@affine/env": "workspace:*"
|
||||
"@affine/templates": "workspace:*"
|
||||
"@aws-sdk/client-s3": "npm:3.609.0"
|
||||
"@blocksuite/presets": "npm:0.16.0-canary-202407040721-5bf36c3"
|
||||
"@blocksuite/presets": "npm:0.16.0-canary-202407050348-4620c21"
|
||||
"@clack/core": "npm:^0.3.4"
|
||||
"@clack/prompts": "npm:^0.7.0"
|
||||
"@magic-works/i18n-codegen": "npm:^0.6.0"
|
||||
@@ -282,12 +282,12 @@ __metadata:
|
||||
"@affine/electron-api": "workspace:*"
|
||||
"@affine/graphql": "workspace:*"
|
||||
"@affine/i18n": "workspace:*"
|
||||
"@blocksuite/block-std": "npm:0.16.0-canary-202407040721-5bf36c3"
|
||||
"@blocksuite/blocks": "npm:0.16.0-canary-202407040721-5bf36c3"
|
||||
"@blocksuite/global": "npm:0.16.0-canary-202407040721-5bf36c3"
|
||||
"@blocksuite/block-std": "npm:0.16.0-canary-202407050348-4620c21"
|
||||
"@blocksuite/blocks": "npm:0.16.0-canary-202407050348-4620c21"
|
||||
"@blocksuite/global": "npm:0.16.0-canary-202407050348-4620c21"
|
||||
"@blocksuite/icons": "npm:2.1.58"
|
||||
"@blocksuite/presets": "npm:0.16.0-canary-202407040721-5bf36c3"
|
||||
"@blocksuite/store": "npm:0.16.0-canary-202407040721-5bf36c3"
|
||||
"@blocksuite/presets": "npm:0.16.0-canary-202407050348-4620c21"
|
||||
"@blocksuite/store": "npm:0.16.0-canary-202407050348-4620c21"
|
||||
"@dnd-kit/core": "npm:^6.1.0"
|
||||
"@dnd-kit/modifiers": "npm:^7.0.0"
|
||||
"@dnd-kit/sortable": "npm:^8.0.0"
|
||||
@@ -383,13 +383,13 @@ __metadata:
|
||||
"@affine/graphql": "workspace:*"
|
||||
"@affine/i18n": "workspace:*"
|
||||
"@affine/templates": "workspace:*"
|
||||
"@blocksuite/block-std": "npm:0.16.0-canary-202407040721-5bf36c3"
|
||||
"@blocksuite/blocks": "npm:0.16.0-canary-202407040721-5bf36c3"
|
||||
"@blocksuite/global": "npm:0.16.0-canary-202407040721-5bf36c3"
|
||||
"@blocksuite/block-std": "npm:0.16.0-canary-202407050348-4620c21"
|
||||
"@blocksuite/blocks": "npm:0.16.0-canary-202407050348-4620c21"
|
||||
"@blocksuite/global": "npm:0.16.0-canary-202407050348-4620c21"
|
||||
"@blocksuite/icons": "npm:2.1.58"
|
||||
"@blocksuite/inline": "npm:0.16.0-canary-202407040721-5bf36c3"
|
||||
"@blocksuite/presets": "npm:0.16.0-canary-202407040721-5bf36c3"
|
||||
"@blocksuite/store": "npm:0.16.0-canary-202407040721-5bf36c3"
|
||||
"@blocksuite/inline": "npm:0.16.0-canary-202407050348-4620c21"
|
||||
"@blocksuite/presets": "npm:0.16.0-canary-202407050348-4620c21"
|
||||
"@blocksuite/store": "npm:0.16.0-canary-202407050348-4620c21"
|
||||
"@dnd-kit/core": "npm:^6.1.0"
|
||||
"@dnd-kit/modifiers": "npm:^7.0.0"
|
||||
"@dnd-kit/sortable": "npm:^8.0.0"
|
||||
@@ -502,7 +502,7 @@ __metadata:
|
||||
resolution: "@affine/electron-api@workspace:packages/frontend/electron-api"
|
||||
dependencies:
|
||||
"@toeverything/infra": "workspace:*"
|
||||
electron: "npm:^31.1.0"
|
||||
electron: "npm:~30.1.0"
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
@@ -516,10 +516,10 @@ __metadata:
|
||||
"@affine/env": "workspace:*"
|
||||
"@affine/i18n": "workspace:*"
|
||||
"@affine/native": "workspace:*"
|
||||
"@blocksuite/block-std": "npm:0.16.0-canary-202407040721-5bf36c3"
|
||||
"@blocksuite/blocks": "npm:0.16.0-canary-202407040721-5bf36c3"
|
||||
"@blocksuite/presets": "npm:0.16.0-canary-202407040721-5bf36c3"
|
||||
"@blocksuite/store": "npm:0.16.0-canary-202407040721-5bf36c3"
|
||||
"@blocksuite/block-std": "npm:0.16.0-canary-202407050348-4620c21"
|
||||
"@blocksuite/blocks": "npm:0.16.0-canary-202407050348-4620c21"
|
||||
"@blocksuite/presets": "npm:0.16.0-canary-202407050348-4620c21"
|
||||
"@blocksuite/store": "npm:0.16.0-canary-202407050348-4620c21"
|
||||
"@electron-forge/cli": "npm:^7.3.0"
|
||||
"@electron-forge/core": "npm:^7.3.0"
|
||||
"@electron-forge/core-utils": "npm:^7.3.0"
|
||||
@@ -540,7 +540,7 @@ __metadata:
|
||||
builder-util-runtime: "npm:^9.2.5-alpha.2"
|
||||
core-js: "npm:^3.36.1"
|
||||
cross-env: "npm:^7.0.3"
|
||||
electron: "npm:^31.1.0"
|
||||
electron: "npm:~30.1.0"
|
||||
electron-log: "npm:^5.1.2"
|
||||
electron-squirrel-startup: "npm:1.0.1"
|
||||
electron-updater: "npm:^6.2.1"
|
||||
@@ -575,8 +575,8 @@ __metadata:
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@affine/env@workspace:packages/common/env"
|
||||
dependencies:
|
||||
"@blocksuite/global": "npm:0.16.0-canary-202407040721-5bf36c3"
|
||||
"@blocksuite/store": "npm:0.16.0-canary-202407040721-5bf36c3"
|
||||
"@blocksuite/global": "npm:0.16.0-canary-202407050348-4620c21"
|
||||
"@blocksuite/store": "npm:0.16.0-canary-202407050348-4620c21"
|
||||
lit: "npm:^3.1.2"
|
||||
react: "npm:18.3.1"
|
||||
react-dom: "npm:18.3.1"
|
||||
@@ -652,7 +652,7 @@ __metadata:
|
||||
"@vitest/coverage-istanbul": "npm:1.6.0"
|
||||
"@vitest/ui": "npm:1.6.0"
|
||||
cross-env: "npm:^7.0.3"
|
||||
electron: "npm:^31.1.0"
|
||||
electron: "npm:~30.1.0"
|
||||
eslint: "npm:^8.57.0"
|
||||
eslint-config-prettier: "npm:^9.1.0"
|
||||
eslint-plugin-import-x: "npm:^0.5.0"
|
||||
@@ -3422,30 +3422,30 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@blocksuite/block-std@npm:0.16.0-canary-202407040721-5bf36c3":
|
||||
version: 0.16.0-canary-202407040721-5bf36c3
|
||||
resolution: "@blocksuite/block-std@npm:0.16.0-canary-202407040721-5bf36c3"
|
||||
"@blocksuite/block-std@npm:0.16.0-canary-202407050348-4620c21":
|
||||
version: 0.16.0-canary-202407050348-4620c21
|
||||
resolution: "@blocksuite/block-std@npm:0.16.0-canary-202407050348-4620c21"
|
||||
dependencies:
|
||||
"@blocksuite/global": "npm:0.16.0-canary-202407040721-5bf36c3"
|
||||
"@blocksuite/global": "npm:0.16.0-canary-202407050348-4620c21"
|
||||
lit: "npm:^3.1.3"
|
||||
lz-string: "npm:^1.5.0"
|
||||
w3c-keyname: "npm:^2.2.8"
|
||||
zod: "npm:^3.23.8"
|
||||
peerDependencies:
|
||||
"@blocksuite/inline": 0.16.0-canary-202407040721-5bf36c3
|
||||
"@blocksuite/store": 0.16.0-canary-202407040721-5bf36c3
|
||||
checksum: 10/15f36f3b1df6f030a39a0db990edd165164adc5089905b1a71e8afe905c0dfbe5d3835cfd70fe1984a56783770cc28e007aa2f8cd3e7807afb6774968ecdef36
|
||||
"@blocksuite/inline": 0.16.0-canary-202407050348-4620c21
|
||||
"@blocksuite/store": 0.16.0-canary-202407050348-4620c21
|
||||
checksum: 10/772fad3bbf779435453e780f305eb3e73e39f68de56226bc1d98b27e571d426a132334086f08e3f4eff63d809a7f57512482f3673d606e379da30f1e1b39a3e4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@blocksuite/blocks@npm:0.16.0-canary-202407040721-5bf36c3":
|
||||
version: 0.16.0-canary-202407040721-5bf36c3
|
||||
resolution: "@blocksuite/blocks@npm:0.16.0-canary-202407040721-5bf36c3"
|
||||
"@blocksuite/blocks@npm:0.16.0-canary-202407050348-4620c21":
|
||||
version: 0.16.0-canary-202407050348-4620c21
|
||||
resolution: "@blocksuite/blocks@npm:0.16.0-canary-202407050348-4620c21"
|
||||
dependencies:
|
||||
"@blocksuite/block-std": "npm:0.16.0-canary-202407040721-5bf36c3"
|
||||
"@blocksuite/global": "npm:0.16.0-canary-202407040721-5bf36c3"
|
||||
"@blocksuite/inline": "npm:0.16.0-canary-202407040721-5bf36c3"
|
||||
"@blocksuite/store": "npm:0.16.0-canary-202407040721-5bf36c3"
|
||||
"@blocksuite/block-std": "npm:0.16.0-canary-202407050348-4620c21"
|
||||
"@blocksuite/global": "npm:0.16.0-canary-202407050348-4620c21"
|
||||
"@blocksuite/inline": "npm:0.16.0-canary-202407050348-4620c21"
|
||||
"@blocksuite/store": "npm:0.16.0-canary-202407050348-4620c21"
|
||||
"@dotlottie/player-component": "npm:^2.7.12"
|
||||
"@floating-ui/dom": "npm:^1.6.5"
|
||||
"@lit/context": "npm:^1.1.1"
|
||||
@@ -3482,17 +3482,17 @@ __metadata:
|
||||
sortablejs: "npm:^1.15.2"
|
||||
unified: "npm:^11.0.4"
|
||||
zod: "npm:^3.23.8"
|
||||
checksum: 10/b81303df075841af51b337c7c2678632fae3122a02ad6a675559523c1424c167ce18cb4c3864dbd73cfbbe747e1972967b93fa16b0c415ab97fdb72fef056348
|
||||
checksum: 10/321dd311a43156b1b232b4ba52efd4ac88114bc342d093eaa9ccb843bc6e53e7752ad8821b0d898ffa2b9e83353e6e3bfd01a5d972c486d5745b65cde4fba50b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@blocksuite/global@npm:0.16.0-canary-202407040721-5bf36c3":
|
||||
version: 0.16.0-canary-202407040721-5bf36c3
|
||||
resolution: "@blocksuite/global@npm:0.16.0-canary-202407040721-5bf36c3"
|
||||
"@blocksuite/global@npm:0.16.0-canary-202407050348-4620c21":
|
||||
version: 0.16.0-canary-202407050348-4620c21
|
||||
resolution: "@blocksuite/global@npm:0.16.0-canary-202407050348-4620c21"
|
||||
dependencies:
|
||||
lib0: "npm:^0.2.94"
|
||||
zod: "npm:^3.23.8"
|
||||
checksum: 10/924960728f76888956db328b7af438623f1e2b466877ad8656c3981e18c119d0c568d239f86edfbc90dc99ebb1c298683682d096d4799898aeaa32c55775c01a
|
||||
checksum: 10/5d966494be823109acd8a5cefb84a748e1603b477a2d89f223e673a0e1fc07b7c0424403a6993a2ea555931d4b0f496c6f30d362324c874a43334f7dd2e81b33
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -3512,45 +3512,45 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@blocksuite/inline@npm:0.16.0-canary-202407040721-5bf36c3":
|
||||
version: 0.16.0-canary-202407040721-5bf36c3
|
||||
resolution: "@blocksuite/inline@npm:0.16.0-canary-202407040721-5bf36c3"
|
||||
"@blocksuite/inline@npm:0.16.0-canary-202407050348-4620c21":
|
||||
version: 0.16.0-canary-202407050348-4620c21
|
||||
resolution: "@blocksuite/inline@npm:0.16.0-canary-202407050348-4620c21"
|
||||
dependencies:
|
||||
"@blocksuite/global": "npm:0.16.0-canary-202407040721-5bf36c3"
|
||||
"@blocksuite/global": "npm:0.16.0-canary-202407050348-4620c21"
|
||||
zod: "npm:^3.23.8"
|
||||
peerDependencies:
|
||||
lit: ^3.1.1
|
||||
yjs: ^13.6.15
|
||||
checksum: 10/fca1645bd3705a2a1f81e182bb016e167fea477adbddfd9411772fc921a8b2f4fd9cbd79d037cae506bef0e4923dfbb0421a0acedeeafd55b5449c714bcaff22
|
||||
checksum: 10/a77c66f61a6f21467612d65924e64641474e2ee3500c63f0fcdcfd06a74b1d5a474fae8332e5321ccb4fb18e7b4f5706a52d4644d3ce26ddba78d88ee0f5f05b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@blocksuite/presets@npm:0.16.0-canary-202407040721-5bf36c3":
|
||||
version: 0.16.0-canary-202407040721-5bf36c3
|
||||
resolution: "@blocksuite/presets@npm:0.16.0-canary-202407040721-5bf36c3"
|
||||
"@blocksuite/presets@npm:0.16.0-canary-202407050348-4620c21":
|
||||
version: 0.16.0-canary-202407050348-4620c21
|
||||
resolution: "@blocksuite/presets@npm:0.16.0-canary-202407050348-4620c21"
|
||||
dependencies:
|
||||
"@blocksuite/block-std": "npm:0.16.0-canary-202407040721-5bf36c3"
|
||||
"@blocksuite/blocks": "npm:0.16.0-canary-202407040721-5bf36c3"
|
||||
"@blocksuite/global": "npm:0.16.0-canary-202407040721-5bf36c3"
|
||||
"@blocksuite/inline": "npm:0.16.0-canary-202407040721-5bf36c3"
|
||||
"@blocksuite/store": "npm:0.16.0-canary-202407040721-5bf36c3"
|
||||
"@blocksuite/block-std": "npm:0.16.0-canary-202407050348-4620c21"
|
||||
"@blocksuite/blocks": "npm:0.16.0-canary-202407050348-4620c21"
|
||||
"@blocksuite/global": "npm:0.16.0-canary-202407050348-4620c21"
|
||||
"@blocksuite/inline": "npm:0.16.0-canary-202407050348-4620c21"
|
||||
"@blocksuite/store": "npm:0.16.0-canary-202407050348-4620c21"
|
||||
"@dotlottie/player-component": "npm:^2.7.12"
|
||||
"@fal-ai/serverless-client": "npm:^0.10.0"
|
||||
"@floating-ui/dom": "npm:^1.6.5"
|
||||
"@toeverything/theme": "npm:^0.7.35"
|
||||
lit: "npm:^3.1.3"
|
||||
openai: "npm:^4.47.2"
|
||||
checksum: 10/97107817fb73292b7d7a692a6e3074b8c87de1973a5917191d6e8d64f66b9e4685e0b27041aefa6def690166d9096e28f6a7ae08443d0087b09d0660fd6dd7b5
|
||||
checksum: 10/23f9d454650340637c8795b03d54d5086cb385887550d7e3e2e69597228b1b286255906fc95368d3cd134afcdab769c9a5300875a33202c02ca05f33aafc4b27
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@blocksuite/store@npm:0.16.0-canary-202407040721-5bf36c3":
|
||||
version: 0.16.0-canary-202407040721-5bf36c3
|
||||
resolution: "@blocksuite/store@npm:0.16.0-canary-202407040721-5bf36c3"
|
||||
"@blocksuite/store@npm:0.16.0-canary-202407050348-4620c21":
|
||||
version: 0.16.0-canary-202407050348-4620c21
|
||||
resolution: "@blocksuite/store@npm:0.16.0-canary-202407050348-4620c21"
|
||||
dependencies:
|
||||
"@blocksuite/global": "npm:0.16.0-canary-202407040721-5bf36c3"
|
||||
"@blocksuite/inline": "npm:0.16.0-canary-202407040721-5bf36c3"
|
||||
"@blocksuite/sync": "npm:0.16.0-canary-202407040721-5bf36c3"
|
||||
"@blocksuite/global": "npm:0.16.0-canary-202407050348-4620c21"
|
||||
"@blocksuite/inline": "npm:0.16.0-canary-202407050348-4620c21"
|
||||
"@blocksuite/sync": "npm:0.16.0-canary-202407050348-4620c21"
|
||||
"@types/flexsearch": "npm:^0.7.6"
|
||||
flexsearch: "npm:0.7.43"
|
||||
lib0: "npm:^0.2.94"
|
||||
@@ -3561,21 +3561,21 @@ __metadata:
|
||||
zod: "npm:^3.23.8"
|
||||
peerDependencies:
|
||||
yjs: ^13.6.15
|
||||
checksum: 10/603536aff1e652f2467d917ac1f204d967d85429416439630a5010721116dc4a8449f8fcfa1a8a22c880344dac73d8957f324dfc0387fe151e8eee1683cc5346
|
||||
checksum: 10/9fa98e314ebebb899a15c85905c6493a2d23430c6ba1b60521b881ff0b1acc78c36bc7307ca8f01a4847837050b39207e578a977364d052dc3ec7e28411738e4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@blocksuite/sync@npm:0.16.0-canary-202407040721-5bf36c3":
|
||||
version: 0.16.0-canary-202407040721-5bf36c3
|
||||
resolution: "@blocksuite/sync@npm:0.16.0-canary-202407040721-5bf36c3"
|
||||
"@blocksuite/sync@npm:0.16.0-canary-202407050348-4620c21":
|
||||
version: 0.16.0-canary-202407050348-4620c21
|
||||
resolution: "@blocksuite/sync@npm:0.16.0-canary-202407050348-4620c21"
|
||||
dependencies:
|
||||
"@blocksuite/global": "npm:0.16.0-canary-202407040721-5bf36c3"
|
||||
"@blocksuite/global": "npm:0.16.0-canary-202407050348-4620c21"
|
||||
idb: "npm:^8.0.0"
|
||||
idb-keyval: "npm:^6.2.1"
|
||||
y-protocols: "npm:^1.0.6"
|
||||
peerDependencies:
|
||||
yjs: ^13.6.15
|
||||
checksum: 10/f9c9add14f7dd8311ec9b208041c829783b8e22a5e6567b30f893d20955726b52c31edbe3bea6a6713ac7f82f7f6cafb2f6552e4ba2b5edf8f2439a9a346fc1e
|
||||
checksum: 10/f2800fb336922f7fe6d7ea3a0028648897d3c5d20788af036273027d1c6fef44935f464c5c22301c8e6e531cd61e49de35871345819fdd277ef00a75116efff8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -14730,11 +14730,11 @@ __metadata:
|
||||
"@affine/debug": "workspace:*"
|
||||
"@affine/env": "workspace:*"
|
||||
"@affine/templates": "workspace:*"
|
||||
"@blocksuite/block-std": "npm:0.16.0-canary-202407040721-5bf36c3"
|
||||
"@blocksuite/blocks": "npm:0.16.0-canary-202407040721-5bf36c3"
|
||||
"@blocksuite/global": "npm:0.16.0-canary-202407040721-5bf36c3"
|
||||
"@blocksuite/presets": "npm:0.16.0-canary-202407040721-5bf36c3"
|
||||
"@blocksuite/store": "npm:0.16.0-canary-202407040721-5bf36c3"
|
||||
"@blocksuite/block-std": "npm:0.16.0-canary-202407050348-4620c21"
|
||||
"@blocksuite/blocks": "npm:0.16.0-canary-202407050348-4620c21"
|
||||
"@blocksuite/global": "npm:0.16.0-canary-202407050348-4620c21"
|
||||
"@blocksuite/presets": "npm:0.16.0-canary-202407050348-4620c21"
|
||||
"@blocksuite/store": "npm:0.16.0-canary-202407050348-4620c21"
|
||||
"@datastructures-js/binary-search-tree": "npm:^5.3.2"
|
||||
"@testing-library/react": "npm:^16.0.0"
|
||||
async-call-rpc: "npm:^6.4.0"
|
||||
@@ -21259,16 +21259,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"electron@npm:^31.1.0":
|
||||
version: 31.1.0
|
||||
resolution: "electron@npm:31.1.0"
|
||||
"electron@npm:~30.1.0":
|
||||
version: 30.1.2
|
||||
resolution: "electron@npm:30.1.2"
|
||||
dependencies:
|
||||
"@electron/get": "npm:^2.0.0"
|
||||
"@types/node": "npm:^20.9.0"
|
||||
extract-zip: "npm:^2.0.1"
|
||||
bin:
|
||||
electron: cli.js
|
||||
checksum: 10/a7eaa4ac9b63c69cd3791753559bc01c39297772606b62f2a61d3f55a12f67ddffc00d38fd0976d288b707fe3cf05fde522a3bf95f801c09c34487eb8109f2c7
|
||||
checksum: 10/ab8a67ac4a318aa8a1751d561b070007001398c642f98ebf41ce98e37b4b38e0ae80c7cb5bda1603c889433235f2e0176136e4d3b0ce177ebd9d1ba79f12cdc4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
||||
Reference in New Issue
Block a user