@jant/core 0.6.1 → 0.6.3
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/dist/app-BX2XKxq0.js +6 -0
- package/dist/{app-DYQdDMs8.js → app-CyysIxj_.js} +464 -240
- package/dist/client/.vite/manifest.json +3 -3
- package/dist/client/_assets/client-BMPMuwvV.css +2 -0
- package/dist/client/_assets/client-CTrEFM5W.js +275 -0
- package/dist/client/_assets/{client-auth-CSNcTJwP.js → client-auth-LBSZxqNC.js} +24 -24
- package/dist/{env-C7e2Nlnt.js → env-CoSe-1y4.js} +1 -1
- package/dist/{export-Bbn86HmS.js → export-CzuQyg5h.js} +32 -19
- package/dist/{github-api-Bh0PH3zr.js → github-api-UD4u_7fa.js} +1 -1
- package/dist/{github-app-D0GvNnqp.js → github-app-DeX6Td1O.js} +1 -1
- package/dist/{github-sync-dXsiZa_e.js → github-sync-CerNYCAn.js} +3 -3
- package/dist/{github-sync-CBQPRZ8H.js → github-sync-Dbrb1DS5.js} +7 -4
- package/dist/index.js +5 -5
- package/dist/node.js +6 -6
- package/dist/{url-umUptr5z.js → url-XF0GbKGO.js} +22 -1
- package/package.json +1 -1
- package/src/__tests__/export-service.test.ts +127 -0
- package/src/client/__tests__/image-processor.test.ts +64 -0
- package/src/client/components/__tests__/jant-collection-directory.test.ts +0 -42
- package/src/client/components/__tests__/jant-media-lightbox.test.ts +79 -8
- package/src/client/components/collection-manager-types.ts +0 -2
- package/src/client/components/jant-collection-directory.ts +0 -23
- package/src/client/components/jant-compose-editor.ts +2 -2
- package/src/client/components/jant-media-lightbox.ts +33 -5
- package/src/client/image-processor.ts +89 -30
- package/src/client/media-scroll-hint.ts +62 -9
- package/src/i18n/coverage.generated.ts +2 -2
- package/src/i18n/locales/public/en.po +0 -12
- package/src/i18n/locales/public/zh-Hans.po +0 -12
- package/src/i18n/locales/public/zh-Hant.po +0 -12
- package/src/i18n/locales/settings/zh-Hans.po +24 -24
- package/src/i18n/locales/settings/zh-Hans.ts +1 -1
- package/src/i18n/locales/settings/zh-Hant.po +24 -24
- package/src/i18n/locales/settings/zh-Hant.ts +1 -1
- package/src/lib/__tests__/structured-data.test.ts +87 -0
- package/src/lib/github-sync-site-config.ts +4 -2
- package/src/lib/post-display.ts +78 -1
- package/src/lib/render.tsx +28 -0
- package/src/lib/structured-data.ts +113 -0
- package/src/lib/url.ts +26 -0
- package/src/routes/api/internal/__tests__/sites.test.ts +65 -0
- package/src/routes/api/internal/sites.ts +19 -0
- package/src/routes/pages/home.tsx +21 -1
- package/src/routes/pages/page.tsx +53 -2
- package/src/services/export-theme/assets/client-site.css +1 -1
- package/src/services/export-theme/assets/client-site.js +30 -29
- package/src/services/export-theme/layouts/partials/media-gallery.html +16 -7
- package/src/services/export-theme/styles/main.css +4 -3
- package/src/services/export.ts +47 -17
- package/src/services/github-sync.ts +8 -2
- package/src/services/site-admin.ts +53 -1
- package/src/styles/site-media.css +70 -24
- package/src/styles/ui.css +30 -48
- package/src/ui/layouts/BaseLayout.tsx +110 -16
- package/src/ui/layouts/__tests__/BaseLayout.test.tsx +146 -0
- package/src/ui/pages/CollectionsPage.tsx +0 -22
- package/src/ui/shared/CollectionsManager.tsx +6 -41
- package/src/ui/shared/MediaGallery.tsx +50 -7
- package/src/ui/shared/__tests__/media-gallery.test.ts +31 -0
- package/dist/app-CMSW_AYG.js +0 -6
- package/dist/client/_assets/client-BRTh1ii1.js +0 -274
- package/dist/client/_assets/client-CO4b-RKd.css +0 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import "./url-
|
|
2
|
-
import "./export-
|
|
3
|
-
import { i as classifyRepoForSync, o as createGitHubSyncService } from "./github-sync-
|
|
1
|
+
import "./url-XF0GbKGO.js";
|
|
2
|
+
import "./export-CzuQyg5h.js";
|
|
3
|
+
import { i as classifyRepoForSync, o as createGitHubSyncService } from "./github-sync-Dbrb1DS5.js";
|
|
4
4
|
export { classifyRepoForSync, createGitHubSyncService };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { c as parseMarkdownDocument, r as parseFrontMatter, t as createExportService } from "./export-
|
|
2
|
-
import { r as getInstallationToken } from "./github-app-
|
|
3
|
-
import { r as parseRepoSlug, t as createGitHubClient } from "./github-api-
|
|
1
|
+
import { c as parseMarkdownDocument, r as parseFrontMatter, t as createExportService } from "./export-CzuQyg5h.js";
|
|
2
|
+
import { r as getInstallationToken } from "./github-app-DeX6Td1O.js";
|
|
3
|
+
import { r as parseRepoSlug, t as createGitHubClient } from "./github-api-UD4u_7fa.js";
|
|
4
4
|
//#region src/lib/markdown-to-tiptap.ts
|
|
5
5
|
/**
|
|
6
6
|
* Markdown → TipTap JSON Conversion
|
|
@@ -274,7 +274,10 @@ function createGitHubSyncService(services, siteId, siteConfig, deps = {}) {
|
|
|
274
274
|
const config = await loadConfig();
|
|
275
275
|
if (!config) throw new Error("GitHub Sync is not configured");
|
|
276
276
|
const { client, owner, repo } = createClient(config);
|
|
277
|
-
const exportFiles = await createExportService(services, siteConfig,
|
|
277
|
+
const exportFiles = await createExportService(services, siteConfig, {
|
|
278
|
+
storage: deps.storage,
|
|
279
|
+
bundleMedia: false
|
|
280
|
+
}).generateHugoFiles();
|
|
278
281
|
const defaultBranch = (await client.getRepo(owner, repo)).default_branch;
|
|
279
282
|
const now = Math.floor(Date.now() / 1e3);
|
|
280
283
|
const existingMarkerBeforeInit = await client.getFileContent(owner, repo, JANT_SYNC_MARKER_PATH).catch(() => null);
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { A as MAX_MEDIA_ATTACHMENTS, C as toMediaView, D as toPostViews, E as toPostView, F as STATUSES, I as TEXT_ATTACHMENT_CONTENT_FORMATS, M as MEDIA_KINDS, N as NAV_ITEM_TYPES, O as toSearchResultView, P as SORT_ORDERS, S as toArchiveGroupsWithMedia, T as toNavItemViews, b as createMediaContext, h as defaultFeedRenderer, j as MAX_PINNED_POSTS, k as FORMATS, t as createApp, w as toNavItemView, x as toArchiveGroups } from "./app-
|
|
3
|
-
import { T as time_exports, a as markdown_exports } from "./export-
|
|
4
|
-
import "./env-
|
|
5
|
-
import "./github-sync-
|
|
1
|
+
import { v as url_exports } from "./url-XF0GbKGO.js";
|
|
2
|
+
import { A as MAX_MEDIA_ATTACHMENTS, C as toMediaView, D as toPostViews, E as toPostView, F as STATUSES, I as TEXT_ATTACHMENT_CONTENT_FORMATS, M as MEDIA_KINDS, N as NAV_ITEM_TYPES, O as toSearchResultView, P as SORT_ORDERS, S as toArchiveGroupsWithMedia, T as toNavItemViews, b as createMediaContext, h as defaultFeedRenderer, j as MAX_PINNED_POSTS, k as FORMATS, t as createApp, w as toNavItemView, x as toArchiveGroups } from "./app-CyysIxj_.js";
|
|
3
|
+
import { T as time_exports, a as markdown_exports } from "./export-CzuQyg5h.js";
|
|
4
|
+
import "./env-CoSe-1y4.js";
|
|
5
|
+
import "./github-sync-Dbrb1DS5.js";
|
|
6
6
|
export { FORMATS, MAX_MEDIA_ATTACHMENTS, MAX_PINNED_POSTS, MEDIA_KINDS, NAV_ITEM_TYPES, SORT_ORDERS, STATUSES, TEXT_ATTACHMENT_CONTENT_FORMATS, createApp, createMediaContext, defaultFeedRenderer, markdown_exports as markdown, time_exports as time, toArchiveGroups, toArchiveGroupsWithMedia, toMediaView, toNavItemView, toNavItemViews, toPostView, toPostViews, toSearchResultView, url_exports as url };
|
package/dist/node.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import "./url-
|
|
2
|
-
import { B as isAssetPath, L as buildThemeStyle, R as BUILTIN_COLOR_THEMES, _ as sqliteSchemaBundle, a as resolveDatabaseDialect, c as resolveConfig, d as setWebhook, f as BUILTIN_FONT_THEMES, g as pgSchemaBundle, i as createSiteService, l as getWebhookUrl, m as getFontThemeCssVariables, n as createNodeCliRuntime, o as getHostBasedStartupConfigurationIssues, p as getCjkSerifCssVariables, r as createNodeRequestRuntime, s as createStorageDriver, t as createApp, u as setMyCommands, v as createNodeDatabase, y as schema_exports, z as getPublicAssetBasePath } from "./app-
|
|
3
|
-
import { t as createExportService } from "./export-
|
|
4
|
-
import { C as shouldTrustProxy, S as getTelegramWebhookSecret, b as getSiteResolutionMode, d as getHostedControlPlaneBaseUrl, i as getConfiguredSingleSitePathPrefix, l as getEnvString, r as getConfiguredSingleSiteOrigin, x as getTelegramBotPool, y as getPort } from "./env-
|
|
5
|
-
import "./github-sync-
|
|
1
|
+
import "./url-XF0GbKGO.js";
|
|
2
|
+
import { B as isAssetPath, L as buildThemeStyle, R as BUILTIN_COLOR_THEMES, _ as sqliteSchemaBundle, a as resolveDatabaseDialect, c as resolveConfig, d as setWebhook, f as BUILTIN_FONT_THEMES, g as pgSchemaBundle, i as createSiteService, l as getWebhookUrl, m as getFontThemeCssVariables, n as createNodeCliRuntime, o as getHostBasedStartupConfigurationIssues, p as getCjkSerifCssVariables, r as createNodeRequestRuntime, s as createStorageDriver, t as createApp, u as setMyCommands, v as createNodeDatabase, y as schema_exports, z as getPublicAssetBasePath } from "./app-CyysIxj_.js";
|
|
3
|
+
import { t as createExportService } from "./export-CzuQyg5h.js";
|
|
4
|
+
import { C as shouldTrustProxy, S as getTelegramWebhookSecret, b as getSiteResolutionMode, d as getHostedControlPlaneBaseUrl, i as getConfiguredSingleSitePathPrefix, l as getEnvString, r as getConfiguredSingleSiteOrigin, x as getTelegramBotPool, y as getPort } from "./env-CoSe-1y4.js";
|
|
5
|
+
import "./github-sync-Dbrb1DS5.js";
|
|
6
6
|
import { drizzle } from "drizzle-orm/better-sqlite3";
|
|
7
7
|
import { serve } from "@hono/node-server";
|
|
8
8
|
import Database from "better-sqlite3";
|
|
@@ -529,7 +529,7 @@ async function createNodeRequestHandler(options) {
|
|
|
529
529
|
async function start(env = process.env, app) {
|
|
530
530
|
const handler = await createNodeRequestHandler({
|
|
531
531
|
env,
|
|
532
|
-
app: async () => app ?? (await import("./app-
|
|
532
|
+
app: async () => app ?? (await import("./app-BX2XKxq0.js")).createApp()
|
|
533
533
|
});
|
|
534
534
|
const hostname = resolveHost(env);
|
|
535
535
|
const port = resolvePort(env);
|
|
@@ -29,6 +29,7 @@ var __exportAll = (all, no_symbols) => {
|
|
|
29
29
|
sanitizeUrl: () => sanitizeUrl,
|
|
30
30
|
slugify: () => slugify,
|
|
31
31
|
stripSitePathPrefix: () => stripSitePathPrefix,
|
|
32
|
+
toAbsoluteAssetUrl: () => toAbsoluteAssetUrl,
|
|
32
33
|
toAbsoluteSiteUrl: () => toAbsoluteSiteUrl,
|
|
33
34
|
toPublicHref: () => toPublicHref,
|
|
34
35
|
toPublicPath: () => toPublicPath
|
|
@@ -330,5 +331,25 @@ var SAFE_URL_PROTOCOLS = new Set([
|
|
|
330
331
|
if (!siteUrl) return toPublicPath(path, sitePathPrefix);
|
|
331
332
|
return new URL(toPublicPath(path, sitePathPrefix), siteUrl).toString();
|
|
332
333
|
}
|
|
334
|
+
/**
|
|
335
|
+
* Resolve a possibly-relative asset URL to an absolute URL, leaving
|
|
336
|
+
* already-absolute (`http(s):`) and protocol-relative (`//host`) URLs
|
|
337
|
+
* untouched. Use for assets — like media — whose stored URL may be either an
|
|
338
|
+
* app-local path or a full CDN URL.
|
|
339
|
+
*
|
|
340
|
+
* @param url - Asset URL: an internal path or an already-absolute URL
|
|
341
|
+
* @param siteUrl - Normalized site URL
|
|
342
|
+
* @param sitePathPrefix - Public site path prefix
|
|
343
|
+
* @returns Absolute URL, or the original value when it is already absolute
|
|
344
|
+
*
|
|
345
|
+
* @example
|
|
346
|
+
* ```ts
|
|
347
|
+
* toAbsoluteAssetUrl("/m/a.png", "https://site.com"); // "https://site.com/m/a.png"
|
|
348
|
+
* toAbsoluteAssetUrl("https://cdn.example/a.png", "x"); // "https://cdn.example/a.png"
|
|
349
|
+
* ```
|
|
350
|
+
*/ function toAbsoluteAssetUrl(url, siteUrl, sitePathPrefix = "") {
|
|
351
|
+
if (isFullUrl(url) || url.startsWith("//")) return url;
|
|
352
|
+
return toAbsoluteSiteUrl(url, siteUrl, sitePathPrefix);
|
|
353
|
+
}
|
|
333
354
|
//#endregion
|
|
334
|
-
export {
|
|
355
|
+
export { toPublicPath as _, getSitePathPrefix as a, normalizePath as c, sanitizeUrl as d, slugify as f, toPublicHref as g, toAbsoluteSiteUrl as h, getSiteOrigin as i, normalizeSitePathPrefix as l, toAbsoluteAssetUrl as m, extractDisplayDomain as n, isFullUrl as o, stripSitePathPrefix as p, extractDomain as r, isSafeInternalRedirect as s, buildSiteUrl as t, normalizeSiteUrl as u, url_exports as v, __exportAll as y };
|
package/package.json
CHANGED
|
@@ -800,4 +800,131 @@ describe("createExportService (Hugo)", () => {
|
|
|
800
800
|
const files = filesToMap(await service.generateHugoFiles());
|
|
801
801
|
expect(files.has("static/media/med-x.webp")).toBe(false);
|
|
802
802
|
});
|
|
803
|
+
|
|
804
|
+
it("Sync mode (bundleMedia false) links media by absolute site URL without reading bytes", async () => {
|
|
805
|
+
const root = makePost({ id: "post-root", slug: "with-media" });
|
|
806
|
+
const media = makeMedia({
|
|
807
|
+
id: "med-1",
|
|
808
|
+
filename: "photo.webp",
|
|
809
|
+
storageKey: "media/med-1.webp",
|
|
810
|
+
});
|
|
811
|
+
const storage = {
|
|
812
|
+
// Must NOT be called — Sync links by URL, never reads attachment bytes.
|
|
813
|
+
get: async () => {
|
|
814
|
+
throw new Error("storage.get should not be called in Sync mode");
|
|
815
|
+
},
|
|
816
|
+
};
|
|
817
|
+
const service = createExportService(
|
|
818
|
+
buildServices({
|
|
819
|
+
posts: [root],
|
|
820
|
+
mediaByPost: new Map([["post-root", [media]]]),
|
|
821
|
+
}),
|
|
822
|
+
makeSiteConfig(),
|
|
823
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
824
|
+
{ storage: storage as any, bundleMedia: false },
|
|
825
|
+
);
|
|
826
|
+
const files = filesToMap(await service.generateHugoFiles());
|
|
827
|
+
expect(files.has("static/media/med-1.webp")).toBe(false);
|
|
828
|
+
const { frontMatter } = await parseFrontMatter(
|
|
829
|
+
files.get("content/with-media/_index.md") as string,
|
|
830
|
+
);
|
|
831
|
+
const entry = frontMatter.media![0];
|
|
832
|
+
expect(entry.src).toBe("https://example.com/media/med-1.webp");
|
|
833
|
+
expect(entry.storage_key).toBe("media/med-1.webp");
|
|
834
|
+
});
|
|
835
|
+
|
|
836
|
+
it("Sync mode links the video poster by absolute site URL without bundling", async () => {
|
|
837
|
+
const root = makePost({ id: "post-root", slug: "with-video" });
|
|
838
|
+
const videoMedia = makeMedia({
|
|
839
|
+
id: "med-video",
|
|
840
|
+
filename: "clip.mp4",
|
|
841
|
+
mimeType: "video/mp4",
|
|
842
|
+
storageKey: "media/med-video.mp4",
|
|
843
|
+
mediaKind: "video",
|
|
844
|
+
posterKey: "media/posters/med-video.webp",
|
|
845
|
+
});
|
|
846
|
+
const storage = {
|
|
847
|
+
get: async () => {
|
|
848
|
+
throw new Error("storage.get should not be called in Sync mode");
|
|
849
|
+
},
|
|
850
|
+
};
|
|
851
|
+
const service = createExportService(
|
|
852
|
+
buildServices({
|
|
853
|
+
posts: [root],
|
|
854
|
+
mediaByPost: new Map([["post-root", [videoMedia]]]),
|
|
855
|
+
}),
|
|
856
|
+
makeSiteConfig(),
|
|
857
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
858
|
+
{ storage: storage as any, bundleMedia: false },
|
|
859
|
+
);
|
|
860
|
+
const files = filesToMap(await service.generateHugoFiles());
|
|
861
|
+
expect(files.has("static/media/med-video.mp4")).toBe(false);
|
|
862
|
+
expect(files.has("static/media/med-video-poster.webp")).toBe(false);
|
|
863
|
+
const { frontMatter } = await parseFrontMatter(
|
|
864
|
+
files.get("content/with-video/_index.md") as string,
|
|
865
|
+
);
|
|
866
|
+
const entry = frontMatter.media![0];
|
|
867
|
+
expect(entry.src).toBe("https://example.com/media/med-video.mp4");
|
|
868
|
+
expect(entry.poster).toBe(
|
|
869
|
+
"https://example.com/media/posters/med-video.webp",
|
|
870
|
+
);
|
|
871
|
+
});
|
|
872
|
+
|
|
873
|
+
it("Sync mode still prefers a dedicated provider public URL when configured", async () => {
|
|
874
|
+
const root = makePost({ id: "post-root", slug: "with-cdn" });
|
|
875
|
+
const media = makeMedia({
|
|
876
|
+
id: "med-cdn",
|
|
877
|
+
filename: "cdn.webp",
|
|
878
|
+
storageKey: "media/med-cdn.webp",
|
|
879
|
+
});
|
|
880
|
+
const service = createExportService(
|
|
881
|
+
buildServices({
|
|
882
|
+
posts: [root],
|
|
883
|
+
mediaByPost: new Map([["post-root", [media]]]),
|
|
884
|
+
}),
|
|
885
|
+
makeSiteConfig({ r2PublicUrl: "https://cdn.example.com" }),
|
|
886
|
+
{ bundleMedia: false },
|
|
887
|
+
);
|
|
888
|
+
const files = filesToMap(await service.generateHugoFiles());
|
|
889
|
+
expect(files.has("static/media/med-cdn.webp")).toBe(false);
|
|
890
|
+
const { frontMatter } = await parseFrontMatter(
|
|
891
|
+
files.get("content/with-cdn/_index.md") as string,
|
|
892
|
+
);
|
|
893
|
+
expect(frontMatter.media![0].src).toBe(
|
|
894
|
+
"https://cdn.example.com/media/med-cdn.webp",
|
|
895
|
+
);
|
|
896
|
+
});
|
|
897
|
+
|
|
898
|
+
it("Sync mode falls back to bundling bytes when the site URL is unknown", async () => {
|
|
899
|
+
const root = makePost({ id: "post-root", slug: "with-media" });
|
|
900
|
+
const media = makeMedia({
|
|
901
|
+
id: "med-1",
|
|
902
|
+
filename: "photo.webp",
|
|
903
|
+
storageKey: "media/med-1.webp",
|
|
904
|
+
});
|
|
905
|
+
const storage = {
|
|
906
|
+
get: async (key: string) =>
|
|
907
|
+
key === "media/med-1.webp"
|
|
908
|
+
? { body: new Blob([new Uint8Array([1, 2, 3])]).stream() }
|
|
909
|
+
: null,
|
|
910
|
+
};
|
|
911
|
+
const service = createExportService(
|
|
912
|
+
buildServices({
|
|
913
|
+
posts: [root],
|
|
914
|
+
mediaByPost: new Map([["post-root", [media]]]),
|
|
915
|
+
}),
|
|
916
|
+
makeSiteConfig({ siteUrl: "" }),
|
|
917
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
918
|
+
{ storage: storage as any, bundleMedia: false },
|
|
919
|
+
);
|
|
920
|
+
const files = filesToMap(await service.generateHugoFiles());
|
|
921
|
+
// No resolvable URL — bundling is the only way to avoid a broken link.
|
|
922
|
+
expect(files.get("static/media/med-1.webp")).toEqual(
|
|
923
|
+
new Uint8Array([1, 2, 3]),
|
|
924
|
+
);
|
|
925
|
+
const { frontMatter } = await parseFrontMatter(
|
|
926
|
+
files.get("content/with-media/_index.md") as string,
|
|
927
|
+
);
|
|
928
|
+
expect(frontMatter.media![0].src).toBe("/media/med-1.webp");
|
|
929
|
+
});
|
|
803
930
|
});
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { planImageProcessing } from "../image-processor.js";
|
|
3
|
+
|
|
4
|
+
const OPTS = { maxShortSide: 1920, maxLongSide: 8192 };
|
|
5
|
+
|
|
6
|
+
describe("planImageProcessing", () => {
|
|
7
|
+
it("leaves small images untouched", () => {
|
|
8
|
+
expect(planImageProcessing(800, 600, OPTS)).toEqual({
|
|
9
|
+
passthrough: false,
|
|
10
|
+
width: 800,
|
|
11
|
+
height: 600,
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("keeps long screenshots at full resolution", () => {
|
|
16
|
+
expect(planImageProcessing(1080, 6000, OPTS)).toEqual({
|
|
17
|
+
passthrough: false,
|
|
18
|
+
width: 1080,
|
|
19
|
+
height: 6000,
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("keeps wide screenshots at full resolution", () => {
|
|
24
|
+
expect(planImageProcessing(6000, 1080, OPTS)).toEqual({
|
|
25
|
+
passthrough: false,
|
|
26
|
+
width: 6000,
|
|
27
|
+
height: 1080,
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("downscales a large photo by its short side, not its long side", () => {
|
|
32
|
+
const plan = planImageProcessing(4032, 3024, OPTS);
|
|
33
|
+
expect(plan.passthrough).toBe(false);
|
|
34
|
+
expect(plan.height).toBe(1920);
|
|
35
|
+
expect(plan.width).toBe(Math.round(4032 * (1920 / 3024)));
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("caps the short side regardless of orientation", () => {
|
|
39
|
+
const portrait = planImageProcessing(3024, 4032, OPTS);
|
|
40
|
+
expect(portrait.width).toBe(1920);
|
|
41
|
+
expect(portrait.height).toBe(Math.round(4032 * (1920 / 3024)));
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("uploads images taller than the canvas limit untouched", () => {
|
|
45
|
+
expect(planImageProcessing(1080, 12000, OPTS)).toEqual({
|
|
46
|
+
passthrough: true,
|
|
47
|
+
width: 1080,
|
|
48
|
+
height: 12000,
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("uploads images wider than the canvas limit untouched", () => {
|
|
53
|
+
expect(planImageProcessing(12000, 1080, OPTS)).toEqual({
|
|
54
|
+
passthrough: true,
|
|
55
|
+
width: 12000,
|
|
56
|
+
height: 1080,
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("treats the long-side cap as inclusive", () => {
|
|
61
|
+
expect(planImageProcessing(1080, 8192, OPTS).passthrough).toBe(false);
|
|
62
|
+
expect(planImageProcessing(1080, 8193, OPTS).passthrough).toBe(true);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
@@ -24,8 +24,6 @@ import type { JantCollectionsManager } from "../jant-collection-directory.js";
|
|
|
24
24
|
|
|
25
25
|
const labels: CollectionManagerLabels = {
|
|
26
26
|
collectionsTitle: "Collections",
|
|
27
|
-
collectionSingular: "collection",
|
|
28
|
-
collectionPlural: "collections",
|
|
29
27
|
organize: "Organize",
|
|
30
28
|
done: "Done",
|
|
31
29
|
organizeHint: "Drag to reorder.",
|
|
@@ -186,7 +184,6 @@ async function createElementWithManagerRoot(): Promise<JantCollectionsManager> {
|
|
|
186
184
|
const root = document.createElement("div");
|
|
187
185
|
root.setAttribute("data-collections-manager-root", "");
|
|
188
186
|
root.innerHTML = `
|
|
189
|
-
<p data-collections-count></p>
|
|
190
187
|
<div data-collections-reorder-actions hidden>
|
|
191
188
|
<button type="button" data-collections-action="divider">New divider</button>
|
|
192
189
|
<button type="button" data-collections-action="done">Done</button>
|
|
@@ -208,32 +205,6 @@ async function createElementWithManagerRoot(): Promise<JantCollectionsManager> {
|
|
|
208
205
|
return el;
|
|
209
206
|
}
|
|
210
207
|
|
|
211
|
-
async function createEmptyElementWithManagerRoot(): Promise<JantCollectionsManager> {
|
|
212
|
-
const root = document.createElement("div");
|
|
213
|
-
root.setAttribute("data-collections-manager-root", "");
|
|
214
|
-
root.innerHTML = `
|
|
215
|
-
<p data-collections-count hidden></p>
|
|
216
|
-
<div data-collections-reorder-actions hidden>
|
|
217
|
-
<button type="button" data-collections-action="divider">New divider</button>
|
|
218
|
-
<button type="button" data-collections-action="done">Done</button>
|
|
219
|
-
</div>
|
|
220
|
-
<div data-collections-toolbar></div>
|
|
221
|
-
<p data-collections-hint hidden></p>
|
|
222
|
-
<div data-collections-more-menu hidden></div>
|
|
223
|
-
<button type="button" data-collections-action="toggle-menu"></button>
|
|
224
|
-
`;
|
|
225
|
-
|
|
226
|
-
const el = document.createElement(
|
|
227
|
-
"jant-collections-manager",
|
|
228
|
-
) as JantCollectionsManager;
|
|
229
|
-
el.labels = labels;
|
|
230
|
-
el.items = [];
|
|
231
|
-
root.appendChild(el);
|
|
232
|
-
document.body.appendChild(root);
|
|
233
|
-
await el.updateComplete;
|
|
234
|
-
return el;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
208
|
describe("JantCollectionsManager", () => {
|
|
238
209
|
beforeEach(() => {
|
|
239
210
|
document.body.innerHTML = "";
|
|
@@ -308,19 +279,6 @@ describe("JantCollectionsManager", () => {
|
|
|
308
279
|
expect(toolbar?.hidden).toBe(true);
|
|
309
280
|
});
|
|
310
281
|
|
|
311
|
-
it("keeps the collection count visible when the list is empty", async () => {
|
|
312
|
-
const el = await createEmptyElementWithManagerRoot();
|
|
313
|
-
const root = el.closest<HTMLElement>("[data-collections-manager-root]");
|
|
314
|
-
|
|
315
|
-
expect(root).not.toBeNull();
|
|
316
|
-
if (!root) throw new Error("Expected collections manager root");
|
|
317
|
-
|
|
318
|
-
const count = root.querySelector<HTMLElement>("[data-collections-count]");
|
|
319
|
-
|
|
320
|
-
expect(count?.hidden).toBe(false);
|
|
321
|
-
expect(count?.textContent).toBe("0 collections");
|
|
322
|
-
});
|
|
323
|
-
|
|
324
282
|
it("renders divider labels as aggregate links when followed by a grouped section", async () => {
|
|
325
283
|
const el = await createElementWithItems(groupedItems);
|
|
326
284
|
|
|
@@ -94,7 +94,7 @@ describe("JantMediaLightbox", () => {
|
|
|
94
94
|
).toBe(false);
|
|
95
95
|
});
|
|
96
96
|
|
|
97
|
-
it("
|
|
97
|
+
it("previews tall images contained, then expands to a scrollable stage on click", async () => {
|
|
98
98
|
const el = await createElement();
|
|
99
99
|
|
|
100
100
|
el.open(
|
|
@@ -110,14 +110,67 @@ describe("JantMediaLightbox", () => {
|
|
|
110
110
|
);
|
|
111
111
|
await flush(el);
|
|
112
112
|
|
|
113
|
-
const
|
|
114
|
-
const
|
|
113
|
+
const initialStage = el.querySelector(".media-lightbox-stage");
|
|
114
|
+
const initialImg = el.querySelector<HTMLImageElement>(
|
|
115
|
+
".media-lightbox-img",
|
|
116
|
+
);
|
|
115
117
|
|
|
116
|
-
expect(
|
|
117
|
-
|
|
118
|
+
expect(
|
|
119
|
+
initialStage?.classList.contains("media-lightbox-stage-scroll"),
|
|
120
|
+
).toBe(false);
|
|
121
|
+
expect(initialImg?.classList.contains("media-lightbox-img-scroll")).toBe(
|
|
122
|
+
false,
|
|
123
|
+
);
|
|
124
|
+
expect(initialImg?.classList.contains("media-lightbox-img-zoomable")).toBe(
|
|
125
|
+
true,
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
initialImg?.dispatchEvent(
|
|
129
|
+
new MouseEvent("click", { bubbles: true, cancelable: true }),
|
|
130
|
+
);
|
|
131
|
+
await flush(el);
|
|
132
|
+
|
|
133
|
+
const zoomedStage = el.querySelector(".media-lightbox-stage");
|
|
134
|
+
const zoomedImg = el.querySelector(".media-lightbox-img");
|
|
135
|
+
expect(zoomedStage?.classList.contains("media-lightbox-stage-scroll")).toBe(
|
|
136
|
+
true,
|
|
137
|
+
);
|
|
138
|
+
expect(zoomedImg?.classList.contains("media-lightbox-img-scroll")).toBe(
|
|
139
|
+
true,
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
zoomedImg?.dispatchEvent(
|
|
143
|
+
new MouseEvent("click", { bubbles: true, cancelable: true }),
|
|
144
|
+
);
|
|
145
|
+
await flush(el);
|
|
146
|
+
|
|
147
|
+
const collapsedStage = el.querySelector(".media-lightbox-stage");
|
|
148
|
+
expect(
|
|
149
|
+
collapsedStage?.classList.contains("media-lightbox-stage-scroll"),
|
|
150
|
+
).toBe(false);
|
|
118
151
|
});
|
|
119
152
|
|
|
120
|
-
it("
|
|
153
|
+
it("does not show the zoom affordance for regular-aspect images", async () => {
|
|
154
|
+
const el = await createElement();
|
|
155
|
+
|
|
156
|
+
el.open(
|
|
157
|
+
[
|
|
158
|
+
{
|
|
159
|
+
url: "https://example.com/wide.jpg",
|
|
160
|
+
alt: "",
|
|
161
|
+
width: 1600,
|
|
162
|
+
height: 900,
|
|
163
|
+
},
|
|
164
|
+
],
|
|
165
|
+
0,
|
|
166
|
+
);
|
|
167
|
+
await flush(el);
|
|
168
|
+
|
|
169
|
+
const img = el.querySelector(".media-lightbox-img");
|
|
170
|
+
expect(img?.classList.contains("media-lightbox-img-zoomable")).toBe(false);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it("resets zoom and stage scroll when navigating between images", async () => {
|
|
121
174
|
const el = await createElement();
|
|
122
175
|
|
|
123
176
|
el.open(
|
|
@@ -139,10 +192,18 @@ describe("JantMediaLightbox", () => {
|
|
|
139
192
|
);
|
|
140
193
|
await flush(el);
|
|
141
194
|
|
|
195
|
+
el.querySelector<HTMLImageElement>(".media-lightbox-img")?.dispatchEvent(
|
|
196
|
+
new MouseEvent("click", { bubbles: true, cancelable: true }),
|
|
197
|
+
);
|
|
198
|
+
await flush(el);
|
|
199
|
+
|
|
142
200
|
const initialStage = el.querySelector<HTMLElement>(".media-lightbox-stage");
|
|
143
201
|
if (!initialStage) {
|
|
144
202
|
throw new Error("expected lightbox stage");
|
|
145
203
|
}
|
|
204
|
+
expect(initialStage.classList.contains("media-lightbox-stage-scroll")).toBe(
|
|
205
|
+
true,
|
|
206
|
+
);
|
|
146
207
|
initialStage.scrollTop = 180;
|
|
147
208
|
|
|
148
209
|
el.querySelector<HTMLButtonElement>(".media-lightbox-nav-next")?.click();
|
|
@@ -150,6 +211,9 @@ describe("JantMediaLightbox", () => {
|
|
|
150
211
|
|
|
151
212
|
const nextStage = el.querySelector<HTMLElement>(".media-lightbox-stage");
|
|
152
213
|
expect(nextStage?.scrollTop).toBe(0);
|
|
214
|
+
expect(nextStage?.classList.contains("media-lightbox-stage-scroll")).toBe(
|
|
215
|
+
false,
|
|
216
|
+
);
|
|
153
217
|
});
|
|
154
218
|
|
|
155
219
|
it("uses natural image dimensions for inline post-body images", async () => {
|
|
@@ -179,8 +243,15 @@ describe("JantMediaLightbox", () => {
|
|
|
179
243
|
);
|
|
180
244
|
await flush(el);
|
|
181
245
|
|
|
182
|
-
const
|
|
183
|
-
|
|
246
|
+
const lightboxImg = el.querySelector<HTMLImageElement>(
|
|
247
|
+
".media-lightbox-img",
|
|
248
|
+
);
|
|
249
|
+
expect(lightboxImg?.classList.contains("media-lightbox-img-zoomable")).toBe(
|
|
250
|
+
true,
|
|
251
|
+
);
|
|
252
|
+
expect(lightboxImg?.classList.contains("media-lightbox-img-scroll")).toBe(
|
|
253
|
+
false,
|
|
254
|
+
);
|
|
184
255
|
});
|
|
185
256
|
|
|
186
257
|
it("renders custom controls for short videos", async () => {
|
|
@@ -245,21 +245,6 @@ export class JantCollectionsManager extends LitElement {
|
|
|
245
245
|
);
|
|
246
246
|
}
|
|
247
247
|
|
|
248
|
-
#collectionCount() {
|
|
249
|
-
return this._items.filter(
|
|
250
|
-
(item) => item.type === "collection" && item.collection,
|
|
251
|
-
).length;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
#collectionCountLabel() {
|
|
255
|
-
const count = this.#collectionCount();
|
|
256
|
-
return `${count} ${
|
|
257
|
-
count === 1
|
|
258
|
-
? this.labels.collectionSingular
|
|
259
|
-
: this.labels.collectionPlural
|
|
260
|
-
}`;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
248
|
#countLabel(count: number) {
|
|
264
249
|
return `${count} ${
|
|
265
250
|
count === 1 ? this.labels.entrySingular : this.labels.entryPlural
|
|
@@ -280,14 +265,6 @@ export class JantCollectionsManager extends LitElement {
|
|
|
280
265
|
}
|
|
281
266
|
|
|
282
267
|
#syncHeaderState() {
|
|
283
|
-
const countEl = this.#queryHeaderElement<HTMLElement>(
|
|
284
|
-
"[data-collections-count]",
|
|
285
|
-
);
|
|
286
|
-
if (countEl) {
|
|
287
|
-
countEl.textContent = this.#collectionCountLabel();
|
|
288
|
-
countEl.hidden = false;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
268
|
const doneButton = this.#queryHeaderElement<HTMLButtonElement>(
|
|
292
269
|
'[data-collections-action="done"]',
|
|
293
270
|
);
|
|
@@ -2524,9 +2524,9 @@ export class JantComposeEditor extends LitElement {
|
|
|
2524
2524
|
: this.format === "link"
|
|
2525
2525
|
? this._renderLinkFields()
|
|
2526
2526
|
: this._renderQuoteFields()}
|
|
2527
|
-
${this._renderStarRating()}
|
|
2528
2527
|
</section>
|
|
2529
|
-
${this._renderAttachmentDock()} ${this.
|
|
2528
|
+
${this._renderAttachmentDock()} ${this._renderStarRating()}
|
|
2529
|
+
${this._renderToolsRow()}
|
|
2530
2530
|
`;
|
|
2531
2531
|
}
|
|
2532
2532
|
}
|