summaryrefslogtreecommitdiffstats
path: root/lib/modifiers/summary/moveArticle.js
blob: 06d82ca9f9b47e05a1e3a7abf007873642ca885b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
var is = require('is');
var removeArticle = require('./removeArticle');
var insertArticle = require('./insertArticle');

/**
    Returns a new summary, with the given article removed from its
    origin level, and placed at the given target level.

    @param {Summary} summary
    @param {String|SummaryArticle} origin: level to remove
    @param {String|SummaryArticle} target: the level where the article will be found
    @return {Summary}
*/
function moveArticle(summary, origin, target) {
    // Coerce to level
    var originLevel = is.string(origin)? origin : origin.getLevel();
    var targetLevel = is.string(target)? target : target.getLevel();

    var article = summary.getByLevel(originLevel);

    // Remove
    var removed = removeArticle(summary, origin);

    // Adjust targetLevel if removing impacted it
    targetLevel = arrayToLevel(
        shiftLevel(levelToArray(originLevel),
                   levelToArray(targetLevel)));
    // Re-insert
    return insertArticle(removed, target, article);
}

/**
    @param {Array<Number>} removedLevel
    @param {Array<Number>} level The level to udpate
    @return {Array<Number>}
 */
function shiftLevel(removedLevel, level) {
    if (level.length === 0) {
        // `removedLevel` is under level, so no effect
        return level;
    } else if (removedLevel.length === 0) {
        // Either `level` is a child of `removedLevel`... or they are equal
        // This is undefined behavior.
        return level;
    }

    var removedRoot = removedLevel[0];
    var root = level[0];
    var removedRest = removedLevel.slice(1);
    var rest = level.slice(1);

    if (removedRoot < root) {
        // It will shift levels at this point. The rest is unchanged.
        return Array.prototype.concat(root - 1, rest);
    } else if (removedRoot === root) {
        // Look deeper
        return Array.prototype.concat(root, shiftLevel(removedRest, rest));
    } else {
        // No impact
        return level;
    }
}

/**
    @param {String}
    @return {Array<Number>}
 */
function levelToArray(l) {
    return l.split('.').map(function (char) {
        return parseInt(char, 10);
    });
}

/**
    @param {Array<Number>}
    @return {String}
 */
function arrayToLevel(a) {
    return a.join('.');
}

module.exports = moveArticle;