mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-13 04:48:53 +00:00
feat(core): init organize (#7456)
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
import { expect, test } from 'vitest';
|
||||
|
||||
import { generateFractionalIndexingKeyBetween } from '../fractional-indexing';
|
||||
|
||||
function gen(a: string | null, b: string | null) {
|
||||
const result = generateFractionalIndexingKeyBetween(a, b);
|
||||
|
||||
expect(
|
||||
a === null || b === null || (a < result && result < b),
|
||||
`${a} ${b} ${result}`
|
||||
).toBe(true);
|
||||
return result;
|
||||
}
|
||||
|
||||
test('fractional-indexing', () => {
|
||||
for (let i = 0; i < 100; i++) {
|
||||
const set = new Set<string>();
|
||||
let a = null;
|
||||
let b = null;
|
||||
for (let i = 0; i < 100; i++) {
|
||||
const s1 = gen(a, b);
|
||||
expect(a === null || b === null || (a < s1 && s1 < b)).toBe(true);
|
||||
const s2 = gen(a, b);
|
||||
expect(a === null || b === null || (a < s2 && s2 < b)).toBe(true);
|
||||
|
||||
if (set.has(s1) || set.has(s2) || s1 === s2) {
|
||||
throw new Error('Duplicate key, ' + set.size + ', ' + s1 + ', ' + s2);
|
||||
break;
|
||||
}
|
||||
set.add(s1);
|
||||
set.add(s2);
|
||||
if (s1 < s2) {
|
||||
a = s1;
|
||||
b = s2;
|
||||
} else {
|
||||
a = s2;
|
||||
b = s1;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -8,6 +8,9 @@ export interface SortableProvider<T, K extends string | number> {
|
||||
}
|
||||
|
||||
// Using fractional-indexing managing orders of items in a list
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
export function createFractionalIndexingSortableHelper<
|
||||
T,
|
||||
K extends string | number,
|
||||
@@ -60,6 +63,35 @@ export function createFractionalIndexingSortableHelper<
|
||||
provider.setItemOrder(fromItem, generateKeyBetween(...args));
|
||||
}
|
||||
|
||||
function moveTo(fromId: K, toId: K, position: 'before' | 'after') {
|
||||
const items = getOrderedItems();
|
||||
const from = items.findIndex(i => provider.getItemId(i) === fromId);
|
||||
const to = items.findIndex(i => provider.getItemId(i) === toId);
|
||||
const fromItem = items[from] as T | undefined;
|
||||
if (fromItem === undefined) return;
|
||||
const toItem = items[to] as T | undefined;
|
||||
const toItemPrev = items[to - 1] as T | undefined;
|
||||
const toItemNext = items[to + 1] as T | undefined;
|
||||
const toItemOrder = toItem ? provider.getItemOrder(toItem) : null;
|
||||
const toItemPrevOrder = toItemPrev
|
||||
? provider.getItemOrder(toItemPrev)
|
||||
: null;
|
||||
const toItemNextOrder = toItemNext
|
||||
? provider.getItemOrder(toItemNext)
|
||||
: null;
|
||||
if (position === 'before') {
|
||||
provider.setItemOrder(
|
||||
fromItem,
|
||||
generateKeyBetween(toItemPrevOrder, toItemOrder)
|
||||
);
|
||||
} else {
|
||||
provider.setItemOrder(
|
||||
fromItem,
|
||||
generateKeyBetween(toItemOrder, toItemNextOrder)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cases example:
|
||||
* Imagine we have the following items, | a | b | c |
|
||||
@@ -108,6 +140,74 @@ export function createFractionalIndexingSortableHelper<
|
||||
getSmallestOrder,
|
||||
getNewItemOrder,
|
||||
move,
|
||||
moveTo,
|
||||
insertBefore,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* generate a key between a and b, the result key is always satisfied with a < result < b.
|
||||
* the key always has a random suffix, so there is no need to worry about collision.
|
||||
*
|
||||
* make sure a and b are generated by this function.
|
||||
*/
|
||||
export function generateFractionalIndexingKeyBetween(
|
||||
a: string | null,
|
||||
b: string | null
|
||||
) {
|
||||
const randomSize = 32;
|
||||
function randomString(length: number = randomSize) {
|
||||
const values = new Uint8Array(length);
|
||||
crypto.getRandomValues(values);
|
||||
const chars =
|
||||
'123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
|
||||
let result = '';
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += chars.charAt(values[i] % chars.length);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
if (a !== null && b !== null && a >= b) {
|
||||
throw new Error('a should be smaller than b');
|
||||
}
|
||||
|
||||
// get the subkey in full key
|
||||
// e.g.
|
||||
// a0xxxx -> a
|
||||
// a0x0xxxx -> a0x
|
||||
function subkey(key: string | null) {
|
||||
if (key === null) {
|
||||
return null;
|
||||
}
|
||||
if (key.length <= randomSize + 1) {
|
||||
// no subkey
|
||||
return key;
|
||||
}
|
||||
const splitAt = key.substring(0, key.length - randomSize - 1);
|
||||
return splitAt;
|
||||
}
|
||||
|
||||
const aSubkey = subkey(a);
|
||||
const bSubkey = subkey(b);
|
||||
|
||||
if (aSubkey === null && bSubkey === null) {
|
||||
// generate a new key
|
||||
return generateKeyBetween(null, null) + '0' + randomString();
|
||||
} else if (aSubkey === null && bSubkey !== null) {
|
||||
// generate a key before b
|
||||
return generateKeyBetween(null, bSubkey) + '0' + randomString();
|
||||
} else if (bSubkey === null && aSubkey !== null) {
|
||||
// generate a key after a
|
||||
return generateKeyBetween(aSubkey, null) + '0' + randomString();
|
||||
} else if (aSubkey !== null && bSubkey !== null) {
|
||||
// generate a key between a and b
|
||||
if (aSubkey === bSubkey && a !== null && b !== null) {
|
||||
// conflict, if the subkeys are the same, generate a key between fullkeys
|
||||
return generateKeyBetween(a, b) + '0' + randomString();
|
||||
} else {
|
||||
return generateKeyBetween(aSubkey, bSubkey) + '0' + randomString();
|
||||
}
|
||||
}
|
||||
throw new Error('Never reach here');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user