소스 검색

Introduce blog post tab

Posts previews are automatically populated from social data from labs public URLs
Alessandro Pignotti 8 달 전
부모
커밋
529f720ae1
7개의 변경된 파일205개의 추가작업 그리고 0개의 파일을 삭제
  1. 133 0
      package-lock.json
  2. 1 0
      package.json
  3. 1 0
      postcss.config.js
  4. 9 0
      src/lib/BlogPost.svelte
  5. 14 0
      src/lib/PostsTab.svelte
  6. 4 0
      src/lib/SideBar.svelte
  7. 43 0
      src/routes/+layout.server.js

+ 133 - 0
package-lock.json

@@ -20,6 +20,7 @@
 				"@xterm/xterm": "^5.5.0",
 				"@xterm/xterm": "^5.5.0",
 				"autoprefixer": "^10.4.20",
 				"autoprefixer": "^10.4.20",
 				"labs": "git@github.com:leaningtech/labs.git",
 				"labs": "git@github.com:leaningtech/labs.git",
+				"node-html-parser": "^6.1.13",
 				"postcss": "^8.4.47",
 				"postcss": "^8.4.47",
 				"postcss-discard": "^2.0.0",
 				"postcss-discard": "^2.0.0",
 				"svelte": "^4.2.7",
 				"svelte": "^4.2.7",
@@ -1028,6 +1029,12 @@
 				"url": "https://github.com/sponsors/sindresorhus"
 				"url": "https://github.com/sponsors/sindresorhus"
 			}
 			}
 		},
 		},
+		"node_modules/boolbase": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
+			"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
+			"dev": true
+		},
 		"node_modules/brace-expansion": {
 		"node_modules/brace-expansion": {
 			"version": "2.0.1",
 			"version": "2.0.1",
 			"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
 			"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
@@ -1221,6 +1228,22 @@
 				"node": ">= 8"
 				"node": ">= 8"
 			}
 			}
 		},
 		},
+		"node_modules/css-select": {
+			"version": "5.1.0",
+			"resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz",
+			"integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==",
+			"dev": true,
+			"dependencies": {
+				"boolbase": "^1.0.0",
+				"css-what": "^6.1.0",
+				"domhandler": "^5.0.2",
+				"domutils": "^3.0.1",
+				"nth-check": "^2.0.1"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/fb55"
+			}
+		},
 		"node_modules/css-tree": {
 		"node_modules/css-tree": {
 			"version": "2.3.1",
 			"version": "2.3.1",
 			"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz",
 			"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz",
@@ -1234,6 +1257,18 @@
 				"node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
 				"node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
 			}
 			}
 		},
 		},
+		"node_modules/css-what": {
+			"version": "6.1.0",
+			"resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz",
+			"integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==",
+			"dev": true,
+			"engines": {
+				"node": ">= 6"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/fb55"
+			}
+		},
 		"node_modules/cssesc": {
 		"node_modules/cssesc": {
 			"version": "3.0.0",
 			"version": "3.0.0",
 			"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
 			"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
@@ -1290,6 +1325,61 @@
 			"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
 			"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
 			"dev": true
 			"dev": true
 		},
 		},
+		"node_modules/dom-serializer": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
+			"integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
+			"dev": true,
+			"dependencies": {
+				"domelementtype": "^2.3.0",
+				"domhandler": "^5.0.2",
+				"entities": "^4.2.0"
+			},
+			"funding": {
+				"url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
+			}
+		},
+		"node_modules/domelementtype": {
+			"version": "2.3.0",
+			"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
+			"integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
+			"dev": true,
+			"funding": [
+				{
+					"type": "github",
+					"url": "https://github.com/sponsors/fb55"
+				}
+			]
+		},
+		"node_modules/domhandler": {
+			"version": "5.0.3",
+			"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
+			"integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
+			"dev": true,
+			"dependencies": {
+				"domelementtype": "^2.3.0"
+			},
+			"engines": {
+				"node": ">= 4"
+			},
+			"funding": {
+				"url": "https://github.com/fb55/domhandler?sponsor=1"
+			}
+		},
+		"node_modules/domutils": {
+			"version": "3.1.0",
+			"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz",
+			"integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==",
+			"dev": true,
+			"dependencies": {
+				"dom-serializer": "^2.0.0",
+				"domelementtype": "^2.3.0",
+				"domhandler": "^5.0.3"
+			},
+			"funding": {
+				"url": "https://github.com/fb55/domutils?sponsor=1"
+			}
+		},
 		"node_modules/eastasianwidth": {
 		"node_modules/eastasianwidth": {
 			"version": "0.2.0",
 			"version": "0.2.0",
 			"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
 			"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
@@ -1308,6 +1398,18 @@
 			"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
 			"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
 			"dev": true
 			"dev": true
 		},
 		},
+		"node_modules/entities": {
+			"version": "4.5.0",
+			"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
+			"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+			"dev": true,
+			"engines": {
+				"node": ">=0.12"
+			},
+			"funding": {
+				"url": "https://github.com/fb55/entities?sponsor=1"
+			}
+		},
 		"node_modules/esbuild": {
 		"node_modules/esbuild": {
 			"version": "0.21.5",
 			"version": "0.21.5",
 			"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
 			"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
@@ -1547,6 +1649,15 @@
 				"node": ">= 0.4"
 				"node": ">= 0.4"
 			}
 			}
 		},
 		},
+		"node_modules/he": {
+			"version": "1.2.0",
+			"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
+			"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
+			"dev": true,
+			"bin": {
+				"he": "bin/he"
+			}
+		},
 		"node_modules/import-meta-resolve": {
 		"node_modules/import-meta-resolve": {
 			"version": "4.1.0",
 			"version": "4.1.0",
 			"resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz",
 			"resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz",
@@ -1840,6 +1951,16 @@
 				"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
 				"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
 			}
 			}
 		},
 		},
+		"node_modules/node-html-parser": {
+			"version": "6.1.13",
+			"resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-6.1.13.tgz",
+			"integrity": "sha512-qIsTMOY4C/dAa5Q5vsobRpOOvPfC4pB61UVW2uSwZNUp0QU/jCekTal1vMmbO0DgdHeLUJpv/ARmDqErVxA3Sg==",
+			"dev": true,
+			"dependencies": {
+				"css-select": "^5.1.0",
+				"he": "1.2.0"
+			}
+		},
 		"node_modules/node-releases": {
 		"node_modules/node-releases": {
 			"version": "2.0.18",
 			"version": "2.0.18",
 			"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz",
 			"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz",
@@ -1864,6 +1985,18 @@
 				"node": ">=0.10.0"
 				"node": ">=0.10.0"
 			}
 			}
 		},
 		},
+		"node_modules/nth-check": {
+			"version": "2.1.1",
+			"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
+			"integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
+			"dev": true,
+			"dependencies": {
+				"boolbase": "^1.0.0"
+			},
+			"funding": {
+				"url": "https://github.com/fb55/nth-check?sponsor=1"
+			}
+		},
 		"node_modules/object-assign": {
 		"node_modules/object-assign": {
 			"version": "4.1.1",
 			"version": "4.1.1",
 			"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
 			"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",

+ 1 - 0
package.json

@@ -19,6 +19,7 @@
 		"@xterm/xterm": "^5.5.0",
 		"@xterm/xterm": "^5.5.0",
 		"autoprefixer": "^10.4.20",
 		"autoprefixer": "^10.4.20",
 		"labs": "git@github.com:leaningtech/labs.git",
 		"labs": "git@github.com:leaningtech/labs.git",
+		"node-html-parser": "^6.1.13",
 		"postcss": "^8.4.47",
 		"postcss": "^8.4.47",
 		"postcss-discard": "^2.0.0",
 		"postcss-discard": "^2.0.0",
 		"svelte": "^4.2.7",
 		"svelte": "^4.2.7",

+ 1 - 0
postcss.config.js

@@ -17,6 +17,7 @@ export default {
 				case '.fa-star:before':
 				case '.fa-star:before':
 				case '.fa-circle:before':
 				case '.fa-circle:before':
 				case '.fa-trash-can:before':
 				case '.fa-trash-can:before':
+				case '.fa-book-open:before':
 					return false;
 					return false;
 			}
 			}
 			return true;
 			return true;

+ 9 - 0
src/lib/BlogPost.svelte

@@ -0,0 +1,9 @@
+<script>
+	export let title;
+	export let image;
+	export let url;
+</script>
+<a href={url} target="_blank"><div class="bg-neutral-700 hover:bg-neutral-500 p-2 rounded-md">
+	<img class="w-56 h-32 object-fit" src={image}>
+	<h2 class="text-sm font-bold">{title}</h2>
+</div></a>

+ 14 - 0
src/lib/PostsTab.svelte

@@ -0,0 +1,14 @@
+<script>
+	import BlogPost from './BlogPost.svelte';
+	import { page } from '$app/stores';
+</script>
+<h1 class="text-lg font-bold">Blog posts</h1>
+<div class="overflow-y-scroll scrollbar flex flex-col gap-2">
+{#each $page.data.posts as post}
+	<BlogPost
+		title={post.title}
+		image={post.image}
+		url={post.url}
+	/>
+{/each}
+</div>

+ 4 - 0
src/lib/SideBar.svelte

@@ -4,6 +4,7 @@
 	import NetworkingTab from './NetworkingTab.svelte';
 	import NetworkingTab from './NetworkingTab.svelte';
 	import CpuTab from './CpuTab.svelte';
 	import CpuTab from './CpuTab.svelte';
 	import DiskTab from './DiskTab.svelte';
 	import DiskTab from './DiskTab.svelte';
+	import PostsTab from './PostsTab.svelte';
 	import DiscordTab from './DiscordTab.svelte';
 	import DiscordTab from './DiscordTab.svelte';
 	import GitHubTab from './GitHubTab.svelte';
 	import GitHubTab from './GitHubTab.svelte';
 	import { cpuActivity, diskActivity } from './activities.js'
 	import { cpuActivity, diskActivity } from './activities.js'
@@ -14,6 +15,7 @@
 		{ icon: 'fas fa-microchip', info: 'CPU', activity: cpuActivity },
 		{ icon: 'fas fa-microchip', info: 'CPU', activity: cpuActivity },
 		{ icon: 'fas fa-compact-disc', info: 'Disk', activity: diskActivity },
 		{ icon: 'fas fa-compact-disc', info: 'Disk', activity: diskActivity },
 		null,
 		null,
+		{ icon: 'fas fa-book-open', info: 'Posts', activity: null },
 		{ icon: 'fab fa-discord', info: 'Discord', activity: null },
 		{ icon: 'fab fa-discord', info: 'Discord', activity: null },
 		{ icon: 'fab fa-github', info: 'GitHub', activity: null },
 		{ icon: 'fab fa-github', info: 'GitHub', activity: null },
 	];
 	];
@@ -53,6 +55,8 @@
 			<CpuTab/>
 			<CpuTab/>
 		{:else if activeInfo === 'Disk'}
 		{:else if activeInfo === 'Disk'}
 			<DiskTab on:reset/>
 			<DiskTab on:reset/>
+		{:else if activeInfo === 'Posts'}
+			<PostsTab/>
 		{:else if activeInfo === 'Discord'}
 		{:else if activeInfo === 'Discord'}
 			<DiscordTab/>
 			<DiscordTab/>
 		{:else if activeInfo === 'GitHub'}
 		{:else if activeInfo === 'GitHub'}

+ 43 - 0
src/routes/+layout.server.js

@@ -0,0 +1,43 @@
+import { parse } from 'node-html-parser';
+import { read } from '$app/server';
+
+var posts = [
+	"https://labs.leaningtech.com/blog/join-the-webvm-hackathon",
+	"https://labs.leaningtech.com/blog/mini-webvm-your-linux-box-from-dockerfile-via-wasm",
+	"https://labs.leaningtech.com/blog/webvm-virtual-machine-with-networking-via-tailscale",
+	"https://labs.leaningtech.com/blog/webvm-server-less-x86-virtual-machines-in-the-browser",
+];
+
+async function getPostData(u)
+{
+	var ret = { title: null, image: null, url: u };
+	var response = await fetch(u);
+	var str = await response.text();
+	var root = parse(str);
+	var tags = root.getElementsByTagName("meta");
+	for(var i=0;i<tags.length;i++)
+	{
+		var metaName = tags[i].getAttribute("property");
+		var metaContent = tags[i].getAttribute("content");
+		switch(metaName)
+		{
+			case "og:title":
+				ret.title = metaContent;
+				break;
+			case "og:image":
+				ret.image = metaContent;
+				break;
+		}
+	}
+	return ret;
+}
+
+export async function load()
+{
+	var ret = [];
+	for(var i=0;i<posts.length;i++)
+	{
+		ret.push(await getPostData(posts[i]));
+	}
+	return { posts: ret };
+}