@axium/storage 0.20.4 → 0.21.0

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.
@@ -1,6 +1,6 @@
1
1
  import { configDir, session } from '@axium/client/cli/config';
2
2
  import { formatBytes } from '@axium/core/format';
3
- import * as io from '@axium/core/node/io';
3
+ import * as io from 'ioium/node';
4
4
  import { Option, program } from 'commander';
5
5
  import { statSync, unlinkSync } from 'node:fs';
6
6
  import { stat } from 'node:fs/promises';
@@ -1,5 +1,5 @@
1
1
  import { configDir } from '@axium/client/cli/config';
2
- import { debug, readJSON, writeJSON } from '@axium/core/node/io';
2
+ import { debug, readJSON, writeJSON } from 'ioium/node';
3
3
  import { join } from 'node:path/posix';
4
4
  import * as z from 'zod';
5
5
  import { Sync } from './sync.js';
@@ -1,7 +1,7 @@
1
1
  import { cacheDir, session } from '@axium/client/cli/config';
2
2
  import { userInfo } from '@axium/client/user';
3
3
  import { UserPublic } from '@axium/core';
4
- import * as io from '@axium/core/node/io';
4
+ import * as io from 'ioium/node';
5
5
  import { ENOENT, ENOTDIR } from 'node:constants';
6
6
  import { stat } from 'node:fs/promises';
7
7
  import { join } from 'node:path';
@@ -1,6 +1,6 @@
1
1
  import { configDir, saveConfig } from '@axium/client/cli/config';
2
2
  import { fetchAPI } from '@axium/client/requests';
3
- import * as io from '@axium/core/node/io';
3
+ import * as io from 'ioium/node';
4
4
  import mime from 'mime';
5
5
  import { createHash } from 'node:crypto';
6
6
  import * as fs from 'node:fs';
package/dist/polyfills.js CHANGED
@@ -7,7 +7,7 @@ https://github.com/microsoft/TypeScript/issues/61695
7
7
 
8
8
  @todo Remove when TypeScript 5.9 is released
9
9
  */
10
- import { debug } from '@axium/core/io';
10
+ import { debug } from 'ioium';
11
11
  Uint8Array.prototype.toHex ??=
12
12
  (debug('Using a polyfill of Uint8Array.prototype.toHex'),
13
13
  function toHex() {
@@ -1,4 +1,4 @@
1
- import { getConfig } from '@axium/core';
1
+ import { checkACL, getConfig } from '@axium/core';
2
2
  import * as acl from '@axium/server/acl';
3
3
  import { audit } from '@axium/server/audit';
4
4
  import { requireSession } from '@axium/server/auth';
@@ -74,7 +74,7 @@ addRoute({
74
74
  continue;
75
75
  if (!item.acl || !item.acl.length)
76
76
  error(403, 'Missing permission for item: ' + item.id);
77
- acl.check(item.acl, changedIds.has(item.id) ? { write: true } : { manage: true });
77
+ checkACL(item.acl, changedIds.has(item.id) ? { write: true } : { manage: true });
78
78
  error(403, 'Missing permission for item: ' + item.id);
79
79
  }
80
80
  if (limits.user_size &&
@@ -1,8 +1,8 @@
1
1
  import { formatBytes, parseByteSize } from '@axium/core';
2
- import { io } from '@axium/core/node';
3
2
  import { lookupUser } from '@axium/server/cli';
4
3
  import { count, database } from '@axium/server/database';
5
4
  import { Option, program } from 'commander';
5
+ import * as io from 'ioium/node';
6
6
  import { styleText } from 'node:util';
7
7
  import * as z from 'zod';
8
8
  import { parseItem } from './db.js';
@@ -1,6 +1,6 @@
1
1
  import { getConfig } from '@axium/core';
2
2
  import { formatBytes } from '@axium/core/format';
3
- import { done, start } from '@axium/core/node/io';
3
+ import { done, start } from 'ioium/node';
4
4
  import { count, database } from '@axium/server/database';
5
5
  import { mkdirSync } from 'node:fs';
6
6
  import '../common.js';
package/lib/List.svelte CHANGED
@@ -7,7 +7,7 @@
7
7
  import type { AccessControllable, UserPublic } from '@axium/core';
8
8
  import { formatBytes } from '@axium/core/format';
9
9
  import { forMime as iconForMime } from '@axium/core/icons';
10
- import { errorText } from '@axium/core/io';
10
+ import { errorText } from 'ioium';
11
11
  import { getDirectoryMetadata, updateItemMetadata } from '@axium/storage/client';
12
12
  import { copyShortURL, formatItemName } from '@axium/storage/client/frontend';
13
13
  import { StorageItemSorting, type StorageItemMetadata } from '@axium/storage/common';
@@ -123,18 +123,7 @@
123
123
  {@render action('download', 'download', i)}
124
124
  {@render action('trash', 'trash', i)}
125
125
  </div>
126
- <AccessControlDialog
127
- bind:dialog={dialogs['share:' + item.id]}
128
- {item}
129
- itemType="storage"
130
- editable={(item.acl?.find(
131
- a =>
132
- a.userId == user?.id ||
133
- (a.role && user?.roles.includes(a.role)) ||
134
- (a.tag && user?.tags?.includes(a.tag)) ||
135
- (!a.userId && !a.role && !a.tag)
136
- )?.manage as boolean | undefined) ?? true}
137
- />
126
+ <AccessControlDialog bind:dialog={dialogs['share:' + item.id]} {item} itemType="storage" {user} />
138
127
  </div>
139
128
  {:else}
140
129
  <p class="list-empty">{emptyText}</p>
package/lib/Preview.css CHANGED
@@ -54,6 +54,10 @@
54
54
  justify-content: center;
55
55
  flex-direction: column;
56
56
 
57
+ &.no-top-bar {
58
+ top: 1em;
59
+ }
60
+
57
61
  .full-fill {
58
62
  position: absolute;
59
63
  inset: 0;
@@ -14,11 +14,14 @@
14
14
  shareDialog,
15
15
  previewDialog,
16
16
  onDelete = () => {},
17
+ noTopBar,
17
18
  }: {
18
19
  item: StorageItemMetadata & AccessControllable;
19
20
  shareDialog?: HTMLDialogElement;
20
21
  previewDialog?: HTMLDialogElement;
21
22
  onDelete?(): unknown;
23
+ /** Set when the preview is displayed for an item not inside a directory (e.g. when sharing links to files) */
24
+ noTopBar?: boolean;
22
25
  } = $props();
23
26
 
24
27
  const itemOpeners = openers.filter(opener => opener.types.includes(item.type));
@@ -32,44 +35,47 @@
32
35
  </span>
33
36
  {/snippet}
34
37
 
35
- <div class="preview-top-bar">
36
- <div class="title">{item.name}</div>
37
- {#if itemOpeners.length}
38
- {@const [first, ...others] = itemOpeners}
39
- <div class="openers">
40
- <span>{text('storage.Preview.open_with')} <a href={first.openURL(item)} target="_blank">{first.name}</a></span>
41
- {#if others.length}
42
- <Popover>
43
- {#snippet toggle()}
44
- <Icon i="caret-down" />
45
- {/snippet}
46
- {#each others as opener}
47
- <a href={opener.openURL(item)} target="_blank">{opener.name}</a>
48
- {/each}
49
- </Popover>
50
- {/if}
51
- </div>
52
- {/if}
53
- <div class="actions">
54
- {@render action('rename', 'pencil')}
55
- {#if shareDialog}
56
- <span class="icon-text preview-action" onclick={() => shareDialog.showModal()}>
57
- <Icon i="user-group" />
58
- </span>
38
+ {#if !noTopBar}
39
+ <div class="preview-top-bar">
40
+ <div class="title">{item.name}</div>
41
+
42
+ {#if itemOpeners.length}
43
+ {@const [first, ...others] = itemOpeners}
44
+ <div class="openers">
45
+ <span>{text('storage.Preview.open_with')} <a href={first.openURL(item)} target="_blank">{first.name}</a></span>
46
+ {#if others.length}
47
+ <Popover>
48
+ {#snippet toggle()}
49
+ <Icon i="caret-down" />
50
+ {/snippet}
51
+ {#each others as opener}
52
+ <a href={opener.openURL(item)} target="_blank">{opener.name}</a>
53
+ {/each}
54
+ </Popover>
55
+ {/if}
56
+ </div>
59
57
  {/if}
60
- {@render action('download', 'download')}
61
- <span class="icon-text preview-action" onclick={() => copyShortURL(item)}>
62
- <Icon i="link-horizontal" />
63
- </span>
64
- {@render action('trash', 'trash')}
65
- {#if previewDialog}
66
- <span class="icon-text preview-action mobile-hide" onclick={() => previewDialog.close()}>
67
- <Icon i="xmark" --size="20px" />
58
+ <div class="actions">
59
+ {@render action('rename', 'pencil')}
60
+ {#if shareDialog}
61
+ <span class="icon-text preview-action" onclick={() => shareDialog.showModal()}>
62
+ <Icon i="user-group" />
63
+ </span>
64
+ {/if}
65
+ {@render action('download', 'download')}
66
+ <span class="icon-text preview-action" onclick={() => copyShortURL(item)}>
67
+ <Icon i="link-horizontal" />
68
68
  </span>
69
- {/if}
69
+ {@render action('trash', 'trash')}
70
+ {#if previewDialog}
71
+ <span class="icon-text preview-action mobile-hide" onclick={() => previewDialog.close()}>
72
+ <Icon i="xmark" --size="20px" />
73
+ </span>
74
+ {/if}
75
+ </div>
70
76
  </div>
71
- </div>
72
- <div class="preview-content">
77
+ {/if}
78
+ <div class={['preview-content', noTopBar && 'no-top-bar']}>
73
79
  {#if item.type.startsWith('image/')}
74
80
  <img src={item.dataURL} alt={item.name} />
75
81
  {:else if item.type.startsWith('audio/')}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axium/storage",
3
- "version": "0.20.4",
3
+ "version": "0.21.0",
4
4
  "author": "James Prevett <axium@jamespre.dev>",
5
5
  "description": "User file storage for Axium",
6
6
  "funding": {
@@ -40,8 +40,8 @@
40
40
  "build": "tsc"
41
41
  },
42
42
  "peerDependencies": {
43
- "@axium/client": ">=0.19.8",
44
- "@axium/core": ">=0.22.0",
43
+ "@axium/client": ">=0.20.0",
44
+ "@axium/core": ">=0.24.0",
45
45
  "@axium/server": ">=0.39.0",
46
46
  "@sveltejs/kit": "^2.27.3",
47
47
  "utilium": "^2.6.3"
@@ -33,46 +33,35 @@
33
33
  {text('page.files.restore')}
34
34
  </button>
35
35
  {:else}
36
- <AccessControlDialog
37
- bind:dialog={shareDialog}
38
- {item}
39
- itemType="storage"
40
- editable={(item.acl?.find(
41
- a =>
42
- a.userId == user?.id ||
43
- (a.role && user?.roles.includes(a.role)) ||
44
- (a.tag && user?.tags?.includes(a.tag)) ||
45
- (!a.userId && !a.role && !a.tag)
46
- )?.manage as boolean | undefined) ?? true}
47
- />
36
+ <AccessControlDialog bind:dialog={shareDialog} {item} itemType="storage" {user} />
48
37
  {#if item.parents}
49
38
  <p class="parents" data-sveltekit-reload>
39
+ <a href="/files">~</a>
50
40
  {#each item.parents as { id, name } (id)}<a href="/files/{id}">{name}</a>{/each}
51
41
  </p>
52
42
  {/if}
53
- {#if item.type == 'inode/directory'}
54
- {#snippet action(i: string, text: string, handler: (e: Event) => unknown)}
55
- <button
56
- class="icon-text"
57
- onclick={e => {
58
- e.preventDefault();
59
- handler(e);
60
- }}
61
- >
62
- <Icon {i} />
63
- <span class="mobile-hide">{text}</span>
64
- </button>
65
- {/snippet}
66
-
67
- <div class="folder-actions">
68
- {@render action('folder-arrow-up', text('page.files.back'), () => (location.href = parentHref))}
69
- {@render action('pencil', text('page.files.rename'), () => dialogs.rename.showModal())}
70
- {@render action('user-group', text('page.files.share'), () => shareDialog.showModal())}
71
- {@render action('download', text('page.files.download'), () => dialogs.download.showModal())}
72
- {@render action('link-horizontal', text('page.files.copy_link'), () => copyShortURL(item))}
73
- {@render action('trash', text('page.files.trash'), () => dialogs.trash.showModal())}
74
- </div>
43
+ {#snippet action(i: string, text: string, handler: (e: Event) => unknown)}
44
+ <button
45
+ class="icon-text"
46
+ onclick={e => {
47
+ e.preventDefault();
48
+ handler(e);
49
+ }}
50
+ >
51
+ <Icon {i} />
52
+ <span class="mobile-hide">{text}</span>
53
+ </button>
54
+ {/snippet}
75
55
 
56
+ <div class="folder-actions">
57
+ {@render action('folder-arrow-up', text('page.files.back'), () => (location.href = parentHref))}
58
+ {@render action('pencil', text('page.files.rename'), () => dialogs.rename.showModal())}
59
+ {@render action('user-group', text('page.files.share'), () => shareDialog.showModal())}
60
+ {@render action('download', text('page.files.download'), () => dialogs.download.showModal())}
61
+ {@render action('link-horizontal', text('page.files.copy_link'), () => copyShortURL(item))}
62
+ {@render action('trash', text('page.files.trash'), () => dialogs.trash.showModal())}
63
+ </div>
64
+ {#if item.type == 'inode/directory'}
76
65
  <List appMode bind:items user={data.session?.user} />
77
66
  <Add parentId={item.id} onAdd={item => items.push(item)} />
78
67
 
@@ -113,7 +102,7 @@
113
102
  </FormDialog>
114
103
  {:else}
115
104
  <div class="preview-container">
116
- <Preview {item} {shareDialog} onDelete={() => (location.href = parentHref)} />
105
+ <Preview {item} {shareDialog} onDelete={() => (location.href = parentHref)} noTopBar />
117
106
  </div>
118
107
  {/if}
119
108
  {/if}
@@ -122,7 +111,11 @@
122
111
  .preview-container {
123
112
  position: relative;
124
113
  width: 100%;
125
- height: 100%;
114
+ height: calc(100% - 5em);
115
+
116
+ @media (width < 700px) {
117
+ height: calc(100% - 6em);
118
+ }
126
119
  }
127
120
 
128
121
  .folder-actions {
@@ -134,7 +127,7 @@
134
127
  .parents {
135
128
  margin-top: 0;
136
129
 
137
- a::before {
130
+ a:not(:first-child)::before {
138
131
  content: ' / ';
139
132
  color: #888;
140
133
  }