diff --git a/crates/vendor/tauri-plugin-http/permissions/schemas/schema.json b/crates/vendor/tauri-plugin-http/permissions/schemas/schema.json index ea774399d5..31640bee16 100644 --- a/crates/vendor/tauri-plugin-http/permissions/schemas/schema.json +++ b/crates/vendor/tauri-plugin-http/permissions/schemas/schema.json @@ -343,10 +343,10 @@ "markdownDescription": "Denies the fetch_send command without any pre-configured scope." }, { - "description": "This permission set configures what kind of\nfetch operations are available from the http plugin.\n\nThis enables all fetch operations but does not\nallow explicitly any origins to be fetched. This needs to\nbe manually configured before usage.\n\n#### Granted Permissions\n\nAll fetch operations are enabled.\n\n\n#### This default permission set includes:\n\n- `allow-fetch`\n- `allow-fetch-cancel`\n- `allow-fetch-read-body`\n- `allow-fetch-send`", + "description": "This permission set configures what kind of\r\nfetch operations are available from the http plugin.\r\n\r\nThis enables all fetch operations but does not\r\nallow explicitly any origins to be fetched. This needs to\r\nbe manually configured before usage.\r\n\r\n#### Granted Permissions\r\n\r\nAll fetch operations are enabled.\r\n\r\n\n#### This default permission set includes:\n\n- `allow-fetch`\n- `allow-fetch-cancel`\n- `allow-fetch-read-body`\n- `allow-fetch-send`", "type": "string", "const": "default", - "markdownDescription": "This permission set configures what kind of\nfetch operations are available from the http plugin.\n\nThis enables all fetch operations but does not\nallow explicitly any origins to be fetched. This needs to\nbe manually configured before usage.\n\n#### Granted Permissions\n\nAll fetch operations are enabled.\n\n\n#### This default permission set includes:\n\n- `allow-fetch`\n- `allow-fetch-cancel`\n- `allow-fetch-read-body`\n- `allow-fetch-send`" + "markdownDescription": "This permission set configures what kind of\r\nfetch operations are available from the http plugin.\r\n\r\nThis enables all fetch operations but does not\r\nallow explicitly any origins to be fetched. This needs to\r\nbe manually configured before usage.\r\n\r\n#### Granted Permissions\r\n\r\nAll fetch operations are enabled.\r\n\r\n\n#### This default permission set includes:\n\n- `allow-fetch`\n- `allow-fetch-cancel`\n- `allow-fetch-read-body`\n- `allow-fetch-send`" } ] } diff --git a/frontend/apps/artcraft-website/_redirects b/frontend/apps/artcraft-website/_redirects index 743c9077fe..112c1a15fc 100644 --- a/frontend/apps/artcraft-website/_redirects +++ b/frontend/apps/artcraft-website/_redirects @@ -12,5 +12,11 @@ /faq/ /index.html 200 /faq/* /index.html 200 +/news /index.html 200 +/news/ /index.html 200 +/news/* /index.html 200 + # Redirects for growth measurement /reddit /?utm_source=reddit 301 + +/news.json /index.html 200 \ No newline at end of file diff --git a/frontend/apps/artcraft-website/public/news.json b/frontend/apps/artcraft-website/public/news.json new file mode 100644 index 0000000000..f33ff47216 --- /dev/null +++ b/frontend/apps/artcraft-website/public/news.json @@ -0,0 +1,9 @@ +[ + { + "slug": "welcome", + "title": "Welcome to the ArtCraft Blog", + "description": "We are excited to launch our new blog where we will share updates, tutorials, and behind-the-scenes content.", + "date": "2026-01-16", + "body": "\n# Hello World!\n\nWelcome to the official **ArtCraft Blog**. This is where we'll be posting about:\n\n- **New Features**: Be the first to know about the latest tools and capabilities added to ArtCraft.\n- **Tutorials**: Learn how to get the most out of our AI generation and editing suite.\n- **Community Showcases**: Highlighting amazing work created by our users.\n- **Development Updates**: Insights into what we are building next.\n\n## Stay Tuned\n\nWe have a lot of exciting things in the pipeline. Make sure to check back regularly!\n\nHappy Crafting!\nThe ArtCraft Team\n" + } +] \ No newline at end of file diff --git a/frontend/apps/artcraft-website/src/app/app.tsx b/frontend/apps/artcraft-website/src/app/app.tsx index 85c22e2e20..188fe5634f 100644 --- a/frontend/apps/artcraft-website/src/app/app.tsx +++ b/frontend/apps/artcraft-website/src/app/app.tsx @@ -7,6 +7,7 @@ import TutorialsPage from "../pages/tutorials"; import TutorialsArticle from "../pages/tutorials/article"; import FaqIndex from "../pages/faq/index"; import FaqArticle from "../pages/faq/article"; +import { NewsIndex, NewsPost } from "@storyteller/markdown-content"; export function App() { return ( @@ -22,6 +23,8 @@ export function App() { } /> } /> } /> + } /> + } /> ); diff --git a/frontend/apps/artcraft-website/src/components/footer/footer.tsx b/frontend/apps/artcraft-website/src/components/footer/footer.tsx index 9578b7abb7..36d3f0e315 100644 --- a/frontend/apps/artcraft-website/src/components/footer/footer.tsx +++ b/frontend/apps/artcraft-website/src/components/footer/footer.tsx @@ -6,19 +6,10 @@ import { faGithubAlt, } from "@fortawesome/free-brands-svg-icons"; import { SOCIAL_LINKS } from "../../config/links"; -import { parseFrontmatter, pathToFilename } from "../../utils/markdown"; +import { getFaqItems, getTutorialItems } from "@storyteller/markdown-content"; import { Link } from "react-router-dom"; -const faqFiles = import.meta.glob("../../pages/faq/content/*.md", { - query: "?raw", - import: "default", - eager: true, -}); -const tutorialFiles = import.meta.glob("../../pages/tutorials/content/*.md", { - query: "?raw", - import: "default", - eager: true, -}); +// Imports removed. const navigation = { main: [ @@ -100,23 +91,24 @@ export default function Example() { Home + + News +
Tutorials
All Tutorials - {Object.entries(tutorialFiles).map(([path, raw]) => { - const { frontmatter } = parseFrontmatter(raw as string); - const slug = pathToFilename(path); - if (frontmatter.isPublished === "false") return null; + {getTutorialItems().map((item) => { + if (!item.isPublished) return null; return ( - {frontmatter.title || slug} + {item.title} ); })} @@ -126,17 +118,14 @@ export default function Example() { All FAQs - {Object.entries(faqFiles).map(([path, raw]) => { - const { frontmatter } = parseFrontmatter(raw as string); - const slug = pathToFilename(path); - if (frontmatter.isPublished === "false") return null; - const title = (frontmatter.title || slug) as string; + {getFaqItems().map((item) => { + if (!item.isPublished) return null; const truncated = - title.length > 36 ? title.slice(0, 33) + "…" : title; + item.title.length > 36 ? item.title.slice(0, 33) + "…" : item.title; return ( {truncated} diff --git a/frontend/apps/artcraft-website/src/components/navbar/navbar.tsx b/frontend/apps/artcraft-website/src/components/navbar/navbar.tsx index 9210e619c6..fe3f96da33 100644 --- a/frontend/apps/artcraft-website/src/components/navbar/navbar.tsx +++ b/frontend/apps/artcraft-website/src/components/navbar/navbar.tsx @@ -7,6 +7,7 @@ import { DiscordButton } from "../discord-button"; const NAV_ITEMS = [ { name: "Home", href: "/" }, { name: "Tutorials", href: "/tutorials" }, + { name: "News", href: "/news" }, { name: "FAQ", href: "/faq" }, ]; diff --git a/frontend/apps/artcraft-website/src/pages/faq/article.tsx b/frontend/apps/artcraft-website/src/pages/faq/article.tsx index fe91c9d99c..5676d65ffe 100644 --- a/frontend/apps/artcraft-website/src/pages/faq/article.tsx +++ b/frontend/apps/artcraft-website/src/pages/faq/article.tsx @@ -1,42 +1,19 @@ -import { useMemo } from "react"; import { Link, useParams } from "react-router-dom"; import Seo from "../../components/seo"; -import { parseFrontmatter, markdownToHtml } from "../../utils/markdown"; +import { getFaqItemBySlug, markdownToHtml } from "@storyteller/markdown-content"; import { faChevronLeft } from "@fortawesome/pro-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; - -const markdownFiles = import.meta.glob("./content/*.md", { - query: "?raw", - import: "default", - eager: true, -}); +import { useMemo } from "react"; const FaqArticle = () => { const { slug } = useParams(); - const entries = Object.entries(markdownFiles); - const markdown = (() => { - const byFilename = entries.find(([path]) => - path.endsWith(`/content/${slug}.md`) - ); - if (byFilename) return byFilename[1] as string; - for (const [_, raw] of entries) { - const { frontmatter } = parseFrontmatter(raw as string); - if ( - frontmatter.slug && - frontmatter.slug.trim().toLowerCase() === (slug || "").toLowerCase() - ) { - return raw as string; - } - } - return null; - })(); - const { frontmatter, body } = useMemo( - () => - markdown ? parseFrontmatter(markdown) : { frontmatter: {}, body: "" }, - [markdown] - ); + const item = slug ? getFaqItemBySlug(slug) : null; - if (!markdown) { + const html = useMemo(() => + item ? markdownToHtml(item.body, "/faq/") : "" + , [item]); + + if (!item) { return (
@@ -47,16 +24,16 @@ const FaqArticle = () => { ); } - const title = `${frontmatter.title || slug} - ArtCraft`; - const description = frontmatter.abstract || ""; - const thumbnail = frontmatter.thumbnail || ""; - const html = markdownToHtml(body, "/faq/"); + const title = `${item.title} - ArtCraft`; + const description = item.description || ""; + const thumbnail = item.thumbnail || ""; + const jsonLd = { "@context": "https://schema.org", "@type": "Article", - headline: frontmatter.title || slug, + headline: item.title, description: description, - articleBody: body, + articleBody: item.body, }; return ( @@ -79,7 +56,7 @@ const FaqArticle = () => {

- {frontmatter.title || slug} + {item.title || slug}

{description &&

{description}

} @@ -87,7 +64,7 @@ const FaqArticle = () => {
{frontmatter.title
diff --git a/frontend/apps/artcraft-website/src/pages/faq/index.tsx b/frontend/apps/artcraft-website/src/pages/faq/index.tsx index 34a8d76352..3a5dae2d78 100644 --- a/frontend/apps/artcraft-website/src/pages/faq/index.tsx +++ b/frontend/apps/artcraft-website/src/pages/faq/index.tsx @@ -1,27 +1,15 @@ import { Link } from "react-router-dom"; import Seo from "../../components/seo"; -import { parseFrontmatter, pathToFilename } from "../../utils/markdown"; - -const faqFiles = import.meta.glob("./content/*.md", { - query: "?raw", - import: "default", - eager: true, -}); +import { getFaqItems } from "@storyteller/markdown-content"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faArrowRight } from "@fortawesome/pro-solid-svg-icons"; const FaqIndex = () => { const title = "Frequently Asked Questions - ArtCraft"; const description = "Explore ArtCraft FAQs: guides on AI image generation, editing, and workflows."; - const items = Object.entries(faqFiles).map(([path, raw]) => { - const { frontmatter } = parseFrontmatter(raw as string); - const slug = pathToFilename(path); - return { - slug, - title: frontmatter.title || slug, - description: frontmatter.abstract || "", - }; - }); + const items = getFaqItems(); const jsonLd = { "@context": "https://schema.org", @@ -59,9 +47,12 @@ const FaqIndex = () => { -

{item.title}

+

+ {item.title} + +

{item.description}

))} diff --git a/frontend/apps/artcraft-website/src/pages/index.ts b/frontend/apps/artcraft-website/src/pages/index.ts index 29627e4d79..5985fc1f24 100644 --- a/frontend/apps/artcraft-website/src/pages/index.ts +++ b/frontend/apps/artcraft-website/src/pages/index.ts @@ -2,6 +2,6 @@ export * from "./landing"; export * from "./landing2"; export * from "./download"; export * from "./tutorials"; -export { default as TutorialsIndex } from "./tutorials/md-index"; +export { default as TutorialsIndex } from "./tutorials/tutorials"; export { default as TutorialsArticle } from "./tutorials/article"; export * from "./faq"; diff --git a/frontend/apps/artcraft-website/src/pages/tutorials/article.tsx b/frontend/apps/artcraft-website/src/pages/tutorials/article.tsx index 200a30c2d7..59b83ba733 100644 --- a/frontend/apps/artcraft-website/src/pages/tutorials/article.tsx +++ b/frontend/apps/artcraft-website/src/pages/tutorials/article.tsx @@ -1,50 +1,20 @@ import { useMemo } from "react"; import { Link, useParams } from "react-router-dom"; import Seo from "../../components/seo"; -import { parseFrontmatter, markdownToHtml } from "../../utils/markdown"; +import { getTutorialItemBySlug, markdownToHtml } from "@storyteller/markdown-content"; import { faChevronLeft } from "@fortawesome/pro-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -const tutorialFiles = import.meta.glob("./content/*.md", { - query: "?raw", - import: "default", - eager: true, -}); - const TutorialsArticle = () => { const { slug } = useParams(); - const entries = Object.entries(tutorialFiles); - const markdown = (() => { - // 1) filename match - const byFilename = entries.find(([path]) => - path.endsWith(`/content/${slug}.md`) - ); - if (byFilename) return byFilename[1] as string; - // 2) frontmatter slug match - const wanted = (slug || "").toLowerCase(); - for (const [_, raw] of entries) { - const { frontmatter } = parseFrontmatter(raw as string); - const fmSlug = (frontmatter.slug || "").trim().toLowerCase(); - if (fmSlug && fmSlug === wanted) return raw as string; - const aliasesRaw = (frontmatter.aliases || "").toString(); - if (aliasesRaw) { - const candidateList = aliasesRaw - .split(/[\s,]+/) - .map((s) => s.trim().toLowerCase()) - .filter(Boolean); - if (candidateList.includes(wanted)) return raw as string; - } - } - return null; - })(); - const { frontmatter, body } = useMemo( - () => - markdown ? parseFrontmatter(markdown) : { frontmatter: {}, body: "" }, - [markdown] - ); + const item = slug ? getTutorialItemBySlug(slug) : null; - if (!markdown) { + const html = useMemo(() => + item ? markdownToHtml(item.body) : "" + , [item]); + + if (!item) { return (
@@ -55,8 +25,8 @@ const TutorialsArticle = () => { ); } - const title = `${frontmatter.title || slug} - ArtCraft`; - const description = frontmatter.abstract || ""; + const title = `${item.title} - ArtCraft`; + const description = item.abstract || ""; const toEmbed = (url: string): string => { if (!url) return url; if (url.includes("youtu.be/")) @@ -65,18 +35,17 @@ const TutorialsArticle = () => { return url; }; const frontmatterVideo = - (frontmatter.videoUrl as string) || - (frontmatter.youtubeId - ? `https://www.youtube.com/embed/${frontmatter.youtubeId}` + (item.videoUrl as string) || + (item.youtubeId + ? `https://www.youtube.com/embed/${item.youtubeId}` : ""); const videoUrl = toEmbed(frontmatterVideo); - const html = markdownToHtml(body); const jsonLd = { "@context": "https://schema.org", "@type": "Article", - headline: frontmatter.title || slug, + headline: item.title, description: description, - articleBody: body, + articleBody: item.body, }; return ( @@ -99,14 +68,14 @@ const TutorialsArticle = () => {

- {frontmatter.title || slug} + {item.title || slug}

{description &&

{description}

} {videoUrl && (