Skip to the content

Capsid menu

app/routes/admin.posts.new.tsx

85 lines and 10 functions in DrDustinEdwards/dustinedwards-info, at commit 1590ba4 on 2026-10-24. This file on GitHub.

Functions

The functions of admin.posts.new.tsx
FunctionLinesComplexityCoveredCRAPMutation
meta20 to 221No data: no coverage report in this mapNo data: no coverage report in this mapNo data: no mutants in this code
loader24 to 551No data: no coverage report in this mapNo data: no coverage report in this mapNo data: no mutants in this code
<anonymous:25>25 to 541No data: no coverage report in this mapNo data: no coverage report in this mapNo data: no mutants in this code
<anonymous:29>29 to 291No data: no coverage report in this mapNo data: no coverage report in this mapNo data: no mutants in this code
failed31 to 351No data: no coverage report in this mapNo data: no coverage report in this mapNo data: no mutants in this code
<anonymous:31>31 to 351No data: no coverage report in this mapNo data: no coverage report in this mapNo data: no mutants in this code
<anonymous:44>44 to 471No data: no coverage report in this mapNo data: no coverage report in this mapNo data: no mutants in this code
<anonymous:49>49 to 491No data: no coverage report in this mapNo data: no coverage report in this mapNo data: no mutants in this code
action58 to 622No data: no coverage report in this mapNo data: no coverage report in this mapNo data: no mutants in this code
NewPost64 to 958No data: no coverage report in this mapNo data: no coverage report in this mapNo data: no mutants in this code

Imports

It imports app/components/admin/post-editor.tsx, app/db/index.ts, app/lib/admin-actor.server.ts, app/lib/context.ts, app/lib/editor/action.server.ts, app/lib/editor/feedback.ts, app/lib/editor/frontmatter.ts, app/lib/editor/head.server.ts, app/lib/editor/link-targets.server.ts, app/lib/error-message.mjs, app/lib/timing.ts. No module of this repo imports it.

Source

import { data, redirect } from "react-router";

import { timed, timedLoader } from "~/lib/timing";

import { PostEditor } from "~/components/admin/post-editor";
import { listAllPostsForAdmin } from "~/db";
import { getEnv } from "~/lib/context";
import { loadEditorOptions } from "~/lib/editor/link-targets.server";
import { handleEditorAction } from "~/lib/editor/action.server";
import { savedRedirectPath } from "~/lib/editor/feedback";
import { EMPTY_FIELDS } from "~/lib/editor/frontmatter";
import { readHead } from "~/lib/editor/head.server";
import { adminActorContext } from "~/lib/admin-actor.server";
import type { Route } from "./+types/admin.posts.new";
import { errorMessage } from "~/lib/error-message.mjs";

/* Always: the exact-preview pane copies this document's stylesheets into its iframe. */
export const handle = { math: true };

export function meta() {
  return [{ title: "New post ยท Admin" }, { name: "robots", content: "noindex" }];
}

export async function loader({ context }: Route.LoaderArgs) {
  return timedLoader(context, async (timings) => {
    const env = getEnv(context);
    const today = new Date().toISOString().slice(0, 10);
    // A broken token must not blank the page: preview does not touch GitHub, and the editor names the failure.
    const { headSha, headError } = await timed(timings, "gh_head", () => readHead(env));
    const { tagOptions, linkTargets, problems: loadProblems } = await loadEditorOptions(env, timings);
    const failed = (what: string, sentence: string) => (error: unknown) => {
      console.error(JSON.stringify({ alert: "editor-read-failed", what, detail: errorMessage(error) }));
      loadProblems.push(`${sentence}: ${errorMessage(error)}`);
      return [];
    };
    const payload = {
      headSha,
      headError,
      fields: { ...EMPTY_FIELDS, date: today },
      tagOptions,
      linkTargets,
      // The save gate remains the authority; this only saves a wasted submit.
      existingSlugs: (
        await timed(timings, "d1_all_posts", () =>
          listAllPostsForAdmin(env).catch(
            failed("posts", "Existing posts could not be read, so a taken slug is only caught on save"),
          ),
        )
      ).map((post) => post.slug),
      loadProblems,
    };

    return data(payload);
  });
}


export async function action({ request, context }: Route.ActionArgs) {
  const result = await handleEditorAction(getEnv(context), request, context.get(adminActorContext));
  if (result.kind === "saved") return redirect(savedRedirectPath(result));
  return result;
}

export default function NewPost({ loaderData, actionData }: Route.ComponentProps) {
  const fields = actionData && "fields" in actionData ? actionData.fields : loaderData.fields;
  const headSha = actionData && "headSha" in actionData ? actionData.headSha : loaderData.headSha;

  return (
    <PostEditor
      fields={fields}
      isNew
      headSha={headSha}
      headError={headSha ? null : loaderData.headError}
      loadProblems={loaderData.loadProblems}
      previewHtml={actionData?.kind === "preview" ? actionData.previewHtml : null}
      feedback={
        actionData?.kind === "problem"
          ? {
              state: "failed",
              message: actionData.problem.message,
              conflict: Boolean(actionData.problem.conflict),
              field: actionData.problem.field,
              line: actionData.problem.line,
            }
          : null
      }
      state="draft"
      everPublished={false}
      awaitingPublishConfirmation={actionData?.kind === "confirm-publish"}
      tagOptions={loaderData.tagOptions}
      linkTargets={loaderData.linkTargets}
      existingSlugs={loaderData.existingSlugs}
    />
  );
}