initial public commit
This commit is contained in:
6
dist-demo/demo/add-default-route.js
Normal file
6
dist-demo/demo/add-default-route.js
Normal file
@@ -0,0 +1,6 @@
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
// add default #overview route
|
||||
if (!window.location.hash) {
|
||||
window.location.hash = "overview";
|
||||
}
|
||||
});
|
||||
12
dist-demo/demo/external-links.js
Normal file
12
dist-demo/demo/external-links.js
Normal file
@@ -0,0 +1,12 @@
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const currentHost = window.location.hostname;
|
||||
|
||||
const links = document.querySelectorAll("a");
|
||||
|
||||
links.forEach((link) => {
|
||||
if (link.hostname && link.hostname !== currentHost) {
|
||||
link.setAttribute("target", "_blank");
|
||||
link.setAttribute("rel", "noopener noreferrer");
|
||||
}
|
||||
});
|
||||
});
|
||||
44
dist-demo/demo/format-source.js
Normal file
44
dist-demo/demo/format-source.js
Normal file
@@ -0,0 +1,44 @@
|
||||
export default function formatSource(source, panelId) {
|
||||
source = (source ?? "").trim();
|
||||
|
||||
if (!source || !panelId) return;
|
||||
|
||||
const sourceSplit = source.split(/\r?\n/);
|
||||
const spaceCount = sourceSplit[sourceSplit.length - 1].search(/\S/);
|
||||
if (spaceCount <= 0) return; // empty string
|
||||
|
||||
const trimRE = new RegExp(`^\\s{${spaceCount}}`);
|
||||
|
||||
const preDom = document.createElement("pre");
|
||||
preDom.append(sourceSplit.map((val) => val.replace(trimRE, "")).join("\n"));
|
||||
|
||||
preDom.innerHTML = preDom.innerHTML
|
||||
// strRegEx must be applied first to prevent false positives
|
||||
.replace(/\"[^"]+\"/g, (val) =>
|
||||
val !== '"module"' ? `<span class="code-str">${val}</span>` : val,
|
||||
)
|
||||
.replace(/\/\/.*/g, (val) => `<span class="code-cmnt">${val}</span>`)
|
||||
.replace(/\`[^`]+\`/g, (val) => `<span class="code-str">${val}</span>`)
|
||||
.replace(/\.\w+/g, (val) =>
|
||||
val !== ".js" ? `<span class="code-func">${val}</span>` : val,
|
||||
)
|
||||
.replace(
|
||||
/<\/?script[^&]*>/g,
|
||||
(val) => `<span class="code-cmd">${val}</span>`,
|
||||
);
|
||||
|
||||
const sourceHTML = `
|
||||
<details class='js-sourceDetails' open="true">
|
||||
<summary>Source</summary>
|
||||
<div>
|
||||
<figure>
|
||||
${preDom.outerHTML}
|
||||
</figure>
|
||||
</div>
|
||||
</details>
|
||||
`.trim();
|
||||
|
||||
document
|
||||
.getElementById(panelId)
|
||||
.insertAdjacentHTML("beforeend", sourceHTML);
|
||||
}
|
||||
63
dist-demo/demo/hashTwt-example-result.js
Normal file
63
dist-demo/demo/hashTwt-example-result.js
Normal file
@@ -0,0 +1,63 @@
|
||||
import { hashTwt } from "/dist-browser/twtxt-lib.js";
|
||||
|
||||
let wasHashTwtResultAppended = false;
|
||||
|
||||
const formHash = document.forms["formHash"];
|
||||
formHash.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
const content = formHash.elements["content"].value;
|
||||
const created = formHash.elements["created"].value;
|
||||
const url = formHash.elements["url"].value;
|
||||
const hash = hashTwt({
|
||||
content,
|
||||
created,
|
||||
url,
|
||||
});
|
||||
|
||||
const result = [
|
||||
`content: ${content}`,
|
||||
`created: ${created}`,
|
||||
`url: ${url}`,
|
||||
`hash: ${hash}`,
|
||||
].join("\n");
|
||||
|
||||
console.log((wasHashTwtResultAppended ? "\n" : "") + result);
|
||||
|
||||
const resultHTML = result
|
||||
.split("\n")
|
||||
.map((line) =>
|
||||
line.replace(
|
||||
// to color properties
|
||||
/^\w+:/,
|
||||
(val) => `<span class="code-str">${val}</span>`,
|
||||
),
|
||||
)
|
||||
.join("\n");
|
||||
|
||||
if (wasHashTwtResultAppended) {
|
||||
document
|
||||
.getElementById("preHashTwtResult")
|
||||
.insertAdjacentHTML("afterbegin", resultHTML + "<br />\n");
|
||||
return;
|
||||
}
|
||||
|
||||
const resultsHTML = `
|
||||
<details open="">
|
||||
<summary>Results</summary>
|
||||
<figure>
|
||||
<pre id="preHashTwtResult">${resultHTML}</pre>
|
||||
</figure>
|
||||
</details>
|
||||
`.trim();
|
||||
|
||||
document
|
||||
.getElementById("tabHashTwt-panel")
|
||||
.insertAdjacentHTML("beforeend", resultsHTML);
|
||||
|
||||
document
|
||||
.querySelector("#tabHashTwt-panel .js-sourceDetails")
|
||||
?.removeAttribute("open");
|
||||
|
||||
document.body.classList.add("isLoaded");
|
||||
wasHashTwtResultAppended = true;
|
||||
});
|
||||
40
dist-demo/demo/hashTwt-example-source.js
Normal file
40
dist-demo/demo/hashTwt-example-source.js
Normal file
@@ -0,0 +1,40 @@
|
||||
import formatSource from "./format-source.js";
|
||||
|
||||
formatSource(
|
||||
`
|
||||
\<script type="module">
|
||||
import { hashTwt } from "/web/dist/twtxt-lib.js";
|
||||
|
||||
let wasHashTwtResultAppended = false;
|
||||
|
||||
const formHash = document.forms["formHash"];
|
||||
formHash.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const content = formHash.elements["content"].value;
|
||||
const created = formHash.elements["created"].value;
|
||||
const url = formHash.elements["url"].value;
|
||||
|
||||
const hash = hashTwt({
|
||||
content,
|
||||
created,
|
||||
url,
|
||||
});
|
||||
|
||||
const result = [
|
||||
\`content: \${content}\`,
|
||||
\`created: \${created}\`,
|
||||
\`url: \${url}\`,
|
||||
\`hash: \${hash}\`,
|
||||
].join("\\n");
|
||||
|
||||
console.log(
|
||||
(wasHashTwtResultAppended ? "\\n" : "") + result,
|
||||
);
|
||||
|
||||
wasHashTwtResultAppended = true;
|
||||
});
|
||||
<\/script>
|
||||
`,
|
||||
"tabHashTwt-panel",
|
||||
);
|
||||
79
dist-demo/demo/loadAndParseTwtxtFile-example-result.js
Normal file
79
dist-demo/demo/loadAndParseTwtxtFile-example-result.js
Normal file
@@ -0,0 +1,79 @@
|
||||
import { loadAndParseTwtxtFile } from "/dist-browser/twtxt-lib.js";
|
||||
|
||||
const tabLoadAndParsePanel = document.getElementById("tabLoadAndParse-panel");
|
||||
let wasLoadAndParseResultAppended = false;
|
||||
|
||||
document
|
||||
.getElementById("formLoadAndParse")
|
||||
.addEventListener("submit", async (ev) => {
|
||||
ev?.preventDefault();
|
||||
tabLoadAndParsePanel.classList.add("isLoading");
|
||||
|
||||
const loadAndParseURL = document.getElementById("loadAndParseURL");
|
||||
|
||||
const url =
|
||||
loadAndParseURL?.value || "/twtxt-demos/demo-hipster-twtxt.txt";
|
||||
|
||||
const parsedFile = await loadAndParseTwtxtFile(url);
|
||||
|
||||
console.log(parsedFile);
|
||||
|
||||
tabLoadAndParsePanel.classList.remove("isLoading");
|
||||
|
||||
if (wasLoadAndParseResultAppended) {
|
||||
document.getElementById("preLoadAndParseResult").outerHTML = `
|
||||
<pre id="preLoadAndParseResult">${JSON.stringify(
|
||||
parsedFile,
|
||||
null,
|
||||
2,
|
||||
).replace(
|
||||
// to color properties
|
||||
/"\w+":/g,
|
||||
(val) => `<span class="code-str">${val}</span>`,
|
||||
)}</pre>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
const resultsHTML = `
|
||||
<details open="true">
|
||||
<summary>Results</summary>
|
||||
<figure>
|
||||
<pre id="preLoadAndParseResult">${JSON.stringify(
|
||||
parsedFile,
|
||||
null,
|
||||
2,
|
||||
).replace(
|
||||
// to color properties
|
||||
/"\w+":/g,
|
||||
(val) => `<span class="code-str">${val}</span>`,
|
||||
)}</pre>
|
||||
</figure>
|
||||
</details>
|
||||
`;
|
||||
|
||||
tabLoadAndParsePanel.insertAdjacentHTML("beforeend", resultsHTML);
|
||||
|
||||
document
|
||||
.querySelector("#tabLoadAndParse-panel .js-sourceDetails")
|
||||
?.removeAttribute("open");
|
||||
|
||||
document.body.classList.add("isLoaded");
|
||||
|
||||
wasLoadAndParseResultAppended = true;
|
||||
});
|
||||
|
||||
const loadAndParseClickHandler = (ev) => {
|
||||
ev?.preventDefault();
|
||||
loadAndParseURL.value = ev.target.dataset.url;
|
||||
};
|
||||
|
||||
[
|
||||
"loadAndParseHipsterButton",
|
||||
"loadAndParsePirateButton",
|
||||
"loadAndParseSaganButton",
|
||||
].forEach((curr) => {
|
||||
document
|
||||
.getElementById(curr)
|
||||
.addEventListener("click", loadAndParseClickHandler);
|
||||
});
|
||||
25
dist-demo/demo/loadAndParseTwtxtFile-example-source.js
Normal file
25
dist-demo/demo/loadAndParseTwtxtFile-example-source.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import formatSource from "./format-source.js";
|
||||
|
||||
formatSource(
|
||||
`
|
||||
\<script type="module">
|
||||
import { loadAndParseTwtxtFile } from "/web/dist/twtxt-lib.js";
|
||||
|
||||
document
|
||||
.getElementById("formLoadAndParse")
|
||||
.addEventListener("submit", async (ev) => {
|
||||
ev?.preventDefault();
|
||||
|
||||
const url =
|
||||
document.getElementById("loadAndParseURL")?.value ??
|
||||
"/twtxt-demos/demo-hipster-twtxt.txt";
|
||||
|
||||
const parsedFile = await loadAndParseTwtxtFile(url);
|
||||
|
||||
console.log(parsedFile);
|
||||
});
|
||||
|
||||
<\/script>
|
||||
`,
|
||||
"tabLoadAndParse-panel",
|
||||
);
|
||||
36
dist-demo/demo/overview-example-result.js
Normal file
36
dist-demo/demo/overview-example-result.js
Normal file
@@ -0,0 +1,36 @@
|
||||
import { loadAndParseTwtxtFile } from "/dist-browser/twtxt-lib.js";
|
||||
|
||||
// run in an IIFE (or event listener) to avoid issues with top-level await
|
||||
(async () => {
|
||||
try {
|
||||
const parsedFile = await loadAndParseTwtxtFile(
|
||||
"/twtxt-demos/demo-hipster-twtxt.txt",
|
||||
);
|
||||
|
||||
console.log(parsedFile);
|
||||
|
||||
document.getElementById("tabOverview-example")?.insertAdjacentHTML(
|
||||
"beforeend",
|
||||
`
|
||||
<details open="true">
|
||||
<summary>Result</summary>
|
||||
<figure>
|
||||
<pre id="preResult">${JSON.stringify(
|
||||
parsedFile,
|
||||
null,
|
||||
2,
|
||||
).replace(
|
||||
// to color properties
|
||||
/"\w+":/g,
|
||||
(val) => `<span class="code-str">${val}</span>`,
|
||||
)}</pre>
|
||||
</figure>
|
||||
</details>
|
||||
`,
|
||||
);
|
||||
|
||||
document.body.classList.add("isLoaded");
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
})();
|
||||
24
dist-demo/demo/overview-example-source.js
Normal file
24
dist-demo/demo/overview-example-source.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import formatSource from "./format-source.js";
|
||||
|
||||
formatSource(
|
||||
`
|
||||
\<script type="module">
|
||||
import { loadAndParseTwtxtFile } from "/web/dist/twtxt-lib.js";
|
||||
|
||||
// run in an IIFE (or event listener) to avoid issues with top-level await
|
||||
(async () => {
|
||||
try {
|
||||
const parsedFile = await loadAndParseTwtxtFile(
|
||||
"/twtxt-demos/demo-hipster-twtxt.txt",
|
||||
);
|
||||
|
||||
console.log(parsedFile);
|
||||
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
})();
|
||||
<\/script>
|
||||
`,
|
||||
"tabOverview-example",
|
||||
);
|
||||
76
dist-demo/demo/parseTwtxt-example-result.js
Normal file
76
dist-demo/demo/parseTwtxt-example-result.js
Normal file
@@ -0,0 +1,76 @@
|
||||
import { parseTwtxt } from "/dist-browser/twtxt-lib.js";
|
||||
|
||||
const tabParsePanel = document.getElementById("tabParse-panel");
|
||||
let wasParseResultAppended = false;
|
||||
|
||||
document.getElementById("formParse").addEventListener("submit", async (ev) => {
|
||||
ev?.preventDefault();
|
||||
tabParsePanel.classList.add("isLoading");
|
||||
|
||||
const parseURL = document.getElementById("parseURL");
|
||||
|
||||
const url = parseURL?.value ?? "/twtxt-demos/demo-hipster-twtxt.txt";
|
||||
|
||||
const response = await fetch(url);
|
||||
const twtxtFile = await response.text();
|
||||
const parsedFile = parseTwtxt(twtxtFile);
|
||||
|
||||
console.log(parsedFile);
|
||||
|
||||
tabParsePanel.classList.remove("isLoading");
|
||||
|
||||
if (wasParseResultAppended) {
|
||||
document.getElementById("preParseResult").outerHTML = `
|
||||
<pre id="preParseResult">${JSON.stringify(
|
||||
parsedFile,
|
||||
null,
|
||||
2,
|
||||
).replace(
|
||||
// to color properties
|
||||
/"\w+":/g,
|
||||
(val) => `<span class="code-str">${val}</span>`,
|
||||
)}</pre>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
const resultsHTML = `
|
||||
<details open="true">
|
||||
<summary>Results</summary>
|
||||
<figure>
|
||||
<pre id="preParseResult">${JSON.stringify(
|
||||
parsedFile,
|
||||
null,
|
||||
2,
|
||||
).replace(
|
||||
// to color properties
|
||||
/"\w+":/g,
|
||||
(val) => `<span class="code-str">${val}</span>`,
|
||||
)}</pre>
|
||||
</figure>
|
||||
</details>
|
||||
`;
|
||||
|
||||
tabParsePanel.insertAdjacentHTML("beforeend", resultsHTML);
|
||||
|
||||
document
|
||||
.querySelector("#tabParse-panel .js-sourceDetails")
|
||||
?.removeAttribute("open");
|
||||
|
||||
document.body.classList.add("isLoaded");
|
||||
|
||||
wasParseResultAppended = true;
|
||||
});
|
||||
|
||||
const parseClickHandler = (ev) => {
|
||||
ev?.preventDefault();
|
||||
parseURL.value = ev.target.dataset.url;
|
||||
};
|
||||
|
||||
["parseHipsterButton", "parsePirateButton", "parseSaganButton"].forEach(
|
||||
(curr) => {
|
||||
document
|
||||
.getElementById(curr)
|
||||
.addEventListener("click", parseClickHandler);
|
||||
},
|
||||
);
|
||||
27
dist-demo/demo/parseTwtxt-example-source.js
Normal file
27
dist-demo/demo/parseTwtxt-example-source.js
Normal file
@@ -0,0 +1,27 @@
|
||||
import formatSource from "./format-source.js";
|
||||
|
||||
formatSource(
|
||||
`
|
||||
\<script type="module">
|
||||
import { parseTwtxt } from "/web/dist/twtxt-lib.js";
|
||||
|
||||
document
|
||||
.getElementById("formParse")
|
||||
.addEventListener("submit", async (ev) => {
|
||||
ev?.preventDefault();
|
||||
|
||||
const url =
|
||||
document.getElementById("parseURL")?.value ??
|
||||
"/twtxt-demos/demo-hipster-twtxt.txt";
|
||||
|
||||
const response = await fetch(url);
|
||||
const twtxtFile = await response.text();
|
||||
const parsedFile = parseTwtxt(twtxtFile);
|
||||
|
||||
console.log(parsedFile);
|
||||
});
|
||||
|
||||
<\/script>
|
||||
`,
|
||||
"tabParse-panel",
|
||||
);
|
||||
471
dist-demo/demo/styles.css
Normal file
471
dist-demo/demo/styles.css
Normal file
@@ -0,0 +1,471 @@
|
||||
:root {
|
||||
--fg-main: #DBDFAC;
|
||||
--bg-main: #3B1F2B;
|
||||
|
||||
--fg-light: #edefd5;
|
||||
--bg-dark: rgba(10, 10, 10, .5);
|
||||
--bg-light: rgba(245, 245, 245, .6);
|
||||
|
||||
--main-link: #8598AD;
|
||||
--link-active: #ccc;
|
||||
--link-active: lch(from var(--main-link) calc(l + 20) c h);
|
||||
|
||||
--gray-light: #5F758E;
|
||||
}
|
||||
|
||||
@keyframes riseInDetails {
|
||||
0% {opacity: 0; margin-top: 2rem}
|
||||
100% {opacity: 1; margin-top: 0rem}
|
||||
}
|
||||
|
||||
@keyframes riseInTab {
|
||||
0% {opacity: 0; margin-top: -2rem}
|
||||
100% {opacity: 1; margin-top: -4rem}
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--bg-main);
|
||||
color: var(--fg-main);
|
||||
margin: 0;
|
||||
transition:
|
||||
background-color .5s,
|
||||
border-color .5s,
|
||||
color .5s;
|
||||
}
|
||||
|
||||
a {
|
||||
border: 1px solid transparent;
|
||||
border-bottom-color: var(--main-link);
|
||||
border-radius: 0;
|
||||
color: var(--main-link);
|
||||
padding: 0 .5rem ;
|
||||
text-decoration: none;
|
||||
transition: all .5s;
|
||||
}
|
||||
|
||||
a:active {
|
||||
background-color: var(--fg-main);
|
||||
border-color: var(--bg-main);
|
||||
color: var(--link-active);
|
||||
}
|
||||
|
||||
a:hover {
|
||||
border-color: var(--link-active);
|
||||
border-radius: .5rem;
|
||||
color: var(--link-active);
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: var(--link-active);
|
||||
}
|
||||
|
||||
label {
|
||||
display: inline-block;
|
||||
border: none;
|
||||
max-width: 25rem;
|
||||
padding: 1rem;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
input {
|
||||
background-color: var(--fg-main);
|
||||
}
|
||||
|
||||
input[type="reset"], input[type="submit"] {
|
||||
background-color: var(--link-active);
|
||||
}
|
||||
|
||||
input[type="text"], input[type="url"] {
|
||||
background-color: var(--fg-light);
|
||||
font-size: 1rem;
|
||||
max-width: 25rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
pre {
|
||||
background-color: var(--bg-dark);
|
||||
border: 1px solid var(--fg-main);
|
||||
color: var(--fg-main);
|
||||
font-size: smaller;
|
||||
padding: 1rem;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
textarea {
|
||||
background-color: var(--fg-light);
|
||||
font-size: 1rem;
|
||||
max-width: 30rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
details figure {
|
||||
margin: 1rem .5rem;
|
||||
}
|
||||
|
||||
details figure pre {
|
||||
overflow: auto;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
|
||||
details[open] summary ~ * {
|
||||
animation: riseInDetails .5s ease-in-out;
|
||||
}
|
||||
|
||||
summary {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.code-cmd {
|
||||
color: var(--gray-light);
|
||||
}
|
||||
|
||||
.code-cmnt {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.code-str {
|
||||
color: var(--gray-light);
|
||||
}
|
||||
|
||||
.copyright {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.flexCol {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
max-width: 25rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.flexRow {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
padding: .5rem 1rem;
|
||||
}
|
||||
|
||||
.tab {
|
||||
border: 2px solid transparent;
|
||||
border-top: 2px solid var(--fg-main);
|
||||
border-radius: .5rem .5rem 0 0;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.tab-link {
|
||||
background-color: var(--bg-main);
|
||||
border-radius: .5rem .5rem 0 0;
|
||||
border-bottom: 0;
|
||||
color: var(--main-link);
|
||||
display: block;
|
||||
padding: 1rem 2rem;
|
||||
position: relative;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.tab-panel {
|
||||
bottom: 0;
|
||||
display: none;
|
||||
left: 0;
|
||||
overflow: auto;
|
||||
padding: 0 1rem 1rem;
|
||||
right: 0;
|
||||
top: 4.25rem;
|
||||
width: 100%;
|
||||
z-index: -2;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
border: 2px solid var(--fg-main);
|
||||
border-top-color: transparent;
|
||||
border-radius: .5rem .5rem 0 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.tab:target .tab-panel {
|
||||
display: block;
|
||||
}
|
||||
|
||||
body:not(:has(:target)) #tabOverview-panel {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.tab:target .tab-link {
|
||||
background: linear-gradient(var(--bg-dark), var(--bg-main));
|
||||
}
|
||||
|
||||
.tab:target + .tab {
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
body:not(:has(:target)) #tabOverview-link {
|
||||
background-color: var(--bg-dark);
|
||||
color: var(--fg-main);
|
||||
}
|
||||
|
||||
.themeToggle-button {
|
||||
background-color: var(--fg-main);
|
||||
border: 1px solid var(--gray-light);
|
||||
border-radius: 1rem;
|
||||
bottom: .5rem;
|
||||
color: var(--gray-light);
|
||||
display: flex;
|
||||
font-size: 1.5rem;
|
||||
padding: .5rem;
|
||||
position: fixed;
|
||||
right: .5rem;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.themeToggle-svgIndicator {
|
||||
rotate: 180deg;
|
||||
transition: rotate .5s;
|
||||
}
|
||||
|
||||
/** Loader from https://www.cssportal.com/css-loader-generator/ */
|
||||
|
||||
.dotLoader {
|
||||
animation: dotLoaderFrames 1s infinite steps(6);
|
||||
background:
|
||||
linear-gradient(var(--bg-main) 0 0) left -25% top 0 /20% 100% no-repeat var(--fg-main);
|
||||
display: block;
|
||||
height: 20px;
|
||||
margin-top: 1rem;
|
||||
mask: linear-gradient(90deg,var(--bg-main) 70%,#0000 0) left/20% 100%;
|
||||
-webkit-mask: linear-gradient(90deg,var(--bg-main) 70%,#0000 0) left/20% 100%;
|
||||
transition: opacity .5s;
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
@keyframes dotLoaderFrames {
|
||||
100% {background-position: right -25% top 0}
|
||||
}
|
||||
|
||||
/** ID Overrides */
|
||||
|
||||
#formHash .flexCol {
|
||||
min-width: 15rem;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
#formHash input[type="submit"] {
|
||||
margin-top: 1rem;
|
||||
min-width: 5rem;
|
||||
}
|
||||
|
||||
#formHash textarea {
|
||||
min-height: 6rem;
|
||||
width: 100%
|
||||
}
|
||||
|
||||
#formLoadAndParse .flexCol {
|
||||
width: 100%;
|
||||
max-width: 25rem;
|
||||
}
|
||||
|
||||
#formLoadAndParse label {
|
||||
width: 100%;
|
||||
max-width: 25rem;
|
||||
}
|
||||
|
||||
/** Media-Query Overrides */
|
||||
@media (min-width: 900px) {
|
||||
.flexRow {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.tab {
|
||||
border-color: transparent;
|
||||
display: inline-block;
|
||||
position: static;
|
||||
}
|
||||
|
||||
.tab-link {
|
||||
background-color: transparent;;
|
||||
border: 2px solid var(--fg-main);
|
||||
border-bottom-color: transparent;
|
||||
margin-top: -4.5rem;
|
||||
}
|
||||
|
||||
.tab-link:hover {
|
||||
background-color: var(--link-active);
|
||||
border-radius: .5rem .5rem 0 0;
|
||||
color: var(--bg-main);
|
||||
font-size: 1.2rem;
|
||||
margin-top: -4.65rem;
|
||||
}
|
||||
|
||||
|
||||
.tab-panel {
|
||||
animation: riseInTab .5s ease-in-out;
|
||||
margin-top: -4rem;
|
||||
position: relative;
|
||||
padding: 0 2rem;
|
||||
}
|
||||
|
||||
|
||||
.tabs {
|
||||
border-top: 2px solid var(--fg-main);
|
||||
bottom: 0;
|
||||
flex-direction: row;
|
||||
left: 0;
|
||||
margin-top: 4.5rem;
|
||||
min-height: auto;
|
||||
padding: 1rem;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.tab:target + .tab {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.tab:target .tab-link {
|
||||
border-bottom: 0;
|
||||
font-size: 1.2rem;
|
||||
margin-top: -4.55rem;
|
||||
}
|
||||
|
||||
.tab:target .tab-link:hover {
|
||||
border-radius: .5rem .5rem 0 0;
|
||||
background-color: var(--bg-dark);
|
||||
color: var(--main-link);
|
||||
font-size: 1.2rem;
|
||||
margin-top: -4.55rem;
|
||||
}
|
||||
|
||||
|
||||
.tab:target .tab-panel {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
body:not(:has(:target)) #tabOverview-panel {
|
||||
display: block;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.themeToggle-button {
|
||||
bottom: auto;
|
||||
position: absolute;
|
||||
right: 1rem;
|
||||
top: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
/** State-Based Overrides */
|
||||
|
||||
body.invertedTheme {
|
||||
background-color: var(--fg-main);
|
||||
color: var(--bg-main);
|
||||
}
|
||||
|
||||
body.invertedTheme a:hover {
|
||||
background-color: var(--main-link);
|
||||
border-color: var(--bg-dark);
|
||||
color: var(--fg-main);
|
||||
}
|
||||
|
||||
|
||||
body.invertedTheme figcaption {
|
||||
border-color: var(--bg-main);
|
||||
}
|
||||
|
||||
body.invertedTheme figure {
|
||||
background-color: var(--gray-light);
|
||||
border-color: var(--bg-main);
|
||||
}
|
||||
|
||||
body.invertedTheme input,
|
||||
body.invertedTheme textarea {
|
||||
background-color: var(--fg-light);
|
||||
}
|
||||
|
||||
body.invertedTheme pre {
|
||||
background-color: var(--bg-light);
|
||||
border-color: var(--bg-main);
|
||||
color: var(--bg-main);
|
||||
}
|
||||
|
||||
body.invertedTheme .code-cmd {
|
||||
color: var(--bg-dark);
|
||||
}
|
||||
|
||||
body.invertedTheme .code-str {
|
||||
color: var(--bg-dark);
|
||||
}
|
||||
|
||||
body.invertedTheme .tab-link {
|
||||
color: var(--bg-dark);
|
||||
background-color: var(--fg-main);
|
||||
border-color: var(--bg-main);
|
||||
}
|
||||
|
||||
body.invertedTheme .tab:target .tab-link {
|
||||
background: linear-gradient(var(--link-active), var(--fg-main));
|
||||
}
|
||||
|
||||
body.invertedTheme .tab-link:hover {
|
||||
background-color: var(--main-link);
|
||||
border-color: var(--bg-dark);
|
||||
}
|
||||
|
||||
body.invertedTheme .tab:target .tab-link {
|
||||
border-color: var(--bg-main);
|
||||
}
|
||||
|
||||
body.invertedTheme .tab-panel {
|
||||
border-color: var(--bg-main);
|
||||
}
|
||||
|
||||
body.invertedTheme .tabs {
|
||||
border: 2px solid var(--bg-main);
|
||||
}
|
||||
|
||||
body.invertedTheme .themeToggle-button {
|
||||
background-color: var(--bg-main);
|
||||
color: var(--main-link);
|
||||
}
|
||||
|
||||
body.invertedTheme .themeToggle-svgIndicator {
|
||||
rotate: 0deg;
|
||||
}
|
||||
|
||||
.isLoaded .dotLoader {
|
||||
margin: 0;
|
||||
max-height: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.isLoading .dotLoader {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
a[href^='http']::after {
|
||||
content: '\2197'; /* Code for ↗ */
|
||||
display: inline-block;
|
||||
margin-left: 5px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
29
dist-demo/demo/theme-toggle.js
Normal file
29
dist-demo/demo/theme-toggle.js
Normal file
@@ -0,0 +1,29 @@
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const toggle = document.createElement("button");
|
||||
|
||||
toggle.classList.add("themeToggle-button");
|
||||
toggle.setAttribute("id", "themeToggle-button");
|
||||
|
||||
toggle.addEventListener("click", () => {
|
||||
document.body.classList.toggle("invertedTheme");
|
||||
});
|
||||
|
||||
toggle.innerHTML = `
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden="true"
|
||||
width="1em"
|
||||
height="1em"
|
||||
class="themeToggle-svgIndicator"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 32 32"
|
||||
>
|
||||
<path
|
||||
d="M16 .5C7.4.5.5 7.4.5 16S7.4 31.5 16 31.5 31.5 24.6 31.5 16 24.6.5
|
||||
16 .5zm0 28.1V3.4C23 3.4 28.6 9 28.6 16S23 28.6 16 28.6z"
|
||||
/>
|
||||
</svg>
|
||||
`.trim();
|
||||
|
||||
document.body.appendChild(toggle);
|
||||
});
|
||||
1
dist-demo/dist-browser/constants.d.ts
vendored
Normal file
1
dist-demo/dist-browser/constants.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export declare const __dirname: string;
|
||||
2
dist-demo/dist-browser/hashTwt.d.ts
vendored
Normal file
2
dist-demo/dist-browser/hashTwt.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
import { Twt } from './types.ts';
|
||||
export default function hashTwt(twt: Twt): string;
|
||||
5
dist-demo/dist-browser/index.d.ts
vendored
Normal file
5
dist-demo/dist-browser/index.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
export type * from './types.ts';
|
||||
export { default as hashTwt } from './hashTwt.ts';
|
||||
export { default as loadAndParseTwtxtFile } from './loadAndParseTwtxt.ts';
|
||||
export { default as parseTwtxt } from './parseTwtxt.ts';
|
||||
export { base32Encode } from './utils.ts';
|
||||
14
dist-demo/dist-browser/loadAndParseTwtxt.d.ts
vendored
Normal file
14
dist-demo/dist-browser/loadAndParseTwtxt.d.ts
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
export default function loadAndParseTwtxtFile(url?: string): Promise<{
|
||||
lastModified: string;
|
||||
following: import('./types.ts').Twttr[];
|
||||
metadata: import('./types.ts').Metadata;
|
||||
twts: {
|
||||
content: string;
|
||||
created: string;
|
||||
createdUTC: string;
|
||||
hash: string;
|
||||
replyHash: string | undefined;
|
||||
replyNick: string | undefined;
|
||||
replyUrl: string | undefined;
|
||||
}[];
|
||||
}>;
|
||||
18
dist-demo/dist-browser/parseTwtxt.d.ts
vendored
Normal file
18
dist-demo/dist-browser/parseTwtxt.d.ts
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Metadata, Twttr } from './types.ts';
|
||||
/**
|
||||
* @param twtxt
|
||||
* @returns object containing: following, metadata, twts
|
||||
*/
|
||||
export default function parseTwtxt(twtxt: string): {
|
||||
following: Twttr[];
|
||||
metadata: Metadata;
|
||||
twts: {
|
||||
content: string;
|
||||
created: string;
|
||||
createdUTC: string;
|
||||
hash: string;
|
||||
replyHash: string | undefined;
|
||||
replyNick: string | undefined;
|
||||
replyUrl: string | undefined;
|
||||
}[];
|
||||
};
|
||||
3401
dist-demo/dist-browser/twtxt-lib.js
Normal file
3401
dist-demo/dist-browser/twtxt-lib.js
Normal file
File diff suppressed because it is too large
Load Diff
1
dist-demo/dist-browser/twtxt-lib.js.map
Normal file
1
dist-demo/dist-browser/twtxt-lib.js.map
Normal file
File diff suppressed because one or more lines are too long
2318
dist-demo/dist-browser/twtxt-lib.min.js
vendored
Normal file
2318
dist-demo/dist-browser/twtxt-lib.min.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
dist-demo/dist-browser/twtxt-lib.min.js.map
Normal file
1
dist-demo/dist-browser/twtxt-lib.min.js.map
Normal file
File diff suppressed because one or more lines are too long
31
dist-demo/dist-browser/types.d.ts
vendored
Normal file
31
dist-demo/dist-browser/types.d.ts
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
export interface LoadAndParseTwtxtWithCacheConfig {
|
||||
cacheKeyPrefix: string;
|
||||
onLoad?: (data: Twtxt) => void;
|
||||
user?: Twttr;
|
||||
}
|
||||
export interface Metadata {
|
||||
[key: string]: string | string[];
|
||||
}
|
||||
export interface Twt {
|
||||
avatar?: string;
|
||||
content: string;
|
||||
created: string;
|
||||
createdUTC: string;
|
||||
hash?: string;
|
||||
nick?: string;
|
||||
noDom?: boolean;
|
||||
replyHash?: string;
|
||||
replyNick?: string;
|
||||
replyUrl?: string;
|
||||
url?: string;
|
||||
}
|
||||
export interface Twttr {
|
||||
avatar?: string;
|
||||
nick: string;
|
||||
url: string;
|
||||
}
|
||||
export interface Twtxt {
|
||||
following: Twttr[];
|
||||
metadata: Metadata;
|
||||
twts: Twt[];
|
||||
}
|
||||
2
dist-demo/dist-browser/utils.d.ts
vendored
Normal file
2
dist-demo/dist-browser/utils.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
export declare const base32Encode: (payload: string | Uint8Array<ArrayBufferLike>) => any;
|
||||
export declare const getValueOrFirstEntry: (value: unknown | unknown[]) => any;
|
||||
297
dist-demo/index.html
Normal file
297
dist-demo/index.html
Normal file
@@ -0,0 +1,297 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>twtxt-lib Demo</title>
|
||||
<link rel="stylesheet" href="/demo/styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<ul class="tabs">
|
||||
<li class="tab tabOverview" id="overview">
|
||||
<a class="tab-link" href="#overview" id="tabOverview-link">Overview</a>
|
||||
<div class="tab-panel" id="tabOverview-panel">
|
||||
<h1>twtxt-lib</h2>
|
||||
<p>
|
||||
An isomorphic TypeScript library of
|
||||
utility functions for parsing and interacting with
|
||||
<a href="https://twtxt.dev/"
|
||||
><code>twtxt.txt</code> files</a
|
||||
>.
|
||||
</p>
|
||||
|
||||
<p>These functions include:</p>
|
||||
|
||||
<ul>
|
||||
<li><a href="#hashTwt">hashTwt</a>: takes the constituent parts of a
|
||||
“twt” and generates an
|
||||
<a href="https://twtxt.dev/exts/twt-hash.html">extension-compatible
|
||||
hash</a> for it.</li>
|
||||
<li><a href="#parseTwtxt">parseTwtxt</a>: parses a twtxt file string,
|
||||
returning an object with information about the file and its owner
|
||||
(including <a href="https://twtxt.dev/exts/twt-hash.html">hashes</a>
|
||||
for each twt and any
|
||||
<a href="https://twtxt.dev/exts/metadata.html">metadata</a> in the
|
||||
file).</li>
|
||||
<li><a href="#loadAndParseTwtxtFile">loadAndParseTwtxtFile</a>: fetches a
|
||||
twtxt file from <em>the internet</em> and parses it into an object (as
|
||||
above).</li>
|
||||
</ul>
|
||||
|
||||
<h2>Features</h2>
|
||||
|
||||
<ul>
|
||||
<li>Isomorphic, available as an
|
||||
(<a href="/dist-browser/twtxt-lib.min.js">optionally minified</a>)
|
||||
<a href="/dist-browser/twtxt-lib.js">ES6+ library for the browser</a>,
|
||||
with NPM and JSR versions coming soon.
|
||||
<li>Fully typed and source-mapped.</li>
|
||||
<li><a href="https://caniuse.com/es6-module">ESM</a> (and <a href="https://antfu.me/posts/move-on-to-esm-only">ESM only</a>)</li>
|
||||
<li>Includes an interactive demo <em>(you're looking at it)</em>.</li>
|
||||
</ul>
|
||||
|
||||
<h2>Installation</h2>
|
||||
|
||||
<p>
|
||||
Browser
|
||||
<ul>
|
||||
<li>Download <a href="/dist-browser/twtxt-lib.js">library</a> (or
|
||||
<a href="/dist-browser/twtxt-lib.min.js">minified version</a>).</li>
|
||||
<li>Import desired function(s) into your project: `import { hashTwt, loadAndParseTwtxtFile } from "./twtxt-lib.js"`</li>
|
||||
</ul>
|
||||
</p>
|
||||
<p>
|
||||
JSR
|
||||
<ul>
|
||||
<li>Coming soon</li>
|
||||
</ul>
|
||||
</p>
|
||||
<p>
|
||||
NPM
|
||||
<ul>
|
||||
<li>Coming soon</li>
|
||||
</ul>
|
||||
</p>
|
||||
|
||||
<p>Use the tabs to learn more.</p>
|
||||
|
||||
<hr />
|
||||
|
||||
<div id="tabOverview-example">
|
||||
<p>
|
||||
Example parsed file from
|
||||
<a href="/twtxt-demos/demo-hipster-twtxt.txt">
|
||||
<code>/twtxt-demos/demo-hipster-twtxt.txt</code> </a
|
||||
>:
|
||||
</p>
|
||||
<span class="dotLoader"></span>
|
||||
</div>
|
||||
<p class="copyright">
|
||||
Copyright © 2026 Eric Woodward, released under the <a href="https://www.itsericwoodward.com/licenses/mit/">MIT License</a>.
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="tab tabHashTwt" id="hashTwt">
|
||||
<a class="tab-link" href="#hashTwt">hashTwt</a>
|
||||
<div class="tab-panel" id="tabHashTwt-panel">
|
||||
<p>
|
||||
A function that takes the constituent parts of a “twt” and
|
||||
generates an
|
||||
<a href="https://twtxt.dev/exts/twt-hash.html">extension-compatible hash</a>
|
||||
for it.
|
||||
</p>
|
||||
<form id="formHash" name="formHash" method="post">
|
||||
<div class="flexRow">
|
||||
<div class="flexCol">
|
||||
<label for="content">Content</label>
|
||||
<textarea
|
||||
id="content"
|
||||
name="content"
|
||||
>Prow scuttle parley provost Sail ho shrouds spirits boom mizzenmast yardarm.</textarea>
|
||||
</div>
|
||||
<div class="flexCol">
|
||||
<label>
|
||||
Created
|
||||
<input
|
||||
type="text"
|
||||
name="created"
|
||||
value="2026-02-01T01:23:45Z"
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
URL
|
||||
<input
|
||||
type="text"
|
||||
name="url"
|
||||
value="https://example.org/~pirate/twtxt.txt"
|
||||
/>
|
||||
</label>
|
||||
<input type="submit" value="Go" />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="tab tabParse" id="parseTwtxt">
|
||||
<a class="tab-link" href="#parseTwtxt">parseTwtxt</a>
|
||||
<div class="tab-panel" id="tabParse-panel">
|
||||
<p>
|
||||
A function that parses a twtxt file string,
|
||||
returning an object with information about the file and its owner
|
||||
(including <a href="https://twtxt.dev/exts/twt-hash.html">hashes</a>
|
||||
for each twt and any
|
||||
<a href="https://twtxt.dev/exts/metadata.html">metadata</a> in the
|
||||
file).
|
||||
</p>
|
||||
|
||||
<p>Pre-included examples:</p>
|
||||
<ul>
|
||||
<li>
|
||||
<code>/twtxt-demos/demo-hipster-twtxt.txt</code>
|
||||
<button
|
||||
data-url="/twtxt-demos/demo-hipster-twtxt.txt"
|
||||
id="parseHipsterButton"
|
||||
name="parseHipsterButton"
|
||||
>
|
||||
Load
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<code>/twtxt-demos/demo-pirate-twtxt.txt</code>
|
||||
<button
|
||||
data-url="/twtxt-demos/demo-pirate-twtxt.txt"
|
||||
id="parsePirateButton"
|
||||
name="parsePirateButton"
|
||||
>
|
||||
Load
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<code>/twtxt-demos/demo-sagan-twtxt.txt</code>
|
||||
<button
|
||||
data-url="/twtxt-demos/demo-sagan-twtxt.txt"
|
||||
id="parseSaganButton"
|
||||
name="parseSaganButton"
|
||||
/>
|
||||
Load
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
<em
|
||||
>Note that CORS restrictions may limit the
|
||||
effectiveness of using this function from another
|
||||
domain.</em
|
||||
>
|
||||
</p>
|
||||
|
||||
<form id="formParse" name="formParse" method="post">
|
||||
<div class="flexRow">
|
||||
<div class="flexCol">
|
||||
<label>
|
||||
URL
|
||||
<input
|
||||
id="parseURL"
|
||||
name="url"
|
||||
type="text"
|
||||
value="/twtxt-demos/demo-hipster-twtxt.txt"
|
||||
/>
|
||||
</label>
|
||||
<input type="submit" value="Go" />
|
||||
<span class="dotLoader"></span>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="tab tabLoadAndParse" id="loadAndParseTwtxtFile">
|
||||
<a class="tab-link" href="#loadAndParseTwtxtFile">loadAndParseTwtxtFile</a>
|
||||
<div class="tab-panel" id="tabLoadAndParse-panel">
|
||||
<p>
|
||||
An async function that fetches a twtxt.txt-compatible file
|
||||
from a URL and parses it into an object
|
||||
</p>
|
||||
<p>Pre-included examples:</p>
|
||||
<ul>
|
||||
<li>
|
||||
<code>/twtxt-demos/demo-hipster-twtxt.txt</code>
|
||||
<button
|
||||
data-url="/twtxt-demos/demo-hipster-twtxt.txt"
|
||||
id="loadAndParseHipsterButton"
|
||||
name="loadAndParseHipsterButton"
|
||||
>
|
||||
Load
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<code>/twtxt-demos/demo-pirate-twtxt.txt</code>
|
||||
<button
|
||||
data-url="/twtxt-demos/demo-pirate-twtxt.txt"
|
||||
id="loadAndParsePirateButton"
|
||||
name="loadAndParsePirateButton"
|
||||
>
|
||||
Load
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<code>/twtxt-demos/demo-sagan-twtxt.txt</code>
|
||||
<button
|
||||
data-url="/twtxt-demos/demo-sagan-twtxt.txt"
|
||||
id="loadAndParseSaganButton"
|
||||
name="loadAndParseSaganButton"
|
||||
>
|
||||
Load
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
<em
|
||||
>Note that CORS restrictions may limit the
|
||||
effectiveness of using this function from another
|
||||
domain.</em
|
||||
>
|
||||
</p>
|
||||
|
||||
<form
|
||||
id="formLoadAndParse"
|
||||
name="formLoadAndParse"
|
||||
method="post"
|
||||
>
|
||||
<div class="flexRow">
|
||||
<div class="flexCol">
|
||||
<label>
|
||||
URL
|
||||
<input
|
||||
id="loadAndParseURL"
|
||||
name="url"
|
||||
type="text"
|
||||
value="/twtxt-demos/demo-hipster-twtxt.txt"
|
||||
/>
|
||||
</label>
|
||||
<input type="submit" value="Go" />
|
||||
<span class="dotLoader"></span>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<script src="/dist-browser/twtxt-lib.js" type="module"></script>
|
||||
<script src="/demo/theme-toggle.js" type="module"></script>
|
||||
<script src="/demo/external-links.js" type="module"></script>
|
||||
<script src="/demo/add-default-route.js" type="module"></script>
|
||||
<script src="/demo/overview-example-source.js" type="module"></script>
|
||||
<script src="/demo/overview-example-result.js" type="module"></script>
|
||||
<script src="/demo/hashTwt-example-source.js" type="module"></script>
|
||||
<script src="/demo/hashTwt-example-result.js" type="module"></script>
|
||||
<script src="/demo/parseTwtxt-example-source.js" type="module"></script>
|
||||
<script src="/demo/parseTwtxt-example-result.js" type="module"></script>
|
||||
<script src="/demo/loadAndParseTwtxtFile-example-source.js" type="module"></script>
|
||||
<script src="/demo/loadAndParseTwtxtFile-example-result.js" type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
20
dist-demo/twtxt-demos/demo-hipster-twtxt.txt
Normal file
20
dist-demo/twtxt-demos/demo-hipster-twtxt.txt
Normal file
@@ -0,0 +1,20 @@
|
||||
# nick = demo_hipster
|
||||
# url = https://example.com/demo-hipster-twtxt.txt
|
||||
# avatar = https://i.pravatar.cc/150?img=67
|
||||
# description = Kitsch ut post-ironic, bruh tilde non shabby chic iceland fixie consequat?
|
||||
#
|
||||
# follow = demo_hipster https://example.com/demo-hipster-twtxt.txt
|
||||
# follow = demo_pirate https://example.org/~pirate/twtxt.txt
|
||||
# follow = demo_sagan https://example.net/~saganos/twtxt.txt
|
||||
|
||||
2025-06-02T18:47:01+01:00 Normcore tilde ad selfies, culpa cupping nostrud gatekeep aesthetic PBR&B 3 wolf moon mustache twee.
|
||||
2025-07-05T00:17:46+02:00 Cardigan jean shorts eu 90's. Kitsch knausgaard culpa, marfa mumblecore portland raclette banjo retro exercitation pariatur snackwave williamsburg.
|
||||
2025-07-05T19:35:33+03:00 Literally deep v snackwave nostrud pug YOLO yes plz anim. JOMO crucifix bespoke chambray lomo keytar, labore ipsum.
|
||||
2025-08-05T22:46:11+04:00 Ut letterpress synth hoodie, wayfarers kitsch air plant eu selvage tilde taiyaki grailed cliche ex. Skateboard pariatur non leggings.
|
||||
2025-08-08T13:49:20+05:00 Ad pug ex hashtag live-edge distillery affogato. Succulents hammock taiyaki biodiesel chartreuse, nulla you probably haven't heard of them four dollar toast quinoa keytar cornhole.
|
||||
2025-09-09T12:48:04+05:00 Cardigan JOMO blackbird spyplane, whatever commodo pop-up normcore ad yr in eiusmod forage echo park exercitation +1.
|
||||
2025-10-09T20:33:15+04:00 Culpa snackwave williamsburg, asymmetrical wolf microdosing literally. La croix coloring book jean shorts poutine, 3 wolf moon chicharrones hashtag chillwave affogato green juice.
|
||||
2025-11-11T12:54:43+03:00 Kickstarter kale chips williamsburg swag sunt disrupt chartreuse jianbing banh mi craft beer anim vaporware readymade.
|
||||
2025-12-05T17:15:28+02:00 Pinterest Brooklyn direct trade freegan. Health goth consequat bespoke ad hoodie in est ugh. IPhone typewriter lomo venmo. Hashtag chillwave hella lumbersexual in blackbird spyplane yr tbh. Yr waistcoat kogi est neutra hammock mollit. Drinking vinegar godard hell of occaecat direct trade. In 3 wolf moon jianbing bitters, roof party mixtape yuccie.
|
||||
2026-01-01T11:32:39+01:00 Ethical twee swag, farm-to-table irure semiotics bodega boys umami sriracha stumptown cred four dollar toast tofu photo booth tbh.
|
||||
2026-02-01T13:13:13+02:00 (#4f5dlsa) <@demo_pirate https://example.org/~pirate/twtxt.txt> Asymmetrical kombucha trust fund jawn gentrify sartorial cloud bread artisan live-edge.
|
||||
30
dist-demo/twtxt-demos/demo-pirate-twtxt.txt
Normal file
30
dist-demo/twtxt-demos/demo-pirate-twtxt.txt
Normal file
@@ -0,0 +1,30 @@
|
||||
# nick = demo_pirate
|
||||
# url = https://example.org/~pirate/twtxt.txt
|
||||
# avatar = https://i.pravatar.cc/150?img=22
|
||||
# description = Drink up me hearties, yo ho!
|
||||
# finger = demo_pirate@example.org
|
||||
# link = Web https://example.org/~pirate/
|
||||
# link = Ship https://example.org/~pirate/vessel/
|
||||
# link = Profile https://example.org/~pirate/profile/
|
||||
#
|
||||
# follow = demo_pirate https://example.org/~pirate/twtxt.txt
|
||||
# follow = demo_hipster https://example.com/demo-hipster-twtxt.txt
|
||||
# follow = demo_sagan https://example.net/~saganos/twtxt.txt
|
||||
#
|
||||
# following = 3
|
||||
#
|
||||
2026-02-01T01:23:45Z Prow scuttle parley provost Sail ho shrouds spirits boom mizzenmast yardarm.
|
||||
2026-02-02T02:24:36Z Swab barque interloper chantey doubloon starboard grog black jack gangway rutters.
|
||||
|
||||
2026-02-03T10:48:44Z Pinnace holystone mizzenmast quarter crow's nest nipperkin grog yardarm hempen halter furl.
|
||||
2026-02-04T11:54:12Z Belay yo-ho-ho keelhaul squiffy black spot yardarm spyglass sheet transom heave to.
|
||||
2026-02-05T13:56:12Z Deadlights jack lad schooner scallywag dance the hempen jig carouser broadside cable strike colors.
|
||||
2026-02-06T14:57:58Z Bring a spring upon her cable holystone blow the man down spanker Shiver me timbers to go on account lookout wherry doubloon chase.
|
||||
|
||||
2026-02-07T13:58:56Z Trysail Sail ho Corsair red ensign hulk smartly boom jib rum gangway.
|
||||
2026-02-08T14:26:54Z Case shot Shiver me timbers gangplank crack Jennys tea cup ballast Blimey lee snow crow's nest rutters.
|
||||
2026-02-09T15:01:53Z Fluke jib scourge of the seven seas boatswain schooner gaff booty Jack Tar transom spirits.
|
||||
2026-02-10T19:20:15Z Spyglass snow quarter wench cutlass coxswain scurvy landlubber or just lubber parley walk the plank.
|
||||
|
||||
2026-02-11T19:34:20Z Barbary Coast transom broadside clap of thunder dead men tell no tales gangplank barque bilge rat brigantine list.
|
||||
2026-02-12T13:17:54Z Prow swab tender case shot aye capstan brigantine loot bucko take a caulk.
|
||||
30
dist-demo/twtxt-demos/demo-sagan-twtxt.txt
Normal file
30
dist-demo/twtxt-demos/demo-sagan-twtxt.txt
Normal file
@@ -0,0 +1,30 @@
|
||||
# nick = demo_sagan
|
||||
# url = https://example.net/~saganos/twtxt.txt
|
||||
# avatar = https://i.pravatar.cc/150?img=69
|
||||
# description = Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit?
|
||||
# finger = sagaan@example.net
|
||||
# link = Web https://example.net/~saganos/
|
||||
#
|
||||
# follow = demo_sagan https://example.net/~saganos/twtxt.txt
|
||||
# follow = demo_pirate https://example.org/~pirate/twtxt.txt
|
||||
# follow = demo_hipster https://example.com/demo-hipster-twtxt.txt
|
||||
#
|
||||
# following = 3
|
||||
#
|
||||
|
||||
2026-02-05T23:17:47Z The ash of stellar alchemy permanence of the stars extraordinary claims require extraordinary evidence rings of Uranus vanquish the impossible encyclopaedia galactica?
|
||||
2026-01-22T12:09:44Z Something incredible is waiting to be known another world hearts of the stars tendrils of gossamer clouds a still more glorious dawn awaits venture.
|
||||
2026-01-10T13:44:37Z Dream of the mind's eye citizens of distant epochs a still more glorious dawn awaits preserve and cherish that pale blue dot hearts of the stars preserve and cherish that pale blue dot and billions upon billions upon billions upon billions upon billions upon billions upon billions.
|
||||
2026-01-01T02:05:04Z With pretty stories for which there's little good evidence muse about a billion trillion globular star cluster inconspicuous motes of rock and gas dream of the mind's eye.
|
||||
2025-12-19T20:34:01Z Emerged into consciousness the carbon in our apple pies a very small stage in a vast cosmic arena extraordinary claims require extraordinary evidence from which we spring a mote of dust suspended in a sunbeam.
|
||||
2025-12-15T20:18:08Z Finite but unbounded kindling the energy hidden in matter from which we spring vastness is bearable only through love vastness is bearable only through love billions upon billions.
|
||||
2025-12-11T18:54:56Z Venture emerged into consciousness Vangelis how far away rings of Uranus radio telescope.
|
||||
2025-12-10T17:53:00Z A mote of dust suspended in a sunbeam concept of the number one made in the interiors of collapsing stars vastness is bearable only through love across the centuries inconspicuous motes of rock and gas.
|
||||
2025-11-08T15:23:30Z The ash of stellar alchemy another world at the edge of forever kindling the energy hidden in matter bits of moving fluff star stuff harvesting star light.
|
||||
2025-11-07T14:49:29Z Network of wormholes citizens of distant epochs a mote of dust suspended in a sunbeam hearts of the stars are creatures of the cosmos network of wormholes.
|
||||
2025-11-03T14:06:39Z Ship of the imagination the carbon in our apple pies dream of the mind's eye concept of the number one science as a patch of light.
|
||||
2025-11-02T14:25:45Z The sky calls to us of brilliant syntheses tingling of the spine a very small stage in a vast cosmic arena venture the only home we've ever known.
|
||||
2025-11-01T13:46:45Z Across the centuries take root and flourish preserve and cherish that pale blue dot from which we spring great turbulent clouds muse about.
|
||||
2025-10-25T12:58:20Z White dwarf a mote of dust suspended in a sunbeam muse about globular star cluster cosmic ocean circumnavigated.
|
||||
2025-10-24T12:29:20Z Are creatures of the cosmos another world citizens of distant epochs courage of our questions are creatures of the cosmos venture.
|
||||
2025-10-23T12:49:02Z Something incredible is waiting to be known the only home we've ever known courage of our questions hydrogen atoms vastness is bearable only through love a very small stage in a vast cosmic arena.
|
||||
Reference in New Issue
Block a user