-
Notifications
You must be signed in to change notification settings - Fork 414
/
parsePanesSegment.js
115 lines (97 loc) · 3.04 KB
/
parsePanesSegment.js
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
import {EMPTY_PARAMS} from '../'
import {exclusiveParams} from '../contexts/PaneRouterContext'
// old: authors;knut,{"template":"diaryEntry"}
// new: authors;knut,view=diff,eyJyZXYxIjoiYWJjMTIzIiwicmV2MiI6ImRlZjQ1NiJ9|latest-posts
const panePattern = /^([.a-z0-9_-]+),?({.*?})?(?:(;|$))/i
const isParam = str => /^[a-z0-9]+=[^=]+/i.test(str)
const isPayload = str =>
/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(str)
function parseChunks(chunks, initial = {}) {
return chunks.reduce(
(pane, chunk) => {
if (isParam(chunk)) {
const key = chunk.slice(0, chunk.indexOf('='))
const value = chunk.slice(key.length + 1)
pane.params = {...pane.params, [key]: value}
} else if (isPayload(chunk)) {
pane.payload = tryParseBase64Payload(chunk)
} else {
// eslint-disable-next-line no-console
console.warn('Unknown pane segment: %s - skipping', chunk)
}
return pane
},
{...initial, params: EMPTY_PARAMS, payload: undefined}
)
}
function encodeChunks(pane, i, group) {
const {payload, params = {}, id} = pane
const sameAsFirst = i !== 0 && id === group[0].id
const encodedPayload = typeof payload === 'undefined' ? undefined : btoa(JSON.stringify(payload))
const encodedParams = Object.keys(params).reduce((pairs, key) => {
if (
sameAsFirst &&
i !== 0 &&
!exclusiveParams.includes(key) &&
group[0].params[key] === params[key]
) {
return pairs
}
return params[key] ? [...pairs, `${key}=${params[key]}`] : pairs
}, [])
return (
[sameAsFirst ? '' : id]
.concat([encodedParams.length > 0 && encodedParams, encodedPayload].filter(Boolean))
.join(',') || ','
)
}
export function parsePanesSegment(str) {
if (str.indexOf(',{') !== -1) {
return parseOldPanesSegment(str)
}
return str
.split(';')
.map(group =>
group
.split('|')
.map(segment => {
const [id, ...chunks] = segment.split(',')
return parseChunks(chunks, {id})
})
.map((pane, i, siblings) => (pane.id ? pane : {...pane, id: siblings[0].id}))
)
.filter(group => group.length > 0)
}
export function encodePanesSegment(panes) {
return (panes || [])
.map(group => group.map(encodeChunks).join('|'))
.map(encodeURIComponent)
.join(';')
}
export function parseOldPanesSegment(str) {
const chunks = []
let buffer = str
while (buffer.length) {
const [match, id, payloadChunk] = buffer.match(panePattern) || []
if (!match) {
buffer = buffer.slice(1)
continue
}
const payload = payloadChunk && tryParsePayload(payloadChunk)
chunks.push({id, payload})
buffer = buffer.slice(match.length)
}
return chunks
}
function tryParsePayload(json) {
try {
return JSON.parse(json)
} catch (err) {
// eslint-disable-next-line no-console
console.warn(`Failed to parse parameters: ${err.message}`)
return undefined
}
}
function tryParseBase64Payload(data) {
return data ? tryParsePayload(atob(data)) : undefined
}