mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-25 02:13:00 +08:00
feat(editor): gfx link extension (#12046)
Closes: BS-3368 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Introduced a new link tool extension, enabling enhanced link-related functionality within the edgeless workspace. - Added a new view extension for link tools, improving integration and usability in edgeless mode. - **Chores** - Added a new package for link tool functionality with appropriate dependencies and exports. - Registered new custom elements for edgeless toolbars and link tools to support modular UI components. - Updated project configurations and workspace dependencies to include the new link tool module. - **Refactor** - Removed unused quick tool exports and toolbar component registrations to streamline the edgeless extension codebase. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -4,6 +4,8 @@ import {
|
||||
EDGELESS_TOOLBAR_WIDGET,
|
||||
EdgelessToolbarWidget,
|
||||
} from './edgeless-toolbar';
|
||||
import { EdgelessSlideMenu } from './menu/slide-menu';
|
||||
import { ToolbarArrowUpIcon } from './menu/toolbar-arrow-up-icon';
|
||||
import { EdgelessFontFamilyPanel } from './panel/font-family-panel';
|
||||
import { EdgelessFontWeightAndStylePanel } from './panel/font-weight-and-style-panel';
|
||||
|
||||
@@ -16,6 +18,8 @@ export function effects() {
|
||||
EdgelessFontWeightAndStylePanel
|
||||
);
|
||||
customElements.define('edgeless-font-family-panel', EdgelessFontFamilyPanel);
|
||||
customElements.define('edgeless-slide-menu', EdgelessSlideMenu);
|
||||
customElements.define('toolbar-arrow-up-icon', ToolbarArrowUpIcon);
|
||||
}
|
||||
|
||||
declare global {
|
||||
@@ -25,5 +29,7 @@ declare global {
|
||||
'edgeless-toolbar-widget': EdgelessToolbarWidget;
|
||||
'edgeless-font-weight-and-style-panel': EdgelessFontWeightAndStylePanel;
|
||||
'edgeless-font-family-panel': EdgelessFontFamilyPanel;
|
||||
'edgeless-slide-menu': EdgelessSlideMenu;
|
||||
'toolbar-arrow-up-icon': ToolbarArrowUpIcon;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
import { WithDisposable } from '@blocksuite/global/lit';
|
||||
import { ArrowRightSmallIcon } from '@blocksuite/icons/lit';
|
||||
import { consume } from '@lit/context';
|
||||
import { css, html, LitElement } from 'lit';
|
||||
import { property, query } from 'lit/decorators.js';
|
||||
import { styleMap } from 'lit/directives/style-map.js';
|
||||
|
||||
import {
|
||||
type EdgelessToolbarSlots,
|
||||
edgelessToolbarSlotsContext,
|
||||
} from '../index';
|
||||
|
||||
export class EdgelessSlideMenu extends WithDisposable(LitElement) {
|
||||
static override styles = css`
|
||||
:host {
|
||||
max-width: 100%;
|
||||
}
|
||||
::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
.slide-menu-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
.menu-container {
|
||||
background: var(--affine-background-overlay-panel-color);
|
||||
border-radius: 8px 8px 0 0;
|
||||
border: 1px solid var(--affine-border-color);
|
||||
border-bottom: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: fit-content;
|
||||
max-width: 100%;
|
||||
position: relative;
|
||||
height: calc(var(--menu-height) + 1px);
|
||||
box-sizing: border-box;
|
||||
padding-left: 10px;
|
||||
scroll-snap-type: x mandatory;
|
||||
}
|
||||
.menu-container-scrollable {
|
||||
overflow-x: auto;
|
||||
overscroll-behavior: none;
|
||||
scrollbar-width: none;
|
||||
height: 100%;
|
||||
padding-right: 10px;
|
||||
}
|
||||
.slide-menu-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
transition: left 0.5s ease-in-out;
|
||||
width: fit-content;
|
||||
}
|
||||
.next-slide-button,
|
||||
.previous-slide-button {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
border: 1px solid var(--affine-border-color);
|
||||
background: var(--affine-background-overlay-panel-color);
|
||||
box-shadow: var(--affine-shadow-2);
|
||||
color: var(--affine-icon-color);
|
||||
transition:
|
||||
transform 0.3s ease-in-out,
|
||||
opacity 0.5s ease-in-out;
|
||||
z-index: 12;
|
||||
}
|
||||
.next-slide-button {
|
||||
opacity: 0;
|
||||
display: flex;
|
||||
top: 50%;
|
||||
right: 0;
|
||||
transform: translate(50%, -50%) scale(0.5);
|
||||
}
|
||||
.next-slide-button:hover {
|
||||
cursor: pointer;
|
||||
transform: translate(50%, -50%) scale(1);
|
||||
}
|
||||
.previous-slide-button {
|
||||
opacity: 0;
|
||||
top: 50%;
|
||||
left: 0;
|
||||
transform: translate(-50%, -50%) scale(0.5);
|
||||
}
|
||||
.previous-slide-button:hover {
|
||||
cursor: pointer;
|
||||
transform: translate(-50%, -50%) scale(1);
|
||||
}
|
||||
.previous-slide-button svg {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
`;
|
||||
|
||||
private _handleSlideButtonClick(direction: 'left' | 'right') {
|
||||
const totalWidth = this._slideMenuContent.clientWidth;
|
||||
const currentScrollLeft = this._menuContainer.scrollLeft;
|
||||
const menuWidth = this._menuContainer.clientWidth;
|
||||
const newLeft =
|
||||
currentScrollLeft + (direction === 'left' ? -menuWidth : menuWidth);
|
||||
this._menuContainer.scrollTo({
|
||||
left: Math.max(0, Math.min(newLeft, totalWidth)),
|
||||
behavior: 'smooth',
|
||||
});
|
||||
}
|
||||
|
||||
private _handleWheel(event: WheelEvent) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
private _toggleSlideButton() {
|
||||
const scrollLeft = this._menuContainer.scrollLeft;
|
||||
const menuWidth = this._menuContainer.clientWidth;
|
||||
|
||||
const leftMin = 0;
|
||||
const leftMax = this._slideMenuContent.clientWidth - menuWidth;
|
||||
this.showPrevious = scrollLeft > leftMin;
|
||||
this.showNext = scrollLeft < leftMax;
|
||||
}
|
||||
|
||||
override firstUpdated() {
|
||||
setTimeout(this._toggleSlideButton.bind(this), 0);
|
||||
this._disposables.addFromEvent(this._menuContainer, 'scrollend', () => {
|
||||
this._toggleSlideButton();
|
||||
});
|
||||
this._disposables.add(
|
||||
this.toolbarSlots.resize.subscribe(() => this._toggleSlideButton())
|
||||
);
|
||||
}
|
||||
|
||||
override render() {
|
||||
const iconSize = { width: '32px', height: '32px' };
|
||||
|
||||
return html`
|
||||
<div class="slide-menu-wrapper">
|
||||
<div
|
||||
class="previous-slide-button"
|
||||
@click=${() => this._handleSlideButtonClick('left')}
|
||||
style=${styleMap({ opacity: this.showPrevious ? '1' : '0' })}
|
||||
>
|
||||
${ArrowRightSmallIcon(iconSize)}
|
||||
</div>
|
||||
<div
|
||||
class="menu-container"
|
||||
style=${styleMap({ '--menu-height': this.height })}
|
||||
>
|
||||
<slot name="prefix"></slot>
|
||||
<div class="menu-container-scrollable">
|
||||
<div class="slide-menu-content" @wheel=${this._handleWheel}>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
style=${styleMap({ opacity: this.showNext ? '1' : '0' })}
|
||||
class="next-slide-button"
|
||||
@click=${() => this._handleSlideButtonClick('right')}
|
||||
>
|
||||
${ArrowRightSmallIcon(iconSize)}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@query('.menu-container-scrollable')
|
||||
private accessor _menuContainer!: HTMLDivElement;
|
||||
|
||||
@query('.slide-menu-content')
|
||||
private accessor _slideMenuContent!: HTMLDivElement;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor height = '40px';
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor showNext = false;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor showPrevious = false;
|
||||
|
||||
@consume({ context: edgelessToolbarSlotsContext })
|
||||
accessor toolbarSlots!: EdgelessToolbarSlots;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { ArrowUpSmallIcon } from '@blocksuite/icons/lit';
|
||||
import { ShadowlessElement } from '@blocksuite/std';
|
||||
import { css, html } from 'lit';
|
||||
|
||||
export class ToolbarArrowUpIcon extends ShadowlessElement {
|
||||
static override styles = css`
|
||||
.arrow-up-icon {
|
||||
position: absolute;
|
||||
top: -2px;
|
||||
right: -2px;
|
||||
}
|
||||
`;
|
||||
|
||||
override render() {
|
||||
return html`<span class="arrow-up-icon"> ${ArrowUpSmallIcon()} </span>`;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user