chore: merge blocksuite source code (#9213)

This commit is contained in:
Mirone
2024-12-20 15:38:06 +08:00
committed by GitHub
parent 2c9ef916f4
commit 30200ff86d
2031 changed files with 238888 additions and 229 deletions
@@ -0,0 +1,11 @@
import { EmbedGithubBlockSchema } from '@blocksuite/affine-model';
import { BlockHtmlAdapterExtension } from '@blocksuite/affine-shared/adapters';
import { createEmbedBlockHtmlAdapterMatcher } from '../../common/adapters/html.js';
export const embedGithubBlockHtmlAdapterMatcher =
createEmbedBlockHtmlAdapterMatcher(EmbedGithubBlockSchema.model.flavour);
export const EmbedGithubBlockHtmlAdapterExtension = BlockHtmlAdapterExtension(
embedGithubBlockHtmlAdapterMatcher
);
@@ -0,0 +1,3 @@
export * from './html.js';
export * from './markdown.js';
export * from './plain-text.js';
@@ -0,0 +1,10 @@
import { EmbedGithubBlockSchema } from '@blocksuite/affine-model';
import { BlockMarkdownAdapterExtension } from '@blocksuite/affine-shared/adapters';
import { createEmbedBlockMarkdownAdapterMatcher } from '../../common/adapters/markdown.js';
export const embedGithubBlockMarkdownAdapterMatcher =
createEmbedBlockMarkdownAdapterMatcher(EmbedGithubBlockSchema.model.flavour);
export const EmbedGithubMarkdownAdapterExtension =
BlockMarkdownAdapterExtension(embedGithubBlockMarkdownAdapterMatcher);
@@ -0,0 +1,10 @@
import { EmbedGithubBlockSchema } from '@blocksuite/affine-model';
import { BlockPlainTextAdapterExtension } from '@blocksuite/affine-shared/adapters';
import { createEmbedBlockPlainTextAdapterMatcher } from '../../common/adapters/plain-text.js';
export const embedGithubBlockPlainTextAdapterMatcher =
createEmbedBlockPlainTextAdapterMatcher(EmbedGithubBlockSchema.model.flavour);
export const EmbedGithubBlockPlainTextAdapterExtension =
BlockPlainTextAdapterExtension(embedGithubBlockPlainTextAdapterMatcher);
@@ -0,0 +1,6 @@
import { toEdgelessEmbedBlock } from '../common/to-edgeless-embed-block.js';
import { EmbedGithubBlockComponent } from './embed-github-block.js';
export class EmbedEdgelessGithubBlockComponent extends toEdgelessEmbedBlock(
EmbedGithubBlockComponent
) {}
@@ -0,0 +1,275 @@
import { OpenIcon } from '@blocksuite/affine-components/icons';
import type {
EmbedGithubModel,
EmbedGithubStyles,
} from '@blocksuite/affine-model';
import { ThemeProvider } from '@blocksuite/affine-shared/services';
import { html, nothing } from 'lit';
import { property, state } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import { repeat } from 'lit/directives/repeat.js';
import { styleMap } from 'lit/directives/style-map.js';
import { EmbedBlockComponent } from '../common/embed-block-element.js';
import { getEmbedCardIcons } from '../common/utils.js';
import { githubUrlRegex } from './embed-github-model.js';
import type { EmbedGithubBlockService } from './embed-github-service.js';
import { GithubIcon, styles } from './styles.js';
import {
getGithubStatusIcon,
refreshEmbedGithubStatus,
refreshEmbedGithubUrlData,
} from './utils.js';
export class EmbedGithubBlockComponent extends EmbedBlockComponent<
EmbedGithubModel,
EmbedGithubBlockService
> {
static override styles = styles;
override _cardStyle: (typeof EmbedGithubStyles)[number] = 'horizontal';
open = () => {
let link = this.model.url;
if (!link.match(/^[a-zA-Z]+:\/\//)) {
link = 'https://' + link;
}
window.open(link, '_blank');
};
refreshData = () => {
refreshEmbedGithubUrlData(this, this.fetchAbortController.signal).catch(
console.error
);
};
refreshStatus = () => {
refreshEmbedGithubStatus(this, this.fetchAbortController.signal).catch(
console.error
);
};
private _handleAssigneeClick(assignee: string) {
const link = `https://www.github.com/${assignee}`;
window.open(link, '_blank');
}
private _handleDoubleClick(event: MouseEvent) {
event.stopPropagation();
this.open();
}
private _selectBlock() {
const selectionManager = this.host.selection;
const blockSelection = selectionManager.create('block', {
blockId: this.blockId,
});
selectionManager.setGroup('note', [blockSelection]);
}
protected _handleClick(event: MouseEvent) {
event.stopPropagation();
this._selectBlock();
}
override connectedCallback() {
super.connectedCallback();
this._cardStyle = this.model.style;
if (!this.model.owner || !this.model.repo || !this.model.githubId) {
this.doc.withoutTransact(() => {
const url = this.model.url;
const urlMatch = url.match(githubUrlRegex);
if (urlMatch) {
const [, owner, repo, githubType, githubId] = urlMatch;
this.doc.updateBlock(this.model, {
owner,
repo,
githubType: githubType === 'issue' ? 'issue' : 'pr',
githubId,
});
}
});
}
this.doc.withoutTransact(() => {
if (!this.model.description && !this.model.title) {
this.refreshData();
} else {
this.refreshStatus();
}
});
this.disposables.add(
this.model.propsUpdated.on(({ key }) => {
if (key === 'url') {
this.refreshData();
}
})
);
this.disposables.add(
this.selection.slots.changed.on(() => {
this._isSelected =
!!this.selected?.is('block') || !!this.selected?.is('surface');
})
);
}
override renderBlock() {
const {
title = 'GitHub',
githubType,
status,
statusReason,
owner,
repo,
createdAt,
assignees,
description,
image,
style,
} = this.model;
const loading = this.loading;
const theme = this.std.get(ThemeProvider).theme;
const { LoadingIcon, EmbedCardBannerIcon } = getEmbedCardIcons(theme);
const titleIcon = loading ? LoadingIcon : GithubIcon;
const statusIcon = status
? getGithubStatusIcon(githubType, status, statusReason)
: nothing;
const statusText = loading ? '' : status;
const titleText = loading ? 'Loading...' : title;
const descriptionText = loading ? '' : description;
const bannerImage =
!loading && image
? html`<object type="image/webp" data=${image} draggable="false">
${EmbedCardBannerIcon}
</object>`
: EmbedCardBannerIcon;
let dateText = '';
if (createdAt) {
const date = new Date(createdAt);
dateText = date.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
});
const day = date.getDate();
const suffix =
['th', 'st', 'nd', 'rd'][((day / 10) | 0) !== 1 ? day % 10 : 4] || 'th';
dateText = dateText.replace(/\d+/, `${day}${suffix}`);
}
return this.renderEmbed(
() => html`
<div
class=${classMap({
'affine-embed-github-block': true,
loading,
[style]: true,
selected: this._isSelected,
})}
style=${styleMap({
transform: `scale(${this._scale})`,
transformOrigin: '0 0 ',
})}
@click=${this._handleClick}
@dblclick=${this._handleDoubleClick}
>
<div class="affine-embed-github-banner">${bannerImage}</div>
<div class="affine-embed-github-content">
<div class="affine-embed-github-content-title">
<div class="affine-embed-github-content-title-icons">
<div class="affine-embed-github-content-title-site-icon">
${titleIcon}
</div>
${status && statusText
? html`<div
class=${classMap({
'affine-embed-github-content-title-status-icon': true,
[githubType]: true,
[status]: true,
success: statusReason === 'completed',
failure: statusReason === 'not_planned',
})}
>
${statusIcon}
<span>${statusText}</span>
</div>`
: nothing}
</div>
<div class="affine-embed-github-content-title-text">
${titleText}
</div>
</div>
<div class="affine-embed-github-content-description">
${descriptionText}
</div>
${githubType === 'issue' && assignees
? html`
<div class="affine-embed-github-content-assignees">
<div
class="affine-embed-github-content-assignees-text label"
>
Assignees
</div>
<div
class="affine-embed-github-content-assignees-text users"
>
${assignees.length === 0
? html`<span
class="affine-embed-github-content-assignees-text-users placeholder"
>No one</span
>`
: repeat(
assignees,
assignee => assignee,
(assignee, index) =>
html`<span
class="affine-embed-github-content-assignees-text-users user"
@click=${() =>
this._handleAssigneeClick(assignee)}
>${`@${assignee}`}</span
>
${index === assignees.length - 1 ? '' : `, `}`
)}
</div>
</div>
`
: nothing}
<div class="affine-embed-github-content-url" @click=${this.open}>
<span class="affine-embed-github-content-repo"
>${`${owner}/${repo} |`}</span
>
${createdAt
? html`<span class="affine-embed-github-content-date"
>${dateText} |</span
>`
: nothing}
<span>github.com</span>
<div class="affine-embed-github-content-url-icon">
${OpenIcon}
</div>
</div>
</div>
</div>
`
);
}
@state()
private accessor _isSelected = false;
@property({ attribute: false })
accessor loading = false;
}
@@ -0,0 +1,2 @@
export const githubUrlRegex: RegExp =
/^(?:https?:\/\/)?(?:www\.)?github\.com\/([^/]+)\/([^/]+)\/(issue|pull)s?\/(\d+)$/;
@@ -0,0 +1,43 @@
import {
EmbedGithubBlockSchema,
type EmbedGithubModel,
EmbedGithubStyles,
} from '@blocksuite/affine-model';
import { EmbedOptionProvider } from '@blocksuite/affine-shared/services';
import { BlockService } from '@blocksuite/block-std';
import { LinkPreviewer } from '../common/link-previewer.js';
import { githubUrlRegex } from './embed-github-model.js';
import { queryEmbedGithubApiData, queryEmbedGithubData } from './utils.js';
export class EmbedGithubBlockService extends BlockService {
static override readonly flavour = EmbedGithubBlockSchema.model.flavour;
private static readonly linkPreviewer = new LinkPreviewer();
static setLinkPreviewEndpoint =
EmbedGithubBlockService.linkPreviewer.setEndpoint;
queryApiData = (embedGithubModel: EmbedGithubModel, signal?: AbortSignal) => {
return queryEmbedGithubApiData(embedGithubModel, signal);
};
queryUrlData = (embedGithubModel: EmbedGithubModel, signal?: AbortSignal) => {
return queryEmbedGithubData(
embedGithubModel,
EmbedGithubBlockService.linkPreviewer,
signal
);
};
override mounted() {
super.mounted();
this.std.get(EmbedOptionProvider).registerEmbedBlockOptions({
flavour: this.flavour,
urlRegex: githubUrlRegex,
styles: EmbedGithubStyles,
viewType: 'card',
});
}
}
@@ -0,0 +1,18 @@
import {
BlockViewExtension,
type ExtensionType,
FlavourExtension,
} from '@blocksuite/block-std';
import { literal } from 'lit/static-html.js';
import { EmbedGithubBlockService } from './embed-github-service.js';
export const EmbedGithubBlockSpec: ExtensionType[] = [
FlavourExtension('affine:embed-github'),
EmbedGithubBlockService,
BlockViewExtension('affine:embed-github', model => {
return model.parent?.flavour === 'affine:surface'
? literal`affine-embed-edgeless-github-block`
: literal`affine-embed-github-block`;
}),
];
@@ -0,0 +1,5 @@
export * from './adapters/index.js';
export * from './embed-github-block.js';
export * from './embed-github-service.js';
export * from './embed-github-spec.js';
export { GithubIcon } from './styles.js';
@@ -0,0 +1,513 @@
import {
EMBED_CARD_HEIGHT,
EMBED_CARD_WIDTH,
} from '@blocksuite/affine-shared/consts';
import { css, html } from 'lit';
export const styles = css`
.affine-embed-github-block {
box-sizing: border-box;
display: flex;
width: 100%;
height: ${EMBED_CARD_HEIGHT.horizontal}px;
border-radius: 8px;
border: 1px solid var(--affine-background-tertiary-color);
opacity: var(--add, 1);
background: var(--affine-background-primary-color);
user-select: none;
}
.affine-embed-github-content {
display: flex;
flex-grow: 1;
flex-direction: column;
align-self: stretch;
gap: 4px;
padding: 12px;
border-radius: var(--1, 0px);
opacity: var(--add, 1);
}
.affine-embed-github-content-title {
display: flex;
min-height: 22px;
flex-direction: row;
gap: 8px;
align-items: center;
align-self: stretch;
padding: var(--1, 0px);
border-radius: var(--1, 0px);
opacity: var(--add, 1);
}
.affine-embed-github-content-title-icons {
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
}
.affine-embed-github-content-title-icons img,
.affine-embed-github-content-title-icons object,
.affine-embed-github-content-title-icons svg {
width: 16px;
height: 16px;
color: var(--affine-pure-white);
}
.affine-embed-github-content-title-site-icon {
display: flex;
width: 16px;
height: 16px;
justify-content: center;
align-items: center;
.github-icon {
fill: var(--affine-black);
color: var(--affine-black);
}
}
.affine-embed-github-content-title-status-icon {
display: flex;
align-items: center;
gap: 4px;
padding: 3px 6px;
border-radius: 20px;
color: var(--affine-pure-white);
leading-trim: both;
text-edge: cap;
font-feature-settings:
'clig' off,
'liga' off;
text-transform: capitalize;
font-family: var(--affine-font-family);
font-size: 12px;
font-style: normal;
font-weight: 500;
line-height: 16px;
}
.affine-embed-github-content-title-status-icon.issue.open {
background: #238636;
}
.affine-embed-github-content-title-status-icon.issue.closed.success {
background: #8957e5;
}
.affine-embed-github-content-title-status-icon.issue.closed.failure {
background: #6e7681;
}
.affine-embed-github-content-title-status-icon.pr.open {
background: #238636;
}
.affine-embed-github-content-title-status-icon.pr.draft {
background: #6e7681;
}
.affine-embed-github-content-title-status-icon.pr.merged {
background: #8957e5;
}
.affine-embed-github-content-title-status-icon.pr.closed {
background: #c03737;
}
.affine-embed-github-content-title-status-icon > svg {
height: 16px;
width: 16px;
padding: 2px;
}
.affine-embed-github-content-title-status-icon > span {
padding: 0px 1.5px;
}
.affine-embed-github-content-title-text {
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
word-break: break-word;
overflow: hidden;
text-overflow: ellipsis;
color: var(--affine-text-primary-color);
font-family: var(--affine-font-family);
font-size: var(--affine-font-sm);
font-style: normal;
font-weight: 600;
line-height: 22px;
}
.affine-embed-github-content-description {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
flex-grow: 1;
word-break: break-word;
white-space: normal;
overflow: hidden;
text-overflow: ellipsis;
color: var(--affine-text-primary-color);
font-family: var(--affine-font-family);
font-size: var(--affine-font-xs);
font-style: normal;
font-weight: 400;
line-height: 20px;
}
.affine-embed-github-content-assignees {
display: none;
}
.affine-embed-github-content-url {
display: flex;
align-items: center;
justify-content: flex-start;
gap: 4px;
width: max-content;
max-width: 100%;
cursor: pointer;
}
.affine-embed-github-content-url > span {
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
word-break: break-all;
white-space: normal;
overflow: hidden;
text-overflow: ellipsis;
color: var(--affine-text-secondary-color);
font-family: var(--affine-font-family);
font-size: var(--affine-font-xs);
font-style: normal;
font-weight: 400;
line-height: 20px;
}
.affine-embed-github-content-url:hover > span {
color: var(--affine-link-color);
}
.affine-embed-github-content-url:hover .open-icon {
fill: var(--affine-link-color);
}
.affine-embed-github-content-url-icon {
display: flex;
align-items: center;
justify-content: center;
width: 12px;
height: 12px;
}
.affine-embed-github-content-url-icon .open-icon {
height: 12px;
width: 12px;
fill: var(--affine-text-secondary-color);
}
.affine-embed-github-banner {
margin: 12px 0px 0px 12px;
width: 204px;
height: 102px;
opacity: var(--add, 1);
}
.affine-embed-github-banner img,
.affine-embed-github-banner object,
.affine-embed-github-banner svg {
width: 204px;
height: 102px;
object-fit: cover;
border-radius: 4px 4px var(--1, 0px) var(--1, 0px);
}
.affine-embed-github-block.loading {
.affine-embed-github-content-title-text {
color: var(--affine-placeholder-color);
}
}
.affine-embed-github-block.selected {
.affine-embed-github-content-url > span {
color: var(--affine-link-color);
}
.affine-embed-github-content-url .open-icon {
fill: var(--affine-link-color);
}
}
.affine-embed-github-block.list {
height: ${EMBED_CARD_HEIGHT.list}px;
width: ${EMBED_CARD_WIDTH.list}px;
.affine-embed-github-content {
width: 100%;
flex-direction: row;
align-items: center;
justify-content: space-between;
}
.affine-embed-github-content-title {
width: 660px;
}
.affine-embed-github-content-repo {
display: none;
}
.affine-embed-github-content-date {
display: none;
}
.affine-embed-github-content-url {
width: 90px;
justify-content: flex-end;
}
.affine-embed-github-content-description {
display: none;
}
.affine-embed-github-banner {
display: none;
}
}
.affine-embed-github-block.horizontal {
width: ${EMBED_CARD_WIDTH.horizontal}px;
height: ${EMBED_CARD_HEIGHT.horizontal}px;
}
.affine-embed-github-block.vertical {
width: ${EMBED_CARD_WIDTH.vertical}px;
height: ${EMBED_CARD_HEIGHT.vertical}px;
flex-direction: column;
.affine-embed-github-content {
width: 100%;
}
.affine-embed-github-content-description {
-webkit-line-clamp: 6;
}
.affine-embed-github-content-assignees {
display: flex;
padding: var(--1, 0px);
align-items: center;
justify-content: flex-start;
gap: 2px;
align-self: stretch;
}
.affine-embed-github-content-assignees-text {
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
font-family: var(--affine-font-family);
font-size: var(--affine-font-xs);
font-style: normal;
font-weight: 600;
line-height: 20px;
}
.affine-embed-github-content-assignees-text.label {
width: 72px;
color: var(--affine-text-primary-color);
font-weight: 600;
}
.affine-embed-github-content-assignees-text.users {
width: calc(100% - 72px);
word-break: break-all;
white-space: normal;
overflow: hidden;
text-overflow: ellipsis;
font-weight: 400;
}
.affine-embed-github-content-assignees-text-users.user {
color: var(--affine-link-color);
cursor: pointer;
}
.affine-embed-github-content-assignees-text-users.placeholder {
color: var(--affine-placeholder-color);
}
.affine-embed-github-banner {
width: 340px;
height: 170px;
margin-left: 12px;
}
.affine-embed-github-banner img,
.affine-embed-github-banner object,
.affine-embed-github-banner svg {
width: 340px;
height: 170px;
}
}
.affine-embed-github-block.cube {
width: ${EMBED_CARD_WIDTH.cube}px;
height: ${EMBED_CARD_HEIGHT.cube}px;
.affine-embed-github-content {
width: 100%;
flex-direction: column;
align-items: flex-start;
justify-content: space-between;
}
.affine-embed-github-content-title {
flex-direction: column;
gap: 4px;
align-items: flex-start;
}
.affine-embed-github-content-title-text {
-webkit-line-clamp: 2;
}
.affine-embed-github-content-description {
display: none;
}
.affine-embed-github-banner {
display: none;
}
.affine-embed-github-content-repo {
display: none;
}
.affine-embed-github-content-date {
display: none;
}
}
`;
export const GithubIcon = html`<svg
class="github-icon"
width="20"
height="20"
viewBox="0 0 16 16"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M8.00016 1.33334C4.31683 1.33334 1.3335 4.39214 1.3335 8.16864C1.3335 11.1933 3.24183 13.7479 5.89183 14.6536C6.22516 14.7134 6.35016 14.5084 6.35016 14.3289C6.35016 14.1666 6.34183 13.6283 6.34183 13.0559C4.66683 13.372 4.2335 12.6372 4.10016 12.2527C4.02516 12.0562 3.70016 11.4496 3.41683 11.2872C3.1835 11.1591 2.85016 10.8429 3.4085 10.8344C3.9335 10.8259 4.3085 11.33 4.4335 11.535C5.0335 12.5689 5.99183 12.2784 6.37516 12.0989C6.4335 11.6546 6.6085 11.3556 6.80016 11.1847C5.31683 11.0138 3.76683 10.4243 3.76683 7.80978C3.76683 7.06644 4.02516 6.45127 4.45016 5.9728C4.3835 5.80192 4.15016 5.1013 4.51683 4.16145C4.51683 4.16145 5.07516 3.98202 6.35016 4.86206C6.8835 4.70827 7.45016 4.63137 8.01683 4.63137C8.5835 4.63137 9.15016 4.70827 9.6835 4.86206C10.9585 3.97348 11.5168 4.16145 11.5168 4.16145C11.8835 5.1013 11.6502 5.80192 11.5835 5.9728C12.0085 6.45127 12.2668 7.0579 12.2668 7.80978C12.2668 10.4328 10.7085 11.0138 9.22516 11.1847C9.46683 11.3983 9.67516 11.8084 9.67516 12.4492C9.67516 13.3635 9.66683 14.0983 9.66683 14.3289C9.66683 14.5084 9.79183 14.722 10.1252 14.6536C11.4486 14.1955 12.5986 13.3234 13.4133 12.1601C14.228 10.9968 14.6664 9.60079 14.6668 8.16864C14.6668 4.39214 11.6835 1.33334 8.00016 1.33334Z"
/>
</svg> `;
export const GithubIssueOpenIcon = html`<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M8 9.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Z"></path>
<path
d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Z"
></path>
</svg>`;
export const GithubIssueClosedSuccessIcon = html`<svg
aria-hidden="true"
height="16"
viewBox="0 0 16 16"
version="1.1"
width="16"
data-view-component="true"
class="octicon octicon-issue-closed flex-items-center mr-1"
fill="currentColor"
>
<path
d="M11.28 6.78a.75.75 0 0 0-1.06-1.06L7.25 8.69 5.78 7.22a.75.75 0 0 0-1.06 1.06l2 2a.75.75 0 0 0 1.06 0l3.5-3.5Z"
></path>
<path
d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0Zm-1.5 0a6.5 6.5 0 1 0-13 0 6.5 6.5 0 0 0 13 0Z"
></path>
</svg>`;
export const GithubIssueClosedFailureIcon = html`<svg
aria-hidden="true"
height="16"
viewBox="0 0 16 16"
version="1.1"
width="16"
data-view-component="true"
class="octicon octicon-skip flex-items-center mr-1"
fill="currentColor"
>
<path
d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm9.78-2.22-5.5 5.5a.749.749 0 0 1-1.275-.326.749.749 0 0 1 .215-.734l5.5-5.5a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042Z"
></path>
</svg>`;
export const GithubPROpenIcon = html`<svg
height="16"
class="octicon octicon-git-pull-request"
viewBox="0 0 16 16"
version="1.1"
width="16"
aria-hidden="true"
fill="currentColor"
>
<path
d="M1.5 3.25a2.25 2.25 0 1 1 3 2.122v5.256a2.251 2.251 0 1 1-1.5 0V5.372A2.25 2.25 0 0 1 1.5 3.25Zm5.677-.177L9.573.677A.25.25 0 0 1 10 .854V2.5h1A2.5 2.5 0 0 1 13.5 5v5.628a2.251 2.251 0 1 1-1.5 0V5a1 1 0 0 0-1-1h-1v1.646a.25.25 0 0 1-.427.177L7.177 3.427a.25.25 0 0 1 0-.354ZM3.75 2.5a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Zm0 9.5a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Zm8.25.75a.75.75 0 1 0 1.5 0 .75.75 0 0 0-1.5 0Z"
></path>
</svg>`;
export const GithubPRDraftIcon = html`<svg
height="16"
class="octicon octicon-git-pull-request-draft"
viewBox="0 0 16 16"
version="1.1"
width="16"
aria-hidden="true"
fill="currentColor"
>
<path
d="M3.25 1A2.25 2.25 0 0 1 4 5.372v5.256a2.251 2.251 0 1 1-1.5 0V5.372A2.251 2.251 0 0 1 3.25 1Zm9.5 14a2.25 2.25 0 1 1 0-4.5 2.25 2.25 0 0 1 0 4.5ZM2.5 3.25a.75.75 0 1 0 1.5 0 .75.75 0 0 0-1.5 0ZM3.25 12a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Zm9.5 0a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5ZM14 7.5a1.25 1.25 0 1 1-2.5 0 1.25 1.25 0 0 1 2.5 0Zm0-4.25a1.25 1.25 0 1 1-2.5 0 1.25 1.25 0 0 1 2.5 0Z"
></path>
</svg>`;
export const GithubPRMergedIcon = html`<svg
height="16"
class="octicon octicon-git-merge"
viewBox="0 0 16 16"
version="1.1"
width="16"
aria-hidden="true"
fill="currentColor"
>
<path
d="M5.45 5.154A4.25 4.25 0 0 0 9.25 7.5h1.378a2.251 2.251 0 1 1 0 1.5H9.25A5.734 5.734 0 0 1 5 7.123v3.505a2.25 2.25 0 1 1-1.5 0V5.372a2.25 2.25 0 1 1 1.95-.218ZM4.25 13.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Zm8.5-4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5ZM5 3.25a.75.75 0 1 0 0 .005V3.25Z"
></path>
</svg>`;
export const GithubPRClosedIcon = html`<svg
height="16"
class="octicon octicon-git-pull-request-closed"
viewBox="0 0 16 16"
version="1.1"
width="16"
aria-hidden="true"
fill="currentColor"
>
<path
d="M3.25 1A2.25 2.25 0 0 1 4 5.372v5.256a2.251 2.251 0 1 1-1.5 0V5.372A2.251 2.251 0 0 1 3.25 1Zm9.5 5.5a.75.75 0 0 1 .75.75v3.378a2.251 2.251 0 1 1-1.5 0V7.25a.75.75 0 0 1 .75-.75Zm-2.03-5.273a.75.75 0 0 1 1.06 0l.97.97.97-.97a.748.748 0 0 1 1.265.332.75.75 0 0 1-.205.729l-.97.97.97.97a.751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018l-.97-.97-.97.97a.749.749 0 0 1-1.275-.326.749.749 0 0 1 .215-.734l.97-.97-.97-.97a.75.75 0 0 1 0-1.06ZM2.5 3.25a.75.75 0 1 0 1.5 0 .75.75 0 0 0-1.5 0ZM3.25 12a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Zm9.5 0a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Z"
></path>
</svg>`;
@@ -0,0 +1,170 @@
import type {
EmbedGithubBlockUrlData,
EmbedGithubModel,
} from '@blocksuite/affine-model';
import { isAbortError } from '@blocksuite/affine-shared/utils';
import { assertExists } from '@blocksuite/global/utils';
import { nothing } from 'lit';
import type { LinkPreviewer } from '../common/link-previewer.js';
import type { EmbedGithubBlockComponent } from './embed-github-block.js';
import {
GithubIssueClosedFailureIcon,
GithubIssueClosedSuccessIcon,
GithubIssueOpenIcon,
GithubPRClosedIcon,
GithubPRDraftIcon,
GithubPRMergedIcon,
GithubPROpenIcon,
} from './styles.js';
export async function queryEmbedGithubData(
embedGithubModel: EmbedGithubModel,
linkPreviewer: LinkPreviewer,
signal?: AbortSignal
): Promise<Partial<EmbedGithubBlockUrlData>> {
const [githubApiData, openGraphData] = await Promise.all([
queryEmbedGithubApiData(embedGithubModel, signal),
linkPreviewer.query(embedGithubModel.url, signal),
]);
return { ...githubApiData, ...openGraphData };
}
export async function queryEmbedGithubApiData(
embedGithubModel: EmbedGithubModel,
signal?: AbortSignal
): Promise<Partial<EmbedGithubBlockUrlData>> {
const { owner, repo, githubType, githubId } = embedGithubModel;
let githubApiData: Partial<EmbedGithubBlockUrlData> = {};
// github's public api has a rate limit of 60 requests per hour
const apiUrl = `https://api.github.com/repos/${owner}/${repo}/${
githubType === 'issue' ? 'issues' : 'pulls'
}/${githubId}`;
const githubApiResponse = await fetch(apiUrl, {
cache: 'no-cache',
signal,
}).catch(() => null);
if (githubApiResponse && githubApiResponse.ok) {
const githubApiJson = await githubApiResponse.json();
const { state, state_reason, draft, merged, created_at, assignees } =
githubApiJson;
const assigneeLogins = assignees.map(
(assignee: { login: string }) => assignee.login
);
let status = state;
if (merged) {
status = 'merged';
} else if (state === 'open' && draft) {
status = 'draft';
}
githubApiData = {
status,
statusReason: state_reason,
createdAt: created_at,
assignees: assigneeLogins,
};
}
return githubApiData;
}
export async function refreshEmbedGithubUrlData(
embedGithubElement: EmbedGithubBlockComponent,
signal?: AbortSignal
): Promise<void> {
let image = null,
status = null,
statusReason = null,
title = null,
description = null,
createdAt = null,
assignees = null;
try {
embedGithubElement.loading = true;
const queryUrlData = embedGithubElement.service?.queryUrlData;
assertExists(queryUrlData);
const githubUrlData = await queryUrlData(embedGithubElement.model);
({
image = null,
status = null,
statusReason = null,
title = null,
description = null,
createdAt = null,
assignees = null,
} = githubUrlData);
if (signal?.aborted) return;
embedGithubElement.doc.updateBlock(embedGithubElement.model, {
image,
status,
statusReason,
title,
description,
createdAt,
assignees,
});
} catch (error) {
if (signal?.aborted || isAbortError(error)) return;
throw Error;
} finally {
embedGithubElement.loading = false;
}
}
export async function refreshEmbedGithubStatus(
embedGithubElement: EmbedGithubBlockComponent,
signal?: AbortSignal
) {
const queryApiData = embedGithubElement.service?.queryApiData;
assertExists(queryApiData);
const githubApiData = await queryApiData(embedGithubElement.model, signal);
if (!githubApiData.status || signal?.aborted) return;
embedGithubElement.doc.updateBlock(embedGithubElement.model, {
status: githubApiData.status,
statusReason: githubApiData.statusReason,
createdAt: githubApiData.createdAt,
assignees: githubApiData.assignees,
});
}
export function getGithubStatusIcon(
type: 'issue' | 'pr',
status: string,
statusReason: string | null
) {
if (type === 'issue') {
if (status === 'open') {
return GithubIssueOpenIcon;
} else if (status === 'closed' && statusReason === 'completed') {
return GithubIssueClosedSuccessIcon;
} else if (status === 'closed' && statusReason === 'not_planned') {
return GithubIssueClosedFailureIcon;
} else {
return nothing;
}
} else if (type === 'pr') {
if (status === 'open') {
return GithubPROpenIcon;
} else if (status === 'draft') {
return GithubPRDraftIcon;
} else if (status === 'merged') {
return GithubPRMergedIcon;
} else if (status === 'closed') {
return GithubPRClosedIcon;
}
}
return nothing;
}