Skip to content

Commit

Permalink
WIP link to page heading
Browse files Browse the repository at this point in the history
This still has issues with overwriting the current location on forward/backward
navigation. See molefrog/wouter#300. Might still be
worthwhile to have anyway.
  • Loading branch information
noemica committed Jun 26, 2024
1 parent 98983ef commit f161e46
Show file tree
Hide file tree
Showing 5 changed files with 60 additions and 25 deletions.
2 changes: 1 addition & 1 deletion src/json/wiki.json
Original file line number Diff line number Diff line change
Expand Up @@ -8021,7 +8021,7 @@
},
{
"Name": "Example",
"Content": "This is a test of wiki content.\nContent separated by a newline is split into multiple paragraphs.\nA link: [[Example]]. A [[Example|link with content different than the link]]. A [[Assault Rifle|part link]] with part information shown on mouseover. A [[S-10 Pest|bot link]] with bot information shown on mouseover. A [[Materials|location link]]. A [[~/hacks|link to the Cog-Minder Hacks page]].\nLinks are a type of inline content that can be used anywhere. Other types of inline content are [[B]]bolded text[[/B]] and [[I]]italicized text[[/I]]. Links can also be [[B]][[Example|bolded]][[/B]], [[I]][[Example|italicized]][[/I]], and [[B]][[I]][[Example|both together]][[/I]][[/B]]. Another option is [[GameText]]Inline Game Text[[/GameText]] that uses a separate font style. [[Sub]]Subscript[[/Sub]] and [[Sup]]Superscript[[/Sup]] are also available.\n[[Heading]]Heading type 1[[/Heading]]\n[[Heading:2]]Heading type 2[[/Heading]]\n[[Heading:1]]Heading type 1 [[B]]with[[/B]] [[I]]inline[[/I]] [[Example|content]][[/Heading]]\nText content before an image shows up vertically above.[[Image]]Materials.png|An image [[I]]with[[/I]] [[B]]inline[[/B]] [[Materials|content]] in the caption[[/Image]]Images will show on the far right side of the page while text content after an image shows up to the left of it.\n[[List]]Lists of items|can be generated|like this[[/List]]\n[[List:Unordered]]Lists can be|explicitly unordered[[/List]]\n[[List:Ordered]]And they can also|be ordered|and [[B]]have[[/B]] [[I]]inline[[/I]] [[Example|content]][[/List]]\n2-dimensional tables can also be created and have inline content effects added.\n[[Table]]Column 1|Column 2||Row 1 Column 1|Row 1 Column 2||[[B]]Row 2 Column 1[[/B]]|[[I]]Row 2 Column 2[[/I]]||[[CellStyle:Good]]Styles can be applied for individual cells|[[CellStyle:Neutral]]Currently 3 styles supported||[[CellStyle:Bad]]Good, Neutral, and Bad| ||[[CellSpan:2]]Cells can also span multiple columns[[/Table]]\nLore entries can also be added like below: [[Lore]]0b10 Records|Materials[[/Lore]]\n[[Gallery]]Materials.png|Here is a gallery of images with|Materials Main Exit.png|captions that will stack|Materials Mines Exit.png|horizontally until there is no|Materials OOD Cache.png|more space, then they will spill|Materials Storage Cache.png|eventually over a lot of space|Mines.png|onto the next line|Materials.png|These images also [[B]]support[[/B]] [[I]]inline[[/I]] [[Example|content]]|Image Not Found.png|There is an image not found backup but these shouldn't show up normally.|../game_sprites/Terminal.png|An image accessing a local non-wiki image.|https://i.ibb.co/JHBS25D/image.png|An external image by URL.[[/Gallery]]\nThere are two types of spoilers: [[Spoiler]]Spoiler-tier spoilers[[/Spoiler]] and [[Redacted]]Redacted-tier spoilers[[/Redacted]]. Text will be blocked with gray bars unless moused over, and images will be faded/blurred out. It is possible to nest [[Spoiler]]spoiler text and [[Redacted]] redacted text underneath.[[/Redacted]][[/Spoiler]]This will create 2 layers of spoilered text that will only show the appropriate spoiler effects for each level. Spoilered content can also contain any types [[Redacted]][[B]]of[[/B]] [[I]]inline[[/I]] [[Example|content]][[/Redacted]][[Redacted]][[Image]]Factory.png|Redacted image[[/Image]]\n[[Heading]]Redacted Heading[[/Heading]]\n[[Gallery]]Research.png|Redacted group|Access.png|of images[[/Gallery]]\n[[/Redacted]][[Image]]Factory.png|Image with a partially [[Redacted]]Redacted[[/Redacted]] caption[[/Image]]\n[[Gallery]]Research.png|These images can also show|Access.png|[[Spoiler]]Spoiler[[/Spoiler]] and [[Redacted]]Redacted[[/Redacted]] content[[/Gallery]]\n[[Redacted]][[List]]Lists can also|be spoiled entirely[[/List]][[/Redacted]][[List]]They can also have|some [[Redacted]]spoiled content[[/Redacted]][[/List]]\nGame text and Lore entries can also be spoilered: [[Redacted]][[GameText]]Game text[[/GameText]]\nLore entry: [[Lore]]0b10 Records|Materials[[/Lore]][[/Redacted]]",
"Content": "[[Alert#Ways to raise alert|Ways to raise alert]]\nThis is a test of wiki content.\nContent separated by a newline is split into multiple paragraphs.\nA link: [[Example]]. A [[Example|link with content different than the link]]. A [[Assault Rifle|part link]] with part information shown on mouseover. A [[S-10 Pest|bot link]] with bot information shown on mouseover. A [[Materials|location link]]. A [[~/hacks|link to the Cog-Minder Hacks page]].\nLinks are a type of inline content that can be used anywhere. Other types of inline content are [[B]]bolded text[[/B]] and [[I]]italicized text[[/I]]. Links can also be [[B]][[Example|bolded]][[/B]], [[I]][[Example|italicized]][[/I]], and [[B]][[I]][[Example|both together]][[/I]][[/B]]. Another option is [[GameText]]Inline Game Text[[/GameText]] that uses a separate font style. [[Sub]]Subscript[[/Sub]] and [[Sup]]Superscript[[/Sup]] are also available.\n[[Heading]]Heading type 1[[/Heading]]\n[[Heading:2]]Heading type 2[[/Heading]]\n[[Heading:1]]Heading type 1 [[B]]with[[/B]] [[I]]inline[[/I]] [[Example|content]][[/Heading]]\nText content before an image shows up vertically above.[[Image]]Materials.png|An image [[I]]with[[/I]] [[B]]inline[[/B]] [[Materials|content]] in the caption[[/Image]]Images will show on the far right side of the page while text content after an image shows up to the left of it.\n[[List]]Lists of items|can be generated|like this[[/List]]\n[[List:Unordered]]Lists can be|explicitly unordered[[/List]]\n[[List:Ordered]]And they can also|be ordered|and [[B]]have[[/B]] [[I]]inline[[/I]] [[Example|content]][[/List]]\n2-dimensional tables can also be created and have inline content effects added.\n[[Table]]Column 1|Column 2||Row 1 Column 1|Row 1 Column 2||[[B]]Row 2 Column 1[[/B]]|[[I]]Row 2 Column 2[[/I]]||[[CellStyle:Good]]Styles can be applied for individual cells|[[CellStyle:Neutral]]Currently 3 styles supported||[[CellStyle:Bad]]Good, Neutral, and Bad| ||[[CellSpan:2]]Cells can also span multiple columns[[/Table]]\nLore entries can also be added like below: [[Lore]]0b10 Records|Materials[[/Lore]]\n[[Gallery]]Materials.png|Here is a gallery of images with|Materials Main Exit.png|captions that will stack|Materials Mines Exit.png|horizontally until there is no|Materials OOD Cache.png|more space, then they will spill|Materials Storage Cache.png|eventually over a lot of space|Mines.png|onto the next line|Materials.png|These images also [[B]]support[[/B]] [[I]]inline[[/I]] [[Example|content]]|Image Not Found.png|There is an image not found backup but these shouldn't show up normally.|../game_sprites/Terminal.png|An image accessing a local non-wiki image.|https://i.ibb.co/JHBS25D/image.png|An external image by URL.[[/Gallery]]\nThere are two types of spoilers: [[Spoiler]]Spoiler-tier spoilers[[/Spoiler]] and [[Redacted]]Redacted-tier spoilers[[/Redacted]]. Text will be blocked with gray bars unless moused over, and images will be faded/blurred out. It is possible to nest [[Spoiler]]spoiler text and [[Redacted]] redacted text underneath.[[/Redacted]][[/Spoiler]]This will create 2 layers of spoilered text that will only show the appropriate spoiler effects for each level. Spoilered content can also contain any types [[Redacted]][[B]]of[[/B]] [[I]]inline[[/I]] [[Example|content]][[/Redacted]][[Redacted]][[Image]]Factory.png|Redacted image[[/Image]]\n[[Heading]]Redacted Heading[[/Heading]]\n[[Gallery]]Research.png|Redacted group|Access.png|of images[[/Gallery]]\n[[/Redacted]][[Image]]Factory.png|Image with a partially [[Redacted]]Redacted[[/Redacted]] caption[[/Image]]\n[[Gallery]]Research.png|These images can also show|Access.png|[[Spoiler]]Spoiler[[/Spoiler]] and [[Redacted]]Redacted[[/Redacted]] content[[/Gallery]]\n[[Redacted]][[List]]Lists can also|be spoiled entirely[[/List]][[/Redacted]][[List]]They can also have|some [[Redacted]]spoiled content[[/Redacted]][[/List]]\nGame text and Lore entries can also be spoilered: [[Redacted]][[GameText]]Game text[[/GameText]]\nLore entry: [[Lore]]0b10 Records|Materials[[/Lore]][[/Redacted]]",
"Spoiler": "Spoiler"
},
{
Expand Down
2 changes: 1 addition & 1 deletion src/ts/components/App/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ export default function App() {
const [location] = useLocation();
const [lastLocation, setLastLocation] = useLastLocation();

// Explicitly scroll back to top whenever the URL changes
// Explicitly scroll back to top whenever the subpage changes
useEffect(() => {
if (location !== lastLocation) {
window.scrollTo(0, 0);
Expand Down
12 changes: 7 additions & 5 deletions src/ts/components/Pages/WikiPage/WikiPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -413,12 +413,14 @@ export default function WikiPage() {
// element needs to be immediately available when the page first
// renders, which may not be true if the javascript is not immediately
// loaded/cached. Thus, this only needs to be done on initial load.
const element = document.getElementById(hashLocation.slice(1));
setTimeout(() => {
const element = document.getElementById(hashLocation.slice(1));

if (element !== null) {
element.scrollIntoView();
}
}, []);
if (element !== null) {
element.scrollIntoView();
}
}, 0);
}, [hashLocation]);

let baseEntry: WikiEntry | undefined;
let entry: WikiEntry | undefined;
Expand Down
21 changes: 15 additions & 6 deletions src/ts/components/Pages/WikiPage/WikiTooltips.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ import ItemDetails from "../../GameDetails/ItemDetails";
import LocationDetails from "../../GameDetails/LocationDetails";
import { Tooltip, TooltipContent, TooltipTrigger } from "../../Popover/Tooltip";

export function BotLink({ bot, text }: { bot: Bot; text?: string }) {
export function BotLink({ bot, linkTarget, text }: { bot: Bot; linkTarget?: string; text?: string }) {
const positioning = usePopoverPositioning();

return (
<Tooltip placement={positioning.placement} shouldShift={positioning.shouldShift}>
<TooltipTrigger asChild={true}>
<Link href={`/${getLinkSafeString(bot.name)}`}>{text || bot.name}</Link>
<Link href={linkTarget || `/${getLinkSafeString(bot.name)}`}>{text || bot.name}</Link>
</TooltipTrigger>
<TooltipContent floatingArrowClassName="bot-tooltip-arrow" className="bot-tooltip">
<BotDetails bot={bot} />
Expand All @@ -26,10 +26,10 @@ export function BotLink({ bot, text }: { bot: Bot; text?: string }) {
);
}

export function ItemLink({ item, text }: { item: Item; text?: ReactNode }) {
export function ItemLink({ item, linkTarget, text }: { item: Item; linkTarget?: string; text?: ReactNode }) {
return (
<ItemTooltip item={item}>
<Link href={`/${getLinkSafeString(item.name)}`}>{text || item.name}</Link>
<Link href={linkTarget || `/${getLinkSafeString(item.name)}`}>{text || item.name}</Link>
</ItemTooltip>
);
}
Expand All @@ -47,13 +47,22 @@ export function ItemTooltip({ item, children }: { item: Item; children: ReactNod
);
}

export function LocationLink({ location, text }: { location: MapLocation; text?: string; inPopover?: boolean }) {
export function LocationLink({
linkTarget,
location,
text,
}: {
linkTarget?: string;
location: MapLocation;
text?: string;
inPopover?: boolean;
}) {
const positioning = usePopoverPositioning();

return (
<Tooltip placement={positioning.placement} shouldShift={positioning.shouldShift}>
<TooltipTrigger asChild={true}>
<Link href={`/${getLinkSafeString(location.name)}`}>{text || location.name}</Link>
<Link href={linkTarget || `/${getLinkSafeString(location.name)}`}>{text || location.name}</Link>
</TooltipTrigger>
<TooltipContent floatingArrowClassName="location-tooltip-arrow" className="bot-tooltip">
<LocationDetails location={location} inPopover={true} />
Expand Down
48 changes: 36 additions & 12 deletions src/ts/utilities/wikiParser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -128,10 +128,7 @@ function cleanHeadingText(text: string): { text: string; id: string } {

// Replace all spaces with underscore since spaces are technically not allowed
// Then strip out all chars except for alphabetical and -/_s.
const textId = cleanedText
.replaceAll(" ", "_")
.replaceAll(/[^\w-]/g, "")
.toLowerCase();
const textId = createIdFromText(cleanedText);

return { text: cleanedText, id: textId };
}
Expand Down Expand Up @@ -195,6 +192,14 @@ export function createContentHtml(
};
}

// Turns an arbitrary string into an HTML ID-compatible one
function createIdFromText(text: string) {
return text
.replaceAll(" ", "_")
.replaceAll(/[^\w-]/g, "")
.toLowerCase();
}

// Creates the preview content for a search result
// This strips out any spoiler/redacted tags as well as their internal content
// if not allowed by current spoiler level
Expand Down Expand Up @@ -230,20 +235,20 @@ export function createPreviewContent(content: string, spoilerState: Spoiler): st
return content;
}

function getLinkNode(state: ParserState, referenceEntry: WikiEntry, linkText: string) {
function getLinkNode(state: ParserState, referenceEntry: WikiEntry, linkText: string, linkTarget?: string) {
let node: ReactNode | undefined;

if (referenceEntry.type === "Bot") {
const bot = referenceEntry.extraData as Bot;
node = <BotLink bot={bot} text={linkText} />;
node = <BotLink bot={bot} linkTarget={linkTarget} text={linkText} />;
} else if (referenceEntry.type === "Location") {
const location = referenceEntry.extraData as MapLocation;
node = <LocationLink location={location} text={linkText} />;
node = <LocationLink linkTarget={linkTarget} location={location} text={linkText} />;
} else if (referenceEntry.type === "Part") {
const item = referenceEntry.extraData as Item;
node = <ItemLink item={item} text={linkText} />;
node = <ItemLink item={item} linkTarget={linkTarget} text={linkText} />;
} else {
node = <Link href={`/${getLinkSafeString(referenceEntry.name)}`}>{linkText}</Link>;
node = <Link href={linkTarget || `/${getLinkSafeString(referenceEntry.name)}`}>{linkText}</Link>;
}

if (!canShowSpoiler(referenceEntry.spoiler, state.spoiler) && !state.inSpoiler) {
Expand Down Expand Up @@ -964,7 +969,7 @@ function processLinkTag(state: ParserState, result: RegExpExecArray) {
// Remove the earlier substituted {{ and }}s for their proper [ and ] counterparts
const split = result[1].replace("{{", "[").replace("}}", "]").split("|");

const linkTarget = split[0];
let linkTarget: string | undefined = split[0];
let linkText = linkTarget;
if (split.length > 1) {
linkText = split[1];
Expand All @@ -974,9 +979,28 @@ function processLinkTag(state: ParserState, result: RegExpExecArray) {
}
}

const referenceEntry = state.allEntries.get(linkTarget);
let referenceEntry: WikiEntry | undefined;

const hashSplit = linkTarget.split("#");
if (hashSplit.length > 1 && state.allEntries.get(hashSplit[0]) !== undefined) {
// Need to split # portion out in order to convert to the real heading ID
referenceEntry = state.allEntries.get(hashSplit[0]);

linkTarget = `/${getLinkSafeString(hashSplit[0])}#${createIdFromText(hashSplit[1])}`;

if (split.length > 2) {
recordError(state, "Too many # in link");
}
} else {
referenceEntry = state.allEntries.get(linkTarget);

if (referenceEntry !== undefined) {
linkTarget = `/${getLinkSafeString(linkTarget)}`;
}
}

if (referenceEntry !== undefined) {
const html = getLinkNode(state, referenceEntry, linkText);
const html = getLinkNode(state, referenceEntry, linkText, linkTarget);

state.output.push({
groupType: "Grouped",
Expand Down

0 comments on commit f161e46

Please sign in to comment.