Compare commits

...

10 Commits

Author SHA1 Message Date
df20a47253 toolbox: adds devtoolbox to easily manage debug components 2024-03-16 10:15:03 -06:00
9cbd0a62ca ttcMD: Changes link decoration to ~~ instead of ``` 2024-03-16 06:53:48 -06:00
5e038ff5cf help article: includes tables and updated card/block logic 2024-03-16 04:08:06 -06:00
f6474f934c ttcMD: add col-span logic to blocks and cards 2024-03-16 04:07:26 -06:00
a72741e4d1 ttcMD: improves table with full width 2024-03-16 03:19:18 -06:00
2e7eaccde8 lint: updates lint rules for no-unused-vars 2024-03-16 02:12:20 -06:00
16497edd46 ttcMD: added a cleanup step to make sure that line endings are as we expect,
fixes p blocks not replacing line breaks with spaces leading to unintentional linguistic collisions
2024-03-16 00:06:22 -06:00
30a1c74711 help article: added section about query elements, added frontmatter on how to article,
added explanation of block elements
2024-03-15 12:22:07 -06:00
b2c7a35e8b ttcMD: initial table renderer 2024-03-15 12:20:42 -06:00
3c7ef5a185 ttcMD: allow for typing metadata 2024-03-15 10:02:46 -06:00
23 changed files with 952 additions and 444 deletions

View File

@ -1,3 +1,23 @@
{
"extends": "next/core-web-vitals"
"parser": "@typescript-eslint/parser",
"plugins": [
"@typescript-eslint"
],
"extends": [
"next/core-web-vitals"
],
"rules": {
"@typescript-eslint/no-unused-vars": [
"error",
{
"args": "all",
"argsIgnorePattern": "^_",
"caughtErrors": "all",
"caughtErrorsIgnorePattern": "^_",
"destructuredArrayIgnorePattern": "^_",
"varsIgnorePattern": "^_",
"ignoreRestSiblings": true
}
]
}
}

View File

@ -9,6 +9,8 @@ export const HomeClient: FC<{ body: Promise<string> }> = ({ body }) => {
return (
<TTCMD
body={text}
parserId="home"
title="home"
/>
);
};

View File

@ -31,7 +31,7 @@
}
.heading {
@apply pb-6 border-b border-b-primary-500 dark:border-b-dark-500 min-w-full;
@apply pb-6 mb-6 border-b border-b-primary-500 dark:border-b-dark-500 min-w-full;
}
.heading h1 {
@apply text-5xl font-bold;
@ -50,7 +50,7 @@
@apply dark:text-primary-500 text-primary-100 py-4 px-6 font-bold text-lg;
}
.p {
@apply py-1;
@apply pb-1;
}
.poppable {
@ -73,6 +73,14 @@
.accordion:not(:has(+ .accordion)) {
@apply rounded-b-md;
}
.md-table {
@apply bg-black/20 rounded-md overflow-clip;
}
.md-table td,
.md-table th {
@apply px-4 border border-black/20 dark:border-white/20;
}
}
@keyframes identifier {

View File

@ -52,6 +52,8 @@ export const HelpClient: FC<{ body: Promise<string>; title: string }> = ({
<TTCMD
body={cleanBody}
escapeTOC={escapeTOC}
parserId={title}
title={title}
/>
</div>
{toc && (

View File

@ -2,7 +2,6 @@
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;

View File

@ -9,6 +9,7 @@ import {
QuestionMarkCircleIcon,
} from "@heroicons/react/24/solid";
import Link from "next/link";
import { DevToolboxContextProvider } from "@/components/devtools/context";
const inter = Inter({ subsets: ["latin"] });
@ -50,6 +51,8 @@ export default function RootLayout({
},
];
console.log(process.env.NODE_ENV);
return (
<html lang="en">
<body className={inter.className + " flex min-h-[100vh]"}>
@ -71,9 +74,13 @@ export default function RootLayout({
))}
</ul>
</nav>
<main className="p-8 w-full overflow-visible">
{children}
</main>
<DevToolboxContextProvider
isDev={process.env.NODE_ENV !== "production"}
>
<main className="p-8 w-full overflow-visible">
{children}
</main>
</DevToolboxContextProvider>
<div id="root-portal"></div>
</body>
</html>

View File

@ -1,6 +1,4 @@
import { readMD } from "@/actions/readMD";
import { TTCMD } from "@/components/ttcmd";
import { readFile } from "fs/promises";
import { Suspense } from "react";
import { HomeClient } from "./client";
import { MDSkeletonLoader } from "@/components/loader";

BIN
bun.lockb

Binary file not shown.

View File

@ -0,0 +1,52 @@
import { Portal } from "@/lib/portal/components";
import { FC, PropsWithChildren, use, useEffect, useState } from "react";
import { DevToolboxContext } from "./context";
import { WrenchScrewdriverIcon } from "@heroicons/react/24/solid";
import { XMarkIcon } from "@heroicons/react/16/solid";
export const DevToolbox: FC = () => {
const { tools, shouldShowDevTools } = use(DevToolboxContext);
const [open, setOpen] = useState(false);
return shouldShowDevTools
? (
<Portal>
<div className="dev-portal flex flex-col gap-2 fixed bottom-2 right-2 border rounded-lg bg-black/50">
{open
? (
<div className="relative p-2">
<button
className="p-1 absolute top-2 right-2"
onClick={() => setOpen(!open)}
>
<XMarkIcon className="w-3 h-3">
</XMarkIcon>
</button>
<p className="mb-4 mr-8">Dev Toolbox</p>
{Object.values(tools)}
</div>
)
: (
<div>
<button className="p-4" onClick={() => setOpen(!open)}>
<WrenchScrewdriverIcon className="w-4 h-4">
</WrenchScrewdriverIcon>
</button>
</div>
)}
</div>
</Portal>
)
: <></>;
};
export const DevTool: FC<PropsWithChildren<{ id: string }>> = (
{ children, id },
) => {
const { addTool, removeTool } = use(DevToolboxContext);
useEffect(() => {
addTool(id, children);
(() => removeTool(id));
}, [addTool, children, id, removeTool]);
return <></>;
};

View File

@ -0,0 +1,66 @@
"use client";
import {
createContext,
Dispatch,
FC,
PropsWithChildren,
ReactNode,
SetStateAction,
useCallback,
useEffect,
useState,
} from "react";
import { DevToolbox } from "./Toolbox";
interface ContextProps {
tools: Record<string, ReactNode>;
addTool: (key: string, r: ReactNode) => void;
removeTool: (key: string) => void;
shouldShowDevTools: boolean;
setShouldShowDevTools: Dispatch<SetStateAction<boolean>>;
}
export const DevToolboxContext = createContext<ContextProps>({
tools: {},
addTool: () => {},
removeTool: () => {},
shouldShowDevTools: false,
setShouldShowDevTools: () => {},
});
export const DevToolboxContextProvider: FC<
PropsWithChildren<{ isDev: boolean }>
> = (
{ children, isDev },
) => {
console.log(isDev);
const [tools, setTools] = useState<Record<string, ReactNode>>({});
const [shouldShowDevTools, setShouldShowDevTools] = useState(isDev);
const addTool = useCallback((key: string, r: ReactNode) => {
setTools((t) => ({ ...t, [key]: r }));
}, []);
const removeTool = useCallback((key: string) => {
setTools((t) => ({ ...t, [key]: undefined }));
}, []);
useEffect(() => {
if (localStorage.getItem("dev")) setShouldShowDevTools(true);
}, []);
return (
<DevToolboxContext.Provider
value={{
tools,
addTool,
removeTool,
shouldShowDevTools,
setShouldShowDevTools,
}}
>
{children}
<DevToolbox></DevToolbox>
</DevToolboxContext.Provider>
);
};

View File

@ -72,7 +72,7 @@ export const MDSkeletonLoader: FC = () => {
i,
) => (
<li
key={t}
key={t + i}
className={"my-2 leading-8 text-black/20 " + indentation[
i === 0 ? 0 : Math.floor(Math.random() * indentation.length)
]}

View File

@ -1,13 +1,19 @@
"use client";
import { createElements } from "@/lib/tcmd";
import React, { FC, Suspense, useEffect, useMemo, useState } from "react";
import { MDSkeletonLoader } from "../loader";
import { DevTool } from "../devtools/Toolbox";
export const TTCMD: FC<
{ body: string; escapeTOC?: (tokens: Token[]) => boolean }
> = ({ body, escapeTOC = () => false }) => {
interface Props {
body: string;
escapeTOC?: (tokens: Token[]) => boolean;
parserId: string;
title: string;
}
export const TTCMD: FC<Props> = (
{ body, parserId, escapeTOC = () => false, title },
) => {
const elements = useMemo(() => createElements(body), [body]);
const [toc, start, end] = useMemo(() => {
@ -31,15 +37,19 @@ export const TTCMD: FC<
setHasEscapedTOC(escapeTOC(toc));
}, [escapeTOC, toc]);
console.log("mdId", parserId);
return (
<Suspense fallback={<MDSkeletonLoader />}>
<button
className="btn-primary"
onClick={() =>
navigator.clipboard.writeText(JSON.stringify(elements, null, 2))}
>
copy ast
</button>
<DevTool id={parserId}>
<button
className="btn-primary"
onClick={() =>
navigator.clipboard.writeText(JSON.stringify(elements, null, 2))}
>
Copy AST for {title}
</button>
</DevTool>
{hasEscapedTOC !== undefined &&
(
<TTCMDRenderer

View File

@ -1,9 +1,11 @@
import { ReactNode, useCallback, useRef } from 'react'
import { ReactNode, useCallback, useRef } from "react";
export const useRefCallback = <T = ReactNode>() : [T | null, (arg: T) => void] => {
export const useRefCallback = <T = ReactNode>(): [
T | null,
(arg: T) => void,
] => {
const ref = useRef<T | null>(null);
const setRef = useCallback((val: T) => {
console.log(val);
if (ref.current) {
// does something?
}
@ -12,8 +14,8 @@ export const useRefCallback = <T = ReactNode>() : [T | null, (arg: T) => void] =
// also does something?
}
ref.current = val
}, [])
ref.current = val;
}, []);
return [ref.current, setRef]
}
return [ref.current, setRef];
};

View File

@ -4,7 +4,6 @@ import {
PropsWithChildren,
SetStateAction,
useCallback,
useEffect,
useState,
} from "react";
import { bulkRound } from "../../utils/bulkRound";
@ -24,7 +23,7 @@ interface IProps {
setHover: Dispatch<SetStateAction<boolean>>;
}
type position = { top: number; left: number; width?: number };
type position = { top: number; left: number; width?: number; };
export const PoppableContent: FC<PropsWithChildren<IProps>> = (
{
@ -35,7 +34,6 @@ export const PoppableContent: FC<PropsWithChildren<IProps>> = (
spacing = 10,
setHover,
isClosing,
isClosed,
},
) => {
const [popRef, setPopRef] = useState<HTMLDivElement>();
@ -52,7 +50,7 @@ export const PoppableContent: FC<PropsWithChildren<IProps>> = (
relHeight: number,
popWidth: number,
popHeight: number,
edge: edge,
_edge: edge,
align: alignment,
): position => {
const pos = {

View File

@ -1,13 +1,6 @@
"use client";
import {
FC,
PropsWithChildren,
ReactNode,
useCallback,
useEffect,
useState,
} from "react";
import { FC, PropsWithChildren, ReactNode, useCallback, useState } from "react";
import { PoppableContent } from "./poppable-content";
import { useDebounce } from "../../../hooks/useDebounce";

File diff suppressed because it is too large Load Diff

View File

@ -11,6 +11,8 @@ export const createElements = (body: string): Token[] => {
const tokenize = (body: string) => {
const tokenizedBody: TokenMarker[] = [];
body = body.replaceAll(/[ \t]+\n/g, "\n").replaceAll(/\n{3,}/g, "\n\n");
const addToken = (thing: TokenMarker) => {
tokenizedBody.push(thing);
};
@ -116,7 +118,7 @@ function isAcceptableChild(parentType: string, childType: string): boolean {
return acceptableChildren ? acceptableChildren.includes(childType) : true;
}
// Occasionally, some P blocks start exactly at the same point as another block (a side effect of needing to exclude preceding linebreaks from the regex while also having the only clear delineation being those linebreaks) so we just remove those P blocks so that when searching for a parent, it doesn't need to figure out if the P block is valid or not. This doesn't cause issues during rendering since each block handles its own container element
// Occasionally, some P blocks start exactly at the same point as another block (a side effect of needing to exclude preceding line-breaks from the regex while also having the only clear delineation being those line-breaks) so we just remove those P blocks so that when searching for a parent, it doesn't need to figure out if the P block is valid or not. This doesn't cause issues during rendering since each block handles its own container element
function filterOverlappingPBlocks(blocks: TokenMarker[]): TokenMarker[] {
return blocks.filter((block) => {
if (block.type !== "p") {
@ -154,7 +156,7 @@ const contentToChildren = (token: Token) => {
token.children = zipArrays(
content.split(splitMarker).map((c): Token => ({
content: c.replaceAll("\n", ""),
content: c.replaceAll("\n", " "),
metadata: {},
raw: c,
type: token.rendersChildrenOnly ? "p" : "text",
@ -164,7 +166,7 @@ const contentToChildren = (token: Token) => {
children: token.rendersChildrenOnly && c.replaceAll("\n", "")
? [
{
content: c.replaceAll("\n", ""),
content: c.replaceAll("\n", " "),
metadata: {},
raw: c,
type: "text",

View File

@ -1,14 +1,28 @@
---
title: How to use ttcMD
author: Emmaline Autumn
date: March 14th, 2024
updated: March 14th, 2024
---
# Table of Contents
- [Table of Contents](#table-of-contents)
- [What even is ttcMD?](#what-even-is-ttcmd)
- [Enhanced Standard Elements](#enhanced-standard-elements)
- [Links](#links)
- [Tables](#tables)
- [Custom Elements](#custom-elements)
- [Pop-outs](#pop-outs)
- [Block-level Elements](#block-level-elements)
- [Accordions](#accordions)
- [Card](#card)
- [Block](#block)
- [Grid](#grid)
- [Query Elements](#query-elements)
- [Resolver](#resolver)
- [On-demand Resolver](#on-demand-resolver)
- [Query Block Template](#query-block-template)
- [Query Block](#query-block)
---
@ -16,27 +30,91 @@
ttcMD is a flavor of markdown that has been specifically designed to use with [ttcQuery](/help/ttcQuery.md). It has all of the basic syntax of [markdown](https://www.markdownguide.org/cheat-sheet/), but also includes Tables, basic Fenced Code Blocks and a slew of custom elements and styling annotations.
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:**
**Primary Button:**
Prefix the link name with ````button` to create a button.
`[```button link name](#links)` produces:
Prefix the link name with `~~button` to create a button.
`[~~button link name](#links)` produces:
[```button link name](#links)
[~~button link name](#links)
**Call to Action:**
**Call to Action:**
Prefix the link name with ````cta` to create a modestly styled button/call to action.
`[```cta link name](#links)` produces:
Prefix the link name with `~~cta` to create a modestly styled button/call to action.
`[~~cta link name](#links)` produces:
[```cta link name](#links)
[~~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:
[][][]
With a header:
```
| Table | Header | Row |
| <---- | ------ | --> |
| Table | Body | Row 1 |
| Table | Body | Row 2 |
| Table | Body | Row 3 |
```
Without a header:
```
| <---- | ------ | --> |
| Table | Body | Row 1 |
| Table | Body | Row 2 |
| Table | Body | Row 3 |
```
As you can see, it makes use of the default separator line and uses `<` and `>` to denote that it should be full width. Note that the length of the dashes does not matter, all that matters is that the separator line starts with `| <-` and ends with `-> |`.
[[!2
Additionally, you can specify if a column is meant to be centered by using `^` inside the separator. For example, `| <--- | -^- | ---> |` will make the second column of all table rows centered. Its positioning within the column doesn't matter.
]]
/[]
There is also an additional feature of adding a table footer by using a second separator. This second separator does not have any affect on the width or centering of the footer columns.
**Examples:**
[][][]
Normal table:
| Table | Header | Row |
| ---- | ------ | -- |
| Table | Body | Row 1 |
| Table | Body | Row 2 |
| Table | Body | Row 3 |
Full width:
| <---- | ----- | --> |
| Table | Body | Row 1 |
| Table | Body | Row 2 |
| Table | Body | Row 3 |
Full width with a centered body column
| Table | Header | Row |
| <---- | ---^-- | --> |
| Table | Body | Row 1 |
| Table | Body | Row 2 |
| Table | Body | Row 3 |
/[]
## Custom Elements
@ -50,7 +128,7 @@ The syntax is thus: `^[pop-out title]<<pop-out content>>`. The pop-out title wil
Example:
This syntax `^[goofy!]<<This is my *favorite* picture: ![goofy](https://yt3.ggpht.com/a/AATXAJwbIW0TwEhqdT2ZPeSB1AtdtWD2ZXam80oijg=s900-c-k-c0xffffffff-no-rj-mo)>>` will produce this element: ^[goofy!]<<This is my *favorite* picture: ![goofy](https://yt3.ggpht.com/a/AATXAJwbIW0TwEhqdT2ZPeSB1AtdtWD2ZXam80oijg=s900-c-k-c0xffffffff-no-rj-mo)>>
This syntax `^[This is my favorite image]<<*Goofy!* ![goofy](https:///example.com/image.png)>>` will produce this element: ^[This is my favorite image]<<*Goofy!* ![goofy](https://yt3.ggpht.com/a/AATXAJwbIW0TwEhqdT2ZPeSB1AtdtWD2ZXam80oijg=s900-c-k-c0xffffffff-no-rj-mo)>>
Note: currently, only inline elements are available, so formatting is limited
@ -85,6 +163,43 @@ I can include a [link](#accordions), or *italic* and **bold** text.
I can even include a card, like this one
]]
[/accordion]
[[!
[accordion Accordions look great stacked!]
Conveniently group data together in stacked accordions
[/accordion]
[accordion It's great for saving a lot of space]
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Deserunt, nesciunt repellat ipsa unde, reiciendis nostrum molestiae necessitatibus tempore ea neque quae nihil veritatis autem recusandae nam eaque facilis rerum quos nisi quam temporibus! Quasi nostrum iste praesentium consequatur a itaque. Eaque temporibus aliquam ex tempore rem iste at in itaque?
Sint iure nam maiores dolorem dolor officia omnis eos. Odio, corporis sunt totam accusamus, ut necessitatibus molestias molestiae error numquam suscipit quas obcaecati aspernatur qui atque optio unde minima hic autem saepe, nihil debitis cumque vel. Placeat magnam nesciunt sequi suscipit provident dolore commodi corrupti, porro facere assumenda, distinctio magni?
Suscipit illum temporibus sequi! Nulla excepturi hic tempore nihil deleniti. Hic tempora debitis iure inventore, dolores facilis, sequi dolorem veritatis corrupti dignissimos voluptate consequuntur quaerat quidem totam, odio odit quisquam harum? Consequuntur, doloribus. Necessitatibus rem quidem, saepe eum nobis, tenetur nihil iusto debitis incidunt repudiandae molestiae, corrupti rerum deleniti. Numquam.
Recusandae iusto distinctio cupiditate quam in aliquid cum ipsa et cumque adipisci voluptatum fuga expedita voluptate assumenda error hic, doloribus beatae libero animi ratione aut modi? Ex reprehenderit facere explicabo quo iusto ad minima nihil. Minus magni quidem architecto at fugiat quo, ab ipsa tenetur facilis, optio iusto deleniti illo.
Eaque dicta iusto reiciendis natus qui excepturi nesciunt asperiores totam nam, reprehenderit perspiciatis amet beatae libero. Laudantium labore aliquam eveniet ab minima ut sunt laboriosam consequuntur alias impedit magnam voluptatem ea at, soluta nisi tenetur rem officia rerum facilis perferendis voluptas nulla incidunt. Sequi, facere vero doloremque saepe magni consequatur!
Veritatis quod eveniet expedita quae explicabo, nobis enim reiciendis repellat maiores accusantium id doloribus magni dolorem quis qui illum perferendis repudiandae nisi aut maxime doloremque nulla quam fuga aspernatur? Unde sapiente nisi hic, voluptatibus facilis rem omnis saepe asperiores numquam vitae perferendis eligendi nihil temporibus! Commodi voluptatibus eos perspiciatis quaerat?
Natus impedit quasi voluptates libero vel dicta totam vitae expedita nulla, saepe doloremque veniam, beatae molestiae quod! Dolores, doloribus est! Iste asperiores fuga, ab esse, aliquid enim laborum delectus rem doloribus earum voluptates? Quibusdam dolorum esse voluptatem quas nisi aspernatur? Accusamus est totam recusandae rerum nisi accusantium dignissimos reprehenderit sapiente!
Debitis ratione molestiae veniam iure quibusdam quae, eos voluptate adipisci! Tempore blanditiis illum optio ea debitis praesentium, iure pariatur neque nisi facere, unde velit eos expedita dolores fugiat! Accusamus iusto amet perferendis adipisci natus iure sunt assumenda aut, esse tempore provident, repudiandae officiis dolor deserunt cumque aspernatur unde voluptatum suscipit!
Asperiores maiores expedita qui officia laudantium eos molestiae iusto obcaecati, odio numquam cumque dolores laborum consequatur voluptatem dolore voluptatum corrupti repudiandae doloribus quisquam? Ratione maxime aut molestiae rem suscipit, ab animi corrupti ullam deleniti aliquam, doloremque nemo ducimus quisquam laboriosam nobis debitis! Corporis maxime atque cum nulla consequatur asperiores facere!
Veniam necessitatibus molestiae explicabo nam vel ipsam non sapiente tempora quo nesciunt ullam, accusamus illo! Voluptatem tempore, quos magnam vitae similique deleniti mollitia. Totam, eligendi corrupti. Commodi ullam tenetur, quo ipsum, quam reiciendis eaque assumenda voluptates consequatur, aliquam perferendis praesentium beatae esse dignissimos iste quidem voluptatibus molestiae qui culpa exercitationem?
[/accordion]
[accordion And can be very useful for secret items in games]
Let's just pretend that this accordion holds a poker hand instead of a random picture.
You'll never believe me when I tell you that this legitimately is one of my favorite images of all time.
![goofy](https://yt3.ggpht.com/a/AATXAJwbIW0TwEhqdT2ZPeSB1AtdtWD2ZXam80oijg=s900-c-k-c0xffffffff-no-rj-mo)
[/accordion]
]]
/[]
### Card
@ -103,8 +218,8 @@ super secret! I'll never tell!
[/accordion]
]]
```
[[
[[
Card text!
This is a real wild thing! Look, an accordion!
@ -123,11 +238,43 @@ And hurt you.
[/accordion]
]]
[[2
Additionally, you can specify a number after the opening brackets (`[[2 ... ]]`) to specify how many grid columns the card should cover, just like this card!
]]
/[]
### Block
[][][]
An unstyled version of the card is the Block block. This is for grouping items in a grid that should be in the same cell. Same syntax, but you simply add an exclamation point after the opening brackets
```
[[!
Any markup :D
]]
```
[[!
Normally all of these paragraphs would end up in their own cell. However, since I put them in a block, they will be kept together.
And it's not just paragraphs that you can group together, you can use any other ttcMD element, like this picture!
![goofy](https://yt3.ggpht.com/a/AATXAJwbIW0TwEhqdT2ZPeSB1AtdtWD2ZXam80oijg=s900-c-k-c0xffffffff-no-rj-mo)
]]
[[!2
Additionally, you can specify a number after the opening brackets (`[[!2 ... ]]`) to specify how many grid columns the block should cover, just like this block!
]]
/[]
### Grid
Grid blocks give you access to basic grid layouts. You define the number of columns in the grid by using a number of matching brackets.
Grid blocks give you access to basic grid layouts. You define the number of columns in the grid by using a number of matching brackets. On smaller screens or containers, grids are ignored, turned into a single column.
[][][]
[[
@ -169,3 +316,61 @@ 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
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.
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.
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.
### 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]<<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.
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>>`
For drawing a card and having it show the card's display: `??[]<<_path.to.deck,$0.draw,$1.display>>`
### Query Block Template
The Query Block Template is the keystone of the query elements. It allows you to query for an object and use that object as the root for rendering a block of markdown. It has not been fully implemented yet.
[][][]
Syntax:
```
??<<QueryValue>>[[
Any 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
Similar to the Query Block Template, the query block passes 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
Unlike the Query Block Template, it does not change the `_` variable to the queried value. For resolvers to work with the queried data, you need to access it with the `$$` variable. `$$` will be iterated over and will act similar to the Query Block Template, but on a per-line basis, meaning that if a line contains the `$$` variable, it will render that line of markdown for each result of the query. This is primarily useful for something like a List or a Table.
Syntax:
```
Still working on the syntax, it's difficult coming up with all of this on the fly when it needs to be unique and easily parsed
```

View File

@ -1,4 +1,4 @@
| test | Table | header |
-------------------------
| ---- | ----- | ------ |
| test | table | row |
| look | another |
| shorter | *row* |

View File

@ -1,10 +1,13 @@
[[
Tabletop Commander (TC) is a rules-and-tools app for tabletop games - board, card, war, role-playing, you name it! It is the spiritual successor of Chapter Master by Emmaline Autumn, a Warhammer 40,000 9th Edition rules reference and game helper.
Tabletop Commander (TTC) is a rules-and-tools app for tabletop games - board, card, war, role-playing, you name it! It is the spiritual successor of Chapter Master by Emmaline Autumn, a Warhammer 40,000 9th Edition rules reference and game helper.
Emma decided to move on from Chapter Master as her interest in 40k was supplanted by the greater wargaming hobby after the release of 10th edition made clear that Chapter Master was too inflexible and tedious to work on.
Emma decided to move on from Chapter Master as her interest in Warhammer 40k waned, replaced by a broader enthusiasm for the wargaming hobby. The release of the 10th edition highlighted Chapter Master's inflexibility and tediousness, prompting her to seek new avenues.
See, Emma had a vision that anyone could contribute to making rules corrections so that anyone could have all of the rules as they currently exist. This ballooned into the idea that you could have all the rules as they existed at *any point in time.* As soon as she realized that every code change either needed to keep that backward compatibility in mind or would cause the data to no longer be usable, she decided to drop Chapter Master entirely.
It didn't sit right with her. A big project no longer being worked on and a dead dream. Enter Tabletop Commander. Inspired by the flexibility of Battlescribe and disappointed in its features and lack of updates, Emma started designing a new system, from the ground up, that can be used to build almost anything.
See, Emma had a vision that anyone could contribute to making rules corrections so that anyone could have all of the rules as they currently exist. This ballooned into the idea that you could have all the rules as they existed at *any time*
]]
[][][]
@ -13,7 +16,7 @@ See, Emma had a vision that anyone could contribute to making rules corrections
### Game Systems
The basis of TC is called a Game System Package. This package
The basis of TTC is called a Game System. This package
includes everything needed for a game system, including schemas,
publications, and tools. Players can follow a Game System to get
consistently updated content publications, or fork it to
@ -32,7 +35,7 @@ your approval and contributions carry.
If your score is high enough, and a contribution request has
enough approvals, you can even be the one to merge it in!
[```cta Learn More](/help/Game%20Systems.md)
[~~cta Learn More](/help/Game%20Systems.md)
]]
@ -41,22 +44,22 @@ enough approvals, you can even be the one to merge it in!
### Schemas
Those who have studied English or databases, you would know that
a schema is a structural pattern. TC aims to provide a simple,
a schema is a structural pattern. TTC aims to provide a simple,
user-edited and maintained schema system for *any* game.
If that flew over your head, don&apos;t worry. Others can share
the schemas they&apos;ve made with everyone, which come as part
If that flew over your head, don't worry. Others can share
the schemas they've made with everyone, which come as part
of a Game System package that you can fork or follow to get both
content and schemas ready to use.
**For the techies:**
The schema system makes use of a powerful custom query language
(tcQuery) I designed. By writing queries directly into the
(ttcQuery) I designed. By writing queries directly into the
schema, we can reduce the amount of re-written content, while
maintaining the presence of data anywhere we need it.
[```cta Learn More](/help/Schemas.md)
[~~cta Learn More](/help/Schemas.md)
]]
@ -65,7 +68,7 @@ maintaining the presence of data anywhere we need it.
### Publications
Publications are the actual content of the rules. They
don&apos;t just contain the content, but also the style in which
don't just contain the content, but also the style in which
the content is shown.
Content can include text, images, and even video (through
@ -75,15 +78,23 @@ parts of the publication through context based pop-overs.
**For the techies (again):**
Publications use an enhanced markdown syntax (ttcMD) that
implements tcQuery, and adds a bit of custom syntax for things
implements ttcQuery, and adds a bit of custom syntax for things
like pop-overs and styling hints for rendering.
The styling aspect is similar to a very trimmed down CSS, but
can accomplish quite a lot. For example, this page is actually
built using ttcMD!
Though it uses markdown as its base syntax, ttcMD could more accurately be called a templating language as it contains a lot of custom elements to handle different layouts
[```cta Learn More](/help/Publications.md)
[~~cta Learn More](/help/Publications.md)
]]
[[2
Want to keep up with TTC? Join us over at the CyborgGrizzly Games Discord server! Come discuss tabletop gaming, stay updated on the latest developments with Tabletop Commander, and collaborate with fellow gamers to create and update game systems. It's the perfect spot to connect with like-minded enthusiasts and be part of shaping the future of tabletop gaming. Don't miss out hop on the Discord server today!
Disclaimer: *I'm so sorry, I had ChatGPT write that last paragraph. I tried to save it, but it's just so... corporate*
[~~cta Join the Discord](https://discord.gg/bePt7MQHQA)
]]
/[]

View File

@ -16,14 +16,15 @@
"react-dom": "^18"
},
"devDependencies": {
"typescript": "^5",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"autoprefixer": "^10.0.1",
"eslint": "^8",
"eslint-config-next": "14.1.0",
"postcss": "^8",
"tailwindcss": "^3.3.0",
"eslint": "^8",
"eslint-config-next": "14.1.0"
"typescript": "^5",
"typescript-eslint": "^7.2.0"
}
}

View File

@ -46,6 +46,9 @@ const config: Config = {
height: {
variable: "var(--v-height)",
},
gridColumn: {
variable: "span var(--v-span) / span var(--v-span)",
},
},
},
plugins: [],

40
types.d.ts vendored
View File

@ -1,5 +1,5 @@
type IdentifiedToken = {
metadata: Record<string, string>;
type IdentifiedToken<M> = {
metadata: M;
children?: Token[];
uuid: string;
raw: string;
@ -8,21 +8,49 @@ type IdentifiedToken = {
rendersContentOnly?: boolean;
};
type TokenRenderer = (t: Token) => ReactNode;
type TokenRenderer<M> = (t: Token<M>) => ReactNode;
type TokenAttributes = {
type: string;
render: TokenRenderer;
};
type Token = IdentifiedToken & TokenAttributes;
type Token<M = Record<string, string>> = IdentifiedToken<M> & TokenAttributes;
type TokenMarker = {
type TokenMarker<M = Record<string, string>> = {
start: number;
end: number;
type: string;
parent?: TokenMarker;
token: Token;
token: Token<M>;
};
type FrontMatter = Record<string, string>;
type SearchFunction = (
s: string,
start: number,
end: number,
) => {
start: number;
end: number;
text: string;
lastIndex: number;
};
type TokenIdentifier<M> = {
rx: RegExp;
parse: (s: string) => Token<M>;
search?: SearchFunction;
};
type TokenIdentifierMap = Map<string, TokenIdentifier<any>>;
type IdentifierRegistration = <N = Record<string, string>>(
type: string,
match: RegExp,
parseFunction: (s: string, rx: RegExp) => IdentifiedToken<N>,
renderFunction: TokenRenderer<N>,
openTagRx?: RegExp,
closeTagRx?: RegExp,
) => void;