snapshot of https://github.com/toeverything/AFFiNE.git (canary, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Build & Test / Analyze (javascript, affine) (push) Failing after 4s
Build & Test / Analyze (typescript, affine) (push) Failing after 3s
Build & Test / Rust test filter (push) Failing after 4s
Build & Test / Check yarn binary (push) Failing after 3s
Build & Test / E2E BlockSuite Test (3) (push) Failing after 3s
Build & Test / Analyze (javascript, blocksuite) (push) Failing after 3s
Build & Test / Analyze (typescript, blocksuite) (push) Failing after 3s
Build & Test / Mobile native build filter (push) Failing after 3s
Build & Test / E2E BlockSuite Test (1) (push) Failing after 4s
Build & Test / E2E BlockSuite Test (2) (push) Failing after 4s
Build & Test / E2E Mobile Test (1) (push) Failing after 3s
Build & Test / Unit Test (1) (push) Has been skipped
Build & Test / Unit Test (2) (push) Has been skipped
Build & Test / Unit Test (3) (push) Has been skipped
Build & Test / Unit Test (4) (push) Has been skipped
Build & Test / Unit Test (5) (push) Has been skipped
Build & Test / Native Unit Test (push) Has been skipped
Build & Test / E2E BlockSuite Test (4) (push) Failing after 3s
Build & Test / E2E BlockSuite Test (5) (push) Failing after 3s
Build & Test / E2E BlockSuite Cross Browser Test (push) Failing after 3s
Build & Test / E2E Mobile Test (2) (push) Failing after 3s
Build & Test / Build AFFiNE native (x86_64-unknown-linux-gnu) (push) Failing after 3s
Build & Test / Build Server native (push) Failing after 3s
Build & Test / Check Git Status (push) Has been skipped
Build & Test / Server Test (0, 4) (push) Has been skipped
Build & Test / Server Test (1, 4) (push) Has been skipped
Build & Test / Server Test (2, 4) (push) Has been skipped
Build & Test / Server Test (3, 4) (push) Has been skipped
Build & Test / Server Test with Elasticsearch (push) Has been skipped
Build & Test / Build @affine/electron renderer (push) Failing after 3s
Build & Test / Copilot test filter (push) Failing after 4s
Build & Test / Lint Rust (push) Has been skipped
Build & Test / Server Test with Embedded Search (push) Has been skipped
Build & Test / Server Test with Manticore Search (push) Has been skipped
Build & Test / Server E2E Test (push) Has been skipped
Build & Test / Cloud Desktop E2E Test (push) Has been skipped
Build & Test / Cloud E2E Test 1/2 (push) Has been skipped
Build & Test / Cloud E2E Test 2/2 (push) Has been skipped
Build & Test / Build Android app (push) Has been skipped
Build & Test / Test Android app (push) Has been skipped
Build & Test / Run native tests (push) Has been skipped
Build & Test / Server Copilot Api Test (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (1, 5) (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (2, 5) (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (3, 5) (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (4, 5) (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (5, 5) (push) Has been skipped
Release / Canary Gate (push) Has been skipped
Build & Test / Build AFFiNE native (aarch64-apple-darwin) (push) Has been cancelled
Build & Test / Lint (push) Has been cancelled
Build & Test / Typecheck (push) Has been cancelled
Build & Test / Build AFFiNE native (x86_64-apple-darwin) (push) Has been cancelled
Build & Test / Desktop Test (ubuntu-latest, linux, x64, x86_64-unknown-linux-gnu, true) (push) Has been cancelled
Build & Test / Desktop Test (windows-latest, windows, x64, x86_64-pc-windows-msvc, true) (push) Has been cancelled
Build & Test / E2E Test (1) (push) Has been cancelled
Build & Test / E2E Test (2) (push) Has been cancelled
Build & Test / E2E Test (3) (push) Has been cancelled
Build & Test / E2E Test (4) (push) Has been cancelled
Build & Test / E2E Test (5) (push) Has been cancelled
Build & Test / Build AFFiNE native (aarch64-pc-windows-msvc) (push) Has been cancelled
Build & Test / Build AFFiNE native (x86_64-pc-windows-msvc) (push) Has been cancelled
Build & Test / Desktop Test (macos-latest, macos, arm64, aarch64-apple-darwin, true) (push) Has been cancelled
Build & Test / Desktop Test (macos-latest, macos, x64, x86_64-apple-darwin, false) (push) Has been cancelled
Build & Test / 3, 2, 1 Launch (push) Has been cancelled
Build & Test / Build iOS app (push) Has been cancelled
Copilot Test Automatically / Setup Test (push) Failing after 1s
Release / Prepare (push) Failing after 2s
Release / Release Cloud (push) Has been skipped
Release / Release Desktop (push) Has been skipped
Release / Release Docker Image (push) Has been skipped
Release / Release Mobile (push) Has been skipped

This commit is contained in:
upstream-mirror
2026-09-16 04:01:20 +05:00
commit 702cc698a7
9990 changed files with 1073618 additions and 0 deletions

View File

@@ -0,0 +1,33 @@
{
"name": "@blocksuite/affine-widget-scroll-anchoring",
"description": "Affine scroll anchoring widget.",
"type": "module",
"scripts": {
"build": "yarn run -T tsc"
},
"sideEffects": false,
"keywords": [],
"author": "toeverything",
"license": "MIT",
"dependencies": {
"@blocksuite/affine-ext-loader": "workspace:*",
"@blocksuite/affine-model": "workspace:*",
"@blocksuite/affine-shared": "workspace:*",
"@blocksuite/global": "workspace:*",
"@blocksuite/std": "workspace:*",
"@preact/signals-core": "^1.8.0",
"@toeverything/theme": "^1.1.23",
"lit": "^3.2.0"
},
"exports": {
".": "./src/index.ts",
"./view": "./src/view.ts"
},
"files": [
"src",
"dist",
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.27.5"
}

View File

@@ -0,0 +1,17 @@
import {
AFFINE_SCROLL_ANCHORING_WIDGET,
AffineScrollAnchoringWidget,
} from './scroll-anchoring.js';
export function effects() {
customElements.define(
AFFINE_SCROLL_ANCHORING_WIDGET,
AffineScrollAnchoringWidget
);
}
declare global {
interface HTMLElementTagNameMap {
[AFFINE_SCROLL_ANCHORING_WIDGET]: AffineScrollAnchoringWidget;
}
}

View File

@@ -0,0 +1,12 @@
import { WidgetViewExtension } from '@blocksuite/std';
import { literal, unsafeStatic } from 'lit/static-html.js';
import { AFFINE_SCROLL_ANCHORING_WIDGET } from './scroll-anchoring.js';
export * from './scroll-anchoring.js';
export const scrollAnchoringWidget = WidgetViewExtension(
'affine:page',
AFFINE_SCROLL_ANCHORING_WIDGET,
literal`${unsafeStatic(AFFINE_SCROLL_ANCHORING_WIDGET)}`
);

View File

@@ -0,0 +1,267 @@
import type { DocMode } from '@blocksuite/affine-model';
import { HighlightSelection } from '@blocksuite/affine-shared/selection';
import { Bound, deserializeXYWH } from '@blocksuite/global/gfx';
import { WidgetComponent } from '@blocksuite/std';
import { GfxControllerIdentifier, type GfxModel } from '@blocksuite/std/gfx';
import { computed, signal } from '@preact/signals-core';
import { cssVarV2 } from '@toeverything/theme/v2';
import { css, html, nothing, unsafeCSS } from 'lit';
import { classMap } from 'lit/directives/class-map.js';
import { styleMap } from 'lit/directives/style-map.js';
type Anchor = {
id: string;
mode: DocMode;
highlight: boolean;
};
export const AFFINE_SCROLL_ANCHORING_WIDGET = 'affine-scroll-anchoring-widget';
export class AffineScrollAnchoringWidget extends WidgetComponent {
static override styles = css`
:host {
pointer-events: none;
position: absolute;
left: 0px;
top: 0px;
transform-origin: left top;
contain: size layout;
z-index: 1;
& .highlight {
position: absolute;
box-sizing: border-box;
&.edgeless {
border-width: 1.39px;
border-style: solid;
border-color: ${unsafeCSS(
cssVarV2('layer/insideBorder/primaryBorder')
)};
box-shadow: var(--affine-active-shadow);
}
&.page {
border-radius: 5px;
background-color: var(--affine-hover-color);
}
}
}
`;
#listened = false;
readonly #requestUpdateFn = () => this.requestUpdate();
readonly #resizeObserver: ResizeObserver = new ResizeObserver(
this.#requestUpdateFn
);
anchor$ = signal<Anchor | null>(null);
anchorBounds$ = signal<Bound | null>(null);
highlighted$ = computed(() => this.std.selection.find(HighlightSelection));
#getBoundsInEdgeless() {
const controller = this.std.get(GfxControllerIdentifier);
const bounds = this.anchorBounds$.peek();
if (!bounds) return;
const { x, y, w, h } = bounds;
const zoom = controller.viewport.zoom;
const [vx, vy] = controller.viewport.toViewCoord(x, y);
return new Bound(vx, vy, w * zoom, h * zoom);
}
#getBoundsInPage(id: string) {
const blockComponent = this.std.view.getBlock(id);
if (!blockComponent) return;
const container = this.host;
const containerRect = container.getBoundingClientRect();
const { left, top, width, height } = blockComponent.getBoundingClientRect();
const offsetX = containerRect.left - container.offsetLeft;
const offsetY = containerRect.top - container.offsetTop;
return new Bound(left - offsetX, top - offsetY, width, height);
}
#moveToAnchorInEdgeless(id: string) {
const controller = this.std.get(GfxControllerIdentifier);
const surface = controller.surface;
if (!surface) return;
const xywh = controller.getElementById<GfxModel>(id)?.xywh;
if (!xywh) {
if (!this.#listened) return;
const blockUpdatedSubscription =
this.std.store.slots.blockUpdated.subscribe(v => {
if (v.type === 'add' && v.id === id) {
blockUpdatedSubscription.unsubscribe();
this.#moveToAnchorInEdgeless(id);
}
});
const elementAddedSubscription = surface.elementAdded.subscribe(v => {
if (v.id === id && v.local === false) {
elementAddedSubscription.unsubscribe();
this.#moveToAnchorInEdgeless(id);
}
});
// listen for document updates
this.disposables.add(blockUpdatedSubscription);
this.disposables.add(elementAddedSubscription);
return;
}
let bounds = Bound.fromXYWH(deserializeXYWH(xywh));
const viewport = controller.viewport;
const blockComponent = this.std.view.getBlock(id);
const parentComponent = blockComponent?.parentComponent;
if (parentComponent && parentComponent.flavour === 'affine:note') {
const { left: x, width: w } = parentComponent.getBoundingClientRect();
const { top: y, height: h } = blockComponent.getBoundingClientRect();
const coord = viewport.toModelCoordFromClientCoord([x, y]);
bounds = new Bound(
coord[0],
coord[1],
w / viewport.zoom,
h / viewport.zoom
);
}
const { zoom, centerX, centerY } = viewport.getFitToScreenData(
bounds,
[20, 20, 100, 20]
);
viewport.setZoom(zoom);
viewport.setCenter(centerX, centerY);
this.#listened = false;
this.anchorBounds$.value = bounds;
}
#moveToAnchorInPage(id: string) {
const blockComponent = this.std.view.getBlock(id);
if (!blockComponent) {
if (!this.#listened) return;
// listen for document updates
const subscription = this.std.store.slots.blockUpdated.subscribe(v => {
if (v.type === 'add' && v.id === id) {
subscription.unsubscribe();
this.#moveToAnchorInPage(id);
}
});
this.disposables.add(subscription);
return;
}
// use `requestAnimationFrame` to better scroll to the target
// because sometimes it is impossible to scroll to the target
requestAnimationFrame(() => {
blockComponent.scrollIntoView({
behavior: 'instant',
block: 'center',
});
});
this.#listened = false;
this.anchorBounds$.value = Bound.fromDOMRect(
blockComponent.getBoundingClientRect()
);
}
override connectedCallback() {
super.connectedCallback();
this.#resizeObserver.observe(this.host);
this.handleEvent('wheel', this.#requestUpdateFn);
this.disposables.addFromEvent(window, 'resize', this.#requestUpdateFn);
// Clears highlight
this.disposables.addFromEvent(this.host, 'pointerdown', () => {
this.#listened = false;
this.anchor$.value = null;
this.anchorBounds$.value = null;
});
// In edgeless
const controler = this.std.get(GfxControllerIdentifier);
this.disposables.add(
controler.viewport.viewportUpdated.subscribe(this.#requestUpdateFn)
);
this.disposables.add(
this.anchor$.subscribe(anchor => {
if (!anchor) return;
const { mode, id } = anchor;
if (mode === 'page') {
this.#moveToAnchorInPage(id);
return;
}
this.#moveToAnchorInEdgeless(id);
})
);
this.disposables.add(
this.highlighted$.subscribe(highlighted => {
if (!highlighted) return;
const {
mode,
blockIds: [bid],
elementIds: [eid],
highlight,
} = highlighted;
const id = mode === 'page' ? bid : eid || bid;
if (!id) return;
// Consumes highlight selection
this.std.selection.clear(['highlight']);
this.anchor$.value = { mode, id, highlight };
this.#listened = true;
})
);
}
override disconnectedCallback() {
super.disconnectedCallback();
this.#resizeObserver.disconnect();
}
override render() {
const anchor = this.anchor$.value;
if (!anchor || !anchor.highlight) return nothing;
const { mode, id } = anchor;
const bounds =
mode === 'page' ? this.#getBoundsInPage(id) : this.#getBoundsInEdgeless();
if (!bounds) return nothing;
const classes = { highlight: true, [mode]: true };
const style = {
left: `${bounds.x}px`,
top: `${bounds.y}px`,
width: `${bounds.w}px`,
height: `${bounds.h}px`,
};
return html`<div
class=${classMap(classes)}
style=${styleMap(style)}
></div>`;
}
}

View File

@@ -0,0 +1,21 @@
import {
type ViewExtensionContext,
ViewExtensionProvider,
} from '@blocksuite/affine-ext-loader';
import { effects } from './effects';
import { scrollAnchoringWidget } from './index';
export class ScrollAnchoringViewExtension extends ViewExtensionProvider {
override name = 'affine-scroll-anchoring-widget';
override effect() {
super.effect();
effects();
}
override setup(context: ViewExtensionContext) {
super.setup(context);
context.register(scrollAnchoringWidget);
}
}

View File

@@ -0,0 +1,16 @@
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./dist",
"tsBuildInfoFile": "./dist/tsconfig.tsbuildinfo"
},
"include": ["./src"],
"references": [
{ "path": "../../ext-loader" },
{ "path": "../../model" },
{ "path": "../../shared" },
{ "path": "../../../framework/global" },
{ "path": "../../../framework/std" }
]
}