From 2b2b88f9701b0b1383e5a8aece8f132385e2df8a Mon Sep 17 00:00:00 2001 From: Emma Date: Mon, 5 Aug 2024 06:25:55 -0600 Subject: [PATCH] MD handlers for resolvers --- lib/tcmd/Resolver.tsx | 55 +++++++++++++++++++ lib/tcmd/TokenIdentifiers.tsx | 81 +++++++++++++++++++++++++++- lib/tcmd/index.ts | 1 - lib/ttcQuery/TTCQueryParser.ts | 10 ---- lib/ttcQuery/TTCResolvers.ts | 13 ++--- md/help articles/How to use ttcMD.md | 49 ++++++++++++++--- recoil/atoms/publication.ts | 9 ++++ 7 files changed, 193 insertions(+), 25 deletions(-) create mode 100644 lib/tcmd/Resolver.tsx create mode 100644 recoil/atoms/publication.ts diff --git a/lib/tcmd/Resolver.tsx b/lib/tcmd/Resolver.tsx new file mode 100644 index 0000000..f12b1e5 --- /dev/null +++ b/lib/tcmd/Resolver.tsx @@ -0,0 +1,55 @@ +import { PublicationAtom } from "@/recoil/atoms/publication"; +import { useState, useEffect, useCallback, useRef } from "react"; +import { useRecoilValue } from "recoil"; +import { TTCQueryResolver } from "../ttcQuery/TTCResolvers"; + +export function Resolver({ resolver }: { resolver: string }) { + const parser = useRecoilValue(PublicationAtom); + const [res] = useState(new TTCQueryResolver(parser)); + const [content, setContent] = useState(""); + useEffect(() => { + setContent(res.resolve(resolver)); + }, [resolver, res]); + return {content}; +} + +export function OnDemandResolver({ + resolver, + template, + title, +}: { + resolver: string; + template: string; + title?: string; +}) { + const parser = useRecoilValue(PublicationAtom); + const res = useRef(new TTCQueryResolver(parser)); + const [content, setContent] = useState(""); + const generateContent = useCallback(() => { + let content = template; + const stackIdxs = Array.from(new Set(template.match(/\$\d/g))); + for (const idx of stackIdxs) { + let thing = res.current.getFromStack(idx); + debugger; + if (Array.isArray(thing)) thing = thing.at(0); + if (typeof thing === "function") thing = thing(); + content = content.replaceAll(idx, thing as string); + } + setContent(content); + }, [res, template]); + + const resolve = useCallback(() => { + res.current.resolve(resolver, true); + generateContent(); + }, [res, resolver, generateContent]); + + return ( + <> + +
+ {!!content && {content}} + + ); +} diff --git a/lib/tcmd/TokenIdentifiers.tsx b/lib/tcmd/TokenIdentifiers.tsx index 06e0604..7d2e62b 100644 --- a/lib/tcmd/TokenIdentifiers.tsx +++ b/lib/tcmd/TokenIdentifiers.tsx @@ -3,6 +3,7 @@ import Link from "next/link"; import React, { Fragment } from "react"; import { Poppable } from "../poppables/components/poppable"; import { Accordion, AccordionContent } from "../accordion"; +import { OnDemandResolver, Resolver } from "./Resolver"; export const TokenRenderers = new Map>(); @@ -123,7 +124,7 @@ export const buildOnlyDefaultElements = () => { ); }, - /(? { } ); + // accordion registerIdentifier( "accordion", /\[accordion(\s.*?)?]\n+((?:.|\n)*?)\n+\[\/accordion\]/g, @@ -546,6 +548,7 @@ export const buildOnlyDefaultElements = () => { } ); + // paragraph registerIdentifier( "p", /(?<=\n\n)([\s\S]*?)(?=\n\n)/g, @@ -570,6 +573,7 @@ export const buildOnlyDefaultElements = () => { } ); + // horizontal rule registerIdentifier( "hr", /^-{3,}$/gm, @@ -587,6 +591,7 @@ export const buildOnlyDefaultElements = () => { } ); + // comment registerIdentifier( "comment", //g, @@ -604,6 +609,7 @@ export const buildOnlyDefaultElements = () => { } ); + // frontmatter registerIdentifier( "frontmatter", /^---([\s\S]*?)---/g, @@ -622,6 +628,7 @@ export const buildOnlyDefaultElements = () => { } ); + // table registerIdentifier( "table", /(?<=\n|^)\| [\s\S]*? \|(?=(\n|$)(?!\|))/g, @@ -767,6 +774,75 @@ export const buildOnlyDefaultElements = () => { } ); + // resolver + registerIdentifier( + "resolver", + /\?\?<<(.*?)>>/g, + (s) => { + const inp = s.match(/(?<=<<)(.*?)(?=>>)/)![0]; + if (inp == undefined) + return { + content: "Error parsing resolver: " + s, + metadata: {}, + raw: "ERROR", + uuid: crypto.randomUUID(), + }; + return { + content: inp, + metadata: {}, + raw: s, + uuid: crypto.randomUUID(), + }; + }, + (t) => { + if (t.content.startsWith("Error")) + return {t.content}; + return ; + } + ); + + // on-demand resolver + registerIdentifier( + "on-demand resolver", + /\?\?\[.*?\](\(.*?\))?<<(.*?)>>/g, + (s) => { + const inp = s.match(/(?<=<<)(.*?)(?=>>)/)![0]; + const template = s.match(/(?<=\?\?\[)(.*?)(?=\])/)![0]; + const title = s.match(/(?<=\]\()(.*?)(?=\))/)?.at(0); + if (inp == undefined) + return { + content: "Error parsing resolver: " + s, + metadata: { + title: "", + template: "", + }, + raw: "ERROR", + uuid: crypto.randomUUID(), + }; + return { + content: inp, + metadata: { + title, + template, + }, + raw: s, + uuid: crypto.randomUUID(), + }; + }, + (t) => { + if (t.content.startsWith("Error")) + return {t.content}; + + return ( + + ); + } + ); + return TokenIdentifiers; }; @@ -840,7 +916,8 @@ function search( closeRx ); - if (newEnd === null) throw Error("There was an issue finding a closing tag"); + if (newEnd === null) + throw Error("There was an issue finding a closing tag for "); end = newEnd + start; diff --git a/lib/tcmd/index.ts b/lib/tcmd/index.ts index 7da4f4d..f6151a6 100644 --- a/lib/tcmd/index.ts +++ b/lib/tcmd/index.ts @@ -5,7 +5,6 @@ import { buildOnlyDefaultElements, TokenRenderers } from "./TokenIdentifiers"; export const createElements = (body: string): Token[] => { const tokens = tokenize(body); - console.log(tokens); return buildAbstractSyntaxTree(tokens).map((t) => t.token); }; diff --git a/lib/ttcQuery/TTCQueryParser.ts b/lib/ttcQuery/TTCQueryParser.ts index 8026652..e064b4c 100644 --- a/lib/ttcQuery/TTCQueryParser.ts +++ b/lib/ttcQuery/TTCQueryParser.ts @@ -147,13 +147,3 @@ export class TTCQueryParser { } } } - -// Example usage: -// const - -// const parser = new TTCQueryParser(data); - -// // Example queries -// console.log(parser.search("$weapon_abilities[name=Rapid Fire].body")); // Example output -// // console.log(parser.search("^weapon_abilities[name=Rapid Fire]", data)); // Example output -// console.log(parser.search("$weapon_abilities[name=Rapid Fire].body", data)); // Example output diff --git a/lib/ttcQuery/TTCResolvers.ts b/lib/ttcQuery/TTCResolvers.ts index 80299ce..cd2d8b0 100644 --- a/lib/ttcQuery/TTCResolvers.ts +++ b/lib/ttcQuery/TTCResolvers.ts @@ -26,12 +26,14 @@ export class TTCQueryResolver { } const last = this.stack.at(-1); if (typeof last === "function" && !onDemand) return last(); + + if (onDemand) debugger; return last; } private parseResolver(resolver: string) { if (this.isArithmetic(resolver)) return this.solveArithmetic(resolver); - if (this.isQuery(resolver)) this.runQuery(resolver); + if (this.isQuery(resolver)) return this.runQuery(resolver); } private isQuery(resolver: string) { return ( @@ -56,15 +58,15 @@ export class TTCQueryResolver { // if (query.startsWith("?") || query.startsWith("_")) return query.startsWith("_") && this.context - ? this.parser.search(query.replace("_", ""), this.context) - : this.parser.search(query.replace(/^[?_].?/, "")); + ? this.parser.search(query.replace("_", "^"), this.context).at(0) + : this.parser.search(query.replace(/^[?_].?/, "")).at(0); } private handleDice(dice: string, query: string) { const d = new Dice(dice); const [method, n] = query.split(":"); let num = Number(n); - if (n.startsWith("$")) num = this.getFromStack(n); + if (n && n.startsWith("$")) num = this.getFromStack(n); switch (method) { case "roll": return () => d.roll(); @@ -91,7 +93,6 @@ export class TTCQueryResolver { if (n1.startsWith("$")) num1 = this.getFromStack(n1); if (n2.startsWith("$")) num2 = this.getFromStack(n2); - console.log(num1, num2); switch (op) { case "+": @@ -109,7 +110,7 @@ export class TTCQueryResolver { } } - private getFromStack(stackIndex: string): T { + public getFromStack(stackIndex: string): T { const i = Number(stackIndex.replace("$", "")); const val = this.stack[i] as T; return val; diff --git a/md/help articles/How to use ttcMD.md b/md/help articles/How to use ttcMD.md index 7340ff3..cc45930 100644 --- a/md/help articles/How to use ttcMD.md +++ b/md/help articles/How to use ttcMD.md @@ -34,13 +34,17 @@ ttcMD is a flavor of markdown that has been specifically designed to use with [t One thing to note, however, is that ttcMD is *not* standard markdown. It does not generate valid markup for just about any element, and as such it will not be released in any scope wider than inside TTC itself. One could more accurately call it a layout language than actual markdown. +--- + ## Enhanced Standard Elements This section will cover all of the enhancements that are added for basic markdown elements +--- + ### Links -You can use the typical link syntax `[link name](/link/location)`, but there are a few presets that allow you to style them to look a bit nicer. +You can use the typical link syntax `[link name](/link/location)` but there are a few presets that allow you to style them to look a bit nicer. **Primary Button:** @@ -56,6 +60,8 @@ You can use the typical link syntax `[link name](/link/location)`, but there are [~~cta link name](#links) +--- + ### Tables Generally tables will only be as wide as their content needs. To make a table take up the full width, you can use this syntax: @@ -118,10 +124,14 @@ Full width with a centered body column /[] +--- + ## Custom Elements This section will cover the specific elements custom built for Tabletop Commander. +--- + ### Pop-outs Pop-outs, or popovers, are the little cards that "pop out" when you hover over an item. @@ -134,10 +144,14 @@ This syntax `^[This is my favorite image]<<*Goofy!* ![goofy](https:///example.co Note: currently, only inline elements are available, so formatting is limited +--- + ## Block-level Elements Block-level elements have a slightly different syntax than the single-line and inline elements we've seen so far. In order to use block-level elements, they *must* be formatted correctly, including the empty lines. As a general rule, you cannot nest block-level elements within themselves, but you can nest different block-level elements within it. +--- + ### Accordions Accordions are when you can click an item to expand it to show additional information. @@ -204,6 +218,8 @@ You'll never believe me when I tell you that this legitimately is one of my favo ]] /[] +--- + ### Card Cards are just neat boxes. They can hold any markdown within them, but not other cards (it looks bad). @@ -248,6 +264,8 @@ Additionally, you can specify a number after the opening brackets (`[[2 ... ]]`) /[] +--- + ### Block [][][] @@ -273,6 +291,7 @@ Additionally, you can specify a number after the opening brackets (`[[!2 ... ]]` /[] +--- ### Grid @@ -319,12 +338,16 @@ This card will end up in the third column... /[] +--- + ## Query Elements The following elements are used in combination with ttcQuery. These are definitely more advanced. If you understand generally what "dot notation" is in programming, then it will be a lot easier, but don't let that deter you. Once you understand what it is, you can come back to this and be able to create really cool layouts! Query elements (aside for the on-demand resolver) are calculated before parsing the markdown. Will that matter to you? Probably not, but could be necessary as you think about how you are writing your query elements. +--- + ### Resolver The resolver is the basic element that allows you to get data, but it has a lot of functionality to it. It has not been fully implemented yet @@ -333,21 +356,33 @@ Syntax: `??< QueryValue>>` If you've read the ttcQuery docs, you'll know that the `List` type means that you can input any number of `QueryValues` in this element. When the resolver runs, it runs each `QueryValue` from left to right. The last `QueryValue` to run is what gets rendered, but you have access to previous values through their index by using the `$#` variables. -As `QueryValues` are capable of arithmetic, you can quite easily do simple math inline, though it's unfortunately not quite as simple as just writing an equation. Each `QueryValue` can only do one arithmetic operation. This is to prevent performance issues with parsing arbitrary calculations. So, if you wanted to get the average of the values of 1, 2, 3 and 4, the query would look like this: `??<<1+2,3+4,$0+$1,$2/4>>` which will result in the value 2.5. Arithmetic will fail if a value provided is not a number and will render a message in the markdown. +As `QueryValues` are capable of arithmetic, you can quite easily do simple math inline, though it's unfortunately not quite as simple as just writing an equation. Each `QueryValue` can only do one arithmetic operation. This is to prevent performance issues with parsing arbitrary calculations. So, if you wanted to get the average of the values of 1, 2, 3 and 4, the query would look like this: `??<<1+2,3+4,$0+$1,$2/4>>` which will result in the value ??<<1+2,3+4,$0+$1,$2/4>>. Arithmetic will fail if a value provided is not a number and will render a message in the markdown. If the resolver results in a list of items, it will list all of them together, separated by commas. -Let's say you want to get the the result of rolling a dice field. You would simply write `$$<<_.path.to.dice,$0.roll>>`. This will roll the dice when the markdown is render, which means it only happens once. If you want to reroll the dice, you either need to reload the markdown by closing the viewer and reopening it, or you need to use an On-demand Resolver. +Let's say you want to get the the result of rolling a dice field. You would simply write `??<<_.path.to.dice,$0.roll>>` and get the result of the dice roll like this: ??<<_.path.to.dice,$0.roll>>. This will roll the dice when the markdown is rendered, which means it only happens once. If you want to reroll the dice, you either need to reload the markdown by closing the viewer and reopening it, or you need to use an On-demand Resolver. + +--- ### On-demand Resolver This works very similarly to the normal resolver, but when it renders it will have a button that you can press. When you press it, it recalculates its value. This is very useful for dice and decks of cards. It has not been fully implemented yet -Here's the syntax: `??[Template]<>`. Template is a basic string that has access to all values of the resolver using the `$#` variables. If template is left blank, the value that the resolver finally reaches will be rendered. In lieu of a template, you can also have it render the display of a field if it has one. +Here's the syntax: `??[Template](Text?)<>`. Template is a basic string that has access to all values of the resolver using the `$#` variables. If template is left blank, the value that the resolver finally reaches will be rendered. In lieu of a template, you can also have it render the display of a field if it has one. Text is the text that will be rendered in the button. If not provided, the button will simply same "Resolve." -To use the dice as an example again, here's how you would do that: `??[Rolling $0, you got: $1]<<_path.to.dice,$0.roll>>` +[][] +[[ +To use the dice as an example again, here's how you would do that: `??[Rolling $0, you got: $1](Roll 2d6)<<_.path.to.dice,$0.roll>>` -For drawing a card and having it show the card's display: `??[]<<_path.to.deck,$0.draw,$1.display>>` +??[Rolling $0, you got: $1](Roll 2d6)<<_.path.to.dice,$0.roll>> +]] + +[[ +For drawing a card and having it show the card's display: `??[]<<_.path.to.deck,$0.draw,$1.display>>` +]] +/[] + +--- ### Query Block Template @@ -366,6 +401,8 @@ Syntax: While in a Query Block Template, all resolvers will use the queried value as the root `_` variable. If the original query returns multiple values, it will render the markdown for each result. This is what makes it so useful. When you want to render your 40k army list, you can do so with very little markdown. +--- + ### Query Block Similar to the Query Block Template, the query block collects the values of the query and passes it to the markdown inside of it. However, instead of rendering the whole block, it instead renders the markdown a single time. It has not been fully implemented yet diff --git a/recoil/atoms/publication.ts b/recoil/atoms/publication.ts new file mode 100644 index 0000000..fa81829 --- /dev/null +++ b/recoil/atoms/publication.ts @@ -0,0 +1,9 @@ +import { TTCQueryParser } from "@/lib/ttcQuery/TTCQueryParser"; +import { atom } from "recoil"; + +export const PublicationAtom = atom({ + key: "publication", + default: new TTCQueryParser({ + path: { to: { dice: "2d6" } }, + }), +});