From dfc1069b0799f8e973227a156c45e85e30939851 Mon Sep 17 00:00:00 2001 From: Fraser Date: Tue, 8 Aug 2023 23:53:22 -0400 Subject: [PATCH 01/10] blank all glossary terms in AI text --- web/src/glossary.tsx | 52 +++++++++++++++++++++++++++++++++++++++++ web/src/pages/index.tsx | 3 ++- 2 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 web/src/glossary.tsx diff --git a/web/src/glossary.tsx b/web/src/glossary.tsx new file mode 100644 index 0000000..adbbd30 --- /dev/null +++ b/web/src/glossary.tsx @@ -0,0 +1,52 @@ +import { useState, useEffect } from "react"; + +// temporary hack to get glossary working +const GLOSSARY_JSON = {"chain of thought prompting":{"term":"chain of thought prompting","pageid":"8EL7","contents":"

Chain-of-thought prompting is a technique which makes a language model generate intermediate reasoning steps in its output.

\n"},"chain-of-thought":{"term":"chain-of-thought","pageid":"8EL7","contents":"

Chain-of-thought prompting is a technique which makes a language model generate intermediate reasoning steps in its output.

\n"},"goodhart's law":{"term":"goodhart's law","pageid":"8185","contents":"

Goodhart’s law states that when a measure becomes a target, it ceases to be a good measure.

\n"},"the big g,":{"term":"the big g,","pageid":"8185","contents":"

Goodhart’s law states that when a measure becomes a target, it ceases to be a good measure.

\n"},"terminal goals":{"term":"terminal goals","pageid":"","contents":"

Goals which are valued as ends in themselves, rather than as instrumental to something else.

\n"},"terminal goal":{"term":"terminal goal","pageid":"","contents":"

Goals which are valued as ends in themselves, rather than as instrumental to something else.

\n"},"orthogonality thesis":{"term":"orthogonality thesis","pageid":"6568","contents":"

The thesis that any level of intelligence is compatible with any terminal goals. This implies that intelligence alone is not enough to make a system moral.

\n"},"instrumental convergence":{"term":"instrumental convergence","pageid":"897I","contents":"

Instrumental convergence is the idea that different AI agents, each with distinct terminal goals, will end up adopting many of the same instrumental goals.

\n"},"instrumentally convergent goals":{"term":"instrumentally convergent goals","pageid":"897I","contents":"

Instrumental convergence is the idea that different AI agents, each with distinct terminal goals, will end up adopting many of the same instrumental goals.

\n"},"llm":{"term":"llm","pageid":"","contents":"

A large language model is an AI model which has been trained on a large body of text, in order to produce texts in a human-like way.

\n"},"large language model":{"term":"large language model","pageid":"","contents":"

A large language model is an AI model which has been trained on a large body of text, in order to produce texts in a human-like way.

\n"},"goal misgeneralization":{"term":"goal misgeneralization","pageid":"","contents":"

pursuing a different goal during deployment from the one that was pursued during training due to distribution shift

\n"},"interpretability":{"term":"interpretability","pageid":"8241","contents":"

Interpretability is an area of alignment research that aims to make machine learning systems easier for humans to understand.

\n"},"existential risk":{"term":"existential risk","pageid":"89LL","contents":"

risks that threaten the destruction of humanity's long-term potential, including human extinction

\n"}} + +type GlossaryItem = { + term: string; + pageid: string; + contents: string; +}; + +// A component which wraps a paragraph and injects glossary terms into it as +// hoverable pop-up links. The text is immediately rendered normally, but after +// the glossary is loaded (which happens once per page, asynchronously), the +// glossary terms are replaced with elements. +export const GlossaryP: React.FC<{content: string}> = ({content}) => { + const [glossary, setGlossary] = useState | null>(null); + const [glossaryRegex, setGlossaryRegex] = useState(null); + + useEffect(() => { + if (glossary === null) { + const glossary = new Map(Object.entries(GLOSSARY_JSON)); + setGlossary(glossary); + setGlossaryRegex(new RegExp(Array.from(glossary.keys()).join("|"), "gim")); + } + }, [glossary]); + + // If the glossary hasn't loaded yet, just render the text normally. + if (glossary == null || glossaryRegex == null) { + return ; + } + + // Otherwise, replace glossary terms with links. We can do this in + // O(n * sum of term lengths) by finding String.prototype.indexOf of + // each term in the glossary (since that'd probably be backed by KMP) + // but I think it should be faster to compile a regex state machine + // once and use that instead. + + return { + const item = glossary.get(match.toLowerCase()); + if (item == undefined) return match; + + const hover_content = item.contents; + const pageid = item.pageid; + return "AAAAAAAAA"; + + })}} />; +} + + + + diff --git a/web/src/pages/index.tsx b/web/src/pages/index.tsx index 9a18f48..a268d9f 100644 --- a/web/src/pages/index.tsx +++ b/web/src/pages/index.tsx @@ -10,6 +10,7 @@ import Image from 'next/image'; import Header from "../header"; import { SearchBox, Followup } from "../searchbox"; import logo from "../logo.svg" +import { GlossaryP } from "~/glossary"; type Citation = { title: string; @@ -202,7 +203,7 @@ const ShowAssistantEntry: React.FC<{entry: AssistantEntry}> = ({entry}) => { response.split("\n").map(paragraph => (

{ paragraph.split(in_text_citation_regex).map((text, i) => { if (i % 2 === 0) { - return text.trim(); + return ; } i = parseInt(text) - 1; if (!citations.has(i)) return `[${text}]`; From 3bc1fad58dfe4405fd42518e7002c80944707d4b Mon Sep 17 00:00:00 2001 From: Fraser Date: Wed, 9 Aug 2023 00:26:10 -0400 Subject: [PATCH 02/10] display hover text, link to aisafety.info --- web/src/glossary.tsx | 13 +++++++++++-- web/src/styles/globals.css | 13 +++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/web/src/glossary.tsx b/web/src/glossary.tsx index adbbd30..5b40aad 100644 --- a/web/src/glossary.tsx +++ b/web/src/glossary.tsx @@ -16,7 +16,7 @@ type GlossaryItem = { export const GlossaryP: React.FC<{content: string}> = ({content}) => { const [glossary, setGlossary] = useState | null>(null); const [glossaryRegex, setGlossaryRegex] = useState(null); - + useEffect(() => { if (glossary === null) { const glossary = new Map(Object.entries(GLOSSARY_JSON)); @@ -37,12 +37,21 @@ export const GlossaryP: React.FC<{content: string}> = ({content}) => { // once and use that instead. return { + const item = glossary.get(match.toLowerCase()); if (item == undefined) return match; const hover_content = item.contents; const pageid = item.pageid; - return "AAAAAAAAA"; + + return ` + + ${match} + +

${hover_content}
+ `; })}} />; } diff --git a/web/src/styles/globals.css b/web/src/styles/globals.css index 8715069..e56d570 100644 --- a/web/src/styles/globals.css +++ b/web/src/styles/globals.css @@ -37,3 +37,16 @@ ol { @apply list-decimal; } + +/* glossary terms with a definition that shows on hover */ +.glossary-hover { + position: absolute; + display: none; + width: 300px; + @apply bg-white hover:bg-gray-300 h-fit px-3; + @apply border border-gray-300; +} + +.glossary-link:hover + .glossary-hover { + display: block; +} From f00bde5e88e28cf426f28629422a67d35e325a18 Mon Sep 17 00:00:00 2001 From: Fraser Date: Sat, 12 Aug 2023 16:59:37 -0400 Subject: [PATCH 03/10] provide glossary in context, so we only need to fetch it once same story with compiling the regex state machine that identifies our terms. --- web/src/glossary.tsx | 24 ++++++++++-------------- web/src/pages/_app.tsx | 40 +++++++++++++++++++++++++++++++++++++++- web/src/pages/index.tsx | 2 +- 3 files changed, 50 insertions(+), 16 deletions(-) diff --git a/web/src/glossary.tsx b/web/src/glossary.tsx index 5b40aad..645e7f0 100644 --- a/web/src/glossary.tsx +++ b/web/src/glossary.tsx @@ -1,7 +1,4 @@ -import { useState, useEffect } from "react"; - -// temporary hack to get glossary working -const GLOSSARY_JSON = {"chain of thought prompting":{"term":"chain of thought prompting","pageid":"8EL7","contents":"

Chain-of-thought prompting is a technique which makes a language model generate intermediate reasoning steps in its output.

\n"},"chain-of-thought":{"term":"chain-of-thought","pageid":"8EL7","contents":"

Chain-of-thought prompting is a technique which makes a language model generate intermediate reasoning steps in its output.

\n"},"goodhart's law":{"term":"goodhart's law","pageid":"8185","contents":"

Goodhart’s law states that when a measure becomes a target, it ceases to be a good measure.

\n"},"the big g,":{"term":"the big g,","pageid":"8185","contents":"

Goodhart’s law states that when a measure becomes a target, it ceases to be a good measure.

\n"},"terminal goals":{"term":"terminal goals","pageid":"","contents":"

Goals which are valued as ends in themselves, rather than as instrumental to something else.

\n"},"terminal goal":{"term":"terminal goal","pageid":"","contents":"

Goals which are valued as ends in themselves, rather than as instrumental to something else.

\n"},"orthogonality thesis":{"term":"orthogonality thesis","pageid":"6568","contents":"

The thesis that any level of intelligence is compatible with any terminal goals. This implies that intelligence alone is not enough to make a system moral.

\n"},"instrumental convergence":{"term":"instrumental convergence","pageid":"897I","contents":"

Instrumental convergence is the idea that different AI agents, each with distinct terminal goals, will end up adopting many of the same instrumental goals.

\n"},"instrumentally convergent goals":{"term":"instrumentally convergent goals","pageid":"897I","contents":"

Instrumental convergence is the idea that different AI agents, each with distinct terminal goals, will end up adopting many of the same instrumental goals.

\n"},"llm":{"term":"llm","pageid":"","contents":"

A large language model is an AI model which has been trained on a large body of text, in order to produce texts in a human-like way.

\n"},"large language model":{"term":"large language model","pageid":"","contents":"

A large language model is an AI model which has been trained on a large body of text, in order to produce texts in a human-like way.

\n"},"goal misgeneralization":{"term":"goal misgeneralization","pageid":"","contents":"

pursuing a different goal during deployment from the one that was pursued during training due to distribution shift

\n"},"interpretability":{"term":"interpretability","pageid":"8241","contents":"

Interpretability is an area of alignment research that aims to make machine learning systems easier for humans to understand.

\n"},"existential risk":{"term":"existential risk","pageid":"89LL","contents":"

risks that threaten the destruction of humanity's long-term potential, including human extinction

\n"}} +import { createContext, useContext } from "react"; type GlossaryItem = { term: string; @@ -9,27 +6,26 @@ type GlossaryItem = { contents: string; }; +export type Glossary = Map; + +export const GlossaryContext = createContext<{g: Glossary, r: RegExp} | null>(null); + // A component which wraps a paragraph and injects glossary terms into it as // hoverable pop-up links. The text is immediately rendered normally, but after // the glossary is loaded (which happens once per page, asynchronously), the // glossary terms are replaced with elements. export const GlossaryP: React.FC<{content: string}> = ({content}) => { - const [glossary, setGlossary] = useState | null>(null); - const [glossaryRegex, setGlossaryRegex] = useState(null); - useEffect(() => { - if (glossary === null) { - const glossary = new Map(Object.entries(GLOSSARY_JSON)); - setGlossary(glossary); - setGlossaryRegex(new RegExp(Array.from(glossary.keys()).join("|"), "gim")); - } - }, [glossary]); + const g = useContext(GlossaryContext); // If the glossary hasn't loaded yet, just render the text normally. - if (glossary == null || glossaryRegex == null) { + if (g == null) { return ; } + const glossary = g.g; + const glossaryRegex = g.r; + // Otherwise, replace glossary terms with links. We can do this in // O(n * sum of term lengths) by finding String.prototype.indexOf of // each term in the glossary (since that'd probably be backed by KMP) diff --git a/web/src/pages/_app.tsx b/web/src/pages/_app.tsx index d280cbd..1935ffc 100644 --- a/web/src/pages/_app.tsx +++ b/web/src/pages/_app.tsx @@ -1,9 +1,47 @@ import { type AppType } from "next/dist/shared/lib/utils"; +import { useEffect, useState } from "react"; import "~/styles/globals.css"; +import { Glossary, GlossaryContext } from "../glossary"; + const MyApp: AppType = ({ Component, pageProps }) => { - return ; + const [glossary, setGlossary] = useState<{ g: Glossary, r: RegExp } | null>(null); + + // fetch glossary and compile regex once on load + useEffect(() => { + if (glossary === null) + tempHackFetch("/questions/glossary") + .then((res) => res.json()) + .then((data) => { + const glossary: Glossary = new Map(Object.entries(data)); + const regex = new RegExp(Array.from(glossary.keys()).join("|"), "gim"); + setGlossary({ g: glossary, r: regex }); + }); + }, []); + + return ( + + + + ); }; export default MyApp; + +// ------------------- hack until server endpoint is working ------------------- + +const GLOSSARY_JSON = {"chain of thought prompting":{"term":"chain of thought prompting","pageid":"8EL7","contents":"

Chain-of-thought prompting is a technique which makes a language model generate intermediate reasoning steps in its output.

\n"},"chain-of-thought":{"term":"chain-of-thought","pageid":"8EL7","contents":"

Chain-of-thought prompting is a technique which makes a language model generate intermediate reasoning steps in its output.

\n"},"goodhart's law":{"term":"goodhart's law","pageid":"8185","contents":"

Goodhart’s law states that when a measure becomes a target, it ceases to be a good measure.

\n"},"the big g,":{"term":"the big g,","pageid":"8185","contents":"

Goodhart’s law states that when a measure becomes a target, it ceases to be a good measure.

\n"},"terminal goals":{"term":"terminal goals","pageid":"","contents":"

Goals which are valued as ends in themselves, rather than as instrumental to something else.

\n"},"terminal goal":{"term":"terminal goal","pageid":"","contents":"

Goals which are valued as ends in themselves, rather than as instrumental to something else.

\n"},"orthogonality thesis":{"term":"orthogonality thesis","pageid":"6568","contents":"

The thesis that any level of intelligence is compatible with any terminal goals. This implies that intelligence alone is not enough to make a system moral.

\n"},"instrumental convergence":{"term":"instrumental convergence","pageid":"897I","contents":"

Instrumental convergence is the idea that different AI agents, each with distinct terminal goals, will end up adopting many of the same instrumental goals.

\n"},"instrumentally convergent goals":{"term":"instrumentally convergent goals","pageid":"897I","contents":"

Instrumental convergence is the idea that different AI agents, each with distinct terminal goals, will end up adopting many of the same instrumental goals.

\n"},"llm":{"term":"llm","pageid":"","contents":"

A large language model is an AI model which has been trained on a large body of text, in order to produce texts in a human-like way.

\n"},"large language model":{"term":"large language model","pageid":"","contents":"

A large language model is an AI model which has been trained on a large body of text, in order to produce texts in a human-like way.

\n"},"goal misgeneralization":{"term":"goal misgeneralization","pageid":"","contents":"

pursuing a different goal during deployment from the one that was pursued during training due to distribution shift

\n"},"interpretability":{"term":"interpretability","pageid":"8241","contents":"

Interpretability is an area of alignment research that aims to make machine learning systems easier for humans to understand.

\n"},"existential risk":{"term":"existential risk","pageid":"89LL","contents":"

risks that threaten the destruction of humanity's long-term potential, including human extinction

\n"}} + +const tempHackFetch = (_url: string) => { + return new Promise((resolve, _reject) => { + setTimeout(() => { + resolve({ + ok: true, + json: () => Promise.resolve(GLOSSARY_JSON), + } as unknown as Response); + }, 1000); + }); +} + + diff --git a/web/src/pages/index.tsx b/web/src/pages/index.tsx index a268d9f..27fdcf4 100644 --- a/web/src/pages/index.tsx +++ b/web/src/pages/index.tsx @@ -10,7 +10,7 @@ import Image from 'next/image'; import Header from "../header"; import { SearchBox, Followup } from "../searchbox"; import logo from "../logo.svg" -import { GlossaryP } from "~/glossary"; +import { GlossaryP } from "../glossary"; type Citation = { title: string; From 42f3666dec64f4ed96a35d4de2d4fef23084dbe6 Mon Sep 17 00:00:00 2001 From: Fraser Date: Sat, 12 Aug 2023 17:06:54 -0400 Subject: [PATCH 04/10] rename element to be more accurate --- web/src/glossary.tsx | 11 ++++++----- web/src/pages/_app.tsx | 2 -- web/src/pages/index.tsx | 4 ++-- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/web/src/glossary.tsx b/web/src/glossary.tsx index 645e7f0..1e43326 100644 --- a/web/src/glossary.tsx +++ b/web/src/glossary.tsx @@ -10,11 +10,12 @@ export type Glossary = Map; export const GlossaryContext = createContext<{g: Glossary, r: RegExp} | null>(null); -// A component which wraps a paragraph and injects glossary terms into it as -// hoverable pop-up links. The text is immediately rendered normally, but after -// the glossary is loaded (which happens once per page, asynchronously), the -// glossary terms are replaced with elements. -export const GlossaryP: React.FC<{content: string}> = ({content}) => { +// A component which wraps arbitrary html in a span, and injects glossary terms +// into it as hoverable pop-up links. The text is immediately rendered normally, +// but after the glossary is loaded (which happens once per page, asynchronously), +// the glossary terms are replaced with elements. + +export const GlossarySpan: React.FC<{content: string}> = ({content}) => { const g = useContext(GlossaryContext); diff --git a/web/src/pages/_app.tsx b/web/src/pages/_app.tsx index 1935ffc..39d7cd4 100644 --- a/web/src/pages/_app.tsx +++ b/web/src/pages/_app.tsx @@ -43,5 +43,3 @@ const tempHackFetch = (_url: string) => { }, 1000); }); } - - diff --git a/web/src/pages/index.tsx b/web/src/pages/index.tsx index 27fdcf4..d1a84a5 100644 --- a/web/src/pages/index.tsx +++ b/web/src/pages/index.tsx @@ -10,7 +10,7 @@ import Image from 'next/image'; import Header from "../header"; import { SearchBox, Followup } from "../searchbox"; import logo from "../logo.svg" -import { GlossaryP } from "../glossary"; +import { GlossarySpan } from "../glossary"; type Citation = { title: string; @@ -203,7 +203,7 @@ const ShowAssistantEntry: React.FC<{entry: AssistantEntry}> = ({entry}) => { response.split("\n").map(paragraph => (

{ paragraph.split(in_text_citation_regex).map((text, i) => { if (i % 2 === 0) { - return ; + return ; } i = parseInt(text) - 1; if (!citations.has(i)) return `[${text}]`; From 48d0c48964d181464b39bf1173635ff1a8e02ffa Mon Sep 17 00:00:00 2001 From: Fraser Date: Sat, 12 Aug 2023 17:27:46 -0400 Subject: [PATCH 05/10] fix positioning of hover box when text is not on far left --- web/src/styles/globals.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/web/src/styles/globals.css b/web/src/styles/globals.css index e56d570..0d117aa 100644 --- a/web/src/styles/globals.css +++ b/web/src/styles/globals.css @@ -43,10 +43,11 @@ ol { position: absolute; display: none; width: 300px; + transform: translate(-50%, 1.7rem); @apply bg-white hover:bg-gray-300 h-fit px-3; @apply border border-gray-300; } .glossary-link:hover + .glossary-hover { - display: block; + display: initial; } From 463e3904e3afb810869a0e3339ba2549f033e02f Mon Sep 17 00:00:00 2001 From: Fraser Date: Sat, 12 Aug 2023 17:45:48 -0400 Subject: [PATCH 06/10] swap order of elements for more consistent layout --- web/src/glossary.tsx | 2 +- web/src/styles/globals.css | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/web/src/glossary.tsx b/web/src/glossary.tsx index 1e43326..8269719 100644 --- a/web/src/glossary.tsx +++ b/web/src/glossary.tsx @@ -42,12 +42,12 @@ export const GlossarySpan: React.FC<{content: string}> = ({content}) => { const pageid = item.pageid; return ` +

${hover_content}
${match} -
${hover_content}
`; })}} />; diff --git a/web/src/styles/globals.css b/web/src/styles/globals.css index 0d117aa..ceef29c 100644 --- a/web/src/styles/globals.css +++ b/web/src/styles/globals.css @@ -43,11 +43,12 @@ ol { position: absolute; display: none; width: 300px; - transform: translate(-50%, 1.7rem); + transform: translateY(1.7rem); @apply bg-white hover:bg-gray-300 h-fit px-3; @apply border border-gray-300; } -.glossary-link:hover + .glossary-hover { +/* .glossary-link:hover + .glossary-hover { */ +.glossary-hover:has(+ .glossary-link:hover) { display: initial; } From 65da8bb599f9ba7e949b4440bef9ac908b8d0918 Mon Sep 17 00:00:00 2001 From: Fraser Date: Sat, 12 Aug 2023 17:48:04 -0400 Subject: [PATCH 07/10] fill glossary terms in human written answers --- web/src/glossary.tsx | 4 ---- web/src/pages/index.tsx | 2 +- web/src/styles/globals.css | 1 + 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/web/src/glossary.tsx b/web/src/glossary.tsx index 8269719..c30329e 100644 --- a/web/src/glossary.tsx +++ b/web/src/glossary.tsx @@ -52,7 +52,3 @@ export const GlossarySpan: React.FC<{content: string}> = ({content}) => { })}} />; } - - - - diff --git a/web/src/pages/index.tsx b/web/src/pages/index.tsx index d1a84a5..fc5a14a 100644 --- a/web/src/pages/index.tsx +++ b/web/src/pages/index.tsx @@ -540,7 +540,7 @@ const Home: NextPage = () => { maxWidth: "99.8%", }} > -
+
diff --git a/web/src/styles/globals.css b/web/src/styles/globals.css index ceef29c..bf3258a 100644 --- a/web/src/styles/globals.css +++ b/web/src/styles/globals.css @@ -46,6 +46,7 @@ ol { transform: translateY(1.7rem); @apply bg-white hover:bg-gray-300 h-fit px-3; @apply border border-gray-300; + @apply text-black; } /* .glossary-link:hover + .glossary-hover { */ From 4fcc46dac184483d9e6d00a8fb3b3304209dd12c Mon Sep 17 00:00:00 2001 From: Fraser Date: Tue, 15 Aug 2023 22:32:51 -0400 Subject: [PATCH 08/10] pre-sort keys --- web/src/pages/_app.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/web/src/pages/_app.tsx b/web/src/pages/_app.tsx index 39d7cd4..51f9b5f 100644 --- a/web/src/pages/_app.tsx +++ b/web/src/pages/_app.tsx @@ -15,7 +15,9 @@ const MyApp: AppType = ({ Component, pageProps }) => { .then((res) => res.json()) .then((data) => { const glossary: Glossary = new Map(Object.entries(data)); - const regex = new RegExp(Array.from(glossary.keys()).join("|"), "gim"); + const keys = Array.from(glossary.keys()).sort((a, b) => b.length - a.length); + console.log(keys); + const regex = new RegExp(keys.join("|"), "gim"); setGlossary({ g: glossary, r: regex }); }); }, []); From 5e4198aef91b45bb4bc134f8604a95fba2bfefa9 Mon Sep 17 00:00:00 2001 From: Fraser Date: Tue, 15 Aug 2023 22:39:08 -0400 Subject: [PATCH 09/10] add word boundaries to terms, escape regex chars --- web/src/pages/_app.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/web/src/pages/_app.tsx b/web/src/pages/_app.tsx index 51f9b5f..6e1fa20 100644 --- a/web/src/pages/_app.tsx +++ b/web/src/pages/_app.tsx @@ -15,8 +15,11 @@ const MyApp: AppType = ({ Component, pageProps }) => { .then((res) => res.json()) .then((data) => { const glossary: Glossary = new Map(Object.entries(data)); - const keys = Array.from(glossary.keys()).sort((a, b) => b.length - a.length); - console.log(keys); + const keys = Array.from(glossary.keys()) + .sort((a, b) => b.length - a.length) // sort by length descending + .map((k) => k.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')) // escape regex chars + .map((k) => `\\b${k}\\b`); // add word boundaries + const regex = new RegExp(keys.join("|"), "gim"); setGlossary({ g: glossary, r: regex }); }); From f47c68b6659122126ec958e79be1e53a0cb8ad91 Mon Sep 17 00:00:00 2001 From: Fraser Date: Tue, 15 Aug 2023 22:47:22 -0400 Subject: [PATCH 10/10] don't link when glossary term has no page entry --- web/src/glossary.tsx | 23 +++++++++++++++-------- web/src/styles/globals.css | 4 ++++ 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/web/src/glossary.tsx b/web/src/glossary.tsx index c30329e..3cbc5a5 100644 --- a/web/src/glossary.tsx +++ b/web/src/glossary.tsx @@ -41,14 +41,21 @@ export const GlossarySpan: React.FC<{content: string}> = ({content}) => { const hover_content = item.contents; const pageid = item.pageid; - return ` -
${hover_content}
-
- ${match} - - `; + if (pageid == undefined || pageid.trim() == "") { + return ` +
${hover_content}
+ ${match} + `; + } else { + return ` +
${hover_content}
+ + ${match} + + `; + } })}} />; } diff --git a/web/src/styles/globals.css b/web/src/styles/globals.css index bf3258a..c9c3612 100644 --- a/web/src/styles/globals.css +++ b/web/src/styles/globals.css @@ -53,3 +53,7 @@ ol { .glossary-hover:has(+ .glossary-link:hover) { display: initial; } + +.glossary-link { + @apply underline hover:no-underline; +}