diff --git a/apps/docs/package.json b/apps/docs/package.json index 04f2d7f5..4b9bf60e 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -14,11 +14,11 @@ "@astrojs/sitemap": "^3.0.0", "@astrojs/solid-js": "^3.0.1", "@mdi/js": "^7.2.96", - "@microsoft/fetch-event-source": "^2.0.1", "@solid-primitives/scheduled": "^1.4.0", "@types/marked": "^5.0.1", "@unocss/reset": "^0.55.7", "@vrite/components": "workspace:*", + "@vrite/sdk": "workspace:*", "astro": "^3.1.0", "clsx": "^2.0.0", "curl-string": "^3.1.0", @@ -32,6 +32,7 @@ "tinykeys": "^2.1.0", "typescript": "^5.2.2", "unocss": "^0.55.7", + "url-slug": "^4.0.1", "vite": "^4.4.9" }, "devDependencies": { diff --git a/apps/docs/src/components/fragments/navigation.tsx b/apps/docs/src/components/fragments/footer.tsx similarity index 80% rename from apps/docs/src/components/fragments/navigation.tsx rename to apps/docs/src/components/fragments/footer.tsx index c9bf72c5..7578c36d 100644 --- a/apps/docs/src/components/fragments/navigation.tsx +++ b/apps/docs/src/components/fragments/footer.tsx @@ -2,18 +2,19 @@ import { mdiChevronLeft, mdiChevronRight } from "@mdi/js"; import { Component, Show } from "solid-js"; import { IconButton } from "#components/primitives"; -interface NavigateProps { - nextEntry?: { label: string; link: string }; - previousEntry?: { label: string; link: string }; +interface FooterProps { + nextEntry?: { label: string; link: string } | null; + previousEntry?: { label: string; link: string } | null; } -const Navigation: Component = (props) => { +const Footer: Component = (props) => { return (
= (props) => { {props.nextEntry!.label}} + text="soft" path={mdiChevronRight} iconProps={{ class: "min-w-8" }} size="large" @@ -38,4 +40,4 @@ const Navigation: Component = (props) => { ); }; -export { Navigation }; +export { Footer }; diff --git a/apps/docs/src/components/fragments/header.tsx b/apps/docs/src/components/fragments/header.tsx index 5a01106e..ea8f3596 100644 --- a/apps/docs/src/components/fragments/header.tsx +++ b/apps/docs/src/components/fragments/header.tsx @@ -1,6 +1,6 @@ import { SearchPaletteProvider, useSearchPalette } from "./search-palette"; import { mdiAppleKeyboardCommand, mdiGithub, mdiMagnify } from "@mdi/js"; -import { For, type Component } from "solid-js"; +import { For, type Component, Show } from "solid-js"; import clsx from "clsx"; import { Button, Icon, IconButton, Tooltip } from "#components/primitives"; import { discordIcon } from "#assets/icons"; @@ -59,16 +59,23 @@ const Header: Component = () => { label={ } text="soft" class="lg:min-w-48 justify-start m-0 group" onClick={() => setOpened(!opened())} /> -
diff --git a/apps/docs/src/components/fragments/index.tsx b/apps/docs/src/components/fragments/index.tsx index a48e5cf0..aa26b9a6 100644 --- a/apps/docs/src/components/fragments/index.tsx +++ b/apps/docs/src/components/fragments/index.tsx @@ -4,5 +4,6 @@ export * from "./header"; export * from "./side-bar"; export * from "./on-this-page"; export * from "./svg-defs"; -export * from "./navigation"; +export * from "./footer"; export * from "./search-palette"; +export * from "./footer"; diff --git a/apps/docs/src/components/fragments/on-this-page.tsx b/apps/docs/src/components/fragments/on-this-page.tsx index 720b181d..1f82c826 100644 --- a/apps/docs/src/components/fragments/on-this-page.tsx +++ b/apps/docs/src/components/fragments/on-this-page.tsx @@ -1,4 +1,12 @@ -import { Component, For, onMount, onCleanup, createSignal, createMemo } from "solid-js"; +import { + Component, + For, + onMount, + onCleanup, + createSignal, + createMemo, + createEffect +} from "solid-js"; import { mdiListBox } from "@mdi/js"; import clsx from "clsx"; import { scroll } from "seamless-scroll-polyfill"; @@ -16,11 +24,39 @@ const OnThisPage: Component = (props) => { return heading.depth === 2 || heading.depth === 3; }); }); + const scrollToActiveHeading = (smooth?: boolean): void => { + const heading = activeHeading(); + const element = document.getElementById(heading); + + if (!element) return; + + const rect = element.getBoundingClientRect(); + const y = rect.top + window.scrollY - 60; + + scroll(window, { + top: y, + behavior: smooth === false ? "instant" : "smooth" + }); + }; + const handleClick = (event: MouseEvent): void => { + const target = event.target as HTMLElement; + + if (target.matches("h1, h2, h3, h4, h5, h6")) { + const { id } = target; + + if (id) { + setActiveHeading(id); + scrollToActiveHeading(); + history.replaceState(null, "", `#${id}`); + navigator.clipboard.writeText(window.location.href); + } + } + }; onMount(() => { if (!headings().length) return; - const observedElements: HTMLElement[] = []; + const hash = location.hash.slice(1); const setCurrent: IntersectionObserverCallback = (entries) => { for (const entry of entries) { if (entry.isIntersecting) { @@ -60,10 +96,17 @@ const OnThisPage: Component = (props) => { ) .forEach((h) => headingsObserver.observe(h)); container?.addEventListener("scroll", handleScroll); + document.body.addEventListener("click", handleClick); onCleanup(() => { headingsObserver.disconnect(); container?.removeEventListener("scroll", handleScroll); + document.body.removeEventListener("click", handleClick); }); + + if (hash) { + setActiveHeading(hash); + scrollToActiveHeading(false); + } }); return ( @@ -93,18 +136,8 @@ const OnThisPage: Component = (props) => { class={clsx("text-start m-0", heading.depth === 3 && "ml-6")} size={heading.depth === 2 ? "medium" : "small"} onClick={() => { - const element = document.getElementById(heading.slug); - - if (!element) return; - - const rect = element.getBoundingClientRect(); - const y = rect.top + window.scrollY - 60; - - scroll(window, { - top: y, - behavior: "smooth" - }); setActiveHeading(heading.slug); + scrollToActiveHeading(); }} > {heading.text} diff --git a/apps/docs/src/components/fragments/search-palette.tsx b/apps/docs/src/components/fragments/search-palette.tsx index 5098c0b3..a5976cca 100644 --- a/apps/docs/src/components/fragments/search-palette.tsx +++ b/apps/docs/src/components/fragments/search-palette.tsx @@ -8,7 +8,6 @@ import { Show, Switch, createEffect, - createMemo, createSignal, on, onCleanup, @@ -16,7 +15,6 @@ import { } from "solid-js"; import { mdiChevronRight, - mdiConsoleLine, mdiCreationOutline, mdiFileDocumentOutline, mdiHeadSnowflakeOutline, @@ -30,8 +28,9 @@ import clsx from "clsx"; import { scrollIntoView } from "seamless-scroll-polyfill"; import { createContext } from "solid-js"; import { debounce } from "@solid-primitives/scheduled"; -import { fetchEventSource } from "@microsoft/fetch-event-source"; +import { convert as convertToSlug } from "url-slug"; import { marked } from "marked"; +import { createClient, SearchResult } from "@vrite/sdk/api"; import { Button, Card, @@ -54,12 +53,14 @@ interface SearchPaletteContextData { const SearchPaletteContext = createContext(); const SearchPalette: Component = (props) => { + const client = createClient({ + token: import.meta.env.PUBLIC_VRITE_SEARCH_TOKEN, + baseURL: "http://localhost:4444" + }); const [inputRef, setInputRef] = createSignal(null); const [abortControllerRef, setAbortControllerRef] = createSignal(null); const [mode, setMode] = createSignal<"search" | "ask">("search"); - const [searchResults, setSearchResults] = createSignal< - Array<{ content: string; breadcrumb: string[]; contentPieceId: string }> - >([]); + const [searchResults, setSearchResults] = createSignal([]); const [answer, setAnswer] = createSignal(""); const [loading, setLoading] = createSignal(false); const [scrollableContainerRef, setScrollableContainerRef] = createSignal( @@ -71,25 +72,17 @@ const SearchPalette: Component = (props) => { const ask = async (): Promise => { let content = ""; - await fetchEventSource(`http://localhost:4444/search/ask/?query=${query()}`, { - method: "GET", - headers: { - "Authorization": `Bearer Fq0U5T6vebJHtIqCPmyAt:jihNYSVwqNVgS9v8RqccR`, - "Content-Type": "application/json", - "Accept": "text/event-stream" - }, - signal: abortControllerRef()?.signal, - onerror(error) { + client.useSignal(abortControllerRef()?.signal || null).ask({ + query: query(), + onError(error) { setLoading(false); throw error; }, - onmessage(event) { - const partOfContent = decodeURIComponent(event.data); - - content += partOfContent; + onChunk(chunk) { + content += chunk; setAnswer(marked.parse(content, { gfm: true })); }, - onclose() { + onEnd() { setLoading(false); } }); @@ -98,29 +91,23 @@ const SearchPalette: Component = (props) => { const search = debounce(async () => { setSearchResults([]); + if (abortControllerRef()) abortControllerRef()?.abort(); + if (!query()) { setLoading(false); + setSearchResults([]); return; } - if (abortControllerRef()) abortControllerRef()?.abort(); - setAbortControllerRef(new AbortController()); try { - const response = await fetch(`http://localhost:4444/search/?query=${query()}`, { - signal: abortControllerRef()?.signal, - credentials: "include", - headers: { - "Authorization": `Bearer Fq0U5T6vebJHtIqCPmyAt:jihNYSVwqNVgS9v8RqccR`, - "Content-Type": "application/json", - "Accept": "application/json" - } - }); - const results = await response.json(); + const search = await client + .useSignal(abortControllerRef()?.signal || null) + .search({ query: query() }); - setSearchResults(results); + setSearchResults(search); setLoading(false); return; @@ -128,7 +115,7 @@ const SearchPalette: Component = (props) => { const trpcError = error as any; const causeErrorName = trpcError.cause?.name?.toLowerCase() || ""; - if (!causeErrorName.includes("aborterror")) { + if (!causeErrorName.includes("aborterror") && abortControllerRef()?.signal.aborted) { setLoading(false); } } @@ -143,10 +130,16 @@ const SearchPalette: Component = (props) => { }); } }; - const goToContentPiece = (contentPieceId: string, breadcrumb?: string[]): void => { + const goToContentPiece = (searchResult: SearchResult): void => { // eslint-disable-next-line no-console - console.log(contentPieceId, breadcrumb); props.setOpened(false); + + const { slug } = searchResult.contentPiece; + const [title, subHeading1, subHeading2] = searchResult.breadcrumb; + + window.location.href = `/${slug.startsWith("/") ? slug.slice(1) : slug}#${convertToSlug( + subHeading2 || subHeading1 + )}`; }; createEffect( @@ -223,10 +216,7 @@ const SearchPalette: Component = (props) => { if (!props.opened) return; if (mode() === "search") { - goToContentPiece( - searchResults()[selectedIndex()].contentPieceId, - searchResults()[selectedIndex()].breadcrumb - ); + goToContentPiece(searchResults()[selectedIndex()]); } } }); @@ -354,7 +344,7 @@ const SearchPalette: Component = (props) => { )} color="base" onClick={() => { - goToContentPiece(result.contentPieceId, result.breadcrumb); + goToContentPiece(result); }} onPointerEnter={() => { if (!mouseHoverEnabled()) return; diff --git a/apps/docs/src/components/fragments/side-bar.tsx b/apps/docs/src/components/fragments/side-bar.tsx index c53b6c3f..118fff32 100644 --- a/apps/docs/src/components/fragments/side-bar.tsx +++ b/apps/docs/src/components/fragments/side-bar.tsx @@ -1,11 +1,4 @@ -import { - mdiMenu, - mdiClose, - mdiChevronDown, - mdiConsoleLine, - mdiTextBoxMultiple, - mdiBookOpenBlankVariant -} from "@mdi/js"; +import { mdiMenu, mdiClose, mdiChevronDown } from "@mdi/js"; import clsx from "clsx"; import { Component, For, JSX, createSignal } from "solid-js"; import { menuOpened, setMenuOpened } from "#lib/state"; @@ -13,41 +6,34 @@ import { Card, Button, IconButton } from "#components/primitives"; import { logoIcon } from "#assets/icons/logo"; interface SideBarProps { - menu: Array<{ + currentSection: string; + sections: Array<{ label: string; - menu: Array<{ label: string; link: string }>; + icon: string; + link: string; + id: string; }>; + menu: Record< + string, + Array<{ + label: string; + menu: Array<{ label: string; link?: string; menu?: Array<{ label: string; link: string }> }>; + }> + >; currentPath: string; } - -const externalLinks = [ - { - label: "Documentation", - icon: mdiBookOpenBlankVariant, - href: "https://github.com/vriteio/vrite", - active: true - }, - { - label: "API reference", - icon: mdiConsoleLine, - href: "https://discord.gg/yYqDWyKnqE" - }, - { - label: "Recipes", - icon: mdiTextBoxMultiple, - href: "https://app.vrite.io" - } -]; -const SideBarNestedMenu: Component<{ - menu: Array<{ label: string; link: string; menu?: Array<{ label: string; link: string }> }>; +interface SideBarNestedMenuProps { + menu: Array<{ label: string; link?: string; menu?: Array<{ label: string; link: string }> }>; currentPath: string; children: JSX.Element; openedByDefault?: boolean; -}> = (props) => { +} + +const SideBarNestedMenu: Component = (props) => { const [opened, setOpened] = createSignal( props.openedByDefault || props.menu.filter((item) => { - return props.currentPath.includes(item.link); + return item.link && props.currentPath.includes(item.link); }).length > 0 ); @@ -91,7 +77,7 @@ const SideBarNestedMenu: Component<{ } const active = (): boolean => { - return props.currentPath.includes(item.link); + return Boolean(item.link && props.currentPath.includes(item.link)); }; return ( @@ -139,28 +125,27 @@ const SideBar: Component = (props) => {
- - {(link) => { + + {(section) => { return ( - {link.label} + {section.label} ); }}
- + {(menuItem) => { return ( ; + }>, + section: string +): Array<{ label: string; link: string; section: string }> => { + return menu.flatMap((item) => { + if (item.link) { + return [{ label: item.label, link: item.link, section }]; + } + if (item.menu) { + return flattenMenu(item.menu, section); + } + return []; + }); +}; +const sections = [ + { + label: "Documentation", + link: "/usage-guide/getting-started", + icon: mdiBookOpenBlankVariant, + id: "docs" + }, + { + label: "API reference", + link: "/javascript-sdk/introduction", + icon: mdiConsoleLine, + id: "api" + }, + { + label: "Recipes", + link: "/self-hosting/docker", + icon: mdiTextBoxMultiple, + id: "recipes" + } +]; +const flatMenu = [ + ...flattenMenu(menu.docs, "docs"), + ...flattenMenu(menu.api, "api"), + ...flattenMenu(menu.recipes, "recipes") +]; +const currentEntry = flatMenu.find((item) => item.link === Astro.url.pathname); +const nextEntry = currentEntry ? flatMenu[flatMenu.indexOf(currentEntry) + 1] : null; +const prevEntry = currentEntry ? flatMenu[flatMenu.indexOf(currentEntry) - 1] : null; type Props = { headings: MarkdownHeading[]; @@ -25,8 +72,14 @@ type Props = {
-
- +
+

{Astro.props.title}

+
diff --git a/apps/docs/src/components/layouts/menu.json b/apps/docs/src/components/layouts/menu.json index e94284c3..88e3b552 100644 --- a/apps/docs/src/components/layouts/menu.json +++ b/apps/docs/src/components/layouts/menu.json @@ -1,53 +1,59 @@ -[ - { - "label": "Usage Guide", - "menu": [ - { - "label": "Getting Started", - "link": "/usage-guide/getting-started" - }, - { - "label": "Managing Content with Kanban", - "link": "/usage-guide/kanban-dashboard" - }, - { - "label": "Managing metadata", - "link": "/usage-guide/metadata" - }, - { - "label": "Writing in the Vrite Editor", - "link": "/usage-guide/content-editor" - }, - { - "label": "Configuring Vrite", - "link": "/usage-guide/configuring-vrite" - }, - { - "label": "Publishing content", - "link": "/usage-guide/publishing" - } - ] - }, - { - "label": "JavaScript SDK", - "menu": [ - { - "label": "Introduction", - "link": "/javascript-sdk/introduction" - } - ] - }, - { - "label": "Self-Hosting", - "menu": [ - { - "label": "Docker", - "link": "/self-hosting/docker" - }, - { - "label": "Configuration", - "link": "/self-hosting/configuration" - } - ] - } -] +{ + "docs": [ + { + "label": "Usage Guide", + "menu": [ + { + "label": "Getting Started", + "link": "/usage-guide/getting-started" + }, + { + "label": "Managing Content with Kanban", + "link": "/usage-guide/kanban-dashboard" + }, + { + "label": "Managing metadata", + "link": "/usage-guide/metadata" + }, + { + "label": "Writing in the Vrite Editor", + "link": "/usage-guide/content-editor" + }, + { + "label": "Configuring Vrite", + "link": "/usage-guide/configuring-vrite" + }, + { + "label": "Publishing content", + "link": "/usage-guide/publishing" + } + ] + } + ], + "api": [ + { + "label": "JavaScript SDK", + "menu": [ + { + "label": "Introduction", + "link": "/javascript-sdk/introduction" + } + ] + } + ], + "recipes": [ + { + "label": "Self-Hosting", + "menu": [ + { + "label": "Docker", + "link": "/self-hosting/docker" + }, + { + "label": "Configuration", + "link": "/self-hosting/configuration" + } + ] + } + ] +} diff --git a/apps/docs/src/env.d.ts b/apps/docs/src/env.d.ts index acef35f1..9a6d3ac2 100644 --- a/apps/docs/src/env.d.ts +++ b/apps/docs/src/env.d.ts @@ -1,2 +1,9 @@ /// /// +interface ImportMetaEnv { + readonly PUBLIC_VRITE_SEARCH_TOKEN: string; +} + +interface ImportMeta { + readonly env: ImportMetaEnv; +} diff --git a/apps/docs/src/styles/styles.scss b/apps/docs/src/styles/styles.scss index 3f80aa1a..d4f52dea 100644 --- a/apps/docs/src/styles/styles.scss +++ b/apps/docs/src/styles/styles.scss @@ -103,64 +103,16 @@ kbd { font-kerning: none; :where(h1, h2, h3, h4, h5, h6):not(:where(.not-prose, .not-prose *)) { - &::before, - &::before, - &::before, - &::before, - &::before, - &::before, - .open-slash-menu > button { - @apply font-bold dark:bg-gray-900 dark:border-gray-700; - position: absolute; - left: -2.5rem; - background: #f9fafb; - height: 2rem; - width: 2rem; - justify-content: center; - display: flex; - align-items: center; - color: #6b7280; - border-radius: 25%; - font-size: 1rem; - line-height: 1rem; - text-align: center; - top: 50%; - transform: translateY(-50%); - //border: 2px solid #e5e7eb; - @apply border-2; - opacity: 1; + @apply m-0 relative hover:cursor-pointer; + &::before { + @apply hidden md:block absolute pr-6 -left-6 transform scale-90 text-gray-500 dark:text-gray-400 opacity-0; + content: "#"; } - } - - h1, - h2, - h3, - h4, - h5, - h6, - .open-slash-menu { - @apply m-0 relative; - & > button { - @apply mt-4; - } - } - .open-slash-menu { - button { - font-size: 1.25em; - line-height: 1.25em; - font-weight: 600; + &:hover::before { + @apply opacity-100; } } - :where(h4):not(:where(.not-prose, .not-prose *)) { - line-height: 2.1rem; - } - :where(h5):not(:where(.not-prose, .not-prose *)) { - line-height: 2.1rem; - } - :where(h6):not(:where(.not-prose, .not-prose *)) { - font-size: 0.875em; - line-height: 2.1rem; - } + :where(ul p, ol p):not(:where(.not-prose, .not-prose *)) { @apply m-0; } diff --git a/apps/web/public/sandbox.js b/apps/web/public/sandbox.js index d1b13c02..0ef35594 100644 --- a/apps/web/public/sandbox.js +++ b/apps/web/public/sandbox.js @@ -143,8 +143,8 @@ var string = ""; var bitsNeeded = this.bitsNeeded; var codePoint = this.codePoint; - for (var i2 = 0; i2 < octets.length; i2 += 1) { - var octet = octets[i2]; + for (var i = 0; i < octets.length; i += 1) { + var octet = octets[i]; if (bitsNeeded !== 0) { if (octet < 128 || octet > 191 || !valid(codePoint << 6 | octet & 63, bitsNeeded - 6, octetsCount(bitsNeeded, codePoint))) { bitsNeeded = 0; @@ -417,8 +417,8 @@ function HeadersPolyfill(all) { var map = /* @__PURE__ */ Object.create(null); var array = all.split("\r\n"); - for (var i2 = 0; i2 < array.length; i2 += 1) { - var line = array[i2]; + for (var i = 0; i < array.length; i += 1) { + var line = array[i]; var parts = line.split(": "); var name = parts.shift(); var value = parts.join(": "); @@ -539,8 +539,8 @@ var typeListeners = this._listeners[event.type]; if (typeListeners != void 0) { var length = typeListeners.length; - for (var i2 = 0; i2 < length; i2 += 1) { - var listener = typeListeners[i2]; + for (var i = 0; i < length; i += 1) { + var listener = typeListeners[i]; try { if (typeof listener.handleEvent === "function") { listener.handleEvent(event); @@ -562,8 +562,8 @@ listeners[type] = typeListeners; } var found = false; - for (var i2 = 0; i2 < typeListeners.length; i2 += 1) { - if (typeListeners[i2] === listener) { + for (var i = 0; i < typeListeners.length; i += 1) { + if (typeListeners[i] === listener) { found = true; } } @@ -577,9 +577,9 @@ var typeListeners = listeners[type]; if (typeListeners != void 0) { var filtered = []; - for (var i2 = 0; i2 < typeListeners.length; i2 += 1) { - if (typeListeners[i2] !== listener) { - filtered.push(typeListeners[i2]); + for (var i = 0; i < typeListeners.length; i += 1) { + if (typeListeners[i] !== listener) { + filtered.push(typeListeners[i]); } } if (filtered.length === 0) { @@ -722,10 +722,10 @@ var onProgress = function(textChunk) { if (currentState === OPEN) { var n = -1; - for (var i2 = 0; i2 < textChunk.length; i2 += 1) { - var c = textChunk.charCodeAt(i2); + for (var i = 0; i < textChunk.length; i += 1) { + var c = textChunk.charCodeAt(i); if (c === "\n".charCodeAt(0) || c === "\r".charCodeAt(0)) { - n = i2; + n = i; } } var chunk = (n !== -1 ? textBuffer : "") + textChunk.slice(0, n + 1); @@ -879,8 +879,8 @@ var requestURL = url; if (url.slice(0, 5) !== "data:" && url.slice(0, 5) !== "blob:") { if (lastEventId !== "") { - var i2 = url.indexOf("?"); - requestURL = i2 === -1 ? url : url.slice(0, i2 + 1) + url.slice(i2 + 1).replace(/(?:^|&)([^=&]*)(?:=[^&]*)?/g, function(p, paramName) { + var i = url.indexOf("?"); + requestURL = i === -1 ? url : url.slice(0, i + 1) + url.slice(i + 1).replace(/(?:^|&)([^=&]*)(?:=[^&]*)?/g, function(p, paramName) { return paramName === lastEventIdQueryParameterName ? "" : p; }); requestURL += (url.indexOf("?") === -1 ? "?" : "&") + lastEventIdQueryParameterName + "=" + encodeURIComponent(lastEventId); @@ -957,7 +957,7 @@ "../../node_modules/.pnpm/unfetch@4.2.0/node_modules/unfetch/dist/unfetch.js"(exports, module) { module.exports = function(e, n) { return n = n || {}, new Promise(function(t, r) { - var s = new XMLHttpRequest(), o = [], u2 = [], i2 = {}, a = function() { + var s = new XMLHttpRequest(), o = [], u2 = [], i = {}, a = function() { return { ok: 2 == (s.status / 100 | 0), statusText: s.statusText, status: s.status, url: s.responseURL, text: function() { return Promise.resolve(s.responseText); }, json: function() { @@ -969,14 +969,14 @@ }, entries: function() { return u2; }, get: function(e2) { - return i2[e2.toLowerCase()]; + return i[e2.toLowerCase()]; }, has: function(e2) { - return e2.toLowerCase() in i2; + return e2.toLowerCase() in i; } } }; }; for (var l2 in s.open(n.method || "get", e, true), s.onload = function() { s.getAllResponseHeaders().replace(/^(.*?):[^\S\n]*([\s\S]*?)$/gm, function(e2, n2, t2) { - o.push(n2 = n2.toLowerCase()), u2.push([n2, t2]), i2[n2] = i2[n2] ? i2[n2] + "," + t2 : t2; + o.push(n2 = n2.toLowerCase()), u2.push([n2, t2]), i[n2] = i[n2] ? i[n2] + "," + t2 : t2; }), t(a()); }, s.onerror = r, s.withCredentials = "include" == n.credentials, n.headers) s.setRequestHeader(l2, n.headers[l2]); @@ -996,74 +996,83 @@ // ../../packages/sdk/javascript/dist/api.mjs var api_exports = {}; __export(api_exports, { - createClient: () => B + createClient: () => M }); - var import_eventsource, i, w, $, U, f, g, L, d, S, D, O, E, R, C, x, m, I, h, j, v, k, q, l, A, u, z, B; + var import_eventsource, l, U, E, S, f, h, L, m, D, O, R, T, x, C, I, u, v, k, j, q, G, A, b, z, y, B, M; var init_api = __esm({ "../../packages/sdk/javascript/dist/api.mjs"() { import_eventsource = __toESM(require_browser(), 1); - i = "/content-groups"; - w = (t) => ({ get: (e) => t("GET", `${i}`, { params: e }), list: (e) => t("GET", `${i}/list`, { params: e }), create: (e) => t("POST", `${i}`, { body: e }), update: (e) => t("PUT", `${i}`, { body: e }), delete: (e) => t("DELETE", `${i}`, { params: e }) }); - $ = "/content-pieces"; - U = (t) => ({ get: (e) => t("GET", `${$}`, { params: e }), create: (e) => t("POST", `${$}`, { body: e }), update: (e) => t("PUT", `${$}`, { body: e }), delete: (e) => t("DELETE", `${$}`, { params: e }), list: (e) => t("GET", `${$}/list`, { params: e }) }); + l = "/content-groups"; + U = (t) => ({ get: (e) => t("GET", `${l}`, { params: e }), list: (e) => t("GET", `${l}/list`, { params: e }), create: (e) => t("POST", `${l}`, { body: e }), update: (e) => t("PUT", `${l}`, { body: e }), delete: (e) => t("DELETE", `${l}`, { params: e }) }); + E = "/content-pieces"; + S = (t) => ({ get: (e) => t("GET", `${E}`, { params: e }), create: (e) => t("POST", `${E}`, { body: e }), update: (e) => t("PUT", `${E}`, { body: e }), delete: (e) => t("DELETE", `${E}`, { params: e }), list: (e) => t("GET", `${E}/list`, { params: e }) }); f = (t) => { - let e = t.baseURL || "https://api.vrite.io", n = t.extensionId || "", c = t.headers || {}, { token: s } = t; - return { sendRequest: async (a, p, r) => { + let e = t.baseURL || "https://api.vrite.io", c = t.extensionId || "", i = t.headers || {}, $ = null, { token: p } = t; + return { sendRequest: async (s, a, n) => { try { - const { default: o } = await Promise.resolve().then(() => __toESM(require_browser2(), 1)), y = await o(`${e}${p}/?${encodeURI(Object.entries(r?.params || {}).filter(([, b]) => b).map(([b, G]) => `${b}=${G}`).join("&"))}`, { headers: { Authorization: `Bearer ${s}`, Accept: "application/json", ...r?.body ? { "Content-Type": "application/json" } : {}, ...n ? { "X-Vrite-Extension-ID": n } : {}, ...c }, body: r?.body ? JSON.stringify(r.body) : null, method: a }); - let T = null; + const { default: o } = await Promise.resolve().then(() => __toESM(require_browser2(), 1)), d = await o(`${e}${a}/?${encodeURI(Object.entries(n?.params || {}).filter(([, g]) => g).map(([g, w]) => `${g}=${w}`).join("&"))}`, { headers: { Authorization: `Bearer ${p}`, Accept: "application/json", ...n?.body ? { "Content-Type": "application/json" } : {}, ...c ? { "X-Vrite-Extension-ID": c } : {}, ...i }, body: n?.body ? JSON.stringify(n.body) : null, signal: $, method: s }); + $ = null; + let r = null; try { - if (T = await y.json(), !T) + if (r = await d.json(), !r) return; } catch { return; } - if (!y.ok) - throw T; - return T; + if (!d.ok) + throw r; + return r; } catch (o) { throw console.error(o), o; } - }, reconfigure: (a) => { - e = a.baseURL || e, s = a.token || s, n = a.extensionId || n, c = a.headers || c; - }, getConfig: () => ({ baseURL: e, token: s, extensionId: n, headers: c }) }; + }, reconfigure: (s) => { + e = s.baseURL || e, p = s.token || p, c = s.extensionId || c, i = s.headers || i; + }, useSignal: (s) => { + $ = s; + }, getConfig: () => ({ baseURL: e, token: p, extensionId: c, headers: i }), getSignal: () => $ }; }; - g = "/user-settings"; - L = (t) => ({ get: () => t("GET", `${g}`), update: (e) => t("PUT", `${g}`, { body: e }) }); - d = "/tags"; - S = (t) => ({ get: (e) => t("GET", `${d}`, { params: e }), update: (e) => t("PUT", `${d}`, { body: e }), create: (e) => t("PUT", `${d}`, { body: e }), delete: (e) => t("DELETE", `${d}`, { params: e }), list: (e) => t("GET", `${d}/list`, { params: e }) }); - D = "/profile"; - O = (t) => ({ get: () => t("GET", `${D}`) }); - E = "/webhooks"; - R = (t) => ({ get: (e) => t("GET", `${E}`, { params: e }), create: (e) => t("POST", `${E}`, { body: e }), update: (e) => t("PUT", `${E}`, { body: e }), delete: (e) => t("DELETE", `${E}`, { params: e }), list: (e) => t("GET", `${E}/list`, { params: e }) }); + h = "/user-settings"; + L = (t) => ({ get: () => t("GET", `${h}`), update: (e) => t("PUT", `${h}`, { body: e }) }); + m = "/tags"; + D = (t) => ({ get: (e) => t("GET", `${m}`, { params: e }), update: (e) => t("PUT", `${m}`, { body: e }), create: (e) => t("PUT", `${m}`, { body: e }), delete: (e) => t("DELETE", `${m}`, { params: e }), list: (e) => t("GET", `${m}/list`, { params: e }) }); + O = "/profile"; + R = (t) => ({ get: () => t("GET", `${O}`) }); + T = "/webhooks"; + x = (t) => ({ get: (e) => t("GET", `${T}`, { params: e }), create: (e) => t("POST", `${T}`, { body: e }), update: (e) => t("PUT", `${T}`, { body: e }), delete: (e) => t("DELETE", `${T}`, { params: e }), list: (e) => t("GET", `${T}/list`, { params: e }) }); C = "/workspace"; - x = (t) => ({ get: () => t("GET", `${C}`) }); - m = "/roles"; - I = (t) => ({ get: (e) => t("GET", `${m}`, { params: e }), create: (e) => t("POST", `${m}`, { body: e }), update: (e) => t("PUT", `${m}`, { body: e }), delete: (e) => t("DELETE", `${m}`, { params: e }), list: (e) => t("GET", `${m}/list`, { params: e }) }); - h = "/workspace-settings"; - j = (t) => ({ get: () => t("GET", `${h}`), update: (e) => t("PUT", `${h}`, { body: e }) }); - v = (t) => ({ listMembers: (e) => t("GET", "/workspace-memberships/list-members", { params: e }), listWorkspaces: (e) => t("GET", "/workspace-memberships/list-workspaces", { params: e }), create: (e) => t("POST", "/workspace-memberships", { body: e }), update: (e) => t("PUT", "/workspace-memberships", { body: e }), delete: (e) => t("DELETE", "/workspace-memberships", { params: e }) }); - k = "/extension"; - q = (t) => ({ get: () => t("GET", `${k}`), updateContentPieceData: (e) => t("POST", `${k}/content-piece-data`, { body: e }) }); - l = "/variants"; - A = (t) => ({ create: (e) => t("POST", `${l}`, { body: e }), update: (e) => t("PUT", `${l}`, { body: e }), delete: (e) => t("DELETE", `${l}`, { params: e }), list: () => t("GET", `${l}/list`) }); - u = "/transformers"; - z = (t) => ({ create: (e) => t("POST", `${u}`, { body: e }), delete: (e) => t("DELETE", `${u}`, { params: e }), list: () => t("GET", `${u}/list`) }); - B = (t) => { - const { sendRequest: e, reconfigure: n, getConfig: c } = f(t); - return { contentGroups: w(e), contentPieces: U(e), tags: S(e), profile: O(e), userSettings: L(e), webhooks: R(e), workspace: x(e), roles: I(e), workspaceSettings: j(e), workspaceMemberships: v(e), extension: q(e), variants: A(e), transformers: z(e), search(s) { - return e("GET", "/search", { params: s }); - }, async ask(s) { - let a = ""; - const p = new import_eventsource.default(`${c().baseURL}/search/ask?query=${encodeURIComponent(s.query)}`, { headers: { Authorization: `Bearer ${c().token}` } }); - p.addEventListener("error", (r) => { - const o = r; - return o.message ? s.onError?.(o.message) : (p.close(), s.onEnd?.(a)); - }), p.addEventListener("message", (r) => { - const o = decodeURIComponent(r.data); - a += o, s.onChunk?.(o, a); - }); - }, reconfigure: n }; + I = (t) => ({ get: () => t("GET", `${C}`) }); + u = "/roles"; + v = (t) => ({ get: (e) => t("GET", `${u}`, { params: e }), create: (e) => t("POST", `${u}`, { body: e }), update: (e) => t("PUT", `${u}`, { body: e }), delete: (e) => t("DELETE", `${u}`, { params: e }), list: (e) => t("GET", `${u}/list`, { params: e }) }); + k = "/workspace-settings"; + j = (t) => ({ get: () => t("GET", `${k}`), update: (e) => t("PUT", `${k}`, { body: e }) }); + q = (t) => ({ listMembers: (e) => t("GET", "/workspace-memberships/list-members", { params: e }), listWorkspaces: (e) => t("GET", "/workspace-memberships/list-workspaces", { params: e }), create: (e) => t("POST", "/workspace-memberships", { body: e }), update: (e) => t("PUT", "/workspace-memberships", { body: e }), delete: (e) => t("DELETE", "/workspace-memberships", { params: e }) }); + G = "/extension"; + A = (t) => ({ get: () => t("GET", `${G}`), updateContentPieceData: (e) => t("POST", `${G}/content-piece-data`, { body: e }) }); + b = "/variants"; + z = (t) => ({ create: (e) => t("POST", `${b}`, { body: e }), update: (e) => t("PUT", `${b}`, { body: e }), delete: (e) => t("DELETE", `${b}`, { params: e }), list: () => t("GET", `${b}/list`) }); + y = "/transformers"; + B = (t) => ({ create: (e) => t("POST", `${y}`, { body: e }), delete: (e) => t("DELETE", `${y}`, { params: e }), list: () => t("GET", `${y}/list`) }); + M = (t) => { + const { sendRequest: e, reconfigure: c, getConfig: i, getSignal: $, useSignal: p } = f(t), s = { contentGroups: U(e), contentPieces: S(e), tags: D(e), profile: R(e), userSettings: L(e), webhooks: x(e), workspace: I(e), roles: v(e), workspaceSettings: j(e), workspaceMemberships: q(e), extension: A(e), variants: z(e), transformers: B(e), search(a) { + return e("GET", "/search", { params: a }); + }, async ask(a) { + let n = ""; + const o = new import_eventsource.default(`${i().baseURL}/search/ask?query=${encodeURIComponent(a.query)}`, { headers: { Authorization: `Bearer ${i().token}` } }); + o.addEventListener("error", (d) => { + const r = d; + return r.message ? a.onError?.(r.message) : (o.close(), a.onEnd?.(n)); + }), o.addEventListener("message", (d) => { + const r = decodeURIComponent(d.data); + n += r, a.onChunk?.(r, n); + }), $()?.addEventListener("abort", () => { + o.close(); + }), p(null); + }, useSignal(a) { + return p(a), s; + }, reconfigure(a) { + return c(a), s; + } }; + return s; }; } }); diff --git a/packages/sdk/javascript/src/api/client.ts b/packages/sdk/javascript/src/api/client.ts index 21210584..1dc3af43 100644 --- a/packages/sdk/javascript/src/api/client.ts +++ b/packages/sdk/javascript/src/api/client.ts @@ -1,5 +1,9 @@ import { createContentGroupsEndpoints, ContentGroupsEndpoints } from "./content-groups"; -import { ContentPiecesEndpoints, createContentPiecesEndpoints } from "./content-pieces"; +import { + ContentPiece, + ContentPiecesEndpoints, + createContentPiecesEndpoints +} from "./content-pieces"; import { APIFetcherConfig, createAPIFetcher } from "./request"; import { UserSettingsEndpoints, createUserSettingsEndpoints } from "./user-settings"; import { TagsEndpoints, createTagsEndpoints } from "./tags"; @@ -17,6 +21,12 @@ import { VariantsEndpoints, createVariantsEndpoints } from "./variants"; import { TransformersEndpoints, createTransformersEndpoints } from "./transformers"; import PolyfilledEventSource from "@sanity/eventsource"; +interface SearchResult { + contentPieceId: string; + contentPiece: Omit; + breadcrumb: string[]; + content: string; +} interface ClientConfig extends APIFetcherConfig {} interface Client { contentGroups: ContentGroupsEndpoints; @@ -36,27 +46,21 @@ interface Client { query: string; limit?: number; variantId?: string; - contentPieceID?: string; - }): Promise< - Array<{ - contentPieceId: string; - breadcrumb: string[]; - content: string; - }> - >; + contentPieceId?: string; + }): Promise; ask(input: { query: string; onChunk?(chunk: string, content: string): void; onEnd?(content: string): void; onError?(error: string): void; }): void; - reconfigure(config: ClientConfig): void; + useSignal(signal: AbortSignal | null): Client; + reconfigure(config: ClientConfig): Client; } const createClient = (config: ClientConfig): Client => { - const { sendRequest, reconfigure, getConfig } = createAPIFetcher(config); - - return { + const { sendRequest, reconfigure, getConfig, getSignal, useSignal } = createAPIFetcher(config); + const client: Client = { contentGroups: createContentGroupsEndpoints(sendRequest), contentPieces: createContentPiecesEndpoints(sendRequest), tags: createTagsEndpoints(sendRequest), @@ -102,10 +106,25 @@ const createClient = (config: ClientConfig): Client => { content += chunk; input.onChunk?.(chunk, content); }); + getSignal()?.addEventListener("abort", () => { + source.close(); + }); + useSignal(null); }, - reconfigure + useSignal(signal) { + useSignal(signal); + + return client; + }, + reconfigure(config) { + reconfigure(config); + + return client; + } }; + + return client; }; export { createClient }; -export type { Client }; +export type { Client, SearchResult }; diff --git a/packages/sdk/javascript/src/api/index.ts b/packages/sdk/javascript/src/api/index.ts index b487d3f3..3020ef9b 100644 --- a/packages/sdk/javascript/src/api/index.ts +++ b/packages/sdk/javascript/src/api/index.ts @@ -1,5 +1,5 @@ export { createClient } from "./client"; -export type { Client } from "./client"; +export type { Client, SearchResult } from "./client"; export type { ContentGroup } from "./content-groups"; export type { ContentPiece, diff --git a/packages/sdk/javascript/src/api/request.ts b/packages/sdk/javascript/src/api/request.ts index d3049d35..1e4bfdc5 100644 --- a/packages/sdk/javascript/src/api/request.ts +++ b/packages/sdk/javascript/src/api/request.ts @@ -19,13 +19,16 @@ type PaginationParams = { interface APIFetcher { sendRequest: SendRequest; reconfigure: (config: APIFetcherConfig) => void; + useSignal: (signal: AbortSignal | null) => void; getConfig: () => Required; + getSignal: () => AbortSignal | null; } const createAPIFetcher = (config: APIFetcherConfig): APIFetcher => { let baseURL = config.baseURL || "https://api.vrite.io"; let extensionId = config.extensionId || ""; let headers = config.headers || {}; + let signal: AbortSignal | null = null; let { token } = config; const sendRequest: SendRequest = async (method, path, options) => { @@ -49,10 +52,13 @@ const createAPIFetcher = (config: APIFetcherConfig): APIFetcher => { ...headers }, body: options?.body ? JSON.stringify(options.body) : null, + signal, method } ); + signal = null; + let json = null; try { @@ -81,6 +87,9 @@ const createAPIFetcher = (config: APIFetcherConfig): APIFetcher => { extensionId = config.extensionId || extensionId; headers = config.headers || headers; }; + const useSignal = (newSignal: AbortSignal | null): void => { + signal = newSignal; + }; const getConfig = (): Required => { return { baseURL, @@ -89,8 +98,11 @@ const createAPIFetcher = (config: APIFetcherConfig): APIFetcher => { headers }; }; + const getSignal = (): AbortSignal | null => { + return signal; + }; - return { sendRequest, reconfigure, getConfig }; + return { sendRequest, reconfigure, useSignal, getConfig, getSignal }; }; export { createAPIFetcher }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c02cf564..bc62cfef 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -283,9 +283,6 @@ importers: '@mdi/js': specifier: ^7.2.96 version: 7.2.96 - '@microsoft/fetch-event-source': - specifier: ^2.0.1 - version: 2.0.1 '@solid-primitives/scheduled': specifier: ^1.4.0 version: 1.4.0(solid-js@1.7.11) @@ -298,6 +295,9 @@ importers: '@vrite/components': specifier: workspace:* version: link:../../packages/components + '@vrite/sdk': + specifier: workspace:* + version: link:../../packages/sdk/javascript astro: specifier: ^3.1.0 version: 3.1.0(sass@1.64.1) @@ -337,6 +337,9 @@ importers: unocss: specifier: ^0.55.7 version: 0.55.7(postcss@8.4.31)(vite@4.4.9) + url-slug: + specifier: ^4.0.1 + version: 4.0.1 vite: specifier: ^4.4.9 version: 4.4.9(@types/node@20.4.4)(sass@1.64.1)(terser@5.19.2)