kempo-inventory 0.5.8 → 0.5.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -45,6 +45,15 @@
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
/* The category is a built-in property of every item; '' means uncategorised. */
|
|
48
|
+
/* Only web addresses become links; anything else (a javascript: or data: address, say) shows as plain text. */
|
|
49
|
+
function isWebLink(value){
|
|
50
|
+
try {
|
|
51
|
+
const { protocol } = new URL(String(value).trim());
|
|
52
|
+
return protocol === 'http:' || protocol === 'https:';
|
|
53
|
+
} catch {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
48
57
|
function categoryOf(item){ return (item.category ?? '').trim(); }
|
|
49
58
|
function categoryHref(name){ return name ? `/category?name=${encodeURIComponent(name)}` : '/category?uncategorised=1'; }
|
|
50
59
|
function tagChips(tags){ return tags?.length ? html`<div class="tag-list">${tags.map(tag => html`<a class="tag-chip" href="/search?tag=${encodeURIComponent(tag)}">${tag}</a>`)}</div>` : ''; }
|
|
@@ -112,7 +121,7 @@
|
|
|
112
121
|
? html`<span style="display: inline-block; width: 4rem; height: 4rem; vertical-align: middle; margin-right: var(--spacer_h); border: 1px solid var(--c_border); border-radius: var(--radius); background: ${item.fields[f.key]};"></span>${item.fields[f.key]}`
|
|
113
122
|
: f.type === 'item' && linked[item.fields[f.key]]
|
|
114
123
|
? html`<a href="/items/${item.fields[f.key]}">${displayValue(f, item.fields[f.key], context)}</a>`
|
|
115
|
-
: f.type === 'url'
|
|
124
|
+
: f.type === 'url' && isWebLink(item.fields[f.key])
|
|
116
125
|
? html`<a href=${item.fields[f.key]} target="_blank" rel="noopener">${item.fields[f.key]}</a>`
|
|
117
126
|
: f.type === 'longtext'
|
|
118
127
|
? html`<span style="white-space: pre-wrap;">${item.fields[f.key]}</span>`
|
|
@@ -28,6 +28,11 @@ export const CONVERSIONS = { select: ['text'], text: ['longtext', 'url'] };
|
|
|
28
28
|
|
|
29
29
|
export const canConvert = (from, to) => from === to || (CONVERSIONS[from] ?? []).includes(to);
|
|
30
30
|
|
|
31
|
+
/* A javascript: link runs code when clicked. Browsers ignore spaces and control characters inside the scheme, so they are allowed for. */
|
|
32
|
+
const SCRIPT_SCHEME = /^[\s\x00-\x1f\x7f]*j[\s\x00-\x1f\x7f]*a[\s\x00-\x1f\x7f]*v[\s\x00-\x1f\x7f]*a[\s\x00-\x1f\x7f]*s[\s\x00-\x1f\x7f]*c[\s\x00-\x1f\x7f]*r[\s\x00-\x1f\x7f]*i[\s\x00-\x1f\x7f]*p[\s\x00-\x1f\x7f]*t[\s\x00-\x1f\x7f]*:/i;
|
|
33
|
+
export const hasScriptScheme = value => SCRIPT_SCHEME.test(String(value));
|
|
34
|
+
export const stripScriptScheme = value => String(value).replace(SCRIPT_SCHEME, '');
|
|
35
|
+
|
|
31
36
|
/*
|
|
32
37
|
A `media` value is a list of ids, each saying where the file lives. A bare id is a kempo-media
|
|
33
38
|
asset; "files:<id>" is a kempo-files file.
|
package/server/utils/fields.js
CHANGED
|
@@ -3,7 +3,7 @@ import { eq, and, asc, sql } from 'drizzle-orm';
|
|
|
3
3
|
import crypto from 'crypto';
|
|
4
4
|
import { kempoInventoryField, kempoInventoryItem, kempoInventoryCategory } from '../db/schema.js';
|
|
5
5
|
import { categoryKey, tidyCategory, MAX_CATEGORY } from './categoryLogic.js';
|
|
6
|
-
import { normalizeFieldDefinition, OPTIONAL_TYPES, canConvert, coerceValue } from './fieldTypes.js';
|
|
6
|
+
import { normalizeFieldDefinition, OPTIONAL_TYPES, canConvert, coerceValue, hasScriptScheme, stripScriptScheme } from './fieldTypes.js';
|
|
7
7
|
import { mediaAvailable } from './media.js';
|
|
8
8
|
import { EVENTS, guard, notify } from './hooks.js';
|
|
9
9
|
|
|
@@ -20,10 +20,12 @@ const addressesOf = async (key, label) => {
|
|
|
20
20
|
.where(sql`${kempoInventoryItem.data}->>${key} IS NOT NULL`);
|
|
21
21
|
const rewrites = [];
|
|
22
22
|
for(const row of rows){
|
|
23
|
-
const
|
|
24
|
-
if(
|
|
25
|
-
const
|
|
26
|
-
|
|
23
|
+
const original = row.data[key];
|
|
24
|
+
if(original === '' || original === null) continue;
|
|
25
|
+
const cleaned = hasScriptScheme(original) ? stripScriptScheme(original) : original;
|
|
26
|
+
const [invalid, address] = coerceValue({ type: 'url', label }, cleaned);
|
|
27
|
+
const value = !invalid && address ? address : cleaned;
|
|
28
|
+
if(value !== original) rewrites.push({ id: row.id, data: { ...row.data, [key]: value } });
|
|
27
29
|
}
|
|
28
30
|
return rewrites;
|
|
29
31
|
};
|