@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,51 @@
1
+ import { createContext } from 'svelte';
2
+ const [get, set] = createContext();
3
+ const DEFAULT_CONFIG = {
4
+ siteName: '',
5
+ gallery: {
6
+ DefaultCardComponent: undefined, // TODO
7
+ DefaultPieceComponent: undefined // TODO
8
+ },
9
+ modal: {
10
+ chipOptionsByType: {
11
+ artist: {
12
+ action: { makeHref: (artist) => `/artist/${artist.name}` }
13
+ },
14
+ tag: { action: { makeHref: (tag) => `/tag/${tag}` } },
15
+ character: { action: { makeHref: (ch) => `/characters/${ch.name}` } },
16
+ permalink: { action: { makeHref: (pl) => `/piece/${pl.slug}` } }
17
+ }
18
+ }
19
+ };
20
+ function spread(cfg) {
21
+ const modalBase = DEFAULT_CONFIG.modal;
22
+ let gallery = DEFAULT_CONFIG.gallery;
23
+ let modal = modalBase;
24
+ let chipOptionsByType = modalBase?.chipOptionsByType;
25
+ if (cfg.gallery) {
26
+ gallery = { ...DEFAULT_CONFIG.gallery, ...cfg.gallery };
27
+ }
28
+ if (cfg.modal) {
29
+ if (cfg.modal.chipOptionsByType) {
30
+ chipOptionsByType = { ...modalBase?.chipOptionsByType, ...cfg.modal.chipOptionsByType };
31
+ }
32
+ modal = { ...modalBase, ...cfg.modal, chipOptionsByType };
33
+ }
34
+ return { ...cfg, gallery, modal };
35
+ }
36
+ export function useChipConfig(type) {
37
+ const config = useLibraryConfig();
38
+ if (!config.modal) {
39
+ return null;
40
+ }
41
+ return config.modal.chipOptionsByType?.[type] ?? null;
42
+ }
43
+ export function setLibraryConfig(config) {
44
+ set(spread(config));
45
+ $effect(() => {
46
+ set(spread(config));
47
+ });
48
+ }
49
+ export function useLibraryConfig() {
50
+ return get() ?? DEFAULT_CONFIG;
51
+ }
@@ -0,0 +1,3 @@
1
+ import { type ArtPiece } from './art.ts';
2
+ export declare function executeSearch(query: string, pieces: ArtPiece[]): ArtPiece[];
3
+ //# sourceMappingURL=search.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/lib/util/search.ts"],"names":[],"mappings":"AACA,OAAO,EAAuC,KAAK,QAAQ,EAAE,MAAM,UAAU,CAAC;AAG9E,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,QAAQ,EAAE,CAuB3E"}
@@ -0,0 +1,24 @@
1
+ import fz from 'fuzzysort';
2
+ import { normalizeArtist, normalizeCharacter } from "./art.js";
3
+ import { asRecord } from "./util.js";
4
+ export function executeSearch(query, pieces) {
5
+ const allPieces = asRecord(pieces, (p) => p.slug);
6
+ const searchObj = Object.values(allPieces).map((p) => ({
7
+ name: p.name,
8
+ description: p.description,
9
+ alt: p.alt,
10
+ tags: p.tags.join(', '),
11
+ characters: p.characters
12
+ .map((ch) => normalizeCharacter(ch))
13
+ .map((ch) => `${ch.name} ${ch.from}`),
14
+ artists: normalizeArtist(p.artist)
15
+ .map((a) => a.name)
16
+ .join(', '),
17
+ slug: p.slug
18
+ }));
19
+ const results = fz.go(query, searchObj, {
20
+ keys: ['name', 'characters', 'artists', 'tags', 'alt', 'description'],
21
+ threshold: 0.3
22
+ });
23
+ return results.map((res) => allPieces[res.obj.slug]);
24
+ }
@@ -0,0 +1,4 @@
1
+ export declare const smoothScroll: (anchor: HTMLAnchorElement) => {
2
+ destroy(): void;
3
+ };
4
+ //# sourceMappingURL=smoothscroll.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"smoothscroll.d.ts","sourceRoot":"","sources":["../../src/lib/util/smoothscroll.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,YAAY,GAAI,QAAQ,iBAAiB;;CAoBrD,CAAC"}
@@ -0,0 +1,21 @@
1
+ export const smoothScroll = (anchor) => {
2
+ const cb = (e) => {
3
+ e.preventDefault();
4
+ const href = anchor.getAttribute('href');
5
+ if (!href || !href.startsWith('#'))
6
+ return;
7
+ const target = document.getElementById(href.replace(/^#/g, ''));
8
+ if (!target)
9
+ return;
10
+ window.scrollTo({
11
+ top: target.offsetTop,
12
+ behavior: 'smooth'
13
+ });
14
+ };
15
+ anchor.addEventListener('click', cb);
16
+ return {
17
+ destroy() {
18
+ anchor.removeEventListener('click', cb);
19
+ }
20
+ };
21
+ };
@@ -0,0 +1,19 @@
1
+ import type { GalleryCache } from '../server/gallery.ts';
2
+ import type { ArtPiece, Gallery } from './art.ts';
3
+ export type FolderElement = {
4
+ $type: 'folder';
5
+ data: GalleryTree;
6
+ items: number;
7
+ };
8
+ export type GalleryElement = {
9
+ $type: 'gallery';
10
+ data: Gallery;
11
+ fullpath: string;
12
+ };
13
+ export type TreeElement = FolderElement | GalleryElement;
14
+ export type GalleryTree = {
15
+ [element: string]: TreeElement;
16
+ };
17
+ export declare function asTree(cache: GalleryCache, transformPieces?: (pieces: Array<ArtPiece>) => Array<ArtPiece>): FolderElement;
18
+ export declare function pathView(tree: FolderElement, path: string[]): GalleryTree;
19
+ //# sourceMappingURL=tree.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tree.d.ts","sourceRoot":"","sources":["../../src/lib/util/tree.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAElD,MAAM,MAAM,aAAa,GAAG;IAAE,KAAK,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,WAAW,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAClF,MAAM,MAAM,cAAc,GAAG;IAAE,KAAK,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC;AACnF,MAAM,MAAM,WAAW,GAAG,aAAa,GAAG,cAAc,CAAC;AAEzD,MAAM,MAAM,WAAW,GAAG;IACzB,CAAC,OAAO,EAAE,MAAM,GAAG,WAAW,CAAC;CAC/B,CAAC;AAEF,wBAAgB,MAAM,CACrB,KAAK,EAAE,YAAY,EACnB,eAAe,CAAC,EAAE,CAAC,MAAM,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,KAAK,CAAC,QAAQ,CAAC,GAC5D,aAAa,CA8Bf;AAED,wBAAgB,QAAQ,CAAC,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,WAAW,CA4BzE"}
@@ -0,0 +1,58 @@
1
+ export function asTree(cache, transformPieces) {
2
+ const paths = Object.entries(cache);
3
+ const tree = { $type: 'folder', data: {}, items: 0 };
4
+ for (const [path, gallery] of paths) {
5
+ let cur = tree;
6
+ tree.items += gallery.pieces.length;
7
+ const segments = path.split('/');
8
+ const dir = segments.slice(0, -1);
9
+ for (const segment of dir) {
10
+ let next = cur.data[segment];
11
+ if (!next) {
12
+ cur.data[segment] = { $type: 'folder', data: {}, items: 0 };
13
+ next = cur.data[segment];
14
+ }
15
+ if (next.$type !== 'folder') {
16
+ throw new Error('Somehow found gallery + folder with same name??');
17
+ }
18
+ next.items += gallery.pieces.length;
19
+ cur = next;
20
+ }
21
+ cur.data[segments.at(-1)] = {
22
+ $type: 'gallery',
23
+ data: {
24
+ pieces: transformPieces?.(gallery.pieces) ?? gallery.pieces
25
+ },
26
+ fullpath: path
27
+ };
28
+ }
29
+ return tree;
30
+ }
31
+ export function pathView(tree, path) {
32
+ let cur = tree;
33
+ // Go to path
34
+ for (let i = 0; i < path.length; i++) {
35
+ const el = path[i];
36
+ const next = cur.data[el];
37
+ if (!next) {
38
+ return {};
39
+ }
40
+ if (next.$type !== 'folder') {
41
+ return {};
42
+ }
43
+ cur = next;
44
+ }
45
+ cur = { ...cur, data: { ...cur.data } };
46
+ // Prune deeper levels
47
+ for (const [k, el] of Object.entries(cur.data)) {
48
+ if (el.$type === 'folder') {
49
+ if (el.items === 0) {
50
+ delete cur.data[k];
51
+ }
52
+ else {
53
+ cur.data[k] = { $type: 'folder', data: {}, items: el.items };
54
+ }
55
+ }
56
+ }
57
+ return cur.data;
58
+ }
@@ -0,0 +1,4 @@
1
+ export declare function asRecord<T>(arr: T[], key: (el: T) => string, combine?: (a: T, b: T) => T): Record<string, T>;
2
+ export declare function multiRecordBy<T>(arr: T[], key: (el: T) => string): Record<string, T[]>;
3
+ export declare function deduplicateBy<T>(arr: T[], key: (el: T) => string, combine?: (a: T, b: T) => T): T[];
4
+ //# sourceMappingURL=util.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../src/lib/util/util.ts"],"names":[],"mappings":"AAIA,wBAAgB,QAAQ,CAAC,CAAC,EACzB,GAAG,EAAE,CAAC,EAAE,EACR,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,KAAK,MAAM,EACtB,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,CAAC,GACzB,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAYnB;AAED,wBAAgB,aAAa,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,KAAK,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,CAMtF;AAED,wBAAgB,aAAa,CAAC,CAAC,EAC9B,GAAG,EAAE,CAAC,EAAE,EACR,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,KAAK,MAAM,EACtB,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,CAAC,GACzB,CAAC,EAAE,CAEL"}
@@ -0,0 +1,22 @@
1
+ function defaultCombine(a) {
2
+ return a;
3
+ }
4
+ export function asRecord(arr, key, combine) {
5
+ const record = {};
6
+ for (const val of arr) {
7
+ const k = key(val);
8
+ if (k in record) {
9
+ record[k] = (combine ?? defaultCombine)(record[k], val);
10
+ }
11
+ else {
12
+ record[k] = val;
13
+ }
14
+ }
15
+ return record;
16
+ }
17
+ export function multiRecordBy(arr, key) {
18
+ return asRecord(arr.map((v) => [v]), (arr) => key(arr[0]), (a1, a2) => [...a1, ...a2]);
19
+ }
20
+ export function deduplicateBy(arr, key, combine) {
21
+ return Object.values(asRecord(arr, key, combine));
22
+ }
package/package.json ADDED
@@ -0,0 +1,102 @@
1
+ {
2
+ "name": "@phosart/common",
3
+ "version": "0.4.22",
4
+ "files": [
5
+ "dist",
6
+ "!dist/**/*.test.*",
7
+ "!dist/**/*.spec.*",
8
+ "src/lib",
9
+ "!src/lib/**/*.test.*",
10
+ "!src/lib/**/*.spec.*"
11
+ ],
12
+ "sideEffects": [
13
+ "**/*.css"
14
+ ],
15
+ "svelte": "./dist/index.js",
16
+ "types": "./dist/index.d.ts",
17
+ "type": "module",
18
+ "exports": {
19
+ ".": {
20
+ "types": "./dist/index.d.ts",
21
+ "svelte": "./dist/index.js"
22
+ },
23
+ "./util": {
24
+ "types": "./dist/util/index.d.ts",
25
+ "default": "./dist/util/index.js"
26
+ },
27
+ "./server": {
28
+ "types": "./dist/server/index.d.ts",
29
+ "default": "./dist/server/index.js"
30
+ }
31
+ },
32
+ "peerDependencies": {
33
+ "svelte": "^5.0.0"
34
+ },
35
+ "devDependencies": {
36
+ "@eslint/compat": "^1.4.0",
37
+ "@eslint/js": "^9.39.1",
38
+ "@playwright/test": "^1.57.0",
39
+ "@sveltejs/adapter-auto": "^7.0.0",
40
+ "@sveltejs/kit": "^2.49.2",
41
+ "@sveltejs/package": "^2.5.7",
42
+ "@sveltejs/vite-plugin-svelte": "^6.2.1",
43
+ "@tailwindcss/typography": "^0.5.19",
44
+ "@tailwindcss/vite": "^4.1.17",
45
+ "@types/msgpack": "^0.0.34",
46
+ "@types/node": "^22",
47
+ "@vitest/browser-playwright": "^4.0.15",
48
+ "@vitest/coverage-v8": "^4.0.15",
49
+ "eslint": "^9.39.1",
50
+ "eslint-config-prettier": "^10.1.8",
51
+ "eslint-plugin-svelte": "^3.13.1",
52
+ "globals": "^16.5.0",
53
+ "husky": "^9.1.7",
54
+ "lint-staged": "^16.2.7",
55
+ "playwright": "^1.57.0",
56
+ "prettier": "^3.7.4",
57
+ "prettier-plugin-svelte": "^3.4.0",
58
+ "prettier-plugin-tailwindcss": "^0.7.2",
59
+ "publint": "^0.3.15",
60
+ "svelte": "^5.45.6",
61
+ "svelte-check": "^4.3.4",
62
+ "tailwindcss": "^4.1.17",
63
+ "typescript": "^5.9.3",
64
+ "typescript-eslint": "^8.48.1",
65
+ "vite": "^7.2.6",
66
+ "vitest": "^4.0.15",
67
+ "vitest-browser-svelte": "^2.0.1"
68
+ },
69
+ "keywords": [
70
+ "svelte"
71
+ ],
72
+ "dependencies": {
73
+ "@fortawesome/fontawesome-free": "^7.1.0",
74
+ "async-sema": "^3.1.1",
75
+ "fuzzysort": "^3.1.0",
76
+ "glob": "^13.0.0",
77
+ "msgpackr": "^1.11.8",
78
+ "rehype-sanitize": "^6.0.0",
79
+ "rehype-stringify": "^10.0.1",
80
+ "remark-gfm": "^4.0.1",
81
+ "remark-parse": "^11.0.0",
82
+ "remark-rehype": "^11.1.2",
83
+ "sharp": "^0.34.5",
84
+ "sharp-phash": "^2.2.0",
85
+ "tslog": "^4.10.2",
86
+ "unified": "^11.0.5",
87
+ "yaml": "^2.8.2",
88
+ "zod": "^4.3.2"
89
+ },
90
+ "scripts": {
91
+ "dev": "vite dev",
92
+ "build": "vite build && npm run prepack && WRITE_SCHEMA=1 node ./dist/server/theme/schema.js",
93
+ "preview": "vite preview",
94
+ "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
95
+ "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
96
+ "format": "prettier --write .",
97
+ "lint": "prettier --check . && eslint .",
98
+ "test:unit": "vitest",
99
+ "test": "npm run test:unit -- --run && npm run test:e2e",
100
+ "test:e2e": "playwright test"
101
+ }
102
+ }
@@ -0,0 +1,51 @@
1
+ <script lang="ts">
2
+ import type { ArtPiece } from './util/art.ts';
3
+ import ModalGallery from './ModalGallery.svelte';
4
+ import type { Component } from 'svelte';
5
+ import { useLibraryConfig } from './util/phosart_config.svelte.ts';
6
+
7
+ interface Props {
8
+ pieces: ArtPiece[];
9
+ addNav?: boolean;
10
+ noDetails?: boolean;
11
+ browser: boolean;
12
+ CardComponent?: Component<{
13
+ piece: ArtPiece;
14
+ onselect: () => void;
15
+ showDescriptionByDefault: boolean;
16
+ }>;
17
+ }
18
+
19
+ let { pieces, addNav = false, noDetails = false, browser, CardComponent }: Props = $props();
20
+
21
+ let config = useLibraryConfig();
22
+
23
+ const TheCardComponent = $derived(CardComponent ?? config.gallery?.DefaultCardComponent);
24
+
25
+ let selected: number | null = $state(null);
26
+
27
+ function onSelect(idx: number) {
28
+ selected = idx;
29
+ }
30
+ </script>
31
+
32
+ <div
33
+ style="margin-bottom: 24px; align-items: stretch; display: flex; flex-direction: column; row-gap: 2rem;"
34
+ >
35
+ {#each pieces as piece, i (piece.slug)}
36
+ <section
37
+ id={piece.name}
38
+ data-nav={piece.name}
39
+ data-nav-icon="palette"
40
+ class:scroll-ignore={!addNav}
41
+ >
42
+ <TheCardComponent
43
+ {piece}
44
+ onselect={() => onSelect(i)}
45
+ showDescriptionByDefault={!noDetails}
46
+ />
47
+ </section>
48
+ {/each}
49
+
50
+ <ModalGallery {pieces} {browser} bind:selected />
51
+ </div>
@@ -0,0 +1,30 @@
1
+ <script lang="ts">
2
+ import type { ArtPiece } from './util/art.ts';
3
+ import ModalGallery from './ModalGallery.svelte';
4
+ import type { Component } from 'svelte';
5
+ import { useLibraryConfig } from './util/phosart_config.svelte.ts';
6
+
7
+ interface Props {
8
+ pieces: ArtPiece[];
9
+ browser: boolean;
10
+ PieceComponent?: Component<{ piece: ArtPiece; onselect: () => void }>;
11
+ }
12
+
13
+ let { pieces, browser, PieceComponent }: Props = $props();
14
+
15
+ let config = useLibraryConfig();
16
+
17
+ let ThePieceComponent = $derived(PieceComponent ?? config.gallery?.DefaultPieceComponent);
18
+
19
+ let selected: number | null = $state(null);
20
+
21
+ function onSelect(idx: number) {
22
+ selected = idx;
23
+ }
24
+ </script>
25
+
26
+ {#each pieces as piece, i (piece.slug)}
27
+ <ThePieceComponent {piece} onselect={() => onSelect(i)} />
28
+ {/each}
29
+
30
+ <ModalGallery {pieces} {browser} bind:selected />
@@ -0,0 +1,60 @@
1
+ <script lang="ts">
2
+ import type { Gallery } from './util/art.ts';
3
+ import Image from './Image.svelte';
4
+
5
+ interface Props {
6
+ gallery: Gallery;
7
+ cssSize: string;
8
+ }
9
+
10
+ const { gallery, cssSize }: Props = $props();
11
+
12
+ const pieces = $derived(gallery.pieces.slice(0, 4));
13
+ </script>
14
+
15
+ {#if pieces[0]}
16
+ {@const topw = pieces[1] ? 'calc(var(--preview-size) / 2)' : 'var(--preview-size)'}
17
+ {@const toph = pieces[2] ? 'calc(var(--preview-size) / 2)' : 'var(--preview-size)'}
18
+ {@const botw = pieces[3] ? 'calc(var(--preview-size) / 2)' : 'var(--preview-size)'}
19
+ {@const both = 'calc(var(--preview-size) / 2)'}
20
+ <div class="preview" style="--preview-size: {cssSize}">
21
+ <div style="height: {toph}; width: {topw}" class="imgcontainer" class:square={topw === toph}>
22
+ <div><Image alt={pieces[0].alt} picture={pieces[0].image.thumbnail} /></div>
23
+ </div>
24
+ {#if pieces[1]}
25
+ <div style="height: {toph}; width: {topw}" class="imgcontainer" class:square={topw === toph}>
26
+ <div><Image alt={pieces[1].alt} picture={pieces[1].image.thumbnail} /></div>
27
+ </div>
28
+ {/if}
29
+ {#if pieces[2]}
30
+ <div style="height: {both}; width: {botw}" class="imgcontainer" class:square={botw === both}>
31
+ <div><Image alt={pieces[2].alt} picture={pieces[2].image.thumbnail} /></div>
32
+ </div>
33
+ {/if}
34
+ {#if pieces[3]}
35
+ <div style="height: {both}; width: {botw}" class="imgcontainer" class:square={botw === both}>
36
+ <div><Image alt={pieces[3].alt} picture={pieces[3].image.thumbnail} /></div>
37
+ </div>
38
+ {/if}
39
+ </div>
40
+ {/if}
41
+
42
+ <style>
43
+ .preview {
44
+ display: contents;
45
+ }
46
+ .imgcontainer {
47
+ overflow: hidden;
48
+ display: flex;
49
+ justify-content: center;
50
+ align-items: center;
51
+ }
52
+ .imgcontainer:not(.square) > div {
53
+ width: var(--preview-size);
54
+ height: var(--preview-size);
55
+ max-width: var(--preview-size);
56
+ max-height: var(--preview-size);
57
+ min-width: var(--preview-size);
58
+ min-height: var(--preview-size);
59
+ }
60
+ </style>
@@ -0,0 +1,21 @@
1
+ <script lang="ts" module>
2
+ import { getContext } from 'svelte';
3
+ const ctxKey = Symbol();
4
+
5
+ export function isHighRes() {
6
+ return getContext(ctxKey) === true;
7
+ }
8
+ </script>
9
+
10
+ <script lang="ts">
11
+ import { setContext } from 'svelte';
12
+ interface Props {
13
+ children?: import('svelte').Snippet;
14
+ }
15
+
16
+ let { children }: Props = $props();
17
+
18
+ setContext(ctxKey, true);
19
+ </script>
20
+
21
+ {@render children?.()}
@@ -0,0 +1,171 @@
1
+ <script lang="ts">
2
+ import { onlyHighRes, type Image as ImageModel, no4K } from './util/art.ts';
3
+ import { untrack } from 'svelte';
4
+ import { isHighRes } from './HighResContext.svelte';
5
+
6
+ interface Props {
7
+ picture: ImageModel;
8
+ alt: string;
9
+ video?: string | undefined;
10
+ controls?: boolean;
11
+ nolqip?: boolean;
12
+ onloaded?: () => void;
13
+ loading?: boolean;
14
+ }
15
+
16
+ let {
17
+ picture,
18
+ alt,
19
+ onloaded = undefined,
20
+ video = undefined,
21
+ controls = false,
22
+ nolqip = false,
23
+ loading = $bindable(true)
24
+ }: Props = $props();
25
+
26
+ let showBackground = $state(true);
27
+ let loadingMeta = $state(true);
28
+ // svelte-ignore state_referenced_locally
29
+ let lastSrc = $state(picture.fallback.src);
30
+
31
+ const highRes = isHighRes();
32
+ let src = $derived(highRes ? onlyHighRes(picture) : no4K(picture));
33
+
34
+ let background = $derived(
35
+ !nolqip && src.lqip && showBackground
36
+ ? `url(${src.lqip?.src}) no-repeat center/contain`
37
+ : 'none'
38
+ );
39
+
40
+ $effect(() => {
41
+ let prev = untrack(() => lastSrc);
42
+
43
+ // eslint-disable-next-line @typescript-eslint/no-unused-expressions
44
+ picture;
45
+ untrack(() => {
46
+ lastSrc = picture.fallback.src;
47
+ if (prev !== picture.fallback.src) {
48
+ showBackground = true;
49
+ loading = true;
50
+ }
51
+ });
52
+ });
53
+
54
+ function onload(el: HTMLImageElement | HTMLVideoElement) {
55
+ let timeoutHandle: ReturnType<typeof setTimeout> | null = null;
56
+ const onloadend = () => {
57
+ loading = false;
58
+ onloaded?.();
59
+ };
60
+
61
+ const onloadstart = () => {
62
+ if (loading) {
63
+ showBackground = true;
64
+ loadingMeta = true;
65
+ }
66
+ };
67
+
68
+ const animationend = () => {
69
+ showBackground = false;
70
+ };
71
+
72
+ const onloadmeta = () => {
73
+ loadingMeta = false;
74
+ };
75
+
76
+ el.addEventListener('loadstart', onloadstart);
77
+ el.addEventListener('load', onloadend);
78
+ el.addEventListener('loadeddata', onloadend);
79
+ el.addEventListener('loadedmetadata', onloadmeta);
80
+ el.addEventListener('animationend', animationend);
81
+
82
+ onloadstart();
83
+
84
+ return {
85
+ destroy() {
86
+ if (timeoutHandle) {
87
+ clearTimeout(timeoutHandle);
88
+ }
89
+ el.removeEventListener('loadstart', onloadstart);
90
+ el.removeEventListener('load', onloadend);
91
+ el.removeEventListener('loadeddata', onloadend);
92
+ el.removeEventListener('loadedmetadata', onloadmeta);
93
+ el.removeEventListener('animationend', animationend);
94
+ }
95
+ };
96
+ }
97
+ </script>
98
+
99
+ {#each [{ src, video }] as pic (JSON.stringify(pic))}
100
+ {#if video}
101
+ <video
102
+ style="background: {background}; aspect-ratio: {src.fallback.w} / {src.fallback.h};"
103
+ muted
104
+ autoplay
105
+ {controls}
106
+ disablepictureinpicture
107
+ disableremoteplayback
108
+ loop
109
+ playsinline
110
+ use:onload
111
+ >
112
+ <source src={video} type="video/mp4" />
113
+ </video>
114
+ {:else}
115
+ <picture style="background: {background}; aspect-ratio: {src.fallback.w} / {src.fallback.h};">
116
+ {#each Object.entries(src.sources) as [format, images] (format)}
117
+ <source
118
+ srcset={images.map((img) => `${img.src} ${img.w}w`).join(', ')}
119
+ type={'image/' + format}
120
+ />
121
+ {/each}
122
+ <img
123
+ src={src.fallback.src}
124
+ {alt}
125
+ use:onload
126
+ loading="lazy"
127
+ decoding="async"
128
+ class:loading
129
+ class:loaded={!loading}
130
+ width={src.fallback.w}
131
+ height={src.fallback.h}
132
+ style="font-size: 0; {!(!loadingMeta || !loading) &&
133
+ `width: ${src.fallback.w}px; height: ${src.fallback.h}px;`}"
134
+ />
135
+ </picture>
136
+ {/if}
137
+ {/each}
138
+
139
+ <style>
140
+ picture,
141
+ video {
142
+ display: flex;
143
+ width: 100%;
144
+ height: 100%;
145
+ max-height: 100%;
146
+ max-width: 100%;
147
+ overflow: hidden;
148
+ }
149
+ img {
150
+ object-fit: contain;
151
+ object-position: center;
152
+ width: 100%;
153
+ height: 100%;
154
+ }
155
+
156
+ .loading {
157
+ opacity: 0;
158
+ }
159
+ .loaded {
160
+ animation: in 0.1s linear;
161
+ }
162
+
163
+ @keyframes in {
164
+ from {
165
+ opacity: 0;
166
+ }
167
+ to {
168
+ opacity: 1;
169
+ }
170
+ }
171
+ </style>