@phosart/common 0.4.22

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.
Files changed (178) hide show
  1. package/README.md +58 -0
  2. package/dist/FullGallery.svelte +51 -0
  3. package/dist/FullGallery.svelte.d.ts +17 -0
  4. package/dist/FullGallery.svelte.d.ts.map +1 -0
  5. package/dist/Gallery.svelte +30 -0
  6. package/dist/Gallery.svelte.d.ts +14 -0
  7. package/dist/Gallery.svelte.d.ts.map +1 -0
  8. package/dist/GalleryPreview.svelte +60 -0
  9. package/dist/GalleryPreview.svelte.d.ts +9 -0
  10. package/dist/GalleryPreview.svelte.d.ts.map +1 -0
  11. package/dist/HighResContext.svelte +21 -0
  12. package/dist/HighResContext.svelte.d.ts +8 -0
  13. package/dist/HighResContext.svelte.d.ts.map +1 -0
  14. package/dist/Image.svelte +171 -0
  15. package/dist/Image.svelte.d.ts +14 -0
  16. package/dist/Image.svelte.d.ts.map +1 -0
  17. package/dist/Modal.svelte +87 -0
  18. package/dist/Modal.svelte.d.ts +9 -0
  19. package/dist/Modal.svelte.d.ts.map +1 -0
  20. package/dist/ModalGallery/Carousel.svelte +76 -0
  21. package/dist/ModalGallery/Carousel.svelte.d.ts +10 -0
  22. package/dist/ModalGallery/Carousel.svelte.d.ts.map +1 -0
  23. package/dist/ModalGallery/ImageSection.svelte +156 -0
  24. package/dist/ModalGallery/ImageSection.svelte.d.ts +11 -0
  25. package/dist/ModalGallery/ImageSection.svelte.d.ts.map +1 -0
  26. package/dist/ModalGallery/ImageView.svelte +92 -0
  27. package/dist/ModalGallery/ImageView.svelte.d.ts +9 -0
  28. package/dist/ModalGallery/ImageView.svelte.d.ts.map +1 -0
  29. package/dist/ModalGallery/Spinner.svelte +71 -0
  30. package/dist/ModalGallery/Spinner.svelte.d.ts +7 -0
  31. package/dist/ModalGallery/Spinner.svelte.d.ts.map +1 -0
  32. package/dist/ModalGallery.svelte +165 -0
  33. package/dist/ModalGallery.svelte.d.ts +16 -0
  34. package/dist/ModalGallery.svelte.d.ts.map +1 -0
  35. package/dist/OpengraphMeta.svelte +125 -0
  36. package/dist/OpengraphMeta.svelte.d.ts +15 -0
  37. package/dist/OpengraphMeta.svelte.d.ts.map +1 -0
  38. package/dist/Postcard/ArtistLink.svelte +46 -0
  39. package/dist/Postcard/ArtistLink.svelte.d.ts +9 -0
  40. package/dist/Postcard/ArtistLink.svelte.d.ts.map +1 -0
  41. package/dist/Postcard/Chip.svelte +100 -0
  42. package/dist/Postcard/Chip.svelte.d.ts +12 -0
  43. package/dist/Postcard/Chip.svelte.d.ts.map +1 -0
  44. package/dist/Postcard/Description/Character.svelte +79 -0
  45. package/dist/Postcard/Description/Character.svelte.d.ts +9 -0
  46. package/dist/Postcard/Description/Character.svelte.d.ts.map +1 -0
  47. package/dist/Postcard/Description.svelte +146 -0
  48. package/dist/Postcard/Description.svelte.d.ts +13 -0
  49. package/dist/Postcard/Description.svelte.d.ts.map +1 -0
  50. package/dist/Postcard/Headline.svelte +70 -0
  51. package/dist/Postcard/Headline.svelte.d.ts +10 -0
  52. package/dist/Postcard/Headline.svelte.d.ts.map +1 -0
  53. package/dist/index.d.ts +17 -0
  54. package/dist/index.d.ts.map +1 -0
  55. package/dist/index.js +17 -0
  56. package/dist/server/artist.d.ts +7 -0
  57. package/dist/server/artist.d.ts.map +1 -0
  58. package/dist/server/artist.js +40 -0
  59. package/dist/server/character.d.ts +9 -0
  60. package/dist/server/character.d.ts.map +1 -0
  61. package/dist/server/character.js +76 -0
  62. package/dist/server/directories.d.ts +4 -0
  63. package/dist/server/directories.d.ts.map +1 -0
  64. package/dist/server/directories.js +39 -0
  65. package/dist/server/fastcache.d.ts +11 -0
  66. package/dist/server/fastcache.d.ts.map +1 -0
  67. package/dist/server/fastcache.js +43 -0
  68. package/dist/server/filter.d.ts +6 -0
  69. package/dist/server/filter.d.ts.map +1 -0
  70. package/dist/server/filter.js +53 -0
  71. package/dist/server/gallery.d.ts +16 -0
  72. package/dist/server/gallery.d.ts.map +1 -0
  73. package/dist/server/gallery.js +162 -0
  74. package/dist/server/imageprocess.d.ts +18 -0
  75. package/dist/server/imageprocess.d.ts.map +1 -0
  76. package/dist/server/imageprocess.js +243 -0
  77. package/dist/server/index.d.ts +15 -0
  78. package/dist/server/index.d.ts.map +1 -0
  79. package/dist/server/index.js +15 -0
  80. package/dist/server/models/Artist.d.ts +7 -0
  81. package/dist/server/models/Artist.d.ts.map +1 -0
  82. package/dist/server/models/Artist.js +15 -0
  83. package/dist/server/models/Character.d.ts +108 -0
  84. package/dist/server/models/Character.d.ts.map +1 -0
  85. package/dist/server/models/Character.js +21 -0
  86. package/dist/server/models/Gallery.d.ts +373 -0
  87. package/dist/server/models/Gallery.d.ts.map +1 -0
  88. package/dist/server/models/Gallery.js +60 -0
  89. package/dist/server/models/image.d.ts +64 -0
  90. package/dist/server/models/image.d.ts.map +1 -0
  91. package/dist/server/models/image.js +17 -0
  92. package/dist/server/pack.d.ts +3 -0
  93. package/dist/server/pack.d.ts.map +1 -0
  94. package/dist/server/pack.js +26 -0
  95. package/dist/server/theme/schema.d.ts +57 -0
  96. package/dist/server/theme/schema.d.ts.map +1 -0
  97. package/dist/server/theme/schema.js +217 -0
  98. package/dist/server/util.d.ts +24 -0
  99. package/dist/server/util.d.ts.map +1 -0
  100. package/dist/server/util.js +71 -0
  101. package/dist/util/art.d.ts +52 -0
  102. package/dist/util/art.d.ts.map +1 -0
  103. package/dist/util/art.js +57 -0
  104. package/dist/util/artistcontext.svelte.d.ts +8 -0
  105. package/dist/util/artistcontext.svelte.d.ts.map +1 -0
  106. package/dist/util/artistcontext.svelte.js +18 -0
  107. package/dist/util/charactercontext.svelte.d.ts +4 -0
  108. package/dist/util/charactercontext.svelte.d.ts.map +1 -0
  109. package/dist/util/charactercontext.svelte.js +11 -0
  110. package/dist/util/date.d.ts +2 -0
  111. package/dist/util/date.d.ts.map +1 -0
  112. package/dist/util/date.js +6 -0
  113. package/dist/util/index.d.ts +13 -0
  114. package/dist/util/index.d.ts.map +1 -0
  115. package/dist/util/index.js +10 -0
  116. package/dist/util/markdown.d.ts +2 -0
  117. package/dist/util/markdown.d.ts.map +1 -0
  118. package/dist/util/markdown.js +16 -0
  119. package/dist/util/phosart_config.svelte.d.ts +44 -0
  120. package/dist/util/phosart_config.svelte.d.ts.map +1 -0
  121. package/dist/util/phosart_config.svelte.js +51 -0
  122. package/dist/util/search.d.ts +3 -0
  123. package/dist/util/search.d.ts.map +1 -0
  124. package/dist/util/search.js +24 -0
  125. package/dist/util/smoothscroll.d.ts +4 -0
  126. package/dist/util/smoothscroll.d.ts.map +1 -0
  127. package/dist/util/smoothscroll.js +21 -0
  128. package/dist/util/tree.d.ts +19 -0
  129. package/dist/util/tree.d.ts.map +1 -0
  130. package/dist/util/tree.js +58 -0
  131. package/dist/util/util.d.ts +4 -0
  132. package/dist/util/util.d.ts.map +1 -0
  133. package/dist/util/util.js +22 -0
  134. package/package.json +102 -0
  135. package/src/lib/FullGallery.svelte +51 -0
  136. package/src/lib/Gallery.svelte +30 -0
  137. package/src/lib/GalleryPreview.svelte +60 -0
  138. package/src/lib/HighResContext.svelte +21 -0
  139. package/src/lib/Image.svelte +171 -0
  140. package/src/lib/Modal.svelte +87 -0
  141. package/src/lib/ModalGallery/Carousel.svelte +76 -0
  142. package/src/lib/ModalGallery/ImageSection.svelte +156 -0
  143. package/src/lib/ModalGallery/ImageView.svelte +92 -0
  144. package/src/lib/ModalGallery/Spinner.svelte +71 -0
  145. package/src/lib/ModalGallery.svelte +165 -0
  146. package/src/lib/OpengraphMeta.svelte +125 -0
  147. package/src/lib/Postcard/ArtistLink.svelte +46 -0
  148. package/src/lib/Postcard/Chip.svelte +100 -0
  149. package/src/lib/Postcard/Description/Character.svelte +79 -0
  150. package/src/lib/Postcard/Description.svelte +146 -0
  151. package/src/lib/Postcard/Headline.svelte +70 -0
  152. package/src/lib/index.ts +20 -0
  153. package/src/lib/server/artist.ts +50 -0
  154. package/src/lib/server/character.ts +113 -0
  155. package/src/lib/server/directories.ts +45 -0
  156. package/src/lib/server/fastcache.ts +66 -0
  157. package/src/lib/server/filter.ts +71 -0
  158. package/src/lib/server/gallery.ts +259 -0
  159. package/src/lib/server/imageprocess.ts +382 -0
  160. package/src/lib/server/index.ts +57 -0
  161. package/src/lib/server/models/Artist.ts +19 -0
  162. package/src/lib/server/models/Character.ts +24 -0
  163. package/src/lib/server/models/Gallery.ts +70 -0
  164. package/src/lib/server/models/image.ts +20 -0
  165. package/src/lib/server/pack.ts +31 -0
  166. package/src/lib/server/theme/schema.ts +286 -0
  167. package/src/lib/server/util.ts +102 -0
  168. package/src/lib/util/art.ts +136 -0
  169. package/src/lib/util/artistcontext.svelte.ts +25 -0
  170. package/src/lib/util/charactercontext.svelte.ts +15 -0
  171. package/src/lib/util/date.ts +7 -0
  172. package/src/lib/util/index.ts +29 -0
  173. package/src/lib/util/markdown.ts +17 -0
  174. package/src/lib/util/phosart_config.svelte.ts +101 -0
  175. package/src/lib/util/search.ts +28 -0
  176. package/src/lib/util/smoothscroll.ts +21 -0
  177. package/src/lib/util/tree.ts +75 -0
  178. package/src/lib/util/util.ts +37 -0
@@ -0,0 +1,286 @@
1
+ import { writeFileSync } from 'node:fs';
2
+ import { readFile, writeFile } from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import z from 'zod';
5
+ import { $DATA } from '../directories.ts';
6
+ import { parse, stringify } from 'yaml';
7
+ import { Logger } from 'tslog';
8
+ import { getLogLevel } from '../util.ts';
9
+ const ThemeLogger = new Logger({ minLevel: getLogLevel() });
10
+
11
+ const $THEMESCHEMA = () => path.resolve($DATA(), '.theme-schema.json');
12
+ const $TYPE = () => path.resolve($DATA(), 'generated-schema.ts');
13
+ const $THEMECONFIG = () => path.resolve($DATA(), 'theme-config.yaml');
14
+ const BLANK = `{
15
+ "$schema": "https://raw.githubusercontent.com/PhosphoriteArt/phosart-common/refs/heads/main/settings.schema.json"
16
+ }`;
17
+
18
+ const ZColorOption = z.object({ type: z.literal('color') });
19
+ const ZSelectionOption = z.object({
20
+ type: z.literal('selection'),
21
+ options: z.array(z.string()),
22
+ multi: z.literal(false).optional()
23
+ });
24
+ const ZMultiSelectionOption = z.object({
25
+ type: z.literal('selection'),
26
+ options: z.array(z.string()),
27
+ multi: z.literal(true)
28
+ });
29
+ const ZStringOption = z.object({ type: z.literal('string') });
30
+ const ZTagsOption = z.object({ type: z.literal('tag-list') });
31
+ const ZStringList = z.object({ type: z.literal('string-list') });
32
+ export const ZThemeSettingsSchema = z.record(
33
+ z.string(),
34
+ z.union([
35
+ ZColorOption,
36
+ ZSelectionOption,
37
+ ZMultiSelectionOption,
38
+ ZTagsOption,
39
+ ZStringList,
40
+ ZStringOption
41
+ ])
42
+ );
43
+
44
+ export type ThemeSettingsSchema = z.infer<typeof ZThemeSettingsSchema>;
45
+
46
+ export const builtinSettings = {
47
+ defaultArtist: { type: 'string' }
48
+ } as const satisfies ThemeSettingsSchema;
49
+ export type BuiltinSettings = typeof builtinSettings;
50
+
51
+ type MaterializedOptionFor<T extends ThemeSettingsSchema[string]> =
52
+ T extends z.infer<typeof ZColorOption>
53
+ ? `#${string}`
54
+ : T extends z.infer<typeof ZSelectionOption>
55
+ ? [...T['options']]
56
+ : T extends z.infer<typeof ZMultiSelectionOption>
57
+ ? Array<[...T['options']]>
58
+ : T extends z.infer<typeof ZStringList>
59
+ ? Array<string>
60
+ : T extends z.infer<typeof ZTagsOption>
61
+ ? Array<string>
62
+ : T extends z.infer<typeof ZStringOption>
63
+ ? string
64
+ : never;
65
+
66
+ export type SettingsFor<T extends ThemeSettingsSchema> = {
67
+ [K in keyof T]: MaterializedOptionFor<T[K]>;
68
+ };
69
+
70
+ export async function readThemeSchema<T extends ThemeSettingsSchema>(): Promise<T> {
71
+ let text: string;
72
+ try {
73
+ text = await readFile($THEMESCHEMA(), { encoding: 'utf-8' });
74
+ } catch (err) {
75
+ ThemeLogger.warn('Error reading theme file:', err, 'recreating');
76
+ await writeFile($THEMESCHEMA(), BLANK, { encoding: 'utf-8' });
77
+ text = BLANK;
78
+ }
79
+ try {
80
+ const data = JSON.parse(text);
81
+ if (typeof data === 'object') {
82
+ delete data['$schema'];
83
+ }
84
+ const settings = await ZThemeSettingsSchema.parseAsync(data);
85
+ await writeGeneratedSchema(settings);
86
+ return settings as T;
87
+ } catch (err) {
88
+ ThemeLogger.warn('Error parsing theme file:', err);
89
+ throw err;
90
+ }
91
+ }
92
+
93
+ function escape(token: string): string {
94
+ return token.replaceAll('\\', '\\\\').replaceAll('"', '\\"');
95
+ }
96
+ async function writeGeneratedSchema<T extends ThemeSettingsSchema>(schema: T) {
97
+ let ts = `export interface ThemeSchema {`;
98
+
99
+ for (const [k, v] of Object.entries(schema)) {
100
+ switch (v.type) {
101
+ case 'color':
102
+ ts += `\n "${escape(k)}": \`#\${string}\`;`;
103
+ break;
104
+ case 'selection': {
105
+ const opts = v.options
106
+ .map(escape)
107
+ .map((k) => `"${k}"`)
108
+ .join(' | ');
109
+ const typ = v.multi ? `Array<${opts}>` : opts;
110
+ ts += `\n "${escape(k)}": ` + typ + ';';
111
+ break;
112
+ }
113
+ case 'string':
114
+ ts += `\n "${escape(k)}": string;`;
115
+ break;
116
+ case 'string-list':
117
+ case 'tag-list':
118
+ ts += `\n "${escape(k)}": string[]`;
119
+ break;
120
+ }
121
+ }
122
+
123
+ if (Object.keys(schema).length === 0) {
124
+ ts = 'export type ThemeSchema = Record<string, never>;\n';
125
+ } else {
126
+ ts += '\n}\n';
127
+ }
128
+
129
+ await writeFile($TYPE(), ts, { encoding: 'utf-8' });
130
+ }
131
+
132
+ export function validateSchema<T extends ThemeSettingsSchema>(
133
+ schema: T,
134
+ doc: unknown
135
+ ): doc is SettingsFor<T & BuiltinSettings> {
136
+ return doValidateSchema(Object.assign({}, builtinSettings, schema), doc);
137
+ }
138
+
139
+ function doValidateSchema<T extends ThemeSettingsSchema>(
140
+ schema: T,
141
+ doc: unknown
142
+ ): doc is SettingsFor<T> {
143
+ if (typeof doc !== 'object' || !doc) {
144
+ throw new Error(
145
+ "document '" +
146
+ JSON.stringify(doc) +
147
+ "' is not a truthy object and thus cannot conform to schema " +
148
+ JSON.stringify(schema)
149
+ );
150
+ }
151
+ const cast = doc as Record<string, unknown>;
152
+
153
+ for (const [k, v] of Object.entries(schema)) {
154
+ if (k in cast) {
155
+ const docval: unknown = cast[k];
156
+ switch (v.type) {
157
+ case 'color':
158
+ if (typeof docval !== 'string' || !docval.startsWith('#')) {
159
+ throw new Error(
160
+ "document '" +
161
+ JSON.stringify(doc) +
162
+ "' @ key '" +
163
+ k +
164
+ "' does not have type color (instead has value '" +
165
+ v +
166
+ "') and thus does not conform to schema " +
167
+ JSON.stringify(schema)
168
+ );
169
+ }
170
+ break;
171
+ case 'selection':
172
+ if (v.multi) {
173
+ if (
174
+ !Array.isArray(docval) ||
175
+ docval.some((item) => typeof item !== 'string' || !v.options.includes(item))
176
+ ) {
177
+ throw new Error(
178
+ "document '" +
179
+ JSON.stringify(doc) +
180
+ "' @ key '" +
181
+ k +
182
+ "' does not have a valid selection list at that key (instead has value '" +
183
+ v +
184
+ "') and thus does not conform to schema " +
185
+ JSON.stringify(schema)
186
+ );
187
+ }
188
+ break;
189
+ }
190
+ if (typeof docval !== 'string' || !v.options.includes(docval)) {
191
+ throw new Error(
192
+ "document '" +
193
+ JSON.stringify(doc) +
194
+ "' @ key '" +
195
+ k +
196
+ "' does not have a valid selection at that key (instead has value '" +
197
+ v +
198
+ "') and thus does not conform to schema " +
199
+ JSON.stringify(schema)
200
+ );
201
+ }
202
+ break;
203
+ case 'string':
204
+ if (typeof docval !== 'string') {
205
+ throw new Error(
206
+ "document '" +
207
+ JSON.stringify(doc) +
208
+ "' @ key '" +
209
+ k +
210
+ "' does not have a valid string at that key (instead has value '" +
211
+ v +
212
+ "') and thus does not conform to schema " +
213
+ JSON.stringify(schema)
214
+ );
215
+ }
216
+ break;
217
+ case 'string-list':
218
+ case 'tag-list':
219
+ if (!Array.isArray(docval) || docval.some((item) => typeof item !== 'string')) {
220
+ throw new Error(
221
+ "document '" +
222
+ JSON.stringify(doc) +
223
+ "' @ key '" +
224
+ k +
225
+ "' does not have a valid string list at that key (instead has value '" +
226
+ v +
227
+ "') and thus does not conform to schema " +
228
+ JSON.stringify(schema)
229
+ );
230
+ }
231
+ }
232
+ }
233
+ }
234
+ return true;
235
+ }
236
+
237
+ export async function readThemeConfig<T extends ThemeSettingsSchema>(
238
+ schema: T
239
+ ): Promise<SettingsFor<T & BuiltinSettings>> {
240
+ let text: string;
241
+ try {
242
+ text = await readFile($THEMECONFIG(), { encoding: 'utf-8' });
243
+ } catch (err) {
244
+ ThemeLogger.warn('Error reading theme file:', err, 'recreating');
245
+ await writeFile($THEMECONFIG(), '', { encoding: 'utf-8' });
246
+ text = '';
247
+ }
248
+ try {
249
+ const doc = parse(text) ?? {};
250
+ if (validateSchema(schema, doc)) {
251
+ return doc;
252
+ }
253
+
254
+ throw new Error('unreachable');
255
+ } catch (err) {
256
+ ThemeLogger.warn('Error parsing theme file:', err);
257
+ throw err;
258
+ }
259
+ }
260
+
261
+ export async function writeThemeConfig<T extends ThemeSettingsSchema>(
262
+ schema: T,
263
+ config: SettingsFor<T & BuiltinSettings>
264
+ ): Promise<void> {
265
+ if (validateSchema(schema, config)) {
266
+ await writeFile($THEMECONFIG(), stringify(config), { encoding: 'utf-8' });
267
+ }
268
+ }
269
+
270
+ function writeJsonSchema() {
271
+ const p = path.resolve(path.join(import.meta.dirname, '../../../settings.schema.json'));
272
+ writeFileSync(
273
+ p,
274
+ JSON.stringify(
275
+ z.intersection(z.object({ $schema: z.string() }), ZThemeSettingsSchema).toJSONSchema(),
276
+ null,
277
+ 2
278
+ ),
279
+ { encoding: 'utf-8' }
280
+ );
281
+ console.log('Wrote jsonschema to ' + p);
282
+ }
283
+
284
+ if (process.env.WRITE_SCHEMA) {
285
+ writeJsonSchema();
286
+ }
@@ -0,0 +1,102 @@
1
+ import * as fs from 'node:fs/promises';
2
+ import * as crypto from 'node:crypto';
3
+ import path from 'node:path';
4
+ import { $DATA } from './directories.ts';
5
+ import type { GalleryCache, RawGalleryCache } from './gallery.ts';
6
+ import type { CharacterCache, RawCharacterCache } from './character.ts';
7
+ import type { ArtistCache } from './artist.ts';
8
+ import { $ART } from './index.ts';
9
+
10
+ export async function hashUrl(url: string) {
11
+ const buf = await fs.readFile(url);
12
+ return crypto.createHash('md5').update(buf).digest('hex').substring(24);
13
+ }
14
+
15
+ export function hash(object: object) {
16
+ return crypto.createHash('md5').update(JSON.stringify(object)).digest('hex').substring(24);
17
+ }
18
+
19
+ export function relPath(startFile: string, nextFile: string) {
20
+ const out = path.join(path.dirname(startFile), nextFile);
21
+ return out;
22
+ }
23
+ export function fullPath(startFile: string, nextFile: string) {
24
+ const out = path.join($DATA(), path.dirname(startFile), nextFile);
25
+ return out;
26
+ }
27
+
28
+ type FileStructure = {
29
+ [name: string]: FileStructure | string;
30
+ };
31
+
32
+ async function getStructureHash(scanPath?: string): Promise<FileStructure> {
33
+ const list = await fs.readdir(scanPath ?? $ART(), { withFileTypes: true });
34
+ const structure: FileStructure = {};
35
+
36
+ for (const element of list) {
37
+ const next = path.join(scanPath ?? $ART(), element.name);
38
+ if (element.isDirectory()) {
39
+ structure[element.name] = await getStructureHash(next);
40
+ } else if (element.isFile() && /\.(gallery|character|yaml|yml)$/gi.test(element.name)) {
41
+ structure[element.name] = await hashUrl(next);
42
+ }
43
+ }
44
+
45
+ return structure;
46
+ }
47
+
48
+ interface Cache<T> {
49
+ cache: T | null;
50
+ version: string | null;
51
+ }
52
+
53
+ export interface GlobalCache {
54
+ galleryCache: Cache<GalleryCache>;
55
+ rawGalleryCache: Cache<RawGalleryCache>;
56
+ characterCache: Cache<CharacterCache>;
57
+ rawCharacterCache: Cache<RawCharacterCache>;
58
+ artistCache: Cache<ArtistCache>;
59
+ }
60
+
61
+ export async function cacheVersion(): Promise<string> {
62
+ return hash(await getStructureHash());
63
+ }
64
+
65
+ export function getCache(): GlobalCache {
66
+ if (typeof global == 'undefined') {
67
+ throw new Error('global is undefined');
68
+ }
69
+
70
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
71
+ let cache: GlobalCache | undefined = (global as any).__art_global_cache;
72
+ if (!cache) {
73
+ cache = {
74
+ galleryCache: { cache: null, version: null },
75
+ characterCache: { cache: null, version: null },
76
+ rawCharacterCache: { cache: null, version: null },
77
+ artistCache: { cache: null, version: null },
78
+ rawGalleryCache: { cache: null, version: null }
79
+ };
80
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
81
+ (global as any).__art_global_cache = cache;
82
+ }
83
+
84
+ return cache;
85
+ }
86
+
87
+ export function clearCache() {
88
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
89
+ (global as any).__art_global_cache = null;
90
+ }
91
+
92
+ export function getLogLevel(defaultLevel: number = 3): number {
93
+ try {
94
+ if (!process.env.LOG_LEVEL) {
95
+ return defaultLevel;
96
+ }
97
+
98
+ return parseInt(process.env.LOG_LEVEL);
99
+ } catch {
100
+ return defaultLevel;
101
+ }
102
+ }
@@ -0,0 +1,136 @@
1
+ import type { z } from 'zod';
2
+
3
+ import type { FullCharacter } from '../server/models/Character.ts';
4
+ import type { Artist as ZArtist } from '../server/models/Artist.ts';
5
+ import type {
6
+ Picture as ZPicture,
7
+ Image as ZImage,
8
+ Source as ZSource
9
+ } from '../server/models/image.ts';
10
+ import type {
11
+ FullGallery,
12
+ FullArtPiece,
13
+ BaseArtPiece as ZBaseArtPiece,
14
+ RawGallery as ZRawGallery,
15
+ BaseArtist as ZBaseArtist,
16
+ BaseGallery as ZBaseGallery,
17
+ ExtendedGallery as ZExtendedGallery
18
+ } from '../server/models/Gallery.ts';
19
+ import type { ArtistCache } from '../server/artist.ts';
20
+ import type { CharacterCache } from '../server/character.ts';
21
+
22
+ export type RawGallery = z.infer<typeof ZRawGallery>;
23
+ export type BaseGallery = z.infer<typeof ZBaseGallery>;
24
+ export type ExtendedGallery = z.infer<typeof ZExtendedGallery>;
25
+ export type Gallery = z.infer<typeof FullGallery>;
26
+ export type Character = z.infer<typeof FullCharacter>;
27
+ export type BaseArtist = z.infer<typeof ZBaseArtist>;
28
+ export type Artist = z.infer<typeof ZArtist>;
29
+ export type ArtPiece = z.infer<typeof FullArtPiece>;
30
+ export type BaseArtPiece = z.infer<typeof ZBaseArtPiece>;
31
+ export type Picture = z.infer<typeof ZPicture>;
32
+ export type Image = z.infer<typeof ZImage>;
33
+ export type Source = z.infer<typeof ZSource>;
34
+ export type CharacterRef = ArtPiece['characters'][number];
35
+
36
+ export interface NormalizedCharacter {
37
+ name: string;
38
+ from: string | null;
39
+ info: Character | null;
40
+ }
41
+ export function normalizeCharacter(
42
+ ref: CharacterRef,
43
+ characters?: CharacterCache
44
+ ): NormalizedCharacter;
45
+ export function normalizeCharacter(
46
+ ref: Array<CharacterRef>,
47
+ characters?: CharacterCache
48
+ ): Array<NormalizedCharacter>;
49
+ export function normalizeCharacter(
50
+ ref: CharacterRef | Array<CharacterRef>,
51
+ characters?: CharacterCache
52
+ ): NormalizedCharacter | Array<NormalizedCharacter> {
53
+ if (Array.isArray(ref)) {
54
+ return ref.map((ch) => normalizeCharacter(ch, characters));
55
+ }
56
+ if (typeof ref === 'string') {
57
+ return { name: ref, from: null, info: characters?.[ref] ?? null };
58
+ }
59
+ return { ...ref, info: null };
60
+ }
61
+
62
+ export interface NormalizedArtist {
63
+ name: string;
64
+ anonymous: boolean;
65
+ info: Artist | null;
66
+ }
67
+ function normalizeSingleArtist(
68
+ a: string | { name: string; anonymous: boolean },
69
+ artists?: ArtistCache
70
+ ): NormalizedArtist {
71
+ const name = typeof a === 'string' ? a : a.name;
72
+ const foundArtist = artists?.[name] ?? null;
73
+
74
+ return {
75
+ name,
76
+ anonymous: typeof a === 'string' ? false : a.anonymous,
77
+ info: foundArtist
78
+ };
79
+ }
80
+
81
+ export function normalizeArtist(
82
+ as: ArtPiece['artist'],
83
+ artists?: ArtistCache
84
+ ): Array<NormalizedArtist> {
85
+ if (!as) {
86
+ return [];
87
+ }
88
+
89
+ if (!Array.isArray(as)) {
90
+ return [normalizeSingleArtist(as, artists)];
91
+ }
92
+ return as.map((a) => normalizeSingleArtist(a, artists));
93
+ }
94
+
95
+ export function no4K(image: Image): Image {
96
+ const sources = Object.entries(image.sources).reduce(
97
+ (prev, [k, sources]) => ({ ...prev, [k]: sources.filter((source) => source.w < 3840) }),
98
+ {} as Image['sources']
99
+ );
100
+ const [, best] = bestSource(sources);
101
+ return {
102
+ ...image,
103
+ fallback: best,
104
+ sources: sources
105
+ };
106
+ }
107
+
108
+ export function onlyHighRes(image: Image): Image {
109
+ const [key, best] = bestSource(image.sources);
110
+ return {
111
+ ...image,
112
+ fallback: best,
113
+ sources: {
114
+ [key]: [best]
115
+ }
116
+ };
117
+ }
118
+
119
+ function bestSource(sources: Image['sources']): [string, z.infer<typeof ZSource>] {
120
+ const [key, bestSourceType] = sources.webp ? ['webp', sources.webp] : Object.entries(sources)[0];
121
+ const bestImage = bestSourceType.reduce((p, v) => (v.w > p.max ? { max: v.w, source: v } : p), {
122
+ max: 0,
123
+ source: null as Image['sources'][string][number] | null
124
+ });
125
+ if (bestImage.source === null) {
126
+ throw new Error('Could not find any sources?');
127
+ }
128
+ return [key, bestImage.source];
129
+ }
130
+
131
+ export type ResourceRef =
132
+ | { type: 'character'; resource: NormalizedCharacter }
133
+ | { type: 'artist'; resource: NormalizedArtist }
134
+ | { type: 'tag'; resource: string }
135
+ | { type: 'piece'; resource: ArtPiece }
136
+ | { type: undefined; resource: undefined };
@@ -0,0 +1,25 @@
1
+ import { getContext, setContext } from 'svelte';
2
+ import { normalizeArtist, type ArtPiece, type NormalizedArtist } from './art.ts';
3
+ import type { ArtistCache } from '../server/index.ts';
4
+
5
+ const key = Symbol();
6
+
7
+ export function useArtistsContext(artists: ArtistCache) {
8
+ setContext(key, artists);
9
+ $effect(() => {
10
+ setContext(key, artists);
11
+ });
12
+ }
13
+
14
+ export function useArtists(): ArtistCache {
15
+ return getContext(key);
16
+ }
17
+
18
+ export function useArtist(piece: null): null;
19
+ export function useArtist(piece: ArtPiece): Array<NormalizedArtist>;
20
+ export function useArtist(name: string): Array<NormalizedArtist>;
21
+ export function useArtist(piece: string | ArtPiece | null): Array<NormalizedArtist> | null {
22
+ const allArtists = useArtists();
23
+ if (!piece) return null;
24
+ return normalizeArtist(typeof piece === 'string' ? piece : piece.artist, allArtists);
25
+ }
@@ -0,0 +1,15 @@
1
+ import { getContext, setContext } from 'svelte';
2
+ import type { CharacterCache } from '../server/character.ts';
3
+
4
+ const key = Symbol();
5
+
6
+ export function useCharacterContext(characters: CharacterCache) {
7
+ setContext(key, characters);
8
+ $effect(() => {
9
+ setContext(key, characters);
10
+ });
11
+ }
12
+
13
+ export function useCharacters(): CharacterCache {
14
+ return getContext(key);
15
+ }
@@ -0,0 +1,7 @@
1
+ export function formatDate(d: Date | string) {
2
+ if (typeof d === 'string') {
3
+ d = new Date(d);
4
+ }
5
+
6
+ return d.toLocaleDateString();
7
+ }
@@ -0,0 +1,29 @@
1
+ export { no4K, normalizeArtist, normalizeCharacter, onlyHighRes } from './art.ts';
2
+ export type {
3
+ ArtPiece,
4
+ Artist,
5
+ Character,
6
+ CharacterRef,
7
+ Gallery,
8
+ RawGallery,
9
+ Image,
10
+ NormalizedArtist,
11
+ Picture,
12
+ Source,
13
+ BaseArtist,
14
+ BaseArtPiece,
15
+ BaseGallery,
16
+ ExtendedGallery,
17
+ NormalizedCharacter,
18
+ ResourceRef
19
+ } from './art.ts';
20
+ export { executeSearch } from './search.ts';
21
+ export { useArtist, useArtists, useArtistsContext } from './artistcontext.svelte.ts';
22
+ export { useCharacterContext, useCharacters } from './charactercontext.svelte.ts';
23
+ export { formatDate } from './date.ts';
24
+ export { markdown } from './markdown.ts';
25
+ export { smoothScroll } from './smoothscroll.ts';
26
+ export { setLibraryConfig, type LibraryConfig } from './phosart_config.svelte.ts';
27
+ export { asRecord, multiRecordBy, deduplicateBy } from './util.ts';
28
+ export { asTree, pathView } from './tree.ts';
29
+ export type { FolderElement, GalleryElement, GalleryTree, TreeElement } from './tree.ts';
@@ -0,0 +1,17 @@
1
+ import { unified } from 'unified';
2
+ import remarkParse from 'remark-parse';
3
+ import remarkGfm from 'remark-gfm';
4
+ import remarkRehype from 'remark-rehype';
5
+ import rehypeSanitize from 'rehype-sanitize';
6
+ import rehypeStringify from 'rehype-stringify';
7
+
8
+ export function markdown(text: string): string {
9
+ return unified()
10
+ .use(remarkParse)
11
+ .use(remarkGfm)
12
+ .use(remarkRehype)
13
+ .use(rehypeSanitize)
14
+ .use(rehypeStringify)
15
+ .processSync(text)
16
+ .toString();
17
+ }