Skip to content

Commit

Permalink
feat: affine-link supports in-app jumps
Browse files Browse the repository at this point in the history
  • Loading branch information
fundon committed Sep 25, 2024
1 parent 2ec2744 commit 41a9b80
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 80 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import type { AffineTextAttributes } from '../../../extension/index.js';
export function affineTextStyles(
props: AffineTextAttributes,
override?: Readonly<StyleInfo>
): ReturnType<typeof styleMap> {
): StyleInfo {
let textDecorations = '';
if (props.underline) {
textDecorations += 'underline';
Expand All @@ -31,34 +31,34 @@ export function affineTextStyles(
};
}

return styleMap({
return {
'font-weight': props.bold ? 'bolder' : 'inherit',
'font-style': props.italic ? 'italic' : 'normal',
'background-color': props.background ? props.background : undefined,
color: props.color ? props.color : undefined,
'text-decoration': textDecorations.length > 0 ? textDecorations : 'none',
...inlineCodeStyle,
...override,
});
};
}

export class AffineText extends ShadowlessElement {
override render() {
const style = this.delta.attributes
? affineTextStyles(this.delta.attributes)
: styleMap({});
: {};

// we need to avoid \n appearing before and after the span element, which will
// cause the unexpected space
if (this.delta.attributes?.code) {
return html`<code style=${style}
return html`<code style=${styleMap(style)}
><v-text .str=${this.delta.insert}></v-text
></code>`;
}

// we need to avoid \n appearing before and after the span element, which will
// cause the unexpected space
return html`<span style=${style}
return html`<span style=${styleMap(style)}
><v-text .str=${this.delta.insert}></v-text
></span>`;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import type { ReferenceInfo, ReferenceParams } from '@blocksuite/affine-model';
import type { BlockComponent } from '@blocksuite/block-std';

import { ParseDocUrlProvider } from '@blocksuite/affine-shared/services';
import { BLOCK_ID_ATTR, ShadowlessElement } from '@blocksuite/block-std';
import {
type DeltaInsert,
Expand All @@ -10,11 +12,13 @@ import {
import { css, html } from 'lit';
import { property } from 'lit/decorators.js';
import { ref } from 'lit/directives/ref.js';
import { styleMap } from 'lit/directives/style-map.js';

import type { AffineTextAttributes } from '../../../../extension/index.js';
import { type StyleInfo, styleMap } from 'lit/directives/style-map.js';

import { HoverController } from '../../../../../hover/index.js';
import {
type AffineTextAttributes,
RefNodeSlotsProvider,
} from '../../../../extension/index.js';
import { affineTextStyles } from '../affine-text.js';
import { toggleLinkPopup } from './link-popup/toggle-link-popup.js';

Expand All @@ -25,6 +29,38 @@ export class AffineLink extends ShadowlessElement {
}
`;

// The link has been identified.
private _identified: boolean = false;

private _onClick = (e: MouseEvent) => {
if (!this._identified) {
this._identified = true;
this._identify();
}

const referenceInfo = this._referenceInfo;
if (!referenceInfo) return;

const refNodeSlotsProvider = this.std?.getOptional(RefNodeSlotsProvider);
if (!refNodeSlotsProvider) return;

e.preventDefault();

refNodeSlotsProvider.docLinkClicked.emit(referenceInfo);
};

// see https://github.com/toeverything/AFFiNE/issues/1540
private _onMouseUp = () => {
const anchorElement = this.querySelector('a');
if (!anchorElement || !anchorElement.isContentEditable) return;
anchorElement.contentEditable = 'false';
setTimeout(() => {
anchorElement.removeAttribute('contenteditable');
}, 0);
};

private _referenceInfo: ReferenceInfo | null = null;

private _whenHover = new HoverController(
this,
({ abortController }) => {
Expand Down Expand Up @@ -80,11 +116,7 @@ export class AffineLink extends ShadowlessElement {
}

get link() {
const link = this.delta.attributes?.link;
if (!link) {
return '';
}
return link;
return this.delta.attributes?.link ?? '';
}

get selfInlineRange() {
Expand All @@ -97,50 +129,59 @@ export class AffineLink extends ShadowlessElement {
return std;
}

// see https://github.com/toeverything/AFFiNE/issues/1540
private _onMouseUp() {
const anchorElement = this.querySelector('a');
if (!anchorElement || !anchorElement.isContentEditable) return;
anchorElement.contentEditable = 'false';
setTimeout(() => {
anchorElement.removeAttribute('contenteditable');
}, 0);
// Identify if url is an in-app link
private _identify() {
const link = this.link;
if (!link) return;

const result = this.std
?.getOptional(ParseDocUrlProvider)
?.parseDocUrl(link);
if (!result) return;

const { docId: pageId, mode, blockIds, elementIds } = result;

let params: ReferenceParams | undefined = undefined;
if (mode || blockIds?.length || elementIds?.length) {
params = { mode, blockIds, elementIds };
}

this._referenceInfo = { pageId, params };
}

private _renderLink(style: StyleInfo) {
return html`<a
${ref(this._whenHover.setReference)}
href=${this.link}
rel="noopener noreferrer"
target="_blank"
style=${styleMap(style)}
@click=${this._onClick}
@mouseup=${this._onMouseUp}
><v-text .str=${this.delta.insert}></v-text
></a>`;
}

override render() {
const linkStyles = {
const linkStyle = {
color: 'var(--affine-link-color)',
fill: 'var(--affine-link-color)',
'text-decoration': 'none',
cursor: 'pointer',
};

if (this.delta.attributes && this.delta.attributes?.code) {
const codeStyle = affineTextStyles(this.delta.attributes);
return html`<code style=${codeStyle}
><a
${ref(this._whenHover.setReference)}
href=${this.link}
style=${styleMap(linkStyles)}
rel="noopener noreferrer"
target="_blank"
@mouseup=${this._onMouseUp}
><v-text .str=${this.delta.insert}></v-text
></a></v-text></code>`;
return html`<code style=${styleMap(codeStyle)}>
${this._renderLink(linkStyle)}
</code>`;
}

const styles = this.delta.attributes
? affineTextStyles(this.delta.attributes, linkStyles)
: styleMap({});
const style = this.delta.attributes
? affineTextStyles(this.delta.attributes, linkStyle)
: {};

return html`<a
${ref(this._whenHover.setReference)}
href=${this.link}
rel="noopener noreferrer"
target="_blank"
style=${styles}
@mouseup=${this._onMouseUp}
><v-text .str=${this.delta.insert}></v-text
></a>`;
return this._renderLink(style);
}

@property({ type: Object })
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import type { ReferenceInfo } from '@blocksuite/affine-model';
import type { Doc, DocMeta } from '@blocksuite/store';

import { getModelByElement } from '@blocksuite/affine-shared/utils';
import {
BLOCK_ID_ATTR,
type BlockComponent,
Expand All @@ -20,6 +19,7 @@ import { css, html, nothing } from 'lit';
import { property, state } from 'lit/decorators.js';
import { choose } from 'lit/directives/choose.js';
import { ref } from 'lit/directives/ref.js';
import { styleMap } from 'lit/directives/style-map.js';

import type { ReferenceNodeConfigProvider } from './reference-config.js';

Expand Down Expand Up @@ -70,18 +70,12 @@ export class AffineReference extends WithDisposable(ShadowlessElement) {
}
`;

private _refAttribute: NonNullable<AffineTextAttributes['reference']> = {
type: 'LinkedPage',
pageId: '0',
};

private _updateRefMeta = (doc: Doc) => {
const refAttribute = this.delta.attributes?.reference;
if (!refAttribute) {
return;
}

this._refAttribute = refAttribute;
const refMeta = doc.collection.meta.docMetas.find(
doc => doc.id === refAttribute.pageId
);
Expand Down Expand Up @@ -166,8 +160,8 @@ export class AffineReference extends WithDisposable(ShadowlessElement) {

get referenceInfo(): ReferenceInfo {
const reference = this.delta.attributes?.reference;
const id = this.doc?.id;
if (!reference) return { pageId: id ?? '' };
const id = this.doc?.id ?? '';
if (!reference) return { pageId: id };

const { pageId, params } = reference;
const info: ReferenceInfo = { pageId };
Expand Down Expand Up @@ -199,22 +193,9 @@ export class AffineReference extends WithDisposable(ShadowlessElement) {

private _onClick() {
if (!this.config.interactable) return;

const refMeta = this.refMeta;
const model = getModelByElement(this);
if (!refMeta) {
// The doc is deleted
console.warn('The doc is deleted', this._refAttribute.pageId);
return;
}
if (!model || refMeta.id === model.doc.id) {
// the doc is the current doc.
return;
}
const targetDocId = refMeta.id;
this.std
.getOptional(RefNodeSlotsProvider)
?.docLinkClicked.emit({ pageId: targetDocId });
?.docLinkClicked.emit(this.referenceInfo);
}

override connectedCallback() {
Expand Down Expand Up @@ -252,13 +233,9 @@ export class AffineReference extends WithDisposable(ShadowlessElement) {

// linking block/element
isLinkedNode() {
const reference = this.delta.attributes?.reference;
if (!reference?.params) return false;
const { mode, blockIds, elementIds } = reference.params;
if (!mode) return false;
if (blockIds && blockIds.length > 0) return true;
if (elementIds && elementIds.length > 0) return true;
return false;
return Boolean(
this.referenceInfo.params && Object.keys(this.referenceInfo.params).length
);
}

override render() {
Expand Down Expand Up @@ -314,7 +291,7 @@ export class AffineReference extends WithDisposable(ShadowlessElement) {
${this.config.interactable ? ref(this._whenHover.setReference) : ''}
data-selected=${this.selected}
class="affine-reference"
style=${style}
style=${styleMap(style)}
@click=${this._onClick}
>${content}<v-text .str=${ZERO_WIDTH_NON_JOINER}></v-text
></span>`;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,10 +156,6 @@ export class ReferencePopup extends WithDisposable(LitElement) {
}

private _openDoc() {
const pageId = this.referenceDocId;
const block = this.block;
if (pageId === block.doc.id) return;

this.std
.getOptional(RefNodeSlotsProvider)
?.docLinkClicked.emit(this.referenceInfo);
Expand Down

0 comments on commit 41a9b80

Please sign in to comment.