mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-23 01:07:12 +08:00
refactor(editor): shadowless TOC with valilla extract css (#9856)
Close [BS-2474](https://linear.app/affine-design/issue/BS-2474/使用shadowlesselement重构toc) This PR refactor TOC with `ShadowlessElement` and `@valilla-extract/css`
This commit is contained in:
@@ -18,12 +18,15 @@
|
|||||||
"@blocksuite/block-std": "workspace:*",
|
"@blocksuite/block-std": "workspace:*",
|
||||||
"@blocksuite/blocks": "workspace:*",
|
"@blocksuite/blocks": "workspace:*",
|
||||||
"@blocksuite/global": "workspace:*",
|
"@blocksuite/global": "workspace:*",
|
||||||
|
"@blocksuite/icons": "^2.2.2",
|
||||||
"@blocksuite/inline": "workspace:*",
|
"@blocksuite/inline": "workspace:*",
|
||||||
"@blocksuite/store": "workspace:*",
|
"@blocksuite/store": "workspace:*",
|
||||||
"@floating-ui/dom": "^1.6.10",
|
"@floating-ui/dom": "^1.6.10",
|
||||||
|
"@lit/context": "^1.1.3",
|
||||||
"@lottiefiles/dotlottie-wc": "^0.4.0",
|
"@lottiefiles/dotlottie-wc": "^0.4.0",
|
||||||
"@preact/signals-core": "^1.8.0",
|
"@preact/signals-core": "^1.8.0",
|
||||||
"@toeverything/theme": "^1.1.7",
|
"@toeverything/theme": "^1.1.7",
|
||||||
|
"@vanilla-extract/css": "^1.17.0",
|
||||||
"lit": "^3.2.0",
|
"lit": "^3.2.0",
|
||||||
"yjs": "^13.6.21",
|
"yjs": "^13.6.21",
|
||||||
"zod": "^3.23.8"
|
"zod": "^3.23.8"
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
import { cssVar } from '@toeverything/theme';
|
||||||
|
import { cssVarV2 } from '@toeverything/theme/v2';
|
||||||
|
import { style } from '@vanilla-extract/css';
|
||||||
|
|
||||||
|
export const outlineNotice = style({
|
||||||
|
position: 'absolute',
|
||||||
|
left: 0,
|
||||||
|
bottom: '8px',
|
||||||
|
padding: '10px 18px',
|
||||||
|
display: 'flex',
|
||||||
|
width: '100%',
|
||||||
|
boxSizing: 'border-box',
|
||||||
|
gap: '14px',
|
||||||
|
fontStyle: 'normal',
|
||||||
|
fontSize: '12px',
|
||||||
|
flexDirection: 'column',
|
||||||
|
borderRadius: '8px',
|
||||||
|
backgroundColor: cssVar('--affine-background-overlay-panel-color'),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const outlineNoticeHeader = style({
|
||||||
|
display: 'flex',
|
||||||
|
width: '100%',
|
||||||
|
height: '20px',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const outlineNoticeLabel = style({
|
||||||
|
fontWeight: 600,
|
||||||
|
lineHeight: '20px',
|
||||||
|
color: cssVarV2('text/secondary'),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const outlineNoticeCloseButton = style({
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
width: '20px',
|
||||||
|
height: '20px',
|
||||||
|
cursor: 'pointer',
|
||||||
|
color: cssVarV2('icon/primary'),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const outlineNoticeBody = style({
|
||||||
|
display: 'flex',
|
||||||
|
width: '100%',
|
||||||
|
gap: '2px',
|
||||||
|
flexDirection: 'column',
|
||||||
|
});
|
||||||
|
|
||||||
|
const outlineNoticeItem = style({
|
||||||
|
display: 'flex',
|
||||||
|
height: '20px',
|
||||||
|
alignItems: 'center',
|
||||||
|
lineHeight: '20px',
|
||||||
|
color: cssVarV2('text/primary'),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const notice = style([
|
||||||
|
outlineNoticeItem,
|
||||||
|
{
|
||||||
|
fontWeight: 400,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const button = style([
|
||||||
|
outlineNoticeItem,
|
||||||
|
{
|
||||||
|
display: 'flex',
|
||||||
|
gap: '2px',
|
||||||
|
fontWeight: 500,
|
||||||
|
textDecoration: 'underline',
|
||||||
|
cursor: 'pointer',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const buttonSpan = style({
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
lineHeight: '20px',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const buttonSvg = style({
|
||||||
|
scale: 0.8,
|
||||||
|
});
|
||||||
@@ -1,89 +1,14 @@
|
|||||||
|
import { ShadowlessElement } from '@blocksuite/block-std';
|
||||||
import { WithDisposable } from '@blocksuite/global/utils';
|
import { WithDisposable } from '@blocksuite/global/utils';
|
||||||
import { css, html, LitElement, nothing } from 'lit';
|
import { html, nothing } from 'lit';
|
||||||
import { property } from 'lit/decorators.js';
|
import { property } from 'lit/decorators.js';
|
||||||
|
|
||||||
import { SmallCloseIcon, SortingIcon } from '../../_common/icons.js';
|
import { SmallCloseIcon, SortingIcon } from '../../_common/icons.js';
|
||||||
|
import * as styles from './outline-notice.css';
|
||||||
const styles = css`
|
|
||||||
:host {
|
|
||||||
width: 100%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
bottom: 8px;
|
|
||||||
padding: 0 8px;
|
|
||||||
}
|
|
||||||
.outline-notice-container {
|
|
||||||
display: flex;
|
|
||||||
width: 100%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
gap: 14px;
|
|
||||||
padding: 10px;
|
|
||||||
font-style: normal;
|
|
||||||
font-size: 12px;
|
|
||||||
flex-direction: column;
|
|
||||||
border-radius: 8px;
|
|
||||||
background-color: var(--affine-background-overlay-panel-color);
|
|
||||||
}
|
|
||||||
.outline-notice-header {
|
|
||||||
display: flex;
|
|
||||||
width: 100%;
|
|
||||||
height: 20px;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
.outline-notice-label {
|
|
||||||
font-weight: 600;
|
|
||||||
line-height: 20px;
|
|
||||||
color: var(--affine-text-secondary-color);
|
|
||||||
}
|
|
||||||
.outline-notice-close-button {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
cursor: pointer;
|
|
||||||
color: var(--affine-icon-color);
|
|
||||||
}
|
|
||||||
.outline-notice-body {
|
|
||||||
display: flex;
|
|
||||||
width: 100%;
|
|
||||||
gap: 2px;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
.outline-notice-item {
|
|
||||||
display: flex;
|
|
||||||
height: 20px;
|
|
||||||
align-items: center;
|
|
||||||
line-height: 20px;
|
|
||||||
color: var(--affine-text-primary-color);
|
|
||||||
}
|
|
||||||
.outline-notice-item.notice {
|
|
||||||
font-weight: 400;
|
|
||||||
}
|
|
||||||
.outline-notice-item.button {
|
|
||||||
display: flex;
|
|
||||||
gap: 2px;
|
|
||||||
font-weight: 500;
|
|
||||||
text-decoration: underline;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.outline-notice-item.button span {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
line-height: 20px;
|
|
||||||
}
|
|
||||||
.outline-notice-item.button svg {
|
|
||||||
scale: 0.8;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const AFFINE_OUTLINE_NOTICE = 'affine-outline-notice';
|
export const AFFINE_OUTLINE_NOTICE = 'affine-outline-notice';
|
||||||
|
|
||||||
export class OutlineNotice extends WithDisposable(LitElement) {
|
export class OutlineNotice extends WithDisposable(ShadowlessElement) {
|
||||||
static override styles = styles;
|
|
||||||
|
|
||||||
private _handleNoticeButtonClick() {
|
private _handleNoticeButtonClick() {
|
||||||
this.toggleNotesSorting();
|
this.toggleNotesSorting();
|
||||||
this.setNoticeVisibility(false);
|
this.setNoticeVisibility(false);
|
||||||
@@ -94,29 +19,28 @@ export class OutlineNotice extends WithDisposable(LitElement) {
|
|||||||
return nothing;
|
return nothing;
|
||||||
}
|
}
|
||||||
|
|
||||||
return html`<div class="outline-notice-container">
|
return html`
|
||||||
<div class="outline-notice-header">
|
<div class=${styles.outlineNotice}>
|
||||||
<span class="outline-notice-label">SOME CONTENTS HIDDEN</span>
|
<div class=${styles.outlineNoticeHeader}>
|
||||||
<span
|
<span class=${styles.outlineNoticeLabel}>SOME CONTENTS HIDDEN</span>
|
||||||
class="outline-notice-close-button"
|
<span
|
||||||
@click=${() => this.setNoticeVisibility(false)}
|
class=${styles.outlineNoticeCloseButton}
|
||||||
>${SmallCloseIcon}</span
|
@click=${() => this.setNoticeVisibility(false)}
|
||||||
>
|
>${SmallCloseIcon}</span
|
||||||
</div>
|
>
|
||||||
<div class="outline-notice-body">
|
|
||||||
<div class="outline-notice-item notice">
|
|
||||||
Some contents are not visible on edgeless.
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div class=${styles.outlineNoticeBody}>
|
||||||
class="outline-notice-item button"
|
<div class="${styles.notice}">
|
||||||
@click=${this._handleNoticeButtonClick}
|
Some contents are not visible on edgeless.
|
||||||
>
|
</div>
|
||||||
<span>Click here or</span>
|
<div class="${styles.button}" @click=${this._handleNoticeButtonClick}>
|
||||||
<span>${SortingIcon}</span>
|
<span class=${styles.buttonSpan}>Click here or</span>
|
||||||
<span>to organize content.</span>
|
<span class=${styles.buttonSpan}>${SortingIcon}</span>
|
||||||
|
<span class=${styles.buttonSpan}>to organize content.</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import { cssVar } from '@toeverything/theme';
|
||||||
|
import { cssVarV2 } from '@toeverything/theme/v2';
|
||||||
|
import { style } from '@vanilla-extract/css';
|
||||||
|
|
||||||
|
export const outlinePanelBody = style({
|
||||||
|
position: 'relative',
|
||||||
|
alignItems: 'start',
|
||||||
|
boxSizing: 'border-box',
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
padding: '0 8px',
|
||||||
|
flexGrow: 1,
|
||||||
|
overflowY: 'scroll',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const cardList = style({
|
||||||
|
position: 'relative',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const edgelessCardListTitle = style({
|
||||||
|
width: '100%',
|
||||||
|
fontSize: '14px',
|
||||||
|
lineHeight: '24px',
|
||||||
|
fontWeight: 500,
|
||||||
|
color: cssVarV2('text/secondary'),
|
||||||
|
paddingLeft: '8px',
|
||||||
|
height: '40px',
|
||||||
|
boxSizing: 'border-box',
|
||||||
|
padding: '6px 8px',
|
||||||
|
marginTop: '8px',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const insertIndicator = style({
|
||||||
|
height: '2px',
|
||||||
|
borderRadius: '1px',
|
||||||
|
backgroundColor: cssVar('brandColor'),
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
contain: 'layout size',
|
||||||
|
width: '100%',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const emptyPanel = style({
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
width: '100%',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const emptyPanelPlaceholder = style({
|
||||||
|
marginTop: '240px',
|
||||||
|
alignSelf: 'center',
|
||||||
|
width: '190px',
|
||||||
|
height: '48px',
|
||||||
|
color: cssVarV2('text/secondary'),
|
||||||
|
textAlign: 'center',
|
||||||
|
fontSize: '15px',
|
||||||
|
fontStyle: 'normal',
|
||||||
|
fontWeight: 400,
|
||||||
|
lineHeight: '24px',
|
||||||
|
});
|
||||||
@@ -1,13 +1,9 @@
|
|||||||
import { SurfaceSelection } from '@blocksuite/block-std';
|
import { ShadowlessElement, SurfaceSelection } from '@blocksuite/block-std';
|
||||||
import type {
|
import type { NoteBlockModel } from '@blocksuite/blocks';
|
||||||
EdgelessRootBlockComponent,
|
|
||||||
NoteBlockModel,
|
|
||||||
} from '@blocksuite/blocks';
|
|
||||||
import {
|
import {
|
||||||
BlocksUtils,
|
BlocksUtils,
|
||||||
matchFlavours,
|
matchFlavours,
|
||||||
NoteDisplayMode,
|
NoteDisplayMode,
|
||||||
ThemeProvider,
|
|
||||||
} from '@blocksuite/blocks';
|
} from '@blocksuite/blocks';
|
||||||
import {
|
import {
|
||||||
Bound,
|
Bound,
|
||||||
@@ -15,30 +11,33 @@ import {
|
|||||||
SignalWatcher,
|
SignalWatcher,
|
||||||
WithDisposable,
|
WithDisposable,
|
||||||
} from '@blocksuite/global/utils';
|
} from '@blocksuite/global/utils';
|
||||||
import type { Store } from '@blocksuite/store';
|
import { consume } from '@lit/context';
|
||||||
import { effect, signal } from '@preact/signals-core';
|
import { effect, signal } from '@preact/signals-core';
|
||||||
import { css, html, LitElement, nothing, type PropertyValues } from 'lit';
|
import { html, nothing, type PropertyValues } from 'lit';
|
||||||
import { property, query, state } from 'lit/decorators.js';
|
import { property, query } from 'lit/decorators.js';
|
||||||
import { classMap } from 'lit/directives/class-map.js';
|
import { classMap } from 'lit/directives/class-map.js';
|
||||||
import { repeat } from 'lit/directives/repeat.js';
|
import { repeat } from 'lit/directives/repeat.js';
|
||||||
|
import { when } from 'lit/directives/when.js';
|
||||||
|
|
||||||
import type { AffineEditorContainer } from '../../../editors/editor-container.js';
|
import type { AffineEditorContainer } from '../../../editors/editor-container';
|
||||||
|
import { editorContext } from '../config';
|
||||||
import type {
|
import type {
|
||||||
ClickBlockEvent,
|
ClickBlockEvent,
|
||||||
DisplayModeChangeEvent,
|
DisplayModeChangeEvent,
|
||||||
FitViewEvent,
|
FitViewEvent,
|
||||||
SelectEvent,
|
SelectEvent,
|
||||||
} from '../utils/custom-events.js';
|
} from '../utils/custom-events';
|
||||||
import { startDragging } from '../utils/drag.js';
|
import { startDragging } from '../utils/drag';
|
||||||
import {
|
import {
|
||||||
getHeadingBlocksFromDoc,
|
getHeadingBlocksFromDoc,
|
||||||
getNotesFromDoc,
|
getNotesFromDoc,
|
||||||
isHeadingBlock,
|
isHeadingBlock,
|
||||||
} from '../utils/query.js';
|
} from '../utils/query';
|
||||||
import {
|
import {
|
||||||
observeActiveHeadingDuringScroll,
|
observeActiveHeadingDuringScroll,
|
||||||
scrollToBlockWithHighlight,
|
scrollToBlockWithHighlight,
|
||||||
} from '../utils/scroll.js';
|
} from '../utils/scroll';
|
||||||
|
import * as styles from './outline-panel-body.css';
|
||||||
|
|
||||||
type OutlineNoteItem = {
|
type OutlineNoteItem = {
|
||||||
note: NoteBlockModel;
|
note: NoteBlockModel;
|
||||||
@@ -54,76 +53,19 @@ type OutlineNoteItem = {
|
|||||||
number: number;
|
number: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
const styles = css`
|
|
||||||
.outline-panel-body-container {
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
align-items: start;
|
|
||||||
box-sizing: border-box;
|
|
||||||
flex-direction: column;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
padding: 0 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.panel-list {
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.panel-list .hidden-title {
|
|
||||||
width: 100%;
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 24px;
|
|
||||||
font-weight: 500;
|
|
||||||
color: var(--affine-text-secondary-color);
|
|
||||||
padding-left: 8px;
|
|
||||||
height: 40px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding: 6px 8px;
|
|
||||||
margin-top: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.insert-indicator {
|
|
||||||
height: 2px;
|
|
||||||
border-radius: 1px;
|
|
||||||
background-color: var(--affine-brand-color);
|
|
||||||
border-radius: 1px;
|
|
||||||
position: absolute;
|
|
||||||
contain: layout size;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.no-note-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.note-placeholder {
|
|
||||||
margin-top: 240px;
|
|
||||||
align-self: center;
|
|
||||||
width: 190px;
|
|
||||||
height: 48px;
|
|
||||||
color: var(--affine-text-secondary-color, #8e8d91);
|
|
||||||
text-align: center;
|
|
||||||
/* light/base */
|
|
||||||
font-size: 15px;
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
line-height: 24px;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const AFFINE_OUTLINE_PANEL_BODY = 'affine-outline-panel-body';
|
export const AFFINE_OUTLINE_PANEL_BODY = 'affine-outline-panel-body';
|
||||||
|
|
||||||
export class OutlinePanelBody extends SignalWatcher(
|
export class OutlinePanelBody extends SignalWatcher(
|
||||||
WithDisposable(LitElement)
|
WithDisposable(ShadowlessElement)
|
||||||
) {
|
) {
|
||||||
static override styles = styles;
|
|
||||||
|
|
||||||
private readonly _activeHeadingId$ = signal<string | null>(null);
|
private readonly _activeHeadingId$ = signal<string | null>(null);
|
||||||
|
|
||||||
|
private readonly _dragging$ = signal(false);
|
||||||
|
|
||||||
|
private readonly _pageVisibleNoteItems$ = signal<OutlineNoteItem[]>([]);
|
||||||
|
|
||||||
|
private readonly _edgelessOnlyNoteItems$ = signal<OutlineNoteItem[]>([]);
|
||||||
|
|
||||||
private _clearHighlightMask = () => {};
|
private _clearHighlightMask = () => {};
|
||||||
|
|
||||||
private _docDisposables: DisposableGroup | null = null;
|
private _docDisposables: DisposableGroup | null = null;
|
||||||
@@ -132,6 +74,21 @@ export class OutlinePanelBody extends SignalWatcher(
|
|||||||
|
|
||||||
private _lockActiveHeadingId = false;
|
private _lockActiveHeadingId = false;
|
||||||
|
|
||||||
|
private get _shouldRenderEmptyPanel() {
|
||||||
|
return (
|
||||||
|
this._pageVisibleNoteItems$.value.length === 0 &&
|
||||||
|
this._edgelessOnlyNoteItems$.value.length === 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private get doc() {
|
||||||
|
return this.editor.doc;
|
||||||
|
}
|
||||||
|
|
||||||
|
private get edgeless() {
|
||||||
|
return this.editor.querySelector('affine-edgeless-root');
|
||||||
|
}
|
||||||
|
|
||||||
get viewportPadding(): [number, number, number, number] {
|
get viewportPadding(): [number, number, number, number] {
|
||||||
return this.fitPadding
|
return this.fitPadding
|
||||||
? ([0, 0, 0, 0].map((val, idx) =>
|
? ([0, 0, 0, 0].map((val, idx) =>
|
||||||
@@ -174,6 +131,8 @@ export class OutlinePanelBody extends SignalWatcher(
|
|||||||
};
|
};
|
||||||
|
|
||||||
private _drag() {
|
private _drag() {
|
||||||
|
const pageVisibleNotes = this._pageVisibleNoteItems$.peek();
|
||||||
|
|
||||||
const selectedVisibleNotes = this._selectedNotes$.peek().filter(id => {
|
const selectedVisibleNotes = this._selectedNotes$.peek().filter(id => {
|
||||||
const model = this.doc.getBlock(id)?.model;
|
const model = this.doc.getBlock(id)?.model;
|
||||||
return (
|
return (
|
||||||
@@ -185,7 +144,7 @@ export class OutlinePanelBody extends SignalWatcher(
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
selectedVisibleNotes.length === 0 ||
|
selectedVisibleNotes.length === 0 ||
|
||||||
!this._pageVisibleNotes.length ||
|
!pageVisibleNotes.length ||
|
||||||
!this.doc.root
|
!this.doc.root
|
||||||
)
|
)
|
||||||
return;
|
return;
|
||||||
@@ -199,12 +158,11 @@ export class OutlinePanelBody extends SignalWatcher(
|
|||||||
this._selectedNotes$.value = selectedVisibleNotes;
|
this._selectedNotes$.value = selectedVisibleNotes;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._dragging = true;
|
this._dragging$.value = true;
|
||||||
|
|
||||||
// cache the notes in case it is changed by other peers
|
// cache the notes in case it is changed by other peers
|
||||||
const children = this.doc.root.children.slice() as NoteBlockModel[];
|
const children = this.doc.root.children.slice() as NoteBlockModel[];
|
||||||
const notes = this._pageVisibleNotes;
|
const notesMap = pageVisibleNotes.reduce((map, note, index) => {
|
||||||
const notesMap = this._pageVisibleNotes.reduce((map, note, index) => {
|
|
||||||
map.set(note.note.id, {
|
map.set(note.note.id, {
|
||||||
...note,
|
...note,
|
||||||
number: index + 1,
|
number: index + 1,
|
||||||
@@ -215,11 +173,11 @@ export class OutlinePanelBody extends SignalWatcher(
|
|||||||
startDragging({
|
startDragging({
|
||||||
container: this,
|
container: this,
|
||||||
document: this.ownerDocument,
|
document: this.ownerDocument,
|
||||||
host: this.domHost ?? this.ownerDocument,
|
host: this.ownerDocument,
|
||||||
doc: this.doc,
|
doc: this.doc,
|
||||||
outlineListContainer: this.panelListElement,
|
outlineListContainer: this._pageVisibleList,
|
||||||
onDragEnd: insertIdx => {
|
onDragEnd: insertIdx => {
|
||||||
this._dragging = false;
|
this._dragging$.value = false;
|
||||||
this.insertIndex = undefined;
|
this.insertIndex = undefined;
|
||||||
|
|
||||||
if (insertIdx === undefined) return;
|
if (insertIdx === undefined) return;
|
||||||
@@ -228,7 +186,7 @@ export class OutlinePanelBody extends SignalWatcher(
|
|||||||
insertIdx,
|
insertIdx,
|
||||||
selectedVisibleNotes,
|
selectedVisibleNotes,
|
||||||
notesMap,
|
notesMap,
|
||||||
notes,
|
pageVisibleNotes,
|
||||||
children
|
children
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -240,8 +198,11 @@ export class OutlinePanelBody extends SignalWatcher(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _EmptyPanel() {
|
private _EmptyPanel() {
|
||||||
return html`<div class="no-note-container">
|
return html`<div class=${styles.emptyPanel}>
|
||||||
<div class="note-placeholder">
|
<div
|
||||||
|
data-testid="empty-panel-placeholder"
|
||||||
|
class=${styles.emptyPanelPlaceholder}
|
||||||
|
>
|
||||||
Use headings to create a table of contents.
|
Use headings to create a table of contents.
|
||||||
</div>
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
@@ -330,77 +291,6 @@ export class OutlinePanelBody extends SignalWatcher(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private _PanelList(withEdgelessOnlyNotes: boolean) {
|
|
||||||
const selectedNotesSet = new Set(this._selectedNotes$.value);
|
|
||||||
const theme = this.editor.std.get(ThemeProvider).theme;
|
|
||||||
|
|
||||||
return html`<div class="panel-list">
|
|
||||||
${this.insertIndex !== undefined
|
|
||||||
? html`<div
|
|
||||||
class="insert-indicator"
|
|
||||||
style=${`transform: translateY(${this._indicatorTranslateY}px)`}
|
|
||||||
></div>`
|
|
||||||
: nothing}
|
|
||||||
${this._pageVisibleNotes.length
|
|
||||||
? repeat(
|
|
||||||
this._pageVisibleNotes,
|
|
||||||
item => item.note.id,
|
|
||||||
(item, idx) => html`
|
|
||||||
<affine-outline-note-card
|
|
||||||
data-note-id=${item.note.id}
|
|
||||||
.note=${item.note}
|
|
||||||
.theme=${theme}
|
|
||||||
.number=${idx + 1}
|
|
||||||
.index=${item.index}
|
|
||||||
.doc=${this.doc}
|
|
||||||
.activeHeadingId=${this._activeHeadingId$.value}
|
|
||||||
.status=${selectedNotesSet.has(item.note.id)
|
|
||||||
? this._dragging
|
|
||||||
? 'placeholder'
|
|
||||||
: 'selected'
|
|
||||||
: undefined}
|
|
||||||
.showPreviewIcon=${this.showPreviewIcon}
|
|
||||||
.enableNotesSorting=${this.enableNotesSorting}
|
|
||||||
@select=${this._selectNote}
|
|
||||||
@drag=${this._drag}
|
|
||||||
@fitview=${this._fitToElement}
|
|
||||||
@clickblock=${(e: ClickBlockEvent) => {
|
|
||||||
this._scrollToBlock(e.detail.blockId).catch(console.error);
|
|
||||||
}}
|
|
||||||
@displaymodechange=${this._handleDisplayModeChange}
|
|
||||||
></affine-outline-note-card>
|
|
||||||
`
|
|
||||||
)
|
|
||||||
: html`${nothing}`}
|
|
||||||
${withEdgelessOnlyNotes
|
|
||||||
? html`<div class="hidden-title">Hidden Contents</div>
|
|
||||||
${repeat(
|
|
||||||
this._edgelessOnlyNotes,
|
|
||||||
note => note.note.id,
|
|
||||||
(item, idx) =>
|
|
||||||
html`<affine-outline-note-card
|
|
||||||
data-note-id=${item.note.id}
|
|
||||||
.note=${item.note}
|
|
||||||
.theme=${theme}
|
|
||||||
.number=${idx + 1}
|
|
||||||
.index=${item.index}
|
|
||||||
.doc=${this.doc}
|
|
||||||
.activeHeadingId=${this._activeHeadingId$.value}
|
|
||||||
.invisible=${true}
|
|
||||||
.showPreviewIcon=${this.showPreviewIcon}
|
|
||||||
.enableNotesSorting=${this.enableNotesSorting}
|
|
||||||
.status=${selectedNotesSet.has(item.note.id)
|
|
||||||
? 'selected'
|
|
||||||
: undefined}
|
|
||||||
@fitview=${this._fitToElement}
|
|
||||||
@select=${this._selectNote}
|
|
||||||
@displaymodechange=${this._handleDisplayModeChange}
|
|
||||||
></affine-outline-note-card>`
|
|
||||||
)} `
|
|
||||||
: nothing}
|
|
||||||
</div>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _renderDocTitle() {
|
private _renderDocTitle() {
|
||||||
if (!this.doc.root) return nothing;
|
if (!this.doc.root) return nothing;
|
||||||
|
|
||||||
@@ -431,6 +321,61 @@ export class OutlinePanelBody extends SignalWatcher(
|
|||||||
></affine-outline-block-preview>`;
|
></affine-outline-block-preview>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _renderNoteCards(items: OutlineNoteItem[]) {
|
||||||
|
return repeat(
|
||||||
|
items,
|
||||||
|
item => item.note.id,
|
||||||
|
(item, idx) =>
|
||||||
|
html`<affine-outline-note-card
|
||||||
|
data-note-id=${item.note.id}
|
||||||
|
.note=${item.note}
|
||||||
|
.number=${idx + 1}
|
||||||
|
.index=${item.index}
|
||||||
|
.activeHeadingId=${this._activeHeadingId$.value}
|
||||||
|
.showPreviewIcon=${this.showPreviewIcon}
|
||||||
|
.enableNotesSorting=${this.enableNotesSorting}
|
||||||
|
.status=${this._selectedNotes$.value.includes(item.note.id)
|
||||||
|
? this._dragging$.value
|
||||||
|
? 'placeholder'
|
||||||
|
: 'selected'
|
||||||
|
: 'normal'}
|
||||||
|
@fitview=${this._fitToElement}
|
||||||
|
@select=${this._selectNote}
|
||||||
|
@displaymodechange=${this._handleDisplayModeChange}
|
||||||
|
@drag=${this._drag}
|
||||||
|
@clickblock=${(e: ClickBlockEvent) => {
|
||||||
|
this._scrollToBlock(e.detail.blockId).catch(console.error);
|
||||||
|
}}
|
||||||
|
></affine-outline-note-card>`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _renderPageVisibleCardList() {
|
||||||
|
return html`<div class=${`page-visible-card-list ${styles.cardList}`}>
|
||||||
|
${when(
|
||||||
|
this.insertIndex !== undefined,
|
||||||
|
() =>
|
||||||
|
html`<div
|
||||||
|
class=${styles.insertIndicator}
|
||||||
|
style=${`transform: translateY(${this._indicatorTranslateY}px)`}
|
||||||
|
></div>`
|
||||||
|
)}
|
||||||
|
${this._renderNoteCards(this._pageVisibleNoteItems$.value)}
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _renderEdgelessOnlyCardList() {
|
||||||
|
const items = this._edgelessOnlyNoteItems$.value;
|
||||||
|
return html`<div class=${styles.cardList}>
|
||||||
|
${when(
|
||||||
|
items.length > 0,
|
||||||
|
() =>
|
||||||
|
html`<div class=${styles.edgelessCardListTitle}>Hidden Contents</div>`
|
||||||
|
)}
|
||||||
|
${this._renderNoteCards(items)}
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
private async _scrollToBlock(blockId: string) {
|
private async _scrollToBlock(blockId: string) {
|
||||||
this._lockActiveHeadingId = true;
|
this._lockActiveHeadingId = true;
|
||||||
this._activeHeadingId$.value = blockId;
|
this._activeHeadingId$.value = blockId;
|
||||||
@@ -471,69 +416,41 @@ export class OutlinePanelBody extends SignalWatcher(
|
|||||||
this._docDisposables = new DisposableGroup();
|
this._docDisposables = new DisposableGroup();
|
||||||
this._docDisposables.add(
|
this._docDisposables.add(
|
||||||
effect(() => {
|
effect(() => {
|
||||||
this._updateNotes();
|
|
||||||
this._updateNoticeVisibility();
|
this._updateNoticeVisibility();
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
this._docDisposables.add(
|
this._docDisposables.add(
|
||||||
this.doc.slots.blockUpdated.on(payload => {
|
effect(() => {
|
||||||
if (
|
this._updateNotes();
|
||||||
payload.type === 'update' &&
|
|
||||||
payload.flavour === 'affine:note' &&
|
|
||||||
payload.props.key === 'displayMode'
|
|
||||||
) {
|
|
||||||
this._updateNotes();
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private _updateNotes() {
|
||||||
* There are two cases that we should render note list:
|
if (this._dragging$.value) return;
|
||||||
* 1. There are headings in the notes
|
|
||||||
* 2. No headings, but there are blocks in the notes and note sorting option is enabled
|
|
||||||
*/
|
|
||||||
private _shouldRenderNoteList(noteItems: OutlineNoteItem[]) {
|
|
||||||
if (!noteItems.length) return false;
|
|
||||||
|
|
||||||
let hasHeadings = false;
|
const isRenderableNote = (item: OutlineNoteItem) => {
|
||||||
let hasChildrenBlocks = false;
|
let hasHeadings = false;
|
||||||
|
|
||||||
for (const noteItem of noteItems) {
|
|
||||||
for (const block of noteItem.note.children) {
|
|
||||||
hasChildrenBlocks = true;
|
|
||||||
|
|
||||||
|
for (const block of item.note.children) {
|
||||||
if (isHeadingBlock(block)) {
|
if (isHeadingBlock(block)) {
|
||||||
hasHeadings = true;
|
hasHeadings = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasHeadings) {
|
return hasHeadings || this.enableNotesSorting;
|
||||||
break;
|
};
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return hasHeadings || (this.enableNotesSorting && hasChildrenBlocks);
|
this._pageVisibleNoteItems$.value = getNotesFromDoc(this.doc, [
|
||||||
}
|
|
||||||
|
|
||||||
private _updateNotes() {
|
|
||||||
const rootModel = this.doc.root;
|
|
||||||
|
|
||||||
if (this._dragging) return;
|
|
||||||
|
|
||||||
if (!rootModel) {
|
|
||||||
this._pageVisibleNotes = [];
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._pageVisibleNotes = getNotesFromDoc(this.doc, [
|
|
||||||
NoteDisplayMode.DocAndEdgeless,
|
NoteDisplayMode.DocAndEdgeless,
|
||||||
NoteDisplayMode.DocOnly,
|
NoteDisplayMode.DocOnly,
|
||||||
]);
|
]).filter(isRenderableNote);
|
||||||
this._edgelessOnlyNotes = getNotesFromDoc(this.doc, [
|
|
||||||
|
this._edgelessOnlyNoteItems$.value = getNotesFromDoc(this.doc, [
|
||||||
NoteDisplayMode.EdgelessOnly,
|
NoteDisplayMode.EdgelessOnly,
|
||||||
]);
|
]).filter(isRenderableNote);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _updateNoticeVisibility() {
|
private _updateNoticeVisibility() {
|
||||||
@@ -544,9 +461,8 @@ export class OutlinePanelBody extends SignalWatcher(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const shouldShowNotice = this._pageVisibleNotes.some(
|
const shouldShowNotice =
|
||||||
note => note.note.displayMode === NoteDisplayMode.DocOnly
|
getNotesFromDoc(this.doc, [NoteDisplayMode.DocOnly]).length > 0;
|
||||||
);
|
|
||||||
|
|
||||||
if (shouldShowNotice && !this.noticeVisible) {
|
if (shouldShowNotice && !this.noticeVisible) {
|
||||||
this.setNoticeVisibility(true);
|
this.setNoticeVisibility(true);
|
||||||
@@ -580,6 +496,8 @@ export class OutlinePanelBody extends SignalWatcher(
|
|||||||
|
|
||||||
override connectedCallback(): void {
|
override connectedCallback(): void {
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
|
this.classList.add(styles.outlinePanelBody);
|
||||||
|
|
||||||
this.disposables.add(
|
this.disposables.add(
|
||||||
observeActiveHeadingDuringScroll(
|
observeActiveHeadingDuringScroll(
|
||||||
() => this.editor,
|
() => this.editor,
|
||||||
@@ -603,54 +521,33 @@ export class OutlinePanelBody extends SignalWatcher(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override render() {
|
override render() {
|
||||||
const shouldRenderPageVisibleNotes = this._shouldRenderNoteList(
|
|
||||||
this._pageVisibleNotes
|
|
||||||
);
|
|
||||||
const shouldRenderEdgelessOnlyNotes =
|
|
||||||
this.renderEdgelessOnlyNotes &&
|
|
||||||
this._shouldRenderNoteList(this._edgelessOnlyNotes);
|
|
||||||
|
|
||||||
const shouldRenderEmptyPanel =
|
|
||||||
!shouldRenderPageVisibleNotes && !shouldRenderEdgelessOnlyNotes;
|
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<div class="outline-panel-body-container">
|
${this._renderDocTitle()}
|
||||||
${this._renderDocTitle()}
|
${when(
|
||||||
${shouldRenderEmptyPanel
|
this._shouldRenderEmptyPanel,
|
||||||
? this._EmptyPanel()
|
() => this._EmptyPanel(),
|
||||||
: this._PanelList(shouldRenderEdgelessOnlyNotes)}
|
() => html`
|
||||||
</div>
|
${this._renderPageVisibleCardList()}
|
||||||
|
${this._renderEdgelessOnlyCardList()}
|
||||||
|
`
|
||||||
|
)}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
override willUpdate(_changedProperties: PropertyValues) {
|
override willUpdate(_changedProperties: PropertyValues) {
|
||||||
if (_changedProperties.has('doc') || _changedProperties.has('edgeless')) {
|
if (_changedProperties.has('editor')) {
|
||||||
this._setDocDisposables();
|
this._setDocDisposables();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_changedProperties.has('edgeless')) {
|
if (_changedProperties.has('enableNotesSorting')) {
|
||||||
this._clearHighlightMask();
|
this._updateNoticeVisibility();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@state()
|
@query('.page-visible-card-list')
|
||||||
private accessor _dragging = false;
|
private accessor _pageVisibleList!: HTMLElement;
|
||||||
|
|
||||||
@state()
|
|
||||||
private accessor _edgelessOnlyNotes: OutlineNoteItem[] = [];
|
|
||||||
|
|
||||||
@state()
|
|
||||||
private accessor _pageVisibleNotes: OutlineNoteItem[] = [];
|
|
||||||
|
|
||||||
@property({ attribute: false })
|
|
||||||
accessor doc!: Store;
|
|
||||||
|
|
||||||
@property({ attribute: false })
|
|
||||||
accessor domHost!: Document | HTMLElement;
|
|
||||||
|
|
||||||
@property({ attribute: false })
|
|
||||||
accessor edgeless!: EdgelessRootBlockComponent | null;
|
|
||||||
|
|
||||||
|
@consume({ context: editorContext })
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor editor!: AffineEditorContainer;
|
accessor editor!: AffineEditorContainer;
|
||||||
|
|
||||||
@@ -666,12 +563,6 @@ export class OutlinePanelBody extends SignalWatcher(
|
|||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor noticeVisible!: boolean;
|
accessor noticeVisible!: boolean;
|
||||||
|
|
||||||
@query('.outline-panel-body-container')
|
|
||||||
accessor OutlinePanelContainer!: HTMLElement;
|
|
||||||
|
|
||||||
@query('.panel-list')
|
|
||||||
accessor panelListElement!: HTMLElement;
|
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor renderEdgelessOnlyNotes: boolean = true;
|
accessor renderEdgelessOnlyNotes: boolean = true;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,162 @@
|
|||||||
|
import { cssVar } from '@toeverything/theme';
|
||||||
|
import { cssVarV2 } from '@toeverything/theme/v2';
|
||||||
|
import { style } from '@vanilla-extract/css';
|
||||||
|
|
||||||
|
export const outlineCard = style({
|
||||||
|
position: 'relative',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
boxSizing: 'border-box',
|
||||||
|
|
||||||
|
selectors: {
|
||||||
|
'&[data-status="placeholder"]': {
|
||||||
|
pointerEvents: 'none',
|
||||||
|
opacity: 0.5,
|
||||||
|
},
|
||||||
|
'&[data-sortable="true"]': {
|
||||||
|
padding: '2px 0px',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const cardPreview = style({
|
||||||
|
position: 'relative',
|
||||||
|
width: '100%',
|
||||||
|
borderRadius: '4px',
|
||||||
|
cursor: 'default',
|
||||||
|
userSelect: 'none',
|
||||||
|
':hover': {
|
||||||
|
background: cssVarV2('layer/background/hoverOverlay'),
|
||||||
|
},
|
||||||
|
selectors: {
|
||||||
|
[`${outlineCard}[data-status="selected"] &`]: {
|
||||||
|
background: cssVarV2('layer/background/hoverOverlay'),
|
||||||
|
},
|
||||||
|
[`${outlineCard}[data-status="placeholder"] &`]: {
|
||||||
|
background: cssVarV2('layer/background/hoverOverlay'),
|
||||||
|
opacity: 0.9,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const cardHeader = style({
|
||||||
|
padding: '0 8px',
|
||||||
|
width: '100%',
|
||||||
|
minHeight: '28px',
|
||||||
|
display: 'none',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '8px',
|
||||||
|
boxSizing: 'border-box',
|
||||||
|
selectors: {
|
||||||
|
[`${outlineCard}[data-sortable="true"] &`]: {
|
||||||
|
display: 'flex',
|
||||||
|
},
|
||||||
|
[`${outlineCard}[data-invisible="false"] &:hover`]: {
|
||||||
|
cursor: 'grab',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const invisibleCard = style({
|
||||||
|
selectors: {
|
||||||
|
[`${outlineCard}[data-invisible="true"] &`]: {
|
||||||
|
color: cssVarV2('text/disable'),
|
||||||
|
pointerEvents: 'none',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const headerIcon = style([
|
||||||
|
{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
invisibleCard,
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const headerNumber = style([
|
||||||
|
{
|
||||||
|
textAlign: 'center',
|
||||||
|
fontSize: cssVar('fontSm'),
|
||||||
|
color: cssVar('brandColor'),
|
||||||
|
fontWeight: 500,
|
||||||
|
lineHeight: '14px',
|
||||||
|
},
|
||||||
|
invisibleCard,
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const divider = style({
|
||||||
|
height: '1px',
|
||||||
|
flex: 1,
|
||||||
|
borderTop: `1px dashed ${cssVar('borderColor')}`,
|
||||||
|
transform: 'translateY(50%)',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const displayModeButtonGroup = style({
|
||||||
|
display: 'none',
|
||||||
|
position: 'absolute',
|
||||||
|
right: '8px',
|
||||||
|
top: '-6px',
|
||||||
|
paddingTop: '8px',
|
||||||
|
paddingBottom: '8px',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '4px',
|
||||||
|
fontSize: '12px',
|
||||||
|
fontWeight: 500,
|
||||||
|
lineHeight: '20px',
|
||||||
|
|
||||||
|
selectors: {
|
||||||
|
[`${cardPreview}:hover &`]: {
|
||||||
|
display: 'flex',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const displayModeButton = style({
|
||||||
|
display: 'flex',
|
||||||
|
borderRadius: '4px',
|
||||||
|
backgroundColor: cssVar('hoverColor'),
|
||||||
|
alignItems: 'center',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const currentModeLabel = style({
|
||||||
|
display: 'flex',
|
||||||
|
padding: '2px 0px 2px 4px',
|
||||||
|
alignItems: 'center',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const cardContent = style([
|
||||||
|
{
|
||||||
|
fontFamily: cssVar('fontSansFamily'),
|
||||||
|
userSelect: 'none',
|
||||||
|
color: cssVarV2('text/primary'),
|
||||||
|
|
||||||
|
':hover': {
|
||||||
|
cursor: 'pointer',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
invisibleCard,
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const modeChangePanel = style({
|
||||||
|
position: 'absolute',
|
||||||
|
display: 'none',
|
||||||
|
background: cssVarV2('layer/background/overlayPanel'),
|
||||||
|
borderRadius: '8px',
|
||||||
|
boxShadow: cssVar('shadow2'),
|
||||||
|
boxSizing: 'border-box',
|
||||||
|
padding: '8px',
|
||||||
|
fontSize: cssVar('fontSm'),
|
||||||
|
color: cssVarV2('text/primary'),
|
||||||
|
lineHeight: '22px',
|
||||||
|
fontWeight: 400,
|
||||||
|
fontFamily: cssVar('fontSansFamily'),
|
||||||
|
|
||||||
|
selectors: {
|
||||||
|
'&[data-show]': {
|
||||||
|
display: 'flex',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
|
import { ShadowlessElement } from '@blocksuite/block-std';
|
||||||
import {
|
import {
|
||||||
type ColorScheme,
|
|
||||||
createButtonPopper,
|
createButtonPopper,
|
||||||
type NoteBlockModel,
|
type NoteBlockModel,
|
||||||
NoteDisplayMode,
|
NoteDisplayMode,
|
||||||
@@ -7,189 +7,19 @@ import {
|
|||||||
once,
|
once,
|
||||||
} from '@blocksuite/blocks';
|
} from '@blocksuite/blocks';
|
||||||
import { SignalWatcher, WithDisposable } from '@blocksuite/global/utils';
|
import { SignalWatcher, WithDisposable } from '@blocksuite/global/utils';
|
||||||
import type { BlockModel, Store } from '@blocksuite/store';
|
import type { BlockModel } from '@blocksuite/store';
|
||||||
import { baseTheme } from '@toeverything/theme';
|
import { html } from 'lit';
|
||||||
import { css, html, LitElement, unsafeCSS } from 'lit';
|
|
||||||
import { property, query, state } from 'lit/decorators.js';
|
import { property, query, state } from 'lit/decorators.js';
|
||||||
import { classMap } from 'lit/directives/class-map.js';
|
|
||||||
|
|
||||||
import { HiddenIcon, SmallArrowDownIcon } from '../../_common/icons.js';
|
import { HiddenIcon, SmallArrowDownIcon } from '../../_common/icons';
|
||||||
import type { SelectEvent } from '../utils/custom-events.js';
|
import type { SelectEvent } from '../utils/custom-events';
|
||||||
|
import * as styles from './outline-card.css';
|
||||||
const styles = css`
|
|
||||||
:host {
|
|
||||||
display: block;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-container {
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-preview {
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
border-radius: 4px;
|
|
||||||
|
|
||||||
cursor: default;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-preview:hover {
|
|
||||||
background: var(--affine-hover-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-header-container {
|
|
||||||
padding: 0 8px;
|
|
||||||
width: 100%;
|
|
||||||
min-height: 28px;
|
|
||||||
display: none;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-header-container.enable-sorting {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-header-container .card-number {
|
|
||||||
text-align: center;
|
|
||||||
font-size: var(--affine-font-sm);
|
|
||||||
font-family: ${unsafeCSS(baseTheme.fontSansFamily)};
|
|
||||||
color: var(--affine-brand-color, #1e96eb);
|
|
||||||
font-weight: 500;
|
|
||||||
line-height: 14px;
|
|
||||||
line-height: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-header-container .card-header-icon {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-header-container .card-divider {
|
|
||||||
height: 1px;
|
|
||||||
flex: 1;
|
|
||||||
border-top: 1px dashed var(--affine-border-color);
|
|
||||||
transform: translateY(50%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.display-mode-button-group {
|
|
||||||
display: none;
|
|
||||||
position: absolute;
|
|
||||||
right: 8px;
|
|
||||||
top: -6px;
|
|
||||||
padding-top: 8px;
|
|
||||||
padding-bottom: 8px;
|
|
||||||
align-items: center;
|
|
||||||
gap: 4px;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 500;
|
|
||||||
line-height: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-preview:hover .display-mode-button-group {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.display-mode-button-label {
|
|
||||||
color: var(--affine-text-primary-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.display-mode-button {
|
|
||||||
display: flex;
|
|
||||||
border-radius: 4px;
|
|
||||||
background-color: var(--affine-hover-color);
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.current-mode-label {
|
|
||||||
display: flex;
|
|
||||||
padding: 2px 0px 2px 4px;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
note-display-mode-panel {
|
|
||||||
position: absolute;
|
|
||||||
display: none;
|
|
||||||
background: var(--affine-background-overlay-panel-color);
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: var(--affine-shadow-2);
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding: 8px;
|
|
||||||
font-size: var(--affine-font-sm);
|
|
||||||
color: var(--affine-text-primary-color);
|
|
||||||
line-height: 22px;
|
|
||||||
font-weight: 400;
|
|
||||||
font-family: ${unsafeCSS(baseTheme.fontSansFamily)};
|
|
||||||
}
|
|
||||||
|
|
||||||
note-display-mode-panel[data-show] {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-content {
|
|
||||||
font-family: ${unsafeCSS(baseTheme.fontSansFamily)};
|
|
||||||
user-select: none;
|
|
||||||
color: var(--affine-text-primary-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-preview .card-content:hover {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-container[data-invisible='false']
|
|
||||||
.card-preview
|
|
||||||
.card-header-container:hover {
|
|
||||||
cursor: grab;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-container.placeholder {
|
|
||||||
pointer-events: none;
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-container.selected .card-preview {
|
|
||||||
background: var(--affine-hover-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-container.placeholder .card-preview {
|
|
||||||
background: var(--affine-hover-color);
|
|
||||||
opacity: 0.9;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-container[data-sortable='true'] {
|
|
||||||
padding: 2px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-container[data-invisible='true'] .card-header-container .card-number,
|
|
||||||
.card-container[data-invisible='true']
|
|
||||||
.card-header-container
|
|
||||||
.card-header-icon,
|
|
||||||
.card-container[data-invisible='true'] .card-preview .card-content {
|
|
||||||
color: var(--affine-text-disable-color);
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-preview outline-block-preview:hover {
|
|
||||||
color: var(--affine-brand-color);
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const AFFINE_OUTLINE_NOTE_CARD = 'affine-outline-note-card';
|
export const AFFINE_OUTLINE_NOTE_CARD = 'affine-outline-note-card';
|
||||||
|
|
||||||
export class OutlineNoteCard extends SignalWatcher(WithDisposable(LitElement)) {
|
export class OutlineNoteCard extends SignalWatcher(
|
||||||
static override styles = styles;
|
WithDisposable(ShadowlessElement)
|
||||||
|
) {
|
||||||
private _displayModePopper: ReturnType<typeof createButtonPopper> | null =
|
private _displayModePopper: ReturnType<typeof createButtonPopper> | null =
|
||||||
null;
|
null;
|
||||||
|
|
||||||
@@ -283,6 +113,10 @@ export class OutlineNoteCard extends SignalWatcher(WithDisposable(LitElement)) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get invisible() {
|
||||||
|
return this.note.displayMode === NoteDisplayMode.EdgelessOnly;
|
||||||
|
}
|
||||||
|
|
||||||
override updated() {
|
override updated() {
|
||||||
this._displayModePopper = createButtonPopper(
|
this._displayModePopper = createButtonPopper(
|
||||||
this._displayModeButtonGroup,
|
this._displayModeButtonGroup,
|
||||||
@@ -302,50 +136,49 @@ export class OutlineNoteCard extends SignalWatcher(WithDisposable(LitElement)) {
|
|||||||
override render() {
|
override render() {
|
||||||
const { children, displayMode } = this.note;
|
const { children, displayMode } = this.note;
|
||||||
const currentMode = this._getCurrentModeLabel(displayMode);
|
const currentMode = this._getCurrentModeLabel(displayMode);
|
||||||
const cardHeaderClasses = classMap({
|
|
||||||
'card-header-container': true,
|
|
||||||
'enable-sorting': this.enableNotesSorting,
|
|
||||||
});
|
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<div
|
<div
|
||||||
data-invisible="${this.invisible ? 'true' : 'false'}"
|
data-invisible=${this.invisible}
|
||||||
data-sortable="${this.enableNotesSorting ? 'true' : 'false'}"
|
data-sortable=${this.enableNotesSorting}
|
||||||
class="card-container ${this.status ?? ''} ${this.theme}"
|
data-status=${this.status}
|
||||||
|
class=${styles.outlineCard}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="card-preview"
|
class=${styles.cardPreview}
|
||||||
@mousedown=${this._dispatchDragEvent}
|
@mousedown=${this._dispatchDragEvent}
|
||||||
@click=${this._dispatchSelectEvent}
|
@click=${this._dispatchSelectEvent}
|
||||||
@dblclick=${this._dispatchFitViewEvent}
|
@dblclick=${this._dispatchFitViewEvent}
|
||||||
>
|
>
|
||||||
${html`<div class=${cardHeaderClasses}>
|
${html`<div class=${styles.cardHeader}>
|
||||||
${
|
${
|
||||||
this.invisible
|
this.invisible
|
||||||
? html`<span class="card-header-icon">${HiddenIcon}</span>`
|
? html`<span class=${styles.headerIcon}>${HiddenIcon}</span>`
|
||||||
: html`<span class="card-number">${this.number}</span>`
|
: html`<span class=${styles.headerNumber}>${this.number}</span>`
|
||||||
}
|
}
|
||||||
<span class="card-divider"></span>
|
<span class=${styles.divider}></span>
|
||||||
<div class="display-mode-button-group">
|
<div class=${styles.displayModeButtonGroup}>
|
||||||
<span class="display-mode-button-label">Show in</span>
|
<span>Show in</span>
|
||||||
<edgeless-tool-icon-button
|
<edgeless-tool-icon-button
|
||||||
.tooltip=${this._showPopper ? '' : 'Display Mode'}
|
.tooltip=${this._showPopper ? '' : 'Display Mode'}
|
||||||
.tipPosition=${'left-start'}
|
.tipPosition=${'left-start'}
|
||||||
.iconContainerPadding=${0}
|
.iconContainerPadding=${0}
|
||||||
|
data-testid="display-mode-button"
|
||||||
@click=${(e: MouseEvent) => {
|
@click=${(e: MouseEvent) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
this._displayModePopper?.toggle();
|
this._displayModePopper?.toggle();
|
||||||
}}
|
}}
|
||||||
@dblclick=${(e: MouseEvent) => e.stopPropagation()}
|
@dblclick=${(e: MouseEvent) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
<div class="display-mode-button">
|
<div class=${styles.displayModeButton}>
|
||||||
<span class="current-mode-label">${currentMode}</span>
|
<span class=${styles.currentModeLabel}>${currentMode}</span>
|
||||||
${SmallArrowDownIcon}
|
${SmallArrowDownIcon}
|
||||||
</div>
|
</div>
|
||||||
</edgeless-tool-icon-button>
|
</edgeless-tool-icon-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<note-display-mode-panel
|
<note-display-mode-panel
|
||||||
|
class=${styles.modeChangePanel}
|
||||||
.displayMode=${displayMode}
|
.displayMode=${displayMode}
|
||||||
.panelWidth=${220}
|
.panelWidth=${220}
|
||||||
.onSelect=${(newMode: NoteDisplayMode) => {
|
.onSelect=${(newMode: NoteDisplayMode) => {
|
||||||
@@ -355,7 +188,7 @@ export class OutlineNoteCard extends SignalWatcher(WithDisposable(LitElement)) {
|
|||||||
>
|
>
|
||||||
</note-display-mode-panel>
|
</note-display-mode-panel>
|
||||||
</div>`}
|
</div>`}
|
||||||
<div class="card-content">
|
<div class=${styles.cardContent}>
|
||||||
${children.map(block => {
|
${children.map(block => {
|
||||||
return html`<affine-outline-block-preview
|
return html`<affine-outline-block-preview
|
||||||
.block=${block}
|
.block=${block}
|
||||||
@@ -377,7 +210,7 @@ export class OutlineNoteCard extends SignalWatcher(WithDisposable(LitElement)) {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@query('.display-mode-button-group')
|
@query(`.${styles.displayModeButtonGroup}`)
|
||||||
private accessor _displayModeButtonGroup!: HTMLDivElement;
|
private accessor _displayModeButtonGroup!: HTMLDivElement;
|
||||||
|
|
||||||
@query('note-display-mode-panel')
|
@query('note-display-mode-panel')
|
||||||
@@ -389,18 +222,12 @@ export class OutlineNoteCard extends SignalWatcher(WithDisposable(LitElement)) {
|
|||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor activeHeadingId: string | null = null;
|
accessor activeHeadingId: string | null = null;
|
||||||
|
|
||||||
@property({ attribute: false })
|
|
||||||
accessor doc!: Store;
|
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor enableNotesSorting!: boolean;
|
accessor enableNotesSorting!: boolean;
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor index!: number;
|
accessor index!: number;
|
||||||
|
|
||||||
@property({ attribute: false })
|
|
||||||
accessor invisible = false;
|
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor note!: NoteBlockModel;
|
accessor note!: NoteBlockModel;
|
||||||
|
|
||||||
@@ -411,10 +238,7 @@ export class OutlineNoteCard extends SignalWatcher(WithDisposable(LitElement)) {
|
|||||||
accessor showPreviewIcon!: boolean;
|
accessor showPreviewIcon!: boolean;
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor status: 'selected' | 'placeholder' | undefined = undefined;
|
accessor status: 'selected' | 'placeholder' | 'normal' = 'normal';
|
||||||
|
|
||||||
@property({ attribute: false })
|
|
||||||
accessor theme!: ColorScheme;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
|||||||
@@ -0,0 +1,114 @@
|
|||||||
|
import { cssVar } from '@toeverything/theme';
|
||||||
|
import { cssVarV2 } from '@toeverything/theme/v2';
|
||||||
|
import { style } from '@vanilla-extract/css';
|
||||||
|
|
||||||
|
export const outlineBlockPreview = style({
|
||||||
|
fontFamily: cssVar('fontFamily'),
|
||||||
|
width: '100%',
|
||||||
|
boxSizing: 'border-box',
|
||||||
|
padding: '6px 8px',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'start',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '8px',
|
||||||
|
|
||||||
|
':hover': {
|
||||||
|
cursor: 'pointer',
|
||||||
|
background: cssVarV2('layer/background/hoverOverlay'),
|
||||||
|
},
|
||||||
|
|
||||||
|
selectors: {
|
||||||
|
'.active > &': {
|
||||||
|
color: cssVarV2('text/emphasis'),
|
||||||
|
},
|
||||||
|
'&:not(:has(span))': {
|
||||||
|
display: 'none',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const icon = style({
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
width: '22px',
|
||||||
|
height: '22px',
|
||||||
|
boxSizing: 'border-box',
|
||||||
|
padding: '4px',
|
||||||
|
background: cssVarV2('layer/background/secondary'),
|
||||||
|
borderRadius: '4px',
|
||||||
|
color: cssVarV2('icon/primary'),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const iconDisabled = style({
|
||||||
|
color: cssVarV2('icon/disable'),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const text = style({
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
flex: 1,
|
||||||
|
fontSize: cssVar('fontSm'),
|
||||||
|
lineHeight: '22px',
|
||||||
|
height: '22px',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const textGeneral = style({
|
||||||
|
fontWeight: 400,
|
||||||
|
paddingLeft: '28px',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const subtypeStyles = {
|
||||||
|
title: style({
|
||||||
|
fontWeight: 600,
|
||||||
|
paddingLeft: '0',
|
||||||
|
}),
|
||||||
|
h1: style({
|
||||||
|
fontWeight: 600,
|
||||||
|
paddingLeft: '0',
|
||||||
|
}),
|
||||||
|
h2: style({
|
||||||
|
fontWeight: 600,
|
||||||
|
paddingLeft: '4px',
|
||||||
|
}),
|
||||||
|
h3: style({
|
||||||
|
fontWeight: 600,
|
||||||
|
paddingLeft: '12px',
|
||||||
|
}),
|
||||||
|
h4: style({
|
||||||
|
fontWeight: 600,
|
||||||
|
paddingLeft: '16px',
|
||||||
|
}),
|
||||||
|
h5: style({
|
||||||
|
fontWeight: 600,
|
||||||
|
paddingLeft: '20px',
|
||||||
|
}),
|
||||||
|
h6: style({
|
||||||
|
fontWeight: 600,
|
||||||
|
paddingLeft: '24px',
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const textSpan = style({
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const linkedDocText = style({
|
||||||
|
fontSize: 'inherit',
|
||||||
|
borderBottom: `0.5px solid ${cssVar('dividerColor')}`,
|
||||||
|
whiteSpace: 'break-spaces',
|
||||||
|
marginRight: '2px',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const linkedDocPreviewUnavailable = style({
|
||||||
|
color: cssVarV2('text/disable'),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const linkedDocTextUnavailable = style({
|
||||||
|
color: cssVarV2('text/disable'),
|
||||||
|
textDecoration: 'line-through',
|
||||||
|
});
|
||||||
@@ -1,22 +1,28 @@
|
|||||||
import type { AffineTextAttributes } from '@blocksuite/affine-shared/types';
|
import type { AffineTextAttributes } from '@blocksuite/affine-shared/types';
|
||||||
import type {
|
import { ShadowlessElement } from '@blocksuite/block-std';
|
||||||
AttachmentBlockModel,
|
import {
|
||||||
BookmarkBlockModel,
|
type AttachmentBlockModel,
|
||||||
CodeBlockModel,
|
type BookmarkBlockModel,
|
||||||
DatabaseBlockModel,
|
type CodeBlockModel,
|
||||||
ImageBlockModel,
|
type DatabaseBlockModel,
|
||||||
ListBlockModel,
|
DocDisplayMetaProvider,
|
||||||
ParagraphBlockModel,
|
type ImageBlockModel,
|
||||||
RootBlockModel,
|
type ListBlockModel,
|
||||||
|
type ParagraphBlockModel,
|
||||||
|
type RootBlockModel,
|
||||||
} from '@blocksuite/blocks';
|
} from '@blocksuite/blocks';
|
||||||
import { noop, SignalWatcher, WithDisposable } from '@blocksuite/global/utils';
|
import { noop, SignalWatcher, WithDisposable } from '@blocksuite/global/utils';
|
||||||
|
import { LinkedPageIcon } from '@blocksuite/icons/lit';
|
||||||
import type { DeltaInsert } from '@blocksuite/inline';
|
import type { DeltaInsert } from '@blocksuite/inline';
|
||||||
import { css, html, LitElement, nothing } from 'lit';
|
import { consume } from '@lit/context';
|
||||||
|
import { html, nothing } from 'lit';
|
||||||
import { property } from 'lit/decorators.js';
|
import { property } from 'lit/decorators.js';
|
||||||
|
import { classMap } from 'lit/directives/class-map.js';
|
||||||
|
|
||||||
import { SmallLinkedDocIcon } from '../../_common/icons.js';
|
import type { AffineEditorContainer } from '../../../editors/editor-container.js';
|
||||||
import { placeholderMap, previewIconMap } from '../config.js';
|
import { editorContext, placeholderMap, previewIconMap } from '../config.js';
|
||||||
import { isHeadingBlock, isRootBlock } from '../utils/query.js';
|
import { isHeadingBlock, isRootBlock } from '../utils/query.js';
|
||||||
|
import * as styles from './outline-preview.css';
|
||||||
|
|
||||||
type ValuesOf<T, K extends keyof T = keyof T> = T[K];
|
type ValuesOf<T, K extends keyof T = keyof T> = T[K];
|
||||||
|
|
||||||
@@ -24,147 +30,16 @@ function assertType<T>(value: unknown): asserts value is T {
|
|||||||
noop(value);
|
noop(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = css`
|
|
||||||
:host {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
font-family: var(--affine-font-family);
|
|
||||||
}
|
|
||||||
|
|
||||||
:host(:hover) {
|
|
||||||
cursor: pointer;
|
|
||||||
background: var(--affine-hover-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
:host(.active) {
|
|
||||||
color: var(--affine-text-emphasis-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.outline-block-preview {
|
|
||||||
width: 100%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding: 6px 8px;
|
|
||||||
white-space: nowrap;
|
|
||||||
display: flex;
|
|
||||||
justify-content: start;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 22px;
|
|
||||||
height: 22px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding: 4px;
|
|
||||||
background: var(--affine-background-secondary-color);
|
|
||||||
border-radius: 4px;
|
|
||||||
color: var(--affine-icon-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon.disabled {
|
|
||||||
color: var(--affine-disabled-icon-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.text {
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
flex: 1;
|
|
||||||
|
|
||||||
font-size: var(--affine-font-sm);
|
|
||||||
line-height: 22px;
|
|
||||||
height: 22px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text.general,
|
|
||||||
.subtype.text,
|
|
||||||
.subtype.quote {
|
|
||||||
font-weight: 400;
|
|
||||||
padding-left: 28px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.subtype.title,
|
|
||||||
.subtype.h1,
|
|
||||||
.subtype.h2,
|
|
||||||
.subtype.h3,
|
|
||||||
.subtype.h4,
|
|
||||||
.subtype.h5,
|
|
||||||
.subtype.h6 {
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.subtype.title {
|
|
||||||
padding-left: 0;
|
|
||||||
}
|
|
||||||
.subtype.h1 {
|
|
||||||
padding-left: 0;
|
|
||||||
}
|
|
||||||
.subtype.h2 {
|
|
||||||
padding-left: 4px;
|
|
||||||
}
|
|
||||||
.subtype.h3 {
|
|
||||||
padding-left: 12px;
|
|
||||||
}
|
|
||||||
.subtype.h4 {
|
|
||||||
padding-left: 16px;
|
|
||||||
}
|
|
||||||
.subtype.h5 {
|
|
||||||
padding-left: 20px;
|
|
||||||
}
|
|
||||||
.subtype.h6 {
|
|
||||||
padding-left: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.outline-block-preview:not(:has(span)) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text span {
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
|
|
||||||
.linked-doc-preview svg {
|
|
||||||
width: 1.1em;
|
|
||||||
height: 1.1em;
|
|
||||||
vertical-align: middle;
|
|
||||||
font-size: inherit;
|
|
||||||
margin-bottom: 0.1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.linked-doc-text {
|
|
||||||
font-size: inherit;
|
|
||||||
border-bottom: 0.5px solid var(--affine-divider-color);
|
|
||||||
white-space: break-spaces;
|
|
||||||
margin-right: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.linked-doc-preview.unavailable svg {
|
|
||||||
color: var(--affine-text-disable-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.linked-doc-preview.unavailable .linked-doc-text {
|
|
||||||
color: var(--affine-text-disable-color);
|
|
||||||
text-decoration: line-through;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const AFFINE_OUTLINE_BLOCK_PREVIEW = 'affine-outline-block-preview';
|
export const AFFINE_OUTLINE_BLOCK_PREVIEW = 'affine-outline-block-preview';
|
||||||
|
|
||||||
export class OutlineBlockPreview extends SignalWatcher(
|
export class OutlineBlockPreview extends SignalWatcher(
|
||||||
WithDisposable(LitElement)
|
WithDisposable(ShadowlessElement)
|
||||||
) {
|
) {
|
||||||
static override styles = styles;
|
|
||||||
|
|
||||||
private _TextBlockPreview(block: ParagraphBlockModel | ListBlockModel) {
|
private _TextBlockPreview(block: ParagraphBlockModel | ListBlockModel) {
|
||||||
const deltas: DeltaInsert<AffineTextAttributes>[] =
|
const deltas: DeltaInsert<AffineTextAttributes>[] =
|
||||||
block.text.yText.toDelta();
|
block.text.yText.toDelta();
|
||||||
if (!block.text.length) return nothing;
|
if (!block.text.length) return nothing;
|
||||||
const iconClass = this.disabledIcon ? 'icon disabled' : 'icon';
|
const iconClass = this.disabledIcon ? styles.iconDisabled : styles.icon;
|
||||||
|
|
||||||
const previewText = deltas.map(delta => {
|
const previewText = deltas.map(delta => {
|
||||||
if (delta.attributes?.reference) {
|
if (delta.attributes?.reference) {
|
||||||
@@ -174,37 +49,65 @@ export class OutlineBlockPreview extends SignalWatcher(
|
|||||||
doc => doc.id === refAttribute.pageId
|
doc => doc.id === refAttribute.pageId
|
||||||
);
|
);
|
||||||
const unavailable = !refMeta;
|
const unavailable = !refMeta;
|
||||||
const title = unavailable ? 'Deleted doc' : refMeta.title;
|
const docDisplayMetaService = this.editor.std.get(
|
||||||
|
DocDisplayMetaProvider
|
||||||
|
);
|
||||||
|
|
||||||
|
const icon = unavailable
|
||||||
|
? LinkedPageIcon({ width: '1.1em', height: '1.1em' })
|
||||||
|
: docDisplayMetaService.icon(refMeta.id).value;
|
||||||
|
const title = unavailable
|
||||||
|
? 'Deleted doc'
|
||||||
|
: docDisplayMetaService.title(refMeta.id).value;
|
||||||
|
|
||||||
return html`<span
|
return html`<span
|
||||||
class="linked-doc-preview ${unavailable ? 'unavailable' : ''}"
|
class=${classMap({
|
||||||
>${SmallLinkedDocIcon}
|
[styles.linkedDocPreviewUnavailable]: unavailable,
|
||||||
<span class="linked-doc-text"
|
})}
|
||||||
|
>
|
||||||
|
${icon}
|
||||||
|
<span
|
||||||
|
class=${classMap({
|
||||||
|
[styles.linkedDocText]: true,
|
||||||
|
[styles.linkedDocTextUnavailable]: unavailable,
|
||||||
|
})}
|
||||||
>${title.length ? title : 'Untitled'}</span
|
>${title.length ? title : 'Untitled'}</span
|
||||||
></span
|
></span
|
||||||
>`;
|
>`;
|
||||||
} else {
|
} else {
|
||||||
// If not linked doc, render the text.
|
// If not linked doc, render the text.
|
||||||
return delta.insert.toString().trim().length > 0
|
return delta.insert.toString().trim().length > 0
|
||||||
? html`<span>${delta.insert.toString()}</span>`
|
? html`<span class=${styles.textSpan}
|
||||||
|
>${delta.insert.toString()}</span
|
||||||
|
>`
|
||||||
: nothing;
|
: nothing;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return html`<span class="text subtype ${block.type}">${previewText}</span>
|
const headingClass =
|
||||||
|
block.type in styles.subtypeStyles
|
||||||
|
? styles.subtypeStyles[block.type as keyof typeof styles.subtypeStyles]
|
||||||
|
: '';
|
||||||
|
|
||||||
|
return html`<span
|
||||||
|
data-testid="outline-block-preview-${block.type}"
|
||||||
|
class="${styles.text} ${styles.textGeneral} ${headingClass}"
|
||||||
|
>${previewText}</span
|
||||||
|
>
|
||||||
${this.showPreviewIcon
|
${this.showPreviewIcon
|
||||||
? html`<span class=${iconClass}>${previewIconMap[block.type]}</span>`
|
? html`<span class=${iconClass}>${previewIconMap[block.type]}</span>`
|
||||||
: nothing}`;
|
: nothing}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
override render() {
|
override render() {
|
||||||
return html`<div class="outline-block-preview">
|
return html`<div class=${styles.outlineBlockPreview}>
|
||||||
${this.renderBlockByFlavour()}
|
${this.renderBlockByFlavour()}
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderBlockByFlavour() {
|
renderBlockByFlavour() {
|
||||||
const { block } = this;
|
const { block } = this;
|
||||||
const iconClass = this.disabledIcon ? 'icon disabled' : 'icon';
|
const iconClass = this.disabledIcon ? styles.iconDisabled : styles.icon;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!this.enableNotesSorting &&
|
!this.enableNotesSorting &&
|
||||||
@@ -217,7 +120,10 @@ export class OutlineBlockPreview extends SignalWatcher(
|
|||||||
case 'affine:page':
|
case 'affine:page':
|
||||||
assertType<RootBlockModel>(block);
|
assertType<RootBlockModel>(block);
|
||||||
return block.title.length > 0
|
return block.title.length > 0
|
||||||
? html`<span class="text subtype title">
|
? html`<span
|
||||||
|
data-testid="outline-block-preview-title"
|
||||||
|
class="${styles.text} ${styles.subtypeStyles.title}"
|
||||||
|
>
|
||||||
${block.title$.value}
|
${block.title$.value}
|
||||||
</span>`
|
</span>`
|
||||||
: nothing;
|
: nothing;
|
||||||
@@ -230,7 +136,7 @@ export class OutlineBlockPreview extends SignalWatcher(
|
|||||||
case 'affine:bookmark':
|
case 'affine:bookmark':
|
||||||
assertType<BookmarkBlockModel>(block);
|
assertType<BookmarkBlockModel>(block);
|
||||||
return html`
|
return html`
|
||||||
<span class="text general"
|
<span class="${styles.text} ${styles.textGeneral}"
|
||||||
>${block.title || block.url || placeholderMap['bookmark']}</span
|
>${block.title || block.url || placeholderMap['bookmark']}</span
|
||||||
>
|
>
|
||||||
${this.showPreviewIcon
|
${this.showPreviewIcon
|
||||||
@@ -242,7 +148,7 @@ export class OutlineBlockPreview extends SignalWatcher(
|
|||||||
case 'affine:code':
|
case 'affine:code':
|
||||||
assertType<CodeBlockModel>(block);
|
assertType<CodeBlockModel>(block);
|
||||||
return html`
|
return html`
|
||||||
<span class="text general"
|
<span class="${styles.text} ${styles.textGeneral}"
|
||||||
>${block.language ?? placeholderMap['code']}</span
|
>${block.language ?? placeholderMap['code']}</span
|
||||||
>
|
>
|
||||||
${this.showPreviewIcon
|
${this.showPreviewIcon
|
||||||
@@ -252,7 +158,7 @@ export class OutlineBlockPreview extends SignalWatcher(
|
|||||||
case 'affine:database':
|
case 'affine:database':
|
||||||
assertType<DatabaseBlockModel>(block);
|
assertType<DatabaseBlockModel>(block);
|
||||||
return html`
|
return html`
|
||||||
<span class="text general"
|
<span class="${styles.text} ${styles.textGeneral}"
|
||||||
>${block.title.toString().length
|
>${block.title.toString().length
|
||||||
? block.title.toString()
|
? block.title.toString()
|
||||||
: placeholderMap['database']}</span
|
: placeholderMap['database']}</span
|
||||||
@@ -264,7 +170,7 @@ export class OutlineBlockPreview extends SignalWatcher(
|
|||||||
case 'affine:image':
|
case 'affine:image':
|
||||||
assertType<ImageBlockModel>(block);
|
assertType<ImageBlockModel>(block);
|
||||||
return html`
|
return html`
|
||||||
<span class="text general"
|
<span class="${styles.text} ${styles.textGeneral}"
|
||||||
>${block.caption?.length
|
>${block.caption?.length
|
||||||
? block.caption
|
? block.caption
|
||||||
: placeholderMap['image']}</span
|
: placeholderMap['image']}</span
|
||||||
@@ -276,7 +182,7 @@ export class OutlineBlockPreview extends SignalWatcher(
|
|||||||
case 'affine:attachment':
|
case 'affine:attachment':
|
||||||
assertType<AttachmentBlockModel>(block);
|
assertType<AttachmentBlockModel>(block);
|
||||||
return html`
|
return html`
|
||||||
<span class="text general"
|
<span class="${styles.text} ${styles.textGeneral}"
|
||||||
>${block.name?.length
|
>${block.name?.length
|
||||||
? block.name
|
? block.name
|
||||||
: placeholderMap['attachment']}</span
|
: placeholderMap['attachment']}</span
|
||||||
@@ -292,6 +198,10 @@ export class OutlineBlockPreview extends SignalWatcher(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@consume({ context: editorContext })
|
||||||
|
@property({ attribute: false })
|
||||||
|
accessor editor!: AffineEditorContainer;
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor block!: ValuesOf<BlockSuite.BlockModels>;
|
accessor block!: ValuesOf<BlockSuite.BlockModels>;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import type { ParagraphBlockModel } from '@blocksuite/blocks';
|
import type { ParagraphBlockModel } from '@blocksuite/blocks';
|
||||||
|
import { createContext } from '@lit/context';
|
||||||
import type { TemplateResult } from 'lit';
|
import type { TemplateResult } from 'lit';
|
||||||
|
|
||||||
|
import type { AffineEditorContainer } from '../../editors/editor-container.js';
|
||||||
import {
|
import {
|
||||||
BlockPreviewIcon,
|
BlockPreviewIcon,
|
||||||
SmallAttachmentIcon,
|
SmallAttachmentIcon,
|
||||||
@@ -84,3 +86,6 @@ export type OutlineSettingsDataType = {
|
|||||||
showIcons: boolean;
|
showIcons: boolean;
|
||||||
enableSorting: boolean;
|
enableSorting: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const editorContext =
|
||||||
|
createContext<AffineEditorContainer>('editorContext');
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import { cssVar } from '@toeverything/theme';
|
||||||
|
import { cssVarV2 } from '@toeverything/theme/v2';
|
||||||
|
import { style } from '@vanilla-extract/css';
|
||||||
|
|
||||||
|
export const host = style({});
|
||||||
|
|
||||||
|
export const container = style({
|
||||||
|
display: 'flex',
|
||||||
|
width: '100%',
|
||||||
|
height: '40px',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
boxSizing: 'border-box',
|
||||||
|
padding: '8px 16px',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const noteSettingContainer = style({
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '8px',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const label = style({
|
||||||
|
width: '119px',
|
||||||
|
height: '22px',
|
||||||
|
fontSize: '14px',
|
||||||
|
fontWeight: 500,
|
||||||
|
lineHeight: '22px',
|
||||||
|
color: cssVarV2('text/secondary'),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const notePreviewSettingContainer = style({
|
||||||
|
display: 'none',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
background: cssVarV2('layer/background/overlayPanel'),
|
||||||
|
boxShadow: cssVar('shadow2'),
|
||||||
|
borderRadius: '8px',
|
||||||
|
selectors: {
|
||||||
|
'&[data-show]': {
|
||||||
|
display: 'flex',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -1,84 +1,15 @@
|
|||||||
|
import { ShadowlessElement } from '@blocksuite/block-std';
|
||||||
import { createButtonPopper } from '@blocksuite/blocks';
|
import { createButtonPopper } from '@blocksuite/blocks';
|
||||||
import { WithDisposable } from '@blocksuite/global/utils';
|
import { WithDisposable } from '@blocksuite/global/utils';
|
||||||
import { css, html, LitElement } from 'lit';
|
import { html } from 'lit';
|
||||||
import { property, query, state } from 'lit/decorators.js';
|
import { property, query, state } from 'lit/decorators.js';
|
||||||
|
|
||||||
import { SettingsIcon, SortingIcon } from '../../_common/icons.js';
|
import { SettingsIcon, SortingIcon } from '../../_common/icons.js';
|
||||||
|
import * as styles from './outline-panel-header.css';
|
||||||
const styles = css`
|
|
||||||
:host {
|
|
||||||
display: flex;
|
|
||||||
width: 100%;
|
|
||||||
height: 40px;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding: 8px 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.outline-panel-header-container {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding-right: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.note-setting-container {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.outline-panel-header-label {
|
|
||||||
width: 119px;
|
|
||||||
height: 22px;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
line-height: 22px;
|
|
||||||
color: var(--affine-text-secondary-color, #8e8d91);
|
|
||||||
}
|
|
||||||
|
|
||||||
.note-sorting-button {
|
|
||||||
justify-self: end;
|
|
||||||
}
|
|
||||||
|
|
||||||
.note-setting-button svg,
|
|
||||||
.note-sorting-button svg {
|
|
||||||
color: var(--affine-icon-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.note-setting-button:hover svg,
|
|
||||||
.note-setting-button.active svg,
|
|
||||||
.note-sorting-button:hover svg {
|
|
||||||
color: var(--affine-icon-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.note-sorting-button.active svg {
|
|
||||||
color: var(--affine-primary-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.note-preview-setting-container {
|
|
||||||
display: none;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
background: var(--affine-background-overlay-panel-color);
|
|
||||||
box-shadow: var(--affine-shadow-2);
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.note-preview-setting-container[data-show] {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const AFFINE_OUTLINE_PANEL_HEADER = 'affine-outline-panel-header';
|
export const AFFINE_OUTLINE_PANEL_HEADER = 'affine-outline-panel-header';
|
||||||
|
|
||||||
export class OutlinePanelHeader extends WithDisposable(LitElement) {
|
export class OutlinePanelHeader extends WithDisposable(ShadowlessElement) {
|
||||||
static override styles = styles;
|
|
||||||
|
|
||||||
private _notePreviewSettingMenuPopper: ReturnType<
|
private _notePreviewSettingMenuPopper: ReturnType<
|
||||||
typeof createButtonPopper
|
typeof createButtonPopper
|
||||||
> | null = null;
|
> | null = null;
|
||||||
@@ -101,13 +32,11 @@ export class OutlinePanelHeader extends WithDisposable(LitElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override render() {
|
override render() {
|
||||||
return html`<div class="outline-panel-header-container">
|
return html`<div class=${styles.container}>
|
||||||
<div class="note-setting-container">
|
<div class=${styles.noteSettingContainer}>
|
||||||
<span class="outline-panel-header-label">Table of Contents</span>
|
<span class=${styles.label}>Table of Contents</span>
|
||||||
<edgeless-tool-icon-button
|
<edgeless-tool-icon-button
|
||||||
class="note-setting-button ${this._settingPopperShow
|
class="${this._settingPopperShow ? 'active' : ''}"
|
||||||
? 'active'
|
|
||||||
: ''}"
|
|
||||||
.tooltip=${this._settingPopperShow ? '' : 'Preview Settings'}
|
.tooltip=${this._settingPopperShow ? '' : 'Preview Settings'}
|
||||||
.tipPosition=${'bottom'}
|
.tipPosition=${'bottom'}
|
||||||
.active=${this._settingPopperShow}
|
.active=${this._settingPopperShow}
|
||||||
@@ -118,7 +47,8 @@ export class OutlinePanelHeader extends WithDisposable(LitElement) {
|
|||||||
</edgeless-tool-icon-button>
|
</edgeless-tool-icon-button>
|
||||||
</div>
|
</div>
|
||||||
<edgeless-tool-icon-button
|
<edgeless-tool-icon-button
|
||||||
class="note-sorting-button ${this.enableNotesSorting ? 'active' : ''}"
|
data-testid="toggle-notes-sorting-button"
|
||||||
|
class="${this.enableNotesSorting ? 'active' : ''}"
|
||||||
.tooltip=${'Visibility and sort'}
|
.tooltip=${'Visibility and sort'}
|
||||||
.tipPosition=${'left'}
|
.tipPosition=${'left'}
|
||||||
.iconContainerPadding=${0}
|
.iconContainerPadding=${0}
|
||||||
@@ -129,7 +59,7 @@ export class OutlinePanelHeader extends WithDisposable(LitElement) {
|
|||||||
${SortingIcon}
|
${SortingIcon}
|
||||||
</edgeless-tool-icon-button>
|
</edgeless-tool-icon-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="note-preview-setting-container">
|
<div class=${styles.notePreviewSettingContainer}>
|
||||||
<affine-outline-note-preview-setting-menu
|
<affine-outline-note-preview-setting-menu
|
||||||
.showPreviewIcon=${this.showPreviewIcon}
|
.showPreviewIcon=${this.showPreviewIcon}
|
||||||
.toggleShowPreviewIcon=${this.toggleShowPreviewIcon}
|
.toggleShowPreviewIcon=${this.toggleShowPreviewIcon}
|
||||||
@@ -137,10 +67,10 @@ export class OutlinePanelHeader extends WithDisposable(LitElement) {
|
|||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@query('.note-preview-setting-container')
|
@query(`.${styles.notePreviewSettingContainer}`)
|
||||||
private accessor _notePreviewSettingMenu!: HTMLDivElement;
|
private accessor _notePreviewSettingMenu!: HTMLDivElement;
|
||||||
|
|
||||||
@query('.note-setting-button')
|
@query(`.${styles.noteSettingContainer}`)
|
||||||
private accessor _noteSettingButton!: HTMLDivElement;
|
private accessor _noteSettingButton!: HTMLDivElement;
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import { cssVarV2 } from '@toeverything/theme/v2';
|
||||||
|
import { style } from '@vanilla-extract/css';
|
||||||
|
|
||||||
|
export const host = style({});
|
||||||
|
|
||||||
|
export const notePreviewSettingMenuContainer = style({
|
||||||
|
padding: '8px',
|
||||||
|
width: '220px',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const notePreviewSettingMenuItem = style({
|
||||||
|
display: 'flex',
|
||||||
|
boxSizing: 'border-box',
|
||||||
|
width: '100%',
|
||||||
|
height: '28px',
|
||||||
|
padding: '4px 12px',
|
||||||
|
alignItems: 'center',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const settingLabel = style({
|
||||||
|
fontFamily: 'sans-serif',
|
||||||
|
fontSize: '12px',
|
||||||
|
fontWeight: 500,
|
||||||
|
lineHeight: '20px',
|
||||||
|
color: cssVarV2('text/secondary'),
|
||||||
|
padding: '0 4px',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const action = style({
|
||||||
|
gap: '4px',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const actionLabel = style({
|
||||||
|
width: '138px',
|
||||||
|
height: '20px',
|
||||||
|
padding: '0 4px',
|
||||||
|
fontSize: '12px',
|
||||||
|
fontWeight: 500,
|
||||||
|
lineHeight: '20px',
|
||||||
|
color: cssVarV2('text/primary'),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const toggleButton = style({
|
||||||
|
display: 'flex',
|
||||||
|
});
|
||||||
@@ -1,76 +1,27 @@
|
|||||||
|
import { ShadowlessElement } from '@blocksuite/block-std';
|
||||||
import { WithDisposable } from '@blocksuite/global/utils';
|
import { WithDisposable } from '@blocksuite/global/utils';
|
||||||
import { css, html, LitElement } from 'lit';
|
import { html } from 'lit';
|
||||||
import { property } from 'lit/decorators.js';
|
import { property } from 'lit/decorators.js';
|
||||||
|
|
||||||
const styles = css`
|
import * as styles from './outline-setting-menu.css';
|
||||||
:host {
|
|
||||||
display: block;
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding: 8px;
|
|
||||||
width: 220px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.note-preview-setting-menu-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
box-sizing: border-box;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.note-preview-setting-menu-item {
|
|
||||||
display: flex;
|
|
||||||
box-sizing: border-box;
|
|
||||||
width: 100%;
|
|
||||||
height: 28px;
|
|
||||||
padding: 4px 12px;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.note-preview-setting-menu-item .setting-label {
|
|
||||||
font-family: sans-serif;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 500;
|
|
||||||
line-height: 20px;
|
|
||||||
color: var(--affine-text-secondary-color);
|
|
||||||
padding: 0 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.note-preview-setting-menu-item.action {
|
|
||||||
gap: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.note-preview-setting-menu-item .action-label {
|
|
||||||
width: 138px;
|
|
||||||
height: 20px;
|
|
||||||
padding: 0 4px;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 500;
|
|
||||||
line-height: 20px;
|
|
||||||
color: var(--affine-text-primary-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.note-preview-setting-menu-item .toggle-button {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const AFFINE_OUTLINE_NOTE_PREVIEW_SETTING_MENU =
|
export const AFFINE_OUTLINE_NOTE_PREVIEW_SETTING_MENU =
|
||||||
'affine-outline-note-preview-setting-menu';
|
'affine-outline-note-preview-setting-menu';
|
||||||
|
|
||||||
export class OutlineNotePreviewSettingMenu extends WithDisposable(LitElement) {
|
export class OutlineNotePreviewSettingMenu extends WithDisposable(
|
||||||
static override styles = styles;
|
ShadowlessElement
|
||||||
|
) {
|
||||||
override render() {
|
override render() {
|
||||||
return html`<div
|
return html`<div
|
||||||
class="note-preview-setting-menu-container"
|
class=${styles.notePreviewSettingMenuContainer}
|
||||||
@click=${(e: MouseEvent) => e.stopPropagation()}
|
@click=${(e: MouseEvent) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
<div class="note-preview-setting-menu-item">
|
<div class=${styles.notePreviewSettingMenuItem}>
|
||||||
<div class="setting-label">Settings</div>
|
<div class=${styles.settingLabel}>Settings</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="note-preview-setting-menu-item action">
|
<div class="${styles.notePreviewSettingMenuItem} ${styles.action}">
|
||||||
<div class="action-label">Show type icon</div>
|
<div class=${styles.actionLabel}>Show type icon</div>
|
||||||
<div class="toggle-button">
|
<div class=${styles.toggleButton}>
|
||||||
<toggle-switch
|
<toggle-switch
|
||||||
.on=${this.showPreviewIcon}
|
.on=${this.showPreviewIcon}
|
||||||
.onChange=${this.toggleShowPreviewIcon}
|
.onChange=${this.toggleShowPreviewIcon}
|
||||||
|
|||||||
@@ -173,10 +173,7 @@ export class MobileOutlineMenu extends SignalWatcher(
|
|||||||
...headingBlocks,
|
...headingBlocks,
|
||||||
];
|
];
|
||||||
|
|
||||||
return html`
|
return repeat(items, block => block.id, this.renderItem);
|
||||||
${repeat(items, block => block.id, this.renderItem)}
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { cssVar } from '@toeverything/theme';
|
||||||
|
import { cssVarV2 } from '@toeverything/theme/v2';
|
||||||
|
import { style } from '@vanilla-extract/css';
|
||||||
|
|
||||||
|
export const outlinePanel = style({
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
backgroundColor: cssVarV2('layer/background/primary'),
|
||||||
|
boxSizing: 'border-box',
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
fontFamily: cssVar('fontSansFamily'),
|
||||||
|
paddingTop: '8px',
|
||||||
|
position: 'relative',
|
||||||
|
});
|
||||||
@@ -1,61 +1,30 @@
|
|||||||
|
import {
|
||||||
|
PropTypes,
|
||||||
|
requiredProperties,
|
||||||
|
ShadowlessElement,
|
||||||
|
} from '@blocksuite/block-std';
|
||||||
import { SignalWatcher, WithDisposable } from '@blocksuite/global/utils';
|
import { SignalWatcher, WithDisposable } from '@blocksuite/global/utils';
|
||||||
|
import { provide } from '@lit/context';
|
||||||
import { effect } from '@preact/signals-core';
|
import { effect } from '@preact/signals-core';
|
||||||
import { baseTheme } from '@toeverything/theme';
|
import { html, type PropertyValues } from 'lit';
|
||||||
import { css, html, LitElement, type PropertyValues, unsafeCSS } from 'lit';
|
|
||||||
import { property, state } from 'lit/decorators.js';
|
import { property, state } from 'lit/decorators.js';
|
||||||
|
|
||||||
import type { AffineEditorContainer } from '../../editors/editor-container.js';
|
import type { AffineEditorContainer } from '../../editors/editor-container.js';
|
||||||
import { type OutlineSettingsDataType, outlineSettingsKey } from './config.js';
|
import {
|
||||||
|
editorContext,
|
||||||
const styles = css`
|
type OutlineSettingsDataType,
|
||||||
:host {
|
outlineSettingsKey,
|
||||||
display: block;
|
} from './config.js';
|
||||||
width: 100%;
|
import * as styles from './outline-panel.css';
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.outline-panel-container {
|
|
||||||
background-color: var(--affine-background-primary-color);
|
|
||||||
box-sizing: border-box;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: stretch;
|
|
||||||
|
|
||||||
height: 100%;
|
|
||||||
font-family: ${unsafeCSS(baseTheme.fontSansFamily)};
|
|
||||||
padding-top: 8px;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.outline-panel-body {
|
|
||||||
flex-grow: 1;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
overflow-y: scroll;
|
|
||||||
}
|
|
||||||
.outline-panel-body::-webkit-scrollbar {
|
|
||||||
width: 4px;
|
|
||||||
}
|
|
||||||
.outline-panel-body::-webkit-scrollbar-thumb {
|
|
||||||
border-radius: 2px;
|
|
||||||
}
|
|
||||||
.outline-panel-body:hover::-webkit-scrollbar-thumb {
|
|
||||||
background-color: var(--affine-black-30);
|
|
||||||
}
|
|
||||||
.outline-panel-body::-webkit-scrollbar-track {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
.outline-panel-body::-webkit-scrollbar-corner {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const AFFINE_OUTLINE_PANEL = 'affine-outline-panel';
|
export const AFFINE_OUTLINE_PANEL = 'affine-outline-panel';
|
||||||
|
|
||||||
export class OutlinePanel extends SignalWatcher(WithDisposable(LitElement)) {
|
@requiredProperties({
|
||||||
static override styles = styles;
|
editor: PropTypes.object,
|
||||||
|
})
|
||||||
|
export class OutlinePanel extends SignalWatcher(
|
||||||
|
WithDisposable(ShadowlessElement)
|
||||||
|
) {
|
||||||
private readonly _setNoticeVisibility = (visibility: boolean) => {
|
private readonly _setNoticeVisibility = (visibility: boolean) => {
|
||||||
this._noticeVisible = visibility;
|
this._noticeVisible = visibility;
|
||||||
};
|
};
|
||||||
@@ -79,10 +48,6 @@ export class OutlinePanel extends SignalWatcher(WithDisposable(LitElement)) {
|
|||||||
return this.editor.doc;
|
return this.editor.doc;
|
||||||
}
|
}
|
||||||
|
|
||||||
get edgeless() {
|
|
||||||
return this.editor.querySelector('affine-edgeless-root');
|
|
||||||
}
|
|
||||||
|
|
||||||
get host() {
|
get host() {
|
||||||
return this.editor.host;
|
return this.editor.host;
|
||||||
}
|
}
|
||||||
@@ -113,6 +78,8 @@ export class OutlinePanel extends SignalWatcher(WithDisposable(LitElement)) {
|
|||||||
|
|
||||||
override connectedCallback() {
|
override connectedCallback() {
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
|
this.classList.add(styles.outlinePanel);
|
||||||
|
|
||||||
this.disposables.add(
|
this.disposables.add(
|
||||||
effect(() => {
|
effect(() => {
|
||||||
if (this.editor.mode === 'edgeless') {
|
if (this.editor.mode === 'edgeless') {
|
||||||
@@ -138,33 +105,27 @@ export class OutlinePanel extends SignalWatcher(WithDisposable(LitElement)) {
|
|||||||
if (!this.host) return;
|
if (!this.host) return;
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<div class="outline-panel-container">
|
<affine-outline-panel-header
|
||||||
<affine-outline-panel-header
|
.showPreviewIcon=${this._showPreviewIcon}
|
||||||
.showPreviewIcon=${this._showPreviewIcon}
|
.enableNotesSorting=${this._enableNotesSorting}
|
||||||
.enableNotesSorting=${this._enableNotesSorting}
|
.toggleShowPreviewIcon=${this._toggleShowPreviewIcon}
|
||||||
.toggleShowPreviewIcon=${this._toggleShowPreviewIcon}
|
.toggleNotesSorting=${this._toggleNotesSorting}
|
||||||
.toggleNotesSorting=${this._toggleNotesSorting}
|
></affine-outline-panel-header>
|
||||||
></affine-outline-panel-header>
|
<affine-outline-panel-body
|
||||||
<affine-outline-panel-body
|
.fitPadding=${this.fitPadding}
|
||||||
class="outline-panel-body"
|
.mode=${this.mode}
|
||||||
.doc=${this.doc}
|
.showPreviewIcon=${this._showPreviewIcon}
|
||||||
.fitPadding=${this.fitPadding}
|
.enableNotesSorting=${this._enableNotesSorting}
|
||||||
.edgeless=${this.edgeless}
|
.toggleNotesSorting=${this._toggleNotesSorting}
|
||||||
.editor=${this.editor}
|
.noticeVisible=${this._noticeVisible}
|
||||||
.mode=${this.mode}
|
.setNoticeVisibility=${this._setNoticeVisibility}
|
||||||
.showPreviewIcon=${this._showPreviewIcon}
|
>
|
||||||
.enableNotesSorting=${this._enableNotesSorting}
|
</affine-outline-panel-body>
|
||||||
.toggleNotesSorting=${this._toggleNotesSorting}
|
<affine-outline-notice
|
||||||
.noticeVisible=${this._noticeVisible}
|
.noticeVisible=${this._noticeVisible}
|
||||||
.setNoticeVisibility=${this._setNoticeVisibility}
|
.toggleNotesSorting=${this._toggleNotesSorting}
|
||||||
>
|
.setNoticeVisibility=${this._setNoticeVisibility}
|
||||||
</affine-outline-panel-body>
|
></affine-outline-notice>
|
||||||
<affine-outline-notice
|
|
||||||
.noticeVisible=${this._noticeVisible}
|
|
||||||
.toggleNotesSorting=${this._toggleNotesSorting}
|
|
||||||
.setNoticeVisibility=${this._setNoticeVisibility}
|
|
||||||
></affine-outline-notice>
|
|
||||||
</div>
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,6 +138,7 @@ export class OutlinePanel extends SignalWatcher(WithDisposable(LitElement)) {
|
|||||||
@state()
|
@state()
|
||||||
private accessor _showPreviewIcon = false;
|
private accessor _showPreviewIcon = false;
|
||||||
|
|
||||||
|
@provide({ context: editorContext })
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor editor!: AffineEditorContainer;
|
accessor editor!: AffineEditorContainer;
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,20 @@
|
|||||||
import { PropTypes, requiredProperties } from '@blocksuite/block-std';
|
import {
|
||||||
|
PropTypes,
|
||||||
|
requiredProperties,
|
||||||
|
ShadowlessElement,
|
||||||
|
} from '@blocksuite/block-std';
|
||||||
import { NoteDisplayMode, scrollbarStyle } from '@blocksuite/blocks';
|
import { NoteDisplayMode, scrollbarStyle } from '@blocksuite/blocks';
|
||||||
import { SignalWatcher, WithDisposable } from '@blocksuite/global/utils';
|
import { SignalWatcher, WithDisposable } from '@blocksuite/global/utils';
|
||||||
|
import { provide } from '@lit/context';
|
||||||
import { signal } from '@preact/signals-core';
|
import { signal } from '@preact/signals-core';
|
||||||
import { css, html, LitElement, nothing } from 'lit';
|
import { css, html, nothing } from 'lit';
|
||||||
import { property, query, state } from 'lit/decorators.js';
|
import { property, query, state } from 'lit/decorators.js';
|
||||||
import { classMap } from 'lit/directives/class-map.js';
|
import { classMap } from 'lit/directives/class-map.js';
|
||||||
import { repeat } from 'lit/directives/repeat.js';
|
import { repeat } from 'lit/directives/repeat.js';
|
||||||
|
|
||||||
import type { AffineEditorContainer } from '../../editors/editor-container.js';
|
import type { AffineEditorContainer } from '../../editors/editor-container.js';
|
||||||
import { TocIcon } from '../_common/icons.js';
|
import { TocIcon } from '../_common/icons.js';
|
||||||
|
import { editorContext } from './config.js';
|
||||||
import { getHeadingBlocksFromDoc } from './utils/query.js';
|
import { getHeadingBlocksFromDoc } from './utils/query.js';
|
||||||
import {
|
import {
|
||||||
observeActiveHeadingDuringScroll,
|
observeActiveHeadingDuringScroll,
|
||||||
@@ -20,9 +26,11 @@ export const AFFINE_OUTLINE_VIEWER = 'affine-outline-viewer';
|
|||||||
@requiredProperties({
|
@requiredProperties({
|
||||||
editor: PropTypes.object,
|
editor: PropTypes.object,
|
||||||
})
|
})
|
||||||
export class OutlineViewer extends SignalWatcher(WithDisposable(LitElement)) {
|
export class OutlineViewer extends SignalWatcher(
|
||||||
|
WithDisposable(ShadowlessElement)
|
||||||
|
) {
|
||||||
static override styles = css`
|
static override styles = css`
|
||||||
:host {
|
affine-outline-viewer {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
.outline-viewer-root {
|
.outline-viewer-root {
|
||||||
@@ -117,9 +125,7 @@ export class OutlineViewer extends SignalWatcher(WithDisposable(LitElement)) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.outline-viewer-item {
|
.outline-viewer-item {
|
||||||
display: flex;
|
width: 100%;
|
||||||
align-items: center;
|
|
||||||
align-self: stretch;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.outline-viewer-root:hover {
|
.outline-viewer-root:hover {
|
||||||
@@ -281,6 +287,7 @@ export class OutlineViewer extends SignalWatcher(WithDisposable(LitElement)) {
|
|||||||
@state()
|
@state()
|
||||||
private accessor _showViewer: boolean = false;
|
private accessor _showViewer: boolean = false;
|
||||||
|
|
||||||
|
@provide({ context: editorContext })
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor editor!: AffineEditorContainer;
|
accessor editor!: AffineEditorContainer;
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export function getNotesFromDoc(
|
|||||||
number: index + 1,
|
number: index + 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (modes.includes(blockModel.displayMode)) {
|
if (modes.includes(blockModel.displayMode$.value)) {
|
||||||
notes.push(OutlineNoteItem);
|
notes.push(OutlineNoteItem);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -232,7 +232,7 @@ test.describe('edgeless note element toolbar', () => {
|
|||||||
const toc = page.locator('affine-outline-panel');
|
const toc = page.locator('affine-outline-panel');
|
||||||
await toc.waitFor({ state: 'visible' });
|
await toc.waitFor({ state: 'visible' });
|
||||||
const highlightNoteCards = toc.locator(
|
const highlightNoteCards = toc.locator(
|
||||||
'affine-outline-note-card > .selected'
|
'affine-outline-note-card > [data-status="selected"]'
|
||||||
);
|
);
|
||||||
expect(highlightNoteCards).toHaveCount(1);
|
expect(highlightNoteCards).toHaveCount(1);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ async function openTocPanel(page: Page) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getTocHeading(panel: Locator, level: number) {
|
function getTocHeading(panel: Locator, level: number) {
|
||||||
return panel.locator(`.h${level} span`);
|
return panel.getByTestId(`outline-block-preview-h${level}`).locator('span');
|
||||||
}
|
}
|
||||||
|
|
||||||
async function dragNoteCard(page: Page, fromCard: Locator, toCard: Locator) {
|
async function dragNoteCard(page: Page, fromCard: Locator, toCard: Locator) {
|
||||||
@@ -67,7 +67,7 @@ test('should display title and headings when there are non-empty headings in edi
|
|||||||
|
|
||||||
const toc = await openTocPanel(page);
|
const toc = await openTocPanel(page);
|
||||||
|
|
||||||
await expect(toc.locator('.title')).toBeVisible();
|
await expect(toc.getByTestId('outline-block-preview-title')).toBeVisible();
|
||||||
for (let i = 1; i <= 6; i++) {
|
for (let i = 1; i <= 6; i++) {
|
||||||
await expect(getTocHeading(toc, i)).toBeVisible();
|
await expect(getTocHeading(toc, i)).toBeVisible();
|
||||||
await expect(getTocHeading(toc, i)).toContainText(`Heading ${i}`);
|
await expect(getTocHeading(toc, i)).toContainText(`Heading ${i}`);
|
||||||
@@ -76,7 +76,7 @@ test('should display title and headings when there are non-empty headings in edi
|
|||||||
|
|
||||||
test('should display placeholder when no headings', async ({ page }) => {
|
test('should display placeholder when no headings', async ({ page }) => {
|
||||||
const toc = await openTocPanel(page);
|
const toc = await openTocPanel(page);
|
||||||
const noHeadingPlaceholder = toc.locator('.note-placeholder');
|
const noHeadingPlaceholder = toc.getByTestId('empty-panel-placeholder');
|
||||||
|
|
||||||
await createTitle(page);
|
await createTitle(page);
|
||||||
await pressEnter(page);
|
await pressEnter(page);
|
||||||
@@ -98,7 +98,7 @@ test('should not display headings when there are only empty headings', async ({
|
|||||||
|
|
||||||
const toc = await openTocPanel(page);
|
const toc = await openTocPanel(page);
|
||||||
|
|
||||||
await expect(toc.locator('.title')).toBeHidden();
|
await expect(toc.getByTestId('outline-block-preview-title')).toBeHidden();
|
||||||
for (let i = 1; i <= 6; i++) {
|
for (let i = 1; i <= 6; i++) {
|
||||||
await expect(getTocHeading(toc, i)).toBeHidden();
|
await expect(getTocHeading(toc, i)).toBeHidden();
|
||||||
}
|
}
|
||||||
@@ -115,10 +115,12 @@ test('should update panel when modify or clear title or headings', async ({
|
|||||||
await title.scrollIntoViewIfNeeded();
|
await title.scrollIntoViewIfNeeded();
|
||||||
await title.click();
|
await title.click();
|
||||||
await type(page, 'xxx');
|
await type(page, 'xxx');
|
||||||
await expect(toc.locator('.title')).toContainText(['Titlexxx']);
|
await expect(toc.getByTestId('outline-block-preview-title')).toContainText([
|
||||||
|
'Titlexxx',
|
||||||
|
]);
|
||||||
await selectAllByKeyboard(page);
|
await selectAllByKeyboard(page);
|
||||||
await pressBackspace(page);
|
await pressBackspace(page);
|
||||||
await expect(toc.locator('.title')).toBeHidden();
|
await expect(toc.getByTestId('outline-block-preview-title')).toBeHidden();
|
||||||
|
|
||||||
for (let i = 1; i <= 6; i++) {
|
for (let i = 1; i <= 6; i++) {
|
||||||
await headings[i - 1].click();
|
await headings[i - 1].click();
|
||||||
@@ -210,7 +212,7 @@ test('should scroll to title when click title in outline panel', async ({
|
|||||||
|
|
||||||
const toc = await openTocPanel(page);
|
const toc = await openTocPanel(page);
|
||||||
|
|
||||||
const titleInPanel = toc.locator('.title');
|
const titleInPanel = toc.getByTestId('outline-block-preview-title');
|
||||||
|
|
||||||
await expect(title).not.toBeInViewport();
|
await expect(title).not.toBeInViewport();
|
||||||
await titleInPanel.click();
|
await titleInPanel.click();
|
||||||
@@ -225,7 +227,7 @@ test('visibility sorting should be enabled in edgeless mode and disabled in page
|
|||||||
|
|
||||||
const toc = await openTocPanel(page);
|
const toc = await openTocPanel(page);
|
||||||
|
|
||||||
const sortingButton = toc.locator('.note-sorting-button');
|
const sortingButton = toc.getByTestId('toggle-notes-sorting-button');
|
||||||
await expect(sortingButton).not.toHaveClass(/active/);
|
await expect(sortingButton).not.toHaveClass(/active/);
|
||||||
expect(toc.locator('[data-sortable="false"]')).toHaveCount(1);
|
expect(toc.locator('[data-sortable="false"]')).toHaveCount(1);
|
||||||
|
|
||||||
@@ -250,10 +252,10 @@ test('should reorder notes when drag and drop note in outline panel', async ({
|
|||||||
const toc = await openTocPanel(page);
|
const toc = await openTocPanel(page);
|
||||||
|
|
||||||
const docVisibleCards = toc.locator(
|
const docVisibleCards = toc.locator(
|
||||||
'.card-container[data-invisible="false"]'
|
'affine-outline-note-card [data-invisible="false"]'
|
||||||
);
|
);
|
||||||
const docInvisibleCards = toc.locator(
|
const docInvisibleCards = toc.locator(
|
||||||
'.card-container[data-invisible="true"]'
|
'affine-outline-note-card [data-invisible="true"]'
|
||||||
);
|
);
|
||||||
|
|
||||||
await expect(docVisibleCards).toHaveCount(1);
|
await expect(docVisibleCards).toHaveCount(1);
|
||||||
@@ -262,7 +264,7 @@ test('should reorder notes when drag and drop note in outline panel', async ({
|
|||||||
while ((await docInvisibleCards.count()) > 0) {
|
while ((await docInvisibleCards.count()) > 0) {
|
||||||
const card = docInvisibleCards.first();
|
const card = docInvisibleCards.first();
|
||||||
await card.hover();
|
await card.hover();
|
||||||
await card.locator('.display-mode-button').click();
|
await card.getByTestId('display-mode-button').click();
|
||||||
await card.locator('note-display-mode-panel').locator('.item.both').click();
|
await card.locator('note-display-mode-panel').locator('.item.both').click();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -310,10 +312,10 @@ test.describe('advanced visibility control', () => {
|
|||||||
const toc = await openTocPanel(page);
|
const toc = await openTocPanel(page);
|
||||||
|
|
||||||
const docVisibleCard = toc.locator(
|
const docVisibleCard = toc.locator(
|
||||||
'.card-container[data-invisible="false"]'
|
'affine-outline-note-card [data-invisible="false"]'
|
||||||
);
|
);
|
||||||
const docInvisibleCard = toc.locator(
|
const docInvisibleCard = toc.locator(
|
||||||
'.card-container[data-invisible="true"]'
|
'affine-outline-note-card [data-invisible="true"]'
|
||||||
);
|
);
|
||||||
|
|
||||||
await expect(docVisibleCard).toHaveCount(1);
|
await expect(docVisibleCard).toHaveCount(1);
|
||||||
@@ -341,17 +343,17 @@ test.describe('advanced visibility control', () => {
|
|||||||
const toc = await openTocPanel(page);
|
const toc = await openTocPanel(page);
|
||||||
|
|
||||||
const docVisibleCard = toc.locator(
|
const docVisibleCard = toc.locator(
|
||||||
'.card-container[data-invisible="false"]'
|
'affine-outline-note-card [data-invisible="false"]'
|
||||||
);
|
);
|
||||||
const docInvisibleCard = toc.locator(
|
const docInvisibleCard = toc.locator(
|
||||||
'.card-container[data-invisible="true"]'
|
'affine-outline-note-card [data-invisible="true"]'
|
||||||
);
|
);
|
||||||
|
|
||||||
await expect(docVisibleCard).toHaveCount(1);
|
await expect(docVisibleCard).toHaveCount(1);
|
||||||
await expect(docInvisibleCard).toHaveCount(1);
|
await expect(docInvisibleCard).toHaveCount(1);
|
||||||
|
|
||||||
await docInvisibleCard.hover();
|
await docInvisibleCard.hover();
|
||||||
await docInvisibleCard.locator('.display-mode-button').click();
|
await docInvisibleCard.getByTestId('display-mode-button').click();
|
||||||
await docInvisibleCard
|
await docInvisibleCard
|
||||||
.locator('note-display-mode-panel .item.both')
|
.locator('note-display-mode-panel .item.both')
|
||||||
.click();
|
.click();
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ test('should highlight indicator when click item in outline panel', async ({
|
|||||||
await indicators.first().hover({ force: true });
|
await indicators.first().hover({ force: true });
|
||||||
|
|
||||||
const headingsInPanel = Array.from({ length: 6 }, (_, i) =>
|
const headingsInPanel = Array.from({ length: 6 }, (_, i) =>
|
||||||
viewer.locator(`.h${i + 1} > span`)
|
viewer.getByTestId(`outline-block-preview-h${i + 1}`)
|
||||||
);
|
);
|
||||||
await headingsInPanel[2].click();
|
await headingsInPanel[2].click();
|
||||||
await expect(headings[2]).toBeVisible();
|
await expect(headings[2]).toBeVisible();
|
||||||
@@ -172,7 +172,9 @@ test('should hide edgeless-only note headings', async ({ page }) => {
|
|||||||
|
|
||||||
const viewer = page.locator('affine-outline-viewer');
|
const viewer = page.locator('affine-outline-viewer');
|
||||||
await expect(viewer).toBeVisible();
|
await expect(viewer).toBeVisible();
|
||||||
const h1InPanel = viewer.locator('.h1 > span');
|
const h1InPanel = viewer
|
||||||
|
.getByTestId('outline-block-preview-h1')
|
||||||
|
.locator('span');
|
||||||
await h1InPanel.waitFor({ state: 'visible' });
|
await h1InPanel.waitFor({ state: 'visible' });
|
||||||
expect(h1InPanel).toContainText(['Heading 1']);
|
expect(h1InPanel).toContainText(['Heading 1']);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3947,7 +3947,7 @@ __metadata:
|
|||||||
languageName: unknown
|
languageName: unknown
|
||||||
linkType: soft
|
linkType: soft
|
||||||
|
|
||||||
"@blocksuite/icons@npm:2.2.2, @blocksuite/icons@npm:^2.2.1":
|
"@blocksuite/icons@npm:2.2.2, @blocksuite/icons@npm:^2.2.1, @blocksuite/icons@npm:^2.2.2":
|
||||||
version: 2.2.2
|
version: 2.2.2
|
||||||
resolution: "@blocksuite/icons@npm:2.2.2"
|
resolution: "@blocksuite/icons@npm:2.2.2"
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -4045,12 +4045,15 @@ __metadata:
|
|||||||
"@blocksuite/block-std": "workspace:*"
|
"@blocksuite/block-std": "workspace:*"
|
||||||
"@blocksuite/blocks": "workspace:*"
|
"@blocksuite/blocks": "workspace:*"
|
||||||
"@blocksuite/global": "workspace:*"
|
"@blocksuite/global": "workspace:*"
|
||||||
|
"@blocksuite/icons": "npm:^2.2.2"
|
||||||
"@blocksuite/inline": "workspace:*"
|
"@blocksuite/inline": "workspace:*"
|
||||||
"@blocksuite/store": "workspace:*"
|
"@blocksuite/store": "workspace:*"
|
||||||
"@floating-ui/dom": "npm:^1.6.10"
|
"@floating-ui/dom": "npm:^1.6.10"
|
||||||
|
"@lit/context": "npm:^1.1.3"
|
||||||
"@lottiefiles/dotlottie-wc": "npm:^0.4.0"
|
"@lottiefiles/dotlottie-wc": "npm:^0.4.0"
|
||||||
"@preact/signals-core": "npm:^1.8.0"
|
"@preact/signals-core": "npm:^1.8.0"
|
||||||
"@toeverything/theme": "npm:^1.1.7"
|
"@toeverything/theme": "npm:^1.1.7"
|
||||||
|
"@vanilla-extract/css": "npm:^1.17.0"
|
||||||
"@vanilla-extract/vite-plugin": "npm:^4.0.19"
|
"@vanilla-extract/vite-plugin": "npm:^4.0.19"
|
||||||
lit: "npm:^3.2.0"
|
lit: "npm:^3.2.0"
|
||||||
vitest: "npm:^3.0.0"
|
vitest: "npm:^3.0.0"
|
||||||
@@ -7776,7 +7779,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@lit/context@npm:^1.1.2":
|
"@lit/context@npm:^1.1.2, @lit/context@npm:^1.1.3":
|
||||||
version: 1.1.3
|
version: 1.1.3
|
||||||
resolution: "@lit/context@npm:1.1.3"
|
resolution: "@lit/context@npm:1.1.3"
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|||||||
Reference in New Issue
Block a user