mirror of https://github.com/ghostfolio/ghostfolio
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
152 lines
4.4 KiB
152 lines
4.4 KiB
import BaseFormatter from './base.js';
|
|
const OPERATIONS = {
|
|
add: 'add',
|
|
remove: 'remove',
|
|
replace: 'replace',
|
|
move: 'move',
|
|
};
|
|
class JSONFormatter extends BaseFormatter {
|
|
constructor() {
|
|
super();
|
|
this.includeMoveDestinations = true;
|
|
}
|
|
prepareContext(context) {
|
|
super.prepareContext(context);
|
|
context.result = [];
|
|
context.path = [];
|
|
context.pushCurrentOp = function (obj) {
|
|
if (obj.op === 'add' || obj.op === 'replace') {
|
|
this.result.push({
|
|
op: obj.op,
|
|
path: this.currentPath(),
|
|
value: obj.value,
|
|
});
|
|
}
|
|
else if (obj.op === 'remove') {
|
|
this.result.push({ op: obj.op, path: this.currentPath() });
|
|
}
|
|
else {
|
|
obj;
|
|
}
|
|
};
|
|
context.pushMoveOp = function (to) {
|
|
const from = this.currentPath();
|
|
this.result.push({
|
|
op: OPERATIONS.move,
|
|
from,
|
|
path: this.toPath(to),
|
|
});
|
|
};
|
|
context.currentPath = function () {
|
|
return `/${this.path.join('/')}`;
|
|
};
|
|
context.toPath = function (toPath) {
|
|
const to = this.path.slice();
|
|
to[to.length - 1] = toPath;
|
|
return `/${to.join('/')}`;
|
|
};
|
|
}
|
|
typeFormattterErrorFormatter(context, err) {
|
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
context.out(`[ERROR] ${err}`);
|
|
}
|
|
rootBegin() { }
|
|
rootEnd() { }
|
|
nodeBegin({ path }, key, leftKey) {
|
|
path.push(leftKey);
|
|
}
|
|
nodeEnd({ path }) {
|
|
path.pop();
|
|
}
|
|
format_unchanged() { }
|
|
format_movedestination() { }
|
|
format_node(context, delta, left) {
|
|
this.formatDeltaChildren(context, delta, left);
|
|
}
|
|
format_added(context, delta) {
|
|
context.pushCurrentOp({ op: OPERATIONS.add, value: delta[0] });
|
|
}
|
|
format_modified(context, delta) {
|
|
context.pushCurrentOp({ op: OPERATIONS.replace, value: delta[1] });
|
|
}
|
|
format_deleted(context) {
|
|
context.pushCurrentOp({ op: OPERATIONS.remove });
|
|
}
|
|
format_moved(context, delta) {
|
|
const to = delta[1];
|
|
context.pushMoveOp(to);
|
|
}
|
|
format_textdiff() {
|
|
throw new Error('Not implemented');
|
|
}
|
|
format(delta, left) {
|
|
const context = {};
|
|
this.prepareContext(context);
|
|
const preparedContext = context;
|
|
this.recurse(preparedContext, delta, left);
|
|
return preparedContext.result;
|
|
}
|
|
}
|
|
export default JSONFormatter;
|
|
const last = (arr) => arr[arr.length - 1];
|
|
const sortBy = (arr, pred) => {
|
|
arr.sort(pred);
|
|
return arr;
|
|
};
|
|
const compareByIndexDesc = (indexA, indexB) => {
|
|
const lastA = parseInt(indexA, 10);
|
|
const lastB = parseInt(indexB, 10);
|
|
if (!(isNaN(lastA) || isNaN(lastB))) {
|
|
return lastB - lastA;
|
|
}
|
|
else {
|
|
return 0;
|
|
}
|
|
};
|
|
const opsByDescendingOrder = (removeOps) => sortBy(removeOps, (a, b) => {
|
|
const splitA = a.path.split('/');
|
|
const splitB = b.path.split('/');
|
|
if (splitA.length !== splitB.length) {
|
|
return splitA.length - splitB.length;
|
|
}
|
|
else {
|
|
return compareByIndexDesc(last(splitA), last(splitB));
|
|
}
|
|
});
|
|
export const partitionOps = (arr, fns) => {
|
|
const initArr = Array(fns.length + 1)
|
|
.fill(undefined)
|
|
.map(() => []);
|
|
return arr
|
|
.map((item) => {
|
|
let position = fns.map((fn) => fn(item)).indexOf(true);
|
|
if (position < 0) {
|
|
position = fns.length;
|
|
}
|
|
return { item, position };
|
|
})
|
|
.reduce((acc, item) => {
|
|
acc[item.position].push(item.item);
|
|
return acc;
|
|
}, initArr);
|
|
};
|
|
const isMoveOp = ({ op }) => op === 'move';
|
|
const isRemoveOp = ({ op }) => op === 'remove';
|
|
const reorderOps = (diff) => {
|
|
const [moveOps, removedOps, restOps] = partitionOps(diff, [
|
|
isMoveOp,
|
|
isRemoveOp,
|
|
]);
|
|
const removeOpsReverse = opsByDescendingOrder(removedOps);
|
|
return [...removeOpsReverse, ...moveOps, ...restOps];
|
|
};
|
|
let defaultInstance;
|
|
export const format = (delta, left) => {
|
|
if (!defaultInstance) {
|
|
defaultInstance = new JSONFormatter();
|
|
}
|
|
return reorderOps(defaultInstance.format(delta, left));
|
|
};
|
|
export const log = (delta, left) => {
|
|
console.log(format(delta, left));
|
|
};
|
|
|