mirror of
https://github.com/lainbo/component-party.git
synced 2026-04-05 13:09:03 +08:00
refactor: migrate from astro to svelte + vite to improve performance (#131)
* add highlighter * add playground url * add markdown support and notification center * fix multiple md files * add meta description with framework list * fix: framework id loop index * store frameworkIdsSelected in locale storage * add generateFrameworkContent vite plugin * add missing snippet case * add content generate cache * add angular component highlighter * improve content generator organization * add format and linter * add git hooks * add default frameworks
This commit is contained in:
committed by
GitHub
parent
4526ef2092
commit
be52cbcbfc
@@ -1,4 +1,4 @@
|
||||
import FRAMEWORKS from "./src/frameworks.mjs";
|
||||
import FRAMEWORKS from "./frameworks.mjs";
|
||||
|
||||
/**
|
||||
* @type {import("eslint").Linter.Config}
|
||||
|
||||
2
.github/workflows/node.js.yml
vendored
2
.github/workflows/node.js.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [14.x, 16.x]
|
||||
node-version: [16.x, 18.x]
|
||||
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
||||
|
||||
steps:
|
||||
|
||||
29
.gitignore
vendored
29
.gitignore
vendored
@@ -1,9 +1,26 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
new-section
|
||||
.eslintcache
|
||||
yarn.lock
|
||||
package-lock.json
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
.history
|
||||
.chrome
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
src/generatedContent
|
||||
@@ -1,2 +0,0 @@
|
||||
tasks:
|
||||
- init: pnpm install --frozen-lockfile
|
||||
1
.husky/.gitignore
vendored
1
.husky/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
_
|
||||
@@ -1,4 +0,0 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
npx lint-staged
|
||||
2
.npmrc
2
.npmrc
@@ -1,2 +0,0 @@
|
||||
# Expose Astro dependencies for `pnpm` users
|
||||
shamefully-hoist=true
|
||||
15
.vscode/settings.json
vendored
15
.vscode/settings.json
vendored
@@ -1,21 +1,8 @@
|
||||
{
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"eslint.probe": ["javascript", "javascriptreact", "vue"],
|
||||
"editor.formatOnSave": false,
|
||||
// Runs Prettier, then ESLint
|
||||
"editor.codeActionsOnSave": ["source.formatDocument", "source.fixAll.eslint"],
|
||||
"[svelte]": {
|
||||
"editor.defaultFormatter": "svelte.svelte-vscode"
|
||||
},
|
||||
"cSpell.words": [
|
||||
"alpinejs",
|
||||
"astro",
|
||||
"astrojs",
|
||||
"matschik",
|
||||
"mdast",
|
||||
"pnpm",
|
||||
"qwik",
|
||||
"shiki",
|
||||
"webp"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ This site is built with [Astro](https://docs.astro.build). Site content is writt
|
||||
1. Fork the project and create a new branch
|
||||
2. Add the new framework SVG logo in `public/framework`
|
||||
3. Install the ESLint plugin associated to the framework
|
||||
4. In `src/frameworks.mjs`, add a new entry with SVG link and ESLint configuration
|
||||
4. In `frameworks.mjs`, add a new entry with SVG link and ESLint configuration
|
||||
|
||||
## Improve website
|
||||
|
||||
|
||||
21
LICENSE
21
LICENSE
@@ -1,21 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2022-present, Mathieu Schimmerling
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
@@ -1,5 +1,3 @@
|
||||
[![Open in Gitpod][gitpod-src]][gitpod-href]
|
||||
|
||||

|
||||
|
||||
> Web component JS frameworks quick overview by their syntax and features
|
||||
@@ -383,7 +381,7 @@ This project requires Node.js to be `v14.0.0` or higher, because we use new Java
|
||||
1. Fork the project and create a new branch
|
||||
2. Add the new framework SVG logo in `public/framework`
|
||||
3. Install the ESLint plugin associated to the framework
|
||||
4. In `src/frameworks.mjs`, add a new entry with SVG link and ESLint configuration
|
||||
4. In `frameworks.mjs`, add a new entry with SVG link and ESLint configuration
|
||||
|
||||
## 🧑💻 Contributors
|
||||
|
||||
@@ -393,8 +391,3 @@ This project exists thanks to all the people who contribute. \[[Contribute](CONT
|
||||
## ⚖️ License
|
||||
|
||||
MIT. Made with 💖
|
||||
|
||||
<!-- variables -->
|
||||
|
||||
[gitpod-src]: https://shields.io/badge/Open%20in-Gitpod-green?logo=Gitpod
|
||||
[gitpod-href]: https://gitpod.io/#https://github.com/matschik/component-party
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
import { defineConfig } from "astro/config";
|
||||
import tailwind from "@astrojs/tailwind";
|
||||
import svelte from "@astrojs/svelte";
|
||||
import FullReload from "vite-plugin-full-reload";
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
// https://docs.astro.build/en/reference/configuration-reference/
|
||||
integrations: [tailwind(), svelte()],
|
||||
vite: {
|
||||
plugins: [FullReload(["content/**/*"])],
|
||||
optimizeDeps: {
|
||||
exclude: ["locate-path", "path-exists", "find-up"],
|
||||
},
|
||||
},
|
||||
});
|
||||
43
build/generateContentVitePlugin.js
Normal file
43
build/generateContentVitePlugin.js
Normal file
@@ -0,0 +1,43 @@
|
||||
import fs from "fs";
|
||||
import generateContent from "./lib/generateContent.js";
|
||||
import { createFsCache } from "micache";
|
||||
import { hashElement } from "folder-hash";
|
||||
|
||||
const contentDirFsCache = await createFsCache("pluginGenerateFrameworkContent");
|
||||
|
||||
export default function pluginGenerateFrameworkContent() {
|
||||
const name = "generateFrameworkContent";
|
||||
|
||||
function logInfo(...args) {
|
||||
console.info(`[${name}]`, ...args);
|
||||
}
|
||||
|
||||
async function build() {
|
||||
logInfo("Generating framework content files...");
|
||||
const contentDirHash =
|
||||
(await hashElement("content")).hash + (await hashElement("build")).hash;
|
||||
const contentDirLastHash = await contentDirFsCache.get("contentDirHash");
|
||||
if (contentDirHash !== contentDirLastHash) {
|
||||
await generateContent();
|
||||
await contentDirFsCache.set("contentDirHash", contentDirHash);
|
||||
logInfo(`done`);
|
||||
} else {
|
||||
logInfo(`done with cache`);
|
||||
}
|
||||
}
|
||||
|
||||
let fsContentWatcher;
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
fsContentWatcher = fs.watch("content", { recursive: true }, build);
|
||||
}
|
||||
|
||||
return {
|
||||
name,
|
||||
async buildStart() {
|
||||
await build();
|
||||
},
|
||||
buildEnd() {
|
||||
fsContentWatcher && fsContentWatcher.close();
|
||||
},
|
||||
};
|
||||
}
|
||||
63
build/lib/angularHighlighter.js
vendored
Normal file
63
build/lib/angularHighlighter.js
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
export function mustUseAngularHighlighter(fileContent) {
|
||||
return (
|
||||
fileContent.includes("@angular/core") && fileContent.includes("template")
|
||||
);
|
||||
}
|
||||
|
||||
export function highlightAngularComponent(highlighter, fileContent, fileExt) {
|
||||
const templateCode = getAngularTemplateCode(fileContent);
|
||||
|
||||
let codeHighlighted = "";
|
||||
if (templateCode) {
|
||||
const componentWithEmptyTemplate =
|
||||
removeAngularTemplateContent(fileContent);
|
||||
const templateCodeHighlighted = highlighter(templateCode, {
|
||||
lang: "html",
|
||||
});
|
||||
|
||||
const componentWithoutTemplateHighlighted = highlighter(
|
||||
componentWithEmptyTemplate,
|
||||
{
|
||||
lang: fileExt,
|
||||
}
|
||||
);
|
||||
|
||||
codeHighlighted = componentWithoutTemplateHighlighted.replace(
|
||||
"template",
|
||||
"template: `" + removeCodeWrapper(templateCodeHighlighted) + "`,"
|
||||
);
|
||||
} else {
|
||||
codeHighlighted = highlighter(fileContent, {
|
||||
lang: fileExt,
|
||||
});
|
||||
}
|
||||
|
||||
return codeHighlighted;
|
||||
}
|
||||
|
||||
function getAngularTemplateCode(fileContent) {
|
||||
// regex to grab what is inside angular component template inside backticks
|
||||
const regex = /template:\s*`([\s\S]*?)`/gm;
|
||||
|
||||
// grab the template string
|
||||
const template = regex.exec(fileContent);
|
||||
|
||||
if (template) return template[1];
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
function removeAngularTemplateContent(fileContent) {
|
||||
const componentWithoutContentInsideTemplate = fileContent.replace(
|
||||
/template:\s*`([\s\S]*?)([^*])`,?/gm,
|
||||
"template"
|
||||
);
|
||||
|
||||
return componentWithoutContentInsideTemplate;
|
||||
}
|
||||
|
||||
function removeCodeWrapper(html) {
|
||||
const regexForWrapper = /<pre([\s\S]*?)><code>([\s\S]*?)<\/code><\/pre>/gm;
|
||||
const code = regexForWrapper.exec(html);
|
||||
return code[2];
|
||||
}
|
||||
2113
build/lib/componentPartyShikiTheme.js
Normal file
2113
build/lib/componentPartyShikiTheme.js
Normal file
File diff suppressed because it is too large
Load Diff
194
build/lib/generateContent.js
Normal file
194
build/lib/generateContent.js
Normal file
@@ -0,0 +1,194 @@
|
||||
import fs from "node:fs/promises";
|
||||
import { packageDirectory } from "pkg-dir";
|
||||
import path from "node:path";
|
||||
import kebabCase from "lodash.kebabcase";
|
||||
import { getHighlighter } from "shiki";
|
||||
import FRAMEWORKS from "../../frameworks.mjs";
|
||||
import frameworkPlayground from "./playground/index.js";
|
||||
import componentPartyShikiTheme from "./componentPartyShikiTheme.js";
|
||||
import prettier from "prettier";
|
||||
import markdownToHtml from "./markdownToHtml.js";
|
||||
import {
|
||||
highlightAngularComponent,
|
||||
mustUseAngularHighlighter,
|
||||
} from "./angularHighlighter.js";
|
||||
|
||||
export default async function generateContent() {
|
||||
const highlighter = await getHighlighter({
|
||||
theme: componentPartyShikiTheme,
|
||||
langs: ["javascript", "svelte", "html", "hbs", "tsx", "jsx", "vue"],
|
||||
});
|
||||
|
||||
const rootDir = await packageDirectory();
|
||||
const contentPath = path.join(rootDir, "content");
|
||||
const sectionDirNames = await fs.readdir(contentPath);
|
||||
|
||||
const treePayload = {
|
||||
sections: [],
|
||||
snippets: [],
|
||||
};
|
||||
|
||||
const byFrameworkId = {};
|
||||
|
||||
for (const sectionDirName of sectionDirNames) {
|
||||
const sectionTitle = dirNameToTitle(sectionDirName);
|
||||
const sectionId = kebabCase(sectionTitle);
|
||||
|
||||
treePayload.sections.push({
|
||||
sectionId,
|
||||
sectionDirName,
|
||||
title: sectionTitle,
|
||||
});
|
||||
|
||||
const snippetsDirPath = path.join(contentPath, sectionDirName);
|
||||
const snippetDirNames = await fs.readdir(snippetsDirPath);
|
||||
|
||||
for (const snippetDirName of snippetDirNames) {
|
||||
const title = dirNameToTitle(snippetDirName);
|
||||
const snippetId = kebabCase(title);
|
||||
|
||||
treePayload.snippets.push({
|
||||
sectionId,
|
||||
snippetId,
|
||||
snippetDirName,
|
||||
sectionDirName,
|
||||
title,
|
||||
});
|
||||
|
||||
const frameworksDirPath = path.join(snippetsDirPath, snippetDirName);
|
||||
const frameworkIds = await fs.readdir(frameworksDirPath);
|
||||
for (const frameworkId of frameworkIds) {
|
||||
const frameworkSnippet = {
|
||||
frameworkId,
|
||||
snippetId,
|
||||
files: [],
|
||||
playgroundURL: "",
|
||||
markdownFiles: [],
|
||||
snippetEditHref: `https://github.com/matschik/component-party/tree/main/content/${sectionDirName}/${snippetDirName}/${frameworkId}`,
|
||||
};
|
||||
|
||||
const codeFilesDirPath = path.join(frameworksDirPath, frameworkId);
|
||||
const codeFileNames = await fs.readdir(codeFilesDirPath);
|
||||
|
||||
for (const codeFileName of codeFileNames) {
|
||||
const codeFilePath = path.join(codeFilesDirPath, codeFileName);
|
||||
const ext = path.parse(codeFilePath).ext.split(".").pop();
|
||||
const content = await fs.readFile(codeFilePath, "utf-8");
|
||||
|
||||
const file = {
|
||||
fileName: codeFileName,
|
||||
ext,
|
||||
content,
|
||||
contentHtml: "",
|
||||
};
|
||||
|
||||
if (ext === "md") {
|
||||
file.contentHtml = await markdownToHtml(content);
|
||||
frameworkSnippet.markdownFiles.push(file);
|
||||
} else {
|
||||
file.contentHtml = mustUseAngularHighlighter(content)
|
||||
? highlightAngularComponent(highlighter.codeToHtml, content, ext)
|
||||
: highlighter.codeToHtml(content, { lang: ext });
|
||||
|
||||
frameworkSnippet.files.push(file);
|
||||
}
|
||||
}
|
||||
|
||||
if (frameworkSnippet.files.length > 0) {
|
||||
const playgroundURL = generatePlaygroundURL(
|
||||
frameworkId,
|
||||
frameworkSnippet.files
|
||||
);
|
||||
|
||||
if (playgroundURL) {
|
||||
frameworkSnippet.playgroundURL = playgroundURL;
|
||||
}
|
||||
}
|
||||
|
||||
if (!byFrameworkId[frameworkId]) {
|
||||
byFrameworkId[frameworkId] = [];
|
||||
}
|
||||
|
||||
byFrameworkId[frameworkId].push(frameworkSnippet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const generatedContentDirPath = path.join(rootDir, "src/generatedContent");
|
||||
const frameworkDirPath = path.join(generatedContentDirPath, "framework");
|
||||
const treeFilePath = path.join(generatedContentDirPath, "tree.js");
|
||||
const frameworkIndexPath = path.join(frameworkDirPath, "index.js");
|
||||
await fs.rm(generatedContentDirPath, { recursive: true, force: true });
|
||||
await fs.mkdir(generatedContentDirPath, { recursive: true });
|
||||
const commentDisclaimer = `// File generated from "node scripts/generateContent.js", DO NOT EDIT`;
|
||||
|
||||
await writeJsFile(
|
||||
treeFilePath,
|
||||
`
|
||||
${commentDisclaimer}
|
||||
export const sections = ${JSON.stringify(treePayload.sections, null, 2)};
|
||||
export const snippets = ${JSON.stringify(treePayload.snippets, null, 2)};
|
||||
`
|
||||
);
|
||||
|
||||
await fs.mkdir(frameworkDirPath, { recursive: true });
|
||||
for (const frameworkId of Object.keys(byFrameworkId)) {
|
||||
const frameworkFilePath = path.join(frameworkDirPath, `${frameworkId}.js`);
|
||||
await writeJsFile(
|
||||
frameworkFilePath,
|
||||
`
|
||||
${commentDisclaimer}
|
||||
export default ${JSON.stringify(byFrameworkId[frameworkId], null, 2)}
|
||||
`
|
||||
);
|
||||
}
|
||||
|
||||
await writeJsFile(
|
||||
frameworkIndexPath,
|
||||
`
|
||||
${commentDisclaimer}
|
||||
export default {
|
||||
${Object.keys(byFrameworkId)
|
||||
.map(
|
||||
(frameworkId) =>
|
||||
`${frameworkId}: () => import("./${frameworkId}.js")`
|
||||
)
|
||||
.join(",\n")}
|
||||
|
||||
};
|
||||
`
|
||||
);
|
||||
}
|
||||
|
||||
function dirNameToTitle(dirName) {
|
||||
return capitalize(dirName.split("-").splice(1).join(" "));
|
||||
}
|
||||
|
||||
function capitalize(string) {
|
||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||
}
|
||||
|
||||
async function writeJsFile(filepath, jsCode) {
|
||||
await fs.writeFile(filepath, prettier.format(jsCode, { parser: "babel" }));
|
||||
}
|
||||
|
||||
function generatePlaygroundURL(frameworkId, files) {
|
||||
const frameworkIdPlayground = frameworkPlayground[frameworkId];
|
||||
if (!frameworkIdPlayground) {
|
||||
return;
|
||||
}
|
||||
|
||||
const frameworkConfig = FRAMEWORKS.find((f) => f.id === frameworkId);
|
||||
|
||||
const contentByFilename = frameworkConfig
|
||||
.filesSorter(files)
|
||||
.reduce((acc, file) => {
|
||||
acc[file.fileName] = file.content;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const playgroundURL =
|
||||
frameworkIdPlayground.fromContentByFilename(contentByFilename);
|
||||
|
||||
return playgroundURL;
|
||||
}
|
||||
29
build/lib/markdownToHtml.js
Normal file
29
build/lib/markdownToHtml.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import withShiki from "@stefanprobst/remark-shiki";
|
||||
import fromMarkdown from "remark-parse";
|
||||
import * as shiki from "shiki";
|
||||
import { unified } from "unified";
|
||||
import toHast from "remark-rehype";
|
||||
import withHtmlInMarkdown from "rehype-raw";
|
||||
import toHtml from "rehype-stringify";
|
||||
import componentPartyShikiTheme from "./componentPartyShikiTheme.js";
|
||||
|
||||
export default async function markdownToHtml(code) {
|
||||
async function createProcessor() {
|
||||
const highlighter = await shiki.getHighlighter({
|
||||
theme: componentPartyShikiTheme,
|
||||
});
|
||||
|
||||
const processor = unified()
|
||||
.use(fromMarkdown)
|
||||
.use(withShiki, { highlighter })
|
||||
.use(toHast, { allowDangerousHtml: true })
|
||||
.use(withHtmlInMarkdown)
|
||||
.use(toHtml);
|
||||
|
||||
return processor;
|
||||
}
|
||||
|
||||
const processor = await createProcessor();
|
||||
const vfile = await processor.process(code);
|
||||
return String(vfile);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import nodePath from "path";
|
||||
import nodePath from "node:path";
|
||||
import { compressToURL } from "@matschik/lz-string";
|
||||
|
||||
export default () => {
|
||||
@@ -1,4 +1,4 @@
|
||||
import nodePath from "path";
|
||||
import nodePath from "node:path";
|
||||
|
||||
export default function createSvelteREPL() {
|
||||
const BASE_URL = "https://svelte-repl.vercel.app/#";
|
||||
11
build/lib/playground/index.js
Normal file
11
build/lib/playground/index.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import createAlpinePlayground from "./createAlpinePlayground.js";
|
||||
import createSveltePlayground from "./createSveltePlayground.js";
|
||||
import createVue3Playground from "./createVue3Playground.js";
|
||||
import createSolidPlayground from "./createSolidPlayground.js";
|
||||
|
||||
export default {
|
||||
vue3: createVue3Playground(),
|
||||
svelte: createSveltePlayground(),
|
||||
alpine: createAlpinePlayground(),
|
||||
solid: createSolidPlayground(),
|
||||
};
|
||||
52
index.html
Normal file
52
index.html
Normal file
@@ -0,0 +1,52 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<title>Component Party</title>
|
||||
<link rel="icon" href="/favicon.png" />
|
||||
<link
|
||||
rel="preload"
|
||||
href="/font/Mona-Sans.woff2"
|
||||
as="font"
|
||||
type="font/woff2"
|
||||
crossorigin=""
|
||||
/>
|
||||
|
||||
<meta
|
||||
name="description"
|
||||
content="Web component JS frameworks overview by their syntax and features: <%= frameworkList %>"
|
||||
/>
|
||||
<!-- Primary Meta Tags -->
|
||||
<meta name="title" content="Component Party" />
|
||||
<!-- Open Graph / Facebook -->
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="https://component-party.dev/" />
|
||||
<meta property="og:title" content="Component Party" />
|
||||
<meta
|
||||
property="og:description"
|
||||
content="Web component JS frameworks overview by their syntax and features: <%= frameworkList %>"
|
||||
/>
|
||||
<meta
|
||||
property="og:image"
|
||||
content="https://component-party.dev/banner2.png"
|
||||
/>
|
||||
|
||||
<!-- Twitter -->
|
||||
<meta property="twitter:card" content="summary_large_image" />
|
||||
<meta property="twitter:url" content="https://component-party.dev/" />
|
||||
<meta property="twitter:title" content="Component Party" />
|
||||
<meta
|
||||
property="twitter:description"
|
||||
content="Web component JS frameworks overview by their syntax and features: <%= frameworkList %>"
|
||||
/>
|
||||
<meta
|
||||
property="twitter:image"
|
||||
content="https://component-party.dev/banner2.png"
|
||||
/>
|
||||
</head>
|
||||
|
||||
<body class="bg-gray-900 text-white">
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
33
jsconfig.json
Normal file
33
jsconfig.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"moduleResolution": "Node",
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
/**
|
||||
* svelte-preprocess cannot figure out whether you have
|
||||
* a value or a type, so tell TypeScript to enforce using
|
||||
* `import type` instead of `import` for Types.
|
||||
*/
|
||||
"importsNotUsedAsValues": "error",
|
||||
"isolatedModules": true,
|
||||
"resolveJsonModule": true,
|
||||
/**
|
||||
* To have warnings / errors of the Svelte compiler at the
|
||||
* correct position, enable source maps by default.
|
||||
*/
|
||||
"sourceMap": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
/**
|
||||
* Typecheck JS in `.svelte` and `.js` files by default.
|
||||
* Disable this if you'd like to use dynamic types.
|
||||
*/
|
||||
"checkJs": true
|
||||
},
|
||||
/**
|
||||
* Use global.d.ts instead of compilerOptions.types
|
||||
* to avoid limiting type declarations.
|
||||
*/
|
||||
"include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.svelte"]
|
||||
}
|
||||
108
package.json
108
package.json
@@ -1,87 +1,83 @@
|
||||
{
|
||||
"name": "component-party",
|
||||
"version": "1.0.0",
|
||||
"description": "Web component JS frameworks overview by their syntax and features",
|
||||
"name": "component-party2",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"packageManager": "pnpm@7.0.0",
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"build": "astro build",
|
||||
"preview": "astro preview",
|
||||
"progression": "node scripts/progress.js",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint . --fix",
|
||||
"prettier": "prettier --ignore-path .gitignore --plugin-search-dir=. . --check",
|
||||
"prettier:fix": "prettier --ignore-path .gitignore --plugin-search-dir=. --write .",
|
||||
"format": "pnpm run prettier:fix && pnpm run lint:fix",
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"lint": "eslint . --fix",
|
||||
"lint:check": "eslint .",
|
||||
"format": "prettier --ignore-path .gitignore --plugin-search-dir=. --write .",
|
||||
"format:check": "prettier --ignore-path .gitignore --plugin-search-dir=. . --check",
|
||||
"build:content": "node scripts/generateContent.js",
|
||||
"build:progress": "node scripts/generateReadMeProgress.js",
|
||||
"prepare": "husky install"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/matschik/component-party.git"
|
||||
"dependencies": {
|
||||
"@veljs/svelte": "^0.1.8",
|
||||
"classnames": "^2.3.2",
|
||||
"heroiconsvelte": "^0.1.5"
|
||||
},
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/matschik/component-party/issues"
|
||||
},
|
||||
"homepage": "https://github.com/matschik/component-party#readme",
|
||||
"devDependencies": {
|
||||
"@angular-eslint/eslint-plugin": "^14.1.2",
|
||||
"@angular-eslint/eslint-plugin-template": "^14.1.2",
|
||||
"@angular-eslint/template-parser": "^14.1.2",
|
||||
"@angular/core": "^14.2.10",
|
||||
"@angular/router": "^14.2.10",
|
||||
"@astrojs/markdown-component": "^1.0.2",
|
||||
"@astrojs/svelte": "^1.0.2",
|
||||
"@astrojs/tailwind": "^2.1.2",
|
||||
"@angular-eslint/eslint-plugin": "^15.1.0",
|
||||
"@angular-eslint/eslint-plugin-template": "^15.1.0",
|
||||
"@angular-eslint/template-parser": "^15.1.0",
|
||||
"@babel/core": "^7.20.7",
|
||||
"@babel/eslint-parser": "^7.19.1",
|
||||
"@babel/plugin-proposal-decorators": "^7.20.2",
|
||||
"@builder.io/qwik": "0.13.3",
|
||||
"@babel/plugin-proposal-decorators": "^7.20.7",
|
||||
"@matschik/lz-string": "^0.0.2",
|
||||
"@stefanprobst/remark-shiki": "^2.2.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^2.0.2",
|
||||
"@tailwindcss/typography": "^0.5.8",
|
||||
"@types/mdast": "^3.0.10",
|
||||
"@typescript-eslint/eslint-plugin": "^5.42.1",
|
||||
"@typescript-eslint/parser": "^5.42.1",
|
||||
"astro": "^1.6.7",
|
||||
"@typescript-eslint/eslint-plugin": "^5.48.0",
|
||||
"autoprefixer": "^10.4.13",
|
||||
"codesandbox": "^2.2.3",
|
||||
"eslint": "^8.27.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-ember": "^11.2.0",
|
||||
"eslint-plugin-lit": "^1.6.1",
|
||||
"eslint": "^8.31.0",
|
||||
"eslint-plugin-ember": "^11.4.0",
|
||||
"eslint-plugin-lit": "^1.7.2",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-qwik": "^0.13.3",
|
||||
"eslint-plugin-react": "^7.31.10",
|
||||
"eslint-plugin-solid": "^0.8.0",
|
||||
"eslint-plugin-qwik": "^0.16.1",
|
||||
"eslint-plugin-react": "^7.31.11",
|
||||
"eslint-plugin-solid": "^0.9.1",
|
||||
"eslint-plugin-svelte3": "^4.0.0",
|
||||
"eslint-plugin-vue": "^9.7.0",
|
||||
"eslint-plugin-vue": "^9.8.0",
|
||||
"esm": "^3.2.25",
|
||||
"folder-hash": "^4.0.2",
|
||||
"husky": "^8.0.2",
|
||||
"lint-staged": "^13.0.3",
|
||||
"lint-staged": "^13.1.0",
|
||||
"lodash.kebabcase": "^4.1.1",
|
||||
"micache": "^2.4.1",
|
||||
"pkg-dir": "^7.0.0",
|
||||
"postcss": "^8.4.19",
|
||||
"prettier": "^2.7.1",
|
||||
"prettier-plugin-astro": "^0.7.0",
|
||||
"prettier-plugin-svelte": "^2.8.0",
|
||||
"prop-types": "^15.8.1",
|
||||
"postcss": "^8.4.20",
|
||||
"prettier": "^2.8.1",
|
||||
"prettier-plugin-svelte": "^2.9.0",
|
||||
"react": "^18.2.0",
|
||||
"rehype-raw": "^6.1.1",
|
||||
"rehype-stringify": "^9.0.3",
|
||||
"remark": "^14.0.2",
|
||||
"shiki": "^0.11.1",
|
||||
"svelte": "^3.53.1",
|
||||
"remark-parse": "^10.0.1",
|
||||
"remark-rehype": "^10.1.0",
|
||||
"shiki": "^0.12.1",
|
||||
"svelte": "^3.55.0",
|
||||
"svelte-preprocess": "^5.0.0",
|
||||
"tailwindcss": "^3.2.4",
|
||||
"typescript": "^4.8.4",
|
||||
"vite": "^3.2.3",
|
||||
"vite-plugin-full-reload": "^1.0.4"
|
||||
"typescript": "^4.9.4",
|
||||
"unified": "^10.1.2",
|
||||
"vite": "^4.0.3",
|
||||
"vite-plugin-html": "^3.2.0"
|
||||
},
|
||||
"overrides": {
|
||||
"@stefanprobst/remark-shiki": {
|
||||
"shiki": "0.12.1"
|
||||
}
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,jsx,ts,tsx,vue,svelte}": [
|
||||
"prettier --write",
|
||||
"eslint --cache --fix"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"alpinejs": "^3.10.5"
|
||||
}
|
||||
}
|
||||
|
||||
3639
pnpm-lock.yaml
generated
3639
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
6
postcss.config.cjs
Normal file
6
postcss.config.cjs
Normal file
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
BIN
public/font/Mona-Sans.woff2
Normal file
BIN
public/font/Mona-Sans.woff2
Normal file
Binary file not shown.
3
scripts/generateContent.js
Normal file
3
scripts/generateContent.js
Normal file
@@ -0,0 +1,3 @@
|
||||
import generateContent from "../build/lib/generateContent.js";
|
||||
|
||||
await generateContent();
|
||||
@@ -1,9 +1,9 @@
|
||||
import { remark } from "remark";
|
||||
import fs from "fs/promises";
|
||||
import frameworks from "../src/frameworks.mjs";
|
||||
import { packageDirectory } from "pkg-dir";
|
||||
import nodePath from "node:path";
|
||||
import kebabCase from "lodash.kebabcase";
|
||||
import FRAMEWORKS from "../frameworks.mjs";
|
||||
|
||||
function removeMarkdownHeadingContent(
|
||||
content,
|
||||
@@ -127,7 +127,7 @@ async function main() {
|
||||
);
|
||||
|
||||
let progressionContent = "";
|
||||
for (const framework of frameworks) {
|
||||
for (const framework of FRAMEWORKS) {
|
||||
function mdCheck(b) {
|
||||
return b ? "x" : " ";
|
||||
}
|
||||
@@ -167,5 +167,4 @@ ${list}
|
||||
|
||||
await fs.writeFile("README.md", newContent);
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
281
src/App.svelte
Normal file
281
src/App.svelte
Normal file
@@ -0,0 +1,281 @@
|
||||
<script>
|
||||
import FRAMEWORKS from "../frameworks.mjs";
|
||||
import c from "classnames";
|
||||
import FrameworkLabel from "./components/FrameworkLabel.svelte";
|
||||
import { sections, snippets } from "./generatedContent/tree.js";
|
||||
import snippetsImporterByFrameworkId from "./generatedContent/framework/index.js";
|
||||
import {
|
||||
EyeSlashIcon,
|
||||
PlayIcon,
|
||||
PencilIcon,
|
||||
ArrowUpIcon,
|
||||
} from "heroiconsvelte/24/outline";
|
||||
import CodeEditor from "./components/CodeEditor.svelte";
|
||||
import AppNotificationCenter from "./components/AppNotificationCenter.svelte";
|
||||
import createLocaleStorage from "./lib/createLocaleStorage.js";
|
||||
import { onMount } from "svelte";
|
||||
import Header from "./components/Header.svelte";
|
||||
import Aside from "./components/Aside.svelte";
|
||||
import GithubIcon from "./components/GithubIcon.svelte";
|
||||
|
||||
const frameworkIdsSelectedStorage = createLocaleStorage("framework_display");
|
||||
|
||||
let frameworkIdsSelected = new Set();
|
||||
let snippetsByFrameworkId = new Map();
|
||||
|
||||
let frameworkIdsSelectedStorageInitialized = false;
|
||||
|
||||
onMount(() => {
|
||||
frameworkIdsSelected = new Set(
|
||||
frameworkIdsSelectedStorage.getJSON() || ["svelte", "react"]
|
||||
);
|
||||
frameworkIdsSelectedStorageInitialized = true;
|
||||
});
|
||||
|
||||
$: {
|
||||
if (frameworkIdsSelectedStorageInitialized) {
|
||||
frameworkIdsSelectedStorage.setJSON([...frameworkIdsSelected]);
|
||||
}
|
||||
}
|
||||
|
||||
function hideFrameworkId(frameworkId) {
|
||||
frameworkIdsSelected.delete(frameworkId);
|
||||
frameworkIdsSelected = frameworkIdsSelected;
|
||||
}
|
||||
|
||||
function toggleFrameworkId(frameworkId) {
|
||||
if (frameworkIdsSelected.has(frameworkId)) {
|
||||
frameworkIdsSelected.delete(frameworkId);
|
||||
} else {
|
||||
frameworkIdsSelected.add(frameworkId);
|
||||
}
|
||||
frameworkIdsSelected = frameworkIdsSelected;
|
||||
}
|
||||
|
||||
$: {
|
||||
async function importFrameworkSnippets(frameworkIds) {
|
||||
for (const frameworkId of frameworkIds) {
|
||||
if (!snippetsByFrameworkId.has(frameworkId)) {
|
||||
const frameworkSnippets = (
|
||||
await snippetsImporterByFrameworkId[frameworkId]()
|
||||
).default;
|
||||
snippetsByFrameworkId.set(frameworkId, frameworkSnippets);
|
||||
}
|
||||
}
|
||||
|
||||
snippetsByFrameworkId = snippetsByFrameworkId;
|
||||
}
|
||||
|
||||
importFrameworkSnippets([...frameworkIdsSelected]);
|
||||
}
|
||||
</script>
|
||||
|
||||
<AppNotificationCenter />
|
||||
|
||||
<Header />
|
||||
|
||||
<div class="flex">
|
||||
<Aside />
|
||||
<div class="pb-8 w-full">
|
||||
<div
|
||||
class="flex px-6 lg:px-20 py-2 sticky top-0 z-10 w-full backdrop-blur bg-gray-900/80 border-b border-gray-700 whitespace-nowrap overflow-x-auto"
|
||||
>
|
||||
{#each FRAMEWORKS as framework}
|
||||
<button
|
||||
title={`Display ${framework.title}`}
|
||||
class={c(
|
||||
"text-sm flex-shrink-0 rounded border border-gray-700 px-3 py-1 border-opacity-50 bg-gray-900 hover:bg-gray-800 transition-all mr-2",
|
||||
frameworkIdsSelected.has(framework.id)
|
||||
? "border-blue-500"
|
||||
: "opacity-70"
|
||||
)}
|
||||
on:click={() => toggleFrameworkId(framework.id)}
|
||||
>
|
||||
<FrameworkLabel id={framework.id} size={15} />
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<main id="main-content" class="px-6 md:px-14 lg:px-20 max-w-full pt-10">
|
||||
<div>
|
||||
{#if frameworkIdsSelected.size === 0}
|
||||
<div class="space-y-4">
|
||||
<div class="flex justify-center">
|
||||
<ArrowUpIcon class="w-6 h-6 animate-bounce" />
|
||||
</div>
|
||||
<div class="flex justify-center">
|
||||
<p
|
||||
class="text-lg opacity-80 flex items-center text-center space-x-3"
|
||||
>
|
||||
<img src="/popper.svg" alt="logo" class="w-6 h-6" />
|
||||
<span>
|
||||
Please select a framework to view framework's snippets
|
||||
</span>
|
||||
<img src="/popper.svg" alt="logo" class="w-6 h-6" />
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div
|
||||
class="max-w-full prose prose-sm prose-invert prose-h1:scroll-mt-[5rem] prose-pre:mt-0 prose-h2:scroll-mt-[5rem]"
|
||||
>
|
||||
{#each sections as section}
|
||||
<h1 id={section.sectionId} class="header-anchor">
|
||||
{section.title}
|
||||
<a
|
||||
href={"#" + section.sectionId}
|
||||
aria-hidden="true"
|
||||
tabindex="-1"
|
||||
>
|
||||
#
|
||||
</a>
|
||||
</h1>
|
||||
|
||||
{#each snippets.filter((s) => s.sectionId === section.sectionId) as snippet}
|
||||
<h2 id={snippet.snippetId} class="header-anchor">
|
||||
{snippet.title}
|
||||
<a
|
||||
href={"#" + snippet.snippetId}
|
||||
aria-hidden="true"
|
||||
tabindex="-1"
|
||||
>
|
||||
#
|
||||
</a>
|
||||
</h2>
|
||||
{#if frameworkIdsSelectedStorageInitialized}
|
||||
<div
|
||||
class="grid grid-cols-1 2xl:grid-cols-2 gap-10"
|
||||
style="margin-top: 1rem;"
|
||||
>
|
||||
{#each [...frameworkIdsSelected] as frameworkId (frameworkId)}
|
||||
{@const framework = FRAMEWORKS.find(
|
||||
(f) => f.id === frameworkId
|
||||
)}
|
||||
{@const frameworkSnippet = snippetsByFrameworkId
|
||||
.get(frameworkId)
|
||||
?.find((s) => s.snippetId === snippet.snippetId)}
|
||||
{#if frameworkSnippet}
|
||||
<div style:margin-top="0rem" style:order="0">
|
||||
<div
|
||||
class="flex justify-between items-center space-x-3"
|
||||
>
|
||||
<h3 style="margin-top: 0rem; margin-bottom: 0rem;">
|
||||
<FrameworkLabel id={framework.id} />
|
||||
</h3>
|
||||
<div class="flex items-center space-x-3">
|
||||
{#if frameworkSnippet.playgroundURL}
|
||||
<a
|
||||
href={frameworkSnippet.playgroundURL}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<button
|
||||
class="opacity-50 hover:opacity-100 bg-gray-800 hover:bg-gray-700 py-1 px-1.5 rounded transition-all"
|
||||
title={`Open playground for ${framework.title}`}
|
||||
aria-label={`Open playground for ${framework.title}`}
|
||||
>
|
||||
<PlayIcon class="h-4 w-4" />
|
||||
</button>
|
||||
</a>
|
||||
{/if}
|
||||
<a
|
||||
href={frameworkSnippet.snippetEditHref}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<button
|
||||
class="opacity-50 hover:opacity-100 bg-gray-800 hover:bg-gray-700 py-1 px-1.5 rounded transition-all"
|
||||
title="Edit on Github"
|
||||
aria-label="Edit on Github"
|
||||
>
|
||||
<PencilIcon class="h-4 w-4" />
|
||||
</button>
|
||||
</a>
|
||||
<div>
|
||||
<button
|
||||
class="opacity-50 hover:opacity-100 bg-gray-800 hover:bg-gray-700 py-1 px-1.5 rounded transition-all"
|
||||
title={`Hide ${framework.title} snippets`}
|
||||
aria-label={`Hide ${framework.title} snippets`}
|
||||
on:click={() => hideFrameworkId(framework.id)}
|
||||
>
|
||||
<EyeSlashIcon class="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
{#if frameworkSnippet.markdownFiles.length > 0}
|
||||
<div class="space-y-2">
|
||||
{#each frameworkSnippet.markdownFiles as markdownFile}
|
||||
<div>
|
||||
{@html markdownFile.contentHtml}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{:else if frameworkSnippet.files.length > 0}
|
||||
<CodeEditor files={frameworkSnippet.files} />
|
||||
{:else}
|
||||
<div
|
||||
class="bg-gray-800 text-white rounded-md mx-auto"
|
||||
>
|
||||
<div class="text-center py-8 px-4 sm:px-6">
|
||||
<div>
|
||||
<span
|
||||
class="block text-2xl tracking-tight font-bold"
|
||||
>
|
||||
Missing snippet
|
||||
</span>
|
||||
<span
|
||||
class="block text-lg mt-1 font-semibold"
|
||||
>
|
||||
Help us to improve Component Party 🎉
|
||||
</span>
|
||||
</div>
|
||||
<div class="mt-6 flex justify-center">
|
||||
<div class="inline-flex rounded-md shadow">
|
||||
<a
|
||||
class="inline-flex space-x-2 items-center justify-center px-4 py-2 border border-transparent text-base font-medium rounded-md text-white bg-[#161b22] hover:bg-[#161b22]/80 no-underline"
|
||||
href={frameworkSnippet.snippetEditHref}
|
||||
>
|
||||
<button
|
||||
class="flex items-center space-x-3"
|
||||
>
|
||||
<span>Contribute on Github</span>
|
||||
<GithubIcon class="h-5 w-5" />
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.header-anchor > a {
|
||||
float: left;
|
||||
margin-left: -0.87em;
|
||||
padding-right: 0.23em;
|
||||
font-weight: 500;
|
||||
transition: color 0.25s, opacity 0.25s;
|
||||
opacity: 0;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.header-anchor:hover > a {
|
||||
opacity: 100;
|
||||
}
|
||||
</style>
|
||||
36
src/app.css
Normal file
36
src/app.css
Normal file
@@ -0,0 +1,36 @@
|
||||
:root {
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Mona Sans";
|
||||
src: url("/font/Mona-Sans.woff2") format("woff2 supports variations"),
|
||||
url("/font/Mona-Sans.woff2") format("woff2-variations");
|
||||
font-weight: 200 500;
|
||||
font-stretch: 75% 125%;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
html {
|
||||
font-family: Mona Sans;
|
||||
@apply bg-gray-900;
|
||||
}
|
||||
|
||||
/* #main-content .astro-code {
|
||||
border-top-left-radius: 0px;
|
||||
font-size: 1rem;
|
||||
} */
|
||||
|
||||
@supports (backdrop-filter: blur(10px)) {
|
||||
[class*="bg-"].backdrop-blur {
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
.shiki {
|
||||
@apply text-[1.05rem];
|
||||
}
|
||||
40
src/components/AppNotificationCenter.svelte
Normal file
40
src/components/AppNotificationCenter.svelte
Normal file
@@ -0,0 +1,40 @@
|
||||
<script>
|
||||
import NotificationCenter from "@veljs/svelte/NotificationCenter.svelte";
|
||||
import TransitionWithClass from "@veljs/svelte/TransitionWithClass.svelte";
|
||||
import { CheckCircleIcon, XMarkIcon } from "heroiconsvelte/24/outline";
|
||||
</script>
|
||||
|
||||
<NotificationCenter zIndex={100} let:notification>
|
||||
{@const { title } = notification}
|
||||
<TransitionWithClass
|
||||
class="pointer-events-auto overflow-hidden rounded-lg bg-[#181622] border border-[#33323e] shadow-lg ring-1 ring-black ring-opacity-5"
|
||||
enter="transform ease-out duration-200 transition"
|
||||
enterfrom="translate-y-2 opacity-0 translate-y-0 translate-x-2"
|
||||
enterto="translate-y-0 opacity-100 translate-x-0"
|
||||
leave="transition ease-in duration-100"
|
||||
leavefrom="opacity-100"
|
||||
leaveto="opacity-0"
|
||||
>
|
||||
<div class="p-4">
|
||||
<div class="flex items-start">
|
||||
<div class="flex-shrink-0">
|
||||
<CheckCircleIcon class="h-6 w-6 text-green-400" />
|
||||
</div>
|
||||
<div class="ml-3 w-0 flex-1 pt-0.5">
|
||||
<p class="text-sm font-medium">
|
||||
{title}
|
||||
</p>
|
||||
</div>
|
||||
<div class="ml-4 flex flex-shrink-0">
|
||||
<button
|
||||
class="inline-flex rounded-md bg-transparent focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
|
||||
on:click={() => notification.close()}
|
||||
>
|
||||
<span class="sr-only">Close</span>
|
||||
<XMarkIcon class="h-5 w-5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</TransitionWithClass>
|
||||
</NotificationCenter>
|
||||
34
src/components/Aside.svelte
Normal file
34
src/components/Aside.svelte
Normal file
@@ -0,0 +1,34 @@
|
||||
<script>
|
||||
import { sections, snippets } from "../generatedContent/tree.js";
|
||||
</script>
|
||||
|
||||
<aside
|
||||
class="hidden lg:block sticky flex-shrink-0 w-[300px] overflow-y-auto top-0 pr-8 max-h-screen border-r border-gray-700"
|
||||
>
|
||||
<nav class="font-semibold w-full text-base py-2 pl-4 pb-20">
|
||||
<ul class="space-y-6">
|
||||
{#each sections as section}
|
||||
<li>
|
||||
<a
|
||||
href={`#${section.sectionId}`}
|
||||
class="inline-block w-full py-1.5 px-4 text-white opacity-90 hover:opacity-100 hover:bg-gray-800 rounded transition-opacity"
|
||||
>
|
||||
{section.title}
|
||||
</a>
|
||||
<ul>
|
||||
{#each snippets.filter((s) => s.sectionId === section.sectionId) as snippet}
|
||||
<li>
|
||||
<a
|
||||
href={`#${snippet.snippetId}`}
|
||||
class="inline-block w-full py-1.5 px-4 text-white opacity-50 hover:bg-gray-800 rounded hover:opacity-100 transition-opacity"
|
||||
>
|
||||
{snippet.title}
|
||||
</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</nav>
|
||||
</aside>
|
||||
76
src/components/CodeEditor.svelte
Normal file
76
src/components/CodeEditor.svelte
Normal file
@@ -0,0 +1,76 @@
|
||||
<script>
|
||||
import c from "classnames";
|
||||
import { notifications } from "@veljs/svelte/NotificationCenter.svelte";
|
||||
import { ClipboardDocumentIcon } from "heroiconsvelte/24/outline";
|
||||
|
||||
export let files = [];
|
||||
|
||||
let codeSnippetEl;
|
||||
|
||||
let filenameSelected = files.length > 0 && files[0]?.fileName;
|
||||
$: snippet =
|
||||
filenameSelected && files.find((s) => s.fileName === filenameSelected);
|
||||
|
||||
function copyToClipboard(value) {
|
||||
const $textarea = document.createElement("textarea");
|
||||
$textarea.innerHTML = value;
|
||||
document.body.appendChild($textarea);
|
||||
$textarea.select();
|
||||
let success = false;
|
||||
try {
|
||||
document.execCommand("copy");
|
||||
success = true;
|
||||
} catch (err) {
|
||||
alert(
|
||||
"Oops, unable to copy to clipboard. Please check website permissions."
|
||||
);
|
||||
}
|
||||
$textarea.remove();
|
||||
return success;
|
||||
}
|
||||
|
||||
function copySnippet() {
|
||||
if (codeSnippetEl) {
|
||||
copyToClipboard(codeSnippetEl.innerText);
|
||||
notifications.show({
|
||||
title: "Snippet copied to clipboard",
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex space-x-1 items-center ml-0 overflow-x-auto">
|
||||
{#each files as file (file.fileName)}
|
||||
<button
|
||||
class={c(
|
||||
"bg-[#0d1117] py-1.5 px-3 font-medium flex-shrink-0 text-xs rounded-t inline-block",
|
||||
filenameSelected !== file.fileName && "opacity-60"
|
||||
)}
|
||||
on:click={() => {
|
||||
filenameSelected = file.fileName;
|
||||
}}
|
||||
>
|
||||
{file.fileName}
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<div class="relative group">
|
||||
<div bind:this={codeSnippetEl} class="bg-[#0d1117]">
|
||||
{@html snippet.contentHtml}
|
||||
</div>
|
||||
<div
|
||||
class="absolute hidden group-hover:block transition-all top-0 right-0 mt-2 mr-2"
|
||||
>
|
||||
<div class="flex items-center space-x-3">
|
||||
<button
|
||||
class="px-1.5 bg-[#0d1117] py-1 rounded border opacity-60 hover:opacity-90"
|
||||
title="Copy to clipboard"
|
||||
aria-label="Copy to clipboard"
|
||||
on:click={copySnippet}
|
||||
>
|
||||
<ClipboardDocumentIcon class="h-5 w-5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,24 +0,0 @@
|
||||
import { getHighlighter as getShikiHighlighter } from "shiki";
|
||||
|
||||
// Caches Promise<Highligher> for reuse when the same theme and langs are provided
|
||||
const _resolvedHighlighters = new Map();
|
||||
|
||||
function stringify(opts) {
|
||||
// Always sort keys before stringifying to make sure objects match regardless of parameter ordering
|
||||
return JSON.stringify(opts, Object.keys(opts).sort());
|
||||
}
|
||||
|
||||
export function getHighlighter(opts) {
|
||||
const key = stringify(opts);
|
||||
|
||||
// Highlighter has already been requested, reuse the same instance
|
||||
if (_resolvedHighlighters.has(key)) {
|
||||
return _resolvedHighlighters.get(key);
|
||||
}
|
||||
|
||||
// Start the async getHighlighter call and cache the Promise
|
||||
const highlighter = getShikiHighlighter(opts);
|
||||
_resolvedHighlighters.set(key, highlighter);
|
||||
|
||||
return highlighter;
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
import { getHighlighter } from "@/components/CodeHighlight/Shiki.js";
|
||||
|
||||
export default async function astroHighlightCode({
|
||||
code,
|
||||
lang = "plaintext",
|
||||
theme = "github-dark",
|
||||
wrap = false,
|
||||
}) {
|
||||
/** Replace the shiki class name with a custom astro class name. */
|
||||
function repairShikiTheme(html) {
|
||||
// Replace "shiki" class naming with "astro" and add "is:raw".
|
||||
html = html.replace('<pre class="shiki"', '<pre is:raw class="astro-code"');
|
||||
// Replace "shiki" css variable naming with "astro".
|
||||
html = html.replace(
|
||||
/style="(background-)?color: var\(--shiki-/g,
|
||||
'style="$1color: var(--astro-code-'
|
||||
);
|
||||
// Handle code wrapping
|
||||
// if wrap=null, do nothing.
|
||||
if (wrap === false) {
|
||||
html = html.replace(/style="(.*?)"/, 'style="$1; overflow-x: auto;"');
|
||||
} else if (wrap === true) {
|
||||
html = html.replace(
|
||||
/style="(.*?)"/,
|
||||
'style="$1; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;"'
|
||||
);
|
||||
}
|
||||
return html;
|
||||
}
|
||||
|
||||
const highlighter = await getHighlighter({
|
||||
theme,
|
||||
// Load custom lang if passed an object, otherwise load the default
|
||||
langs: typeof lang !== "string" ? [lang] : undefined,
|
||||
});
|
||||
const _html = highlighter.codeToHtml(code, {
|
||||
lang: typeof lang === "string" ? lang : lang.id,
|
||||
});
|
||||
const html = repairShikiTheme(_html);
|
||||
|
||||
return html;
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
import astroHighlightCode from "@/components/CodeHighlight/astroHighlightCode.js";
|
||||
|
||||
export async function highlightAngularComponent(
|
||||
fileContent,
|
||||
theme,
|
||||
wrap,
|
||||
fileExt
|
||||
) {
|
||||
const templateCode = getAngularTemplateCode(fileContent);
|
||||
|
||||
if (!templateCode) return fileContent;
|
||||
|
||||
const componentWithEmptyTemplate = removeAngularTemplateContent(fileContent);
|
||||
|
||||
let templateCodeHighlighted = await astroHighlightCode({
|
||||
code: templateCode,
|
||||
lang: "html",
|
||||
theme,
|
||||
wrap,
|
||||
});
|
||||
|
||||
let componentHighlighted = await astroHighlightCode({
|
||||
code: componentWithEmptyTemplate,
|
||||
lang: fileExt,
|
||||
theme,
|
||||
wrap,
|
||||
});
|
||||
|
||||
return componentHighlighted.replace(
|
||||
"template",
|
||||
"template: `" + removeCodeWrapper(templateCodeHighlighted) + "`,"
|
||||
);
|
||||
}
|
||||
|
||||
function getAngularTemplateCode(fileContent) {
|
||||
// regex to grab what is inside angular component template inside backticks
|
||||
const regex = /template:\s*`([\s\S]*?)`/gm;
|
||||
|
||||
// grab the template string
|
||||
const template = regex.exec(fileContent);
|
||||
|
||||
if (template) return template[1];
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
function removeAngularTemplateContent(fileContent) {
|
||||
let componentWithoutContentInsideTemplate = fileContent.replace(
|
||||
/template:\s*`([\s\S]*?)([^*])`,?/gm,
|
||||
"template"
|
||||
);
|
||||
|
||||
return componentWithoutContentInsideTemplate;
|
||||
}
|
||||
|
||||
function removeCodeWrapper(html) {
|
||||
let regexForWrapper = /<pre([\s\S]*?)><code>([\s\S]*?)<\/code><\/pre>/gm;
|
||||
let code = regexForWrapper.exec(html);
|
||||
return code[2];
|
||||
}
|
||||
|
||||
export function isAngularComponent(fileContent) {
|
||||
return (
|
||||
fileContent.includes("@angular/core") && fileContent.includes("template")
|
||||
);
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
---
|
||||
import FileCode from "@/components/FileCode.astro";
|
||||
|
||||
const { path, editHref } = Astro.props;
|
||||
---
|
||||
|
||||
<script>
|
||||
function copyToClipboard(value) {
|
||||
const $textarea = document.createElement("textarea");
|
||||
$textarea.innerHTML = value;
|
||||
document.body.appendChild($textarea);
|
||||
$textarea.select();
|
||||
let success = false;
|
||||
try {
|
||||
document.execCommand("copy");
|
||||
success = true;
|
||||
} catch (err) {
|
||||
alert(
|
||||
"Oops, unable to copy to clipboard. Please check website permissions."
|
||||
);
|
||||
}
|
||||
$textarea.remove();
|
||||
return success;
|
||||
}
|
||||
|
||||
for (const $codeCopyContainer of document.querySelectorAll(
|
||||
"[data-code-copy-container]"
|
||||
)) {
|
||||
const $codeCopyText = $codeCopyContainer.querySelector(
|
||||
"[data-code-copy-text]"
|
||||
);
|
||||
const $codeCopyButton = $codeCopyContainer.querySelector(
|
||||
"[data-code-copy-button]"
|
||||
);
|
||||
|
||||
$codeCopyButton.addEventListener("click", () => {
|
||||
copyToClipboard($codeCopyText.innerText);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<div data-code-copy-container class="relative group">
|
||||
<div data-code-copy-text>
|
||||
<FileCode path={path} />
|
||||
</div>
|
||||
<div
|
||||
class="absolute hidden group-hover:block transition-all top-0 right-0 mt-2 mr-2"
|
||||
>
|
||||
<div class="flex items-center space-x-3">
|
||||
{
|
||||
editHref && (
|
||||
<a href={editHref} target="_blank">
|
||||
<button
|
||||
class="px-1.5 bg-[#0d1117] py-1 rounded border opacity-60 hover:opacity-90"
|
||||
title="Edit on Github"
|
||||
aria-label="Edit on Github"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-5 w-5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</a>
|
||||
)
|
||||
}
|
||||
<button
|
||||
data-code-copy-button
|
||||
class="px-1.5 bg-[#0d1117] py-1 rounded border opacity-60 hover:opacity-90"
|
||||
title="Copy to clipboard"
|
||||
aria-label="Copy to clipboard"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-5 w-5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"
|
||||
></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,37 +0,0 @@
|
||||
---
|
||||
import fs from "node:fs/promises";
|
||||
import nodePath from "node:path";
|
||||
import { createFileMapCache } from "micache";
|
||||
|
||||
import astroHighlightCode from "@/components/CodeHighlight/astroHighlightCode.js";
|
||||
import { highlightAngularComponent, isAngularComponent } from "@/components/CodeHighlight/highlightAngularComponent.js";
|
||||
|
||||
const { path, lang, theme = "github-dark", wrap = false } = Astro.props;
|
||||
|
||||
// check existence
|
||||
await fs.access(path);
|
||||
|
||||
const fileCodeCache = await createFileMapCache("fileCodeCache");
|
||||
|
||||
let html = await fileCodeCache.get(path);
|
||||
|
||||
if (!html) {
|
||||
const fileExt = nodePath.parse(path).ext.split(".").pop();
|
||||
const fileContent = await fs.readFile(path, "utf-8");
|
||||
|
||||
if (isAngularComponent(fileContent)) {
|
||||
html = await highlightAngularComponent(fileContent, theme, wrap, lang || fileExt);
|
||||
} else {
|
||||
html = await astroHighlightCode({
|
||||
code: fileContent,
|
||||
lang: lang || fileExt,
|
||||
theme,
|
||||
wrap,
|
||||
});
|
||||
}
|
||||
|
||||
fileCodeCache.set(path, html);
|
||||
}
|
||||
---
|
||||
|
||||
<Fragment set:html={html} />
|
||||
@@ -1,27 +0,0 @@
|
||||
---
|
||||
import FRAMEWORKS from "@/frameworks.mjs";
|
||||
|
||||
const { id, size = 20 } = Astro.props;
|
||||
|
||||
const framework = FRAMEWORKS.find((f) => f.id === id);
|
||||
|
||||
const baseURL =
|
||||
import.meta.env.MODE === "development"
|
||||
? ""
|
||||
: "https://raw.githubusercontent.com/matschik/component-party/main/public/";
|
||||
---
|
||||
|
||||
<div class="flex items-center space-x-1">
|
||||
{
|
||||
framework?.img && (
|
||||
<img
|
||||
src={baseURL + framework.img}
|
||||
alt={framework.id}
|
||||
width={size}
|
||||
height={size}
|
||||
class="inline mr-[5px] mb-0 mt-0"
|
||||
/>
|
||||
)
|
||||
}
|
||||
<span class="flex-shrink-0">{framework.title}</span>
|
||||
</div>
|
||||
24
src/components/FrameworkLabel.svelte
Normal file
24
src/components/FrameworkLabel.svelte
Normal file
@@ -0,0 +1,24 @@
|
||||
<script>
|
||||
import FRAMEWORKS from "../../frameworks.mjs";
|
||||
|
||||
export let id;
|
||||
export let size = 20;
|
||||
|
||||
const framework = FRAMEWORKS.find((f) => f.id === id);
|
||||
|
||||
const baseURL =
|
||||
"https://raw.githubusercontent.com/matschik/component-party/main/public/";
|
||||
</script>
|
||||
|
||||
<div class="flex items-center space-x-1">
|
||||
{#if framework?.img}
|
||||
<img
|
||||
src={baseURL + framework.img}
|
||||
alt={framework.id}
|
||||
width={size}
|
||||
height={size}
|
||||
class="inline mr-[5px] mb-0 mt-0"
|
||||
/>
|
||||
{/if}
|
||||
<span class="flex-shrink-0">{framework.title}</span>
|
||||
</div>
|
||||
12
src/components/GithubIcon.svelte
Normal file
12
src/components/GithubIcon.svelte
Normal file
@@ -0,0 +1,12 @@
|
||||
<svg
|
||||
{...$$props}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden="true"
|
||||
focusable="false"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path
|
||||
d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"
|
||||
/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 885 B |
26
src/components/Header.svelte
Normal file
26
src/components/Header.svelte
Normal file
@@ -0,0 +1,26 @@
|
||||
<script>
|
||||
import GithubIcon from "./GithubIcon.svelte";
|
||||
</script>
|
||||
|
||||
<header class="backdrop-blur bg-gray-900/80 border-b border-gray-700">
|
||||
<div class="px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex justify-between items-center py-3">
|
||||
<a class="font-semibold text-lg flex items-center space-x-3" href="/">
|
||||
<img src="/popper.svg" alt="logo" class="w-5 h-5" />
|
||||
<span>Component party</span>
|
||||
</a>
|
||||
|
||||
<div>
|
||||
<a
|
||||
href="https://github.com/matschik/component-party"
|
||||
title="Contribute on Github"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<GithubIcon class="h-6 w-6" />
|
||||
<span class="sr-only">github</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
@@ -1,270 +0,0 @@
|
||||
---
|
||||
import fs from "node:fs/promises";
|
||||
import kebabCase from "lodash.kebabcase";
|
||||
import Markdown from "@astrojs/markdown-component";
|
||||
import nodePath from "node:path";
|
||||
|
||||
import Title from "@/components/Title.svelte";
|
||||
import FrameworkLabel from "@/components/FrameworkLabel.astro";
|
||||
import FRAMEWORKS from "@/frameworks.mjs";
|
||||
import CodeViewer from "@/components/CodeViewer.astro";
|
||||
import repl from "@/utils/repl";
|
||||
|
||||
const { path: sectionPath } = Astro.props;
|
||||
|
||||
const sectionId = sectionPath.split("/").pop();
|
||||
const sectionRelativePath = sectionPath.split("/").slice(-2).join("/");
|
||||
|
||||
const sections = [];
|
||||
|
||||
function dirNameToTitle(dirName) {
|
||||
return capitalize(dirName.split("-").splice(1).join(" "));
|
||||
}
|
||||
|
||||
function capitalize(string) {
|
||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||
}
|
||||
|
||||
function generatePlaygroundURL(frameworkId, files) {
|
||||
const frameworkREPL = repl[frameworkId];
|
||||
if (!frameworkREPL) {
|
||||
return;
|
||||
}
|
||||
|
||||
const frameworkConfig = FRAMEWORKS.find((f) => f.id === frameworkId);
|
||||
|
||||
const contentByFilename = frameworkConfig
|
||||
.filesSorter(files)
|
||||
.reduce((acc, file) => {
|
||||
acc[file.fileName] = file.content;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const playgroundURL = frameworkREPL.fromContentByFilename(contentByFilename);
|
||||
|
||||
return playgroundURL;
|
||||
}
|
||||
|
||||
const subSectionDirNames = await fs.readdir(sectionPath);
|
||||
|
||||
for (const dirName of subSectionDirNames) {
|
||||
const path = `${sectionPath}/${dirName}`;
|
||||
const title = dirNameToTitle(dirName);
|
||||
|
||||
const frameworkDirs = await fs.readdir(path);
|
||||
const frameworkSections = [];
|
||||
for (const frameworkDir of frameworkDirs) {
|
||||
const frameworkPath = `${path}/${frameworkDir}`;
|
||||
const files = [];
|
||||
const fileNames = await fs.readdir(`${frameworkPath}`);
|
||||
|
||||
for (const fileName of fileNames) {
|
||||
const filePath = `${frameworkPath}/${fileName}`;
|
||||
const ext = nodePath.parse(filePath).ext.split(".").pop();
|
||||
files.push({
|
||||
path: filePath,
|
||||
fileName,
|
||||
ext,
|
||||
content: await fs.readFile(filePath, "utf-8"),
|
||||
});
|
||||
}
|
||||
|
||||
frameworkSections.push({
|
||||
dirName: frameworkDir,
|
||||
path: frameworkPath,
|
||||
files,
|
||||
playgroundURL: generatePlaygroundURL(frameworkDir, files),
|
||||
});
|
||||
}
|
||||
|
||||
sections.push({
|
||||
id: kebabCase(title),
|
||||
path,
|
||||
dirName,
|
||||
title,
|
||||
frameworkSections,
|
||||
});
|
||||
}
|
||||
|
||||
function getSectionFiles(section, framework) {
|
||||
return section.frameworkSections.find((f) => f.dirName === framework.id)
|
||||
?.files;
|
||||
}
|
||||
|
||||
function getSectionMarkdownFiles(section, framework) {
|
||||
return getSectionFiles(section, framework)?.filter((f) => f.ext === "md");
|
||||
}
|
||||
|
||||
function getSectionCodeFiles(section, framework) {
|
||||
const files = getSectionFiles(section, framework)?.filter(
|
||||
(f) => f.ext !== "md"
|
||||
);
|
||||
if (files?.length > 0 && typeof framework.filesSorter === "function") {
|
||||
return framework.filesSorter(files);
|
||||
} else {
|
||||
return files;
|
||||
}
|
||||
}
|
||||
|
||||
function getFrameworkSectionPlaygroundURL(section, framework) {
|
||||
return section.frameworkSections.find((f) => f.dirName === framework.id)
|
||||
?.playgroundURL;
|
||||
}
|
||||
---
|
||||
|
||||
<Title as="h1" content={dirNameToTitle(sectionId)} />
|
||||
|
||||
{
|
||||
sections.map((section) => (
|
||||
<>
|
||||
<Title as="h2" content={section.title} />
|
||||
<div
|
||||
class="grid grid-cols-1 2xl:grid-cols-2 gap-10"
|
||||
style="margin-top: 1rem;"
|
||||
>
|
||||
{FRAMEWORKS.map((framework) => (
|
||||
<div
|
||||
x-bind:style={`'margin-top: 0rem;' + 'order: ' + $store.frameworksSelected.getOrder('${framework.id}') + ';' + 'display:' + ($store.frameworksSelected.has('${framework.id}') ? 'block' : 'none') + ';'`}
|
||||
>
|
||||
<div class="flex justify-between items-center space-x-3">
|
||||
<h3 style="margin-top: 0rem; margin-bottom: 0rem;">
|
||||
<FrameworkLabel id={framework.id} />
|
||||
</h3>
|
||||
<div class="flex items-center space-x-3">
|
||||
{getFrameworkSectionPlaygroundURL(section, framework) && (
|
||||
<a
|
||||
href={getFrameworkSectionPlaygroundURL(section, framework)}
|
||||
target="_blank"
|
||||
>
|
||||
<button
|
||||
class="opacity-50 hover:opacity-100 bg-gray-800 hover:bg-gray-700 py-1 px-1.5 rounded transition-all"
|
||||
title={`Open playground for ${framework.title}`}
|
||||
aria-label={`Open playground for ${framework.title}`}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-4 w-4"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z"
|
||||
/>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</a>
|
||||
)}
|
||||
<button
|
||||
class="opacity-50 hover:opacity-100 bg-gray-800 hover:bg-gray-700 py-1 px-1.5 rounded transition-all"
|
||||
title={`Hide ${framework.title} snippets`}
|
||||
aria-label={`Hide ${framework.title} snippets`}
|
||||
x-on:click={`$store.frameworksSelected.hide('${framework.id}')`}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-4 w-4"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
{getSectionMarkdownFiles(section, framework)?.length > 0 && (
|
||||
<div>
|
||||
{getSectionMarkdownFiles(section, framework).map((file) => (
|
||||
<Markdown set:html={file.content || ""} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{getSectionCodeFiles(section, framework)?.length > 0 && (
|
||||
<div
|
||||
x-data={`{ filenameSelected: '${
|
||||
getSectionCodeFiles(section, framework)[0].fileName
|
||||
}' }`}
|
||||
>
|
||||
<div class="flex space-x-1 items-center ml-0 overflow-x-auto">
|
||||
{getSectionCodeFiles(section, framework).map((file) => (
|
||||
<button
|
||||
class="bg-[#0d1117] py-1.5 px-3 font-medium flex-shrink-0 text-xs rounded-t inline-block"
|
||||
x-bind:class={`filenameSelected === '${file.fileName}' ? '' : 'opacity-60'`}
|
||||
x-on:click={`filenameSelected = '${file.fileName}'`}
|
||||
>
|
||||
{file.fileName}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{getSectionCodeFiles(section, framework).map((file) => (
|
||||
<div x-show={`filenameSelected === '${file.fileName}'`}>
|
||||
<CodeViewer
|
||||
path={file.path}
|
||||
editHref={`https://github.com/matschik/component-party/tree/main/${sectionRelativePath}/${section.dirName}/${framework.id}/${file.fileName}`}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{!getSectionFiles(section, framework)?.length && (
|
||||
<div class="">
|
||||
<div class="bg-gray-800 text-white rounded-md mx-auto">
|
||||
<div class="text-center py-8 px-4 sm:px-6">
|
||||
<div>
|
||||
<span class="block text-2xl tracking-tight font-bold">
|
||||
Missing snippet
|
||||
</span>
|
||||
<span class="block text-lg mt-1 font-semibold">
|
||||
Help us to improve Component Party 🎉
|
||||
</span>
|
||||
</div>
|
||||
<div class="mt-6 flex justify-center">
|
||||
<div class="inline-flex rounded-md shadow">
|
||||
<a
|
||||
href={`https://github.com/matschik/component-party/tree/main/${sectionRelativePath}/${section.dirName}`}
|
||||
class="inline-flex space-x-2 items-center justify-center px-4 py-2 border border-transparent text-base font-medium rounded-md text-white bg-[#161b22] hover:bg-[#161b22]/80 no-underline"
|
||||
>
|
||||
<span>Contribute on Github</span>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden="true"
|
||||
focusable="false"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
class="h-5 w-5 text-white"
|
||||
>
|
||||
<path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12" />
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
))
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
<script>
|
||||
import kebabCase from "lodash.kebabcase";
|
||||
|
||||
export let as = "h1";
|
||||
export let content = "";
|
||||
$: id = kebabCase(content);
|
||||
</script>
|
||||
|
||||
<svelte:element this={as} {id}
|
||||
>{content}<a
|
||||
class="header-anchor"
|
||||
href={"#" + id}
|
||||
aria-hidden="true"
|
||||
tabindex="-1">#</a
|
||||
></svelte:element
|
||||
>
|
||||
@@ -1,89 +0,0 @@
|
||||
import Alpine from "alpinejs";
|
||||
|
||||
Alpine.store("frameworksSelected", {
|
||||
_frameworksSelectedProxy: createLocaleStorageProxy("frameworks_display"),
|
||||
_selectedIds: [],
|
||||
init() {
|
||||
// set default value of frameworksSelectedProxy
|
||||
if (!Object.values(this._frameworksSelectedProxy).length) {
|
||||
const initialFrameworkIds = ["react", "svelte"];
|
||||
for (let i = 0; i < initialFrameworkIds.length; i++) {
|
||||
this._frameworksSelectedProxy[i] = initialFrameworkIds[i];
|
||||
}
|
||||
}
|
||||
this._selectedIds = [...this._frameworksSelectedProxy];
|
||||
},
|
||||
getOrder(fmwId) {
|
||||
return this._selectedIds.indexOf(fmwId) + 1;
|
||||
},
|
||||
has(fmwId) {
|
||||
return this._selectedIds.includes(fmwId);
|
||||
},
|
||||
toggle(fmwId) {
|
||||
this.has(fmwId) ? this.hide(fmwId) : this.show(fmwId);
|
||||
},
|
||||
hide(fmwId) {
|
||||
if (this.has(fmwId)) {
|
||||
const frameworkIndex = this._frameworksSelectedProxy.indexOf(fmwId);
|
||||
delete this._frameworksSelectedProxy[frameworkIndex];
|
||||
}
|
||||
this._selectedIds = [...this._frameworksSelectedProxy];
|
||||
},
|
||||
show(fmwId) {
|
||||
if (!this.has(fmwId)) {
|
||||
this._frameworksSelectedProxy.push(fmwId);
|
||||
}
|
||||
|
||||
this._selectedIds = [...this._frameworksSelectedProxy];
|
||||
},
|
||||
});
|
||||
Alpine.start();
|
||||
|
||||
function createLocaleStorageProxy(key) {
|
||||
const storage = createLocaleStorage(key);
|
||||
|
||||
return new Proxy(storage.getJSON() || [], {
|
||||
get(target, prop) {
|
||||
return target[prop];
|
||||
},
|
||||
set(target, prop, value, receiver) {
|
||||
target[prop] = value;
|
||||
storage.setJSON(receiver);
|
||||
return true;
|
||||
},
|
||||
deleteProperty(target, prop) {
|
||||
target.splice(prop, 1);
|
||||
storage.setJSON(target);
|
||||
return true;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function createLocaleStorage(k) {
|
||||
function get() {
|
||||
return localStorage.getItem(k);
|
||||
}
|
||||
return {
|
||||
get,
|
||||
getJSON() {
|
||||
var value = get();
|
||||
if (value) {
|
||||
try {
|
||||
return JSON.parse(value);
|
||||
} catch (err) {
|
||||
console.error({ getJSONErr: err });
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
},
|
||||
setJSON(o) {
|
||||
this.set(JSON.stringify(o));
|
||||
},
|
||||
set(v) {
|
||||
localStorage.setItem(k, v);
|
||||
},
|
||||
remove() {
|
||||
localStorage.removeItem(k);
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
---
|
||||
import FRAMEWORKS from "@/frameworks.mjs";
|
||||
import BaseLayout from "@/layouts/BaseLayout.svelte";
|
||||
import generateContentTree from "@/utils/generateContentTree.js";
|
||||
import FrameworkLabel from "@/components/FrameworkLabel.astro";
|
||||
|
||||
const tree = await generateContentTree();
|
||||
---
|
||||
|
||||
<BaseLayout tree={tree}>
|
||||
<script src="@/components/scripts/alpine-init.js"></script>
|
||||
<div
|
||||
x-init="() => { $el.classList.remove('hidden');$el.classList.add('block') }"
|
||||
class="hidden"
|
||||
>
|
||||
<div
|
||||
class="flex px-6 lg:px-20 py-2 sticky top-0 z-10 w-full backdrop-blur bg-gray-900/80 border-b border-gray-700 whitespace-nowrap overflow-x-auto"
|
||||
>
|
||||
{
|
||||
FRAMEWORKS.map((framework) => (
|
||||
<button
|
||||
data-framework-button-show={framework.id}
|
||||
title={`Display ${framework.title}`}
|
||||
class="text-sm flex-shrink-0 rounded border border-gray-700 px-3 py-1 border-opacity-50 bg-gray-900 hover:bg-gray-800 transition-all mr-2"
|
||||
x-bind:class={`$store.frameworksSelected.has('${framework.id}') ? 'border-blue-500' : 'opacity-70'`}
|
||||
x-on:click={`$store.frameworksSelected.toggle('${framework.id}')`}
|
||||
>
|
||||
<FrameworkLabel id={framework.id} size={15} />
|
||||
</button>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
|
||||
<main
|
||||
id="main-content"
|
||||
class="px-6 md:px-14 lg:px-20 pt-10 prose prose-sm prose-invert prose-h1:scroll-mt-[5rem] prose-pre:mt-0 prose-h2:scroll-mt-[5rem]"
|
||||
style="max-width: 100%;"
|
||||
>
|
||||
<slot />
|
||||
</main>
|
||||
</div>
|
||||
</BaseLayout>
|
||||
@@ -1,165 +0,0 @@
|
||||
<script>
|
||||
import FRAMEWORKS from "@/frameworks.mjs";
|
||||
|
||||
export let tree = [];
|
||||
</script>
|
||||
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<title>Component Party</title>
|
||||
<link rel="icon" href="/favicon.png" />
|
||||
<link rel="stylesheet" href="https://rsms.me/inter/inter.css" />
|
||||
<meta
|
||||
name="description"
|
||||
content={`Web component JS frameworks overview by their syntax and features: ${FRAMEWORKS.map(
|
||||
(f) => f.title
|
||||
).join(", ")}`}
|
||||
/>
|
||||
<style>
|
||||
* {
|
||||
scrollbar-color: rgba(255, 255, 255, 0.25) rgba(0, 0, 0, 0);
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(255, 255, 255, 0.25);
|
||||
}
|
||||
|
||||
#main-content h1:hover .header-anchor,
|
||||
#main-content h2:hover .header-anchor {
|
||||
opacity: 100;
|
||||
}
|
||||
|
||||
#main-content .astro-code {
|
||||
border-top-left-radius: 0px;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
#main-content .header-anchor {
|
||||
float: left;
|
||||
margin-left: -0.87em;
|
||||
padding-right: 0.23em;
|
||||
font-weight: 500;
|
||||
transition: color 0.25s, opacity 0.25s;
|
||||
opacity: 0;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
@supports (backdrop-filter: blur(10px)) {
|
||||
[class*="bg-"].backdrop-blur {
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<!-- Primary Meta Tags -->
|
||||
<meta name="title" content="Component Party" />
|
||||
<!-- Open Graph / Facebook -->
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="https://component-party.dev/" />
|
||||
<meta property="og:title" content="Component Party" />
|
||||
<meta
|
||||
property="og:description"
|
||||
content="Web component JS frameworks overview by their syntax and features: Svelte, React, Vue 3, Angular"
|
||||
/>
|
||||
<meta
|
||||
property="og:image"
|
||||
content="https://component-party.dev/banner2.png"
|
||||
/>
|
||||
|
||||
<!-- Twitter -->
|
||||
<meta property="twitter:card" content="summary_large_image" />
|
||||
<meta property="twitter:url" content="https://component-party.dev/" />
|
||||
<meta property="twitter:title" content="Component Party" />
|
||||
<meta
|
||||
property="twitter:description"
|
||||
content="Web component JS frameworks overview by their syntax and features: Svelte, React, Vue 3, Angular"
|
||||
/>
|
||||
<meta
|
||||
property="twitter:image"
|
||||
content="https://component-party.dev/banner2.png"
|
||||
/>
|
||||
</head>
|
||||
<body class="bg-gray-900 text-white font-sans">
|
||||
<header class="backdrop-blur bg-gray-900/80 border-b border-gray-700">
|
||||
<div class="px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex justify-between items-center py-3">
|
||||
<a
|
||||
class="font-semibold text-lg flex items-center space-x-3"
|
||||
href="#reactivity"
|
||||
>
|
||||
<img src="/popper.svg" alt="logo" class="w-5 h-5" />
|
||||
<span>Component party</span>
|
||||
</a>
|
||||
|
||||
<div>
|
||||
<a
|
||||
href="https://github.com/matschik/component-party"
|
||||
title="Contribute on Github"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden="true"
|
||||
focusable="false"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
class="h-6 w-6 text-white"
|
||||
>
|
||||
<path
|
||||
d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"
|
||||
/>
|
||||
</svg>
|
||||
<span class="sr-only">github</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div class="flex">
|
||||
<aside
|
||||
class="hidden lg:block sticky flex-shrink-0 w-[300px] overflow-y-auto top-0 pr-12 max-h-screen border-r border-gray-700"
|
||||
>
|
||||
<nav class="font-semibold w-full text-base py-2 pl-4 pb-20">
|
||||
<ul class="space-y-6">
|
||||
{#each tree as treeNode}
|
||||
<li>
|
||||
<a
|
||||
href={`#${treeNode.id}`}
|
||||
class="inline-block w-full py-1.5 px-4 text-white opacity-90 hover:opacity-100 hover:bg-gray-800 rounded transition-opacity"
|
||||
>
|
||||
{treeNode.title}
|
||||
</a>
|
||||
<ul>
|
||||
{#each treeNode.sections as section}
|
||||
<li>
|
||||
<a
|
||||
href={`#${section.id}`}
|
||||
class="inline-block w-full py-1.5 px-4 text-white opacity-50 hover:bg-gray-800 rounded hover:opacity-100 transition-opacity"
|
||||
>
|
||||
{section.title}
|
||||
</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</nav>
|
||||
</aside>
|
||||
<div class="pb-8 w-full">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
28
src/lib/createLocaleStorage.js
Normal file
28
src/lib/createLocaleStorage.js
Normal file
@@ -0,0 +1,28 @@
|
||||
export default function createLocaleStorage(k) {
|
||||
function get() {
|
||||
return localStorage.getItem(k);
|
||||
}
|
||||
return {
|
||||
get,
|
||||
getJSON() {
|
||||
var value = get();
|
||||
if (value) {
|
||||
try {
|
||||
return JSON.parse(value);
|
||||
} catch (err) {
|
||||
console.error({ getJSONErr: err });
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
},
|
||||
setJSON(o) {
|
||||
this.set(JSON.stringify(o));
|
||||
},
|
||||
set(v) {
|
||||
localStorage.setItem(k, v);
|
||||
},
|
||||
remove() {
|
||||
localStorage.removeItem(k);
|
||||
},
|
||||
};
|
||||
}
|
||||
8
src/main.js
Normal file
8
src/main.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import App from "./App.svelte";
|
||||
import "./app.css";
|
||||
|
||||
const app = new App({
|
||||
target: document.getElementById("app"),
|
||||
});
|
||||
|
||||
export default app;
|
||||
@@ -1,21 +0,0 @@
|
||||
---
|
||||
import fs from "node:fs/promises";
|
||||
import { packageDirectory } from "pkg-dir";
|
||||
|
||||
import Layout from "@/layouts/BaseLayout.astro";
|
||||
import Section from "@/components/Section.astro";
|
||||
|
||||
const rootDir = await packageDirectory();
|
||||
const contentURL = `${rootDir}/content`;
|
||||
const sectionNames = await fs.readdir(contentURL);
|
||||
---
|
||||
|
||||
<Layout>
|
||||
<div class="space-y-12">
|
||||
{
|
||||
sectionNames.map((sectionName) => (
|
||||
<Section path={`${contentURL}/${sectionName}`} />
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</Layout>
|
||||
@@ -1,49 +0,0 @@
|
||||
export default function createCache({ expireSec } = {}) {
|
||||
const cache = {};
|
||||
|
||||
function setCache(key, data) {
|
||||
cache[key] = {
|
||||
created: new Date().getTime(),
|
||||
expireSec,
|
||||
data,
|
||||
};
|
||||
}
|
||||
|
||||
function getCache(key) {
|
||||
if (
|
||||
cache[key] &&
|
||||
cache[key].expireSec &&
|
||||
new Date().getTime() - cache[key].created >= cache[key].expireSec * 1000
|
||||
) {
|
||||
deleteKey(key);
|
||||
}
|
||||
|
||||
return cache[key]?.data;
|
||||
}
|
||||
|
||||
function hasKey(key) {
|
||||
return cache.hasOwnProperty(key);
|
||||
}
|
||||
|
||||
function deleteKey(key) {
|
||||
// Remove the reference to the object on object `cache`.
|
||||
// The v8 garbage collector will pick up any objects with zero references for garbage collection.
|
||||
delete cache[key];
|
||||
}
|
||||
|
||||
function cleanKeyStartsWith(keyStart, except) {
|
||||
for (const key in cache) {
|
||||
if (key.startsWith(keyStart) && except && key !== except) {
|
||||
deleteKey(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
set: setCache,
|
||||
get: getCache,
|
||||
has: hasKey,
|
||||
delete: deleteKey,
|
||||
cleanKeyStartsWith,
|
||||
};
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
import fs from "node:fs/promises";
|
||||
import crypto from "crypto";
|
||||
|
||||
import createFsCache from "@/utils/createFsCache.js";
|
||||
|
||||
async function md5(data) {
|
||||
return crypto.createHash("md5").update(data).digest("hex");
|
||||
}
|
||||
|
||||
async function fileExist(path) {
|
||||
try {
|
||||
await fs.access(path);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export default async function createFileMapCache(name) {
|
||||
const cache = await createFsCache(name);
|
||||
|
||||
async function _getPathHash(path) {
|
||||
return md5(path);
|
||||
}
|
||||
|
||||
async function _getCacheKey(path) {
|
||||
const { mtimeMs } = await fs.stat(path);
|
||||
const pathHash = await _getPathHash(path);
|
||||
return `${pathHash}_${mtimeMs}`;
|
||||
}
|
||||
|
||||
async function set(path, data) {
|
||||
if (await fileExist(path)) {
|
||||
const pathHash = await _getPathHash(path);
|
||||
await cache.cleanKeyStartsWith(pathHash);
|
||||
const cacheKey = await _getCacheKey(path);
|
||||
await cache.set(cacheKey, data);
|
||||
}
|
||||
}
|
||||
|
||||
async function has(path) {
|
||||
const cacheKey = await _getCacheKey(path);
|
||||
return await cache.has(cacheKey);
|
||||
}
|
||||
|
||||
async function get(path) {
|
||||
const cacheKey = await _getCacheKey(path);
|
||||
return await cache.get(cacheKey);
|
||||
}
|
||||
|
||||
return {
|
||||
set,
|
||||
get,
|
||||
has,
|
||||
};
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
import fs from "node:fs/promises";
|
||||
import { packageDirectory } from "pkg-dir";
|
||||
|
||||
async function ensureDir(path) {
|
||||
try {
|
||||
await fs.access(path);
|
||||
} catch (err) {
|
||||
if (err.code === "ENOENT") {
|
||||
await fs.mkdir(path).catch(() => {});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default async function createFsCache(name) {
|
||||
const pkgDir = await packageDirectory();
|
||||
const fsCacheDir = `${pkgDir}/node_modules/.fs-cache`;
|
||||
const cacheDir = `${fsCacheDir}/${name}`;
|
||||
await ensureDir(fsCacheDir);
|
||||
await ensureDir(cacheDir);
|
||||
|
||||
function getPath(key) {
|
||||
return `${cacheDir}/${key}`;
|
||||
}
|
||||
|
||||
async function set(key, data) {
|
||||
await fs.writeFile(getPath(key), data);
|
||||
}
|
||||
|
||||
async function get(key) {
|
||||
if (await has(key)) {
|
||||
return await fs.readFile(getPath(key), "utf8");
|
||||
}
|
||||
}
|
||||
|
||||
async function has(key) {
|
||||
try {
|
||||
await fs.access(getPath(key));
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async function deleteKey(key) {
|
||||
if (await has(key)) {
|
||||
return await fs.deleteFile(getPath(key));
|
||||
}
|
||||
}
|
||||
|
||||
async function cleanKeyStartsWith(keyStart, except) {
|
||||
const files = await fs.readdir(cacheDir);
|
||||
for (const file of files) {
|
||||
if (file.startsWith(keyStart) && except && key !== except) {
|
||||
deleteKey(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
set,
|
||||
get,
|
||||
has,
|
||||
delete: deleteKey,
|
||||
cleanKeyStartsWith,
|
||||
};
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
import fs from "node:fs/promises";
|
||||
import path from "path";
|
||||
|
||||
const CONTENT_DIR = path.resolve("content");
|
||||
|
||||
export default async function generateContentTree() {
|
||||
const tree = [];
|
||||
|
||||
const contentDirs = await fs.readdir(CONTENT_DIR);
|
||||
|
||||
for (const contentDir of contentDirs) {
|
||||
const sectionDir = `${CONTENT_DIR}/${contentDir}`;
|
||||
const subSectionDirs = (await fs.readdir(sectionDir)).filter(
|
||||
(path) => !path.includes(".")
|
||||
);
|
||||
const contentDirTitle = dirNameToTitle(contentDir);
|
||||
const treeNode = {
|
||||
id: contentDir.split("-").splice(1).join("-"),
|
||||
title: contentDirTitle,
|
||||
sections: [],
|
||||
};
|
||||
|
||||
for (const subSectionDir of subSectionDirs) {
|
||||
const subSectionDirTitle = dirNameToTitle(subSectionDir);
|
||||
treeNode.sections.push({
|
||||
id: subSectionDir.split("-").splice(1).join("-"),
|
||||
title: subSectionDirTitle,
|
||||
});
|
||||
}
|
||||
|
||||
tree.push(treeNode);
|
||||
}
|
||||
|
||||
return tree;
|
||||
}
|
||||
|
||||
function dirNameToTitle(dirName) {
|
||||
return capitalize(dirName.split("-").splice(1).join(" "));
|
||||
}
|
||||
|
||||
function capitalize(string) {
|
||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
import createAlpineREPL from "./createAlpineREPL";
|
||||
import createSvelteREPL from "./createSvelteREPL";
|
||||
import createVue3REPL from "./createVue3REPL";
|
||||
import createSolidREPL from "./createSolidREPL";
|
||||
|
||||
export default {
|
||||
vue3: createVue3REPL(),
|
||||
svelte: createSvelteREPL(),
|
||||
alpine: createAlpineREPL(),
|
||||
solid: createSolidREPL(),
|
||||
};
|
||||
@@ -1,13 +1,4 @@
|
||||
const defaultTheme = require("tailwindcss/defaultTheme");
|
||||
|
||||
module.exports = {
|
||||
content: ["./src/**/*.{astro,html,js,jsx,svelte,ts,tsx,vue}"],
|
||||
theme: {
|
||||
extend: {
|
||||
fontFamily: {
|
||||
sans: ["Inter var", ...defaultTheme.fontFamily.sans],
|
||||
},
|
||||
},
|
||||
},
|
||||
content: ["./src/**/*.{html,js,jsx,svelte,ts,tsx,vue}", "./index.html"],
|
||||
plugins: [require("@tailwindcss/typography")],
|
||||
};
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./out-tsc/app",
|
||||
"types": []
|
||||
},
|
||||
"files": ["src/main.ts", "src/polyfills.ts"],
|
||||
"include": ["src/**/*.d.ts"]
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"compileOnSave": false,
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"baseUrl": "./",
|
||||
"outDir": "./dist/out-tsc",
|
||||
"sourceMap": true,
|
||||
"declaration": false,
|
||||
"downlevelIteration": true,
|
||||
"experimentalDecorators": true,
|
||||
"moduleResolution": "node",
|
||||
"importHelpers": true,
|
||||
"target": "es2015",
|
||||
"module": "es2020",
|
||||
"lib": ["es2018", "dom"],
|
||||
"paths": {
|
||||
"@/*": ["src/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./out-tsc/spec",
|
||||
"types": ["jasmine"]
|
||||
},
|
||||
"files": ["src/test.ts", "src/polyfills.ts"],
|
||||
"include": ["src/**/*.spec.ts", "src/**/*.d.ts"]
|
||||
}
|
||||
28
vite.config.js
Normal file
28
vite.config.js
Normal file
@@ -0,0 +1,28 @@
|
||||
import { defineConfig } from "vite";
|
||||
import { svelte } from "@sveltejs/vite-plugin-svelte";
|
||||
import sveltePreprocess from "svelte-preprocess";
|
||||
import { createHtmlPlugin } from "vite-plugin-html";
|
||||
import FRAMEWORKS from "./frameworks.mjs";
|
||||
import pluginGenerateFrameworkContent from "./build/generateContentVitePlugin.js";
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
pluginGenerateFrameworkContent(),
|
||||
svelte(),
|
||||
createHtmlPlugin({
|
||||
minify: true,
|
||||
inject: {
|
||||
data: {
|
||||
frameworkList: FRAMEWORKS.map((f) => f.title).join(", "),
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
ignore: ["content"],
|
||||
preprocess: [
|
||||
sveltePreprocess({
|
||||
postcss: true,
|
||||
}),
|
||||
],
|
||||
});
|
||||
Reference in New Issue
Block a user