Compare commits
No commits in common. "90abfb6de874f6c2c27240627a0398c802e9e3fe" and "dfd9055318d3b7b61746974cd4a8df7e3d7a88da" have entirely different histories.
90abfb6de8
...
dfd9055318
12
TODO.md
12
TODO.md
@ -2,12 +2,14 @@
|
|||||||
|
|
||||||
- [x] chore: initial commit (+ mirror on GitHub)
|
- [x] chore: initial commit (+ mirror on GitHub)
|
||||||
- [x] Deploy first staging version (v1.0.0-staging.1)
|
- [x] Deploy first staging version (v1.0.0-staging.1)
|
||||||
- [ ] Implement Wikipedia Game Solver (`website`) with inputs, button to submit, and list all pages to go from one to another, or none if it is not possible
|
|
||||||
- [ ] Check, cache and store (in `.json` file) all Wikipedia Pages and its internal links, maybe use Wikipedia Dump (<https://en.wikipedia.org/wiki/Wikipedia:Database_download>)?
|
|
||||||
- [ ] Implement toast notifications for errors, warnings, and success messages
|
|
||||||
- [ ] Implement CLI (`cli`)
|
|
||||||
- [ ] Implement REST API (`api`) with JSON responses ([AdonisJS](https://adonisjs.com/))
|
|
||||||
- [ ] Add docs to add locale/edit translations, create component, install a dependency in a package, create a new package, technology used, architecture, links where it's deployed, how to use/install for end users, how to update dependencies with `npx taze -l` etc.
|
- [ ] Add docs to add locale/edit translations, create component, install a dependency in a package, create a new package, technology used, architecture, links where it's deployed, how to use/install for end users, how to update dependencies with `npx taze -l` etc.
|
||||||
|
- [ ] Implement Wikipedia Game Solver (`website`) with inputs, button to submit, and list all articles to go from one to another, or none if it is not possible
|
||||||
|
- [ ] v1.0.0-staging.2
|
||||||
|
- [ ] Implement CLI (`cli`)
|
||||||
|
- [ ] v1.0.0-staging.3
|
||||||
|
- [ ] Implement REST API (`api`) with JSON responses ([AdonisJS](https://adonisjs.com/))
|
||||||
|
- [ ] v1.0.0-staging.4
|
||||||
|
- [ ] v1.0.0
|
||||||
|
|
||||||
## Links
|
## Links
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node --import=tsx ./src/index.ts",
|
"start": "node --import=tsx ./src/index.ts",
|
||||||
"dev-test": "node --import=tsx --watch --watch-preserve-output ./src/index.ts",
|
"dev": "node --import=tsx --watch --watch-preserve-output ./src/index.ts",
|
||||||
"lint:eslint": "eslint src --max-warnings 0 --report-unused-disable-directives",
|
"lint:eslint": "eslint src --max-warnings 0 --report-unused-disable-directives",
|
||||||
"lint:typescript": "tsc --noEmit"
|
"lint:typescript": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
|
@ -1,26 +1,11 @@
|
|||||||
#!/usr/bin/env -S node --import=tsx
|
#!/usr/bin/env -S node --import=tsx
|
||||||
|
|
||||||
import { getWikipediaPageInternalLinks } from "@repo/wikipedia-game-solver/wikipedia-api"
|
import { add } from "#abc/def/add.js"
|
||||||
|
|
||||||
const localeWikipedia = "en"
|
import { VERSION } from "@repo/constants"
|
||||||
|
import { sum } from "@repo/wikipedia-game-solver/wikipedia-api"
|
||||||
|
|
||||||
const fromPageInput = "Linux"
|
console.log("Hello, world!")
|
||||||
const toPageInput = "Node.js"
|
console.log(sum(1, 2))
|
||||||
console.log({
|
console.log(add(2, 3))
|
||||||
fromPageInput,
|
console.log(`v${VERSION}`)
|
||||||
toPageInput,
|
|
||||||
})
|
|
||||||
const [fromPageWikipediaLinks, toPageWikipediaLinks] = await Promise.all([
|
|
||||||
getWikipediaPageInternalLinks({
|
|
||||||
title: fromPageInput,
|
|
||||||
locale: localeWikipedia,
|
|
||||||
}),
|
|
||||||
getWikipediaPageInternalLinks({
|
|
||||||
title: toPageInput,
|
|
||||||
locale: localeWikipedia,
|
|
||||||
}),
|
|
||||||
])
|
|
||||||
console.log({
|
|
||||||
fromPageWikipediaLinks,
|
|
||||||
toPageWikipediaLinks,
|
|
||||||
})
|
|
||||||
|
@ -1,11 +0,0 @@
|
|||||||
#!/usr/bin/env -S node --import=tsx
|
|
||||||
|
|
||||||
import { add } from "#abc/def/add.js"
|
|
||||||
|
|
||||||
import { VERSION } from "@repo/constants"
|
|
||||||
import { sum } from "@repo/wikipedia-game-solver/wikipedia-api"
|
|
||||||
|
|
||||||
console.log("Hello, world!")
|
|
||||||
console.log(sum(1, 2))
|
|
||||||
console.log(add(2, 3))
|
|
||||||
console.log(`v${VERSION}`)
|
|
@ -23,7 +23,6 @@ const config = {
|
|||||||
yellow: "#fef08a",
|
yellow: "#fef08a",
|
||||||
transparent: "transparent",
|
transparent: "transparent",
|
||||||
inherit: "inherit",
|
inherit: "inherit",
|
||||||
current: "currentColor",
|
|
||||||
},
|
},
|
||||||
fontFamily: {
|
fontFamily: {
|
||||||
sans: ["'Montserrat'", ...fontFamily.sans],
|
sans: ["'Montserrat'", ...fontFamily.sans],
|
||||||
|
@ -4,6 +4,3 @@ export const VERSION =
|
|||||||
process.env["NODE_ENV"] === "development"
|
process.env["NODE_ENV"] === "development"
|
||||||
? "0.0.0-development"
|
? "0.0.0-development"
|
||||||
: packageJSON.version
|
: packageJSON.version
|
||||||
|
|
||||||
export const GIT_REPO_LINK =
|
|
||||||
"https://git.theoludwig.fr/theoludwig/wikipedia-game-solver"
|
|
||||||
|
@ -18,6 +18,6 @@
|
|||||||
},
|
},
|
||||||
"home": {
|
"home": {
|
||||||
"title": "Wikipedia Game Solver",
|
"title": "Wikipedia Game Solver",
|
||||||
"description": "Le jeu Wikipédia implique des joueurs en compétition pour naviguer d'une page <wikipedia>Wikipedia</wikipedia> à une autre en utilisant uniquement des liens internes."
|
"description": "Le jeu Wikipédia implique des joueurs en compétition pour naviguer d'une page Wikipédia à une autre en utilisant uniquement des liens internes."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,6 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@repo/config-tailwind": "workspace:*",
|
"@repo/config-tailwind": "workspace:*",
|
||||||
"@repo/constants": "workspace:*",
|
|
||||||
"@repo/i18n": "workspace:*",
|
"@repo/i18n": "workspace:*",
|
||||||
"@repo/react-hooks": "workspace:*",
|
"@repo/react-hooks": "workspace:*",
|
||||||
"cva": "catalog:",
|
"cva": "catalog:",
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { useTranslations } from "next-intl"
|
import { useTranslations } from "next-intl"
|
||||||
|
|
||||||
import { GIT_REPO_LINK } from "@repo/constants"
|
|
||||||
import { Link } from "../design/Link/Link"
|
import { Link } from "../design/Link/Link"
|
||||||
|
|
||||||
export interface FooterProps {
|
export interface FooterProps {
|
||||||
@ -23,13 +22,9 @@ export const Footer: React.FC<FooterProps> = (props) => {
|
|||||||
|
|
||||||
<p>
|
<p>
|
||||||
Version{" "}
|
Version{" "}
|
||||||
<Link
|
<strong className="text-primary dark:text-primary-dark hover:underline">
|
||||||
href={`${GIT_REPO_LINK}/releases/tag/v${version}`}
|
|
||||||
target="_blank"
|
|
||||||
isExternal={false}
|
|
||||||
>
|
|
||||||
{version}
|
{version}
|
||||||
</Link>
|
</strong>
|
||||||
</p>
|
</p>
|
||||||
</footer>
|
</footer>
|
||||||
)
|
)
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { classNames } from "@repo/config-tailwind/classNames"
|
import { classNames } from "@repo/config-tailwind/classNames"
|
||||||
import type { Locale } from "@repo/i18n/config"
|
|
||||||
import { LOCALES } from "@repo/i18n/config"
|
import { LOCALES } from "@repo/i18n/config"
|
||||||
import { usePathname, useRouter } from "@repo/i18n/navigation"
|
import { usePathname, useRouter } from "@repo/i18n/navigation"
|
||||||
import { useLocale, useTranslations } from "next-intl"
|
import { useLocale, useTranslations } from "next-intl"
|
||||||
@ -9,7 +8,7 @@ import { useLocale, useTranslations } from "next-intl"
|
|||||||
export const Locales: React.FC = () => {
|
export const Locales: React.FC = () => {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const pathname = usePathname()
|
const pathname = usePathname()
|
||||||
const localeCurrent = useLocale() as Locale
|
const currentLocale = useLocale()
|
||||||
|
|
||||||
const t = useTranslations()
|
const t = useTranslations()
|
||||||
|
|
||||||
@ -22,7 +21,7 @@ export const Locales: React.FC = () => {
|
|||||||
key={locale}
|
key={locale}
|
||||||
className={classNames("rounded-md p-2", {
|
className={classNames("rounded-md p-2", {
|
||||||
"border-primary dark:border-primary-dark border":
|
"border-primary dark:border-primary-dark border":
|
||||||
locale === localeCurrent,
|
locale === currentLocale,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
|
@ -1,53 +1,21 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import type { Locale } from "@repo/i18n/config"
|
|
||||||
import { Button } from "@repo/ui/design/Button"
|
import { Button } from "@repo/ui/design/Button"
|
||||||
import { Link } from "@repo/ui/design/Link"
|
|
||||||
import { Typography } from "@repo/ui/design/Typography"
|
|
||||||
import { useLocale } from "next-intl"
|
|
||||||
import { useState } from "react"
|
import { useState } from "react"
|
||||||
import {
|
|
||||||
fromLocaleToWikipediaLocale,
|
const wait = async (ms: number): Promise<void> => {
|
||||||
getWikipediaLink,
|
return await new Promise((resolve) => {
|
||||||
getWikipediaPageInternalLinks,
|
setTimeout(resolve, ms)
|
||||||
} from "./wikipedia-api"
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export const WikipediaClient: React.FC = () => {
|
export const WikipediaClient: React.FC = () => {
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
|
|
||||||
const localeCurrent = useLocale() as Locale
|
|
||||||
const localeWikipedia = fromLocaleToWikipediaLocale(localeCurrent)
|
|
||||||
|
|
||||||
const handleClick: React.MouseEventHandler<HTMLButtonElement> = async () => {
|
const handleClick: React.MouseEventHandler<HTMLButtonElement> = async () => {
|
||||||
|
console.log("clicked")
|
||||||
setIsLoading(true)
|
setIsLoading(true)
|
||||||
const fromPageInput = "Linux"
|
await wait(2_000)
|
||||||
const toPageInput = "Node.js"
|
|
||||||
console.log({
|
|
||||||
fromPageInput,
|
|
||||||
toPageInput,
|
|
||||||
})
|
|
||||||
const [fromPageWikipediaLinks, toPageWikipediaLinks] = await Promise.all([
|
|
||||||
getWikipediaPageInternalLinks({
|
|
||||||
title: fromPageInput,
|
|
||||||
locale: localeWikipedia,
|
|
||||||
}),
|
|
||||||
getWikipediaPageInternalLinks({
|
|
||||||
title: toPageInput,
|
|
||||||
locale: localeWikipedia,
|
|
||||||
}),
|
|
||||||
])
|
|
||||||
console.log({
|
|
||||||
fromPageWikipediaLinks,
|
|
||||||
toPageWikipediaLinks,
|
|
||||||
})
|
|
||||||
// const deepInternalLinks = await getDeepWikipediaPageInternalLinks({
|
|
||||||
// locale: localeWikipedia,
|
|
||||||
// data: {
|
|
||||||
// [fromPageWikipediaLinks.title]: fromPageWikipediaLinks,
|
|
||||||
// [toPageWikipediaLinks.title]: toPageWikipediaLinks,
|
|
||||||
// },
|
|
||||||
// })
|
|
||||||
// console.log(deepInternalLinks)
|
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,12 +24,15 @@ export const WikipediaClient: React.FC = () => {
|
|||||||
<Button onClick={handleClick} isLoading={isLoading} className="w-36">
|
<Button onClick={handleClick} isLoading={isLoading} className="w-36">
|
||||||
Wikipedia
|
Wikipedia
|
||||||
</Button>
|
</Button>
|
||||||
<Typography variant="text1" as="p">
|
|
||||||
<span>Using: </span>
|
<Button
|
||||||
<Link href={getWikipediaLink(localeWikipedia)} target="_blank">
|
onClick={handleClick}
|
||||||
{getWikipediaLink(localeWikipedia).replace("https://", "")}
|
variant="outline"
|
||||||
</Link>
|
isLoading={isLoading}
|
||||||
</Typography>
|
className="w-36"
|
||||||
|
>
|
||||||
|
Wikipedia
|
||||||
|
</Button>
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,178 +1,28 @@
|
|||||||
import type { Locale } from "@repo/i18n/config"
|
|
||||||
|
|
||||||
export const sum = (a: number, b: number): number => {
|
export const sum = (a: number, b: number): number => {
|
||||||
return a + b
|
return a + b
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export const wikipediaAPIBaseURL = new URL("https://en.wikipedia.org/w/api.php")
|
||||||
* @see https://www.mediawiki.org/wiki/Wikimedia_REST_API#Terms_and_conditions
|
|
||||||
* To avoid impacting other API users, limit your clients to no more than 200 requests/sec to this API overall. Many entry points additionally specify and enforce more restrictive rate limits (HTTP 429 error).
|
|
||||||
*/
|
|
||||||
|
|
||||||
export const WIKIPEDIA_LOCALES = ["en", "fr"] as const
|
// const getWikipediaPageLinks = async (title: string): Promise<string[]> => {
|
||||||
export type WikipediaLocale = (typeof WIKIPEDIA_LOCALES)[number]
|
// const url = new URL(wikipediaAPIBaseURL)
|
||||||
|
// url.searchParams.append("action", "query")
|
||||||
|
// url.searchParams.append("titles", title)
|
||||||
|
// url.searchParams.append("prop", "links")
|
||||||
|
// url.searchParams.append("pllimit", "max")
|
||||||
|
// url.searchParams.append("format", "json")
|
||||||
|
// url.searchParams.append("origin", "*")
|
||||||
|
// const response = await fetch(url, {
|
||||||
|
// method: "GET",
|
||||||
|
// })
|
||||||
|
// if (!response.ok) {
|
||||||
|
// throw new Error(response.statusText)
|
||||||
|
// }
|
||||||
|
// const json = await response.json()
|
||||||
|
// return json
|
||||||
|
// }
|
||||||
|
|
||||||
const WIKIPEDIA_LOCALES_MAP: Record<Locale, WikipediaLocale> = {
|
// const links = await getWikipediaPageLinks("France")
|
||||||
"en-US": "en",
|
// const links2 = await getWikipediaPageLinks("Frddgdgdgance")
|
||||||
"fr-FR": "fr",
|
// console.log("links.length", links)
|
||||||
}
|
// console.log("links.length", links2)
|
||||||
|
|
||||||
export const fromLocaleToWikipediaLocale = (
|
|
||||||
locale: Locale,
|
|
||||||
): WikipediaLocale => {
|
|
||||||
return WIKIPEDIA_LOCALES_MAP[locale]
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getWikipediaLink = (locale: WikipediaLocale): string => {
|
|
||||||
return `https://${locale}.wikipedia.org`
|
|
||||||
}
|
|
||||||
|
|
||||||
interface WikipediaQueryLinksResponse {
|
|
||||||
continue?: {
|
|
||||||
plcontinue: string
|
|
||||||
continue: string
|
|
||||||
}
|
|
||||||
query: {
|
|
||||||
pages: {
|
|
||||||
[key: string]: {
|
|
||||||
pageid: number
|
|
||||||
ns: number
|
|
||||||
title: string
|
|
||||||
links: [
|
|
||||||
{
|
|
||||||
ns: number
|
|
||||||
title: string
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
limits: {
|
|
||||||
links: number
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface GetWikipediaPageInternalLinksInput {
|
|
||||||
title: string
|
|
||||||
locale: WikipediaLocale
|
|
||||||
}
|
|
||||||
|
|
||||||
interface GetWikipediaPageInternalLinksOutput {
|
|
||||||
/**
|
|
||||||
* Title of the Wikipedia page.
|
|
||||||
*/
|
|
||||||
title: string
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Page id is unique for each page on Wikipedia, can be used to link to the page.
|
|
||||||
* @example `https://${locale}.wikipedia.org/?curid=${pageId}`
|
|
||||||
*/
|
|
||||||
pageId: number
|
|
||||||
|
|
||||||
/**
|
|
||||||
* List of internal links on the Wikipedia page.
|
|
||||||
*/
|
|
||||||
links: string[]
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get internal links from a Wikipedia page.
|
|
||||||
* @param input
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export const getWikipediaPageInternalLinks = async (
|
|
||||||
input: GetWikipediaPageInternalLinksInput,
|
|
||||||
): Promise<GetWikipediaPageInternalLinksOutput> => {
|
|
||||||
const links: string[] = []
|
|
||||||
let title = input.title
|
|
||||||
let pageId = 0
|
|
||||||
let plcontinue: string | null = null
|
|
||||||
|
|
||||||
const fetchLinks = async (): Promise<WikipediaQueryLinksResponse> => {
|
|
||||||
const url = new URL("/w/api.php", getWikipediaLink(input.locale))
|
|
||||||
url.searchParams.append("action", "query")
|
|
||||||
url.searchParams.append("titles", title)
|
|
||||||
url.searchParams.append("prop", "links")
|
|
||||||
url.searchParams.append("pllimit", "max")
|
|
||||||
url.searchParams.append("format", "json")
|
|
||||||
url.searchParams.append("origin", "*")
|
|
||||||
if (plcontinue != null) {
|
|
||||||
url.searchParams.set("plcontinue", plcontinue)
|
|
||||||
}
|
|
||||||
const response = await fetch(url, {
|
|
||||||
method: "GET",
|
|
||||||
})
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(response.statusText)
|
|
||||||
}
|
|
||||||
const json = (await response.json()) as WikipediaQueryLinksResponse
|
|
||||||
return json
|
|
||||||
}
|
|
||||||
|
|
||||||
do {
|
|
||||||
try {
|
|
||||||
const response = await fetchLinks()
|
|
||||||
plcontinue = response?.continue?.plcontinue ?? null
|
|
||||||
const pages = Object.keys(response.query.pages)
|
|
||||||
const page = pages[0] ?? ""
|
|
||||||
if (page === "-1" || page === "") {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
const pageData = response.query.pages[page]
|
|
||||||
if (pageData == null) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
title = pageData.title
|
|
||||||
pageId = pageData.pageid
|
|
||||||
links.push(
|
|
||||||
...pageData.links.map((link) => {
|
|
||||||
return link.title
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
} catch {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
} while (plcontinue != null)
|
|
||||||
|
|
||||||
return {
|
|
||||||
title,
|
|
||||||
pageId,
|
|
||||||
links,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface WikipediaPagesInternalLinks {
|
|
||||||
[key: string]: GetWikipediaPageInternalLinksOutput
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface GetDeepWikipediaPageInternalLinksInput {
|
|
||||||
locale: WikipediaLocale
|
|
||||||
data: WikipediaPagesInternalLinks
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getDeepWikipediaPageInternalLinks = async (
|
|
||||||
input: GetDeepWikipediaPageInternalLinksInput,
|
|
||||||
): Promise<WikipediaPagesInternalLinks> => {
|
|
||||||
const pagesTitles = Object.keys(input.data)
|
|
||||||
await Promise.all(
|
|
||||||
pagesTitles.map(async (pageTitle) => {
|
|
||||||
const links = input.data[pageTitle]?.links ?? []
|
|
||||||
await Promise.all(
|
|
||||||
links.map(async (pageTitleLink) => {
|
|
||||||
if (pageTitleLink in input.data) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
input.data[pageTitleLink] = await getWikipediaPageInternalLinks({
|
|
||||||
locale: input.locale,
|
|
||||||
title: pageTitleLink,
|
|
||||||
})
|
|
||||||
await getDeepWikipediaPageInternalLinks({
|
|
||||||
locale: input.locale,
|
|
||||||
data: input.data,
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
return input.data
|
|
||||||
}
|
|
||||||
|
3
pnpm-lock.yaml
generated
3
pnpm-lock.yaml
generated
@ -547,9 +547,6 @@ importers:
|
|||||||
'@repo/config-tailwind':
|
'@repo/config-tailwind':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../config-tailwind
|
version: link:../config-tailwind
|
||||||
'@repo/constants':
|
|
||||||
specifier: workspace:*
|
|
||||||
version: link:../constants
|
|
||||||
'@repo/i18n':
|
'@repo/i18n':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../i18n
|
version: link:../i18n
|
||||||
|
Reference in New Issue
Block a user