mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-14 21:27:20 +00:00
refactor(editor): remove edit view of database block properties (#10748)
This commit is contained in:
@@ -30,6 +30,7 @@
|
||||
"@preact/signals-core": "^1.8.0",
|
||||
"@toeverything/theme": "^1.1.12",
|
||||
"@types/mdast": "^4.0.4",
|
||||
"@vanilla-extract/css": "^1.17.0",
|
||||
"date-fns": "^4.0.0",
|
||||
"lit": "^3.2.0",
|
||||
"minimatch": "^10.0.1",
|
||||
|
||||
@@ -4,37 +4,20 @@ import { DatabaseBlockComponent } from './database-block';
|
||||
import { DatabaseDndPreviewBlockComponent } from './database-dnd-preview-block';
|
||||
import { BlockRenderer } from './detail-panel/block-renderer';
|
||||
import { NoteRenderer } from './detail-panel/note-renderer';
|
||||
import { LinkCell, LinkCellEditing } from './properties/link/cell-renderer';
|
||||
import { LinkNode } from './properties/link/components/link-node';
|
||||
import {
|
||||
RichTextCell,
|
||||
RichTextCellEditing,
|
||||
} from './properties/rich-text/cell-renderer';
|
||||
import { LinkCell } from './properties/link/cell-renderer';
|
||||
import { RichTextCell } from './properties/rich-text/cell-renderer';
|
||||
import { IconCell } from './properties/title/icon';
|
||||
import {
|
||||
HeaderAreaTextCell,
|
||||
HeaderAreaTextCellEditing,
|
||||
} from './properties/title/text';
|
||||
import { HeaderAreaTextCell } from './properties/title/text';
|
||||
|
||||
export function effects() {
|
||||
customElements.define('affine-database-title', DatabaseTitle);
|
||||
customElements.define('data-view-header-area-icon', IconCell);
|
||||
customElements.define('affine-database-link-cell', LinkCell);
|
||||
customElements.define('affine-database-link-cell-editing', LinkCellEditing);
|
||||
customElements.define('data-view-header-area-text', HeaderAreaTextCell);
|
||||
customElements.define(
|
||||
'data-view-header-area-text-editing',
|
||||
HeaderAreaTextCellEditing
|
||||
);
|
||||
customElements.define('affine-database-rich-text-cell', RichTextCell);
|
||||
customElements.define(
|
||||
'affine-database-rich-text-cell-editing',
|
||||
RichTextCellEditing
|
||||
);
|
||||
customElements.define('center-peek', CenterPeek);
|
||||
customElements.define('database-datasource-note-renderer', NoteRenderer);
|
||||
customElements.define('database-datasource-block-renderer', BlockRenderer);
|
||||
customElements.define('affine-database-link-node', LinkNode);
|
||||
customElements.define('affine-database', DatabaseBlockComponent);
|
||||
|
||||
customElements.define(
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
import { cssVarV2 } from '@blocksuite/affine-shared/theme';
|
||||
import { baseTheme } from '@toeverything/theme';
|
||||
import { style } from '@vanilla-extract/css';
|
||||
|
||||
export const linkCellStyle = style({
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
userSelect: 'none',
|
||||
position: 'relative',
|
||||
});
|
||||
|
||||
export const linkContainerStyle = style({
|
||||
display: 'flex',
|
||||
position: 'relative',
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
outline: 'none',
|
||||
overflow: 'hidden',
|
||||
fontSize: 'var(--data-view-cell-text-size)',
|
||||
lineHeight: 'var(--data-view-cell-text-line-height)',
|
||||
wordBreak: 'break-all',
|
||||
});
|
||||
export const linkIconContainerStyle = style({
|
||||
position: 'absolute',
|
||||
right: '8px',
|
||||
top: '8px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
visibility: 'hidden',
|
||||
backgroundColor: cssVarV2.layer.background.primary,
|
||||
boxShadow: 'var(--affine-button-shadow)',
|
||||
borderRadius: '4px',
|
||||
overflow: 'hidden',
|
||||
zIndex: 1,
|
||||
});
|
||||
export const linkIconStyle = style({
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
cursor: 'pointer',
|
||||
color: cssVarV2.icon.primary,
|
||||
fontSize: '14px',
|
||||
padding: '2px',
|
||||
':hover': {
|
||||
backgroundColor: cssVarV2.layer.background.hoverOverlay,
|
||||
},
|
||||
});
|
||||
|
||||
export const showLinkIconStyle = style({
|
||||
selectors: {
|
||||
[`${linkCellStyle}:hover &`]: {
|
||||
visibility: 'visible',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const linkedDocStyle = style({
|
||||
textDecoration: 'underline',
|
||||
textDecorationColor: 'var(--affine-divider-color)',
|
||||
transition: 'text-decoration-color 0.2s ease-out',
|
||||
cursor: 'pointer',
|
||||
':hover': {
|
||||
textDecorationColor: 'var(--affine-icon-color)',
|
||||
},
|
||||
});
|
||||
|
||||
export const linkEditingStyle = style({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
padding: '0',
|
||||
border: 'none',
|
||||
fontFamily: baseTheme.fontSansFamily,
|
||||
color: 'var(--affine-text-primary-color)',
|
||||
fontWeight: '400',
|
||||
backgroundColor: 'transparent',
|
||||
fontSize: 'var(--data-view-cell-text-size)',
|
||||
lineHeight: 'var(--data-view-cell-text-line-height)',
|
||||
wordBreak: 'break-all',
|
||||
':focus': {
|
||||
outline: 'none',
|
||||
},
|
||||
});
|
||||
|
||||
export const inlineLinkNodeStyle = style({
|
||||
wordBreak: 'break-all',
|
||||
color: 'var(--affine-link-color)',
|
||||
fill: 'var(--affine-link-color)',
|
||||
cursor: 'pointer',
|
||||
fontWeight: 'normal',
|
||||
fontStyle: 'normal',
|
||||
textDecoration: 'none',
|
||||
});
|
||||
|
||||
export const normalTextStyle = style({
|
||||
wordBreak: 'break-all',
|
||||
});
|
||||
@@ -1,6 +1,5 @@
|
||||
import { RefNodeSlotsProvider } from '@blocksuite/affine-rich-text';
|
||||
import { ParseDocUrlProvider } from '@blocksuite/affine-shared/services';
|
||||
import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
|
||||
import {
|
||||
isValidUrl,
|
||||
normalizeUrl,
|
||||
@@ -12,106 +11,69 @@ import {
|
||||
createIcon,
|
||||
} from '@blocksuite/data-view';
|
||||
import { EditIcon } from '@blocksuite/icons/lit';
|
||||
import { baseTheme } from '@toeverything/theme';
|
||||
import { css, nothing, unsafeCSS } from 'lit';
|
||||
import { query, state } from 'lit/decorators.js';
|
||||
import { html } from 'lit/static-html.js';
|
||||
import { computed } from '@preact/signals-core';
|
||||
import { html, nothing, type PropertyValues } from 'lit';
|
||||
import { createRef, ref } from 'lit/directives/ref.js';
|
||||
|
||||
import { HostContextKey } from '../../context/host-context.js';
|
||||
import {
|
||||
inlineLinkNodeStyle,
|
||||
linkCellStyle,
|
||||
linkContainerStyle,
|
||||
linkedDocStyle,
|
||||
linkEditingStyle,
|
||||
linkIconContainerStyle,
|
||||
linkIconStyle,
|
||||
normalTextStyle,
|
||||
showLinkIconStyle,
|
||||
} from './cell-renderer.css.js';
|
||||
import { linkPropertyModelConfig } from './define.js';
|
||||
|
||||
export class LinkCell extends BaseCellRenderer<string> {
|
||||
static override styles = css`
|
||||
affine-database-link-cell {
|
||||
width: 100%;
|
||||
user-select: none;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
affine-database-link-cell:hover .affine-database-link-icon {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.affine-database-link {
|
||||
display: flex;
|
||||
position: relative;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
outline: none;
|
||||
overflow: hidden;
|
||||
font-size: var(--data-view-cell-text-size);
|
||||
line-height: var(--data-view-cell-text-line-height);
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
affine-database-link-node {
|
||||
flex: 1;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.affine-database-link-icon {
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
top: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
visibility: hidden;
|
||||
cursor: pointer;
|
||||
background: ${unsafeCSSVarV2('button/iconButtonSolid')};
|
||||
color: ${unsafeCSSVarV2('icon/primary')};
|
||||
box-shadow: var(--affine-button-shadow);
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.affine-database-link-icon:hover {
|
||||
background: var(--affine-hover-color);
|
||||
}
|
||||
|
||||
.data-view-link-column-linked-doc {
|
||||
text-decoration: underline;
|
||||
text-decoration-color: var(--affine-divider-color);
|
||||
transition: text-decoration-color 0.2s ease-out;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.data-view-link-column-linked-doc:hover {
|
||||
text-decoration-color: var(--affine-icon-color);
|
||||
}
|
||||
`;
|
||||
|
||||
private readonly _onClick = (event: Event) => {
|
||||
event.stopPropagation();
|
||||
const value = this.value ?? '';
|
||||
|
||||
if (!value || !isValidUrl(value)) {
|
||||
this.selectCurrentCell(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isValidUrl(value)) {
|
||||
const target = event.target as HTMLElement;
|
||||
const link = target.querySelector<HTMLAnchorElement>('.link-node');
|
||||
if (link) {
|
||||
event.preventDefault();
|
||||
link.click();
|
||||
}
|
||||
return;
|
||||
}
|
||||
};
|
||||
protected override firstUpdated(_changedProperties: PropertyValues) {
|
||||
super.firstUpdated(_changedProperties);
|
||||
this.classList.add(linkCellStyle);
|
||||
}
|
||||
|
||||
private readonly _onEdit = (e: Event) => {
|
||||
e.stopPropagation();
|
||||
this.selectCurrentCell(true);
|
||||
this.selectCurrentCell(true);
|
||||
};
|
||||
|
||||
private preValue?: string;
|
||||
private readonly _focusEnd = () => {
|
||||
const ele = this._container.value;
|
||||
if (!ele) {
|
||||
return;
|
||||
}
|
||||
const end = ele?.value.length;
|
||||
ele?.focus();
|
||||
ele?.setSelectionRange(end, end);
|
||||
};
|
||||
|
||||
private readonly _onKeydown = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Enter' && !e.isComposing) {
|
||||
this.selectCurrentCell(false);
|
||||
}
|
||||
};
|
||||
|
||||
private readonly _setValue = (
|
||||
value: string = this._container.value?.value ?? ''
|
||||
) => {
|
||||
let url = value;
|
||||
if (isValidUrl(value)) {
|
||||
url = normalizeUrl(value);
|
||||
}
|
||||
|
||||
this.valueSetNextTick(url);
|
||||
if (this._container.value) {
|
||||
this._container.value.value = url;
|
||||
}
|
||||
};
|
||||
|
||||
openDoc = (e: MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
if (!this.docId) {
|
||||
if (!this.docId$.value) {
|
||||
return;
|
||||
}
|
||||
const std = this.std;
|
||||
@@ -120,7 +82,7 @@ export class LinkCell extends BaseCellRenderer<string> {
|
||||
}
|
||||
|
||||
std.getOptional(RefNodeSlotsProvider)?.docLinkClicked.emit({
|
||||
pageId: this.docId,
|
||||
pageId: this.docId$.value,
|
||||
host: std.host,
|
||||
});
|
||||
};
|
||||
@@ -130,128 +92,95 @@ export class LinkCell extends BaseCellRenderer<string> {
|
||||
return host?.std;
|
||||
}
|
||||
|
||||
override render() {
|
||||
const linkText = this.value ?? '';
|
||||
const docName =
|
||||
this.docId && this.std?.workspace.getDoc(this.docId)?.meta?.title;
|
||||
return html`
|
||||
<div class="affine-database-link" @click="${this._onClick}">
|
||||
${docName
|
||||
? html`<span
|
||||
class="data-view-link-column-linked-doc"
|
||||
@click="${this.openDoc}"
|
||||
>${docName}</span
|
||||
>`
|
||||
: html` <affine-database-link-node
|
||||
.link="${linkText}"
|
||||
></affine-database-link-node>`}
|
||||
</div>
|
||||
${docName || linkText
|
||||
? html` <div class="affine-database-link-icon" @click="${this._onEdit}">
|
||||
${EditIcon()}
|
||||
</div>`
|
||||
: nothing}
|
||||
`;
|
||||
}
|
||||
|
||||
override updated() {
|
||||
if (this.value !== this.preValue) {
|
||||
const std = this.std;
|
||||
this.preValue = this.value;
|
||||
if (!this.value || !isValidUrl(this.value)) {
|
||||
this.docId = undefined;
|
||||
return;
|
||||
}
|
||||
|
||||
this.docId =
|
||||
std?.getOptional(ParseDocUrlProvider)?.parseDocUrl(this.value)?.docId ??
|
||||
undefined;
|
||||
docId$ = computed(() => {
|
||||
if (!this.value || !isValidUrl(this.value)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
return this.parseDocUrl(this.value)?.docId;
|
||||
});
|
||||
|
||||
@state()
|
||||
accessor docId: string | undefined = undefined;
|
||||
}
|
||||
private readonly _container = createRef<HTMLInputElement>();
|
||||
|
||||
export class LinkCellEditing extends BaseCellRenderer<string> {
|
||||
static override styles = css`
|
||||
affine-database-link-cell-editing {
|
||||
width: 100%;
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
.affine-database-link-editing {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
border: none;
|
||||
font-family: ${unsafeCSS(baseTheme.fontSansFamily)};
|
||||
color: var(--affine-text-primary-color);
|
||||
font-weight: 400;
|
||||
background-color: transparent;
|
||||
font-size: var(--data-view-cell-text-size);
|
||||
line-height: var(--data-view-cell-text-line-height);
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.affine-database-link-editing:focus {
|
||||
outline: none;
|
||||
}
|
||||
`;
|
||||
|
||||
private readonly _focusEnd = () => {
|
||||
const end = this._container.value.length;
|
||||
this._container.focus();
|
||||
this._container.setSelectionRange(end, end);
|
||||
};
|
||||
|
||||
private readonly _onKeydown = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Enter' && !e.isComposing) {
|
||||
this._setValue();
|
||||
setTimeout(() => {
|
||||
this.selectCurrentCell(false);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
private readonly _setValue = (value: string = this._container.value) => {
|
||||
let url = value;
|
||||
if (isValidUrl(value)) {
|
||||
url = normalizeUrl(value);
|
||||
}
|
||||
|
||||
this.onChange(url);
|
||||
this._container.value = url;
|
||||
};
|
||||
|
||||
override firstUpdated() {
|
||||
override afterEnterEditingMode() {
|
||||
this._focusEnd();
|
||||
}
|
||||
|
||||
override onExitEditMode() {
|
||||
override beforeExitEditingMode() {
|
||||
this._setValue();
|
||||
}
|
||||
|
||||
override render() {
|
||||
const linkText = this.value ?? '';
|
||||
|
||||
return html`<input
|
||||
class="affine-database-link-editing link"
|
||||
.value="${linkText}"
|
||||
@keydown="${this._onKeydown}"
|
||||
@pointerdown="${stopPropagation}"
|
||||
/>`;
|
||||
parseDocUrl(url: string) {
|
||||
return this.std?.getOptional(ParseDocUrlProvider)?.parseDocUrl(url);
|
||||
}
|
||||
|
||||
@query('.affine-database-link-editing')
|
||||
private accessor _container!: HTMLInputElement;
|
||||
docName$ = computed(() => {
|
||||
const title =
|
||||
this.docId$.value &&
|
||||
this.std?.workspace.getDoc(this.docId$.value)?.meta?.title;
|
||||
if (title == null) {
|
||||
return;
|
||||
}
|
||||
return title || 'Untitled';
|
||||
});
|
||||
|
||||
renderLink() {
|
||||
const linkText = this.value ?? '';
|
||||
const docName = this.docName$.value;
|
||||
const isDoc = !!docName;
|
||||
const isLink = !!linkText;
|
||||
const hasLink = isDoc || isLink;
|
||||
return html`
|
||||
<div>
|
||||
<div class="${linkContainerStyle}">
|
||||
${isDoc
|
||||
? html`<span class="${linkedDocStyle}" @click="${this.openDoc}"
|
||||
>${docName}</span
|
||||
>`
|
||||
: isValidUrl(linkText)
|
||||
? html`<a
|
||||
data-testid="property-link-a"
|
||||
class="${inlineLinkNodeStyle}"
|
||||
href="${linkText}"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>${linkText}</a
|
||||
>`
|
||||
: html`<span class="${normalTextStyle}">${linkText}</span>`}
|
||||
</div>
|
||||
${hasLink
|
||||
? html` <div class="${linkIconContainerStyle} ${showLinkIconStyle}">
|
||||
<div
|
||||
class="${linkIconStyle}"
|
||||
data-testid="edit-link-button"
|
||||
@click="${this._onEdit}"
|
||||
>
|
||||
${EditIcon()}
|
||||
</div>
|
||||
</div>`
|
||||
: nothing}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
override render() {
|
||||
if (this.isEditing$.value) {
|
||||
const linkText = this.value ?? '';
|
||||
return html`<input
|
||||
class="${linkEditingStyle} link"
|
||||
${ref(this._container)}
|
||||
.value="${linkText}"
|
||||
@keydown="${this._onKeydown}"
|
||||
@pointerdown="${stopPropagation}"
|
||||
/>`;
|
||||
} else {
|
||||
return this.renderLink();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const linkColumnConfig = linkPropertyModelConfig.createPropertyMeta({
|
||||
icon: createIcon('LinkIcon'),
|
||||
cellRenderer: {
|
||||
view: createFromBaseCellRenderer(LinkCell),
|
||||
edit: createFromBaseCellRenderer(LinkCellEditing),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
import { isValidUrl } from '@blocksuite/affine-shared/utils';
|
||||
import { ShadowlessElement } from '@blocksuite/block-std';
|
||||
import { css, html } from 'lit';
|
||||
import { property } from 'lit/decorators.js';
|
||||
|
||||
export class LinkNode extends ShadowlessElement {
|
||||
static override styles = css`
|
||||
.link-node {
|
||||
word-break: break-all;
|
||||
color: var(--affine-link-color);
|
||||
fill: var(--affine-link-color);
|
||||
cursor: pointer;
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
text-decoration: none;
|
||||
}
|
||||
`;
|
||||
|
||||
protected override render() {
|
||||
if (!isValidUrl(this.link)) {
|
||||
return html`<span class="normal-text">${this.link}</span>`;
|
||||
}
|
||||
|
||||
return html`<a
|
||||
class="link-node"
|
||||
href=${this.link}
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
><span class="link-node-text">${this.link}</span></a
|
||||
>`;
|
||||
}
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor link!: string;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'affine-database-link-node': LinkNode;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { style } from '@vanilla-extract/css';
|
||||
|
||||
export const richTextCellStyle = style({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
userSelect: 'none',
|
||||
});
|
||||
|
||||
export const richTextContainerStyle = style({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
outline: 'none',
|
||||
fontSize: 'var(--data-view-cell-text-size)',
|
||||
lineHeight: 'var(--data-view-cell-text-line-height)',
|
||||
wordBreak: 'break-all',
|
||||
});
|
||||
@@ -21,13 +21,16 @@ import { IS_MAC } from '@blocksuite/global/env';
|
||||
import type { DeltaInsert } from '@blocksuite/inline';
|
||||
import type { BlockSnapshot } from '@blocksuite/store';
|
||||
import { Text } from '@blocksuite/store';
|
||||
import { css } from 'lit';
|
||||
import { query } from 'lit/decorators.js';
|
||||
import { keyed } from 'lit/directives/keyed.js';
|
||||
import { computed, effect, signal } from '@preact/signals-core';
|
||||
import { ref } from 'lit/directives/ref.js';
|
||||
import { html } from 'lit/static-html.js';
|
||||
|
||||
import { HostContextKey } from '../../context/host-context.js';
|
||||
import type { DatabaseBlockComponent } from '../../database-block.js';
|
||||
import {
|
||||
richTextCellStyle,
|
||||
richTextContainerStyle,
|
||||
} from './cell-renderer.css.js';
|
||||
import { richTextPropertyModelConfig } from './define.js';
|
||||
|
||||
function toggleStyle(
|
||||
@@ -78,61 +81,10 @@ function toggleStyle(
|
||||
inlineEditor.syncInlineRange();
|
||||
}
|
||||
|
||||
abstract class BaseRichTextCell extends BaseCellRenderer<Text> {
|
||||
static override styles = css`
|
||||
affine-database-rich-text-cell,
|
||||
affine-database-rich-text-cell-editing {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.affine-database-rich-text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
outline: none;
|
||||
font-size: var(--data-view-cell-text-size);
|
||||
line-height: var(--data-view-cell-text-line-height);
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.affine-database-rich-text v-line {
|
||||
display: flex !important;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.affine-database-rich-text v-line > div {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.data-view-header-area-icon {
|
||||
height: max-content;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 8px;
|
||||
padding: 2px;
|
||||
border-radius: 4px;
|
||||
margin-top: 2px;
|
||||
background-color: var(--affine-background-secondary-color);
|
||||
}
|
||||
|
||||
.data-view-header-area-icon svg {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
fill: var(--affine-icon-color);
|
||||
color: var(--affine-icon-color);
|
||||
}
|
||||
`;
|
||||
|
||||
get inlineEditor() {
|
||||
return this.richText?.inlineEditor;
|
||||
}
|
||||
export class RichTextCell extends BaseCellRenderer<Text> {
|
||||
inlineEditor$ = computed(() => {
|
||||
return this.richText$.value?.inlineEditor;
|
||||
});
|
||||
|
||||
get inlineManager() {
|
||||
return this.view
|
||||
@@ -146,57 +98,11 @@ abstract class BaseRichTextCell extends BaseCellRenderer<Text> {
|
||||
return databaseBlock?.topContenteditableElement;
|
||||
}
|
||||
|
||||
get attributeRenderer() {
|
||||
return this.inlineManager?.getRenderer();
|
||||
}
|
||||
|
||||
get attributesSchema() {
|
||||
return this.inlineManager?.getSchema();
|
||||
}
|
||||
|
||||
get host() {
|
||||
return this.view.contextGet(HostContextKey);
|
||||
}
|
||||
|
||||
@query('rich-text')
|
||||
accessor richText!: RichText;
|
||||
|
||||
@query('.affine-database-rich-text')
|
||||
accessor _richTextElement!: HTMLElement;
|
||||
}
|
||||
|
||||
export class RichTextCell extends BaseRichTextCell {
|
||||
static override styles = css`
|
||||
affine-database-rich-text-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.affine-database-rich-text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
outline: none;
|
||||
font-size: var(--data-view-cell-text-size);
|
||||
line-height: var(--data-view-cell-text-line-height);
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.affine-database-rich-text v-line {
|
||||
display: flex !important;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.affine-database-rich-text v-line > div {
|
||||
flex-grow: 1;
|
||||
}
|
||||
`;
|
||||
private readonly richText$ = signal<RichText>();
|
||||
|
||||
private changeUserSelectAccordToReadOnly() {
|
||||
if (this && this instanceof HTMLElement) {
|
||||
@@ -204,61 +110,6 @@ export class RichTextCell extends BaseRichTextCell {
|
||||
}
|
||||
}
|
||||
|
||||
override connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.changeUserSelectAccordToReadOnly();
|
||||
}
|
||||
|
||||
override render() {
|
||||
if (!this.value || !(this.value instanceof Text)) {
|
||||
return html`<div class="affine-database-rich-text"></div>`;
|
||||
}
|
||||
return keyed(
|
||||
this.value,
|
||||
html`<rich-text
|
||||
.yText=${this.value}
|
||||
.attributesSchema=${this.attributesSchema}
|
||||
.attributeRenderer=${this.attributeRenderer}
|
||||
.embedChecker=${this.inlineManager?.embedChecker}
|
||||
.markdownMatches=${this.inlineManager?.markdownMatches}
|
||||
.readonly=${true}
|
||||
class="affine-database-rich-text inline-editor"
|
||||
></rich-text>`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class RichTextCellEditing extends BaseRichTextCell {
|
||||
static override styles = css`
|
||||
affine-database-rich-text-cell-editing {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
min-width: 1px;
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
.affine-database-rich-text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.affine-database-rich-text v-line {
|
||||
display: flex !important;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.affine-database-rich-text v-line > div {
|
||||
flex-grow: 1;
|
||||
}
|
||||
`;
|
||||
|
||||
private readonly _handleKeyDown = (event: KeyboardEvent) => {
|
||||
if (event.key !== 'Escape') {
|
||||
if (event.key === 'Tab') {
|
||||
@@ -280,7 +131,7 @@ export class RichTextCellEditing extends BaseRichTextCell {
|
||||
return;
|
||||
}
|
||||
|
||||
const inlineEditor = this.inlineEditor;
|
||||
const inlineEditor = this.inlineEditor$.value;
|
||||
if (!inlineEditor) return;
|
||||
|
||||
switch (event.key) {
|
||||
@@ -331,17 +182,17 @@ export class RichTextCellEditing extends BaseRichTextCell {
|
||||
|
||||
private readonly _initYText = (text?: string) => {
|
||||
const yText = new Text(text);
|
||||
this.onChange(yText);
|
||||
this.valueSetImmediate(yText);
|
||||
};
|
||||
|
||||
private readonly _onSoftEnter = () => {
|
||||
if (this.value && this.inlineEditor) {
|
||||
const inlineRange = this.inlineEditor.getInlineRange();
|
||||
if (this.value && this.inlineEditor$.value) {
|
||||
const inlineRange = this.inlineEditor$.value.getInlineRange();
|
||||
if (!inlineRange) return;
|
||||
|
||||
const text = new Text(this.inlineEditor.yText);
|
||||
const text = new Text(this.inlineEditor$.value.yText);
|
||||
text.replace(inlineRange.index, inlineRange.length, '\n');
|
||||
this.inlineEditor.setInlineRange({
|
||||
this.inlineEditor$.value.setInlineRange({
|
||||
index: inlineRange.index + 1,
|
||||
length: 0,
|
||||
});
|
||||
@@ -349,7 +200,7 @@ export class RichTextCellEditing extends BaseRichTextCell {
|
||||
};
|
||||
|
||||
private readonly _onCopy = (e: ClipboardEvent) => {
|
||||
const inlineEditor = this.inlineEditor;
|
||||
const inlineEditor = this.inlineEditor$.value;
|
||||
if (!inlineEditor) return;
|
||||
|
||||
const inlineRange = inlineEditor.getInlineRange();
|
||||
@@ -366,7 +217,7 @@ export class RichTextCellEditing extends BaseRichTextCell {
|
||||
};
|
||||
|
||||
private readonly _onCut = (e: ClipboardEvent) => {
|
||||
const inlineEditor = this.inlineEditor;
|
||||
const inlineEditor = this.inlineEditor$.value;
|
||||
if (!inlineEditor) return;
|
||||
|
||||
const inlineRange = inlineEditor.getInlineRange();
|
||||
@@ -388,7 +239,9 @@ export class RichTextCellEditing extends BaseRichTextCell {
|
||||
};
|
||||
|
||||
private readonly _onPaste = (e: ClipboardEvent) => {
|
||||
const inlineEditor = this.inlineEditor;
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const inlineEditor = this.inlineEditor$.value;
|
||||
if (!inlineEditor) return;
|
||||
|
||||
const inlineRange = inlineEditor.getInlineRange();
|
||||
@@ -419,8 +272,7 @@ export class RichTextCellEditing extends BaseRichTextCell {
|
||||
?.getData('text/plain')
|
||||
?.replace(/\r?\n|\r/g, '\n');
|
||||
if (!text) return;
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
if (isValidUrl(text)) {
|
||||
const std = this.std;
|
||||
const result = std?.getOptional(ParseDocUrlProvider)?.parseDocUrl(text);
|
||||
@@ -459,6 +311,7 @@ export class RichTextCellEditing extends BaseRichTextCell {
|
||||
});
|
||||
}
|
||||
} else {
|
||||
console.log(text);
|
||||
inlineEditor.insertText(inlineRange, text);
|
||||
inlineEditor.setInlineRange({
|
||||
index: inlineRange.index + text.length,
|
||||
@@ -469,67 +322,78 @@ export class RichTextCellEditing extends BaseRichTextCell {
|
||||
|
||||
override connectedCallback() {
|
||||
super.connectedCallback();
|
||||
if (!this.value || typeof this.value === 'string') {
|
||||
this._initYText(this.value);
|
||||
}
|
||||
this.classList.add(richTextCellStyle);
|
||||
|
||||
this.changeUserSelectAccordToReadOnly();
|
||||
|
||||
const selectAll = (e: KeyboardEvent) => {
|
||||
if (e.key === 'a' && (IS_MAC ? e.metaKey : e.ctrlKey)) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
this.inlineEditor?.selectAll();
|
||||
this.inlineEditor$.value?.selectAll();
|
||||
}
|
||||
};
|
||||
this.addEventListener('keydown', selectAll);
|
||||
this.disposables.addFromEvent(this, 'keydown', selectAll);
|
||||
this.disposables.add(
|
||||
effect(() => {
|
||||
const editor = this.inlineEditor$.value;
|
||||
if (editor) {
|
||||
const disposable = editor.slots.keydown.on(this._handleKeyDown);
|
||||
return () => disposable.dispose();
|
||||
}
|
||||
return;
|
||||
})
|
||||
);
|
||||
this.disposables.add(
|
||||
effect(() => {
|
||||
const richText = this.richText$.value;
|
||||
if (richText) {
|
||||
richText.addEventListener('copy', this._onCopy, true);
|
||||
richText.addEventListener('cut', this._onCut, true);
|
||||
richText.addEventListener('paste', this._onPaste, true);
|
||||
return () => {
|
||||
richText.removeEventListener('copy', this._onCopy);
|
||||
richText.removeEventListener('cut', this._onCut);
|
||||
richText.removeEventListener('paste', this._onPaste);
|
||||
};
|
||||
}
|
||||
return;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
override firstUpdated() {
|
||||
this.richText?.updateComplete
|
||||
.then(() => {
|
||||
const inlineEditor = this.inlineEditor;
|
||||
if (!inlineEditor) return;
|
||||
override beforeEnterEditMode() {
|
||||
if (!this.value || typeof this.value === 'string') {
|
||||
this._initYText(this.value);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
this.disposables.add(
|
||||
inlineEditor.slots.keydown.on(this._handleKeyDown)
|
||||
);
|
||||
|
||||
this.disposables.addFromEvent(
|
||||
this._richTextElement!,
|
||||
'copy',
|
||||
this._onCopy
|
||||
);
|
||||
this.disposables.addFromEvent(
|
||||
this._richTextElement!,
|
||||
'cut',
|
||||
this._onCut
|
||||
);
|
||||
this.disposables.addFromEvent(
|
||||
this._richTextElement!,
|
||||
'paste',
|
||||
this._onPaste
|
||||
);
|
||||
|
||||
inlineEditor.focusEnd();
|
||||
})
|
||||
.catch(console.error);
|
||||
override afterEnterEditingMode() {
|
||||
this.inlineEditor$.value?.focusEnd();
|
||||
}
|
||||
|
||||
override render() {
|
||||
return html`<rich-text
|
||||
if (!this.value || !(this.value instanceof Text)) {
|
||||
return html` <div class="${richTextContainerStyle}"></div>`;
|
||||
}
|
||||
return html` <rich-text
|
||||
${ref(this.richText$)}
|
||||
data-disable-ask-ai
|
||||
data-not-block-text
|
||||
.yText=${this.value}
|
||||
.inlineEventSource=${this.topContenteditableElement}
|
||||
.attributesSchema=${this.attributesSchema}
|
||||
.attributeRenderer=${this.attributeRenderer}
|
||||
.embedChecker=${this.inlineManager?.embedChecker}
|
||||
.markdownMatches=${this.inlineManager?.markdownMatches}
|
||||
.verticalScrollContainerGetter=${() =>
|
||||
.yText="${this.value}"
|
||||
.inlineEventSource="${this.topContenteditableElement}"
|
||||
.attributesSchema="${this.inlineManager?.getSchema()}"
|
||||
.attributeRenderer="${this.inlineManager?.getRenderer()}"
|
||||
.embedChecker="${this.inlineManager?.embedChecker}"
|
||||
.markdownMatches="${this.inlineManager?.markdownMatches}"
|
||||
.readonly="${!this.isEditing$.value || this.readonly}"
|
||||
.verticalScrollContainerGetter="${() =>
|
||||
this.topContenteditableElement?.host
|
||||
? getViewportElement(this.topContenteditableElement.host)
|
||||
: null}
|
||||
class="affine-database-rich-text inline-editor"
|
||||
: null}"
|
||||
class="${richTextContainerStyle} inline-editor"
|
||||
></rich-text>`;
|
||||
}
|
||||
|
||||
@@ -538,7 +402,7 @@ export class RichTextCellEditing extends BaseRichTextCell {
|
||||
}
|
||||
|
||||
insertDelta = (delta: DeltaInsert<AffineTextAttributes>) => {
|
||||
const inlineEditor = this.inlineEditor;
|
||||
const inlineEditor = this.inlineEditor$.value;
|
||||
const range = inlineEditor?.getInlineRange();
|
||||
if (!range || !delta.insert) {
|
||||
return;
|
||||
@@ -553,7 +417,7 @@ export class RichTextCellEditing extends BaseRichTextCell {
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'affine-database-rich-text-cell-editing': RichTextCellEditing;
|
||||
'affine-database-rich-text-cell': RichTextCell;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -563,6 +427,5 @@ export const richTextColumnConfig =
|
||||
|
||||
cellRenderer: {
|
||||
view: createFromBaseCellRenderer(RichTextCell),
|
||||
edit: createFromBaseCellRenderer(RichTextCellEditing),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import { cssVarV2 } from '@blocksuite/affine-shared/theme';
|
||||
import { style } from '@vanilla-extract/css';
|
||||
|
||||
export const titleCellStyle = style({
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
});
|
||||
|
||||
export const titleRichTextStyle = style({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
outline: 'none',
|
||||
wordBreak: 'break-all',
|
||||
fontSize: 'var(--data-view-cell-text-size)',
|
||||
lineHeight: 'var(--data-view-cell-text-line-height)',
|
||||
});
|
||||
|
||||
export const headerAreaIconStyle = style({
|
||||
height: 'max-content',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
marginRight: '8px',
|
||||
padding: '2px',
|
||||
borderRadius: '4px',
|
||||
marginTop: '2px',
|
||||
color: cssVarV2.icon.primary,
|
||||
backgroundColor: 'var(--affine-background-secondary-color)',
|
||||
});
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
import { TableSingleView } from '@blocksuite/data-view/view-presets';
|
||||
|
||||
import { titlePropertyModelConfig } from './define.js';
|
||||
import { HeaderAreaTextCell, HeaderAreaTextCellEditing } from './text.js';
|
||||
import { HeaderAreaTextCell } from './text.js';
|
||||
|
||||
export const titleColumnConfig = titlePropertyModelConfig.createPropertyMeta({
|
||||
icon: createIcon('TitleIcon'),
|
||||
@@ -19,12 +19,5 @@ export const titleColumnConfig = titlePropertyModelConfig.createPropertyMeta({
|
||||
showIcon: props.cell.view instanceof TableSingleView,
|
||||
})
|
||||
),
|
||||
edit: uniMap(
|
||||
createFromBaseCellRenderer(HeaderAreaTextCellEditing),
|
||||
(props: CellRenderProps) => ({
|
||||
...props,
|
||||
showIcon: props.cell.view instanceof TableSingleView,
|
||||
})
|
||||
),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import type { RootBlockModel } from '@blocksuite/affine-model';
|
||||
import {
|
||||
DefaultInlineManagerExtension,
|
||||
type RichText,
|
||||
@@ -16,106 +15,31 @@ import { IS_MAC } from '@blocksuite/global/env';
|
||||
import { LinkedPageIcon } from '@blocksuite/icons/lit';
|
||||
import type { DeltaInsert } from '@blocksuite/inline';
|
||||
import type { BlockSnapshot, Text } from '@blocksuite/store';
|
||||
import { computed, effect, signal } from '@preact/signals-core';
|
||||
import { css, type TemplateResult } from 'lit';
|
||||
import { property, query } from 'lit/decorators.js';
|
||||
import { signal } from '@preact/signals-core';
|
||||
import { property } from 'lit/decorators.js';
|
||||
import { createRef, ref } from 'lit/directives/ref.js';
|
||||
import { html } from 'lit/static-html.js';
|
||||
|
||||
import { HostContextKey } from '../../context/host-context.js';
|
||||
import type { DatabaseBlockComponent } from '../../database-block.js';
|
||||
import { getSingleDocIdFromText } from '../../utils/title-doc.js';
|
||||
import {
|
||||
headerAreaIconStyle,
|
||||
titleCellStyle,
|
||||
titleRichTextStyle,
|
||||
} from './cell-renderer.css.js';
|
||||
|
||||
const styles = css`
|
||||
data-view-header-area-text {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
data-view-header-area-text rich-text {
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
data-view-header-area-text-editing {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
.data-view-header-area-rich-text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
outline: none;
|
||||
word-break: break-all;
|
||||
font-size: var(--data-view-cell-text-size);
|
||||
line-height: var(--data-view-cell-text-line-height);
|
||||
}
|
||||
|
||||
.data-view-header-area-rich-text v-line {
|
||||
display: flex !important;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.data-view-header-area-rich-text v-line > div {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.data-view-header-area-icon {
|
||||
height: max-content;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 8px;
|
||||
padding: 2px;
|
||||
border-radius: 4px;
|
||||
margin-top: 2px;
|
||||
background-color: var(--affine-background-secondary-color);
|
||||
}
|
||||
|
||||
.data-view-header-area-icon svg {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
fill: var(--affine-icon-color);
|
||||
color: var(--affine-icon-color);
|
||||
}
|
||||
`;
|
||||
|
||||
abstract class BaseTextCell extends BaseCellRenderer<Text> {
|
||||
static override styles = styles;
|
||||
|
||||
export class HeaderAreaTextCell extends BaseCellRenderer<Text> {
|
||||
activity = true;
|
||||
|
||||
docId$ = signal<string>();
|
||||
|
||||
isLinkedDoc$ = computed(() => false);
|
||||
|
||||
linkedDocTitle$ = computed(() => {
|
||||
if (!this.docId$.value) {
|
||||
return this.value;
|
||||
}
|
||||
const doc = this.host?.std.workspace.getDoc(this.docId$.value);
|
||||
const root = doc?.root as RootBlockModel;
|
||||
return root.title;
|
||||
});
|
||||
|
||||
get attributeRenderer() {
|
||||
return this.inlineManager?.getRenderer();
|
||||
}
|
||||
|
||||
get attributesSchema() {
|
||||
return this.inlineManager?.getSchema();
|
||||
}
|
||||
|
||||
get host() {
|
||||
return this.view.contextGet(HostContextKey);
|
||||
}
|
||||
|
||||
get inlineEditor() {
|
||||
return this.richText.inlineEditor;
|
||||
return this.richText.value?.inlineEditor;
|
||||
}
|
||||
|
||||
get inlineManager() {
|
||||
@@ -128,80 +52,10 @@ abstract class BaseTextCell extends BaseCellRenderer<Text> {
|
||||
return databaseBlock?.topContenteditableElement;
|
||||
}
|
||||
|
||||
override connectedCallback() {
|
||||
super.connectedCallback();
|
||||
const yText = this.value?.yText;
|
||||
if (yText) {
|
||||
const cb = () => {
|
||||
const id = getSingleDocIdFromText(this.value);
|
||||
this.docId$.value = id;
|
||||
};
|
||||
cb();
|
||||
if (this.activity) {
|
||||
yText.observe(cb);
|
||||
this.disposables.add(() => {
|
||||
yText.unobserve(cb);
|
||||
});
|
||||
}
|
||||
}
|
||||
get std() {
|
||||
return this.view.contextGet(HostContextKey)?.std;
|
||||
}
|
||||
|
||||
protected override render(): unknown {
|
||||
return html`${this.renderIcon()}${this.renderBlockText()}`;
|
||||
}
|
||||
|
||||
abstract renderBlockText(): TemplateResult;
|
||||
|
||||
renderIcon() {
|
||||
if (this.docId$.value) {
|
||||
return html` <div class="data-view-header-area-icon">
|
||||
${LinkedPageIcon()}
|
||||
</div>`;
|
||||
}
|
||||
if (!this.showIcon) {
|
||||
return;
|
||||
}
|
||||
const iconColumn = this.view.mainProperties$.value.iconColumn;
|
||||
if (!iconColumn) return;
|
||||
|
||||
const icon = this.view.cellValueGet(this.cell.rowId, iconColumn) as string;
|
||||
if (!icon) return;
|
||||
|
||||
return html` <div class="data-view-header-area-icon">${icon}</div>`;
|
||||
}
|
||||
|
||||
abstract renderLinkedDoc(): TemplateResult;
|
||||
|
||||
@query('rich-text')
|
||||
accessor richText!: RichText;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor showIcon = false;
|
||||
}
|
||||
|
||||
export class HeaderAreaTextCell extends BaseTextCell {
|
||||
override renderBlockText() {
|
||||
return html` <rich-text
|
||||
.yText="${this.value}"
|
||||
.attributesSchema="${this.attributesSchema}"
|
||||
.attributeRenderer="${this.attributeRenderer}"
|
||||
.embedChecker="${this.inlineManager?.embedChecker}"
|
||||
.markdownMatches="${this.inlineManager?.markdownMatches}"
|
||||
.readonly="${true}"
|
||||
class="data-view-header-area-rich-text"
|
||||
></rich-text>`;
|
||||
}
|
||||
|
||||
override renderLinkedDoc(): TemplateResult {
|
||||
return html` <rich-text
|
||||
.yText="${this.linkedDocTitle$.value}"
|
||||
.readonly="${true}"
|
||||
class="data-view-header-area-rich-text"
|
||||
></rich-text>`;
|
||||
}
|
||||
}
|
||||
|
||||
export class HeaderAreaTextCellEditing extends BaseTextCell {
|
||||
private readonly _onCopy = (e: ClipboardEvent) => {
|
||||
const inlineEditor = this.inlineEditor;
|
||||
if (!inlineEditor) return;
|
||||
@@ -318,8 +172,6 @@ export class HeaderAreaTextCellEditing extends BaseTextCell {
|
||||
}
|
||||
};
|
||||
|
||||
override activity = false;
|
||||
|
||||
insertDelta = (delta: DeltaInsert) => {
|
||||
const inlineEditor = this.inlineEditor;
|
||||
const range = inlineEditor?.getInlineRange();
|
||||
@@ -333,12 +185,25 @@ export class HeaderAreaTextCellEditing extends BaseTextCell {
|
||||
});
|
||||
};
|
||||
|
||||
private get std() {
|
||||
return this.host?.std;
|
||||
}
|
||||
|
||||
override connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.classList.add(titleCellStyle);
|
||||
|
||||
const yText = this.value?.yText;
|
||||
if (yText) {
|
||||
const cb = () => {
|
||||
const id = getSingleDocIdFromText(this.value);
|
||||
this.docId$.value = id;
|
||||
};
|
||||
cb();
|
||||
if (this.activity) {
|
||||
yText.observe(cb);
|
||||
this.disposables.add(() => {
|
||||
yText.unobserve(cb);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const selectAll = (e: KeyboardEvent) => {
|
||||
if (e.key === 'a' && (IS_MAC ? e.metaKey : e.ctrlKey)) {
|
||||
e.stopPropagation();
|
||||
@@ -346,82 +211,86 @@ export class HeaderAreaTextCellEditing extends BaseTextCell {
|
||||
this.inlineEditor?.selectAll();
|
||||
}
|
||||
};
|
||||
|
||||
this.addEventListener('keydown', selectAll);
|
||||
this.disposables.add(() => {
|
||||
this.removeEventListener('keydown', selectAll);
|
||||
});
|
||||
this.disposables.addFromEvent(this, 'keydown', selectAll);
|
||||
}
|
||||
|
||||
override firstUpdated(props: Map<string, unknown>) {
|
||||
super.firstUpdated(props);
|
||||
if (!this.isLinkedDoc$.value) {
|
||||
this.disposables.addFromEvent(this.richText, 'copy', this._onCopy);
|
||||
this.disposables.addFromEvent(this.richText, 'cut', this._onCut);
|
||||
this.disposables.addFromEvent(this.richText, 'paste', this._onPaste);
|
||||
}
|
||||
this.richText.updateComplete
|
||||
this.richText.value?.updateComplete
|
||||
.then(() => {
|
||||
this.inlineEditor?.focusEnd();
|
||||
|
||||
this.disposables.add(
|
||||
effect(() => {
|
||||
const inlineRange = this.inlineEditor?.inlineRange$.value;
|
||||
if (inlineRange) {
|
||||
if (!this.isEditing) {
|
||||
this.selectCurrentCell(true);
|
||||
}
|
||||
} else {
|
||||
if (this.isEditing) {
|
||||
this.selectCurrentCell(false);
|
||||
}
|
||||
}
|
||||
})
|
||||
this.disposables.addFromEvent(
|
||||
this.richText.value,
|
||||
'copy',
|
||||
this._onCopy
|
||||
);
|
||||
this.disposables.addFromEvent(this.richText.value, 'cut', this._onCut);
|
||||
this.disposables.addFromEvent(
|
||||
this.richText.value,
|
||||
'paste',
|
||||
this._onPaste
|
||||
);
|
||||
})
|
||||
.catch(console.error);
|
||||
}
|
||||
|
||||
override renderBlockText() {
|
||||
override afterEnterEditingMode() {
|
||||
this.inlineEditor?.focusEnd();
|
||||
}
|
||||
|
||||
protected override render(): unknown {
|
||||
return html`${this.renderIcon()}${this.renderBlockText()}`;
|
||||
}
|
||||
|
||||
renderBlockText() {
|
||||
return html` <rich-text
|
||||
${ref(this.richText)}
|
||||
data-disable-ask-ai
|
||||
data-not-block-text
|
||||
.yText="${this.value}"
|
||||
.inlineEventSource="${this.topContenteditableElement}"
|
||||
.attributesSchema="${this.attributesSchema}"
|
||||
.attributeRenderer="${this.attributeRenderer}"
|
||||
.attributesSchema="${this.inlineManager?.getSchema()}"
|
||||
.attributeRenderer="${this.inlineManager?.getRenderer()}"
|
||||
.embedChecker="${this.inlineManager?.embedChecker}"
|
||||
.markdownMatches="${this.inlineManager?.markdownMatches}"
|
||||
.readonly="${this.readonly}"
|
||||
.readonly="${!this.isEditing$.value}"
|
||||
.enableClipboard="${false}"
|
||||
.verticalScrollContainerGetter="${() =>
|
||||
this.topContenteditableElement?.host
|
||||
? getViewportElement(this.topContenteditableElement.host)
|
||||
: null}"
|
||||
data-parent-flavour="affine:database"
|
||||
class="data-view-header-area-rich-text"
|
||||
class="${titleRichTextStyle}"
|
||||
></rich-text>`;
|
||||
}
|
||||
|
||||
override renderLinkedDoc(): TemplateResult {
|
||||
return html` <rich-text
|
||||
data-disable-ask-ai
|
||||
data-not-block-text
|
||||
.yText="${this.linkedDocTitle$.value}"
|
||||
.inlineEventSource="${this.topContenteditableElement}"
|
||||
.readonly="${this.readonly}"
|
||||
.enableClipboard="${true}"
|
||||
.verticalScrollContainerGetter="${() =>
|
||||
this.topContenteditableElement?.host
|
||||
? getViewportElement(this.topContenteditableElement.host)
|
||||
: null}"
|
||||
class="data-view-header-area-rich-text"
|
||||
></rich-text>`;
|
||||
renderIcon() {
|
||||
if (!this.showIcon) {
|
||||
return;
|
||||
}
|
||||
if (this.docId$.value) {
|
||||
return html` <div class="${headerAreaIconStyle}">
|
||||
${LinkedPageIcon({})}
|
||||
</div>`;
|
||||
}
|
||||
const iconColumn = this.view.mainProperties$.value.iconColumn;
|
||||
if (!iconColumn) return;
|
||||
|
||||
const icon = this.view.cellValueGet(this.cell.rowId, iconColumn) as string;
|
||||
if (!icon) return;
|
||||
|
||||
return html` <div class="${headerAreaIconStyle}">${icon}</div>`;
|
||||
}
|
||||
|
||||
private readonly richText = createRef<RichText>();
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor showIcon = false;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'data-view-header-area-text': HeaderAreaTextCell;
|
||||
'data-view-header-area-text-editing': HeaderAreaTextCellEditing;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user