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,190 @@
|
||||
import iterateItems from './iterateItems';
|
||||
import { LayoutItem, LayoutItemDirection, tempContainerPrefix } from './types';
|
||||
import { produce } from 'immer';
|
||||
import uuid from '@joplin/lib/uuid';
|
||||
import validateLayout from './validateLayout';
|
||||
|
||||
export enum MoveDirection {
|
||||
Up = 'up',
|
||||
Down = 'down',
|
||||
Left = 'left',
|
||||
Right = 'right',
|
||||
}
|
||||
|
||||
enum MovementDirection {
|
||||
Horizontal = 1,
|
||||
Vertical = 2,
|
||||
}
|
||||
|
||||
function array_move<T>(arr: T[], old_index: number, new_index: number) {
|
||||
arr = arr.slice();
|
||||
if (new_index >= arr.length) {
|
||||
let k = new_index - arr.length + 1;
|
||||
while (k--) {
|
||||
arr.push(undefined);
|
||||
}
|
||||
}
|
||||
arr.splice(new_index, 0, arr.splice(old_index, 1)[0]);
|
||||
return arr;
|
||||
}
|
||||
|
||||
function findItemIndex(siblings: LayoutItem[], key: string) {
|
||||
return siblings.findIndex((value: LayoutItem) => {
|
||||
return value.key === key;
|
||||
});
|
||||
}
|
||||
|
||||
function isHorizontalMove(direction: MoveDirection) {
|
||||
return direction === MoveDirection.Left || direction === MoveDirection.Right;
|
||||
}
|
||||
|
||||
function resetItemSizes(items: LayoutItem[]) {
|
||||
return items.map((item: LayoutItem) => {
|
||||
const newItem = { ...item };
|
||||
delete newItem.width;
|
||||
delete newItem.height;
|
||||
return newItem;
|
||||
});
|
||||
}
|
||||
|
||||
export function canMove(direction: MoveDirection, item: LayoutItem, parent: LayoutItem) {
|
||||
if (!parent) return false;
|
||||
|
||||
if (isHorizontalMove(direction)) {
|
||||
if (parent.isRoot) {
|
||||
const idx = direction === MoveDirection.Left ? 0 : parent.children.length - 1;
|
||||
return parent.children[idx] !== item;
|
||||
} else if (parent.direction === LayoutItemDirection.Column) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (parent.isRoot) {
|
||||
return false;
|
||||
} else if (parent.direction === LayoutItemDirection.Column) {
|
||||
const idx = direction === MoveDirection.Up ? 0 : parent.children.length - 1;
|
||||
return parent.children[idx] !== item;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('Unhandled case');
|
||||
}
|
||||
|
||||
// For all movements we make the assumption that there's a root container,
|
||||
// which is a row of multiple columns. Within each of these columns there
|
||||
// can be multiple rows (one item per row). Items cannot be more deeply
|
||||
// nested.
|
||||
function moveItem(direction: MovementDirection, layout: LayoutItem, key: string, inc: number): LayoutItem {
|
||||
const itemParents: Record<string, LayoutItem> = {};
|
||||
|
||||
const itemIsRoot = (item: LayoutItem) => {
|
||||
return !itemParents[item.key];
|
||||
};
|
||||
|
||||
const updatedLayout = produce(layout, (draft: LayoutItem) => {
|
||||
iterateItems(draft, (itemIndex: number, item: LayoutItem, parent: LayoutItem) => {
|
||||
itemParents[item.key] = parent;
|
||||
|
||||
if (item.key !== key || !parent) return true;
|
||||
|
||||
// - "flow" means we are moving an item horizontally within a
|
||||
// row
|
||||
// - "contrary" means we are moving an item horizontally within
|
||||
// a column. Sicen it can't move horizontally, it is moved
|
||||
// out of its container. And vice-versa for vertical
|
||||
// movements.
|
||||
let moveType = null;
|
||||
|
||||
if (direction === MovementDirection.Horizontal && parent.direction === LayoutItemDirection.Row) moveType = 'flow';
|
||||
if (direction === MovementDirection.Horizontal && parent.direction === LayoutItemDirection.Column) moveType = 'contrary';
|
||||
if (direction === MovementDirection.Vertical && parent.direction === LayoutItemDirection.Column) moveType = 'flow';
|
||||
if (direction === MovementDirection.Vertical && parent.direction === LayoutItemDirection.Row) moveType = 'contrary';
|
||||
|
||||
if (moveType === 'flow') {
|
||||
const newIndex = itemIndex + inc;
|
||||
|
||||
if (newIndex >= parent.children.length || newIndex < 0) throw new Error(`Cannot move item "${key}" from position ${itemIndex} to ${newIndex}`);
|
||||
|
||||
// If the item next to it is a container (has children),
|
||||
// move the item inside the container
|
||||
if (parent.children[newIndex].children) {
|
||||
const newParent = parent.children[newIndex];
|
||||
parent.children.splice(itemIndex, 1);
|
||||
newParent.children.push(item);
|
||||
newParent.children = resetItemSizes(newParent.children);
|
||||
} else {
|
||||
// If the item is a child of the root container, create
|
||||
// a new column at `newIndex` and move the item that
|
||||
// was there, as well as the current item, in this
|
||||
// container.
|
||||
if (itemIsRoot(parent)) {
|
||||
const targetChild = parent.children[newIndex];
|
||||
|
||||
// The new container takes the size of the item it
|
||||
// replaces.
|
||||
const newSize: { width?: number; height?: number } = {};
|
||||
if (direction === MovementDirection.Horizontal) {
|
||||
if ('width' in targetChild) newSize.width = targetChild.width;
|
||||
} else {
|
||||
if ('height' in targetChild) newSize.height = targetChild.height;
|
||||
}
|
||||
|
||||
const newParent: LayoutItem = {
|
||||
key: `${tempContainerPrefix}${uuid.createNano()}`,
|
||||
direction: LayoutItemDirection.Column,
|
||||
children: [
|
||||
targetChild,
|
||||
item,
|
||||
],
|
||||
...newSize,
|
||||
};
|
||||
|
||||
parent.children[newIndex] = newParent;
|
||||
parent.children.splice(itemIndex, 1);
|
||||
|
||||
newParent.children = resetItemSizes(newParent.children);
|
||||
} else {
|
||||
// Otherwise the default case is simply to move the
|
||||
// item left/right
|
||||
parent.children = array_move(parent.children, itemIndex, newIndex);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const parentParent = itemParents[parent.key];
|
||||
const parentIndex = findItemIndex(parentParent.children, parent.key);
|
||||
|
||||
parent.children.splice(itemIndex, 1);
|
||||
|
||||
let newInc = inc;
|
||||
if (parent.children.length <= 1) {
|
||||
parentParent.children[parentIndex] = parent.children[0];
|
||||
newInc = inc < 0 ? inc + 1 : inc;
|
||||
}
|
||||
|
||||
const newItemIndex = parentIndex + newInc;
|
||||
|
||||
parentParent.children.splice(newItemIndex, 0, item);
|
||||
parentParent.children = resetItemSizes(parentParent.children);
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
});
|
||||
|
||||
return validateLayout(updatedLayout);
|
||||
}
|
||||
|
||||
export function moveHorizontal(layout: LayoutItem, key: string, inc: number): LayoutItem {
|
||||
return moveItem(MovementDirection.Horizontal, layout, key, inc);
|
||||
}
|
||||
|
||||
export function moveVertical(layout: LayoutItem, key: string, inc: number): LayoutItem {
|
||||
return moveItem(MovementDirection.Vertical, layout, key, inc);
|
||||
}
|
||||
|
||||
export function move(layout: LayoutItem, key: string, direction: MoveDirection): LayoutItem {
|
||||
if (direction === MoveDirection.Up) return moveVertical(layout, key, -1);
|
||||
if (direction === MoveDirection.Down) return moveVertical(layout, key, +1);
|
||||
if (direction === MoveDirection.Left) return moveHorizontal(layout, key, -1);
|
||||
if (direction === MoveDirection.Right) return moveHorizontal(layout, key, +1);
|
||||
throw new Error('Unreachable');
|
||||
}
|
||||
在新工单中引用
屏蔽一个用户