mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-11 07:06:28 +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/blocks": "workspace:*",
|
||||
"@blocksuite/global": "workspace:*",
|
||||
"@blocksuite/icons": "^2.2.2",
|
||||
"@blocksuite/inline": "workspace:*",
|
||||
"@blocksuite/store": "workspace:*",
|
||||
"@floating-ui/dom": "^1.6.10",
|
||||
"@lit/context": "^1.1.3",
|
||||
"@lottiefiles/dotlottie-wc": "^0.4.0",
|
||||
"@preact/signals-core": "^1.8.0",
|
||||
"@toeverything/theme": "^1.1.7",
|
||||
"@vanilla-extract/css": "^1.17.0",
|
||||
"lit": "^3.2.0",
|
||||
"yjs": "^13.6.21",
|
||||
"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 { css, html, LitElement, nothing } from 'lit';
|
||||
import { html, nothing } from 'lit';
|
||||
import { property } from 'lit/decorators.js';
|
||||
|
||||
import { SmallCloseIcon, SortingIcon } from '../../_common/icons.js';
|
||||
|
||||
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;
|
||||
}
|
||||
`;
|
||||
import * as styles from './outline-notice.css';
|
||||
|
||||
export const AFFINE_OUTLINE_NOTICE = 'affine-outline-notice';
|
||||
|
||||
export class OutlineNotice extends WithDisposable(LitElement) {
|
||||
static override styles = styles;
|
||||
|
||||
export class OutlineNotice extends WithDisposable(ShadowlessElement) {
|
||||
private _handleNoticeButtonClick() {
|
||||
this.toggleNotesSorting();
|
||||
this.setNoticeVisibility(false);
|
||||
@@ -94,29 +19,28 @@ export class OutlineNotice extends WithDisposable(LitElement) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
return html`<div class="outline-notice-container">
|
||||
<div class="outline-notice-header">
|
||||
<span class="outline-notice-label">SOME CONTENTS HIDDEN</span>
|
||||
<span
|
||||
class="outline-notice-close-button"
|
||||
@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.
|
||||
return html`
|
||||
<div class=${styles.outlineNotice}>
|
||||
<div class=${styles.outlineNoticeHeader}>
|
||||
<span class=${styles.outlineNoticeLabel}>SOME CONTENTS HIDDEN</span>
|
||||
<span
|
||||
class=${styles.outlineNoticeCloseButton}
|
||||
@click=${() => this.setNoticeVisibility(false)}
|
||||
>${SmallCloseIcon}</span
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
class="outline-notice-item button"
|
||||
@click=${this._handleNoticeButtonClick}
|
||||
>
|
||||
<span>Click here or</span>
|
||||
<span>${SortingIcon}</span>
|
||||
<span>to organize content.</span>
|
||||
<div class=${styles.outlineNoticeBody}>
|
||||
<div class="${styles.notice}">
|
||||
Some contents are not visible on edgeless.
|
||||
</div>
|
||||
<div class="${styles.button}" @click=${this._handleNoticeButtonClick}>
|
||||
<span class=${styles.buttonSpan}>Click here or</span>
|
||||
<span class=${styles.buttonSpan}>${SortingIcon}</span>
|
||||
<span class=${styles.buttonSpan}>to organize content.</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
`;
|
||||
}
|
||||
|
||||
@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 type {
|
||||
EdgelessRootBlockComponent,
|
||||
NoteBlockModel,
|
||||
} from '@blocksuite/blocks';
|
||||
import { ShadowlessElement, SurfaceSelection } from '@blocksuite/block-std';
|
||||
import type { NoteBlockModel } from '@blocksuite/blocks';
|
||||
import {
|
||||
BlocksUtils,
|
||||
matchFlavours,
|
||||
NoteDisplayMode,
|
||||
ThemeProvider,
|
||||
} from '@blocksuite/blocks';
|
||||
import {
|
||||
Bound,
|
||||
@@ -15,30 +11,33 @@ import {
|
||||
SignalWatcher,
|
||||
WithDisposable,
|
||||
} from '@blocksuite/global/utils';
|
||||
import type { Store } from '@blocksuite/store';
|
||||
import { consume } from '@lit/context';
|
||||
import { effect, signal } from '@preact/signals-core';
|
||||
import { css, html, LitElement, nothing, type PropertyValues } from 'lit';
|
||||
import { property, query, state } from 'lit/decorators.js';
|
||||
import { html, nothing, type PropertyValues } from 'lit';
|
||||
import { property, query } from 'lit/decorators.js';
|
||||
import { classMap } from 'lit/directives/class-map.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 {
|
||||
ClickBlockEvent,
|
||||
DisplayModeChangeEvent,
|
||||
FitViewEvent,
|
||||
SelectEvent,
|
||||
} from '../utils/custom-events.js';
|
||||
import { startDragging } from '../utils/drag.js';
|
||||
} from '../utils/custom-events';
|
||||
import { startDragging } from '../utils/drag';
|
||||
import {
|
||||
getHeadingBlocksFromDoc,
|
||||
getNotesFromDoc,
|
||||
isHeadingBlock,
|
||||
} from '../utils/query.js';
|
||||
} from '../utils/query';
|
||||
import {
|
||||
observeActiveHeadingDuringScroll,
|
||||
scrollToBlockWithHighlight,
|
||||
} from '../utils/scroll.js';
|
||||
} from '../utils/scroll';
|
||||
import * as styles from './outline-panel-body.css';
|
||||
|
||||
type OutlineNoteItem = {
|
||||
note: NoteBlockModel;
|
||||
@@ -54,76 +53,19 @@ type OutlineNoteItem = {
|
||||
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 class OutlinePanelBody extends SignalWatcher(
|
||||
WithDisposable(LitElement)
|
||||
WithDisposable(ShadowlessElement)
|
||||
) {
|
||||
static override styles = styles;
|
||||
|
||||
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 _docDisposables: DisposableGroup | null = null;
|
||||
@@ -132,6 +74,21 @@ export class OutlinePanelBody extends SignalWatcher(
|
||||
|
||||
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] {
|
||||
return this.fitPadding
|
||||
? ([0, 0, 0, 0].map((val, idx) =>
|
||||
@@ -174,6 +131,8 @@ export class OutlinePanelBody extends SignalWatcher(
|
||||
};
|
||||
|
||||
private _drag() {
|
||||
const pageVisibleNotes = this._pageVisibleNoteItems$.peek();
|
||||
|
||||
const selectedVisibleNotes = this._selectedNotes$.peek().filter(id => {
|
||||
const model = this.doc.getBlock(id)?.model;
|
||||
return (
|
||||
@@ -185,7 +144,7 @@ export class OutlinePanelBody extends SignalWatcher(
|
||||
|
||||
if (
|
||||
selectedVisibleNotes.length === 0 ||
|
||||
!this._pageVisibleNotes.length ||
|
||||
!pageVisibleNotes.length ||
|
||||
!this.doc.root
|
||||
)
|
||||
return;
|
||||
@@ -199,12 +158,11 @@ export class OutlinePanelBody extends SignalWatcher(
|
||||
this._selectedNotes$.value = selectedVisibleNotes;
|
||||
}
|
||||
|
||||
this._dragging = true;
|
||||
this._dragging$.value = true;
|
||||
|
||||
// cache the notes in case it is changed by other peers
|
||||
const children = this.doc.root.children.slice() as NoteBlockModel[];
|
||||
const notes = this._pageVisibleNotes;
|
||||
const notesMap = this._pageVisibleNotes.reduce((map, note, index) => {
|
||||
const notesMap = pageVisibleNotes.reduce((map, note, index) => {
|
||||
map.set(note.note.id, {
|
||||
...note,
|
||||
number: index + 1,
|
||||
@@ -215,11 +173,11 @@ export class OutlinePanelBody extends SignalWatcher(
|
||||
startDragging({
|
||||
container: this,
|
||||
document: this.ownerDocument,
|
||||
host: this.domHost ?? this.ownerDocument,
|
||||
host: this.ownerDocument,
|
||||
doc: this.doc,
|
||||
outlineListContainer: this.panelListElement,
|
||||
outlineListContainer: this._pageVisibleList,
|
||||
onDragEnd: insertIdx => {
|
||||
this._dragging = false;
|
||||
this._dragging$.value = false;
|
||||
this.insertIndex = undefined;
|
||||
|
||||
if (insertIdx === undefined) return;
|
||||
@@ -228,7 +186,7 @@ export class OutlinePanelBody extends SignalWatcher(
|
||||
insertIdx,
|
||||
selectedVisibleNotes,
|
||||
notesMap,
|
||||
notes,
|
||||
pageVisibleNotes,
|
||||
children
|
||||
);
|
||||
},
|
||||
@@ -240,8 +198,11 @@ export class OutlinePanelBody extends SignalWatcher(
|
||||
}
|
||||
|
||||
private _EmptyPanel() {
|
||||
return html`<div class="no-note-container">
|
||||
<div class="note-placeholder">
|
||||
return html`<div class=${styles.emptyPanel}>
|
||||
<div
|
||||
data-testid="empty-panel-placeholder"
|
||||
class=${styles.emptyPanelPlaceholder}
|
||||
>
|
||||
Use headings to create a table of contents.
|
||||
</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() {
|
||||
if (!this.doc.root) return nothing;
|
||||
|
||||
@@ -431,6 +321,61 @@ export class OutlinePanelBody extends SignalWatcher(
|
||||
></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) {
|
||||
this._lockActiveHeadingId = true;
|
||||
this._activeHeadingId$.value = blockId;
|
||||
@@ -471,69 +416,41 @@ export class OutlinePanelBody extends SignalWatcher(
|
||||
this._docDisposables = new DisposableGroup();
|
||||
this._docDisposables.add(
|
||||
effect(() => {
|
||||
this._updateNotes();
|
||||
this._updateNoticeVisibility();
|
||||
})
|
||||
);
|
||||
|
||||
this._docDisposables.add(
|
||||
this.doc.slots.blockUpdated.on(payload => {
|
||||
if (
|
||||
payload.type === 'update' &&
|
||||
payload.flavour === 'affine:note' &&
|
||||
payload.props.key === 'displayMode'
|
||||
) {
|
||||
this._updateNotes();
|
||||
}
|
||||
effect(() => {
|
||||
this._updateNotes();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* There are two cases that we should render note list:
|
||||
* 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;
|
||||
private _updateNotes() {
|
||||
if (this._dragging$.value) return;
|
||||
|
||||
let hasHeadings = false;
|
||||
let hasChildrenBlocks = false;
|
||||
|
||||
for (const noteItem of noteItems) {
|
||||
for (const block of noteItem.note.children) {
|
||||
hasChildrenBlocks = true;
|
||||
const isRenderableNote = (item: OutlineNoteItem) => {
|
||||
let hasHeadings = false;
|
||||
|
||||
for (const block of item.note.children) {
|
||||
if (isHeadingBlock(block)) {
|
||||
hasHeadings = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasHeadings) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return hasHeadings || this.enableNotesSorting;
|
||||
};
|
||||
|
||||
return hasHeadings || (this.enableNotesSorting && hasChildrenBlocks);
|
||||
}
|
||||
|
||||
private _updateNotes() {
|
||||
const rootModel = this.doc.root;
|
||||
|
||||
if (this._dragging) return;
|
||||
|
||||
if (!rootModel) {
|
||||
this._pageVisibleNotes = [];
|
||||
return;
|
||||
}
|
||||
|
||||
this._pageVisibleNotes = getNotesFromDoc(this.doc, [
|
||||
this._pageVisibleNoteItems$.value = getNotesFromDoc(this.doc, [
|
||||
NoteDisplayMode.DocAndEdgeless,
|
||||
NoteDisplayMode.DocOnly,
|
||||
]);
|
||||
this._edgelessOnlyNotes = getNotesFromDoc(this.doc, [
|
||||
]).filter(isRenderableNote);
|
||||
|
||||
this._edgelessOnlyNoteItems$.value = getNotesFromDoc(this.doc, [
|
||||
NoteDisplayMode.EdgelessOnly,
|
||||
]);
|
||||
]).filter(isRenderableNote);
|
||||
}
|
||||
|
||||
private _updateNoticeVisibility() {
|
||||
@@ -544,9 +461,8 @@ export class OutlinePanelBody extends SignalWatcher(
|
||||
return;
|
||||
}
|
||||
|
||||
const shouldShowNotice = this._pageVisibleNotes.some(
|
||||
note => note.note.displayMode === NoteDisplayMode.DocOnly
|
||||
);
|
||||
const shouldShowNotice =
|
||||
getNotesFromDoc(this.doc, [NoteDisplayMode.DocOnly]).length > 0;
|
||||
|
||||
if (shouldShowNotice && !this.noticeVisible) {
|
||||
this.setNoticeVisibility(true);
|
||||
@@ -580,6 +496,8 @@ export class OutlinePanelBody extends SignalWatcher(
|
||||
|
||||
override connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
this.classList.add(styles.outlinePanelBody);
|
||||
|
||||
this.disposables.add(
|
||||
observeActiveHeadingDuringScroll(
|
||||
() => this.editor,
|
||||
@@ -603,54 +521,33 @@ export class OutlinePanelBody extends SignalWatcher(
|
||||
}
|
||||
|
||||
override render() {
|
||||
const shouldRenderPageVisibleNotes = this._shouldRenderNoteList(
|
||||
this._pageVisibleNotes
|
||||
);
|
||||
const shouldRenderEdgelessOnlyNotes =
|
||||
this.renderEdgelessOnlyNotes &&
|
||||
this._shouldRenderNoteList(this._edgelessOnlyNotes);
|
||||
|
||||
const shouldRenderEmptyPanel =
|
||||
!shouldRenderPageVisibleNotes && !shouldRenderEdgelessOnlyNotes;
|
||||
|
||||
return html`
|
||||
<div class="outline-panel-body-container">
|
||||
${this._renderDocTitle()}
|
||||
${shouldRenderEmptyPanel
|
||||
? this._EmptyPanel()
|
||||
: this._PanelList(shouldRenderEdgelessOnlyNotes)}
|
||||
</div>
|
||||
${this._renderDocTitle()}
|
||||
${when(
|
||||
this._shouldRenderEmptyPanel,
|
||||
() => this._EmptyPanel(),
|
||||
() => html`
|
||||
${this._renderPageVisibleCardList()}
|
||||
${this._renderEdgelessOnlyCardList()}
|
||||
`
|
||||
)}
|
||||
`;
|
||||
}
|
||||
|
||||
override willUpdate(_changedProperties: PropertyValues) {
|
||||
if (_changedProperties.has('doc') || _changedProperties.has('edgeless')) {
|
||||
if (_changedProperties.has('editor')) {
|
||||
this._setDocDisposables();
|
||||
}
|
||||
|
||||
if (_changedProperties.has('edgeless')) {
|
||||
this._clearHighlightMask();
|
||||
if (_changedProperties.has('enableNotesSorting')) {
|
||||
this._updateNoticeVisibility();
|
||||
}
|
||||
}
|
||||
|
||||
@state()
|
||||
private accessor _dragging = false;
|
||||
|
||||
@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;
|
||||
@query('.page-visible-card-list')
|
||||
private accessor _pageVisibleList!: HTMLElement;
|
||||
|
||||
@consume({ context: editorContext })
|
||||
@property({ attribute: false })
|
||||
accessor editor!: AffineEditorContainer;
|
||||
|
||||
@@ -666,12 +563,6 @@ export class OutlinePanelBody extends SignalWatcher(
|
||||
@property({ attribute: false })
|
||||
accessor noticeVisible!: boolean;
|
||||
|
||||
@query('.outline-panel-body-container')
|
||||
accessor OutlinePanelContainer!: HTMLElement;
|
||||
|
||||
@query('.panel-list')
|
||||
accessor panelListElement!: HTMLElement;
|
||||
|
||||
@property({ attribute: false })
|
||||
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 {
|
||||
type ColorScheme,
|
||||
createButtonPopper,
|
||||
type NoteBlockModel,
|
||||
NoteDisplayMode,
|
||||
@@ -7,189 +7,19 @@ import {
|
||||
once,
|
||||
} from '@blocksuite/blocks';
|
||||
import { SignalWatcher, WithDisposable } from '@blocksuite/global/utils';
|
||||
import type { BlockModel, Store } from '@blocksuite/store';
|
||||
import { baseTheme } from '@toeverything/theme';
|
||||
import { css, html, LitElement, unsafeCSS } from 'lit';
|
||||
import type { BlockModel } from '@blocksuite/store';
|
||||
import { html } from 'lit';
|
||||
import { property, query, state } from 'lit/decorators.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
|
||||
import { HiddenIcon, SmallArrowDownIcon } from '../../_common/icons.js';
|
||||
import type { SelectEvent } from '../utils/custom-events.js';
|
||||
|
||||
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);
|
||||
}
|
||||
`;
|
||||
import { HiddenIcon, SmallArrowDownIcon } from '../../_common/icons';
|
||||
import type { SelectEvent } from '../utils/custom-events';
|
||||
import * as styles from './outline-card.css';
|
||||
|
||||
export const AFFINE_OUTLINE_NOTE_CARD = 'affine-outline-note-card';
|
||||
|
||||
export class OutlineNoteCard extends SignalWatcher(WithDisposable(LitElement)) {
|
||||
static override styles = styles;
|
||||
|
||||
export class OutlineNoteCard extends SignalWatcher(
|
||||
WithDisposable(ShadowlessElement)
|
||||
) {
|
||||
private _displayModePopper: ReturnType<typeof createButtonPopper> | null =
|
||||
null;
|
||||
|
||||
@@ -283,6 +113,10 @@ export class OutlineNoteCard extends SignalWatcher(WithDisposable(LitElement)) {
|
||||
}
|
||||
}
|
||||
|
||||
get invisible() {
|
||||
return this.note.displayMode === NoteDisplayMode.EdgelessOnly;
|
||||
}
|
||||
|
||||
override updated() {
|
||||
this._displayModePopper = createButtonPopper(
|
||||
this._displayModeButtonGroup,
|
||||
@@ -302,50 +136,49 @@ export class OutlineNoteCard extends SignalWatcher(WithDisposable(LitElement)) {
|
||||
override render() {
|
||||
const { children, displayMode } = this.note;
|
||||
const currentMode = this._getCurrentModeLabel(displayMode);
|
||||
const cardHeaderClasses = classMap({
|
||||
'card-header-container': true,
|
||||
'enable-sorting': this.enableNotesSorting,
|
||||
});
|
||||
|
||||
return html`
|
||||
<div
|
||||
data-invisible="${this.invisible ? 'true' : 'false'}"
|
||||
data-sortable="${this.enableNotesSorting ? 'true' : 'false'}"
|
||||
class="card-container ${this.status ?? ''} ${this.theme}"
|
||||
data-invisible=${this.invisible}
|
||||
data-sortable=${this.enableNotesSorting}
|
||||
data-status=${this.status}
|
||||
class=${styles.outlineCard}
|
||||
>
|
||||
<div
|
||||
class="card-preview"
|
||||
class=${styles.cardPreview}
|
||||
@mousedown=${this._dispatchDragEvent}
|
||||
@click=${this._dispatchSelectEvent}
|
||||
@dblclick=${this._dispatchFitViewEvent}
|
||||
>
|
||||
${html`<div class=${cardHeaderClasses}>
|
||||
${html`<div class=${styles.cardHeader}>
|
||||
${
|
||||
this.invisible
|
||||
? html`<span class="card-header-icon">${HiddenIcon}</span>`
|
||||
: html`<span class="card-number">${this.number}</span>`
|
||||
? html`<span class=${styles.headerIcon}>${HiddenIcon}</span>`
|
||||
: html`<span class=${styles.headerNumber}>${this.number}</span>`
|
||||
}
|
||||
<span class="card-divider"></span>
|
||||
<div class="display-mode-button-group">
|
||||
<span class="display-mode-button-label">Show in</span>
|
||||
<span class=${styles.divider}></span>
|
||||
<div class=${styles.displayModeButtonGroup}>
|
||||
<span>Show in</span>
|
||||
<edgeless-tool-icon-button
|
||||
.tooltip=${this._showPopper ? '' : 'Display Mode'}
|
||||
.tipPosition=${'left-start'}
|
||||
.iconContainerPadding=${0}
|
||||
data-testid="display-mode-button"
|
||||
@click=${(e: MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
this._displayModePopper?.toggle();
|
||||
}}
|
||||
@dblclick=${(e: MouseEvent) => e.stopPropagation()}
|
||||
>
|
||||
<div class="display-mode-button">
|
||||
<span class="current-mode-label">${currentMode}</span>
|
||||
<div class=${styles.displayModeButton}>
|
||||
<span class=${styles.currentModeLabel}>${currentMode}</span>
|
||||
${SmallArrowDownIcon}
|
||||
</div>
|
||||
</edgeless-tool-icon-button>
|
||||
</div>
|
||||
</div>
|
||||
<note-display-mode-panel
|
||||
class=${styles.modeChangePanel}
|
||||
.displayMode=${displayMode}
|
||||
.panelWidth=${220}
|
||||
.onSelect=${(newMode: NoteDisplayMode) => {
|
||||
@@ -355,7 +188,7 @@ export class OutlineNoteCard extends SignalWatcher(WithDisposable(LitElement)) {
|
||||
>
|
||||
</note-display-mode-panel>
|
||||
</div>`}
|
||||
<div class="card-content">
|
||||
<div class=${styles.cardContent}>
|
||||
${children.map(block => {
|
||||
return html`<affine-outline-block-preview
|
||||
.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;
|
||||
|
||||
@query('note-display-mode-panel')
|
||||
@@ -389,18 +222,12 @@ export class OutlineNoteCard extends SignalWatcher(WithDisposable(LitElement)) {
|
||||
@property({ attribute: false })
|
||||
accessor activeHeadingId: string | null = null;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor doc!: Store;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor enableNotesSorting!: boolean;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor index!: number;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor invisible = false;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor note!: NoteBlockModel;
|
||||
|
||||
@@ -411,10 +238,7 @@ export class OutlineNoteCard extends SignalWatcher(WithDisposable(LitElement)) {
|
||||
accessor showPreviewIcon!: boolean;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor status: 'selected' | 'placeholder' | undefined = undefined;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor theme!: ColorScheme;
|
||||
accessor status: 'selected' | 'placeholder' | 'normal' = 'normal';
|
||||
}
|
||||
|
||||
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 {
|
||||
AttachmentBlockModel,
|
||||
BookmarkBlockModel,
|
||||
CodeBlockModel,
|
||||
DatabaseBlockModel,
|
||||
ImageBlockModel,
|
||||
ListBlockModel,
|
||||
ParagraphBlockModel,
|
||||
RootBlockModel,
|
||||
import { ShadowlessElement } from '@blocksuite/block-std';
|
||||
import {
|
||||
type AttachmentBlockModel,
|
||||
type BookmarkBlockModel,
|
||||
type CodeBlockModel,
|
||||
type DatabaseBlockModel,
|
||||
DocDisplayMetaProvider,
|
||||
type ImageBlockModel,
|
||||
type ListBlockModel,
|
||||
type ParagraphBlockModel,
|
||||
type RootBlockModel,
|
||||
} from '@blocksuite/blocks';
|
||||
import { noop, SignalWatcher, WithDisposable } from '@blocksuite/global/utils';
|
||||
import { LinkedPageIcon } from '@blocksuite/icons/lit';
|
||||
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 { classMap } from 'lit/directives/class-map.js';
|
||||
|
||||
import { SmallLinkedDocIcon } from '../../_common/icons.js';
|
||||
import { placeholderMap, previewIconMap } from '../config.js';
|
||||
import type { AffineEditorContainer } from '../../../editors/editor-container.js';
|
||||
import { editorContext, placeholderMap, previewIconMap } from '../config.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];
|
||||
|
||||
@@ -24,147 +30,16 @@ function assertType<T>(value: unknown): asserts value is T {
|
||||
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 class OutlineBlockPreview extends SignalWatcher(
|
||||
WithDisposable(LitElement)
|
||||
WithDisposable(ShadowlessElement)
|
||||
) {
|
||||
static override styles = styles;
|
||||
|
||||
private _TextBlockPreview(block: ParagraphBlockModel | ListBlockModel) {
|
||||
const deltas: DeltaInsert<AffineTextAttributes>[] =
|
||||
block.text.yText.toDelta();
|
||||
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 => {
|
||||
if (delta.attributes?.reference) {
|
||||
@@ -174,37 +49,65 @@ export class OutlineBlockPreview extends SignalWatcher(
|
||||
doc => doc.id === refAttribute.pageId
|
||||
);
|
||||
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
|
||||
class="linked-doc-preview ${unavailable ? 'unavailable' : ''}"
|
||||
>${SmallLinkedDocIcon}
|
||||
<span class="linked-doc-text"
|
||||
class=${classMap({
|
||||
[styles.linkedDocPreviewUnavailable]: unavailable,
|
||||
})}
|
||||
>
|
||||
${icon}
|
||||
<span
|
||||
class=${classMap({
|
||||
[styles.linkedDocText]: true,
|
||||
[styles.linkedDocTextUnavailable]: unavailable,
|
||||
})}
|
||||
>${title.length ? title : 'Untitled'}</span
|
||||
></span
|
||||
>`;
|
||||
} else {
|
||||
// If not linked doc, render the text.
|
||||
return delta.insert.toString().trim().length > 0
|
||||
? html`<span>${delta.insert.toString()}</span>`
|
||||
? html`<span class=${styles.textSpan}
|
||||
>${delta.insert.toString()}</span
|
||||
>`
|
||||
: 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
|
||||
? html`<span class=${iconClass}>${previewIconMap[block.type]}</span>`
|
||||
: nothing}`;
|
||||
}
|
||||
|
||||
override render() {
|
||||
return html`<div class="outline-block-preview">
|
||||
return html`<div class=${styles.outlineBlockPreview}>
|
||||
${this.renderBlockByFlavour()}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
renderBlockByFlavour() {
|
||||
const { block } = this;
|
||||
const iconClass = this.disabledIcon ? 'icon disabled' : 'icon';
|
||||
const iconClass = this.disabledIcon ? styles.iconDisabled : styles.icon;
|
||||
|
||||
if (
|
||||
!this.enableNotesSorting &&
|
||||
@@ -217,7 +120,10 @@ export class OutlineBlockPreview extends SignalWatcher(
|
||||
case 'affine:page':
|
||||
assertType<RootBlockModel>(block);
|
||||
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}
|
||||
</span>`
|
||||
: nothing;
|
||||
@@ -230,7 +136,7 @@ export class OutlineBlockPreview extends SignalWatcher(
|
||||
case 'affine:bookmark':
|
||||
assertType<BookmarkBlockModel>(block);
|
||||
return html`
|
||||
<span class="text general"
|
||||
<span class="${styles.text} ${styles.textGeneral}"
|
||||
>${block.title || block.url || placeholderMap['bookmark']}</span
|
||||
>
|
||||
${this.showPreviewIcon
|
||||
@@ -242,7 +148,7 @@ export class OutlineBlockPreview extends SignalWatcher(
|
||||
case 'affine:code':
|
||||
assertType<CodeBlockModel>(block);
|
||||
return html`
|
||||
<span class="text general"
|
||||
<span class="${styles.text} ${styles.textGeneral}"
|
||||
>${block.language ?? placeholderMap['code']}</span
|
||||
>
|
||||
${this.showPreviewIcon
|
||||
@@ -252,7 +158,7 @@ export class OutlineBlockPreview extends SignalWatcher(
|
||||
case 'affine:database':
|
||||
assertType<DatabaseBlockModel>(block);
|
||||
return html`
|
||||
<span class="text general"
|
||||
<span class="${styles.text} ${styles.textGeneral}"
|
||||
>${block.title.toString().length
|
||||
? block.title.toString()
|
||||
: placeholderMap['database']}</span
|
||||
@@ -264,7 +170,7 @@ export class OutlineBlockPreview extends SignalWatcher(
|
||||
case 'affine:image':
|
||||
assertType<ImageBlockModel>(block);
|
||||
return html`
|
||||
<span class="text general"
|
||||
<span class="${styles.text} ${styles.textGeneral}"
|
||||
>${block.caption?.length
|
||||
? block.caption
|
||||
: placeholderMap['image']}</span
|
||||
@@ -276,7 +182,7 @@ export class OutlineBlockPreview extends SignalWatcher(
|
||||
case 'affine:attachment':
|
||||
assertType<AttachmentBlockModel>(block);
|
||||
return html`
|
||||
<span class="text general"
|
||||
<span class="${styles.text} ${styles.textGeneral}"
|
||||
>${block.name?.length
|
||||
? block.name
|
||||
: placeholderMap['attachment']}</span
|
||||
@@ -292,6 +198,10 @@ export class OutlineBlockPreview extends SignalWatcher(
|
||||
}
|
||||
}
|
||||
|
||||
@consume({ context: editorContext })
|
||||
@property({ attribute: false })
|
||||
accessor editor!: AffineEditorContainer;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor block!: ValuesOf<BlockSuite.BlockModels>;
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import type { ParagraphBlockModel } from '@blocksuite/blocks';
|
||||
import { createContext } from '@lit/context';
|
||||
import type { TemplateResult } from 'lit';
|
||||
|
||||
import type { AffineEditorContainer } from '../../editors/editor-container.js';
|
||||
import {
|
||||
BlockPreviewIcon,
|
||||
SmallAttachmentIcon,
|
||||
@@ -84,3 +86,6 @@ export type OutlineSettingsDataType = {
|
||||
showIcons: 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 { WithDisposable } from '@blocksuite/global/utils';
|
||||
import { css, html, LitElement } from 'lit';
|
||||
import { html } from 'lit';
|
||||
import { property, query, state } from 'lit/decorators.js';
|
||||
|
||||
import { SettingsIcon, SortingIcon } from '../../_common/icons.js';
|
||||
|
||||
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;
|
||||
}
|
||||
`;
|
||||
import * as styles from './outline-panel-header.css';
|
||||
|
||||
export const AFFINE_OUTLINE_PANEL_HEADER = 'affine-outline-panel-header';
|
||||
|
||||
export class OutlinePanelHeader extends WithDisposable(LitElement) {
|
||||
static override styles = styles;
|
||||
|
||||
export class OutlinePanelHeader extends WithDisposable(ShadowlessElement) {
|
||||
private _notePreviewSettingMenuPopper: ReturnType<
|
||||
typeof createButtonPopper
|
||||
> | null = null;
|
||||
@@ -101,13 +32,11 @@ export class OutlinePanelHeader extends WithDisposable(LitElement) {
|
||||
}
|
||||
|
||||
override render() {
|
||||
return html`<div class="outline-panel-header-container">
|
||||
<div class="note-setting-container">
|
||||
<span class="outline-panel-header-label">Table of Contents</span>
|
||||
return html`<div class=${styles.container}>
|
||||
<div class=${styles.noteSettingContainer}>
|
||||
<span class=${styles.label}>Table of Contents</span>
|
||||
<edgeless-tool-icon-button
|
||||
class="note-setting-button ${this._settingPopperShow
|
||||
? 'active'
|
||||
: ''}"
|
||||
class="${this._settingPopperShow ? 'active' : ''}"
|
||||
.tooltip=${this._settingPopperShow ? '' : 'Preview Settings'}
|
||||
.tipPosition=${'bottom'}
|
||||
.active=${this._settingPopperShow}
|
||||
@@ -118,7 +47,8 @@ export class OutlinePanelHeader extends WithDisposable(LitElement) {
|
||||
</edgeless-tool-icon-button>
|
||||
</div>
|
||||
<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'}
|
||||
.tipPosition=${'left'}
|
||||
.iconContainerPadding=${0}
|
||||
@@ -129,7 +59,7 @@ export class OutlinePanelHeader extends WithDisposable(LitElement) {
|
||||
${SortingIcon}
|
||||
</edgeless-tool-icon-button>
|
||||
</div>
|
||||
<div class="note-preview-setting-container">
|
||||
<div class=${styles.notePreviewSettingContainer}>
|
||||
<affine-outline-note-preview-setting-menu
|
||||
.showPreviewIcon=${this.showPreviewIcon}
|
||||
.toggleShowPreviewIcon=${this.toggleShowPreviewIcon}
|
||||
@@ -137,10 +67,10 @@ export class OutlinePanelHeader extends WithDisposable(LitElement) {
|
||||
</div>`;
|
||||
}
|
||||
|
||||
@query('.note-preview-setting-container')
|
||||
@query(`.${styles.notePreviewSettingContainer}`)
|
||||
private accessor _notePreviewSettingMenu!: HTMLDivElement;
|
||||
|
||||
@query('.note-setting-button')
|
||||
@query(`.${styles.noteSettingContainer}`)
|
||||
private accessor _noteSettingButton!: HTMLDivElement;
|
||||
|
||||
@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 { css, html, LitElement } from 'lit';
|
||||
import { html } from 'lit';
|
||||
import { property } from 'lit/decorators.js';
|
||||
|
||||
const styles = 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;
|
||||
}
|
||||
`;
|
||||
import * as styles from './outline-setting-menu.css';
|
||||
|
||||
export const AFFINE_OUTLINE_NOTE_PREVIEW_SETTING_MENU =
|
||||
'affine-outline-note-preview-setting-menu';
|
||||
|
||||
export class OutlineNotePreviewSettingMenu extends WithDisposable(LitElement) {
|
||||
static override styles = styles;
|
||||
|
||||
export class OutlineNotePreviewSettingMenu extends WithDisposable(
|
||||
ShadowlessElement
|
||||
) {
|
||||
override render() {
|
||||
return html`<div
|
||||
class="note-preview-setting-menu-container"
|
||||
class=${styles.notePreviewSettingMenuContainer}
|
||||
@click=${(e: MouseEvent) => e.stopPropagation()}
|
||||
>
|
||||
<div class="note-preview-setting-menu-item">
|
||||
<div class="setting-label">Settings</div>
|
||||
<div class=${styles.notePreviewSettingMenuItem}>
|
||||
<div class=${styles.settingLabel}>Settings</div>
|
||||
</div>
|
||||
<div class="note-preview-setting-menu-item action">
|
||||
<div class="action-label">Show type icon</div>
|
||||
<div class="toggle-button">
|
||||
<div class="${styles.notePreviewSettingMenuItem} ${styles.action}">
|
||||
<div class=${styles.actionLabel}>Show type icon</div>
|
||||
<div class=${styles.toggleButton}>
|
||||
<toggle-switch
|
||||
.on=${this.showPreviewIcon}
|
||||
.onChange=${this.toggleShowPreviewIcon}
|
||||
|
||||
@@ -173,10 +173,7 @@ export class MobileOutlineMenu extends SignalWatcher(
|
||||
...headingBlocks,
|
||||
];
|
||||
|
||||
return html`
|
||||
${repeat(items, block => block.id, this.renderItem)}
|
||||
</div>
|
||||
`;
|
||||
return repeat(items, block => block.id, this.renderItem);
|
||||
}
|
||||
|
||||
@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 { provide } from '@lit/context';
|
||||
import { effect } from '@preact/signals-core';
|
||||
import { baseTheme } from '@toeverything/theme';
|
||||
import { css, html, LitElement, type PropertyValues, unsafeCSS } from 'lit';
|
||||
import { html, type PropertyValues } from 'lit';
|
||||
import { property, state } from 'lit/decorators.js';
|
||||
|
||||
import type { AffineEditorContainer } from '../../editors/editor-container.js';
|
||||
import { type OutlineSettingsDataType, outlineSettingsKey } from './config.js';
|
||||
|
||||
const styles = css`
|
||||
:host {
|
||||
display: block;
|
||||
width: 100%;
|
||||
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;
|
||||
}
|
||||
`;
|
||||
import {
|
||||
editorContext,
|
||||
type OutlineSettingsDataType,
|
||||
outlineSettingsKey,
|
||||
} from './config.js';
|
||||
import * as styles from './outline-panel.css';
|
||||
|
||||
export const AFFINE_OUTLINE_PANEL = 'affine-outline-panel';
|
||||
|
||||
export class OutlinePanel extends SignalWatcher(WithDisposable(LitElement)) {
|
||||
static override styles = styles;
|
||||
|
||||
@requiredProperties({
|
||||
editor: PropTypes.object,
|
||||
})
|
||||
export class OutlinePanel extends SignalWatcher(
|
||||
WithDisposable(ShadowlessElement)
|
||||
) {
|
||||
private readonly _setNoticeVisibility = (visibility: boolean) => {
|
||||
this._noticeVisible = visibility;
|
||||
};
|
||||
@@ -79,10 +48,6 @@ export class OutlinePanel extends SignalWatcher(WithDisposable(LitElement)) {
|
||||
return this.editor.doc;
|
||||
}
|
||||
|
||||
get edgeless() {
|
||||
return this.editor.querySelector('affine-edgeless-root');
|
||||
}
|
||||
|
||||
get host() {
|
||||
return this.editor.host;
|
||||
}
|
||||
@@ -113,6 +78,8 @@ export class OutlinePanel extends SignalWatcher(WithDisposable(LitElement)) {
|
||||
|
||||
override connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.classList.add(styles.outlinePanel);
|
||||
|
||||
this.disposables.add(
|
||||
effect(() => {
|
||||
if (this.editor.mode === 'edgeless') {
|
||||
@@ -138,33 +105,27 @@ export class OutlinePanel extends SignalWatcher(WithDisposable(LitElement)) {
|
||||
if (!this.host) return;
|
||||
|
||||
return html`
|
||||
<div class="outline-panel-container">
|
||||
<affine-outline-panel-header
|
||||
.showPreviewIcon=${this._showPreviewIcon}
|
||||
.enableNotesSorting=${this._enableNotesSorting}
|
||||
.toggleShowPreviewIcon=${this._toggleShowPreviewIcon}
|
||||
.toggleNotesSorting=${this._toggleNotesSorting}
|
||||
></affine-outline-panel-header>
|
||||
<affine-outline-panel-body
|
||||
class="outline-panel-body"
|
||||
.doc=${this.doc}
|
||||
.fitPadding=${this.fitPadding}
|
||||
.edgeless=${this.edgeless}
|
||||
.editor=${this.editor}
|
||||
.mode=${this.mode}
|
||||
.showPreviewIcon=${this._showPreviewIcon}
|
||||
.enableNotesSorting=${this._enableNotesSorting}
|
||||
.toggleNotesSorting=${this._toggleNotesSorting}
|
||||
.noticeVisible=${this._noticeVisible}
|
||||
.setNoticeVisibility=${this._setNoticeVisibility}
|
||||
>
|
||||
</affine-outline-panel-body>
|
||||
<affine-outline-notice
|
||||
.noticeVisible=${this._noticeVisible}
|
||||
.toggleNotesSorting=${this._toggleNotesSorting}
|
||||
.setNoticeVisibility=${this._setNoticeVisibility}
|
||||
></affine-outline-notice>
|
||||
</div>
|
||||
<affine-outline-panel-header
|
||||
.showPreviewIcon=${this._showPreviewIcon}
|
||||
.enableNotesSorting=${this._enableNotesSorting}
|
||||
.toggleShowPreviewIcon=${this._toggleShowPreviewIcon}
|
||||
.toggleNotesSorting=${this._toggleNotesSorting}
|
||||
></affine-outline-panel-header>
|
||||
<affine-outline-panel-body
|
||||
.fitPadding=${this.fitPadding}
|
||||
.mode=${this.mode}
|
||||
.showPreviewIcon=${this._showPreviewIcon}
|
||||
.enableNotesSorting=${this._enableNotesSorting}
|
||||
.toggleNotesSorting=${this._toggleNotesSorting}
|
||||
.noticeVisible=${this._noticeVisible}
|
||||
.setNoticeVisibility=${this._setNoticeVisibility}
|
||||
>
|
||||
</affine-outline-panel-body>
|
||||
<affine-outline-notice
|
||||
.noticeVisible=${this._noticeVisible}
|
||||
.toggleNotesSorting=${this._toggleNotesSorting}
|
||||
.setNoticeVisibility=${this._setNoticeVisibility}
|
||||
></affine-outline-notice>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -177,6 +138,7 @@ export class OutlinePanel extends SignalWatcher(WithDisposable(LitElement)) {
|
||||
@state()
|
||||
private accessor _showPreviewIcon = false;
|
||||
|
||||
@provide({ context: editorContext })
|
||||
@property({ attribute: false })
|
||||
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 { SignalWatcher, WithDisposable } from '@blocksuite/global/utils';
|
||||
import { provide } from '@lit/context';
|
||||
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 { classMap } from 'lit/directives/class-map.js';
|
||||
import { repeat } from 'lit/directives/repeat.js';
|
||||
|
||||
import type { AffineEditorContainer } from '../../editors/editor-container.js';
|
||||
import { TocIcon } from '../_common/icons.js';
|
||||
import { editorContext } from './config.js';
|
||||
import { getHeadingBlocksFromDoc } from './utils/query.js';
|
||||
import {
|
||||
observeActiveHeadingDuringScroll,
|
||||
@@ -20,9 +26,11 @@ export const AFFINE_OUTLINE_VIEWER = 'affine-outline-viewer';
|
||||
@requiredProperties({
|
||||
editor: PropTypes.object,
|
||||
})
|
||||
export class OutlineViewer extends SignalWatcher(WithDisposable(LitElement)) {
|
||||
export class OutlineViewer extends SignalWatcher(
|
||||
WithDisposable(ShadowlessElement)
|
||||
) {
|
||||
static override styles = css`
|
||||
:host {
|
||||
affine-outline-viewer {
|
||||
display: flex;
|
||||
}
|
||||
.outline-viewer-root {
|
||||
@@ -117,9 +125,7 @@ export class OutlineViewer extends SignalWatcher(WithDisposable(LitElement)) {
|
||||
}
|
||||
|
||||
.outline-viewer-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-self: stretch;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.outline-viewer-root:hover {
|
||||
@@ -281,6 +287,7 @@ export class OutlineViewer extends SignalWatcher(WithDisposable(LitElement)) {
|
||||
@state()
|
||||
private accessor _showViewer: boolean = false;
|
||||
|
||||
@provide({ context: editorContext })
|
||||
@property({ attribute: false })
|
||||
accessor editor!: AffineEditorContainer;
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ export function getNotesFromDoc(
|
||||
number: index + 1,
|
||||
};
|
||||
|
||||
if (modes.includes(blockModel.displayMode)) {
|
||||
if (modes.includes(blockModel.displayMode$.value)) {
|
||||
notes.push(OutlineNoteItem);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -232,7 +232,7 @@ test.describe('edgeless note element toolbar', () => {
|
||||
const toc = page.locator('affine-outline-panel');
|
||||
await toc.waitFor({ state: 'visible' });
|
||||
const highlightNoteCards = toc.locator(
|
||||
'affine-outline-note-card > .selected'
|
||||
'affine-outline-note-card > [data-status="selected"]'
|
||||
);
|
||||
expect(highlightNoteCards).toHaveCount(1);
|
||||
});
|
||||
|
||||
@@ -40,7 +40,7 @@ async function openTocPanel(page: Page) {
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -67,7 +67,7 @@ test('should display title and headings when there are non-empty headings in edi
|
||||
|
||||
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++) {
|
||||
await expect(getTocHeading(toc, i)).toBeVisible();
|
||||
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 }) => {
|
||||
const toc = await openTocPanel(page);
|
||||
const noHeadingPlaceholder = toc.locator('.note-placeholder');
|
||||
const noHeadingPlaceholder = toc.getByTestId('empty-panel-placeholder');
|
||||
|
||||
await createTitle(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);
|
||||
|
||||
await expect(toc.locator('.title')).toBeHidden();
|
||||
await expect(toc.getByTestId('outline-block-preview-title')).toBeHidden();
|
||||
for (let i = 1; i <= 6; i++) {
|
||||
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.click();
|
||||
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 pressBackspace(page);
|
||||
await expect(toc.locator('.title')).toBeHidden();
|
||||
await expect(toc.getByTestId('outline-block-preview-title')).toBeHidden();
|
||||
|
||||
for (let i = 1; i <= 6; i++) {
|
||||
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 titleInPanel = toc.locator('.title');
|
||||
const titleInPanel = toc.getByTestId('outline-block-preview-title');
|
||||
|
||||
await expect(title).not.toBeInViewport();
|
||||
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 sortingButton = toc.locator('.note-sorting-button');
|
||||
const sortingButton = toc.getByTestId('toggle-notes-sorting-button');
|
||||
await expect(sortingButton).not.toHaveClass(/active/);
|
||||
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 docVisibleCards = toc.locator(
|
||||
'.card-container[data-invisible="false"]'
|
||||
'affine-outline-note-card [data-invisible="false"]'
|
||||
);
|
||||
const docInvisibleCards = toc.locator(
|
||||
'.card-container[data-invisible="true"]'
|
||||
'affine-outline-note-card [data-invisible="true"]'
|
||||
);
|
||||
|
||||
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) {
|
||||
const card = docInvisibleCards.first();
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -310,10 +312,10 @@ test.describe('advanced visibility control', () => {
|
||||
const toc = await openTocPanel(page);
|
||||
|
||||
const docVisibleCard = toc.locator(
|
||||
'.card-container[data-invisible="false"]'
|
||||
'affine-outline-note-card [data-invisible="false"]'
|
||||
);
|
||||
const docInvisibleCard = toc.locator(
|
||||
'.card-container[data-invisible="true"]'
|
||||
'affine-outline-note-card [data-invisible="true"]'
|
||||
);
|
||||
|
||||
await expect(docVisibleCard).toHaveCount(1);
|
||||
@@ -341,17 +343,17 @@ test.describe('advanced visibility control', () => {
|
||||
const toc = await openTocPanel(page);
|
||||
|
||||
const docVisibleCard = toc.locator(
|
||||
'.card-container[data-invisible="false"]'
|
||||
'affine-outline-note-card [data-invisible="false"]'
|
||||
);
|
||||
const docInvisibleCard = toc.locator(
|
||||
'.card-container[data-invisible="true"]'
|
||||
'affine-outline-note-card [data-invisible="true"]'
|
||||
);
|
||||
|
||||
await expect(docVisibleCard).toHaveCount(1);
|
||||
await expect(docInvisibleCard).toHaveCount(1);
|
||||
|
||||
await docInvisibleCard.hover();
|
||||
await docInvisibleCard.locator('.display-mode-button').click();
|
||||
await docInvisibleCard.getByTestId('display-mode-button').click();
|
||||
await docInvisibleCard
|
||||
.locator('note-display-mode-panel .item.both')
|
||||
.click();
|
||||
|
||||
@@ -129,7 +129,7 @@ test('should highlight indicator when click item in outline panel', async ({
|
||||
await indicators.first().hover({ force: true });
|
||||
|
||||
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 expect(headings[2]).toBeVisible();
|
||||
@@ -172,7 +172,9 @@ test('should hide edgeless-only note headings', async ({ page }) => {
|
||||
|
||||
const viewer = page.locator('affine-outline-viewer');
|
||||
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' });
|
||||
expect(h1InPanel).toContainText(['Heading 1']);
|
||||
});
|
||||
|
||||
@@ -3947,7 +3947,7 @@ __metadata:
|
||||
languageName: unknown
|
||||
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
|
||||
resolution: "@blocksuite/icons@npm:2.2.2"
|
||||
peerDependencies:
|
||||
@@ -4045,12 +4045,15 @@ __metadata:
|
||||
"@blocksuite/block-std": "workspace:*"
|
||||
"@blocksuite/blocks": "workspace:*"
|
||||
"@blocksuite/global": "workspace:*"
|
||||
"@blocksuite/icons": "npm:^2.2.2"
|
||||
"@blocksuite/inline": "workspace:*"
|
||||
"@blocksuite/store": "workspace:*"
|
||||
"@floating-ui/dom": "npm:^1.6.10"
|
||||
"@lit/context": "npm:^1.1.3"
|
||||
"@lottiefiles/dotlottie-wc": "npm:^0.4.0"
|
||||
"@preact/signals-core": "npm:^1.8.0"
|
||||
"@toeverything/theme": "npm:^1.1.7"
|
||||
"@vanilla-extract/css": "npm:^1.17.0"
|
||||
"@vanilla-extract/vite-plugin": "npm:^4.0.19"
|
||||
lit: "npm:^3.2.0"
|
||||
vitest: "npm:^3.0.0"
|
||||
@@ -7776,7 +7779,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@lit/context@npm:^1.1.2":
|
||||
"@lit/context@npm:^1.1.2, @lit/context@npm:^1.1.3":
|
||||
version: 1.1.3
|
||||
resolution: "@lit/context@npm:1.1.3"
|
||||
dependencies:
|
||||
|
||||
Reference in New Issue
Block a user