Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 36 additions & 36 deletions migration/pages.csv

Large diffs are not rendered by default.

48 changes: 24 additions & 24 deletions migration/src/menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,37 +9,37 @@ pub async fn migrate_menu(db: &Database) -> anyhow::Result<()> {
slug: "default-nav".to_string(),
items: serde_json::from_str(
"[
{\"label\":\"Home\",\"path\":\"/\",\"children\":[]},
{\"label\":\"Tools\",\"path\":\"/tools\",\"children\":[
{\"label\":\"Glossary of Terms\",\"path\":\"/glossary\"},
{\"label\":\"Word Search\",\"path\":\"/search\"},
{\"label\":\"Further Learning\",\"path\":\"/credit\"}
{\"label\":\"Home\", \"path\":\"/\",\"children\":[]},
{\"label\":\"Tools\", \"path\":\"/tools\",\"children\":[
{\"label\":\"Further Learning\", \"path\":\"/tools/further-learning\"}
]},
{\"label\":\"About\",\"path\":\"/\",\"children\":[
{\"label\":\"Support\",\"path\":\"/support\"},
{\"label\":\"Goals\",\"path\":\"/home/goals\"},
{\"label\":\"Team\",\"path\":\"/home/credits\"},
{\"label\":\"Former Contributors\",\"path\":\"/former-contributors\"},
{\"label\":\"Project History\",\"path\":\"/home/project-history\"},
{\"label\":\"Why DAILP? Why Now?\",\"path\":\"/home/why-this-archive-why-now\"},
{\"label\":\"References\",\"path\":\"/sources\"}
{\"label\":\"About\", \"path\":\"/\",\"children\":[
{\"label\":\"Support\", \"path\":\"/about/support\"},
{\"label\":\"Goals\", \"path\":\"/about/goals\"},
{\"label\":\"Team\", \"path\":\"/about/team\"},
{\"label\":\"Former Contributors\", \"path\":\"/about/former-contributors\"},
{\"label\":\"Project History\", \"path\":\"/about/project-history\"},
{\"label\":\"Why DAILP? Why Now?\", \"path\":\"/about/why-this-archive-why-now\"},
{\"label\":\"References\", \"path\":\"/about/sources\"}
]},
{\"label\":\"Stories\",\"path\":\"/stories\",\"children\":[
{\"label\":\"Dollie Duncan Letter Inspires Opera\",\"path\":\"/dollie-duncan-letter-inspires-opera\"},
{\"label\":\"About the Editors of CWKW\",\"path\":\"/editors\"},
{\"label\":\"The Importance of Language Persistence\",\"path\":\"/dailp-language-preservation-narrative\"},
{\"label\":\"UKB Advisory Board Members & Translators Discover Family Voices in DAILP’s Work\",\"path\":\"/ukb-advisory-board-members-translators-discover-family-voices-in-dailps-work\"},
{\"label\":\"DAILP honored with grant from the Henry R. Luce Foundation\",\"path\":\"/stories/the-luce-foundation/dailp-honored-with-grant-from-the-henry-r-luce-foundation\"}
{\"label\":\"Stories\", \"path\":\"/\",\"children\":[
{\"label\":\"Dollie Duncan Letter Inspires Opera\", \"path\":\"/story/dollie-duncan-letter-inspires-opera\"},
{\"label\":\"About the Editors of CWKW\", \"path\":\"/story/editors-of-cwkw\"},
{\"label\":\"The Importance of Language Persistence\", \"path\":\"/story/the-importance-of-language-persistence\"},
{\"label\":\"UKB Advisory Board Members & Translators Discover Family Voices in DAILP’s Work\",\"path\":\"/story/ukb-advisory-board-members-translators-discover-family-voices-in-dailps-work\"},
{\"label\":\"DAILP honored with grant from the Henry R. Luce Foundation\",\"path\":\"/story/dailp-honored-with-grant-from-the-henry-r-luce-foundation\"}
]},
{\"label\":\"Spotlights\",\"path\":\"/category/uncategorized\",\"children\":[
{\"label\":\"DAILP Spotlight: Melissa Torres\",\"path\":\"/stories/melissa-torres\"},
{\"label\":\"DAILP Spotlight: Hazelyn Aroian\",\"path\":\"/dailp-spotlight-hazelyn-aroian\"},
{\"label\":\"DAILP Spotlight: Meg Cassidy\",\"path\":\"/dailp-spotlight-meg-cassidy\"},
{\"label\":\"DAILP Spotlight: Milan Evans\",\"path\":\"/dailp-spotlight-milan-evans\"},
{\"label\":\"Persisting in Cherokee with Mary Rae\",\"path\":\"/persisting-in-cherokee-with-mary-%ea%ae%8a%ea%ae%85-rae\"}
{\"label\":\"Spotlights\", \"path\":\"/\",\"children\":[
{\"label\":\"DAILP Spotlight: Melissa Torres\", \"path\":\"/spotlight/melissa-torres\"},
{\"label\":\"DAILP Spotlight: Hazelyn Aroian\", \"path\":\"/spotlight/hazelyn-aroian\"},
{\"label\":\"DAILP Spotlight: Meg Cassidy\", \"path\":\"/spotlight/meg-cassidy\"},
{\"label\":\"DAILP Spotlight: Milan Evans\", \"path\":\"/spotlight/milan-evans\"},
{\"label\":\"Persisting in Cherokee with Mary Rae\", \"path\":\"/spotlight/persisting-in-cherokee-with-mary-rae\"}

]}
]").unwrap()};

db.insert_menu(menu).await?;
Ok(())
}
26 changes: 17 additions & 9 deletions migration/src/pages.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
use csv::ReaderBuilder;
use dailp::page::ContentBlock;
use dailp::page::Markdown;
use dailp::page::NewPageInput;
use dailp::{page::Page, Database};
use dailp::Database;
use serde::Deserialize;
use std::fs::File;

// CSV row structure (includes all fields even though we skip some)
/// CSV row structure, used to load pages.csv data into a vector
#[derive(Debug, Deserialize)]
struct CsvRow {
#[serde(rename = "page_id")]
Expand Down Expand Up @@ -36,6 +34,17 @@ pub fn load_pages(file_path: &str) -> Result<Vec<NewPageInput>, anyhow::Error> {
let mut reader = ReaderBuilder::new().from_reader(file);

let mut pages = Vec::new();
for result in reader.deserialize() {
let row: CsvRow = result?;
//println!("row: {:?}", row);
let page: NewPageInput = NewPageInput {
title: row.title,
body: vec![row.content],
path: row.path,
};
//println!("result: {:?}", page);
pages.push(page);
}
for (idx, result) in reader.deserialize::<CsvRow>().enumerate() {
match result {
Ok(row) => {
Expand All @@ -59,11 +68,10 @@ pub fn load_pages(file_path: &str) -> Result<Vec<NewPageInput>, anyhow::Error> {
}

pub async fn migrate_pages(db: &Database) -> anyhow::Result<()> {
//println!("Migrating pages...");
let pages = load_pages("pages.csv")?;
//for page in pages {
//db.insert_page(NewPageInput::from(page.clone())).await?;
//}
// Resolve pages.csv relative to this crate's directory so running from target/ works
let csv_path = concat!(env!("CARGO_MANIFEST_DIR"), "/pages.csv");
let pages = load_pages(csv_path)?;

for page in pages {
db.upsert_page(NewPageInput::from(page.clone())).await?;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
-- Add migration script here
ALTER TABLE edited_collection
ADD COLUMN description TEXT,
ADD COLUMN thumbnail_url TEXT;
ADD COLUMN IF NOT EXISTS description TEXT,
ADD COLUMN IF NOT EXISTS thumbnail_url TEXT;
2 changes: 1 addition & 1 deletion types/src/database_sql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1932,7 +1932,7 @@ impl Database {
Ok(None)
}
}
//Ok(page)

pub async fn get_menu_by_slug(&self, slug: String) -> Result<Menu> {
let menu = query_file!("queries/menu_by_slug.sql", slug)
.fetch_one(&self.client)
Expand Down
1 change: 1 addition & 0 deletions website/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
"react-dom": "17",
"react-helmet": "^6.0.0",
"react-icons": "^4.7",
"react-markdown": "7",
"react-select": "^5.7.7",
"react-zoom-pan-pinch": "^2.1.3",
"reakit": "^1.3.11",
Expand Down
2 changes: 1 addition & 1 deletion website/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ export { AudioPlayer } from "./audio-player"
export { Breadcrumbs } from "./breadcrumbs"
export { Carousel } from "./carousel"
export { CreativeCommonsBy } from "./attributions/creative-commons"
export { default as WordpressPage, WordpressPageContents } from "./wordpress"
export { default as WordpressPage, PageContents } from "./wordpress"
export { Card } from "./card"
8 changes: 2 additions & 6 deletions website/src/components/wordpress.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const WordpressPage = ({ slug }: Props) => {
const wpPage = data?.page?.__typename === "Page" && data.page

if (wpPage) {
return <WordpressPageContents content={wpPage.content} />
return <PageContents content={wpPage.content} />
} else if (fetching) {
return (
<div>
Expand All @@ -50,11 +50,7 @@ const WordpressPage = ({ slug }: Props) => {

export default WordpressPage

export const WordpressPageContents = ({
content,
}: {
content: string | null
}) => {
export const PageContents = ({ content }: { content: string | null }) => {
const { "*": slug } = useRouteParams()

let parsed
Expand Down
76 changes: 76 additions & 0 deletions website/src/pages/dailp.page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import React from "react"
import { navigate } from "vite-plugin-ssr/client/router"
import { UserRole, useUserRole } from "src/auth"
import { Link } from "src/components"
import { PageContents } from "src/components/wordpress"
import { useMenuBySlugQuery, usePageByPathQuery } from "src/graphql/dailp"
import { edgePadded, fullWidth } from "src/style/utils.css"
import Layout from "../layout"

interface DailpPageProps {
"*": string
}

const DailpPage = (props: DailpPageProps) => (
<Layout>
<main className={edgePadded}>
<article className={fullWidth}>
<Contents path={"/" + props["*"]} />
</article>
</main>
</Layout>
)

export const Page = DailpPage

const Contents = (props: { path: string }) => {
const [{ data, fetching }] = usePageByPathQuery({
variables: { path: props.path },
})

const [{ data: menuData }] = useMenuBySlugQuery({
variables: { slug: "default-nav" },
pause: fetching, // Don't fetch menu until page query is complete
})

const page = data?.pageByPath
const menu = menuData?.menuBySlug
const firstBlock = page?.body?.[0]
const content =
firstBlock?.__typename === "Markdown" ? firstBlock.content : null

const isInMenu = (slug: string) => {
return (
menu?.items
?.flatMap((item) => item.items)
.some((item) => item?.path === slug) ?? false
)
}

if (fetching) {
return <p>Loading...</p>
}

if (!page || !content) {
return <p>Page content not found.</p>
}

if (!isInMenu(props.path)) {
return <p>Page content found. Add it to the menu to view it.</p>
}

const userRole = useUserRole()

return (
<>
<header>
<h1>{page.title}</h1>
{/* dennis todo: should be admin in the future */}
{userRole === UserRole.Editor && (
<Link href={`/edit${props.path}`}>Edit</Link>
)}
</header>
<PageContents content={content} />
</>
)
}
1 change: 1 addition & 0 deletions website/src/pages/edit.page.route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default "/edit/*"
145 changes: 145 additions & 0 deletions website/src/pages/edit.page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import React, { useEffect, useState } from "react"
import Markdown from "react-markdown"
import { navigate } from "vite-plugin-ssr/client/router"
import { UserRole } from "src/auth"
import { AuthGuard } from "src/components/auth-guard"
import { PageContents } from "src/components/wordpress"
import { usePageByPathQuery, useUpsertPageMutation } from "src/graphql/dailp"
import Layout from "src/layout"
import { useRouteParams } from "src/renderer/PageShell"
import { DELIM, splitMarkdown } from "./page-by-name.page"

const NewPage = () => {
const [title, setTitle] = useState("")
const [content, setContent] = useState("")
const [error, setError] = useState<string | null>(null)
const [path, setPath] = useState("/" + useRouteParams()["*"])
const formatPath = (path: string) => {
return path.startsWith("/")
? path
: `/${path}`
.toLowerCase()
.replace(/ /g, "-")
.replace(/[^a-z0-9-]/g, "")
}

const [{ data }, reexec] = usePageByPathQuery({
variables: { path },
requestPolicy: "network-only",
})
useEffect(() => {
if (data?.pageByPath?.body?.[0]?.__typename === "Markdown") {
setTitle(data?.pageByPath?.title ?? "")
setContent(data?.pageByPath?.body?.[0]?.content ?? "")
}
}, [data])

const [_, upsertPage] = useUpsertPageMutation()
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
if (content.length === 0 || title.length === 0 || path.length === 0) {
setError("Please fill in all fields")
return
}
// check if path is poorly formatted
setPath(formatPath(path))

reexec({ variables: { path } })
if (data?.pageByPath) {
const confirm = window.confirm(
"Page already exists. Would you like to overwrite it?"
)
if (!confirm) {
return
}
}
e.preventDefault()

if (error !== null) {
alert("error: " + error)
return
}
upsertPage({ pageInput: { title, body: [content], path: path } }).then(
(res) => {
if (res.error) {
setError(res.error.message)
} else {
setError(null)
navigate(`/${path}`)
}
}
)
}

const isHtml = content.charAt(0) === "<"

return (
<AuthGuard requiredRole={UserRole.Editor}>
<Layout>
<main>
<h1>New content page</h1>
{error && <p>{error}</p>}
<form
onSubmit={handleSubmit}
style={{
display: "flex",
flexDirection: "column",
gap: "5px",
alignItems: "center",
}}
>
<input
style={{ width: "50%" }}
type="text"
placeholder="path"
value={path}
onChange={(e) => {
setPath(e.target.value)
}}
/>
<br />
<input
style={{ width: "50%" }}
type="text"
placeholder="title"
value={title}
onChange={(e) => {
setTitle(e.target.value)
}}
/>
<br />
<div style={{ display: "flex", flexDirection: "row", gap: "10px" }}>
<textarea
style={{ width: "50%" }}
placeholder="content"
value={content}
onChange={(e) => {
setContent(e.target.value)
}}
/>
<div
style={{
border: "1px solid black",
padding: "10px",
width: "50%",
}}
>
{isHtml ? (
<div dangerouslySetInnerHTML={{ __html: content }} />
) : (
<Markdown>{content}</Markdown>
)}
</div>
</div>
<button type="submit" style={{ width: "50%" }}>
Save
</button>
</form>
</main>
</Layout>
</AuthGuard>
)
}

// preview content component

export const Page = NewPage
1 change: 1 addition & 0 deletions website/src/pages/page-by-name.page.route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default "/pages/@pageName"
Loading