Compare commits

..

2 Commits

Author SHA1 Message Date
0f100bba3d allows for arbitrary pushing of values onto stack 2024-08-05 06:31:58 -06:00
2b2b88f970 MD handlers for resolvers 2024-08-05 06:25:55 -06:00
7 changed files with 195 additions and 27 deletions

55
lib/tcmd/Resolver.tsx Normal file
View File

@ -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 <span>{content}</span>;
}
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 (
<>
<button onMouseDown={() => setContent("")} onClick={resolve}>
{title ?? "Resolve"}
</button>
<br />
{!!content && <span>{content}</span>}
</>
);
}

View File

@ -3,6 +3,7 @@ import Link from "next/link";
import React, { Fragment } from "react"; import React, { Fragment } from "react";
import { Poppable } from "../poppables/components/poppable"; import { Poppable } from "../poppables/components/poppable";
import { Accordion, AccordionContent } from "../accordion"; import { Accordion, AccordionContent } from "../accordion";
import { OnDemandResolver, Resolver } from "./Resolver";
export const TokenRenderers = new Map<string, TokenRenderer<any>>(); export const TokenRenderers = new Map<string, TokenRenderer<any>>();
@ -123,7 +124,7 @@ export const buildOnlyDefaultElements = () => {
</div> </div>
); );
}, },
/(?<!\/)(?:\[\])+/g, /(?<![\/\?])(?:\[\])+/g,
/\/\[\]/g /\/\[\]/g
); );
@ -517,6 +518,7 @@ export const buildOnlyDefaultElements = () => {
} }
); );
// accordion
registerIdentifier( registerIdentifier(
"accordion", "accordion",
/\[accordion(\s.*?)?]\n+((?:.|\n)*?)\n+\[\/accordion\]/g, /\[accordion(\s.*?)?]\n+((?:.|\n)*?)\n+\[\/accordion\]/g,
@ -546,6 +548,7 @@ export const buildOnlyDefaultElements = () => {
} }
); );
// paragraph
registerIdentifier( registerIdentifier(
"p", "p",
/(?<=\n\n)([\s\S]*?)(?=\n\n)/g, /(?<=\n\n)([\s\S]*?)(?=\n\n)/g,
@ -570,6 +573,7 @@ export const buildOnlyDefaultElements = () => {
} }
); );
// horizontal rule
registerIdentifier( registerIdentifier(
"hr", "hr",
/^-{3,}$/gm, /^-{3,}$/gm,
@ -587,6 +591,7 @@ export const buildOnlyDefaultElements = () => {
} }
); );
// comment
registerIdentifier( registerIdentifier(
"comment", "comment",
/<!--[\s\S]+?-->/g, /<!--[\s\S]+?-->/g,
@ -604,6 +609,7 @@ export const buildOnlyDefaultElements = () => {
} }
); );
// frontmatter
registerIdentifier( registerIdentifier(
"frontmatter", "frontmatter",
/^---([\s\S]*?)---/g, /^---([\s\S]*?)---/g,
@ -622,6 +628,7 @@ export const buildOnlyDefaultElements = () => {
} }
); );
// table
registerIdentifier( registerIdentifier(
"table", "table",
/(?<=\n|^)\| [\s\S]*? \|(?=(\n|$)(?!\|))/g, /(?<=\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 <span className="red-500">{t.content}</span>;
return <Resolver resolver={t.content} />;
}
);
// 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 <span className="red-500">{t.content}</span>;
return (
<OnDemandResolver
resolver={t.content}
template={t.metadata.template}
title={t.metadata.title}
/>
);
}
);
return TokenIdentifiers; return TokenIdentifiers;
}; };
@ -840,7 +916,8 @@ function search(
closeRx 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; end = newEnd + start;

View File

@ -5,7 +5,6 @@ import { buildOnlyDefaultElements, TokenRenderers } from "./TokenIdentifiers";
export const createElements = (body: string): Token[] => { export const createElements = (body: string): Token[] => {
const tokens = tokenize(body); const tokens = tokenize(body);
console.log(tokens);
return buildAbstractSyntaxTree(tokens).map((t) => t.token); return buildAbstractSyntaxTree(tokens).map((t) => t.token);
}; };

View File

@ -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

View File

@ -26,12 +26,15 @@ export class TTCQueryResolver {
} }
const last = this.stack.at(-1); const last = this.stack.at(-1);
if (typeof last === "function" && !onDemand) return last(); if (typeof last === "function" && !onDemand) return last();
if (onDemand) debugger;
return last; return last;
} }
private parseResolver(resolver: string) { private parseResolver(resolver: string) {
if (this.isArithmetic(resolver)) return this.solveArithmetic(resolver); if (this.isArithmetic(resolver)) return this.solveArithmetic(resolver);
if (this.isQuery(resolver)) this.runQuery(resolver); if (this.isQuery(resolver)) return this.runQuery(resolver);
return resolver;
} }
private isQuery(resolver: string) { private isQuery(resolver: string) {
return ( return (
@ -42,7 +45,6 @@ export class TTCQueryResolver {
} }
private runQuery(query: string) { private runQuery(query: string) {
if (!this.parser) throw "Parser not defined in query resolver"; if (!this.parser) throw "Parser not defined in query resolver";
if (query.startsWith("$")) { if (query.startsWith("$")) {
const [_, idx, q] = query.match(/^(\$\d+)\.(.*)/) || []; const [_, idx, q] = query.match(/^(\$\d+)\.(.*)/) || [];
if (!_) throw "Detected stack query but did not match the regex"; if (!_) throw "Detected stack query but did not match the regex";
@ -51,20 +53,20 @@ export class TTCQueryResolver {
return this.handleDice(stackItem, q); return this.handleDice(stackItem, q);
} }
return this.parser.search(q); return this.parser.search(q, stackItem as QueryableObject);
} }
// if (query.startsWith("?") || query.startsWith("_")) // if (query.startsWith("?") || query.startsWith("_"))
return query.startsWith("_") && this.context return query.startsWith("_") && this.context
? this.parser.search(query.replace("_", ""), this.context) ? this.parser.search(query.replace("_", "^"), this.context).at(0)
: this.parser.search(query.replace(/^[?_].?/, "")); : this.parser.search(query.replace(/^[?_].?/, "")).at(0);
} }
private handleDice(dice: string, query: string) { private handleDice(dice: string, query: string) {
const d = new Dice(dice); const d = new Dice(dice);
const [method, n] = query.split(":"); const [method, n] = query.split(":");
let num = Number(n); let num = Number(n);
if (n.startsWith("$")) num = this.getFromStack(n); if (n && n.startsWith("$")) num = this.getFromStack(n);
switch (method) { switch (method) {
case "roll": case "roll":
return () => d.roll(); return () => d.roll();
@ -91,7 +93,6 @@ export class TTCQueryResolver {
if (n1.startsWith("$")) num1 = this.getFromStack<number>(n1); if (n1.startsWith("$")) num1 = this.getFromStack<number>(n1);
if (n2.startsWith("$")) num2 = this.getFromStack<number>(n2); if (n2.startsWith("$")) num2 = this.getFromStack<number>(n2);
console.log(num1, num2);
switch (op) { switch (op) {
case "+": case "+":
@ -109,7 +110,7 @@ export class TTCQueryResolver {
} }
} }
private getFromStack<T>(stackIndex: string): T { public getFromStack<T>(stackIndex: string): T {
const i = Number(stackIndex.replace("$", "")); const i = Number(stackIndex.replace("$", ""));
const val = this.stack[i] as T; const val = this.stack[i] as T;
return val; return val;

View File

@ -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. 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 ## Enhanced Standard Elements
This section will cover all of the enhancements that are added for basic markdown elements This section will cover all of the enhancements that are added for basic markdown elements
---
### Links ### 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:** **Primary Button:**
@ -56,6 +60,8 @@ You can use the typical link syntax `[link name](/link/location)`, but there are
[~~cta link name](#links) [~~cta link name](#links)
---
### Tables ### 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: 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 ## Custom Elements
This section will cover the specific elements custom built for Tabletop Commander. This section will cover the specific elements custom built for Tabletop Commander.
---
### Pop-outs ### Pop-outs
Pop-outs, or popovers, are the little cards that "pop out" when you hover over an item. 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 Note: currently, only inline elements are available, so formatting is limited
---
## Block-level Elements ## 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. 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
Accordions are when you can click an item to expand it to show additional information. 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 ### Card
Cards are just neat boxes. They can hold any markdown within them, but not other cards (it looks bad). 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 ### Block
[][][] [][][]
@ -273,6 +291,7 @@ Additionally, you can specify a number after the opening brackets (`[[!2 ... ]]`
/[] /[]
---
### Grid ### Grid
@ -319,12 +338,16 @@ This card will end up in the third column...
/[] /[]
---
## Query Elements ## 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! 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. 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 ### 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 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: `??<<List -> 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. 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. 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 ### 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 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]<<List::QueryValue>>`. 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?)<<List::QueryValue>>`. 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 ### 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. 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 ### 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 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

View File

@ -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" } },
}),
});