Welcome to mirror list, hosted at ThFree Co, Russian Federation.

TrackState.js « tracking « extensions « src - github.com/nextcloud/text.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 2a3832885b250d9c53ea8b6f26214bf03fd92c63 (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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
/*
 * @copyright Copyright (c) 2021 Julius Härtl <jus@bitgrid.net>
 *
 * @author Julius Härtl <jus@bitgrid.net>
 *
 * @license GNU AGPL version 3 or any later version
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 *
 */

import { Span } from './models'

/*
 * This code is heavily inspired by the change tracking example of prosemirror
 * https://github.com/ProseMirror/website/blob/master/example/track/index.js
 */

/**
 * @param {Array} map List of document ranges and corresponding authors
 * @param {object} transform ProseMirror transform object
 * @param {Array} clientIDs List of client IDs
 */
function updateBlameMap(map, transform, clientIDs) {
	const result = []
	const mapping = transform.mapping
	for (let i = 0; i < map.length; i++) {
		const span = map[i]
		const from = mapping.map(span.from, 1)
		const to = mapping.map(span.to, -1)
		if (from < to) result.push(new Span(from, to, span.author))
	}

	for (let i = 0; i < mapping.maps.length; i++) {
		const map = mapping.maps[i]; const after = mapping.slice(i + 1)
		map.forEach((_s, _e, start, end) => {
			insertIntoBlameMap(result, after.map(start, 1), after.map(end, -1), clientIDs[i])
		})
	}

	return result
}

/**
 * @param {Array} map List of document ranges and corresponding authors
 * @param {number} from The lower bound of the selection's main range
 * @param {number} to The upper bound of the selection's main range
 * @param {number} author ClientID of the author
 */
function insertIntoBlameMap(map, from, to, author) {
	if (from >= to) {
		return
	}
	let pos = 0
	let next
	for (; pos < map.length; pos++) {
		next = map[pos]
		if (next.author === author) {
			if (next.to >= from) break
		} else if (next.to > from) { // Different author, not before
			if (next.from < from) { // Sticks out to the left (loop below will handle right side)
				const left = new Span(next.from, from, next.author)
				if (next.to > to) map.splice(pos++, 0, left)
				else map[pos++] = left
			}
			break
		}
	}

	// eslint-ignore
	while ((next = map[pos])) {
		if (next.author === author) {
			if (next.from > to) break
			from = Math.min(from, next.from)
			to = Math.max(to, next.to)
			map.splice(pos, 1)
		} else {
			if (next.from >= to) break
			if (next.to > to) {
				map[pos] = new Span(to, next.to, next.author)
				break
			} else {
				map.splice(pos, 1)
			}
		}
	}

	map.splice(pos, 0, new Span(from, to, author))
}

export default class TrackState {

	constructor(blameMap) {
		// The blame map is a data structure that lists a sequence of
		// document ranges, along with the author that inserted them. This
		// can be used to, for example, highlight the part of the document
		// that was inserted by a author.
		this.blameMap = blameMap
	}

	// Apply a transform to this state
	applyTransform(transform) {
		const clientID = transform.getMeta('clientID') ?? transform.steps.map(item => 'self')
		const newBlame = updateBlameMap(this.blameMap, transform, clientID)
		// Create a new state—since these are part of the editor state, a
		// persistent data structure, they must not be mutated.
		return new TrackState(newBlame)
	}

}