Skip to content

Commit

Permalink
feat: linked doc supports aliases
Browse files Browse the repository at this point in the history
  • Loading branch information
fundon committed Nov 28, 2024
1 parent 293269d commit e3322a3
Show file tree
Hide file tree
Showing 8 changed files with 371 additions and 43 deletions.
3 changes: 3 additions & 0 deletions packages/affine/components/src/rich-text/effects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { LatexEditorMenu } from './inline/presets/nodes/latex-node/latex-editor-
import { LatexEditorUnit } from './inline/presets/nodes/latex-node/latex-editor-unit.js';
import { AffineLatexNode } from './inline/presets/nodes/latex-node/latex-node.js';
import { LinkPopup } from './inline/presets/nodes/link-node/link-popup/link-popup.js';
import { ReferenceAliasPopup } from './inline/presets/nodes/reference-node/reference-alias-popup.js';
import { ReferencePopup } from './inline/presets/nodes/reference-node/reference-popup.js';
import { RichText } from './rich-text.js';

Expand All @@ -34,6 +35,7 @@ export function effects() {
customElements.define('link-popup', LinkPopup);
customElements.define('affine-link', AffineLink);
customElements.define('reference-popup', ReferencePopup);
customElements.define('reference-alias-popup', ReferenceAliasPopup);
customElements.define('affine-reference', AffineReference);
}

Expand All @@ -45,6 +47,7 @@ declare global {
'affine-text': AffineText;
'rich-text': RichText;
'reference-popup': ReferencePopup;
'reference-alias-popup': ReferenceAliasPopup;
'latex-editor-unit': LatexEditorUnit;
'latex-editor-menu': LatexEditorMenu;
'link-popup': LinkPopup;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
getHostName,
isValidUrl,
normalizeUrl,
stopPropagation,
} from '@blocksuite/affine-shared/utils';
import { BLOCK_ID_ATTR, type BlockComponent } from '@blocksuite/block-std';
import { WithDisposable } from '@blocksuite/global/utils';
Expand Down Expand Up @@ -174,7 +175,7 @@ export class LinkPopup extends WithDisposable(LitElement) {
<editor-icon-button
aria-label="Copy"
data-testid="copy-link"
.tooltip=${'Click to copy link'}
.tooltip=${'Copy link'}
@click=${this._copyUrl}
>
${CopyIcon}
Expand Down Expand Up @@ -528,15 +529,9 @@ export class LinkPopup extends WithDisposable(LitElement) {
protected override firstUpdated() {
if (!this.linkInput) return;

this._disposables.addFromEvent(this.linkInput, 'copy', e => {
e.stopPropagation();
});
this._disposables.addFromEvent(this.linkInput, 'cut', e => {
e.stopPropagation();
});
this._disposables.addFromEvent(this.linkInput, 'paste', e => {
e.stopPropagation();
});
this._disposables.addFromEvent(this.linkInput, 'copy', stopPropagation);
this._disposables.addFromEvent(this.linkInput, 'cut', stopPropagation);
this._disposables.addFromEvent(this.linkInput, 'paste', stopPropagation);
}

override render() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,266 @@
import type { ReferenceInfo } from '@blocksuite/affine-model';
import type { DeltaInsert, InlineRange } from '@blocksuite/inline';

import { FONT_XS, PANEL_BASE } from '@blocksuite/affine-shared/styles';
import { ShadowlessElement } from '@blocksuite/block-std';
import {
assertExists,
SignalWatcher,
WithDisposable,
} from '@blocksuite/global/utils';
import { computePosition, inline, offset, shift } from '@floating-ui/dom';
import { css, html } from 'lit';
import { property, query } from 'lit/decorators.js';

import type { EditorIconButton } from '../../../../../toolbar/index.js';
import type { AffineTextAttributes } from '../../../../extension/type.js';
import type { AffineInlineEditor } from '../../affine-inline-specs.js';

import { ConfirmIcon } from '../../../../../icons/text.js';

export class ReferenceAliasPopup extends SignalWatcher(
WithDisposable(ShadowlessElement)
) {
static override styles = css`
:host {
box-sizing: border-box;
}
.overlay-mask {
position: fixed;
z-index: var(--affine-z-index-popover);
top: 0;
left: 0;
width: 100vw;
height: 100vh;
}
.alias-popup-form {
${PANEL_BASE};
position: absolute;
display: flex;
width: 320px;
gap: 8px 12px;
padding: 12px;
box-sizing: content-box;
justify-items: center;
align-items: center;
animation: affine-popover-fade-in 0.2s ease;
z-index: var(--affine-z-index-popover);
}
@keyframes affine-popover-fade-in {
from {
opacity: 0;
transform: translateY(-3px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.row {
width: 100%;
display: flex;
flex-direction: row;
justify-content: space-between;
}
.row > .col {
display: flex;
align-items: center;
}
.row > .col.form-item {
display: grid;
gap: 8px;
grid-template-columns: 26px auto;
grid-template-rows: repeat(1, 1fr);
grid-template-areas: 'label input';
user-select: none;
box-sizing: border-box;
}
.form-item {
width: 280px;
padding: 4px 10px;
display: grid;
gap: 8px;
grid-template-columns: 26px auto;
grid-template-rows: repeat(1, 1fr);
grid-template-areas: 'label input';
user-select: none;
box-sizing: border-box;
border: 1px solid var(--affine-border-color);
box-sizing: border-box;
outline: none;
border-radius: 4px;
background: transparent;
}
.form-item:focus-within {
border-color: var(--affine-blue-700);
box-shadow: var(--affine-active-shadow);
}
label {
grid-area: label;
box-sizing: border-box;
color: var(--affine-icon-color);
${FONT_XS};
font-weight: 400;
}
input {
grid-area: input;
color: inherit;
padding: 0;
border: none;
background: transparent;
color: var(--affine-text-primary-color);
${FONT_XS};
}
input::placeholder {
color: var(--affine-placeholder-color);
}
input:focus {
outline: none;
}
input:focus ~ label,
input:active ~ label {
color: var(--affine-primary-color);
}
`;

private _onConfirm = () => {
const title = this.titleInput.value.trim();
if (!title) return;

const text = ' ';
this.inlineEditor.insertText(this.inlineRange, text, {
reference: {
...this.referenceInfo,
type: 'LinkedPage',
title,
},
});
this.inlineEditor.setInlineRange({
index: this.inlineRange.index + text.length,
length: 0,
});
this.remove();
};

private _onKeydown(e: KeyboardEvent) {
e.stopPropagation();
if (e.key === 'Enter' && !e.isComposing) {
e.preventDefault();
this._onConfirm();
}
}

private _updateConfirmBtn() {
const value = this.titleInput.value.trim();
const disabled = !value;
this.confirmBtn.disabled = disabled;
this.confirmBtn.active = !disabled;
this.confirmBtn.requestUpdate();
}

override firstUpdated() {
this.disposables.addFromEvent(this.overlayMask, 'click', e => {
e.stopPropagation();
this.remove();
});

this.titleInput.value = this.referenceInfo.title ?? this.docTitle;
this.titleInput.select();
this._updateConfirmBtn();
}

override render() {
return html`
<div class="overlay-root">
<div class="overlay-mask"></div>
<div class="alias-popup-container" @keydown=${this._onKeydown}>
<div class="alias-popup-form">
<div class="row">
<div class="col form-item">
<input
id="alias-title"
type="text"
placeholder="Enter title"
@input=${this._updateConfirmBtn}
/>
<label for="alias-title">Title</label>
</div>
<div class="col">
<editor-icon-button
class="confirm"
.iconSize=${'24px'}
.disabled=${true}
@click=${this._onConfirm}
>
${ConfirmIcon}
</editor-icon-button>
</div>
</div>
</div>
</div>
</div>
`;
}

override updated() {
const range = this.inlineEditor.toDomRange(this.inlineRange);
assertExists(range);

const visualElement = {
getBoundingClientRect: () => range.getBoundingClientRect(),
getClientRects: () => range.getClientRects(),
};
computePosition(visualElement, this.popupContainer, {
middleware: [
offset(10),
inline(),
shift({
padding: 6,
}),
],
})
.then(({ x, y }) => {
const popupContainer = this.popupContainer;
if (!popupContainer) return;
popupContainer.style.left = `${x}px`;
popupContainer.style.top = `${y}px`;
})
.catch(console.error);
}

@query('.alias-popup-form editor-icon-button.confirm')
accessor confirmBtn!: EditorIconButton;

@property({ type: Object })
accessor delta!: DeltaInsert<AffineTextAttributes>;

@property({ attribute: false })
accessor docTitle!: string;

@property({ attribute: false })
accessor inlineEditor!: AffineInlineEditor;

@property({ attribute: false })
accessor inlineRange!: InlineRange;

@query('.overlay-mask')
accessor overlayMask!: HTMLDivElement;

@query('.alias-popup-form')
accessor popupContainer!: HTMLDivElement;

@property({ type: Object })
accessor referenceInfo!: ReferenceInfo;

@query('.alias-popup-form input')
accessor titleInput!: HTMLInputElement;
}
Original file line number Diff line number Diff line change
Expand Up @@ -237,18 +237,17 @@ export class AffineReference extends WithDisposable(ShadowlessElement) {
const isDeleted = !refMeta;

const attributes = this.delta.attributes;
const type = attributes?.reference?.type;
if (!type) {
const reference = attributes?.reference;
const type = reference?.type;
if (!attributes || !type) {
return nothing;
}

const title = this.customTitle
? this.customTitle(this)
: isDeleted
? 'Deleted doc'
: refMeta.title.length > 0
? refMeta.title
: DEFAULT_DOC_NAME;
: (reference?.title ?? refMeta.title ?? DEFAULT_DOC_NAME);

const icon = choose(
type,
Expand Down
Loading

0 comments on commit e3322a3

Please sign in to comment.