feat(app): add spa router to handle versus framework pages

This commit is contained in:
Mathieu Schimmerling
2024-03-04 16:49:36 +01:00
parent 7037e50e4b
commit 8a3234f436
7 changed files with 528 additions and 414 deletions

View File

@@ -1,4 +1,4 @@
<footer class="bg-gray-900">
<footer class="bg-gray-900 pb-20">
<h2 id="footer-heading" class="sr-only">Footer</h2>
<div class="mx-auto max-w-7xl px-6 pb-8 pt-16 sm:pt-24 lg:px-8 lg:pt-32">
<div class="xl:grid xl:grid-cols-3 xl:gap-8">
@@ -20,7 +20,7 @@
</a>
</div>
</div>
<div class="mt-16 grid grid-cols-2 gap-8 xl:col-span-2 xl:mt-0">
<div class="mt-16 grid grid-cols-1 sm:grid-cols-2 gap-8 xl:col-span-2 xl:mt-0">
<% for (const navigation of it.navigations) { %>
<div class="md:grid md:gap-8">
<aside>
@@ -28,7 +28,7 @@
<%= navigation.title %>
</h3>
<nav>
<ul role="list" class="mt-6 space-y-2">
<ul role="list" class="mt-2 space-y-2">
<% for (const link of navigation.links) { %>
<li>
<a href="<%= link.url %>" class="text-sm leading-6 text-gray-300 hover:text-white">
@@ -43,10 +43,5 @@
<% } %>
</div>
</div>
<div class="mt-16 border-t border-white/10 pt-8 sm:mt-20 lg:mt-24">
<p class="text-xs leading-5 text-gray-400">
&copy; 2020 Your Company, Inc. All rights reserved.
</p>
</div>
</div>
</footer>

View File

@@ -8,11 +8,12 @@ function sortAllFilenames(files, filenamesSorted) {
].filter(Boolean);
}
export default [
const frameworks = [
{
id: "svelte4",
title: "Svelte 4",
frameworkName: "Svelte",
isCurrentVersion: true,
img: "framework/svelte.svg",
eslint: {
files: ["**/svelte4/*.svelte"],
@@ -30,6 +31,7 @@ export default [
id: "react",
title: "React",
frameworkName: "React",
isCurrentVersion: true,
img: "framework/react.svg",
eslint: {
files: ["**/react/*.jsx", "**/react/*.tsx"],
@@ -56,6 +58,7 @@ export default [
id: "vue3",
title: "Vue 3",
frameworkName: "Vue",
isCurrentVersion: true,
img: "framework/vue.svg",
eslint: {
files: ["**/vue3/*.vue"],
@@ -80,6 +83,7 @@ export default [
id: "angular",
title: "Angular",
frameworkName: "Angular",
isCurrentVersion: true,
img: "framework/angular.svg",
eslint: [
{
@@ -136,6 +140,7 @@ export default [
id: "lit",
title: "Lit",
frameworkName: "Lit",
isCurrentVersion: true,
img: "framework/lit.svg",
eslint: {
files: ["**/lit/**"],
@@ -155,6 +160,7 @@ export default [
id: "vue2",
title: "Vue 2",
frameworkName: "Vue",
isCurrentVersion: false,
img: "framework/vue.svg",
eslint: {
files: ["**/vue2/*.vue"],
@@ -176,6 +182,7 @@ export default [
id: "ember",
title: "Ember",
frameworkName: "Ember",
isCurrentVersion: true,
img: "framework/ember.svg",
eslint: {
files: ["**/ember/**"],
@@ -195,6 +202,7 @@ export default [
id: "solid",
title: "Solid.js",
frameworkName: "Solid",
isCurrentVersion: true,
img: "framework/solid.svg",
eslint: {
files: ["**/solid/*.jsx"],
@@ -213,6 +221,7 @@ export default [
id: "alpine",
title: "Alpine",
frameworkName: "Alpine",
isCurrentVersion: true,
img: "framework/alpine.svg",
eslint: {
files: ["**/alpine/**"],
@@ -230,6 +239,7 @@ export default [
id: "mithril",
title: "Mithril",
frameworkName: "Mithril",
isCurrentVersion: true,
img: "framework/mithril.svg",
eslint: {
env: {
@@ -252,6 +262,7 @@ export default [
id: "aurelia2",
title: "Aurelia 2",
frameworkName: "Aurelia",
isCurrentVersion: true,
img: "framework/aurelia.svg",
eslint: {
env: {
@@ -281,6 +292,7 @@ export default [
id: "qwik",
title: "Qwik",
frameworkName: "Qwik",
isCurrentVersion: true,
img: "framework/qwik.svg",
eslint: {
env: {
@@ -312,6 +324,7 @@ export default [
id: "marko",
title: "Marko",
frameworkName: "Marko",
isCurrentVersion: true,
img: "framework/marko.svg",
eslint: {
files: ["!**"], // Markos linter/prettyprinter doesnt use eslint
@@ -328,6 +341,7 @@ export default [
id: "aurelia1",
title: "Aurelia 1",
frameworkName: "Aurelia",
isCurrentVersion: false,
img: "framework/aurelia.svg",
eslint: {
env: {
@@ -356,6 +370,7 @@ export default [
id: "svelte5",
title: "Svelte 5 (preview)",
frameworkName: "Svelte",
isCurrentVersion: false,
img: "framework/svelte.svg",
eslint: {
files: ["**/TODO-THIS-IS-DISABLED-svelte5/*.svelte"],
@@ -370,3 +385,12 @@ export default [
mainPackageName: "svelte",
},
];
export function matchFrameworkId(id){
return frameworks.find((framework) =>
framework.id === id ||
(framework.isCurrentVersion &&
framework.frameworkName.toLowerCase() === id))
}
export default frameworks;

View File

@@ -22,7 +22,8 @@
"@veljs/svelte": "^0.1.11",
"classnames": "^2.5.1",
"eslint-plugin-svelte": "^2.35.1",
"heroiconsvelte": "^1.0.2"
"heroiconsvelte": "^1.0.2",
"svelte-router-spa": "^7.0.1"
},
"devDependencies": {
"@angular-eslint/eslint-plugin": "^17.2.1",

16
pnpm-lock.yaml generated
View File

@@ -20,6 +20,9 @@ dependencies:
heroiconsvelte:
specifier: ^1.0.2
version: 1.0.2(svelte@4.2.12)
svelte-router-spa:
specifier: ^7.0.1
version: 7.0.1(svelte@4.2.12)
devDependencies:
'@angular-eslint/eslint-plugin':
@@ -6606,6 +6609,15 @@ packages:
typescript: 5.3.3
dev: true
/svelte-router-spa@7.0.1(svelte@4.2.12):
resolution: {integrity: sha512-Oh1bVBkK4UYSXzBSrVa8kTRrPtkc9+C7TSVlUkoFXyAf4dQtRrGXqPbXGU0lR5MJ8VkwXU68XclGtpCeWm5gpg==}
peerDependencies:
svelte: ^3.36.0
dependencies:
svelte: 4.2.12
url-params-parser: 1.0.4
dev: false
/svelte@3.59.2:
resolution: {integrity: sha512-vzSyuGr3eEoAtT/A6bmajosJZIUWySzY2CzB3w2pgPvnkUjGqlDnsNnA0PMO+mMAhuyMul6C2uuZzY6ELSkzyA==}
engines: {node: '>= 8'}
@@ -7037,6 +7049,10 @@ packages:
dependencies:
punycode: 2.3.1
/url-params-parser@1.0.4:
resolution: {integrity: sha512-0m6BqGpY2OetTZ3UPTLKkbTfUHigsX2YhrzORT9iYiyUJ/SP2WJ3cggg2YWtvMs36GPwK9Q44ffddyarniu2Tg==}
dev: false
/url-parse-lax@1.0.0:
resolution: {integrity: sha512-BVA4lR5PIviy2PMseNd2jbFQ+jwSwQGdJejf5ctd1rEXt0Ypd7yanUK9+lYechVlN5VaTJGsu2U/3MDDu6KgBA==}
engines: {node: '>=0.10.0'}

View File

@@ -1,399 +1,18 @@
<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 { 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 frameworkIdsStorage = createLocaleStorage("framework_display");
function removeSearchParamKeyFromURL(k) {
// Get the current search params as an object
const searchParams = new URLSearchParams(window.location.search);
if (!searchParams.has(k)) {
// The key doesn't exist, so don't do anything
return;
}
// Remove the parameter you want to remove
searchParams.delete(k);
let newUrl = window.location.pathname;
if (searchParams.toString().length > 0) {
// There are still search params, so include the `?` character
newUrl += `?${searchParams}`;
}
// Update the URL without reloading the page
history.replaceState({}, "", newUrl);
}
let frameworkIdsSelected = new Set();
let snippetsByFrameworkId = new Map();
let frameworkIdsSelectedStorageInitialized = false;
const frameworkIdsFromURLKey = "f";
onMount(function handleInitialFrameworkIdsSelected() {
let frameworkIdsSelectedOnInit = [];
const url = new URL(window.location.href);
const frameworkIdsFromURLStr = url.searchParams.get(frameworkIdsFromURLKey);
if (frameworkIdsFromURLStr) {
const frameworkIdsFromURL = frameworkIdsFromURLStr
.split(",")
.filter((fId) => FRAMEWORKS.find((framework) => framework.id === fId));
if (frameworkIdsFromURL.length > 0) {
frameworkIdsSelectedOnInit = frameworkIdsFromURL;
} else {
removeSearchParamKeyFromURL(frameworkIdsFromURLKey);
}
}
if (frameworkIdsSelectedOnInit.length === 0) {
const frameworkIdsFromStorage = frameworkIdsStorage.getJSON();
if (frameworkIdsFromStorage?.length > 0) {
frameworkIdsSelectedOnInit = frameworkIdsFromStorage.map((x) =>
x === "svelte" ? "svelte4" : x
);
}
}
if (frameworkIdsSelectedOnInit.length === 0) {
frameworkIdsSelectedOnInit = ["svelte4", "react"];
}
frameworkIdsSelected = new Set(frameworkIdsSelectedOnInit);
frameworkIdsSelectedStorageInitialized = true;
});
function saveFrameworkIdsSelectedOnStorage() {
frameworkIdsStorage.setJSON([...frameworkIdsSelected]);
removeSearchParamKeyFromURL(frameworkIdsFromURLKey);
}
function toggleFrameworkId(frameworkId) {
if (frameworkIdsSelected.has(frameworkId)) {
frameworkIdsSelected.delete(frameworkId);
} else {
frameworkIdsSelected.add(frameworkId);
}
frameworkIdsSelected = frameworkIdsSelected;
saveFrameworkIdsSelectedOnStorage();
}
$: {
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]);
}
const MAX_FRAMEWORK_NB_INITIAL_DISPLAYED = 10;
const FRAMEWORKS_INITIAL_DISPLAYED = FRAMEWORKS.slice(
0,
MAX_FRAMEWORK_NB_INITIAL_DISPLAYED
);
const FRAMEWORKS_MORE = FRAMEWORKS.slice(MAX_FRAMEWORK_NB_INITIAL_DISPLAYED);
$: frameworksSelected = [...frameworkIdsSelected].map((id) =>
FRAMEWORKS.find((f) => f.id === id)
);
$: frameworksNotSelected = FRAMEWORKS_INITIAL_DISPLAYED.filter(
(f) => !frameworkIdsSelected.has(f.id)
);
$: frameworksMoreNotSelected = FRAMEWORKS_MORE.filter(
(f) => !frameworkIdsSelected.has(f.id)
);
let showBonusFrameworks = false;
import { Router } from "svelte-router-spa";
import Index from "./Index.svelte";
const routes = [
{
name: "/",
component: Index,
},
{ name: "/compare/:versus", component: Index },
{
name: "404",
path: "404",
component: Index,
},
];
</script>
<AppNotificationCenter />
<Header {frameworksSelected} />
<div class="flex border-b border-gray-700">
<Aside />
<div class="pb-8 w-10 grow">
<div
class="flex px-6 lg:px-20 py-2 sticky top-0 z-20 w-full backdrop-blur bg-gray-900/80 border-b border-gray-700 whitespace-nowrap overflow-x-auto"
>
{#each [...frameworksSelected, ...frameworksNotSelected] as framework (framework.id)}
<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}
{#if showBonusFrameworks}
{#each frameworksMoreNotSelected as framework (framework.id)}
<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}
{:else if frameworksMoreNotSelected.length > 0}
<button
title="more"
class="opacity-70 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"
on:click={() => {
showBonusFrameworks = !showBonusFrameworks;
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="size-4"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M6.75 12a.75.75 0 11-1.5 0 .75.75 0 011.5 0zM12.75 12a.75.75 0 11-1.5 0 .75.75 0 011.5 0zM18.75 12a.75.75 0 11-1.5 0 .75.75 0 011.5 0z"
/>
</svg>
</button>
{/if}
</div>
<main class="relative pt-10">
<div>
{#if frameworkIdsSelected.size === 0}
<div class="space-y-4">
<div class="flex justify-center">
<ArrowUpIcon class="size-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="size-6" />
<span>
Please select a framework to view framework's snippets
</span>
<img src="/popper.svg" alt="logo" class="size-6" />
</p>
</div>
</div>
{:else}
<div
class="max-w-full prose prose-sm prose-invert prose-h1:font-semibold prose-h2:font-medium prose-h3:font-medium prose-h1:scroll-mt-[5rem] prose-pre:mt-0 prose-h2:scroll-mt-[5rem]"
>
{#each sections as section}
<div class="px-6 md:px-14 lg:px-20 max-w-full">
<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}
<div
id={section.sectionId + "." + snippet.snippetId}
data-snippet-id={section.sectionId +
"." +
snippet.snippetId}
>
<h2
class="header-anchor sticky py-2 top-[2.9531rem] z-10 bg-[var(--bg-color)]"
>
{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-x-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>
</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 space-x-1"
>
<span>
Help us to improve Component Party
</span>
<img
src="/popper.svg"
alt="logo"
class="size-5 m-0 inline-block"
/>
</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}
</div>
{/each}
</div>
{/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>
<Router {routes} />

453
src/Index.svelte Normal file
View File

@@ -0,0 +1,453 @@
<script>
import FRAMEWORKS, { matchFrameworkId } 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 { 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";
import { navigateTo } from "svelte-router-spa";
export let currentRoute;
const frameworkIdsStorage = createLocaleStorage("framework_display");
function removeSearchParamKeyFromURL(k) {
// Get the current search params as an object
const searchParams = new URLSearchParams(window.location.search);
if (!searchParams.has(k)) {
// The key doesn't exist, so don't do anything
return;
}
// Remove the parameter you want to remove
searchParams.delete(k);
let newUrl = window.location.pathname;
if (searchParams.toString().length > 0) {
// There are still search params, so include the `?` character
newUrl += `?${searchParams}`;
}
// Update the URL without reloading the page
history.replaceState({}, "", newUrl);
}
const frameworkIdsFromURLKey = "f";
let frameworkIdsSelected = new Set();
let snippetsByFrameworkId = new Map();
let frameworkIdsSelectedInitialized = false;
let isVersusFrameworks = false;
let onMountCallbacks = new Set();
let isMounted = false;
// -- on route change --
$: {
window.scrollTo(0, 0);
isVersusFrameworks = false;
if (currentRoute.name === "/") {
if (isMounted) {
handleInitialFrameworkIdsSelectedFromStorage();
} else {
onMountCallbacks.add(handleInitialFrameworkIdsSelectedFromStorage);
}
} else if (currentRoute.namedParams?.versus) {
const versusFrameworks = handleVersus(currentRoute.namedParams.versus);
if (versusFrameworks) {
isVersusFrameworks = true;
frameworkIdsSelected = new Set(versusFrameworks.map((f) => f.id));
frameworkIdsSelectedInitialized = true;
} else {
navigateTo("/");
}
} else {
navigateTo("/");
}
}
function handleInitialFrameworkIdsSelectedFromStorage() {
let frameworkIdsSelectedOnInit = [];
const url = new URL(window.location.href);
const frameworkIdsFromURLStr = url.searchParams.get(frameworkIdsFromURLKey);
if (frameworkIdsFromURLStr) {
const frameworkIdsFromURL = frameworkIdsFromURLStr
.split(",")
.filter(matchFrameworkId);
if (frameworkIdsFromURL.length > 0) {
frameworkIdsSelectedOnInit = frameworkIdsFromURL;
} else {
removeSearchParamKeyFromURL(frameworkIdsFromURLKey);
}
}
if (frameworkIdsSelectedOnInit.length === 0) {
const frameworkIdsFromStorage = frameworkIdsStorage.getJSON();
if (frameworkIdsFromStorage?.length > 0) {
frameworkIdsSelectedOnInit = frameworkIdsFromStorage.map((x) =>
x === "svelte" ? "svelte4" : x
);
}
}
if (frameworkIdsSelectedOnInit.length === 0) {
frameworkIdsSelectedOnInit = ["react", "svelte4"];
}
frameworkIdsSelected = new Set(frameworkIdsSelectedOnInit);
frameworkIdsSelectedInitialized = true;
}
onMount(() => {
isMounted = true;
for (const callback of onMountCallbacks) {
callback();
}
onMountCallbacks.clear();
onMountCallbacks = onMountCallbacks;
});
function saveFrameworkIdsSelectedOnStorage() {
frameworkIdsStorage.setJSON([...frameworkIdsSelected]);
removeSearchParamKeyFromURL(frameworkIdsFromURLKey);
}
function toggleFrameworkId(frameworkId) {
if (frameworkIdsSelected.has(frameworkId)) {
frameworkIdsSelected.delete(frameworkId);
} else {
frameworkIdsSelected.add(frameworkId);
}
frameworkIdsSelected = frameworkIdsSelected;
saveFrameworkIdsSelectedOnStorage();
}
$: {
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]);
}
const MAX_FRAMEWORK_NB_INITIAL_DISPLAYED = 10;
const FRAMEWORKS_INITIAL_DISPLAYED = FRAMEWORKS.slice(
0,
MAX_FRAMEWORK_NB_INITIAL_DISPLAYED
);
const FRAMEWORKS_MORE = FRAMEWORKS.slice(MAX_FRAMEWORK_NB_INITIAL_DISPLAYED);
$: frameworksSelected = [...frameworkIdsSelected].map(matchFrameworkId);
$: frameworksNotSelected = FRAMEWORKS_INITIAL_DISPLAYED.filter(
(f) => !frameworkIdsSelected.has(f.id)
);
$: frameworksMoreNotSelected = FRAMEWORKS_MORE.filter(
(f) => !frameworkIdsSelected.has(f.id)
);
let showBonusFrameworks = false;
function handleVersus(versus) {
const fids = versus.split("-vs-");
if (fids.length !== 2) {
return;
}
const frameworks = fids.map(matchFrameworkId);
if (frameworks.some((f) => !f)) {
return;
}
return frameworks;
}
</script>
<AppNotificationCenter />
<Header {frameworksSelected} />
<div class="flex border-b border-gray-700">
<Aside />
<div class="pb-8 w-10 grow">
<div
class="flex px-6 lg:px-20 py-2 sticky top-0 z-20 w-full backdrop-blur bg-gray-900/80 border-b border-gray-700 whitespace-nowrap overflow-x-auto"
>
{#each [...frameworksSelected, ...frameworksNotSelected] as framework (framework.id)}
<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);
if (isVersusFrameworks && currentRoute.name !== "/") {
navigateTo("/");
}
}}
>
<FrameworkLabel id={framework.id} size={15} />
</button>
{/each}
{#if showBonusFrameworks}
{#each frameworksMoreNotSelected as framework (framework.id)}
<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}
{:else if frameworksMoreNotSelected.length > 0}
<button
title="more"
class="opacity-70 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"
on:click={() => {
showBonusFrameworks = !showBonusFrameworks;
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="size-4"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M6.75 12a.75.75 0 11-1.5 0 .75.75 0 011.5 0zM12.75 12a.75.75 0 11-1.5 0 .75.75 0 011.5 0zM18.75 12a.75.75 0 11-1.5 0 .75.75 0 011.5 0z"
/>
</svg>
</button>
{/if}
</div>
<main class="relative pt-10">
<div>
{#if frameworkIdsSelected.size === 0}
<div class="space-y-4">
<div class="flex justify-center">
<ArrowUpIcon class="size-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="size-6" />
<span>
Please select a framework to view framework's snippets
</span>
<img src="/popper.svg" alt="logo" class="size-6" />
</p>
</div>
</div>
{:else}
<div
class="max-w-full prose prose-sm prose-invert prose-h1:font-semibold prose-h2:font-medium prose-h3:font-medium prose-h1:scroll-mt-[5rem] prose-pre:mt-0 prose-h2:scroll-mt-[5rem]"
>
{#each sections as section}
<div class="px-6 md:px-14 lg:px-20 max-w-full">
<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}
<div
id={section.sectionId + "." + snippet.snippetId}
data-snippet-id={section.sectionId +
"." +
snippet.snippetId}
>
<h2
class="header-anchor sticky py-2 top-[2.9531rem] z-10 bg-[var(--bg-color)]"
>
{snippet.title}
<a
href={"#" + snippet.snippetId}
aria-hidden="true"
tabindex="-1"
>
#
</a>
</h2>
{#if frameworkIdsSelectedInitialized}
<div
class="grid grid-cols-1 2xl:grid-cols-2 gap-x-10"
style="margin-top: 1rem;"
>
{#each [...frameworkIdsSelected] as frameworkId (frameworkId)}
{@const framework = matchFrameworkId(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>
</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 space-x-1"
>
<span>
Help us to improve Component Party
</span>
<img
src="/popper.svg"
alt="logo"
class="size-5 m-0 inline-block"
/>
</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}
</div>
{/each}
</div>
{/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>

View File

@@ -7,6 +7,8 @@ import { Eta } from "eta";
import FRAMEWORKS from "./frameworks.mjs";
import pluginGenerateFrameworkContent from "./build/generateContentVitePlugin.js";
// @TODO: sitemap
const footerNavigation = [
{
title: "Most Popular Frameworks",
@@ -33,24 +35,24 @@ const footerNavigation = [
{
title: "Comparing Legacy version & Current Version",
links: [
{ name: "Vue 2 vs. Vue 3", url: "/compare/vue-2-vs-vue-3" },
{ name: "Vue 2 vs. Vue 3", url: "/compare/vue2-vs-vue3" },
{
name: "Aurelia 1 vs. Aurelia 2",
url: "/compare/aurelia-1-vs-aurelia-2",
url: "/compare/aurelia1-vs-aurelia2",
},
],
},
{
title: "Comparing Current Version & Upcoming Version",
links: [
{ name: "Svelte 4 vs. Svelte 5", url: "/compare/svelte-4-vs-svelte-5" },
{ name: "Svelte 4 vs. Svelte 5", url: "/compare/svelte4-vs-svelte5" },
],
},
];
const footerLinks = footerNavigation.map((n) => n.links).flat();
const sharedTemplateData = {
const templateDataDefaults = {
title: "Component Party",
url: "https://component-party.dev/",
description: `Web component JS frameworks overview by their syntax and features: ${FRAMEWORKS.map((f) => f.title).join(", ")}`,
@@ -67,14 +69,14 @@ export default defineConfig({
outputPath: `${link.url}.html`,
template: "dist/index.html",
templateData: {
...sharedTemplateData,
title: `${link.name} - ${sharedTemplateData.title}`,
...templateDataDefaults,
title: `${link.name} - ${templateDataDefaults.title}`,
},
})),
{
outputPath: "index.html",
template: "dist/index.html",
templateData: sharedTemplateData,
templateData: templateDataDefaults,
},
]),
],
@@ -119,7 +121,11 @@ async function generateHtmlPagesPlugin(pages) {
const matchedPage = pages.find(
(page) => ctx.originalUrl === filePathToUrl(page.outputPath)
);
html = htmlTransform.render(html, matchedPage.templateData);
if (matchedPage) {
html = htmlTransform.render(html, matchedPage.templateData);
} else {
html = htmlTransform.render(html, templateDataDefaults);
}
}
return html;
},