From 0fd43a6b223fce16293921ae1a8331085eeb7ad8 Mon Sep 17 00:00:00 2001 From: Michael Gartner Date: Fri, 9 Aug 2024 15:23:30 -0600 Subject: [PATCH 01/58] init --- package-lock.json | 11 +++ package.json | 1 + src/components/nanopub/Example.tsx | 87 ++++++++++++++++++++ src/components/nanopub/Nanopub.tsx | 95 ++++++++++++++++++++++ src/components/nanopub/exampleRdfString.ts | 55 +++++++++++++ src/index.ts | 11 +++ 6 files changed, 260 insertions(+) create mode 100644 src/components/nanopub/Example.tsx create mode 100644 src/components/nanopub/Nanopub.tsx create mode 100644 src/components/nanopub/exampleRdfString.ts diff --git a/package-lock.json b/package-lock.json index b09d775f..da3aea5f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "hasInstallScript": true, "license": "MIT", "dependencies": { + "@nanopub/sign": "^0.1.4", "@samepage/external": "^0.71.10", "@tldraw/tldraw": "^2.0.0-alpha.12", "contrast-color": "^1.0.1", @@ -1965,6 +1966,11 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@nanopub/sign": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@nanopub/sign/-/sign-0.1.4.tgz", + "integrity": "sha512-QoPBC5Fg20GcTPz/Rtlkq3E8DnwpsAIKSLaTpTp/1bTWY8ZlHZh1ik4pQSZZfUwOWiNtZTwdiO2/GtKY0krZkw==" + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -10943,6 +10949,11 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "@nanopub/sign": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@nanopub/sign/-/sign-0.1.4.tgz", + "integrity": "sha512-QoPBC5Fg20GcTPz/Rtlkq3E8DnwpsAIKSLaTpTp/1bTWY8ZlHZh1ik4pQSZZfUwOWiNtZTwdiO2/GtKY0krZkw==" + }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", diff --git a/package.json b/package.json index 6e2cc107..4b10c731 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "widgets" ], "dependencies": { + "@nanopub/sign": "^0.1.4", "@samepage/external": "^0.71.10", "@tldraw/tldraw": "^2.0.0-alpha.12", "contrast-color": "^1.0.1", diff --git a/src/components/nanopub/Example.tsx b/src/components/nanopub/Example.tsx new file mode 100644 index 00000000..eaa731d8 --- /dev/null +++ b/src/components/nanopub/Example.tsx @@ -0,0 +1,87 @@ +import React, { useEffect, useState } from "react"; +import init, { Nanopub, NpProfile } from "@nanopub/sign"; + +export default function Home() { + const [rdfOutput, setRdfOutput] = useState(""); + useEffect(() => { + // â„šī¸ You can also provide JSON-LD objects! + const rdf = { + "@context": { + "@base": "http://purl.org/nanopub/temp/mynanopub#", + np: "http://www.nanopub.org/nschema#", + npx: "http://purl.org/nanopub/x/", + rdf: "http://www.w3.org/1999/02/22-rdf-syntax-ns#", + rdfs: "http://www.w3.org/2000/01/rdf-schema#", + xsd: "http://www.w3.org/2001/XMLSchema#", + foaf: "http://xmlns.com/foaf/0.1/", + dct: "http://purl.org/dc/terms/", + prov: "http://www.w3.org/ns/prov#", + pav: "http://purl.org/pav/", + orcid: "https://orcid.org/", + schema: "https://schema.org/", + }, + "@id": "#Head", + "@graph": { + "@id": "#", + "@type": "np:Nanopublication", + "np:hasAssertion": { + "@id": "#assertion", + "@context": { + ex: "http://example.org/", + }, + "@graph": [ + { + "@id": "ex:mosquito", + "ex:transmits": { "@id": "ex:malaria" }, + }, + ], + }, + "np:hasProvenance": { + "@id": "#provenance", + "@graph": [ + { + "@id": "#assertion", + "prov:hadPrimarySource": { + "@id": "http://dx.doi.org/10.3233/ISU-2010-0613", + }, + }, + ], + }, + "np:hasPublicationInfo": { + "@id": "#pubinfo", + "@graph": [ + { + "@id": "#", + "@type": "npx:ExampleNanopub", + }, + ], + }, + }, + }; + const privateKey = `MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCjY1gsFxmak6SOCouJPuEzHNForkqFhgfHE3aAIAx+Y5q6UDEDM9Q0EksheNffJB4iPqsAfiFpY0ARQY92K5r8P4+a78eu9reYrb2WxZb1qPJmvR7XZ6sN1oHD7dd/EyQoJmQsmOKdrqaLRbzR7tZrf52yvKkwNWXcIVhW8uxe7iUgxiojZpW9srKoK/qFRpaUZSKn7Z/zgtDH9FJkYbBsGPDMqp78Kzt+sJb+U2W+wCSSy34jIUxx6QRbzvn6uexc/emFw/1DU5y7zBudhgC7mVk8vX1gUNKyjZBzlOmRcretrANgffqs5fx/TMHN1xtkA/H1u1IKBfKoyk/xThMLAgMBAAECggEAECuG0GZA3HF8OaqFgMG+W+agOvH04h4Pqv4cHjYNxnxpFcNV9nEssTKWSOvCwYy7hrwZBGV3PQzbjFmmrxVFs20+8yCD7KbyKKQZPVC0zf84bj6NTNgvr6DpGtDxINxuGaMjCt7enqhoRyRRuZ0fj2gD3Wqae/Ds8cpDCefkyMg0TvauHSUj244vGq5nt93txUv1Sa+/8tWZ77Dm0s5a3wUYB2IeAMl5WrO2GMvgzwH+zT+4kvNWg5S0Ze4KE+dG3lSIYZjo99h14LcQS9eALC/VBcAJ6pRXaCTT/TULtcLNeOpoc9Fu25f0yTsDt6Ga5ApliYkb7rDhV+OFrw1sYQKBgQDCE9so+dPg7qbp0cV+lbb7rrV43m5s9Klq0riS7u8m71oTwhmvm6gSLfjzqb8GLrmflCK4lKPDSTdwyvd+2SSmOXySw94zr1Pvc7sHdmMRyA7mH3m+zSOOgyCTTKyhDRCNcRIkysoL+DecDhNo4Fumf71tsqDYogfxpAQhn0re8wKBgQDXhMmmT2oXiMnYHhi2k7CJe3HUqkZgmW4W44SWqKHp0V6sjcHm0N0RT5Hz1BFFUd5Y0ZB3JLcah19myD1kKYCj7xz6oVLb8O7LeAZNlb0FsrtD7NU+Hciywo8qESiA7UYDkU6+hsmxaI01DsttMIdG4lSBbEjA7t4IQC5lyr7xiQKBgQCN87YGJ40Y5ZXCSgOZDepz9hqX2KGOIfnUv2HvXsIfiUwqTXs6HbD18xg3KL4myIBOvywSM+4ABYp+foY+Cpcq2btLIeZhiWjsKIrw71+Q/vIe0YDb1PGf6DsoYhmWBpdHzR9HN+hGjvwlsYny2L9Qbfhgxxmsuf7zeFLpQLijjwKBgH7TD28k8IOk5VKec2CNjKd600OYaA3UfCpP/OhDl/RmVtYoHWDcrBrRvkvEEd2/DZ8qw165Zl7gJs3vK+FTYvYVcfIzGPWA1KU7nkntwewmf3i7V8lT8ZTwVRsmObWU60ySJ8qKuwoBQodki2VX12NpMN1wgWe3qUUlr6gLJU4xAoGAet6nD3QKwk6TTmcGVfSWOzvpaDEzGkXjCLaxLKh9GreM/OE+h5aN2gUoFeQapG5rUwI/7Qq0xiLbRXw+OmfAoV2XKv7iI8DjdIh0F06mlEAwQ/B0CpbqkuuxphIbchtdcz/5ra233r3BMNIqBl3VDDVoJlgHPg9msOTRy13lFqc=`; + + // WebAssembly binary needs to be initialized only if the JS runs on the client + init().then(async () => { + const serverUrl = ""; + const profile = new NpProfile( + privateKey, + "https://orcid.org/0000-0000-0000-0000", + "User Name", + "" + ); + + const np = await new Nanopub(rdf).publish(profile, serverUrl); + console.log("Published info dict:", np.info()); + setRdfOutput(np.rdf()); + }); + }, []); + + return ( +
+

Nanopublication RDF Output:

+
+        {rdfOutput}
+      
+
+ ); +} diff --git a/src/components/nanopub/Nanopub.tsx b/src/components/nanopub/Nanopub.tsx new file mode 100644 index 00000000..a7639700 --- /dev/null +++ b/src/components/nanopub/Nanopub.tsx @@ -0,0 +1,95 @@ +import React, { useEffect, useState } from "react"; +import { Dialog, Button } from "@blueprintjs/core"; +import init, { KeyPair, Nanopub, NpProfile, getNpServer } from "@nanopub/sign"; +import renderOverlay from "roamjs-components/util/renderOverlay"; +import { getNodeEnv } from "roamjs-components/util/env"; +import rdfString from "./exampleRdfString"; + +const SERVER_URL = + getNodeEnv() === "development" + ? "" // leave blank to publish to test server + : getNpServer(false); +const PRIVATE_KEY = getNodeEnv() === "development" ? "" : ""; +const ORCID = "https://orcid.org/0000-0000-0000-0000"; +const NAME = "Test User"; +const RDF_STR = rdfString; + +const NanopubDialog = () => { + const [isWasmReady, setWasmReady] = useState(false); + + useEffect(() => { + init().then(async () => { + // Adjusted usage + console.log("WASM is ready"); + // setWasmReady(true); // Set a flag when WASM is ready + }); + }, []); + + const [isOpen, setIsOpen] = useState(true); + + const generateKeyPair = async () => { + if (!isWasmReady) { + console.error("WASM is not ready. Please wait."); + return; + } + let keyPair = new KeyPair(); + // console.log("KeyPair:", keyPair); + // return keyPair.toJs(); // Assuming you want to return the JavaScript object representation + }; + + const handleClose = () => setIsOpen(false); + + const handlePublishIntro = async () => { + try { + const profile = new NpProfile(PRIVATE_KEY, ORCID, NAME, ""); + const np = new Nanopub(RDF_STR); + const result = await np.publish(profile, SERVER_URL); + + console.log("Nanopub:", np.info()); + console.log("Published:", result.info()); + + console.log("Info"); + console.log("-----------------"); + console.log("Private Key:", PRIVATE_KEY); + console.log("ORCID:", ORCID); + console.log("Name:", NAME); + console.log("Server URL:", SERVER_URL); + // console.log("RDF:", RDF_STR); + console.log("-----------------"); + + // handleClose(); + } catch (error) { + console.error("Error publishing nanopub:", error); + } + }; + + return ( + +
+

Are you sure you want to publish this nanopub?

+
+
+
+ + + +
+
+
+ ); +}; + +export const render = () => renderOverlay({ Overlay: NanopubDialog }); diff --git a/src/components/nanopub/exampleRdfString.ts b/src/components/nanopub/exampleRdfString.ts new file mode 100644 index 00000000..9d04ad76 --- /dev/null +++ b/src/components/nanopub/exampleRdfString.ts @@ -0,0 +1,55 @@ +const rdfString = { + "@context": { + "@base": "http://purl.org/nanopub/temp/mynanopub#", + np: "http://www.nanopub.org/nschema#", + npx: "http://purl.org/nanopub/x/", + rdf: "http://www.w3.org/1999/02/22-rdf-syntax-ns#", + rdfs: "http://www.w3.org/2000/01/rdf-schema#", + xsd: "http://www.w3.org/2001/XMLSchema#", + foaf: "http://xmlns.com/foaf/0.1/", + dct: "http://purl.org/dc/terms/", + prov: "http://www.w3.org/ns/prov#", + pav: "http://purl.org/pav/", + orcid: "https://orcid.org/", + schema: "https://schema.org/", + }, + "@id": "#Head", + "@graph": { + "@id": "#", + "@type": "np:Nanopublication", + "np:hasAssertion": { + "@id": "#assertion", + "@context": { + ex: "http://example.org/", + }, + "@graph": [ + { + "@id": "ex:mosquito", + "ex:transmits": { "@id": "ex:malaria" }, + }, + ], + }, + "np:hasProvenance": { + "@id": "#provenance", + "@graph": [ + { + "@id": "#assertion", + "prov:hadPrimarySource": { + "@id": "http://dx.doi.org/10.3233/ISU-2010-0613", + }, + }, + ], + }, + "np:hasPublicationInfo": { + "@id": "#pubinfo", + "@graph": [ + { + "@id": "#", + "@type": "npx:ExampleNanopub", + }, + ], + }, + }, +}; + +export default rdfString; diff --git a/src/index.ts b/src/index.ts index 2d7d5a35..f03b48d2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -52,6 +52,7 @@ import { fireQuerySync } from "./utils/fireQuery"; import parseQuery from "./utils/parseQuery"; import { render as exportRender } from "./components/Export"; import getPageTitleByPageUid from "roamjs-components/queries/getPageTitleByPageUid"; +import { render as nanopubRender } from "./components/nanopub/Nanopub"; const loadedElsewhere = document.currentScript ? document.currentScript.getAttribute("data-source") === "discourse-graph" @@ -623,6 +624,7 @@ svg.rs-svg-container { ).then((blockUid) => queryRender({ blockUid, + // @ts-ignore clearOnClick, onloadArgs, }) @@ -722,6 +724,15 @@ svg.rs-svg-container { }, }); + // NANOPUB + extensionAPI.ui.commandPalette.addCommand({ + label: "Nanopub", + callback: () => { + console.log("Nanopub"); + nanopubRender(); + }, + }); + const renderCustomBlockView = ({ view, blockUid, From 455603f83c81a26c764113b67764b602e9e62bac Mon Sep 17 00:00:00 2001 From: Michael Gartner Date: Mon, 19 Aug 2024 17:19:44 -0600 Subject: [PATCH 02/58] additions --- src/components/nanopub/Nanopub.tsx | 141 ++++++++++++++++++++++------- src/index.ts | 28 +++--- src/utils/getDiscourseNode.ts | 10 ++ src/utils/handleTitleAdditions.ts | 39 ++++++++ 4 files changed, 170 insertions(+), 48 deletions(-) create mode 100644 src/utils/getDiscourseNode.ts create mode 100644 src/utils/handleTitleAdditions.ts diff --git a/src/components/nanopub/Nanopub.tsx b/src/components/nanopub/Nanopub.tsx index a7639700..1732e9ea 100644 --- a/src/components/nanopub/Nanopub.tsx +++ b/src/components/nanopub/Nanopub.tsx @@ -1,43 +1,112 @@ -import React, { useEffect, useState } from "react"; +import React, { useEffect, useState, useMemo } from "react"; import { Dialog, Button } from "@blueprintjs/core"; import init, { KeyPair, Nanopub, NpProfile, getNpServer } from "@nanopub/sign"; import renderOverlay from "roamjs-components/util/renderOverlay"; import { getNodeEnv } from "roamjs-components/util/env"; import rdfString from "./exampleRdfString"; - +import { OnloadArgs } from "roamjs-components/types"; +import { handleTitleAdditions } from "../../utils/handleTitleAdditions"; +import matchDiscourseNode from "../../utils/matchDiscourseNode"; +import getDiscourseNode from "../../utils/getDiscourseNode"; +import isDiscourseNode from "../../utils/isDiscourseNode"; +import getPageTitleByPageUid from "roamjs-components/queries/getPageTitleByPageUid"; +import getCurrentUserDisplayName from "roamjs-components/queries/getCurrentUserDisplayName"; +import getDiscourseContextResults from "../../utils/getDiscourseContextResults"; +import { DiscourseContextResults } from "../DiscourseContext"; const SERVER_URL = getNodeEnv() === "development" ? "" // leave blank to publish to test server : getNpServer(false); -const PRIVATE_KEY = getNodeEnv() === "development" ? "" : ""; +const PRIVATE_KEY = + getNodeEnv() === "development" + ? "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCjY1gsFxmak6SOCouJPuEzHNForkqFhgfHE3aAIAx" + : ""; const ORCID = "https://orcid.org/0000-0000-0000-0000"; const NAME = "Test User"; const RDF_STR = rdfString; -const NanopubDialog = () => { - const [isWasmReady, setWasmReady] = useState(false); +export const NanoPubTitleButtons = ({ uid }: { uid: string }) => { + return ( +
+
+ ); +}; - useEffect(() => { - init().then(async () => { - // Adjusted usage - console.log("WASM is ready"); - // setWasmReady(true); // Set a flag when WASM is ready - }); - }, []); +const DiscourseContextSection = ({ + discourseContext, +}: { + discourseContext: DiscourseContextResults; +}) => { + return ( + <> + {discourseContext.map((context, index) => { + const hasResults = Object.entries(context.results).length > 0; - const [isOpen, setIsOpen] = useState(true); + if (!hasResults) return null; - const generateKeyPair = async () => { - if (!isWasmReady) { - console.error("WASM is not ready. Please wait."); - return; - } - let keyPair = new KeyPair(); - // console.log("KeyPair:", keyPair); - // return keyPair.toJs(); // Assuming you want to return the JavaScript object representation - }; + return ( +
+
+ {context.label.toLowerCase()}: +
+
+ {Object.values(context.results).map((value, idx) => ( +
{value.text}
+ ))} +
+
+ ); + })} + + ); +}; + +const LabelValuePair = ({ label, value }: { label: string; value: string }) => ( +
+
{label}:
+
{value}
+
+); +const NanopubDialog = ({ uid }: { uid: string }) => { + const [isOpen, setIsOpen] = useState(true); const handleClose = () => setIsOpen(false); + const [discourseContext, setDiscourseContext] = + useState([]); + + const node = useMemo(() => getDiscourseNode(uid), [uid]); + const title = useMemo(() => getPageTitleByPageUid(uid), [uid]); + const name = useMemo(() => getCurrentUserDisplayName(), []); + const pageUrl = `https://roamresearch.com/${window.roamAlphaAPI.graph.name}/page/${uid}`; + useEffect(() => { + const fetchDiscourseContext = async () => { + const results = await getDiscourseContextResults({ uid }); + setDiscourseContext(results); + }; + fetchDiscourseContext(); + }, [uid]); + const ORCID = "0000-0000-0000-0000"; const handlePublishIntro = async () => { try { @@ -70,26 +139,34 @@ const NanopubDialog = () => { title="Nanopub Publication" autoFocus={false} enforceFocus={false} + className="w-full sm:w-full md:w-3/4 lg:w-full lg:max-w-5xl" > -
-

Are you sure you want to publish this nanopub?

-
+ {node ? ( +
+
+ + + + + {/* */} + + +
+
+ ) : ( +

No node found

+ )}
-
); }; -export const render = () => renderOverlay({ Overlay: NanopubDialog }); +export const render = ({ uid }: { uid: string }) => + renderOverlay({ Overlay: NanopubDialog, props: { uid } }); diff --git a/src/index.ts b/src/index.ts index f03b48d2..7eda9e30 100644 --- a/src/index.ts +++ b/src/index.ts @@ -52,7 +52,11 @@ import { fireQuerySync } from "./utils/fireQuery"; import parseQuery from "./utils/parseQuery"; import { render as exportRender } from "./components/Export"; import getPageTitleByPageUid from "roamjs-components/queries/getPageTitleByPageUid"; -import { render as nanopubRender } from "./components/nanopub/Nanopub"; +import { + render as nanopubRender, + NanoPubTitleButtons, +} from "./components/nanopub/Nanopub"; +import { handleTitleAdditions } from "./utils/handleTitleAdditions"; const loadedElsewhere = document.currentScript ? document.currentScript.getAttribute("data-source") === "discourse-graph" @@ -206,24 +210,11 @@ svg.rs-svg-container { const title = getPageTitleValueByHtmlElement(h1); if (!!extensionAPI.settings.get("show-page-metadata")) { const { displayName, date } = getPageMetadata(title); - const container = document.createElement("div"); - const oldMarginBottom = getComputedStyle(h1).marginBottom; - container.style.marginTop = `${ - 4 - Number(oldMarginBottom.replace("px", "")) / 2 - }px`; - container.style.marginBottom = oldMarginBottom; const label = document.createElement("i"); label.innerText = `Created by ${ displayName || "Anonymous" } on ${date.toLocaleString()}`; - container.appendChild(label); - if (h1.parentElement) { - if (h1.parentElement.lastChild === h1) { - h1.parentElement.appendChild(container); - } else { - h1.parentElement.insertBefore(container, h1.nextSibling); - } - } + handleTitleAdditions(h1, label); } if (title.startsWith("discourse-graph/nodes/")) { @@ -261,6 +252,10 @@ svg.rs-svg-container { renderPlayground(title, globalRefs); } else if (isCanvasPage(title) && !!h1.closest(".roam-article")) { renderTldrawCanvas(title, onloadArgs); + } else if (true) { + const uid = getPageUidByPageTitle(title); + if (uid.length === 9) + handleTitleAdditions(h1, NanoPubTitleButtons({ uid })); } }; extensionAPI.settings.panel.create({ @@ -729,7 +724,8 @@ svg.rs-svg-container { label: "Nanopub", callback: () => { console.log("Nanopub"); - nanopubRender(); + const uid = getCurrentPageUid(); + nanopubRender({ uid }); }, }); diff --git a/src/utils/getDiscourseNode.ts b/src/utils/getDiscourseNode.ts new file mode 100644 index 00000000..5e92447d --- /dev/null +++ b/src/utils/getDiscourseNode.ts @@ -0,0 +1,10 @@ +import findDiscourseNode from "./findDiscourseNode"; +import getDiscourseNodes from "./getDiscourseNodes"; + +const isDiscourseNode = (uid: string) => { + const nodes = getDiscourseNodes(); + const node = findDiscourseNode(uid, nodes); + return node || null; +}; + +export default isDiscourseNode; diff --git a/src/utils/handleTitleAdditions.ts b/src/utils/handleTitleAdditions.ts new file mode 100644 index 00000000..31c96a62 --- /dev/null +++ b/src/utils/handleTitleAdditions.ts @@ -0,0 +1,39 @@ +import React from "react"; +import ReactDOM from "react-dom"; + +export const handleTitleAdditions = ( + h1: HTMLHeadingElement, + element: React.ReactNode +) => { + let container = h1.parentElement?.querySelector( + ".query-builder-title-additions" + ) as HTMLElement; + + if (!container) { + container = document.createElement("div"); + container.className = "query-builder-title-additions flex flex-col"; + + const oldMarginBottom = getComputedStyle(h1).marginBottom; + const oldMarginBottomNum = Number(oldMarginBottom.replace("px", "")); + const newMarginTop = `${4 - oldMarginBottomNum / 2}px`; + + container.style.marginTop = newMarginTop; + container.style.marginBottom = oldMarginBottom; + + if (h1.parentElement) { + if (h1.parentElement.lastChild === h1) { + h1.parentElement.appendChild(container); + } else { + h1.parentElement.insertBefore(container, h1.nextSibling); + } + } + } + + if (React.isValidElement(element)) { + const renderContainer = document.createElement("div"); + container.appendChild(renderContainer); + ReactDOM.render(element, renderContainer); + } else { + container.appendChild(element as HTMLElement); + } +}; From 9ddb742dee8a21c7a321df22a1498690135bc300 Mon Sep 17 00:00:00 2001 From: Michael Gartner Date: Tue, 20 Aug 2024 16:22:44 -0600 Subject: [PATCH 03/58] nanopub node config --- src/components/nanopub/NanopubConfigPanel.tsx | 353 ++++++++++++++++++ src/data/defaultNanopubTemplates.ts | 26 ++ src/discourseGraphsMode.ts | 16 + 3 files changed, 395 insertions(+) create mode 100644 src/components/nanopub/NanopubConfigPanel.tsx create mode 100644 src/data/defaultNanopubTemplates.ts diff --git a/src/components/nanopub/NanopubConfigPanel.tsx b/src/components/nanopub/NanopubConfigPanel.tsx new file mode 100644 index 00000000..7af44ee5 --- /dev/null +++ b/src/components/nanopub/NanopubConfigPanel.tsx @@ -0,0 +1,353 @@ +import React, { useState, useRef, useMemo, useCallback } from "react"; +import { + Button, + H6, + InputGroup, + Label, + Switch, + Dialog, + Classes, + FormGroup, +} from "@blueprintjs/core"; +import getBasicTreeByParentUid from "roamjs-components/queries/getBasicTreeByParentUid"; +import createBlock from "roamjs-components/writes/createBlock"; +import deleteBlock from "roamjs-components/writes/deleteBlock"; +import updateBlock from "roamjs-components/writes/updateBlock"; +import getFirstChildUidByBlockUid from "roamjs-components/queries/getFirstChildUidByBlockUid"; +import getSubTree from "roamjs-components/util/getSubTree"; +import AutocompleteInput from "roamjs-components/components/AutocompleteInput"; +import { defaultNanopubTemplate } from "../../data/defaultNanopubTemplates"; +import { DiscourseNode } from "../../utils/getDiscourseNodes"; +import { OnloadArgs } from "roamjs-components/types"; + +const placeholders = { + nodeType: "Type of Discourse Node", + title: "Page Title", + body: "Page Body", + url: "Current page URL", + myORCID: "Your ORCID identifier", + createdBy: "Creator of the page", +}; +const defaultPredicates = [ + "is a", + "has the label", + "has the description", + "has more info at", + "is attributed to", + "is created by", +]; + +type Triple = { + uid: string; + predicate: string; + object: string; +}; + +const TripleInput = React.memo( + ({ + triple, + onChange, + onDelete, + enabled, + node, + }: { + triple: Triple; + onChange: (field: keyof Triple, value: string) => void; + onDelete: () => void; + enabled: boolean; + node: DiscourseNode; + }) => { + // console.log("did render", triple); + const { uid, predicate, object } = triple; + const timeoutRef = useRef(0); + + // TODO: onChange this causes a loop. but is not required? + // TODO: there is still some aggressive updating going via on setValue when adding/deleting + // deleting sometimes results in "Uncaught (in promise) Error: Assert failed: `uid` argument was not passed or invalid." + // I split these into two functions to try and understand/address the aggressive updating + const handlePredicateUpdate = useCallback( + (value: string, timeout: boolean = true) => { + // console.log("predicate", value, uid); + clearTimeout(timeoutRef.current); + // onChange(field, value); + timeoutRef.current = window.setTimeout( + () => { + updateBlock({ text: value, uid }); + }, + timeout ? 1000 : 0 + ); + }, + [onChange, uid] + ); + const handleObjectUpdate = useCallback( + (value: string, timeout: boolean = true) => { + // console.log("object", value, uid); + clearTimeout(timeoutRef.current); + // onChange(field, value); + timeoutRef.current = window.setTimeout( + () => { + updateBlock({ text: value, uid: getFirstChildUidByBlockUid(uid) }); + }, + timeout ? 1000 : 0 + ); + }, + [onChange, uid] + ); + + return ( +
+ + handlePredicateUpdate(v, false)} + id={`${uid}-predicate`} + disabled={!enabled} + options={defaultPredicates} + /> + handleObjectUpdate(v, false)} + id={`${uid}-object`} + options={Object.keys(placeholders).map((key) => `{${key}}`)} + disabled={!enabled} + /> +
+ ); + } +); + +const NanopubConfigPanel = ({ + uid, + node, + onloadArgs, +}: { + uid: string; + node: DiscourseNode; + onloadArgs: OnloadArgs; +}) => { + const tree = useMemo(() => getBasicTreeByParentUid(uid), [uid]); + const [enablePublishing, setEnablePublishing] = useState( + () => !!getSubTree({ tree, key: "enabled" }).uid + ); + + const [triplesUid, setTriplesUid] = useState( + () => getSubTree({ tree, key: "triples" }).uid || null + ); + const [triples, setTriples] = useState(() => + triplesUid + ? getBasicTreeByParentUid(triplesUid).map((t) => ({ + uid: t.uid, + predicate: t.text, + object: t.children[0]?.text || "", + })) + : [] + ); + const initialOrcid = onloadArgs.extensionAPI.settings.get("orcid") as string; + + const isDefaultTemplate = useMemo(() => { + if (triples.length !== defaultNanopubTemplate.length) return false; + return triples.every((triple, index) => { + const defaultTriple = defaultNanopubTemplate[index]; + return ( + triple.predicate === defaultTriple.predicate && + triple.object === defaultTriple.object + ); + }); + }, [triples]); + const [isWarningDialogOpen, setIsWarningDialogOpen] = useState(false); + + const setIsEnabled = useCallback( + (b: boolean) => { + setEnablePublishing(b); + return b + ? createBlock({ + parentUid: uid, + node: { text: "enabled" }, + }) + : deleteBlock(getSubTree({ parentUid: uid, key: "enabled" }).uid); + }, + [uid] + ); + + const addTriple = async () => { + const effectiveTriplesUid = triplesUid + ? triplesUid + : await createBlock({ + parentUid: uid, + node: { text: "triples" }, + }); + + const predicateUid = await createBlock({ + node: { text: "" }, + parentUid: effectiveTriplesUid, + order: "last", + }); + + await createBlock({ + node: { text: "" }, + parentUid: predicateUid, + }); + + setTriples([...triples, { uid: predicateUid, predicate: "", object: "" }]); + }; + const updateTriple = useCallback( + (uid: string, field: keyof Triple, value: string) => { + setTriples( + triples.map((triple) => + triple.uid === uid ? { ...triple, [field]: value } : triple + ) + ); + }, + [triples] + ); + const removeTriple = useCallback( + (uid: string) => { + deleteBlock(uid).then(() => { + setTriples(triples.filter((triple) => triple.uid !== uid)); + }); + }, + [triples] + ); + + const handleConfirmUseDefault = async () => { + if (triplesUid) deleteBlock(triplesUid); + + const defaultTriples = defaultNanopubTemplate.map((triple) => ({ + predicate: triple.predicate, + predicateUid: window.roamAlphaAPI.util.generateUID(), + object: triple.object, + objectUid: window.roamAlphaAPI.util.generateUID(), + })); + + const newTriplesUid = await createBlock({ + parentUid: uid, + node: { + text: "triples", + children: defaultTriples.map((triple) => ({ + text: triple.predicate, + uid: triple.predicateUid, + children: [ + { + text: triple.object, + uid: triple.objectUid, + }, + ], + })), + }, + }); + + setTriplesUid(newTriplesUid); + setTriples( + defaultTriples.map((triple) => ({ + uid: triple.predicateUid, + predicate: triple.predicate, + object: triple.object, + })) + ); + setIsWarningDialogOpen(false); + }; + + return ( + <> +
+ { + setIsEnabled(!enablePublishing); + }} + className="mb-4" + /> + + { + console.log("orcid", e.target.value); + onloadArgs.extensionAPI.settings.set("orcid", e.target.value); + }} + className="mb-4" + disabled={!enablePublishing} + /> + + +
Template
+ {triples.map((triple) => ( + updateTriple(triple.uid, field, value)} + onDelete={() => removeTriple(triple.uid)} + enabled={enablePublishing} + /> + ))} + +
+ + {/* Default Template Warning Dialog */} + setIsWarningDialogOpen(false)} + > +
+

Are you Sure?

+

+ This will replace the current triples with the default template. +

+
+
+
+ + +
+
+
+ + ); +}; + +export default NanopubConfigPanel; diff --git a/src/data/defaultNanopubTemplates.ts b/src/data/defaultNanopubTemplates.ts new file mode 100644 index 00000000..f9e4208e --- /dev/null +++ b/src/data/defaultNanopubTemplates.ts @@ -0,0 +1,26 @@ +export const defaultNanopubTemplate = [ + { + predicate: "is a", + object: "{nodeType}", + }, + { + predicate: "has the label", + object: "{title}", + }, + { + predicate: "has the description", + object: "{body}", + }, + { + predicate: "has more info at", + object: "{url}", + }, + { + predicate: "is attributed to", + object: "{myORCID}", + }, + { + predicate: "is created by", + object: "{createdBy}", + }, +]; diff --git a/src/discourseGraphsMode.ts b/src/discourseGraphsMode.ts index 9e19abdf..fdb32b08 100644 --- a/src/discourseGraphsMode.ts +++ b/src/discourseGraphsMode.ts @@ -57,6 +57,7 @@ import fireQuery from "./utils/fireQuery"; import { render as renderGraphOverviewExport } from "./components/ExportDiscourseContext"; import { Condition, QBClause } from "./utils/types"; import { DiscourseExportResult } from "./utils/getExportTypes"; +import NanopubConfigPanel from "./components/nanopub/NanopubConfigPanel"; export const SETTING = "discourse-graphs"; @@ -249,6 +250,21 @@ export const renderDiscourseNodeTypeConfigPage = ({ description: `Whether to color the node in the graph overview based on canvas color`, defaultValue: true, } as FieldPanel, + // @ts-ignore + { + title: "Nanopub", + description: + "Configure the Nanopub triple template for this node type", + Panel: CustomPanel, + options: { + component: (props) => + React.createElement(NanopubConfigPanel, { + ...props, + node, + onloadArgs, + }), + }, + } as Field, ], }); From b92bf0275b198d2e11b5f0e242c2d871536987aa Mon Sep 17 00:00:00 2001 From: Michael Gartner Date: Tue, 20 Aug 2024 17:39:22 -0600 Subject: [PATCH 04/58] add nanopub to getDiscourseNodes, use template in dialog --- src/components/nanopub/Nanopub.tsx | 97 +++++++++++++------ src/components/nanopub/NanopubConfigPanel.tsx | 10 +- src/index.ts | 5 +- src/utils/getDiscourseNodes.ts | 32 ++++++ 4 files changed, 108 insertions(+), 36 deletions(-) diff --git a/src/components/nanopub/Nanopub.tsx b/src/components/nanopub/Nanopub.tsx index 1732e9ea..1f29a3fd 100644 --- a/src/components/nanopub/Nanopub.tsx +++ b/src/components/nanopub/Nanopub.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState, useMemo } from "react"; +import React, { useEffect, useState, useMemo, useCallback } from "react"; import { Dialog, Button } from "@blueprintjs/core"; import init, { KeyPair, Nanopub, NpProfile, getNpServer } from "@nanopub/sign"; import renderOverlay from "roamjs-components/util/renderOverlay"; @@ -13,6 +13,9 @@ import getPageTitleByPageUid from "roamjs-components/queries/getPageTitleByPageU import getCurrentUserDisplayName from "roamjs-components/queries/getCurrentUserDisplayName"; import getDiscourseContextResults from "../../utils/getDiscourseContextResults"; import { DiscourseContextResults } from "../DiscourseContext"; +import ExtensionApiContextProvider, { + useExtensionAPI, +} from "roamjs-components/components/ExtensionApiContext"; const SERVER_URL = getNodeEnv() === "development" ? "" // leave blank to publish to test server @@ -25,7 +28,13 @@ const ORCID = "https://orcid.org/0000-0000-0000-0000"; const NAME = "Test User"; const RDF_STR = rdfString; -export const NanoPubTitleButtons = ({ uid }: { uid: string }) => { +export const NanoPubTitleButtons = ({ + uid, + onloadArgs, +}: { + uid: string; + onloadArgs: OnloadArgs; +}) => { return (
+ {/* */} +
+ + + ); +}; + +const ContributorRow = ({ + contributor, + key, + // isEditing, + userDisplayNames, + setContributors, +}: { + contributor: Contributor; + key: string; + // isEditing: boolean; + setContributors: React.Dispatch>; + userDisplayNames: string[]; +}) => { + const setContributorName = useCallback( + (newName: string) => { + setContributors((_contributors) => + _contributors.map((con) => + con.id === contributor.id ? { ...con, name: newName } : con + ) + ); + }, + [contributor.id, setContributors] + ); + + const setContributorRoles = useCallback((v, contributor, remove = false) => { + setContributors((_contributors) => + _contributors.map((c) => + contributor.id === c.id + ? { + ...c, + roles: remove + ? c.roles?.filter((r) => r !== v) + : [...(c.roles || []), v], + } + : c + ) + ); + }, []); + + const removeContributor = useCallback(() => { + setContributors((_contributors) => + _contributors.filter((c) => c.id !== contributor.id) + ); + }, [contributor.id, setContributors]); + + return ( +
+ + setContributorRoles(item, contributor)} + tagRenderer={(item) => item} + itemRenderer={(item, { modifiers, handleClick }) => { + if (contributor.roles?.includes(item)) return null; + if (!modifiers.matchesPredicate) return null; + return ( +
+ ); +}; + +export default ContributorManager; diff --git a/src/components/nanopub/Nanopub.tsx b/src/components/nanopub/Nanopub.tsx index 1f29a3fd..cdee8b39 100644 --- a/src/components/nanopub/Nanopub.tsx +++ b/src/components/nanopub/Nanopub.tsx @@ -16,6 +16,7 @@ import { DiscourseContextResults } from "../DiscourseContext"; import ExtensionApiContextProvider, { useExtensionAPI, } from "roamjs-components/components/ExtensionApiContext"; +import ContributorManager from "./ContributorManager"; const SERVER_URL = getNodeEnv() === "development" ? "" // leave blank to publish to test server @@ -180,6 +181,7 @@ const NanopubDialog = ({ {node ? (
+ {templateTriples?.map((triple) => ( Date: Wed, 28 Aug 2024 00:13:39 -0600 Subject: [PATCH 07/58] rename nodeconfig --- ...bConfigPanel.tsx => NanopubNodeConfig.tsx} | 32 ++++++++++++++----- src/utils/getDiscourseNodes.ts | 4 +-- 2 files changed, 26 insertions(+), 10 deletions(-) rename src/components/nanopub/{NanopubConfigPanel.tsx => NanopubNodeConfig.tsx} (91%) diff --git a/src/components/nanopub/NanopubConfigPanel.tsx b/src/components/nanopub/NanopubNodeConfig.tsx similarity index 91% rename from src/components/nanopub/NanopubConfigPanel.tsx rename to src/components/nanopub/NanopubNodeConfig.tsx index 4634f00b..cabcf39b 100644 --- a/src/components/nanopub/NanopubConfigPanel.tsx +++ b/src/components/nanopub/NanopubNodeConfig.tsx @@ -19,6 +19,8 @@ import AutocompleteInput from "roamjs-components/components/AutocompleteInput"; import { defaultNanopubTemplate } from "../../data/defaultNanopubTemplates"; import { DiscourseNode } from "../../utils/getDiscourseNodes"; import { OnloadArgs } from "roamjs-components/types"; +import useSingleChildValue from "roamjs-components/components/ConfigPanels/useSingleChildValue"; +import getFirstChildTextByBlockUid from "roamjs-components/queries/getFirstChildTextByBlockUid"; const placeholders = { nodeType: "Type of Discourse Node", @@ -147,7 +149,24 @@ const NanopubConfigPanel = ({ })) : [] ); - const initialOrcid = onloadArgs.extensionAPI.settings.get("orcid") as string; + const nodeTypeUid = useMemo( + () => tree.find((t) => t.text === "nodeType")?.uid, + [tree] + ); + const defaultNodeType = useMemo(() => { + if (!nodeTypeUid) return ""; + return getFirstChildTextByBlockUid(nodeTypeUid); + }, [nodeTypeUid]); + + const { value: nodeTypeValue, onChange } = useSingleChildValue({ + uid: nodeTypeUid, + defaultValue: defaultNodeType, + title: "nodeType", + parentUid: uid, + order: 0, + transform: (s) => s, + toStr: (s) => s, + }); const isDefaultTemplate = useMemo(() => { if (triples.length !== defaultNanopubTemplate.length) return false; @@ -263,14 +282,11 @@ const NanopubConfigPanel = ({ }} className="mb-4" /> - + { - console.log("orcid", e.target.value); - onloadArgs.extensionAPI.settings.set("orcid", e.target.value); - }} + placeholder="Enter URL to node type definition" + value={nodeTypeValue} + onChange={(e) => onChange(e.target.value)} className="mb-4" disabled={!enablePublishing} /> diff --git a/src/utils/getDiscourseNodes.ts b/src/utils/getDiscourseNodes.ts index 34e79205..a1ba6d28 100644 --- a/src/utils/getDiscourseNodes.ts +++ b/src/utils/getDiscourseNodes.ts @@ -5,7 +5,7 @@ import getDiscourseRelations from "./getDiscourseRelations"; import parseQuery from "./parseQuery"; import { Condition } from "./types"; import { RoamBasicNode } from "roamjs-components/types"; -import { NanopubTriple } from "../components/nanopub/NanopubConfigPanel"; +import { NanopubTriple } from "../components/nanopub/NanopubNodeConfig"; // TODO - only text and type should be required export type DiscourseNode = { @@ -20,7 +20,7 @@ export type DiscourseNode = { // @deprecated - use specification instead format: string; graphOverview?: boolean; - nanopub: { + nanopub?: { enabled: boolean; triples: NanopubTriple[]; }; From 2326a56af6658f88fc16ce4ceca43028af2d3d14 Mon Sep 17 00:00:00 2001 From: Michael Gartner Date: Wed, 28 Aug 2024 02:31:50 -0600 Subject: [PATCH 08/58] get publish working --- src/components/nanopub/Nanopub.tsx | 168 +++++++++++++++++++++++++---- 1 file changed, 148 insertions(+), 20 deletions(-) diff --git a/src/components/nanopub/Nanopub.tsx b/src/components/nanopub/Nanopub.tsx index cdee8b39..460d2b0a 100644 --- a/src/components/nanopub/Nanopub.tsx +++ b/src/components/nanopub/Nanopub.tsx @@ -1,6 +1,16 @@ import React, { useEffect, useState, useMemo, useCallback } from "react"; import { Dialog, Button } from "@blueprintjs/core"; -import init, { KeyPair, Nanopub, NpProfile, getNpServer } from "@nanopub/sign"; + +// web.js:1014 Uncaught (in promise) TypeError: Failed to construct 'URL': Invalid URL +// at __wbg_init (web.js:1014:17) +// at Nanopub.tsx:200:7 +// import init, { KeyPair, Nanopub, NpProfile, getNpServer } from "@nanopub/sign"; +import init, { + Nanopub, + NpProfile, + getNpServer, + KeyPair, +} from "https://unpkg.com/@nanopub/sign"; import renderOverlay from "roamjs-components/util/renderOverlay"; import { getNodeEnv } from "roamjs-components/util/env"; import rdfString from "./exampleRdfString"; @@ -23,7 +33,7 @@ const SERVER_URL = : getNpServer(false); const PRIVATE_KEY = getNodeEnv() === "development" - ? "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCjY1gsFxmak6SOCouJPuEzHNForkqFhgfHE3aAIAx" + ? "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCjY1gsFxmak6SOCouJPuEzHNForkqFhgfHE3aAIAx+Y5q6UDEDM9Q0EksheNffJB4iPqsAfiFpY0ARQY92K5r8P4+a78eu9reYrb2WxZb1qPJmvR7XZ6sN1oHD7dd/EyQoJmQsmOKdrqaLRbzR7tZrf52yvKkwNWXcIVhW8uxe7iUgxiojZpW9srKoK/qFRpaUZSKn7Z/zgtDH9FJkYbBsGPDMqp78Kzt+sJb+U2W+wCSSy34jIUxx6QRbzvn6uexc/emFw/1DU5y7zBudhgC7mVk8vX1gUNKyjZBzlOmRcretrANgffqs5fx/TMHN1xtkA/H1u1IKBfKoyk/xThMLAgMBAAECggEAECuG0GZA3HF8OaqFgMG+W+agOvH04h4Pqv4cHjYNxnxpFcNV9nEssTKWSOvCwYy7hrwZBGV3PQzbjFmmrxVFs20+8yCD7KbyKKQZPVC0zf84bj6NTNgvr6DpGtDxINxuGaMjCt7enqhoRyRRuZ0fj2gD3Wqae/Ds8cpDCefkyMg0TvauHSUj244vGq5nt93txUv1Sa+/8tWZ77Dm0s5a3wUYB2IeAMl5WrO2GMvgzwH+zT+4kvNWg5S0Ze4KE+dG3lSIYZjo99h14LcQS9eALC/VBcAJ6pRXaCTT/TULtcLNeOpoc9Fu25f0yTsDt6Ga5ApliYkb7rDhV+OFrw1sYQKBgQDCE9so+dPg7qbp0cV+lbb7rrV43m5s9Klq0riS7u8m71oTwhmvm6gSLfjzqb8GLrmflCK4lKPDSTdwyvd+2SSmOXySw94zr1Pvc7sHdmMRyA7mH3m+zSOOgyCTTKyhDRCNcRIkysoL+DecDhNo4Fumf71tsqDYogfxpAQhn0re8wKBgQDXhMmmT2oXiMnYHhi2k7CJe3HUqkZgmW4W44SWqKHp0V6sjcHm0N0RT5Hz1BFFUd5Y0ZB3JLcah19myD1kKYCj7xz6oVLb8O7LeAZNlb0FsrtD7NU+Hciywo8qESiA7UYDkU6+hsmxaI01DsttMIdG4lSBbEjA7t4IQC5lyr7xiQKBgQCN87YGJ40Y5ZXCSgOZDepz9hqX2KGOIfnUv2HvXsIfiUwqTXs6HbD18xg3KL4myIBOvywSM+4ABYp+foY+Cpcq2btLIeZhiWjsKIrw71+Q/vIe0YDb1PGf6DsoYhmWBpdHzR9HN+hGjvwlsYny2L9Qbfhgxxmsuf7zeFLpQLijjwKBgH7TD28k8IOk5VKec2CNjKd600OYaA3UfCpP/OhDl/RmVtYoHWDcrBrRvkvEEd2/DZ8qw165Zl7gJs3vK+FTYvYVcfIzGPWA1KU7nkntwewmf3i7V8lT8ZTwVRsmObWU60ySJ8qKuwoBQodki2VX12NpMN1wgWe3qUUlr6gLJU4xAoGAet6nD3QKwk6TTmcGVfSWOzvpaDEzGkXjCLaxLKh9GreM/OE+h5aN2gUoFeQapG5rUwI/7Qq0xiLbRXw+OmfAoV2XKv7iI8DjdIh0F06mlEAwQ/B0CpbqkuuxphIbchtdcz/5ra233r3BMNIqBl3VDDVoJlgHPg9msOTRy13lFqc=" : ""; const ORCID = "https://orcid.org/0000-0000-0000-0000"; const NAME = "Test User"; @@ -145,23 +155,39 @@ const NanopubDialog = ({ // fetchDiscourseContext(); // }, [uid]); - const handlePublishIntro = async () => { + const handlePublish = async () => { try { - const profile = new NpProfile(PRIVATE_KEY, ORCID, NAME, ""); - const np = new Nanopub(RDF_STR); - const result = await np.publish(profile, SERVER_URL); + const privateKey = `MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCjY1gsFxmak6SOCouJPuEzHNForkqFhgfHE3aAIAx+Y5q6UDEDM9Q0EksheNffJB4iPqsAfiFpY0ARQY92K5r8P4+a78eu9reYrb2WxZb1qPJmvR7XZ6sN1oHD7dd/EyQoJmQsmOKdrqaLRbzR7tZrf52yvKkwNWXcIVhW8uxe7iUgxiojZpW9srKoK/qFRpaUZSKn7Z/zgtDH9FJkYbBsGPDMqp78Kzt+sJb+U2W+wCSSy34jIUxx6QRbzvn6uexc/emFw/1DU5y7zBudhgC7mVk8vX1gUNKyjZBzlOmRcretrANgffqs5fx/TMHN1xtkA/H1u1IKBfKoyk/xThMLAgMBAAECggEAECuG0GZA3HF8OaqFgMG+W+agOvH04h4Pqv4cHjYNxnxpFcNV9nEssTKWSOvCwYy7hrwZBGV3PQzbjFmmrxVFs20+8yCD7KbyKKQZPVC0zf84bj6NTNgvr6DpGtDxINxuGaMjCt7enqhoRyRRuZ0fj2gD3Wqae/Ds8cpDCefkyMg0TvauHSUj244vGq5nt93txUv1Sa+/8tWZ77Dm0s5a3wUYB2IeAMl5WrO2GMvgzwH+zT+4kvNWg5S0Ze4KE+dG3lSIYZjo99h14LcQS9eALC/VBcAJ6pRXaCTT/TULtcLNeOpoc9Fu25f0yTsDt6Ga5ApliYkb7rDhV+OFrw1sYQKBgQDCE9so+dPg7qbp0cV+lbb7rrV43m5s9Klq0riS7u8m71oTwhmvm6gSLfjzqb8GLrmflCK4lKPDSTdwyvd+2SSmOXySw94zr1Pvc7sHdmMRyA7mH3m+zSOOgyCTTKyhDRCNcRIkysoL+DecDhNo4Fumf71tsqDYogfxpAQhn0re8wKBgQDXhMmmT2oXiMnYHhi2k7CJe3HUqkZgmW4W44SWqKHp0V6sjcHm0N0RT5Hz1BFFUd5Y0ZB3JLcah19myD1kKYCj7xz6oVLb8O7LeAZNlb0FsrtD7NU+Hciywo8qESiA7UYDkU6+hsmxaI01DsttMIdG4lSBbEjA7t4IQC5lyr7xiQKBgQCN87YGJ40Y5ZXCSgOZDepz9hqX2KGOIfnUv2HvXsIfiUwqTXs6HbD18xg3KL4myIBOvywSM+4ABYp+foY+Cpcq2btLIeZhiWjsKIrw71+Q/vIe0YDb1PGf6DsoYhmWBpdHzR9HN+hGjvwlsYny2L9Qbfhgxxmsuf7zeFLpQLijjwKBgH7TD28k8IOk5VKec2CNjKd600OYaA3UfCpP/OhDl/RmVtYoHWDcrBrRvkvEEd2/DZ8qw165Zl7gJs3vK+FTYvYVcfIzGPWA1KU7nkntwewmf3i7V8lT8ZTwVRsmObWU60ySJ8qKuwoBQodki2VX12NpMN1wgWe3qUUlr6gLJU4xAoGAet6nD3QKwk6TTmcGVfSWOzvpaDEzGkXjCLaxLKh9GreM/OE+h5aN2gUoFeQapG5rUwI/7Qq0xiLbRXw+OmfAoV2XKv7iI8DjdIh0F06mlEAwQ/B0CpbqkuuxphIbchtdcz/5ra233r3BMNIqBl3VDDVoJlgHPg9msOTRy13lFqc=`; + + const serverUrl = ""; + const profile = new NpProfile( + privateKey, + "https://orcid.org/0000-0000-0000-0000", + "User Name", + "" + ); + + const np = await new Nanopub(RDF_STR).publish(profile, serverUrl); + console.log("Published info dict:", np.info()); + // init().then(async () => { + // const profile = new NpProfile(PRIVATE_KEY, ORCID, NAME, ""); - console.log("Nanopub:", np.info()); - console.log("Published:", result.info()); + // const np = new Nanopub(RDF_STR); - console.log("Info"); - console.log("-----------------"); - console.log("Private Key:", PRIVATE_KEY); - console.log("ORCID:", ORCID); - console.log("Name:", NAME); - console.log("Server URL:", SERVER_URL); - // console.log("RDF:", RDF_STR); - console.log("-----------------"); + // const result = await np.publish(profile, SERVER_URL); + + // console.log("Nanopub:", np.info()); + // console.log("Published:", result.info()); + + // console.log("Info"); + // console.log("-----------------"); + // console.log("Private Key:", PRIVATE_KEY); + // console.log("ORCID:", ORCID); + // console.log("Name:", NAME); + // console.log("Server URL:", SERVER_URL); + // // console.log("RDF:", RDF_STR); + // console.log("-----------------"); + // }); // handleClose(); } catch (error) { @@ -169,6 +195,72 @@ const NanopubDialog = ({ } }; + // DEBUG + // DEBUG + // DEBUG + const [rdfOutput, setRdfOutput] = useState(""); + const [checkedOutput, setCheckedOutput] = useState(""); + const [signedOutput, setSignedOutput] = useState(""); + const [publishedOutput, setPublishedOutput] = useState(""); + const [publishedURL, setPublishedURL] = useState(""); + const [keyPair, setKeyPair] = useState(null); + + useEffect(() => { + try { + // @ts-ignore + init().then(() => { + console.log("WASM Initialized"); + }); + } catch (error) { + console.error("Error initializing WASM:", error); + } + }, []); + + const generateKeyPair = () => { + const keypair = new KeyPair().toJs(); + setKeyPair(keypair); + console.log(keypair); + }; + + const checkNanopub = () => { + const np = new Nanopub(RDF_STR); + const checked = np.check(); + console.log("Checked info dict:", checked.info()); + console.log(checked); + setCheckedOutput(JSON.stringify(checked.info(), null, 2)); + }; + + const signNanopub = () => { + const np = new Nanopub(RDF_STR); + console.log(np); + try { + console.log("signNanopub"); + console.log(PRIVATE_KEY); + console.log(ORCID); + console.log(NAME); + const profile = new NpProfile(PRIVATE_KEY, ORCID, NAME, ""); + console.log(profile); + const signed = np.sign(profile); + console.log("Signed info dict:", signed.info()); + console.log(signed); + setSignedOutput(JSON.stringify(signed.info(), null, 2)); + } catch (error) { + console.error("Error signing nanopub:", error); + } + }; + + const publishNanopub = () => { + const serverUrl = ""; + const profile = new NpProfile(PRIVATE_KEY, ORCID, NAME, ""); + const np = new Nanopub(RDF_STR); + np.publish(profile, serverUrl).then((published) => { + console.log("Published info dict:", published.info()); + setPublishedOutput(JSON.stringify(published.info(), null, 2)); + setRdfOutput(published.rdf()); + setPublishedURL(published.info().published); + }); + }; + return ( - {node ? ( +
+

+ Key Pair:{" "} + {keyPair + ? `Public-${keyPair.public.length} Private-${keyPair.private.length}` + : "No output"} +

+

+ Checked Output: {checkedOutput ? checkedOutput.length : "No output"} +

+

Signed Output: {signedOutput ? signedOutput.length : "No output"}

+

+ Published Output:{" "} + {publishedOutput ? publishedOutput.length : "No output"} +

+

RDF Output: {rdfOutput ? rdfOutput.length : "No output"}

+

+ Published URL:{" "} + {publishedURL ? ( + + Link + + ) : ( + "No URL" + )} +

+
+ {/* {node ? (
@@ -194,12 +313,21 @@ const NanopubDialog = ({
) : (

No node found

- )} + )} */}
- + + +
From 343697a8534013745de6dd5b84bb09a9ef7b5dd3 Mon Sep 17 00:00:00 2001 From: Michael Gartner Date: Wed, 28 Aug 2024 02:32:06 -0600 Subject: [PATCH 09/58] WIP rdf mapping/generation --- src/components/nanopub/NanopubNodeConfig.tsx | 86 ++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/src/components/nanopub/NanopubNodeConfig.tsx b/src/components/nanopub/NanopubNodeConfig.tsx index cabcf39b..04b0556d 100644 --- a/src/components/nanopub/NanopubNodeConfig.tsx +++ b/src/components/nanopub/NanopubNodeConfig.tsx @@ -39,6 +39,92 @@ const defaultPredicates = [ "is created by", ]; +// TODO: combine with above +const predicateURIs = { + "is a": "http://www.w3.org/1999/02/22-rdf-syntax-ns#type", + "has the label": "http://www.w3.org/2000/01/rdf-schema#label", + "has the description": "http://purl.org/dc/terms/description", + "has more info at": "http://xmlns.com/foaf/0.1/page", + "is attributed to": "http://purl.org/dc/terms/creator", + "is created by": "http://purl.org/dc/terms/creator", +}; +type RDFStructure = { + "@context": Record; + "@id": string; + "@graph": { + "@id": string; + "@type": string; + "np:hasPublicationInfo": { + "@id": string; + "@graph": Array<{ + "@id": string; + [key: string]: any; // Flexible for additional properties + }>; + }; + }; +}; + +const baseRdf: RDFStructure = { + "@context": { + rdf: "http://www.w3.org/1999/02/22-rdf-syntax-ns#", + rdfs: "http://www.w3.org/2000/01/rdf-schema#", + dc: "http://purl.org/dc/terms/", + np: "http://www.nanopub.org/nschema#", + foaf: "http://xmlns.com/foaf/0.1/", + xsd: "http://www.w3.org/2001/XMLSchema#", + }, + "@id": "#Head", + "@graph": { + "@id": "#", + "@type": "np:Nanopublication", + "np:hasAssertion": { + "@id": "#assertion", + "@graph": [], + }, + "np:hasProvenance": { + "@id": "#provenance", + "@graph": [], + }, + "np:hasPublicationInfo": { + "@id": "#pubinfo", + "@graph": [], + }, + }, +}; + +const generateRdfString = (triples: NanopubTriple[]): string => { + const rdf = { ...baseRdf }; + + rdf["@graph"]["np:hasAssertion"]["@graph"] = triples + .filter((triple) => triple.type === "assertion") + .map((triple) => ({ + "@id": "#assertion", + [predicateURIs[triple.predicate as keyof typeof predicateURIs]]: { + "@id": triple.object, + }, + })); + + rdf["@graph"]["np:hasProvenance"]["@graph"] = triples + .filter((triple) => triple.type === "provenance") + .map((triple) => ({ + "@id": "#provenance", + [predicateURIs[triple.predicate as keyof typeof predicateURIs]]: { + "@value": triple.object, + }, + })); + + rdf["@graph"]["np:hasPublicationInfo"]["@graph"] = triples + .filter((triple) => triple.type === "publicationInfo") + .map((triple) => ({ + "@id": "#pubinfo", + [predicateURIs[triple.predicate as keyof typeof predicateURIs]]: { + "@value": triple.object, + }, + })); + + return JSON.stringify(rdf, null, 2); // Return the stringified RDF with pretty print +}; + export type NanopubTriple = { uid: string; predicate: string; From dc5faf9ce6855cc24fa9f670dd601b04a1ee7e11 Mon Sep 17 00:00:00 2001 From: Michael Gartner Date: Thu, 5 Sep 2024 10:58:17 -0600 Subject: [PATCH 10/58] working publish --- src/components/nanopub/ContributorManager.tsx | 44 +- src/components/nanopub/Example.tsx | 87 --- src/components/nanopub/Nanopub.tsx | 406 +++++++++----- src/components/nanopub/NanopubNodeConfig.tsx | 107 ++-- src/components/nanopub/exampleRdfString.ts | 4 +- .../nanopub/testDiscourseGraphRdfString.ts | 528 ++++++++++++++++++ src/data/defaultNanopubTemplates.ts | 6 + src/utils/getDiscourseNodes.ts | 4 +- 8 files changed, 850 insertions(+), 336 deletions(-) delete mode 100644 src/components/nanopub/Example.tsx create mode 100644 src/components/nanopub/testDiscourseGraphRdfString.ts diff --git a/src/components/nanopub/ContributorManager.tsx b/src/components/nanopub/ContributorManager.tsx index 18918518..e64d0eac 100644 --- a/src/components/nanopub/ContributorManager.tsx +++ b/src/components/nanopub/ContributorManager.tsx @@ -47,7 +47,6 @@ const ContributorManager = ({ pageUid }: { pageUid: string }) => { const initialContributors = nanopub?.contributors || []; const [contributors, setContributors] = useState(initialContributors); - // const [isEditing, setIsEditing] = useState(false); const userDisplayNames = useMemo(() => { const queryResults = window.roamAlphaAPI.data.fast.q( @@ -105,29 +104,19 @@ const ContributorManager = ({ pageUid }: { pageUid: string }) => { userDisplayNames={userDisplayNames} /> ))} -
- - {/* */} -
+
); }; @@ -187,15 +176,15 @@ const ContributorRow = ({ options={userDisplayNames} /> setContributorRoles(item, contributor)} tagRenderer={(item) => item} + popoverProps={{ minimal: true }} + itemListRenderer={({ items, renderItem }) => { + return
{items.map(renderItem)}
; + }} itemRenderer={(item, { modifiers, handleClick }) => { if (contributor.roles?.includes(item)) return null; if (!modifiers.matchesPredicate) return null; @@ -206,6 +195,7 @@ const ContributorRow = ({ onClick={handleClick} text={item} key={item} + className="block w-full" /> ); }} diff --git a/src/components/nanopub/Example.tsx b/src/components/nanopub/Example.tsx deleted file mode 100644 index eaa731d8..00000000 --- a/src/components/nanopub/Example.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import React, { useEffect, useState } from "react"; -import init, { Nanopub, NpProfile } from "@nanopub/sign"; - -export default function Home() { - const [rdfOutput, setRdfOutput] = useState(""); - useEffect(() => { - // â„šī¸ You can also provide JSON-LD objects! - const rdf = { - "@context": { - "@base": "http://purl.org/nanopub/temp/mynanopub#", - np: "http://www.nanopub.org/nschema#", - npx: "http://purl.org/nanopub/x/", - rdf: "http://www.w3.org/1999/02/22-rdf-syntax-ns#", - rdfs: "http://www.w3.org/2000/01/rdf-schema#", - xsd: "http://www.w3.org/2001/XMLSchema#", - foaf: "http://xmlns.com/foaf/0.1/", - dct: "http://purl.org/dc/terms/", - prov: "http://www.w3.org/ns/prov#", - pav: "http://purl.org/pav/", - orcid: "https://orcid.org/", - schema: "https://schema.org/", - }, - "@id": "#Head", - "@graph": { - "@id": "#", - "@type": "np:Nanopublication", - "np:hasAssertion": { - "@id": "#assertion", - "@context": { - ex: "http://example.org/", - }, - "@graph": [ - { - "@id": "ex:mosquito", - "ex:transmits": { "@id": "ex:malaria" }, - }, - ], - }, - "np:hasProvenance": { - "@id": "#provenance", - "@graph": [ - { - "@id": "#assertion", - "prov:hadPrimarySource": { - "@id": "http://dx.doi.org/10.3233/ISU-2010-0613", - }, - }, - ], - }, - "np:hasPublicationInfo": { - "@id": "#pubinfo", - "@graph": [ - { - "@id": "#", - "@type": "npx:ExampleNanopub", - }, - ], - }, - }, - }; - const privateKey = `MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCjY1gsFxmak6SOCouJPuEzHNForkqFhgfHE3aAIAx+Y5q6UDEDM9Q0EksheNffJB4iPqsAfiFpY0ARQY92K5r8P4+a78eu9reYrb2WxZb1qPJmvR7XZ6sN1oHD7dd/EyQoJmQsmOKdrqaLRbzR7tZrf52yvKkwNWXcIVhW8uxe7iUgxiojZpW9srKoK/qFRpaUZSKn7Z/zgtDH9FJkYbBsGPDMqp78Kzt+sJb+U2W+wCSSy34jIUxx6QRbzvn6uexc/emFw/1DU5y7zBudhgC7mVk8vX1gUNKyjZBzlOmRcretrANgffqs5fx/TMHN1xtkA/H1u1IKBfKoyk/xThMLAgMBAAECggEAECuG0GZA3HF8OaqFgMG+W+agOvH04h4Pqv4cHjYNxnxpFcNV9nEssTKWSOvCwYy7hrwZBGV3PQzbjFmmrxVFs20+8yCD7KbyKKQZPVC0zf84bj6NTNgvr6DpGtDxINxuGaMjCt7enqhoRyRRuZ0fj2gD3Wqae/Ds8cpDCefkyMg0TvauHSUj244vGq5nt93txUv1Sa+/8tWZ77Dm0s5a3wUYB2IeAMl5WrO2GMvgzwH+zT+4kvNWg5S0Ze4KE+dG3lSIYZjo99h14LcQS9eALC/VBcAJ6pRXaCTT/TULtcLNeOpoc9Fu25f0yTsDt6Ga5ApliYkb7rDhV+OFrw1sYQKBgQDCE9so+dPg7qbp0cV+lbb7rrV43m5s9Klq0riS7u8m71oTwhmvm6gSLfjzqb8GLrmflCK4lKPDSTdwyvd+2SSmOXySw94zr1Pvc7sHdmMRyA7mH3m+zSOOgyCTTKyhDRCNcRIkysoL+DecDhNo4Fumf71tsqDYogfxpAQhn0re8wKBgQDXhMmmT2oXiMnYHhi2k7CJe3HUqkZgmW4W44SWqKHp0V6sjcHm0N0RT5Hz1BFFUd5Y0ZB3JLcah19myD1kKYCj7xz6oVLb8O7LeAZNlb0FsrtD7NU+Hciywo8qESiA7UYDkU6+hsmxaI01DsttMIdG4lSBbEjA7t4IQC5lyr7xiQKBgQCN87YGJ40Y5ZXCSgOZDepz9hqX2KGOIfnUv2HvXsIfiUwqTXs6HbD18xg3KL4myIBOvywSM+4ABYp+foY+Cpcq2btLIeZhiWjsKIrw71+Q/vIe0YDb1PGf6DsoYhmWBpdHzR9HN+hGjvwlsYny2L9Qbfhgxxmsuf7zeFLpQLijjwKBgH7TD28k8IOk5VKec2CNjKd600OYaA3UfCpP/OhDl/RmVtYoHWDcrBrRvkvEEd2/DZ8qw165Zl7gJs3vK+FTYvYVcfIzGPWA1KU7nkntwewmf3i7V8lT8ZTwVRsmObWU60ySJ8qKuwoBQodki2VX12NpMN1wgWe3qUUlr6gLJU4xAoGAet6nD3QKwk6TTmcGVfSWOzvpaDEzGkXjCLaxLKh9GreM/OE+h5aN2gUoFeQapG5rUwI/7Qq0xiLbRXw+OmfAoV2XKv7iI8DjdIh0F06mlEAwQ/B0CpbqkuuxphIbchtdcz/5ra233r3BMNIqBl3VDDVoJlgHPg9msOTRy13lFqc=`; - - // WebAssembly binary needs to be initialized only if the JS runs on the client - init().then(async () => { - const serverUrl = ""; - const profile = new NpProfile( - privateKey, - "https://orcid.org/0000-0000-0000-0000", - "User Name", - "" - ); - - const np = await new Nanopub(rdf).publish(profile, serverUrl); - console.log("Published info dict:", np.info()); - setRdfOutput(np.rdf()); - }); - }, []); - - return ( -
-

Nanopublication RDF Output:

-
-        {rdfOutput}
-      
-
- ); -} diff --git a/src/components/nanopub/Nanopub.tsx b/src/components/nanopub/Nanopub.tsx index 460d2b0a..97ba3ede 100644 --- a/src/components/nanopub/Nanopub.tsx +++ b/src/components/nanopub/Nanopub.tsx @@ -1,6 +1,5 @@ import React, { useEffect, useState, useMemo, useCallback } from "react"; -import { Dialog, Button } from "@blueprintjs/core"; - +import { Dialog, Button, H5, TextArea, Tabs, Tab } from "@blueprintjs/core"; // web.js:1014 Uncaught (in promise) TypeError: Failed to construct 'URL': Invalid URL // at __wbg_init (web.js:1014:17) // at Nanopub.tsx:200:7 @@ -10,10 +9,11 @@ import init, { NpProfile, getNpServer, KeyPair, + // @ts-ignore } from "https://unpkg.com/@nanopub/sign"; import renderOverlay from "roamjs-components/util/renderOverlay"; import { getNodeEnv } from "roamjs-components/util/env"; -import rdfString from "./exampleRdfString"; +import exampleRdfString from "./exampleRdfString"; import { OnloadArgs } from "roamjs-components/types"; import { handleTitleAdditions } from "../../utils/handleTitleAdditions"; import matchDiscourseNode from "../../utils/matchDiscourseNode"; @@ -27,17 +27,35 @@ import ExtensionApiContextProvider, { useExtensionAPI, } from "roamjs-components/components/ExtensionApiContext"; import ContributorManager from "./ContributorManager"; -const SERVER_URL = - getNodeEnv() === "development" - ? "" // leave blank to publish to test server - : getNpServer(false); +import testDiscourseGraphRdfSring, { + testDiscourseGraphRdfSringWorking, + testing, +} from "./testDiscourseGraphRdfString"; +import { + baseRdf, + defaultPredicates, + NanopubTripleType, + PredicateKey, +} from "./NanopubNodeConfig"; +import { defaultNanopubTemplate } from "../../data/defaultNanopubTemplates"; +import getBlockProps from "../../utils/getBlockProps"; + +type NanopubPage = { + contributors: Contributor[]; +}; +export type Contributor = { + id: string; + name: string; + roles: string[]; +}; + const PRIVATE_KEY = getNodeEnv() === "development" ? "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCjY1gsFxmak6SOCouJPuEzHNForkqFhgfHE3aAIAx+Y5q6UDEDM9Q0EksheNffJB4iPqsAfiFpY0ARQY92K5r8P4+a78eu9reYrb2WxZb1qPJmvR7XZ6sN1oHD7dd/EyQoJmQsmOKdrqaLRbzR7tZrf52yvKkwNWXcIVhW8uxe7iUgxiojZpW9srKoK/qFRpaUZSKn7Z/zgtDH9FJkYbBsGPDMqp78Kzt+sJb+U2W+wCSSy34jIUxx6QRbzvn6uexc/emFw/1DU5y7zBudhgC7mVk8vX1gUNKyjZBzlOmRcretrANgffqs5fx/TMHN1xtkA/H1u1IKBfKoyk/xThMLAgMBAAECggEAECuG0GZA3HF8OaqFgMG+W+agOvH04h4Pqv4cHjYNxnxpFcNV9nEssTKWSOvCwYy7hrwZBGV3PQzbjFmmrxVFs20+8yCD7KbyKKQZPVC0zf84bj6NTNgvr6DpGtDxINxuGaMjCt7enqhoRyRRuZ0fj2gD3Wqae/Ds8cpDCefkyMg0TvauHSUj244vGq5nt93txUv1Sa+/8tWZ77Dm0s5a3wUYB2IeAMl5WrO2GMvgzwH+zT+4kvNWg5S0Ze4KE+dG3lSIYZjo99h14LcQS9eALC/VBcAJ6pRXaCTT/TULtcLNeOpoc9Fu25f0yTsDt6Ga5ApliYkb7rDhV+OFrw1sYQKBgQDCE9so+dPg7qbp0cV+lbb7rrV43m5s9Klq0riS7u8m71oTwhmvm6gSLfjzqb8GLrmflCK4lKPDSTdwyvd+2SSmOXySw94zr1Pvc7sHdmMRyA7mH3m+zSOOgyCTTKyhDRCNcRIkysoL+DecDhNo4Fumf71tsqDYogfxpAQhn0re8wKBgQDXhMmmT2oXiMnYHhi2k7CJe3HUqkZgmW4W44SWqKHp0V6sjcHm0N0RT5Hz1BFFUd5Y0ZB3JLcah19myD1kKYCj7xz6oVLb8O7LeAZNlb0FsrtD7NU+Hciywo8qESiA7UYDkU6+hsmxaI01DsttMIdG4lSBbEjA7t4IQC5lyr7xiQKBgQCN87YGJ40Y5ZXCSgOZDepz9hqX2KGOIfnUv2HvXsIfiUwqTXs6HbD18xg3KL4myIBOvywSM+4ABYp+foY+Cpcq2btLIeZhiWjsKIrw71+Q/vIe0YDb1PGf6DsoYhmWBpdHzR9HN+hGjvwlsYny2L9Qbfhgxxmsuf7zeFLpQLijjwKBgH7TD28k8IOk5VKec2CNjKd600OYaA3UfCpP/OhDl/RmVtYoHWDcrBrRvkvEEd2/DZ8qw165Zl7gJs3vK+FTYvYVcfIzGPWA1KU7nkntwewmf3i7V8lT8ZTwVRsmObWU60ySJ8qKuwoBQodki2VX12NpMN1wgWe3qUUlr6gLJU4xAoGAet6nD3QKwk6TTmcGVfSWOzvpaDEzGkXjCLaxLKh9GreM/OE+h5aN2gUoFeQapG5rUwI/7Qq0xiLbRXw+OmfAoV2XKv7iI8DjdIh0F06mlEAwQ/B0CpbqkuuxphIbchtdcz/5ra233r3BMNIqBl3VDDVoJlgHPg9msOTRy13lFqc=" : ""; -const ORCID = "https://orcid.org/0000-0000-0000-0000"; -const NAME = "Test User"; -const RDF_STR = rdfString; +// const RDF_STR = exampleRdfString; +// const RDF_STR = testDiscourseGraphRdfSring; +// const RDF_STR = testDiscourseGraphRdfSringWorking; export const NanoPubTitleButtons = ({ uid, @@ -73,35 +91,6 @@ export const NanoPubTitleButtons = ({ ); }; -const DiscourseContextSection = ({ - discourseContext, -}: { - discourseContext: DiscourseContextResults; -}) => { - return ( - <> - {discourseContext.map((context, index) => { - const hasResults = Object.entries(context.results).length > 0; - - if (!hasResults) return null; - - return ( -
-
- {context.label.toLowerCase()}: -
-
- {Object.values(context.results).map((value, idx) => ( -
{value.text}
- ))} -
-
- ); - })} - - ); -}; - const NanopubTriple = ({ subject, object, @@ -127,77 +116,103 @@ const NanopubDialog = ({ }) => { const [isOpen, setIsOpen] = useState(true); const handleClose = () => setIsOpen(false); - // const [discourseContext, setDiscourseContext] = - // useState([]); + const [rdfString, setRdfString] = useState(""); + + const discourseNode = useMemo(() => getDiscourseNode(uid), [uid]); + if (!discourseNode) return <> ; + const templateTriples = discourseNode?.nanopub?.triples; - const node = useMemo(() => getDiscourseNode(uid), [uid]); - if (!node) return <> ; - const templateTriples = node?.nanopub?.triples; const updateObjectPlaceholders = (object: string) => { - const OCRID = onloadArgs.extensionAPI.settings.get("orcid") as string; const pageUrl = `https://roamresearch.com/${window.roamAlphaAPI.graph.name}/page/${uid}`; return object - .replace(/\{nodeType\}/g, node.text) + .replace(/\{nodeType\}/g, discourseNode.text) .replace(/\{title\}/g, getPageTitleByPageUid(uid)) .replace(/\{name\}/g, getCurrentUserDisplayName()) .replace(/\{url\}/g, pageUrl) - .replace(/\{myORCID\}/g, OCRID) + .replace(/\{myORCID\}/g, ORCID) .replace(/\{createdBy\}/g, getCurrentUserDisplayName()) .replace(/\{body\}/g, ""); // TODO: Add body }; - // useEffect(() => { - // const fetchDiscourseContext = async () => { - // const results = await getDiscourseContextResults({ uid }); - // setDiscourseContext(results); - // }; - // fetchDiscourseContext(); - // }, [uid]); + const generateRdfString = ({ + triples, + }: { + triples: NanopubTripleType[]; + }): string => { + const rdf = { ...baseRdf }; - const handlePublish = async () => { - try { - const privateKey = `MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCjY1gsFxmak6SOCouJPuEzHNForkqFhgfHE3aAIAx+Y5q6UDEDM9Q0EksheNffJB4iPqsAfiFpY0ARQY92K5r8P4+a78eu9reYrb2WxZb1qPJmvR7XZ6sN1oHD7dd/EyQoJmQsmOKdrqaLRbzR7tZrf52yvKkwNWXcIVhW8uxe7iUgxiojZpW9srKoK/qFRpaUZSKn7Z/zgtDH9FJkYbBsGPDMqp78Kzt+sJb+U2W+wCSSy34jIUxx6QRbzvn6uexc/emFw/1DU5y7zBudhgC7mVk8vX1gUNKyjZBzlOmRcretrANgffqs5fx/TMHN1xtkA/H1u1IKBfKoyk/xThMLAgMBAAECggEAECuG0GZA3HF8OaqFgMG+W+agOvH04h4Pqv4cHjYNxnxpFcNV9nEssTKWSOvCwYy7hrwZBGV3PQzbjFmmrxVFs20+8yCD7KbyKKQZPVC0zf84bj6NTNgvr6DpGtDxINxuGaMjCt7enqhoRyRRuZ0fj2gD3Wqae/Ds8cpDCefkyMg0TvauHSUj244vGq5nt93txUv1Sa+/8tWZ77Dm0s5a3wUYB2IeAMl5WrO2GMvgzwH+zT+4kvNWg5S0Ze4KE+dG3lSIYZjo99h14LcQS9eALC/VBcAJ6pRXaCTT/TULtcLNeOpoc9Fu25f0yTsDt6Ga5ApliYkb7rDhV+OFrw1sYQKBgQDCE9so+dPg7qbp0cV+lbb7rrV43m5s9Klq0riS7u8m71oTwhmvm6gSLfjzqb8GLrmflCK4lKPDSTdwyvd+2SSmOXySw94zr1Pvc7sHdmMRyA7mH3m+zSOOgyCTTKyhDRCNcRIkysoL+DecDhNo4Fumf71tsqDYogfxpAQhn0re8wKBgQDXhMmmT2oXiMnYHhi2k7CJe3HUqkZgmW4W44SWqKHp0V6sjcHm0N0RT5Hz1BFFUd5Y0ZB3JLcah19myD1kKYCj7xz6oVLb8O7LeAZNlb0FsrtD7NU+Hciywo8qESiA7UYDkU6+hsmxaI01DsttMIdG4lSBbEjA7t4IQC5lyr7xiQKBgQCN87YGJ40Y5ZXCSgOZDepz9hqX2KGOIfnUv2HvXsIfiUwqTXs6HbD18xg3KL4myIBOvywSM+4ABYp+foY+Cpcq2btLIeZhiWjsKIrw71+Q/vIe0YDb1PGf6DsoYhmWBpdHzR9HN+hGjvwlsYny2L9Qbfhgxxmsuf7zeFLpQLijjwKBgH7TD28k8IOk5VKec2CNjKd600OYaA3UfCpP/OhDl/RmVtYoHWDcrBrRvkvEEd2/DZ8qw165Zl7gJs3vK+FTYvYVcfIzGPWA1KU7nkntwewmf3i7V8lT8ZTwVRsmObWU60ySJ8qKuwoBQodki2VX12NpMN1wgWe3qUUlr6gLJU4xAoGAet6nD3QKwk6TTmcGVfSWOzvpaDEzGkXjCLaxLKh9GreM/OE+h5aN2gUoFeQapG5rUwI/7Qq0xiLbRXw+OmfAoV2XKv7iI8DjdIh0F06mlEAwQ/B0CpbqkuuxphIbchtdcz/5ra233r3BMNIqBl3VDDVoJlgHPg9msOTRy13lFqc=`; - - const serverUrl = ""; - const profile = new NpProfile( - privateKey, - "https://orcid.org/0000-0000-0000-0000", - "User Name", - "" + // TEMP TODO REMOVE + // Update triples type based on defaultNanopubTemplate + const updatedTriples = triples.map((triple) => { + const templateTriple = defaultNanopubTemplate.find( + (t) => t.predicate === triple.predicate ); + return templateTriple ? { ...triple, type: templateTriple.type } : triple; + }); - const np = await new Nanopub(RDF_STR).publish(profile, serverUrl); - console.log("Published info dict:", np.info()); - // init().then(async () => { - // const profile = new NpProfile(PRIVATE_KEY, ORCID, NAME, ""); + rdf["@graph"]["np:hasAssertion"]["@graph"] = updatedTriples + .filter((triple) => triple.type === "assertion") + .map((triple) => ({ + "@id": "#", + [defaultPredicates[triple.predicate as PredicateKey]]: { + "@value": updateObjectPlaceholders(triple.object), + }, + })); - // const np = new Nanopub(RDF_STR); + rdf["@graph"]["np:hasProvenance"]["@graph"] = updatedTriples + .filter((triple) => triple.type === "provenance") + .map((triple) => ({ + "@id": "#assertion", + [defaultPredicates[triple.predicate as PredicateKey]]: { + "@value": updateObjectPlaceholders(triple.object), + }, + })); - // const result = await np.publish(profile, SERVER_URL); + rdf["@graph"]["np:hasPublicationInfo"]["@graph"] = updatedTriples + .filter((triple) => triple.type === "publicationInfo") + .map((triple) => ({ + "@id": "#pubinfo", + [defaultPredicates[triple.predicate as PredicateKey]]: { + "@value": updateObjectPlaceholders(triple.object), + }, + })); - // console.log("Nanopub:", np.info()); - // console.log("Published:", result.info()); + rdf["@graph"]["np:hasPublicationInfo"]["@graph"].push({ + "@id": "#", + "@type": "npx:ExampleNanopub", + }); - // console.log("Info"); - // console.log("-----------------"); - // console.log("Private Key:", PRIVATE_KEY); - // console.log("ORCID:", ORCID); - // console.log("Name:", NAME); - // console.log("Server URL:", SERVER_URL); - // // console.log("RDF:", RDF_STR); - // console.log("-----------------"); - // }); + const props = getBlockProps(uid) as Record; + const nanopub = props["nanopub"] as NanopubPage; + const contributors = nanopub?.contributors || []; + // Add contributors to provenance + if (contributors.length > 0) { + const provenanceGraph = rdf["@graph"]["np:hasProvenance"]["@graph"]; - // handleClose(); - } catch (error) { - console.error("Error publishing nanopub:", error); + contributors.forEach((contributor) => { + if (contributor.roles.length > 0) { + contributor.roles.forEach((role) => { + const creditRole = `credit:${role + .toLowerCase() + // TODO remove these and replace with creditRoles object + .replace(/\s+|–|—|&/g, "-") // Replace spaces, em/en dashes, and & with hyphens + .replace(/-+/g, "-") // Replace multiple consecutive hyphens with a single hyphen + .replace(/(^-|-$)/g, "")}`; // Remove leading or trailing hyphens + const newAssertion = { + "@id": "#assertion", + [creditRole]: contributor.name, + }; + provenanceGraph.push(newAssertion); + }); + } + }); } + + return JSON.stringify(rdf, null, 2); }; - // DEBUG - // DEBUG - // DEBUG + // DEV const [rdfOutput, setRdfOutput] = useState(""); const [checkedOutput, setCheckedOutput] = useState(""); const [signedOutput, setSignedOutput] = useState(""); @@ -205,37 +220,27 @@ const NanopubDialog = ({ const [publishedURL, setPublishedURL] = useState(""); const [keyPair, setKeyPair] = useState(null); - useEffect(() => { - try { - // @ts-ignore - init().then(() => { - console.log("WASM Initialized"); - }); - } catch (error) { - console.error("Error initializing WASM:", error); - } - }, []); - + // DEV const generateKeyPair = () => { const keypair = new KeyPair().toJs(); setKeyPair(keypair); console.log(keypair); }; - const checkNanopub = () => { - const np = new Nanopub(RDF_STR); + const np = new Nanopub(rdfString); const checked = np.check(); console.log("Checked info dict:", checked.info()); console.log(checked); setCheckedOutput(JSON.stringify(checked.info(), null, 2)); }; - const signNanopub = () => { - const np = new Nanopub(RDF_STR); + const np = new Nanopub(rdfString); console.log(np); try { console.log("signNanopub"); console.log(PRIVATE_KEY); + const ORCID = onloadArgs.extensionAPI.settings.get("orcid") as string; + const NAME = getCurrentUserDisplayName(); console.log(ORCID); console.log(NAME); const profile = new NpProfile(PRIVATE_KEY, ORCID, NAME, ""); @@ -248,19 +253,142 @@ const NanopubDialog = ({ console.error("Error signing nanopub:", error); } }; + const DevDetails = () => { + return ( +
+
+

+ Key Pair:{" "} + {keyPair + ? `Public-${keyPair.public.length} Private-${keyPair.private.length}` + : "No output"} +

+

+ Checked Output: {checkedOutput ? checkedOutput.length : "No output"} +

+

+ Signed Output: {signedOutput ? signedOutput.length : "No output"} +

+

+ Published Output:{" "} + {publishedOutput ? publishedOutput.length : "No output"} +

+

RDF Output: {rdfOutput ? rdfOutput.length : "No output"}

+

+ Published URL:{" "} + {publishedURL ? ( + + Link + + ) : ( + "No URL" + )} +

+
+
+ + + + + +
+
+ ); + }; + + const ORCID = useMemo(() => { + console.log("getORCID"); + const hasORCID = onloadArgs.extensionAPI.settings.get("orcid") as string; + const ORCID = hasORCID ? `https://orcid.org/${hasORCID}` : ""; + return ORCID; + }, [onloadArgs]); - const publishNanopub = () => { - const serverUrl = ""; + const publishNanopub = async ({ isDev = "" }: { isDev?: string }) => { + const LIVERDF = rdfString; + const serverUrl = isDev ? "" : getNpServer(false); + const NAME = getCurrentUserDisplayName(); const profile = new NpProfile(PRIVATE_KEY, ORCID, NAME, ""); - const np = new Nanopub(RDF_STR); - np.publish(profile, serverUrl).then((published) => { + const np = new Nanopub(LIVERDF); + console.log(LIVERDF); + try { + const published = await np.publish(profile, serverUrl); console.log("Published info dict:", published.info()); setPublishedOutput(JSON.stringify(published.info(), null, 2)); setRdfOutput(published.rdf()); setPublishedURL(published.info().published); - }); + } catch (e) { + const error = e as Error; + console.error("Error publishing the Nanopub:", error); + setPublishedOutput(JSON.stringify({ error: error.message }, null, 2)); + } + }; + + const TripleString = () => { + return ( +
+