feat: Initial clean release of CHM
一些检查失败了
automerge / automerge (push) Has been cancelled
react-native-android-build-apk / AssembleRelease (push) Has been cancelled
Build macOS M1 / Main (push) Has been cancelled
Joplin Continuous Integration / Main (macos-15-intel) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-11-vs2026-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-2025) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04-arm) (push) Has been cancelled
Joplin UI tests / Main (ubuntu-22.04) (push) Has been cancelled
Joplin UI tests / Main (windows-2025) (push) Has been cancelled
Joplin UI tests / Mobile (push) Has been cancelled
Close stale issues / ProcessStaleIssues (push) Has been cancelled
一些检查失败了
automerge / automerge (push) Has been cancelled
react-native-android-build-apk / AssembleRelease (push) Has been cancelled
Build macOS M1 / Main (push) Has been cancelled
Joplin Continuous Integration / Main (macos-15-intel) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-11-vs2026-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-2025) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04-arm) (push) Has been cancelled
Joplin UI tests / Main (ubuntu-22.04) (push) Has been cancelled
Joplin UI tests / Main (windows-2025) (push) Has been cancelled
Joplin UI tests / Mobile (push) Has been cancelled
Close stale issues / ProcessStaleIssues (push) Has been cancelled
这个提交包含在:
@@ -0,0 +1,167 @@
|
||||
import { produce } from 'immer';
|
||||
import iterateItems from './iterateItems';
|
||||
import { LayoutItem, LayoutItemDirection } from './types';
|
||||
|
||||
// Fallback size assigned to a non-absorber sibling that has none, so the
|
||||
// absorber stays the only width/height-less child of its container.
|
||||
const itemMinWidthFallback = 250;
|
||||
const itemMinHeightFallback = 250;
|
||||
|
||||
function isLastVisible(itemIndex: number, item: LayoutItem, parent: LayoutItem) {
|
||||
if (item.visible === false) return false;
|
||||
|
||||
for (let i = parent.children.length - 1; i >= 0; i--) {
|
||||
const child = parent.children[i];
|
||||
if (child && child.visible !== false) return i === itemIndex;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// A subtree counts as flexible if it carries the flag at any nested level, so
|
||||
// the absorber role propagates up to the root container.
|
||||
function containsFlexible(item: LayoutItem): boolean {
|
||||
if (item.visible === false) return false;
|
||||
if (item.flexible) return true;
|
||||
if (!item.children) return false;
|
||||
for (const child of item.children) {
|
||||
if (containsFlexible(child)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function explicitAbsorberIndex(parent: LayoutItem) {
|
||||
if (!parent.children) return -1;
|
||||
for (let i = 0; i < parent.children.length; i++) {
|
||||
const child = parent.children[i];
|
||||
if (child.visible === false) continue;
|
||||
if (containsFlexible(child)) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
function updateItemSize(itemIndex: number, itemDraft: LayoutItem, parent: LayoutItem) {
|
||||
if (!parent) return;
|
||||
|
||||
// If a container has only one child, this child should not
|
||||
// have a width and height, and simply fill up the container
|
||||
if (parent.children.length === 1) {
|
||||
delete itemDraft.width;
|
||||
delete itemDraft.height;
|
||||
}
|
||||
|
||||
const explicitIdx = explicitAbsorberIndex(parent);
|
||||
|
||||
if (explicitIdx !== -1) {
|
||||
// Strip the absorber's size and assign fallback sizes to any sibling
|
||||
// that lacks one, so the absorber alone fills remaining space.
|
||||
if (explicitIdx !== itemIndex || itemDraft.visible === false) return;
|
||||
|
||||
if (parent.direction === LayoutItemDirection.Row) {
|
||||
delete itemDraft.width;
|
||||
for (let i = 0; i < parent.children.length; i++) {
|
||||
if (i === itemIndex) continue;
|
||||
const child = parent.children[i];
|
||||
if (child.visible === false) continue;
|
||||
if (!('width' in child)) {
|
||||
(child as { width?: number }).width = child.minWidth || itemMinWidthFallback;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
delete itemDraft.height;
|
||||
for (let i = 0; i < parent.children.length; i++) {
|
||||
if (i === itemIndex) continue;
|
||||
const child = parent.children[i];
|
||||
if (child.visible === false) continue;
|
||||
if (!('height' in child)) {
|
||||
(child as { height?: number }).height = child.minHeight || itemMinHeightFallback;
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// No explicit absorber: legacy fallback makes the last visible child
|
||||
// flexible when every other sibling is already sized.
|
||||
if (isLastVisible(itemIndex, itemDraft, parent)) {
|
||||
let allChildrenAreSized = true;
|
||||
for (const child of parent.children) {
|
||||
if (child.visible === false) continue;
|
||||
|
||||
if (parent.direction === LayoutItemDirection.Row) {
|
||||
if (!child.width) { allChildrenAreSized = false; break; }
|
||||
} else {
|
||||
if (!child.height) { allChildrenAreSized = false; break; }
|
||||
}
|
||||
}
|
||||
|
||||
if (allChildrenAreSized) {
|
||||
if (parent.direction === LayoutItemDirection.Row) {
|
||||
delete itemDraft.width;
|
||||
} else {
|
||||
delete itemDraft.height;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// All items get a draggable trailing edge except the last visible child,
|
||||
// whose trailing edge is the container's edge.
|
||||
function updateResizeRules(itemIndex: number, itemDraft: LayoutItem, parent: LayoutItem) {
|
||||
if (!parent) return;
|
||||
const isLastVisibleChild = isLastVisible(itemIndex, itemDraft, parent);
|
||||
itemDraft.resizableRight = parent.direction === LayoutItemDirection.Row && !isLastVisibleChild;
|
||||
itemDraft.resizableBottom = parent.direction === LayoutItemDirection.Column && !isLastVisibleChild;
|
||||
}
|
||||
|
||||
// Container direction should alternate between row (for the root) and
|
||||
// columns, then rows again.
|
||||
function updateDirection(_itemIndex: number, itemDraft: LayoutItem, parent: LayoutItem) {
|
||||
if (!parent) {
|
||||
itemDraft.direction = LayoutItemDirection.Row;
|
||||
} else {
|
||||
itemDraft.direction = parent.direction === LayoutItemDirection.Row ? LayoutItemDirection.Column : LayoutItemDirection.Row;
|
||||
}
|
||||
}
|
||||
|
||||
function itemShouldBeVisible(item: LayoutItem): boolean {
|
||||
if (!item.children) return item.visible !== false;
|
||||
|
||||
let oneIsVisible = false;
|
||||
|
||||
for (const child of item.children) {
|
||||
if (itemShouldBeVisible(child)) {
|
||||
oneIsVisible = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return oneIsVisible;
|
||||
}
|
||||
|
||||
// If all children of a container are hidden, the container should be
|
||||
// hidden too. A container visibility cannot be changed by the user.
|
||||
function updateContainerVisibility(_itemIndex: number, itemDraft: LayoutItem, _parent: LayoutItem) {
|
||||
if (itemDraft.children) {
|
||||
itemDraft.visible = itemShouldBeVisible(itemDraft);
|
||||
} else {
|
||||
itemDraft.visible = itemDraft.visible !== false;
|
||||
}
|
||||
}
|
||||
|
||||
export default function validateLayout(layout: LayoutItem): LayoutItem {
|
||||
if (!layout) throw new Error('Layout is null');
|
||||
if (!layout.children || !layout.children.length) throw new Error('Root does not have children');
|
||||
|
||||
return produce(layout, (draft: LayoutItem) => {
|
||||
draft.isRoot = true;
|
||||
|
||||
iterateItems(draft, (itemIndex: number, itemDraft: LayoutItem, parent: LayoutItem) => {
|
||||
updateItemSize(itemIndex, itemDraft, parent);
|
||||
updateResizeRules(itemIndex, itemDraft, parent);
|
||||
updateDirection(itemIndex, itemDraft, parent);
|
||||
updateContainerVisibility(itemIndex, itemDraft, parent);
|
||||
return true;
|
||||
});
|
||||
});
|
||||
}
|
||||
在新工单中引用
屏蔽一个用户