Merge remote-tracking branch 'upstream/main'

pull/25/head
cssbubble 2022-06-03 18:03:01 +01:00
commit 94f61d253f
13 zmienionych plików z 120 dodań i 74 usunięć

Wyświetl plik

@ -12,11 +12,17 @@ In conjunction with this, we're also building an online game where this is prese
[Visit https://learnawesome.vercel.app/](https://learnawesome.vercel.app/) [Visit https://learnawesome.vercel.app/](https://learnawesome.vercel.app/)
This is the exact same version. Your bookmarks will still be saved in localStorage so be assured that no personal data is being tracked or saved on this site. <img width="1689" alt="image" src="https://user-images.githubusercontent.com/19304/171865328-b8c22482-15d7-476e-9b91-a1c482809f29.png">
But if you'd like faster performance or to self-host this, say in your company's intranet, you need a general-purpose computer (that means Linux/Windows/Mac but not crippled OSes like Android or iOS) with Datasette (which is an exploratory tool for SQLite databases) installed. You can find [installation instructions specific to your operating system here](https://docs.datasette.io/en/stable/installation.html). <img width="1697" alt="image" src="https://user-images.githubusercontent.com/19304/171865412-e3bec0b3-3205-4f89-8d95-4c0af8476c7a.png">
After cloning this git repository on your local machine, run `datasette . -o` in the top-level directory to start the datasette serve and open the app in your browser. <img width="1687" alt="image" src="https://user-images.githubusercontent.com/19304/171865546-9cd68586-a096-4754-a78f-9aaebe6164ae.png">
Your bookmarks are saved in localStorage so be assured that no personal data is being tracked or saved on this site.
But if you'd like faster performance or to self-host this, you need a general-purpose computer (that means Linux/Windows/Mac) with Datasette (which is an exploratory tool for SQLite databases) installed. You can find [installation instructions specific to your operating system here](https://docs.datasette.io/en/stable/installation.html).
After cloning this git repository on your local machine, run `npm run start` in the top-level directory to start the datasette server and open the app in your browser.
## To contribute: ## To contribute:
@ -26,7 +32,7 @@ This is a Wikipedia-scale project and we could use all kind of help:
- To donate funds, [visit our OpenCollective](https://opencollective.com/learnawesome) - To donate funds, [visit our OpenCollective](https://opencollective.com/learnawesome)
- To report bugs, [create an issue](https://github.com/learn-awesome/learndb/issues) - To report bugs, [create an issue](https://github.com/learn-awesome/learndb/issues)
- To improve our topic taxonomy (improve sub-topics / prerequisites etc), [raise a PR on our Github with changes in `db/topics.csv` file](https://github.com/learn-awesome/learndb/tree/main/db) - To improve our topic taxonomy (improve sub-topics / prerequisites etc), [raise a PR on our Github with changes in `db/topics.csv` file](https://github.com/learn-awesome/learndb/tree/main/db)
- To improve the data about learning resources, [raise a PR on our Github with changes in `db/items.csv` file](https://github.com/learn-awesome/learndb/tree/main/db) - To improve the data about learning resources, first read [db/README.md](db/README.md) and [raise a PR on our Github with changes in `db/items.csv` file](https://github.com/learn-awesome/learndb/tree/main/db)
- To improve design and suggest features, [start a discussion](https://github.com/learn-awesome/learndb/discussions) - To improve design and suggest features, [start a discussion](https://github.com/learn-awesome/learndb/discussions)
- To fix technical bugs, [propose solutions on the issues](https://github.com/learn-awesome/learndb/issues) - To fix technical bugs, [propose solutions on the issues](https://github.com/learn-awesome/learndb/issues)
- For anything else, [start a discussion](https://github.com/learn-awesome/learndb/discussions) - For anything else, [start a discussion](https://github.com/learn-awesome/learndb/discussions)
@ -34,8 +40,7 @@ This is a Wikipedia-scale project and we could use all kind of help:
## To develop: ## To develop:
When you modify the *.csv files in `db/`, you should re-generate the sqlite database with `./generatedb.sh`. When you modify the *.csv files in `db/`, you should re-generate the sqlite database with `./generatedb.sh`.
Run `npm run dev` to keep live-building the JS bundle as you edit the source code. Run `npm run dev` to keep live-building the JS bundle as you edit the source code. This automatically runs `datasette . -o` to open the app in your browser.
And then run `datasette . -o` to open the app in your browser.
You can install Datasette's Vercel plugin with: `datasette install datasette-publish-vercel`. You can install Datasette's Vercel plugin with: `datasette install datasette-publish-vercel`.
To publish this, we first run `npm run build` followed by `npm run publish`. To publish this, we first run `npm run build` followed by `npm run publish`.
@ -44,7 +49,8 @@ To publish this, we first run `npm run build` followed by `npm run publish`.
The dataset here is identical to https://learnawesome.org/. But there are no user accounts, no social features like learning feeds or ActivityPub. Users' bookmarks are saved in browser's localStorage. The dataset here is identical to https://learnawesome.org/. But there are no user accounts, no social features like learning feeds or ActivityPub. Users' bookmarks are saved in browser's localStorage.
The source data is in `db/*.csv` files. This is imported into a sqlite database with `./generatedb.sh`. The source data is in `db/*.csv` files. The schema is described in [db/README.md](db/README.md).
These CSV files get imported into a sqlite database with `./generatedb.sh`.
We then rely on datasette to load this file and offer JSON APIs over HTTP. We then rely on datasette to load this file and offer JSON APIs over HTTP.
Settings and metadata are specified in `settings.json` and `metadata.json` which datasette uses. Settings and metadata are specified in `settings.json` and `metadata.json` which datasette uses.

38
db/README.md 100644
Wyświetl plik

@ -0,0 +1,38 @@
# CSV format
## topics.csv
`name` is used as primary key and therefore, must be unique and avoid uppercase and special characters other than hyphen and slash. Here are some examples: `physics`, `linear-algebra`, `nations/india`, `programming-languages/objective-c`.
`display_name` is used as human-readable name and can preserve uppercase. For eg: `ADHD`.
`parent_id` should be the name of the parent topic. This makes it possible to show a hierarchical view. If a topic does not have `parent_id`, it would be at the top-level but if it doesn't have children topics of its own, it will be clubbed under a dummy top-level topic called `Misc`.
`sort_index` is an integer that's used for controlling the ordering in which topics are displayed.
## items.csv
`iid` should be a unique UUID. It is needed because `reviews.csv` needs to refer to items and there is no other natural primary key. Later, if we'd want to build collections of items, the same `iid` key would be helpful.
`description` can contain markdown with multiple lines.
`links` is an array value separated by `;`. Each item in this array a pair of `format` and `url` separated by `|`. For eg, `links` can have a value like this: `summary|https://sivers.org/book/Decisive;book|https://www.goodreads.com/book/show/15798078-decisive;summary|https://fourminutebooks.com/decisive-summary/`.
We are considering including other fields like `ipfsHash` and `image` in each value of `links`. This decision is yet to be made.
`topics` is a array value of topic names separated by `;`. These should exactly match `topics` table's `name` column.
`creators` is arbitrary string for now. For eg: `Charles Darwin`. In future, this might become a full record on its own including fields like `name`,`website`,`twitter`,`email`. In that case, we will have to somehow figure out unique key for each creator that could serve the role of primary key and foreign key.
`difficulty` must be empty or one of these: `childlike`, `beginner`, `intermediate`, `advanced`, `research`.
`rating` is on a 5.0 point scale with up to two decimal places allowed. This is a curated value and should not be simply copied from external sources.
`tags` can describe quality: `visual`, `entertaining`, `challenging`, `inspirational`, `interactive`.
## reviews.csv
`item_id` is a foreign key to `items.csv`.
`by` is the name of the person or item.
`blurb` is small description in markdown format.

Wyświetl plik

@ -6,7 +6,7 @@
"test": "echo \"Error: no test specified\" && exit 1", "test": "echo \"Error: no test specified\" && exit 1",
"build": "rollup -c", "build": "rollup -c",
"dev": "rollup -c -w", "dev": "rollup -c -w",
"start": "sirv public --no-clear", "start": "datasette . -o",
"publish": "datasette publish vercel learn.db --project learnawesome -m metadata.json --template-dir templates --static static:static --setting max_returned_rows 20000" "publish": "datasette publish vercel learn.db --project learnawesome -m metadata.json --template-dir templates --static static:static --setting max_returned_rows 20000"
}, },
"devDependencies": { "devDependencies": {

Wyświetl plik

@ -20,7 +20,7 @@ function serve() {
return { return {
writeBundle() { writeBundle() {
if (server) return; if (server) return;
server = require('child_process').spawn('npm', ['run', 'start', '--', '--dev'], { server = require('child_process').spawn('npm', ['run', 'start'], {
stdio: ['ignore', 'inherit', 'inherit'], stdio: ['ignore', 'inherit', 'inherit'],
shell: true shell: true
}); });

Wyświetl plik

@ -97,10 +97,6 @@
<LibraryIcon class=" flex-shrink-0 h-6 w-6"/> <LibraryIcon class=" flex-shrink-0 h-6 w-6"/>
</NavButtonWithLabel> </NavButtonWithLabel>
<NavButtonWithLabel isActive={currentView === "/map"} target="#/map" label="TreeMap">
<LibraryIcon class=" flex-shrink-0 h-6 w-6"/>
</NavButtonWithLabel>
<NavButtonWithLabel isActive={currentView === "/formats"} target="#/formats" label="Formats"> <NavButtonWithLabel isActive={currentView === "/formats"} target="#/formats" label="Formats">
<ViewGridIcon class=" flex-shrink-0 h-6 w-6"/> <ViewGridIcon class=" flex-shrink-0 h-6 w-6"/>
</NavButtonWithLabel> </NavButtonWithLabel>
@ -128,10 +124,5 @@
<BookmarkAltIcon class=" flex-shrink-0 h-6 w-6"/> <BookmarkAltIcon class=" flex-shrink-0 h-6 w-6"/>
</NavButtonWithLabel> </NavButtonWithLabel>
<a href="/learn" target="_blank" class="text-indigo-100 hover:bg-lightSecondary1 hover:text-lightSecondary2 hover:dark:bg-darkPrimaryBg w-full group flex justify-start gap-3 items-center py-5 text-sm font-medium hover:dark:text-darkSecondary2 mb-5 pl-4">
<CogIcon class=" flex-shrink-0 h-6 w-6 "/>
<h3 class="text-center"> Datasette</h3>
</a>
</svelte:fragment> </svelte:fragment>
</TailwindUI.AppShell> </TailwindUI.AppShell>

Wyświetl plik

@ -10,28 +10,27 @@
let query = { let query = {
text: "", text: "",
topic: "", topic: "",
format: "",
level: "", level: "",
tags: "", tag: "",
sortby: "rating" sortby: "rating"
}; };
$: fetch(`/learn/items.json?_shape=array&links__contains=${format}|`) $: query && fetch(`/learn/items.json?_shape=array&_size=100&links__contains=${format}|&topics__contains=${query.topic}`)
.then(r => r.json()) .then(r => r.json())
.then(data => { .then(data => {
items = data; items = data;
}); });
function handleQueryChanged(event){ function handleQueryChanged(event){
console.log("queryChanged: ", event.detail); // console.log("queryChanged: ", event.detail);
query = event.detail; query = event.detail;
} }
$: filteredItems = items.filter(item => { $: filteredItems = items.filter(item => {
if(query.text && !item.name.toLowerCase().includes(query.text.toLowerCase())){ return false; } if(query.text && !item.name.toLowerCase().includes(query.text.toLowerCase())){ return false; }
if(query.format && !item.links.includes(query.format)) { return false; } if(query.topic && !item.topics.includes(query.topic)){ return false; }
if(query.level && item.difficulty != query.level){ return false; } if(query.level && item.difficulty != query.level){ return false; }
// TODO Apply tags filter if(query.tag && !item.tags.includes(query.tag)){ return false; }
return true; return true;
}).sort((a,b) => { }).sort((a,b) => {
if(query.sortby == 'rating') { return (a.rating - b.rating) }; if(query.sortby == 'rating') { return (a.rating - b.rating) };

Wyświetl plik

@ -3,16 +3,12 @@
import { createEventDispatcher } from 'svelte'; import { createEventDispatcher } from 'svelte';
export let alltopics; export let alltopics;
export let hideFormat = false;
export let hideTopic = false; export let hideTopic = false;
export let hideQuality = true;
let query = { let query = {
text: "", text: "",
topic: "", topic: "",
format: "",
level: "", level: "",
quality: "",
sortby: "rating", sortby: "rating",
tag: "" tag: ""
}; };
@ -24,24 +20,19 @@
</script> </script>
<form class="w-full p-2 inline-flex flex-wrap" on:submit|preventDefault> <form class="w-full p-2 inline-flex flex-wrap" on:submit|preventDefault>
<sl-input type="search" placeholder="Type something to search items by keywords" size="medium" clearable class="flex-1 border-0 p-0 focus:ring-0" value={query.text} on:sl-input="{e => query.text = e.target.value}"> <sl-input type="search" placeholder="Search by keywords" size="medium" clearable class="flex-1 border-0 p-0 focus:ring-0" value={query.text} on:sl-input="{e => query.text = e.target.value}">
<sl-icon name="search" slot="prefix"></sl-icon> <sl-icon name="search" slot="prefix"></sl-icon>
</sl-input> </sl-input>
{#if !hideTopic} {#if !hideTopic}
<ComboBox options={alltopics.map(t => { return {label: t.display_name, value: t.name}; }).sort((a,b) => a.label.localeCompare(b.label)).slice(0,10)} selected={null}/> <fluent-combobox autocomplete="both" placeholder="Any topic" class="ml-2 mt-1 outline-none border-2 border-grey-600" on:change="{e => query.topic = e.target.value}">
{#each alltopics.sort((a,b) => a.display_name.localeCompare(b.display_name)) as topic}
<fluent-option value={topic.name}>{topic.display_name}</fluent-option>
{/each}
</fluent-combobox>
{/if} {/if}
{#if !hideFormat} <sl-select class="ml-2 w-52" on:sl-change="{e => query.tag = e.target.value}" value={query.tag}>
<sl-select class="ml-2 w-44" on:sl-change="{e => query.format = e.target.value}" value={query.format}>
<sl-menu-item value="">Any format</sl-menu-item>
<sl-menu-item value="book">Books</sl-menu-item>
<sl-menu-item value="video">Videos</sl-menu-item>
<sl-menu-item value="audio">Podcasts</sl-menu-item>
</sl-select>
{/if}
<sl-select class="ml-2 w-44" on:sl-change="{e => query.tag = e.target.value}" value={query.tag}>
<sl-menu-item value="">Any tag</sl-menu-item> <sl-menu-item value="">Any tag</sl-menu-item>
<sl-menu-item value="inspirational">Inspirational</sl-menu-item> <sl-menu-item value="inspirational">Inspirational</sl-menu-item>
<sl-menu-item value="educational">Educational</sl-menu-item> <sl-menu-item value="educational">Educational</sl-menu-item>
@ -49,6 +40,7 @@
<sl-menu-item value="entertaining">Entertaining</sl-menu-item> <sl-menu-item value="entertaining">Entertaining</sl-menu-item>
<sl-menu-item value="visual">Visual</sl-menu-item> <sl-menu-item value="visual">Visual</sl-menu-item>
<sl-menu-item value="interactive">Interactive</sl-menu-item> <sl-menu-item value="interactive">Interactive</sl-menu-item>
<sl-menu-item value="oer">Open (no login or pay)</sl-menu-item>
</sl-select> </sl-select>
<sl-select class="ml-2 w-44" on:sl-change="{e => query.level = e.target.value}" value={query.level}> <sl-select class="ml-2 w-44" on:sl-change="{e => query.level = e.target.value}" value={query.level}>
@ -60,15 +52,6 @@
<sl-menu-item value="research">Research</sl-menu-item> <sl-menu-item value="research">Research</sl-menu-item>
</sl-select> </sl-select>
{#if !hideQuality}
<sl-select class="ml-2 w-44" on:sl-change="{e => query.quality = e.target.value}" value={query.quality}>
<sl-menu-item value="">Any quality</sl-menu-item>
<sl-menu-item value="visual">Visual</sl-menu-item>
<sl-menu-item value="interactive">Interactive</sl-menu-item>
<sl-menu-item value="entertaining">Entertaining</sl-menu-item>
</sl-select>
{/if}
<sl-select class="ml-2 w-52" on:sl-change="{e => query.sortby = e.target.value}" value={query.sortby}> <sl-select class="ml-2 w-52" on:sl-change="{e => query.sortby = e.target.value}" value={query.sortby}>
<sl-icon name="sort-down-alt" slot="prefix"></sl-icon> <sl-icon name="sort-down-alt" slot="prefix"></sl-icon>
<sl-menu-item value="rating">Sort by Rating</sl-menu-item> <sl-menu-item value="rating">Sort by Rating</sl-menu-item>

Wyświetl plik

@ -14,9 +14,8 @@
let query = { let query = {
text: "", text: "",
topic: "", topic: "",
format: "",
level: "", level: "",
tags: "", tag: "",
sortby: "rating" sortby: "rating"
}; };
@ -28,15 +27,14 @@
}); });
function handleQueryChanged(event){ function handleQueryChanged(event){
console.log("queryChanged: ", event.detail); // console.log("queryChanged: ", event.detail);
query = event.detail; query = event.detail;
} }
$: filteredItems = items.filter(item => { $: filteredItems = items.filter(item => {
if(query.text && !item.name.toLowerCase().includes(query.text.toLowerCase())){ return false; } if(query.text && !item.name.toLowerCase().includes(query.text.toLowerCase())){ return false; }
if(query.format && !item.links.includes(query.format)) { return false; }
if(query.level && item.difficulty != query.level){ return false; } if(query.level && item.difficulty != query.level){ return false; }
// TODO: apply tags filter if(query.tag && !item.tags.includes(query.tag)){ return false; }
return true; return true;
}).sort((a,b) => { }).sort((a,b) => {
if(query.sortby == 'rating') { return (a.rating - b.rating) }; if(query.sortby == 'rating') { return (a.rating - b.rating) };

Wyświetl plik

@ -4,4 +4,7 @@
import SearchForm from "./SearchForm.svelte" import SearchForm from "./SearchForm.svelte"
</script> </script>
<div class="flex justify-end">
<a href="#/map" class="font-semibold bg-lightTertiary text-lightBg rounded-lg hover:scale-110 px-3 py-2 hover:ease-in-out transition duration-200">Explore Map</a>
</div>
<TopicMasonryGrid {alltopics}/> <TopicMasonryGrid {alltopics}/>

Wyświetl plik

@ -84,7 +84,7 @@
</sl-breadcrumb> </sl-breadcrumb>
</div> </div>
<div class="gap-8 columns-1 sm:columns-2 lg:columns-3 xl:columns-4 2xl:columns-5 mb-8"> <div class="gap-8 columns-1 sm:columns-2 lg:columns-3 xl:columns-4 3xl:columns-5 mb-8">
{#each [...map.entries()].sort((t1,t2) => (t1[0].sort_index || 100) - (t2[0].sort_index || 100)) as parent} {#each [...map.entries()].sort((t1,t2) => (t1[0].sort_index || 100) - (t2[0].sort_index || 100)) as parent}
<a href={"#/topic/" + parent[0].name}> <a href={"#/topic/" + parent[0].name}>
<div class="rounded-lg shadow-md p-4 break-inside-avoid mb-4 border-8 border-lightPrimCont dark:border-darkPrimCont hover:bg-lightPrimCont dark:bg-darkPrimCont "> <div class="rounded-lg shadow-md p-4 break-inside-avoid mb-4 border-8 border-lightPrimCont dark:border-darkPrimCont hover:bg-lightPrimCont dark:bg-darkPrimCont ">

Wyświetl plik

@ -1,15 +1,24 @@
<script> <script>
export let options = []; export let options = [];
let userInput = '3'; export let userInput = '';
$: filteredOptions = options.filter(x => x.label.startsWith(userInput)) $: filteredOptions = options.filter(x => x.label.startsWith(userInput))
$: console.log(userInput) $: console.log({options})
$: console.log(filteredOptions)
$: console.log({userInput})
</script> </script>
<sl-dropdown> <sl-dropdown class="ml-2">
<sl-input slot="trigger" on:change="{e => userInput = e.target.value}" value={userInput}></sl-input> <sl-input
slot="trigger"
class="p-0 m-0 border-0"
on:sl-input="{e => userInput = e.target.value}"
value={userInput}
placeholder="select topic">
</sl-input>
<sl-menu> <sl-menu>
{#each filteredOptions as opt (opt.value)} {#each filteredOptions as opt (opt.value)}
<sl-menu-item>{opt.label}</sl-menu-item> <sl-menu-item on:click="{e => userInput = opt.value}">{opt.label}</sl-menu-item>
{/each} {/each}
</sl-menu> </sl-menu>
</sl-dropdown> </sl-dropdown>

Wyświetl plik

@ -6,6 +6,7 @@
<title>TreeMap</title> <title>TreeMap</title>
<meta name="description" content=""> <meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<style> <style>
body { body {
margin: 0px; margin: 0px;
@ -21,7 +22,9 @@ body {
} }
text { text {
pointer-events: none; cursor: pointer;
fill: #7a28cb;
} }
.grandparent text { .grandparent text {
@ -39,11 +42,11 @@ rect.parent,
} }
.grandparent rect { .grandparent rect {
fill: rgb(232, 255, 0); fill: #f4b393;
} }
.grandparent:hover rect { .grandparent:hover rect {
fill: rgb(202, 225, 0); fill: #f4d393;
} }
.children rect.parent, .children rect.parent,
@ -51,21 +54,30 @@ rect.parent,
cursor: pointer; cursor: pointer;
} }
.leaf rect.parent {
cursor: pointer;
fill: #ffdbd1;
}
.leaf rect.parent:hover {
cursor: pointer;
fill: #ffcfd4;
}
.children rect.parent { .children rect.parent {
fill: #99F6E4; fill: #ceec97;
fill-opacity: .5; fill-opacity: .5;
} }
.children:hover rect.child { .children:hover rect.child {
fill: #D7CBFA; fill: #deec97;
} }
</style> </style>
<link href='http://fonts.googleapis.com/css?family=Open+Sans:300' rel='stylesheet' type='text/css'> <link href='https://fonts.googleapis.com/css?family=Open+Sans:300' rel='stylesheet' type='text/css'>
</head> </head>
<body> <body>
<p id="chart"></p> <p id="chart"></p>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script type="text/javascript"> <script type="text/javascript">
function hierarchy(topic_array, parent_id){ function hierarchy(topic_array, parent_id){
@ -78,8 +90,8 @@ function hierarchy(topic_array, parent_id){
.map(item => ({ ...item, children: nest(items, item.name), value: 100 })) .map(item => ({ ...item, children: nest(items, item.name), value: 100 }))
.map(item => { .map(item => {
return item.children.length == 0 ? return item.children.length == 0 ?
{id: item.name, name: item.name.split("/").reverse()[0], value: 100} : {id: item.name, name: item.name, value: 100} :
{id: item.name, name: item.name.split("/").reverse()[0], children: item.children} {id: item.name, name: item.name, children: item.children}
}); });
let topnodes = nest(topic_array, ''); // includes childless nodes let topnodes = nest(topic_array, ''); // includes childless nodes
@ -88,13 +100,13 @@ function hierarchy(topic_array, parent_id){
let misc = {id: 'misc', name: 'Misc', children: topnodes.filter(t => t.value).slice(0,15)}; let misc = {id: 'misc', name: 'Misc', children: topnodes.filter(t => t.value).slice(0,15)};
return {name: "·", children: [...topnodes.filter(t => t.children), misc]}; return {name: "", children: [...topnodes.filter(t => t.children), misc]};
} }
d3.json("/learn/topics.json?_shape=array&_size=5000", function(alltopics){ d3.json("/learn/topics.json?_shape=array&_size=5000", function(alltopics){
// console.log({alltopics}); // console.log({alltopics});
var root = hierarchy(alltopics, ""); var root = hierarchy(alltopics, "");
console.log({root}); // console.log({root});
initialize(root); initialize(root);
accumulate(root); accumulate(root);
layout(root); layout(root);
@ -203,7 +215,11 @@ grandparent.append("text")
.classed("children", true) .classed("children", true)
.on("click", transition); .on("click", transition);
g.selectAll(".child") g.filter(function(d) { return !d._children; })
.classed("leaf", true)
.on("click",(n) => { window.parent.location.href = "/#/topic/" + n.name;});
g.filter(function(d) { return d._children; }).selectAll(".child")
.data(function(d) { return d._children || [d]; }) .data(function(d) { return d._children || [d]; })
.enter().append("rect") .enter().append("rect")
.attr("class", "child") .attr("class", "child")
@ -217,7 +233,8 @@ grandparent.append("text")
g.append("text") g.append("text")
.attr("dy", ".75em") .attr("dy", ".75em")
.text(function(d) { return d.name.split('{')[0].split('(')[0] .on("click", (n) => { window.parent.location.href = "/#/topic/" + n.name;})
.text(function(d) { return d.name.split('/').reverse()[0].split('{')[0].split('(')[0]
.split('[')[0]; }) .split('[')[0]; })
.call(text); .call(text);
@ -260,7 +277,8 @@ grandparent.append("text")
function text(text) { function text(text) {
text.attr("x", function(d) { return x(d.x) + 10; }) text.attr("x", function(d) { return x(d.x) + 10; })
.attr("y", function(d) { return y(d.y) + 10; }); .attr("y", function(d) { return y(d.y) + 10; })
.attr("text-decoration","underline");
} }
function rect(rect) { function rect(rect) {

Wyświetl plik

@ -35,6 +35,7 @@
<link href="/static/bundle.css" rel="stylesheet" /> <link href="/static/bundle.css" rel="stylesheet" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.0.0-beta.73/dist/themes/light.css" /> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.0.0-beta.73/dist/themes/light.css" />
<script type="module" src="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.0.0-beta.73/dist/shoelace.js"></script> <script type="module" src="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.0.0-beta.73/dist/shoelace.js"></script>
<script type="module" src="https://unpkg.com/@fluentui/web-components"></script>
<title>LearnAwesome</title> <title>LearnAwesome</title>
<!-- fonts --> <!-- fonts -->