kempo-inventory 0.5.5 → 0.5.7

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/README.md CHANGED
@@ -23,6 +23,7 @@ Users in the `kempo-inventory:admin` group add fields from **Inventory > Fields*
23
23
  | Type | Holds |
24
24
  |---|---|
25
25
  | `text` | Short text (up to 1,000 characters) |
26
+ | `url` | A web address, shown as a link on the item page. Without a scheme `https://` is added; only `http` and `https` are accepted |
26
27
  | `longtext` | Longer text (up to 20,000 characters) |
27
28
  | `number` | Any number |
28
29
  | `boolean` | Yes / no |
@@ -23,6 +23,7 @@ locked: true
23
23
  <script type="module" src="/inventory/components/RowAction.js"></script>
24
24
  <script type="module" src="/inventory/components/FieldsManager.js"></script>
25
25
  <script type="module" src="/inventory/components/ToolbarControl.js"></script>
26
+ <script type="module" src="/admin/components/settings/SettingsPanel.js"></script>
26
27
 
27
28
  <h1 class="mb">Inventory</h1>
28
29
 
@@ -30,6 +31,7 @@ locked: true
30
31
  <k-tab id="itemsTab" for="items">Items</k-tab>
31
32
  <k-tab id="categoriesTab" for="categories">Categories</k-tab>
32
33
  <k-tab id="fieldsTab" for="fields">Fields</k-tab>
34
+ <k-tab for="settings">Settings</k-tab>
33
35
 
34
36
  <k-tab-content id="itemsContent" name="items">
35
37
  <!--
@@ -108,6 +110,11 @@ locked: true
108
110
  <p id="uncategorised" class="mt tc-muted"></p>
109
111
  </k-tab-content>
110
112
 
113
+ <k-tab-content name="settings" class="pt">
114
+ <p class="tc-muted mb">How quantities and item photos behave for everyone who uses the inventory. A change applies the next time it is used.</p>
115
+ <k-settings-panel owner="kempo-inventory"></k-settings-panel>
116
+ </k-tab-content>
117
+
111
118
  <k-tab-content id="fieldsContent" name="fields">
112
119
  <k-inventory-fields></k-inventory-fields>
113
120
  </k-tab-content>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kempo-inventory",
3
- "version": "0.5.5",
3
+ "version": "0.5.7",
4
4
  "description": "A basic inventory management system for kempo cms",
5
5
  "license": "MIT",
6
6
  "author": "Dustin Poissant <dustinpoissant@gmail.com>",
@@ -22,6 +22,7 @@ import { getFields, createField, updateField, deleteField, getCategories } from
22
22
  */
23
23
  const TYPE_LABELS = {
24
24
  text: 'Text',
25
+ url: 'Web address (link)',
25
26
  longtext: 'Long text',
26
27
  number: 'Number',
27
28
  boolean: 'Yes / No',
@@ -354,6 +354,9 @@ export const fieldTemplate = (field, value, { linked = {}, media = {}, ownId = n
354
354
  case 'longtext':
355
355
  control = html`<textarea id=${id} class="full" rows="3" .value=${value ?? ''}></textarea>`;
356
356
  break;
357
+ case 'url':
358
+ control = html`<input type="url" id=${id} class="full" placeholder="https://" .value=${value ?? ''}>`;
359
+ break;
357
360
  case 'number':
358
361
  control = html`<input type="number" step="any" id=${id} class="full" .value=${value ?? ''}>`;
359
362
  break;
@@ -112,7 +112,9 @@
112
112
  ? 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
113
  : f.type === 'item' && linked[item.fields[f.key]]
114
114
  ? html`<a href="/items/${item.fields[f.key]}">${displayValue(f, item.fields[f.key], context)}</a>`
115
- : f.type === 'longtext'
115
+ : f.type === 'url'
116
+ ? html`<a href=${item.fields[f.key]} rel="noopener">${item.fields[f.key]}</a>`
117
+ : f.type === 'longtext'
116
118
  ? html`<span style="white-space: pre-wrap;">${item.fields[f.key]}</span>`
117
119
  : displayValue(f, item.fields[f.key], context)}</dd>`)}
118
120
  </dl>` : ''}
@@ -6,7 +6,7 @@
6
6
  import { categoryKey } from './categoryLogic.js';
7
7
  import { parseRatio } from './ratio.js';
8
8
 
9
- export const FIELD_TYPES = ['text', 'longtext', 'number', 'boolean', 'date', 'color', 'rating', 'select', 'item', 'media'];
9
+ export const FIELD_TYPES = ['text', 'url', 'longtext', 'number', 'boolean', 'date', 'color', 'rating', 'select', 'item', 'media'];
10
10
 
11
11
  /*
12
12
  Types that depend on another extension being installed. They are always valid in a definition,
@@ -47,6 +47,7 @@ export const KEY_PATTERN = /^[a-z][a-zA-Z0-9]{0,39}$/;
47
47
 
48
48
  const MAX_TEXT = 1000;
49
49
  const MAX_LONGTEXT = 20000;
50
+ const MAX_URL = 2000;
50
51
 
51
52
  export const isEmpty = value => value === undefined || value === null || value === '';
52
53
 
@@ -136,6 +137,16 @@ export const coerceValue = (field, raw) => {
136
137
  if(!value) return [null, null];
137
138
  return value.length > MAX_LONGTEXT ? bad(`must be ${MAX_LONGTEXT} characters or fewer`) : [null, value];
138
139
  }
140
+ case 'url': {
141
+ /* Without a scheme https:// is added. Only http and https are kept, so a link can never run a script. */
142
+ const text = String(raw).trim();
143
+ if(!text) return [null, null];
144
+ const address = /^[a-z][a-z0-9+.-]*:/i.test(text) ? text : `https://${text}`;
145
+ if(address.length > MAX_URL) return bad(`must be ${MAX_URL} characters or fewer`);
146
+ let parsed;
147
+ try { parsed = new URL(address); } catch { return bad('must be a web address, such as https://example.com'); }
148
+ return ['http:', 'https:'].includes(parsed.protocol) ? [null, address] : bad('must be a web address, such as https://example.com');
149
+ }
139
150
  case 'number': {
140
151
  const value = typeof raw === 'number' ? raw : Number(String(raw).trim());
141
152
  return Number.isFinite(value) ? [null, value] : bad('must be a number');