mirror of
https://github.com/quartz-community/comments.git
synced 2026-07-22 02:50:28 +00:00
fix: only suppress comments on explicit false / "false" override
Previously the frontmatter check suppressed comments on any falsy value (0, "", null, false, "false"), which is wider than the documented API (docs/features/comments.md only shows `comments: false`). The refactor also removes the `as Record<string, unknown>` cast dance that predated the plugin importing proper QuartzComponentProps. fileData.frontmatter and cfg.baseUrl are now typed directly. Regression tests cover: - frontmatter.comments=false / "false" -> suppressed (documented) - frontmatter.comments=true / "true" / undefined -> rendered - frontmatter.comments=0 / "" / null -> rendered (behavior change) - data attribute wiring and defaults - cfg.baseUrl fallback chain BEHAVIOR CHANGE: sites that relied on the prior permissive disable logic (e.g. setting `comments: 0` to hide comments) will now see comments rendered. Update to `comments: false` to restore the intent.
This commit is contained in:
parent
c044e071a2
commit
d79ee3c4a0
6 changed files with 187 additions and 30 deletions
8
dist/components/index.js
vendored
8
dist/components/index.js
vendored
|
|
@ -12,12 +12,10 @@ function boolToStringBool(b) {
|
|||
}
|
||||
var Comments_default = ((opts) => {
|
||||
const Comments = ({ displayClass, fileData, cfg }) => {
|
||||
const fd = fileData;
|
||||
const disableComment = typeof fd.frontmatter?.comments !== "undefined" && (!fd.frontmatter?.comments || fd.frontmatter?.comments === "false");
|
||||
if (disableComment) {
|
||||
const commentsOverride = fileData.frontmatter?.comments;
|
||||
if (commentsOverride === false || commentsOverride === "false") {
|
||||
return /* @__PURE__ */ jsx(Fragment, {});
|
||||
}
|
||||
const c = cfg;
|
||||
return /* @__PURE__ */ jsx(
|
||||
"div",
|
||||
{
|
||||
|
|
@ -32,7 +30,7 @@ var Comments_default = ((opts) => {
|
|||
"data-input-position": opts.options.inputPosition ?? "bottom",
|
||||
"data-light-theme": opts.options.lightTheme ?? "light",
|
||||
"data-dark-theme": opts.options.darkTheme ?? "dark",
|
||||
"data-theme-url": opts.options.themeUrl ?? `https://${c.baseUrl ?? "example.com"}/static/giscus`,
|
||||
"data-theme-url": opts.options.themeUrl ?? `https://${cfg.baseUrl ?? "example.com"}/static/giscus`,
|
||||
"data-lang": opts.options.lang ?? "en"
|
||||
}
|
||||
);
|
||||
|
|
|
|||
2
dist/components/index.js.map
vendored
2
dist/components/index.js.map
vendored
File diff suppressed because one or more lines are too long
8
dist/index.js
vendored
8
dist/index.js
vendored
|
|
@ -12,12 +12,10 @@ function boolToStringBool(b) {
|
|||
}
|
||||
var Comments_default = ((opts) => {
|
||||
const Comments = ({ displayClass, fileData, cfg }) => {
|
||||
const fd = fileData;
|
||||
const disableComment = typeof fd.frontmatter?.comments !== "undefined" && (!fd.frontmatter?.comments || fd.frontmatter?.comments === "false");
|
||||
if (disableComment) {
|
||||
const commentsOverride = fileData.frontmatter?.comments;
|
||||
if (commentsOverride === false || commentsOverride === "false") {
|
||||
return /* @__PURE__ */ jsx(Fragment, {});
|
||||
}
|
||||
const c = cfg;
|
||||
return /* @__PURE__ */ jsx(
|
||||
"div",
|
||||
{
|
||||
|
|
@ -32,7 +30,7 @@ var Comments_default = ((opts) => {
|
|||
"data-input-position": opts.options.inputPosition ?? "bottom",
|
||||
"data-light-theme": opts.options.lightTheme ?? "light",
|
||||
"data-dark-theme": opts.options.darkTheme ?? "dark",
|
||||
"data-theme-url": opts.options.themeUrl ?? `https://${c.baseUrl ?? "example.com"}/static/giscus`,
|
||||
"data-theme-url": opts.options.themeUrl ?? `https://${cfg.baseUrl ?? "example.com"}/static/giscus`,
|
||||
"data-lang": opts.options.lang ?? "en"
|
||||
}
|
||||
);
|
||||
|
|
|
|||
2
dist/index.js.map
vendored
2
dist/index.js.map
vendored
File diff suppressed because one or more lines are too long
|
|
@ -29,17 +29,11 @@ function boolToStringBool(b: boolean): string {
|
|||
|
||||
export default ((opts: CommentsOptions) => {
|
||||
const Comments: QuartzComponent = ({ displayClass, fileData, cfg }: QuartzComponentProps) => {
|
||||
// check if comments should be displayed according to frontmatter
|
||||
const fd = fileData as Record<string, unknown>;
|
||||
const disableComment: boolean =
|
||||
typeof (fd.frontmatter as Record<string, unknown>)?.comments !== "undefined" &&
|
||||
(!(fd.frontmatter as Record<string, unknown>)?.comments ||
|
||||
(fd.frontmatter as Record<string, unknown>)?.comments === "false");
|
||||
if (disableComment) {
|
||||
const commentsOverride = fileData.frontmatter?.comments;
|
||||
if (commentsOverride === false || commentsOverride === "false") {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
const c = cfg as Record<string, unknown>;
|
||||
return (
|
||||
<div
|
||||
class={classNames(displayClass, "giscus")}
|
||||
|
|
@ -54,7 +48,7 @@ export default ((opts: CommentsOptions) => {
|
|||
data-light-theme={opts.options.lightTheme ?? "light"}
|
||||
data-dark-theme={opts.options.darkTheme ?? "dark"}
|
||||
data-theme-url={
|
||||
opts.options.themeUrl ?? `https://${(c.baseUrl as string) ?? "example.com"}/static/giscus`
|
||||
opts.options.themeUrl ?? `https://${cfg.baseUrl ?? "example.com"}/static/giscus`
|
||||
}
|
||||
data-lang={opts.options.lang ?? "en"}
|
||||
></div>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,60 @@
|
|||
import { describe, it, expect } from "vitest";
|
||||
import { Fragment } from "preact";
|
||||
import Comments from "../src/components/Comments";
|
||||
import type { QuartzComponent, QuartzComponentProps } from "@quartz-community/types";
|
||||
|
||||
type CommentsOptions = Parameters<typeof Comments>[0];
|
||||
|
||||
const baseOpts: CommentsOptions = {
|
||||
provider: "giscus",
|
||||
options: {
|
||||
repo: "test/repo",
|
||||
repoId: "test-id",
|
||||
category: "Announcements",
|
||||
categoryId: "test-cat-id",
|
||||
},
|
||||
};
|
||||
|
||||
function buildProps(
|
||||
overrides: Partial<QuartzComponentProps> & {
|
||||
frontmatter?: Record<string, unknown>;
|
||||
cfg?: Record<string, unknown>;
|
||||
} = {},
|
||||
): QuartzComponentProps {
|
||||
const { frontmatter, cfg, ...rest } = overrides;
|
||||
return {
|
||||
ctx: {},
|
||||
externalResources: { css: [], js: [], additionalHead: [] },
|
||||
fileData: { frontmatter: frontmatter ?? {} } as QuartzComponentProps["fileData"],
|
||||
cfg: (cfg ?? {}) as QuartzComponentProps["cfg"],
|
||||
children: [],
|
||||
tree: null,
|
||||
allFiles: [],
|
||||
...rest,
|
||||
};
|
||||
}
|
||||
|
||||
function renderComments(
|
||||
opts: CommentsOptions,
|
||||
props: QuartzComponentProps,
|
||||
): ReturnType<QuartzComponent> {
|
||||
const Component = Comments(opts);
|
||||
return Component(props);
|
||||
}
|
||||
|
||||
function isFragmentOrEmpty(vnode: unknown): boolean {
|
||||
if (vnode == null || vnode === false) return true;
|
||||
if (typeof vnode !== "object") return false;
|
||||
const v = vnode as { type?: unknown };
|
||||
return v.type === Fragment;
|
||||
}
|
||||
|
||||
function getDivProps(vnode: unknown): Record<string, unknown> | null {
|
||||
if (typeof vnode !== "object" || vnode == null) return null;
|
||||
const v = vnode as { type?: unknown; props?: Record<string, unknown> };
|
||||
if (v.type !== "div") return null;
|
||||
return v.props ?? null;
|
||||
}
|
||||
|
||||
describe("Comments Plugin", () => {
|
||||
it("should export Comments component", () => {
|
||||
|
|
@ -7,16 +62,128 @@ describe("Comments Plugin", () => {
|
|||
});
|
||||
|
||||
it("should create a component with options", () => {
|
||||
const component = Comments({
|
||||
provider: "giscus",
|
||||
options: {
|
||||
repo: "test/repo",
|
||||
repoId: "test-id",
|
||||
category: "Announcements",
|
||||
categoryId: "test-cat-id",
|
||||
},
|
||||
});
|
||||
const component = Comments(baseOpts);
|
||||
expect(component).toBeDefined();
|
||||
expect(typeof component).toBe("function");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Comments: frontmatter.comments override", () => {
|
||||
it("renders the giscus div when frontmatter.comments is undefined", () => {
|
||||
const result = renderComments(baseOpts, buildProps({ frontmatter: {} }));
|
||||
const props = getDivProps(result);
|
||||
expect(props).not.toBeNull();
|
||||
expect(props?.["data-repo"]).toBe("test/repo");
|
||||
});
|
||||
|
||||
it("renders the giscus div when frontmatter.comments is true", () => {
|
||||
const result = renderComments(baseOpts, buildProps({ frontmatter: { comments: true } }));
|
||||
expect(getDivProps(result)).not.toBeNull();
|
||||
});
|
||||
|
||||
it("renders the giscus div when frontmatter.comments is the string 'true'", () => {
|
||||
const result = renderComments(baseOpts, buildProps({ frontmatter: { comments: "true" } }));
|
||||
expect(getDivProps(result)).not.toBeNull();
|
||||
});
|
||||
|
||||
it("suppresses comments when frontmatter.comments is false", () => {
|
||||
const result = renderComments(baseOpts, buildProps({ frontmatter: { comments: false } }));
|
||||
expect(isFragmentOrEmpty(result)).toBe(true);
|
||||
expect(getDivProps(result)).toBeNull();
|
||||
});
|
||||
|
||||
it("suppresses comments when frontmatter.comments is the string 'false'", () => {
|
||||
const result = renderComments(baseOpts, buildProps({ frontmatter: { comments: "false" } }));
|
||||
expect(isFragmentOrEmpty(result)).toBe(true);
|
||||
expect(getDivProps(result)).toBeNull();
|
||||
});
|
||||
|
||||
it("renders the giscus div when frontmatter.comments is the number 0", () => {
|
||||
// 0 is falsy but not a documented disable value; it must not suppress comments.
|
||||
const result = renderComments(baseOpts, buildProps({ frontmatter: { comments: 0 } }));
|
||||
expect(getDivProps(result)).not.toBeNull();
|
||||
});
|
||||
|
||||
it("renders the giscus div when frontmatter.comments is the empty string", () => {
|
||||
// Empty string is falsy but not a documented disable value; it must not suppress comments.
|
||||
const result = renderComments(baseOpts, buildProps({ frontmatter: { comments: "" } }));
|
||||
expect(getDivProps(result)).not.toBeNull();
|
||||
});
|
||||
|
||||
it("renders the giscus div when frontmatter.comments is null", () => {
|
||||
// null is falsy but not a documented disable value; it must not suppress comments.
|
||||
const result = renderComments(baseOpts, buildProps({ frontmatter: { comments: null } }));
|
||||
expect(getDivProps(result)).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Comments: data attribute wiring", () => {
|
||||
it("propagates required giscus options", () => {
|
||||
const result = renderComments(baseOpts, buildProps());
|
||||
const props = getDivProps(result);
|
||||
expect(props).not.toBeNull();
|
||||
expect(props?.["data-repo"]).toBe("test/repo");
|
||||
expect(props?.["data-repo-id"]).toBe("test-id");
|
||||
expect(props?.["data-category"]).toBe("Announcements");
|
||||
expect(props?.["data-category-id"]).toBe("test-cat-id");
|
||||
});
|
||||
|
||||
it("applies default values when optional options are omitted", () => {
|
||||
const result = renderComments(baseOpts, buildProps());
|
||||
const props = getDivProps(result);
|
||||
expect(props?.["data-mapping"]).toBe("url");
|
||||
expect(props?.["data-strict"]).toBe("1");
|
||||
expect(props?.["data-reactions-enabled"]).toBe("1");
|
||||
expect(props?.["data-input-position"]).toBe("bottom");
|
||||
expect(props?.["data-light-theme"]).toBe("light");
|
||||
expect(props?.["data-dark-theme"]).toBe("dark");
|
||||
expect(props?.["data-lang"]).toBe("en");
|
||||
});
|
||||
|
||||
it("respects user-provided optional values", () => {
|
||||
const opts: CommentsOptions = {
|
||||
provider: "giscus",
|
||||
options: {
|
||||
...baseOpts.options,
|
||||
mapping: "pathname",
|
||||
strict: false,
|
||||
reactionsEnabled: false,
|
||||
inputPosition: "top",
|
||||
lightTheme: "catppuccin_latte",
|
||||
darkTheme: "catppuccin_mocha",
|
||||
lang: "nl",
|
||||
},
|
||||
};
|
||||
const result = renderComments(opts, buildProps());
|
||||
const props = getDivProps(result);
|
||||
expect(props?.["data-mapping"]).toBe("pathname");
|
||||
expect(props?.["data-strict"]).toBe("0");
|
||||
expect(props?.["data-reactions-enabled"]).toBe("0");
|
||||
expect(props?.["data-input-position"]).toBe("top");
|
||||
expect(props?.["data-light-theme"]).toBe("catppuccin_latte");
|
||||
expect(props?.["data-dark-theme"]).toBe("catppuccin_mocha");
|
||||
expect(props?.["data-lang"]).toBe("nl");
|
||||
});
|
||||
|
||||
it("uses cfg.baseUrl to build the default themeUrl", () => {
|
||||
const result = renderComments(baseOpts, buildProps({ cfg: { baseUrl: "example.test" } }));
|
||||
const props = getDivProps(result);
|
||||
expect(props?.["data-theme-url"]).toBe("https://example.test/static/giscus");
|
||||
});
|
||||
|
||||
it("falls back when cfg.baseUrl is missing", () => {
|
||||
const result = renderComments(baseOpts, buildProps({ cfg: {} }));
|
||||
const props = getDivProps(result);
|
||||
expect(props?.["data-theme-url"]).toBe("https://example.com/static/giscus");
|
||||
});
|
||||
|
||||
it("honours an explicit themeUrl override", () => {
|
||||
const opts: CommentsOptions = {
|
||||
provider: "giscus",
|
||||
options: { ...baseOpts.options, themeUrl: "https://cdn.test/theme" },
|
||||
};
|
||||
const result = renderComments(opts, buildProps({ cfg: { baseUrl: "example.test" } }));
|
||||
const props = getDivProps(result);
|
||||
expect(props?.["data-theme-url"]).toBe("https://cdn.test/theme");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue