pull/1/head
Nilesh 2022-05-08 13:02:37 +01:00
commit 94cdb77f7f
38 zmienionych plików z 8267 dodań i 0 usunięć

2
.gitignore vendored 100644
Wyświetl plik

@ -0,0 +1,2 @@
node_modules

21
README.md 100644
Wyświetl plik

@ -0,0 +1,21 @@
# Learndb
Non-social version of https://learnawesome.org/
Run `datasette . -o` in the top-level directory.
Schema:
- Format: inline string like book, course, video, audio, podcast, newsletter, game, toy, website, article etc
- Topic (id - using slash or dot separator for hierarchy, display_name, image)
- Why not an inline string?
- Need to support Special characters (dot, hyphen etc), preserve capitalization etc
- Hierarchy may change over time
- Item (id, name, description, image, []{format, URL/hash}, rating, topic_id: [], creator_ids: [], year, difficulty, cost, quality_tags, extra_data: {})
- Creator (id, name, description, category, social_urls_or_ids, photo)
- Review/Recommendation (id, item_id, by: item_id/creator_id, rating, blurb, URL, quality_tags)
Additional pages:
- Syllabus page per topic
- Format page
To generate the sqlite database from the source files, run `generatedb.sh`

2
db/courses.csv 100644
Wyświetl plik

@ -0,0 +1,2 @@
topic,name,description,sequence
mathematics,Learn math in 21 minutes,,
1 topic name description sequence
2 mathematics Learn math in 21 minutes

2
db/creators.csv 100644
Wyświetl plik

@ -0,0 +1,2 @@
name,description,category,photo,social_urls
Bill Gates,techbro,founder,,@billgates
1 name description category photo social_urls
2 Bill Gates techbro founder @billgates

2
db/items.csv 100644
Wyświetl plik

@ -0,0 +1,2 @@
name,description,image,links,topics,creators,year,difficulty,cost,extra_data
sapiens,,https://learn-awesome.github.io/assets/book_covers/23692271.jpg,book|https://learnawesome.org/items/6c2ef4c8-a018-430a-9173-3868310a03ea-sapiens-a-brief-history-of-humankind,history
1 name,description,image,links,topics,creators,year,difficulty,cost,extra_data
2 sapiens,,https://learn-awesome.github.io/assets/book_covers/23692271.jpg,book|https://learnawesome.org/items/6c2ef4c8-a018-430a-9173-3868310a03ea-sapiens-a-brief-history-of-humankind,history

1
db/reviews.csv 100644
Wyświetl plik

@ -0,0 +1 @@
id,item_id,by,rating,blurb,url
1 id item_id by rating blurb url

6
db/topics.csv 100644
Wyświetl plik

@ -0,0 +1,6 @@
id,display_name,image,first_parent_topic_id,second_parent_topic_id
physics,Physics
mathematics,Maths,,physics,
language.english,English
programming.java,Java,,,,
history,History,,language.english,
1 id,display_name,image,first_parent_topic_id,second_parent_topic_id
2 physics,Physics
3 mathematics,Maths,,physics,
4 language.english,English
5 programming.java,Java,,,,
6 history,History,,language.english,

6
generatedb.sh 100755
Wyświetl plik

@ -0,0 +1,6 @@
rm learn.db
sqlite-utils insert learn.db creators db/creators.csv --csv
sqlite-utils insert learn.db topics db/topics.csv --csv
sqlite-utils insert learn.db items db/items.csv --csv
sqlite-utils insert learn.db reviews db/reviews.csv --csv
sqlite-utils insert learn.db courses db/courses.csv --csv

BIN
learn.db 100644

Plik binarny nie jest wyświetlany.

8
metadata.json 100644
Wyświetl plik

@ -0,0 +1,8 @@
{
"title": "LearnAwesome",
"description": "Curated collection of learning resources",
"license": "ISC",
"license_url": "",
"source": "LearnAwesome.org",
"source_url": "https://learnawesome.org/"
}

1950
package-lock.json wygenerowano 100644

Plik diff jest za duży Load Diff

24
package.json 100644
Wyświetl plik

@ -0,0 +1,24 @@
{
"name": "learndb",
"version": "1.0.0",
"private": true,
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "rollup -c",
"dev": "rollup -c -w",
"start": "sirv public --no-clear"
},
"devDependencies": {
"@rollup/plugin-commonjs": "^17.0.0",
"@rollup/plugin-node-resolve": "^11.0.0",
"rollup": "^2.3.4",
"rollup-plugin-css-only": "^3.1.0",
"rollup-plugin-livereload": "^2.0.0",
"rollup-plugin-svelte": "^7.0.0",
"rollup-plugin-terser": "^7.0.0",
"svelte": "^3.0.0"
},
"dependencies": {
"sirv-cli": "^2.0.0"
}
}

76
rollup.config.js 100644
Wyświetl plik

@ -0,0 +1,76 @@
import svelte from 'rollup-plugin-svelte';
import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import livereload from 'rollup-plugin-livereload';
import { terser } from 'rollup-plugin-terser';
import css from 'rollup-plugin-css-only';
const production = !process.env.ROLLUP_WATCH;
function serve() {
let server;
function toExit() {
if (server) server.kill(0);
}
return {
writeBundle() {
if (server) return;
server = require('child_process').spawn('npm', ['run', 'start', '--', '--dev'], {
stdio: ['ignore', 'inherit', 'inherit'],
shell: true
});
process.on('SIGTERM', toExit);
process.on('exit', toExit);
}
};
}
export default {
input: 'src/main.js',
output: {
sourcemap: true,
format: 'iife',
name: 'app',
file: 'static/bundle.js'
},
plugins: [
svelte({
compilerOptions: {
// enable run-time checks when not in production
dev: !production
}
}),
// we'll extract any component CSS out into
// a separate file - better for performance
css({ output: 'bundle.css' }),
// If you have external dependencies installed from
// npm, you'll most likely need these plugins. In
// some cases you'll need additional configuration -
// consult the documentation for details:
// https://github.com/rollup/plugins/tree/master/packages/commonjs
resolve({
browser: true,
dedupe: ['svelte']
}),
commonjs(),
// In dev mode, call `npm run start` once
// the bundle has been generated
!production && serve(),
// Watch the `public` directory and refresh the
// browser on changes when not in production
!production && livereload('public'),
// If we're building for production (npm run build
// instead of npm run dev), minify
production && terser()
],
watch: {
clearScreen: false
}
};

Wyświetl plik

@ -0,0 +1,82 @@
<script>
let query = '';
let results = [];
$: query && fetch(`/learn/items.json?_shape=array&name__contains=${query}`)
.then(r => r.json())
.then(data => {
results = data;
});
</script>
<div class="relative">
<div class="overflow-y-auto p-4 sm:p-6 md:p-20">
<!--
Command palette, show/hide based on modal state.
Entering: "ease-out duration-300"
From: "opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
To: "opacity-100 translate-y-0 sm:scale-100"
Leaving: "ease-in duration-200"
From: "opacity-100 translate-y-0 sm:scale-100"
To: "opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
-->
<div class="mx-auto max-w-xl transform overflow-hidden rounded-xl bg-white shadow-2xl ring-1 ring-black ring-opacity-5 transition-all">
<div class="relative">
<!-- Heroicon name: solid/search -->
<svg class="pointer-events-none absolute top-3.5 left-4 h-5 w-5 text-gray-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd" d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z" clip-rule="evenodd" />
</svg>
<input bind:value={query} type="text" class="h-12 w-full border-0 bg-transparent pl-11 pr-4 text-gray-800 placeholder-gray-400 focus:ring-0 sm:text-sm" placeholder="Search..." role="combobox" aria-expanded="false" aria-controls="options">
</div>
{#if !query }
<!-- Default state, show/hide based on command palette state -->
<div class="border-t border-gray-100 py-14 px-6 text-center text-sm sm:px-14">
<!-- Heroicon name: outline/globe -->
<svg class="mx-auto h-6 w-6 text-gray-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="M3.055 11H5a2 2 0 012 2v1a2 2 0 002 2 2 2 0 012 2v2.945M8 3.935V5.5A2.5 2.5 0 0010.5 8h.5a2 2 0 012 2 2 2 0 104 0 2 2 0 012-2h1.064M15 20.488V18a2 2 0 012-2h3.064M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<p class="mt-4 font-semibold text-gray-900">Search for items, topics and creators</p>
<p class="mt-2 text-gray-500">Quickly look for resources by running a global search.</p>
</div>
{:else if results.length > 0}
<!-- Results, show/hide based on command palette state -->
<ul class="max-h-80 scroll-pt-11 scroll-pb-2 space-y-2 overflow-y-auto pb-2" id="options" role="listbox">
<li>
<h2 class="bg-gray-100 py-2.5 px-4 text-xs font-semibold text-gray-900">Items</h2>
<ul class="mt-2 text-sm text-gray-800">
{#each results as item}
<li><a href="#/item/{item.rowid}" class="block cursor-default select-none px-4 py-2 hover:bg-indigo-600 hover:text-white cursor-pointer" id="option-1" role="option" tabindex="-1">{item.name}</a></li>
{/each}
</ul>
</li>
<li>
<h2 class="bg-gray-100 py-2.5 px-4 text-xs font-semibold text-gray-900">Topics</h2>
<ul class="mt-2 text-sm text-gray-800">
{#each results as topic}
<li><a href="#/topic/{topic.name}" class="block cursor-default select-none px-4 py-2 hover:bg-indigo-600 hover:text-white cursor-pointer" id="option-1" role="option" tabindex="-1">{topic.name}</a></li>
{/each}
</ul>
</li>
</ul>
{:else}
<!-- Empty state, show/hide based on command palette state -->
<div class="border-t border-gray-100 py-14 px-6 text-center text-sm sm:px-14">
<!-- Heroicon name: outline/emoji-sad -->
<svg class="mx-auto h-6 w-6 text-gray-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="M9.172 16.172a4 4 0 015.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<p class="mt-4 font-semibold text-gray-900">No results found</p>
<p class="mt-2 text-gray-500">We couldnt find anything with that term. Please try again.</p>
</div>
{/if}
</div>
</div>
</div>

64
src/App.svelte 100644
Wyświetl plik

@ -0,0 +1,64 @@
<script>
import { onMount } from 'svelte';
import * as TailwindUI from "./tailwindui/index"
import Home from "./Home.svelte"
import TopicList from "./TopicList.svelte"
import TopicDetail from "./TopicDetail.svelte"
import FormatList from "./FormatList.svelte"
import FormatDetail from "./FormatDetail.svelte"
import CourseList from "./CourseList.svelte"
import ItemDetail from "./ItemDetail.svelte"
import AdvancedSearch from "./AdvancedSearch.svelte"
let sidebarItems = [
{text: "Topics", link: "#/topics", icon: "home"},
{text: "Formats", link: "#/formats", icon: "home"},
{text: "Random item", link: "#/item/1", icon: "home"},
{text: "Search", link: "#/search", icon: "home"},
{text: "Datasette", link: "/learn", icon: "home"}
]
let currentView = "/topics";
function handleTabChanged(event) {
currentView = event.detail.tab;
}
async function hashchange() {
// the poor man's router!
const path = window.location.hash.slice(1);
if (path.length > 0) {
currentView = path
} else {
window.location.hash = '/home';
currentView = '/home'
}
}
onMount(hashchange);
</script>
<svelte:window on:hashchange={hashchange}/>
<TailwindUI.AppShell {sidebarItems}>
{#if currentView === "/home" || currentView === "/"}
<Home/>
{:else if currentView === "/topics"}
<TopicList/>
{:else if currentView.startsWith("/topic/")}
<TopicDetail topic={currentView.split("/")[2]}/>
{:else if currentView === "/formats"}
<FormatList/>
{:else if currentView.startsWith("/format/")}
<FormatDetail format={currentView.split("/")[2]}/>
{:else if currentView === "/courses"}
<CourseList/>
{:else if currentView.startsWith("/item/")}
<ItemDetail itemid={currentView.split("/")[2]}/>
{:else if currentView === "/search"}
<AdvancedSearch/>
{/if}
</TailwindUI.AppShell>

Wyświetl plik

Wyświetl plik

@ -0,0 +1,3 @@
<script>
</script>

Wyświetl plik

@ -0,0 +1,3 @@
<script>
</script>

Wyświetl plik

@ -0,0 +1,21 @@
<script>
import ItemCard from "./ItemCard.svelte"
export let format;
let items = [];
$: fetch(`/learn/items.json?_shape=array&links__contains=${format}|`)
.then(r => r.json())
.then(data => {
items = data;
});
</script>
<div class="md:flex md:items-center md:justify-between mb-8">
<div class="flex-1 min-w-0">
<h2 class="text-2xl font-bold leading-7 text-gray-900 sm:text-3xl sm:truncate">{format}</h2>
</div>
</div>
{#each items as item}
<ItemCard {item}/>
{/each}

Wyświetl plik

@ -0,0 +1,34 @@
<script>
let dataPromise = getData();
async function getData() {
const res = await fetch(`/learn.json?_shape=array&sql=select+distinct(substr(links%2C1%2Cinstr(links%2C'|')-1))+as+name+from+items`)
if(res.ok){
return await res.json();
} else {
throw new Error()
}
}
</script>
{#await dataPromise}
<p>Fetching data...</p>
{:then formats}
<div class="mt-6" style="columns: 6 240px; column-gap: 1rem;">
{#each formats as format}
<div tabindex="0" class="inline-block w-full mt-4 bg-white rounded-lg mt-4 px-4 py-4 shadow-lg focus:outline-none">
<a href="#/format/{format.name}"><h4 class="mt-1 p-1 text-gray-900 font-semibold text-lg">{ format.name }</h4></a>
<div class="mt-2 flex flex-wrap text-sm text-gray-900">
</div>
<p class="mt-2 text-sm text-right"><span>and 37 more.</span></p>
</div>
{/each}
</div>
{:catch error}
<p>{error.message}</p>
{/await}

37
src/Home.svelte 100644
Wyświetl plik

@ -0,0 +1,37 @@
<main class="mt-16 mx-auto max-w-7xl px-4 sm:mt-24 sm:px-6 lg:mt-32">
<div class="lg:grid lg:grid-cols-12 lg:gap-8">
<div class="sm:text-center md:max-w-2xl md:mx-auto lg:col-span-6 lg:text-left">
<h1>
<span class="block text-sm font-semibold uppercase tracking-wide text-gray-500 sm:text-base lg:text-sm xl:text-base">Coming soon</span>
<span class="mt-1 block text-4xl tracking-tight font-extrabold sm:text-5xl xl:text-6xl">
<span class="block text-gray-900">Data to enrich your</span>
<span class="block text-indigo-600">online business</span>
</span>
</h1>
<p class="mt-3 text-base text-gray-500 sm:mt-5 sm:text-xl lg:text-lg xl:text-xl">Anim aute id magna aliqua ad ad non deserunt sunt. Qui irure qui lorem cupidatat commodo. Elit sunt amet fugiat veniam occaecat fugiat aliqua ad ad non deserunt sunt.</p>
</div>
<div class="mt-12 relative sm:max-w-lg sm:mx-auto lg:mt-0 lg:max-w-none lg:mx-0 lg:col-span-6 lg:flex lg:items-center">
<svg class="absolute top-0 left-1/2 transform -translate-x-1/2 -translate-y-8 scale-75 origin-top sm:scale-100 lg:hidden" width="640" height="784" fill="none" viewBox="0 0 640 784" aria-hidden="true">
<defs>
<pattern id="4f4f415c-a0e9-44c2-9601-6ded5a34a13e" x="118" y="0" width="20" height="20" patternUnits="userSpaceOnUse">
<rect x="0" y="0" width="4" height="4" class="text-gray-200" fill="currentColor" />
</pattern>
</defs>
<rect y="72" width="640" height="640" class="text-gray-50" fill="currentColor" />
<rect x="118" width="404" height="784" fill="url(#4f4f415c-a0e9-44c2-9601-6ded5a34a13e)" />
</svg>
<div class="relative mx-auto w-full rounded-lg shadow-lg lg:max-w-md">
<button type="button" class="relative block w-full bg-white rounded-lg overflow-hidden focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
<span class="sr-only">Watch our video to learn more</span>
<img class="w-full" src="https://images.unsplash.com/photo-1556740758-90de374c12ad?ixlib=rb-1.2.1&auto=format&fit=crop&w=1350&q=80" alt="">
<div class="absolute inset-0 w-full h-full flex items-center justify-center" aria-hidden="true">
<svg class="h-20 w-20 text-indigo-500" fill="currentColor" viewBox="0 0 84 84">
<circle opacity="0.9" cx="42" cy="42" r="42" fill="white" />
<path d="M55.5039 40.3359L37.1094 28.0729C35.7803 27.1869 34 28.1396 34 29.737V54.263C34 55.8604 35.7803 56.8131 37.1094 55.9271L55.5038 43.6641C56.6913 42.8725 56.6913 41.1275 55.5039 40.3359Z" />
</svg>
</div>
</button>
</div>
</div>
</div>
</main>

Wyświetl plik

@ -0,0 +1,9 @@
<script>
export let item;
</script>
<a href="#/item/{item.rowid}">
<img class="h-64 w-44 mr-6 mb-6 float-left border border-purple-200 rounded-md shadow-md md:shadow-xl transform transition ease-out duration-300 hover:scale-105"
src={item.image} alt="">
</a>

Wyświetl plik

@ -0,0 +1,23 @@
<script>
export let itemid;
let item;
$: fetch(`/learn/items/${itemid}.json?_shape=object`)
.then(r => r.json())
.then(data => {
item = data[itemid];
});
</script>
{#if item}
<h1>{item.name}</h1>
<a href="#/item/{item.rowid}">
<img class="h-64 w-44 mr-6 mb-6 float-left border border-purple-200 rounded-md shadow-md md:shadow-xl transform transition ease-out duration-300 hover:scale-105"
src={item.image} alt="">
</a>
{:else}
<p class="loading">loading...</p>
{/if}

Wyświetl plik

@ -0,0 +1,5 @@
<script>
</script>
review

Wyświetl plik

@ -0,0 +1,22 @@
<script>
export let topic;
import Icon from "./tailwindui/Icon.svelte"
$: abbr = topic.display_name.slice(0,2).toUpperCase()
</script>
<li class="col-span-1 flex shadow-sm rounded-md">
<div class="flex-shrink-0 flex items-center justify-center w-16 bg-pink-600 text-white text-sm font-medium rounded-l-md">{abbr}</div>
<div class="flex-1 flex items-center justify-between border-t border-r border-b border-gray-200 bg-white rounded-r-md truncate">
<div class="flex-1 px-4 py-2 text-sm truncate">
<a href={"/topic/" + topic.id} class="text-gray-900 font-medium hover:text-gray-600">{topic.display_name}</a>
<p class="text-gray-500">{topic.rowid} items</p>
</div>
<div class="flex-shrink-0 pr-2">
<button type="button" class="w-8 h-8 bg-white inline-flex items-center justify-center text-gray-400 rounded-full bg-transparent hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
<span class="sr-only">Open options</span>
<Icon kind="dots"/>
</button>
</div>
</div>
</li>

Wyświetl plik

@ -0,0 +1,23 @@
<script>
import ItemCard from "./ItemCard.svelte"
export let topic;
let items = [];
$: fetch(`/learn/items.json?_shape=array&topics__contains=${topic}`)
.then(r => r.json())
.then(data => {
items = data;
});
</script>
<div class="md:flex md:items-center md:justify-between mb-8">
<div class="flex-1 min-w-0">
<h2 class="text-2xl font-bold leading-7 text-gray-900 sm:text-3xl sm:truncate">{topic}</h2>
</div>
</div>
{#each items as item}
<ItemCard {item}/>
{/each}

Wyświetl plik

@ -0,0 +1,50 @@
<script>
import TopicCard from "./TopicCard.svelte"
let dataPromise = getData();
async function getData() {
const res = await fetch(`/learn/topics.json?_shape=array`)
if(res.ok){
return await res.json();
} else {
throw new Error()
}
}
function hierarchy(topics){
return topics.reduce((map, topic) => {
if(!topic.first_parent_topic_id) {
map.set(topic, []);
} else {
let parent = topics.find(t => t.id == topic.first_parent_topic_id)
map.set(parent, [...map.get(parent), topic])
}
return map;
}, new Map())
}
</script>
{#await dataPromise}
<p>Fetching data...</p>
{:then topics}
<div class="mt-6" style="columns: 6 240px; column-gap: 1rem;">
{#each [...hierarchy(topics).keys()] as parent}
<div tabindex="0" class="inline-block w-full mt-4 bg-white rounded-lg mt-4 px-4 py-4 shadow-lg focus:outline-none">
<h4 class="mt-1 p-1 text-gray-900 font-semibold text-lg">{ parent.display_name }</h4>
<div class="mt-2 flex flex-wrap text-sm text-gray-900">
{#each hierarchy(topics).get(parent) as child}
<a href={"#/topic/" + child.id} class="text-purple-600 no-underline hover:underline hover:text-purple-900 px-2">{child.display_name}</a>
{/each}
</div>
<p class="mt-2 text-sm text-right"><span>and 37 more.</span></p>
</div>
{/each}
</div>
{:catch error}
<p>{error.message}</p>
{/await}

5
src/main.js 100644
Wyświetl plik

@ -0,0 +1,5 @@
import App from './App.svelte';
new App({
target: document.querySelector('#app'),
});

Wyświetl plik

@ -0,0 +1,134 @@
<script>
import Icon from "./Icon.svelte"
import MenuButton from "./MenuButton.svelte"
import SearchForm from "./SearchForm.svelte"
export let sidebarItems = [];
let isNavDrawerOpen = false
export let showNotificationBell = false;
export let showProfileMenu = false;
</script>
<div>
<!-- Off-canvas menu for mobile, show/hide based on off-canvas menu state. -->
{#if isNavDrawerOpen}
<div class="relative z-40 md:hidden" role="dialog" aria-modal="true">
<!--
Off-canvas menu backdrop, show/hide based on off-canvas menu state.
Entering: "transition-opacity ease-linear duration-300"
From: "opacity-0"
To: "opacity-100"
Leaving: "transition-opacity ease-linear duration-300"
From: "opacity-100"
To: "opacity-0"
-->
<div class="fixed inset-0 bg-gray-600 bg-opacity-75"></div>
<div class="fixed inset-0 flex z-40">
<!--
Off-canvas menu, show/hide based on off-canvas menu state.
Entering: "transition ease-in-out duration-300 transform"
From: "-translate-x-full"
To: "translate-x-0"
Leaving: "transition ease-in-out duration-300 transform"
From: "translate-x-0"
To: "-translate-x-full"
-->
<div class="relative flex-1 flex flex-col max-w-xs w-full pt-5 pb-4 bg-indigo-700">
<!--
Close button, show/hide based on off-canvas menu state.
Entering: "ease-in-out duration-300"
From: "opacity-0"
To: "opacity-100"
Leaving: "ease-in-out duration-300"
From: "opacity-100"
To: "opacity-0"
-->
<div class="absolute top-0 right-0 -mr-12 pt-2">
<button on:click={e => isNavDrawerOpen = false} type="button" class="ml-1 flex items-center justify-center h-10 w-10 rounded-full focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white">
<span class="sr-only">Close sidebar</span>
<!-- Heroicon name: outline/x -->
<svg class="h-6 w-6 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<div class="flex-shrink-0 flex items-center px-4">
<a href="#/" class="">LearnAwesome</a>
</div>
<div class="mt-5 flex-1 h-0 overflow-y-auto">
<nav class="px-2 space-y-1">
<!-- Current: "bg-indigo-800 text-white", Default: "text-indigo-100 hover:bg-indigo-600" -->
{#each sidebarItems as { text, link, icon }, i}
<a href={link} class="w-full bg-indigo-800 text-white group flex items-center px-2 py-2 text-base font-medium rounded-md">
<Icon kind={icon}/>
{text}
</a>
{/each}
</nav>
</div>
</div>
<div class="flex-shrink-0 w-14" aria-hidden="true">
<!-- Dummy element to force sidebar to shrink to fit close icon -->
</div>
</div>
</div>
{/if}
<!-- Static sidebar for desktop -->
<div class="hidden md:flex md:w-64 md:flex-col md:fixed md:inset-y-0">
<!-- Sidebar component, swap this element with another sidebar if you like -->
<div class="flex flex-col flex-grow pt-5 bg-indigo-700 overflow-y-auto">
<div class="flex items-center flex-shrink-0 px-4 bg-white">
<a href="/" class="">LearnAwesome</a>
</div>
<div class="mt-5 flex-1 flex flex-col">
<nav class="flex-1 px-2 pb-4 space-y-1">
<!-- Current: "bg-indigo-800 text-white", Default: "text-indigo-100 hover:bg-indigo-600" -->
{#each sidebarItems as { text, link, icon }, i}
<a href={link} class="w-full bg-indigo-800 text-white group flex items-center px-2 py-2 text-sm font-medium rounded-md">
<Icon kind={icon}/>
{text}
</a>
{/each}
</nav>
</div>
</div>
</div>
<div class="md:pl-64 flex flex-col flex-1">
<div class="sticky top-0 z-10 flex-shrink-0 flex h-16 bg-white shadow">
<button on:click={e => isNavDrawerOpen = true} type="button" class="px-4 border-r border-gray-200 text-gray-500 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-500 md:hidden">
<span class="sr-only">Open sidebar</span>
<Icon kind="menu"/>
</button>
<div class="flex-1 px-4 flex justify-between">
{#if showNotificationBell || showProfileMenu}
<div class="ml-4 flex items-center md:ml-6">
{#if showNotificationBell}
<button type="button" class="bg-white p-1 rounded-full text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
<span class="sr-only">View notifications</span>
<Icon kind="bell"/>
</button>
{/if}
{#if showProfileMenu}<MenuButton />{/if}
</div>
{/if}
</div>
</div>
<main>
<div class="py-6">
<div class="max-w-7xl mx-auto px-4 sm:px-6 md:px-8">
<slot></slot>
</div>
</div>
</main>
</div>
</div>

Wyświetl plik

@ -0,0 +1,31 @@
<script>
export let kind;
export let size;
</script>
{#if kind === 'home'}
<!-- Heroicon name: outline/home -->
<svg class="mr-4 flex-shrink-0 h-6 w-6 text-indigo-300" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
</svg>
{:else if kind === "menu"}
<!-- Heroicon name: outline/menu-alt-2 -->
<svg class="h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="M4 6h16M4 12h16M4 18h7" />
</svg>
{:else if kind === "bell"}
<!-- Heroicon name: outline/bell -->
<svg class="h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" />
</svg>
{:else if kind === "search"}
<!-- Heroicon name: solid/search -->
<svg class="h-5 w-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd" d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z" clip-rule="evenodd" />
</svg>
{:else if kind === "dots"}
<!-- Heroicon name: solid/dots-vertical -->
<svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path d="M10 6a2 2 0 110-4 2 2 0 010 4zM10 12a2 2 0 110-4 2 2 0 010 4zM10 18a2 2 0 110-4 2 2 0 010 4z" />
</svg>
{/if}

Wyświetl plik

@ -0,0 +1,28 @@
<!-- Profile dropdown -->
<div class="ml-3 relative">
<div>
<button type="button" class="max-w-xs bg-white flex items-center text-sm rounded-full focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" id="user-menu-button" aria-expanded="false" aria-haspopup="true">
<span class="sr-only">Open user menu</span>
<img class="h-8 w-8 rounded-full" src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80" alt="">
</button>
</div>
<!--
Dropdown menu, show/hide based on menu state.
Entering: "transition ease-out duration-100"
From: "transform opacity-0 scale-95"
To: "transform opacity-100 scale-100"
Leaving: "transition ease-in duration-75"
From: "transform opacity-100 scale-100"
To: "transform opacity-0 scale-95"
-->
<div class="origin-top-right absolute right-0 mt-2 w-48 rounded-md shadow-lg py-1 bg-white ring-1 ring-black ring-opacity-5 focus:outline-none" role="menu" aria-orientation="vertical" aria-labelledby="user-menu-button" tabindex="-1">
<!-- Active: "bg-gray-100", Not Active: "" -->
<a href="#" class="block px-4 py-2 text-sm text-gray-700" role="menuitem" tabindex="-1" id="user-menu-item-0">Your Profile</a>
<a href="#" class="block px-4 py-2 text-sm text-gray-700" role="menuitem" tabindex="-1" id="user-menu-item-1">Settings</a>
<a href="#" class="block px-4 py-2 text-sm text-gray-700" role="menuitem" tabindex="-1" id="user-menu-item-2">Sign out</a>
</div>
</div>

Wyświetl plik

@ -0,0 +1,38 @@
<script>
import Icon from "./Icon.svelte"
let query;
let results = [];
function handleSubmit(ev){
console.log({query})
}
</script>
<form class="w-full flex md:ml-0" on:submit|preventDefault={handleSubmit}>
<label for="search-field" class="sr-only">Search</label>
<div class="relative w-full text-gray-400 focus-within:text-gray-600">
<div class="absolute inset-y-0 left-0 flex items-center pointer-events-none">
<Icon kind="search"/>
</div>
<input bind:value={query} class="block w-full h-full pl-8 pr-3 py-2 border-transparent text-gray-900 placeholder-gray-500 focus:outline-none focus:placeholder-gray-400 focus:ring-0 focus:border-transparent sm:text-sm" placeholder="Search" type="search" name="search">
</div>
</form>
<ul class="max-h-80 scroll-pt-11 scroll-pb-2 space-y-2 overflow-y-auto pb-2" id="options" role="listbox">
<li>
<h2 class="bg-gray-100 py-2.5 px-4 text-xs font-semibold text-gray-900">Clients</h2>
<ul class="mt-2 text-sm text-gray-800">
<!-- Active: "bg-indigo-600 text-white" -->
<li class="cursor-default select-none px-4 py-2" id="option-1" role="option" tabindex="-1">Workflow Inc.</li>
<li class="cursor-default select-none px-4 py-2" id="option-2" role="option" tabindex="-1">Multinational LLC.</li>
</ul>
</li>
<li>
<h2 class="bg-gray-100 py-2.5 px-4 text-xs font-semibold text-gray-900">Projects</h2>
<ul class="mt-2 text-sm text-gray-800">
<!-- Active: "bg-indigo-600 text-white" -->
<li class="cursor-default select-none px-4 py-2" id="option-3" role="option" tabindex="-1">Workflow Inc. / Website Redesign</li>
<li class="cursor-default select-none px-4 py-2" id="option-3" role="option" tabindex="-1">Multinational LLC. / Animation</li>
</ul>
</li>
</ul>

Wyświetl plik

@ -0,0 +1,3 @@
export {default as Icon} from './Icon.svelte'
export {default as MenuButton} from './MenuButton.svelte'
export {default as AppShell} from './AppShell.svelte'

Wyświetl plik

@ -0,0 +1 @@
h1.svelte-b0hmqh{background-color:yellow}

5535
static/bundle.js 100644

Plik diff jest za duży Load Diff

File diff suppressed because one or more lines are too long

Wyświetl plik

Wyświetl plik

@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="en" class="h-full bg-gray-100">
<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 src="//unpkg.com/alpinejs" defer></script>
<script src="/static/bundle.js" defer></script>
<link href="/static/bundle.css" rel="stylesheet" />
<title>LearnAwesome</title>
</head>
<body class="h-full">
<div class="h-full" id="app"></div>
</body>
</html>