generator share links; secure send copy message
This commit is contained in:
116
plugins/tools/frontend/sharelink.js
Normal file
116
plugins/tools/frontend/sharelink.js
Normal file
@@ -0,0 +1,116 @@
|
||||
/**
|
||||
* A link that opens the generator with somebody's work already in it.
|
||||
*
|
||||
* WHY THE FRAGMENT
|
||||
*
|
||||
* Everything rides after the `#`. A fragment is never transmitted: it does not
|
||||
* reach Flask, it is not in the IIS log, and the proxy between the two never
|
||||
* sees it. A query string would put the encoded content in all three, and the
|
||||
* whole point of this feature is to send somebody a code they can print - which
|
||||
* means the content is often an asset number, a serial, or a URL somebody would
|
||||
* rather not scatter through logs.
|
||||
*
|
||||
* It is NOT secrecy. The link still sits in a chat message, in browser history
|
||||
* and in whatever archives that chat. Anyone holding the link has the content.
|
||||
* Secure Send exists for the case where that matters.
|
||||
*
|
||||
* WHAT TRAVELS
|
||||
*
|
||||
* What was typed (content, label, copies, the CSV) plus the settings that
|
||||
* change how the label looks. Not the picture: a data URL runs to megabytes and
|
||||
* would produce a link no chat client will carry.
|
||||
*/
|
||||
|
||||
// Short keys because the whole thing has to survive being pasted into a chat
|
||||
// box. `c` is the content, `t` the code type, `p` the label preset.
|
||||
const SHORT = {
|
||||
c: 'content',
|
||||
l: 'label',
|
||||
n: 'copies',
|
||||
b: 'back',
|
||||
v: 'csv',
|
||||
s: 'source',
|
||||
t: 'codetype',
|
||||
e: 'errorcorrection',
|
||||
p: 'preset',
|
||||
}
|
||||
|
||||
// Layout values travel under their own names - there are enough of them that
|
||||
// abbreviating would cost more in confusion than it saves in characters.
|
||||
const PASSTHROUGH = [
|
||||
'labelwidth', 'labelheight', 'padding', 'quiet', 'gap', 'codesize',
|
||||
'barheight', 'labelfont', 'layout', 'align', 'valign',
|
||||
'contentx', 'contenty', 'nudgex', 'nudgey', 'dpi',
|
||||
'backenabled', 'backsource', 'backcodetype', 'backtext', 'backlayout',
|
||||
'backorder',
|
||||
]
|
||||
|
||||
const LONG = Object.fromEntries(Object.entries(SHORT).map(([k, v]) => [v, k]))
|
||||
|
||||
/**
|
||||
* Build the fragment for a state object. Returns '' when there is nothing to
|
||||
* share, so a caller can disable its button rather than emit a bare '#'.
|
||||
*/
|
||||
export function buildFragment (state, defaults = {}) {
|
||||
const params = new URLSearchParams()
|
||||
|
||||
for (const [key, name] of Object.entries(SHORT)) {
|
||||
const value = state[name]
|
||||
if (value === undefined || value === null || value === '') continue
|
||||
// Copies of 1 is what everybody has; it does not need saying.
|
||||
if (name === 'copies' && Number(value) === 1) continue
|
||||
params.set(key, String(value))
|
||||
}
|
||||
|
||||
// Only the layout values that DIFFER from this browser's current settings.
|
||||
// Sending all twenty would make a link four times longer for no gain, and the
|
||||
// recipient's own saved settings are a reasonable starting point.
|
||||
for (const name of PASSTHROUGH) {
|
||||
const value = state[name]
|
||||
if (value === undefined || value === null || value === '') continue
|
||||
if (defaults[name] !== undefined && String(defaults[name]) === String(value)) continue
|
||||
params.set(name, String(value))
|
||||
}
|
||||
|
||||
const encoded = params.toString()
|
||||
return encoded ? '#' + encoded : ''
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a fragment back into a partial state object.
|
||||
*
|
||||
* Unknown keys are ignored rather than refused: a link made by a newer version
|
||||
* must still open here, minus whatever this version does not understand.
|
||||
*/
|
||||
export function readFragment (hash) {
|
||||
const raw = String(hash || '').replace(/^#/, '')
|
||||
if (!raw) return {}
|
||||
|
||||
const params = new URLSearchParams(raw)
|
||||
const state = {}
|
||||
|
||||
for (const [key, value] of params.entries()) {
|
||||
const name = SHORT[key] || (PASSTHROUGH.includes(key) ? key : null)
|
||||
if (!name) continue
|
||||
state[name] = value
|
||||
}
|
||||
return state
|
||||
}
|
||||
|
||||
/**
|
||||
* Coerce a fragment value to the type the ref already holds.
|
||||
*
|
||||
* Everything arrives as a string. Assigning '203' to a numeric input renders,
|
||||
* but the moment it is used in arithmetic it concatenates - so a nudge of 0
|
||||
* plus '2' becomes '02'. Match the current value's type instead.
|
||||
*/
|
||||
export function coerce (value, current) {
|
||||
if (typeof current === 'number') {
|
||||
const number = Number(value)
|
||||
return Number.isFinite(number) ? number : current
|
||||
}
|
||||
if (typeof current === 'boolean') {
|
||||
return value === 'true' || value === '1'
|
||||
}
|
||||
return value
|
||||
}
|
||||
Reference in New Issue
Block a user