kopia lustrzana https://github.com/learn-awesome/learndb
Use localdata using jsonlines
rodzic
33daea98c3
commit
600417f1e6
12
db/README.md
12
db/README.md
|
@ -1,6 +1,6 @@
|
|||
# CSV format
|
||||
# JSON format
|
||||
|
||||
## topics.csv
|
||||
## topics.js
|
||||
|
||||
`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`.
|
||||
|
||||
|
@ -11,9 +11,9 @@
|
|||
`sort_index` is an integer that's used for controlling the ordering in which topics are displayed.
|
||||
|
||||
|
||||
## items.csv
|
||||
## items.js
|
||||
|
||||
`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.
|
||||
`iid` should be a unique UUID. It is needed because `reviews.js` 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.
|
||||
|
||||
|
@ -31,8 +31,8 @@ Currently `ipfs:` and `doi:` identifiers are supported. In future, ISBN can also
|
|||
|
||||
`tags` can describe quality: `visual`, `entertaining`, `challenging`, `inspirational`, `interactive`, `oer`. `oer` stands for "Open Educational Resource" and can be used if the linked content does not require payment or user login.
|
||||
|
||||
## reviews.csv
|
||||
## reviews.js
|
||||
|
||||
`item_id` is a foreign key to `items.csv`.
|
||||
`item_id` is a foreign key to `items.js`.
|
||||
`by` is the name of the person or item.
|
||||
`blurb` is small description in markdown format.
|
19048
db/items.csv
19048
db/items.csv
Plik diff jest za duży
Load Diff
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,50 @@
|
|||
import { items } from './items.js';
|
||||
import { topics } from './topics.js';
|
||||
import { reviews } from './reviews.js';
|
||||
|
||||
const items_db = items.trimStart().trimEnd().split('\n').map(j => JSON.parse(j));
|
||||
const topics_db = topics.trimStart().trimEnd().split('\n').map(j => JSON.parse(j));
|
||||
const reviews_db = reviews.trimStart().trimEnd().split('\n').map(j => JSON.parse(j));
|
||||
|
||||
export const io_getTopicList = () => {
|
||||
return [...topics_db];
|
||||
}
|
||||
|
||||
export const io_getRandomTopicName = () => {
|
||||
let randomId = Math.floor(Math.random() * topics_db.length);
|
||||
return topics_db[randomId].name;
|
||||
}
|
||||
|
||||
export const io_getTopicByName = (name) => {
|
||||
return topics_db.filter(t => t.name === name)[0];
|
||||
}
|
||||
|
||||
export const io_getRandomItemId = () => {
|
||||
let randomId = Math.floor(Math.random() * items_db.length);
|
||||
return items_db[randomId].iid;
|
||||
}
|
||||
|
||||
export const io_getItemsForTopic = (topicname) => {
|
||||
return items_db.filter(i => i.topics.split(";").includes(topicname))
|
||||
}
|
||||
|
||||
export const io_getItem = (iid) => {
|
||||
if(!iid) return null;
|
||||
return items_db.filter(t => t.iid === iid)[0];
|
||||
}
|
||||
|
||||
export const io_getItemsForTopicAndFormat = (format, topicname) => {
|
||||
let results = items_db.filter(i => i.topics.includes(topicname)).filter(i => i.links?.includes(format + "|"));
|
||||
return results.slice(0, 100);
|
||||
}
|
||||
|
||||
export const io_getReviewsForItem = (item_id) => {
|
||||
if(!item_id) return [];
|
||||
return reviews_db.filter(r => r.id === item_id);
|
||||
}
|
||||
|
||||
export const io_getItemsWithIDs = (ids) => {
|
||||
let results = items_db.filter(i => ids.includes(i.iid));
|
||||
console.log({ids}, {results});
|
||||
return results;
|
||||
}
|
Plik diff jest za duży
Load Diff
2503
db/reviews1.csv
2503
db/reviews1.csv
Plik diff jest za duży
Load Diff
1543
db/reviews2.csv
1543
db/reviews2.csv
Plik diff jest za duży
Load Diff
Plik diff jest za duży
Load Diff
1496
db/topics1.csv
1496
db/topics1.csv
Plik diff jest za duży
Load Diff
1454
db/topics2.csv
1454
db/topics2.csv
Plik diff jest za duży
Load Diff
BIN
dummy.db
BIN
dummy.db
Plik binarny nie jest wyświetlany.
|
@ -1,9 +0,0 @@
|
|||
rm learn.db
|
||||
|
||||
sqlite-utils insert learn.db topics db/topics1.csv --csv
|
||||
sqlite-utils insert learn.db topics db/topics2.csv --csv
|
||||
|
||||
sqlite-utils insert learn.db items db/items.csv --csv
|
||||
|
||||
sqlite-utils insert learn.db reviews db/reviews1.csv --csv
|
||||
sqlite-utils insert learn.db reviews db/reviews2.csv --csv
|
BIN
learn.db
BIN
learn.db
Plik binarny nie jest wyświetlany.
|
@ -1,9 +0,0 @@
|
|||
{
|
||||
"title": "LearnAwesome",
|
||||
"description": "Curated collection of learning resources",
|
||||
"license": "ISC",
|
||||
"license_url": "",
|
||||
"source": "LearnAwesome.org",
|
||||
"source_url": "https://learnawesome.org/",
|
||||
"max_returned_rows": 20000
|
||||
}
|
|
@ -6,7 +6,7 @@
|
|||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"build": "rollup -c",
|
||||
"dev": "rollup -c -w",
|
||||
"start": "datasette . -o",
|
||||
"start": "http-server static -o /",
|
||||
"publish": "datasette publish vercel learn.db --project learnawesome -m metadata.json --template-dir templates --static static:static --setting max_returned_rows 20000"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
{
|
||||
"max_returned_rows": 20000
|
||||
}
|
|
@ -14,32 +14,22 @@
|
|||
import NavButtonWithLabel from './NavButtonWithLabel.svelte';
|
||||
import { SearchIcon, LibraryIcon, ViewGridIcon, GiftIcon, CogIcon, BookmarkAltIcon, BookmarkIcon, SupportIcon } from "@rgossiaux/svelte-heroicons/outline";
|
||||
import Bookmarks from './Bookmarks.svelte';
|
||||
import { io_getRandomTopicName, io_getTopicByName, io_getTopicList, io_getRandomItemId } from "../db/jsonlines.js"
|
||||
|
||||
let currentView = "/topics";
|
||||
let randomTopicName;
|
||||
let randomItemId;
|
||||
let alltopics = [];
|
||||
|
||||
function getRandomItemId(){
|
||||
fetch('/learn.json?_shape=array&sql=select+rowid+from+items+order+by+random()+limit+1').then(r => r.json())
|
||||
.then(data => {
|
||||
randomItemId = data[0].rowid;
|
||||
});
|
||||
$: alltopics = io_getTopicList();
|
||||
|
||||
function getRandomItemId() {
|
||||
randomItemId = io_getRandomItemId();
|
||||
}
|
||||
|
||||
function getRandomTopicName(){
|
||||
fetch('/learn.json?_shape=array&sql=select+name+from+topics+order+by+random()+limit+1').then(r => r.json())
|
||||
.then(data => {
|
||||
randomTopicName = data[0].name;
|
||||
});
|
||||
function getRandomTopicName() {
|
||||
randomTopicName = io_getRandomTopicName();
|
||||
}
|
||||
|
||||
$: fetch(`/learn/topics.json?_shape=array&_size=5000`)
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
alltopics = data;
|
||||
});
|
||||
|
||||
async function hashchange() {
|
||||
// the poor man's router!
|
||||
const path = window.location.hash.slice(1);
|
||||
|
@ -52,8 +42,8 @@
|
|||
}
|
||||
}
|
||||
|
||||
onMount(getRandomItemId);
|
||||
onMount(getRandomTopicName);
|
||||
onMount(getRandomItemId);
|
||||
onMount(hashchange);
|
||||
|
||||
</script>
|
||||
|
|
|
@ -113,7 +113,7 @@
|
|||
<slot name="nav"></slot>
|
||||
{#if window.location.href.startsWith('http://127.0.0.1')}
|
||||
<button class="" on:click={themeRandomize}>Randomize</button>
|
||||
{/if}
|
||||
{/if}
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
</script>
|
||||
|
||||
<a class="relative flex flex-col items-center rounded-md overflow-hidden transform transition ease-out duration-300 drop-shadow-lg hover:drop-shadow-2xl hover:scale-105 break-inside-avoid" href="#/item/{item.rowid}">
|
||||
<a class="relative flex flex-col items-center rounded-md overflow-hidden transform transition ease-out duration-300 drop-shadow-lg hover:drop-shadow-2xl hover:scale-105 break-inside-avoid" href="#/item/{item.iid}">
|
||||
|
||||
<img class=" h-36 w-24 md:h-56 md:w-40 shrink-0" src={item.image || randomCover(item.iid)} alt="{item.name}"/>
|
||||
|
||||
|
|
|
@ -2,24 +2,11 @@
|
|||
import { onMount } from 'svelte';
|
||||
import ItemList from "./ItemList.svelte"
|
||||
import { bookmarks } from "./stores.js"
|
||||
import { io_getItemsWithIDs } from "../db/jsonlines.js"
|
||||
|
||||
export let kind;
|
||||
let items = []
|
||||
|
||||
|
||||
onMount(() => {
|
||||
// items = read();
|
||||
});
|
||||
|
||||
function encodeArray(kind){
|
||||
return Object.entries($bookmarks).filter(pair => pair[1] == kind).map(pair => pair[0]).join("%2C")
|
||||
}
|
||||
|
||||
$: fetch(`/learn/items.json?_shape=array&rowid__in=${encodeArray(kind)}`)
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
items = data;
|
||||
});
|
||||
$: items = io_getItemsWithIDs(Object.entries($bookmarks).filter(pair => pair[1] == kind).map(pair => pair[0]));
|
||||
|
||||
</script>
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
import ItemCard from "./ItemCard.svelte"
|
||||
import SearchForm from "./SearchForm.svelte"
|
||||
import { formats } from "./formats.js"
|
||||
import { io_getItemsForTopicAndFormat } from "../db/jsonlines";
|
||||
|
||||
export let format;
|
||||
export let alltopics;
|
||||
|
@ -16,11 +17,8 @@
|
|||
sortby: "rating"
|
||||
};
|
||||
|
||||
$: query && fetch(`/learn/items.json?_shape=array&_size=100&links__contains=${format}|&topics__contains=${query.topic}`)
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
items = data;
|
||||
});
|
||||
$: items = io_getItemsForTopicAndFormat(format, query?.topic)
|
||||
$: console.log(items.length)
|
||||
|
||||
function handleQueryChanged(event){
|
||||
// console.log("queryChanged: ", event.detail);
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
<script lang="ts">
|
||||
export let item: {name: string, creators: string, rowid: number};
|
||||
export let item: {name: string, creators: string, iid: number};
|
||||
</script>
|
||||
|
||||
<a class="flex flex-wrap p-2 sm:p-8 justify-between rounded break-inside-avoid sm:w-64 max-w-sm bg-primary_light text-primary hover:rounded-3xl border border-secondary ease-in-out duration-300" href="#/item/{item.rowid}">
|
||||
<a class="flex flex-wrap p-2 sm:p-8 justify-between rounded break-inside-avoid sm:w-64 max-w-sm bg-primary_light text-primary hover:rounded-3xl border border-secondary ease-in-out duration-300" href="#/item/{item.iid}">
|
||||
<div class="flex flex-col justify-between">
|
||||
<div class="flex flex-col">
|
||||
<strong class="font-extrabold text-sm sm:text-lg">{item.name}</strong>
|
||||
<span class="text-sm font-medium">{item.creators}</span>
|
||||
{#if item.creators}<span class="text-sm font-medium">{item.creators}</span>{/if}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
|
|
@ -3,23 +3,12 @@
|
|||
import { bookmarks } from "./stores.js"
|
||||
import Review from "./Review.svelte"
|
||||
import { randomCover } from './utility.js'
|
||||
import AdvancedSearch from "./AdvancedSearch.svelte";
|
||||
import { io_getItem, io_getReviewsForItem } from "../db/jsonlines";
|
||||
|
||||
export let itemid;
|
||||
let item;
|
||||
let reviews = [];
|
||||
|
||||
$: fetch(`/learn/items/${itemid}.json?_shape=object`)
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
item = data[itemid];
|
||||
});
|
||||
|
||||
$: item && fetch(`/learn/reviews.json?_shape=array&item_id__exact=${item.iid}`)
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
reviews = data;
|
||||
});
|
||||
$: item = io_getItem(itemid);
|
||||
$: reviews = io_getReviewsForItem(item?.id)
|
||||
|
||||
function get_tld(url){
|
||||
return(new URL(url)).hostname.replace('www.','');
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
import ItemList from "./ItemList.svelte"
|
||||
import TopicMasonryGrid from "./TopicMasonryGrid.svelte"
|
||||
|
||||
import { io_getItemsForTopic } from "../db/jsonlines.js"
|
||||
|
||||
import SearchForm from "./SearchForm.svelte"
|
||||
|
||||
|
@ -19,12 +19,11 @@
|
|||
sortby: "rating"
|
||||
};
|
||||
|
||||
$: fetch(`/learn/items.json?_shape=array&topics__contains=${topicname}`)
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
items = data;
|
||||
filteredItems = data;
|
||||
});
|
||||
$: {
|
||||
let data = io_getItemsForTopic(topicname);
|
||||
items = data;
|
||||
filteredItems = data;
|
||||
}
|
||||
|
||||
function handleQueryChanged(event){
|
||||
// console.log("queryChanged: ", event.detail);
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
<script>
|
||||
import TopicMasonryGrid from "./TopicMasonryGrid.svelte"
|
||||
export let alltopics;
|
||||
import SearchForm from "./SearchForm.svelte"
|
||||
</script>
|
||||
|
||||
<TopicMasonryGrid {alltopics}/>
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
let tempmap = new Map();
|
||||
// first pass to find all top-level objects
|
||||
let parentids = [];
|
||||
// console.log({topic_array}, {parent_id});
|
||||
for(let i = 0; i < topic_array.length; i++){
|
||||
if(topic_array[i].parent_id == parent_id){
|
||||
tempmap.set(topic_array[i], []);
|
||||
|
@ -61,7 +62,7 @@
|
|||
|
||||
$: topic = alltopics.find(t => t.name == topicname)
|
||||
|
||||
$: map = hierarchy(alltopics, topic?.name || "")
|
||||
$: map = hierarchy(alltopics, topic?.name || null)
|
||||
|
||||
|
||||
</script>
|
||||
|
|
|
@ -1 +1 @@
|
|||
<iframe title="treemap" src="/static/map.html" class="w-full h-screen"></iframe>
|
||||
<iframe title="treemap" src="/map.html" class="w-full h-screen"></iframe>
|
|
@ -22,7 +22,7 @@
|
|||
</script>
|
||||
|
||||
|
||||
<a class="flex flex-wrap overflow-hidden rounded-lg duration-300 break-inside-avoid max-w-lg border-secondary" href="#/item/{item.rowid}">
|
||||
<a class="flex flex-wrap overflow-hidden rounded-lg duration-300 break-inside-avoid max-w-lg border-secondary" href="#/item/{item.iid}">
|
||||
<div class="relative w-full max-w-sm w-full md:w-64 ring-black/5 rounded-xl flex flex-col items-start">
|
||||
<div class="h-36 w-full md:w-64 flex justify-center items-center relative ">
|
||||
|
||||
|
@ -51,7 +51,7 @@
|
|||
|
||||
<div class="flex flex-col ml-5 my-5">
|
||||
<strong class="font-extrabold">{item.name}</strong>
|
||||
<span class="text-sm font-medium">{item.creators}</span>
|
||||
{#if item.creators}<span class="text-sm font-medium">{item.creators}</span>{/if}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
|
@ -2,13 +2,13 @@
|
|||
|
||||
export function randomCover(itemid){
|
||||
let images = [
|
||||
'/static/book-cover.png',
|
||||
'/static/book-cover-2.png',
|
||||
'/static/book-cover-3.png',
|
||||
'/static/book-cover-4.png',
|
||||
'/static/book-cover-5.png',
|
||||
'/static/book-cover-6.png',
|
||||
'/static/book-cover-7.png',
|
||||
'/book-cover.png',
|
||||
'/book-cover-2.png',
|
||||
'/book-cover-3.png',
|
||||
'/book-cover-4.png',
|
||||
'/book-cover-5.png',
|
||||
'/book-cover-6.png',
|
||||
'/book-cover-7.png',
|
||||
|
||||
]
|
||||
return images[itemid.charCodeAt(0) % images.length];
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<script src="//cdn.tailwindcss.com/?plugins=forms,typography,aspect-ratio,line-clamp"></script>
|
||||
<script>
|
||||
tailwind.config = {
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
|
||||
primary: '#1e3a8a', //blue-900
|
||||
primary_light: '#FAFAFA', //neutral-50
|
||||
neutral_light: '#e5e5e5', // neutral-200
|
||||
neutral_dark: '#262626', // neutral-800
|
||||
secondary: '#6B21A8', // purple-800
|
||||
// primary_medium: '#60A5FA', //blue-400
|
||||
gradOne: '#DBEAFE', //blue-100
|
||||
gradTwo: '#F3E8FF', //purple-100
|
||||
},
|
||||
},
|
||||
fontFamily: {
|
||||
sans: ['Gentium Plus', 'sans'],
|
||||
serif: ['Libre Franklin','serif']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function darkmode(){
|
||||
return;
|
||||
tailwind.config.theme.extend.colors.primary = '#787A91';
|
||||
tailwind.config.theme.extend.colors.primary_light = '#262626';
|
||||
tailwind.config.theme.extend.colors.neutral_light = '#737373';
|
||||
tailwind.config.theme.extend.colors.neutral_dark = '#FAFAFA';
|
||||
tailwind.config.theme.extend.colors.secondary = '#FAFAFA';
|
||||
tailwind.config.theme.extend.colors.gradOne = '#444444';
|
||||
tailwind.config.theme.extend.colors.gradTwo = '#171717';
|
||||
tailwind.config.theme.extend.colors.primary_medium = '#141E61';
|
||||
}
|
||||
|
||||
function lightmode(){
|
||||
tailwind.config.theme.extend.colors.primary = '#1E3A8A';
|
||||
tailwind.config.theme.extend.colors.primary_light = '#FAFAFA';
|
||||
tailwind.config.theme.extend.colors.neutral_light = '#e5e5e5';
|
||||
tailwind.config.theme.extend.colors.neutral_dark = '#262626';
|
||||
tailwind.config.theme.extend.colors.secondary = '#6B21A8';
|
||||
tailwind.config.theme.extend.colors.gradOne = '#DBEAFE';
|
||||
tailwind.config.theme.extend.colors.gradTwo = '#F3E8FF';
|
||||
tailwind.config.theme.extend.colors.primary_medium = '#60A5FA';
|
||||
}
|
||||
|
||||
window.matchMedia("(prefers-color-scheme: dark)").matches ? darkmode() : lightmode();
|
||||
|
||||
window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", e => e.matches ? darkmode() : lightmode() );
|
||||
</script>
|
||||
|
||||
<script src="/bundle.js" defer></script>
|
||||
<link href="/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" />
|
||||
<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>
|
||||
|
||||
<!-- fonts -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Libre+Franklin:wght@300;400;500&display=swap" rel="stylesheet">
|
||||
|
||||
<!-- fonts Gentium Plus -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Gentium+Plus:ital,wght@0,400;0,700;1,400&display=swap" rel="stylesheet">
|
||||
</head>
|
||||
<body class="max-w-none mx-auto h-full bg-gradient-to-r from-gradOne to-gradTwo text-nutral_dark font-serif">
|
||||
<div class="h-full" id="app"></div>
|
||||
</body>
|
||||
</html>
|
Ładowanie…
Reference in New Issue