├── .github └── FUNDING.yml ├── .gitignore ├── .tool-versions ├── AppIcon.icns ├── README.md ├── appdmg.json ├── db ├── README.md ├── creators.json ├── export_json.rb ├── import_json.rb ├── items.json ├── jsonlines.js ├── postgres.rb ├── schema.sql ├── topics.json └── zlib_search.rb ├── index.html ├── netlify.toml ├── netlify └── functions │ ├── form.html │ ├── handleMetadata.js │ ├── package-lock.json │ └── package.json ├── neutralino.config.json ├── package-lock.json ├── package.json ├── postcss.config.cjs ├── public ├── ._book-cover-2.png ├── ._book-cover-3.png ├── ._book-cover-4.png ├── ._book-cover-5.png ├── ._book-cover-6.png ├── ._book-cover-7.png ├── ._book-cover.png ├── ._course.jpg ├── ._play-solid.svg ├── book-cover-2.png ├── book-cover-3.png ├── book-cover-4.png ├── book-cover-5.png ├── book-cover-6.png ├── book-cover-7.png ├── book-cover.png ├── course.jpg ├── icons │ ├── appIcon.png │ └── trayIcon.png ├── neutra.js └── play-solid.svg ├── scripts └── remove_404.js ├── src ├── ._oEmbedProviders.js ├── AdvancedSearch.svelte ├── App.svelte ├── AppShell.svelte ├── BookCard.svelte ├── Bookmarks.svelte ├── ButtonGroup.svelte ├── Creator.svelte ├── FormatDetail.svelte ├── FormatList.svelte ├── GenericCard.svelte ├── Home.svelte ├── Icon.svelte ├── ItemCard.svelte ├── ItemDetail.svelte ├── ItemList.svelte ├── MasonryItem.svelte ├── NavButtonWithLabel.svelte ├── PancakeTreemap.svelte ├── Review.svelte ├── Roadmap.svelte ├── RoadmapList.svelte ├── SearchForm.svelte ├── Settings.svelte ├── SkillTree.svelte ├── TopicDetail.svelte ├── TopicList.svelte ├── TopicMasonryGrid.svelte ├── TreeMap.svelte ├── TreemapNode.svelte ├── VideoCard.svelte ├── app.postcss ├── formats.js ├── main.js ├── oEmbedProviders.js ├── persistStore.js ├── placeholder.ts ├── roadmapAlt.svelte ├── roadmap_data.js ├── stores.js └── utility.js ├── svelte.config.js ├── tailwind.config.cjs └── vite.config.js /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | open_collective: learnawesome 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | node_modules 4 | 5 | db/*matches.json 6 | 7 | /static 8 | 9 | # Developer tools' files 10 | .lite_workspace.lua 11 | 12 | # Neutralinojs binaries and builds 13 | /bin 14 | /dist 15 | 16 | # Neutralinojs client (minified) 17 | neutralino.js 18 | 19 | # Neutralinojs related files 20 | .storage 21 | *.log 22 | 23 | -------------------------------------------------------------------------------- /.tool-versions: -------------------------------------------------------------------------------- 1 | nodejs 20.18.0 2 | -------------------------------------------------------------------------------- /AppIcon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learn-awesome/learndb/e3b5e2e5df61e8d27259a33a162efb49b6454a07/AppIcon.icns -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SkillTree 2 | 3 | [Visit the online version at https://skilltree.worldclass.quest/](https://skilltree.worldclass.quest/) but you can also run it on your own machine. 4 | 5 | This is a collection of learning resources organized by topics, formats, difficulty levels, and quality tags like visual / interactive / challenging etc. It also includes reviews from experts and metadata like paywall/loginwall to help you find the best resource for your learning goals. 6 | 7 | For certain resources like research paper or books, there will be direct links thanks to projects like InternetArchive, LibGen, Arxiv, SciHub etc. 8 | 9 | This requires us to build a giant taxonomy of all human knowledge. Arranging topics in a hierarchy is not sufficient. Instead we are creating a graph of topics and levels with multiple types of edges: "is-a-subtopic-of", "is-a-prerequisite-of" etc. If you are an expert or educator in some domain, you can contribute to this project via our GitHub repository. 10 | 11 | In conjunction with this, we're also building an online game where this is presented as a skill-tree for life and allows you to chase ambitious life goals and keep track of your progress while inspiring and being inspired by your friends. More on this will be revealed soon. 12 | 13 | ## To use: 14 | 15 | [Visit https://skilltree.worldclass.quest/](https://skilltree.worldclass.quest/) 16 | 17 | image 18 | 19 | image 20 | 21 | image 22 | 23 | 24 | Your bookmarks are saved in localStorage so be assured that no personal data is being tracked or saved on this site. 25 | 26 | ## To contribute: 27 | 28 | This is a Wikipedia-scale project and we could use all kind of help: 29 | 30 | - Spread word about this project among your friends, family, colleagues and online followers 31 | - To donate funds, [visit our OpenCollective](https://opencollective.com/learnawesome) 32 | - To report bugs, [create an issue](https://github.com/learn-awesome/learndb/issues) 33 | - To improve the topic taxonomy or to add/modify the dataset, wait till we put together a public contribution workflow. You can always fork and create your own collection at any time. 34 | - To improve design and suggest features, [start a discussion](https://github.com/learn-awesome/learndb/discussions) 35 | - To fix technical bugs, [propose solutions on the issues](https://github.com/learn-awesome/learndb/issues) 36 | - For anything else, [start a discussion](https://github.com/learn-awesome/learndb/discussions) 37 | 38 | 39 | 40 | ## Architecture 41 | 42 | There are no user accounts, no social features like learning feeds or ActivityPub. Users' bookmarks are saved in browser's localStorage. 43 | 44 | The source data is in `db/*.json` files. The schema is described in [db/README.md](db/README.md). 45 | For the front-end, we write Svelte components in `src` and generate `bundle.js` and `bundle.css` via `npm run dev` / `npm run build`. 46 | 47 | For UI, we make use of TailwindCSS (currently loaded via CDN with some plugins) and Shoelace.Style. Whenever possible, we use Shoelace's existing components. 48 | 49 | ## Build 50 | 51 | We use Neutralino.js to generate native apps for Mac/Windows/Linux. Currently only the .app file (not .dmg) for Mac runs correctly. 52 | 53 | ``` 54 | neu build 55 | mv dist/learndb/learndb-mac_x64 dist/learndb/learndb-mac_x64.app 56 | chmod +x dist/learndb/learndb-mac_x64.app 57 | appdmg ./appdmg.json dist/learndb/LearnDB.dmg 58 | ``` 59 | -------------------------------------------------------------------------------- /appdmg.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "LearnDB", 3 | "icon": "AppIcon.icns", 4 | "contents": [ 5 | { "x": 448, "y": 344, "type": "link", "path": "/Applications" }, 6 | { "x": 192, "y": 344, "type": "file", "path": "dist/learndb/learndb-mac_x64.app" } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /db/README.md: -------------------------------------------------------------------------------- 1 | # JSON format 2 | 3 | These JSON files can also be generated from a PostgreSQL database whose schema is defined in `schema.sql`. 4 | 5 | We have 3 top-level entities: [Topics](topics.json), [Creators](creators.json), [Items](items.json). Reviews are are nested within Items and have links to either another Item or a Creator. 6 | 7 | topis (name, hname, parent, rank) 8 | creators (name, hname, links) 9 | items (id, name, description, image, splinks, topics, creators, reviews, tags, difficulty, rating) 10 | 11 | spnames are topic names designed to fix many shortcomings of traditional names: 12 | - we want case-insensitive, unique, URL-safe, hashtag-compatible names as topic identifiers 13 | - but case-sensitive and including special characters for human display. Eg: "AT & T" or "C++ 20" 14 | - topics have a taxonomy, typically a hierarchy. For eg: comp.lang.python 15 | - within a parent, we usually want to preserve display order which is not alphabetical. This can be done with names like "100-physics", "200-chemistry", "300-biology" etc. 16 | 17 | Wikipedia handles it awkwardly (escape characters that are hard to type manually, no hierarchy): 18 | - https://en.wikipedia.org/wiki/Zorn%27s_lemma 19 | - https://en.wikipedia.org/wiki/C%2B%2B 20 | 21 | [Newsgroups](https://www.big-8.org/wiki/Big-8_Usenet_hierarchies) are quite nice, but the taxonomy is not granular enough for us to build a universal knowledge graph. For eg: there is [no name yet](https://news.novabbs.org/usenet/article-flat.php?id=34&group=news.announce.newgroups#34) for quadratic equations. Also, everything other than Big-8 (comp, humanities, misc, news, rec, sci, soc, and talk) is shoved into the alt.* hierarchy (the historical reason for that is European networks did not want to pay for groups like religion or racism) 22 | 23 | Taxonomy maintenance is not easy. When does a concept/subtopic deserve its own topic-name? What happens when topics get merged or retired? Should they be language-agnostic or English-first? 24 | 25 | Should names optimize for brevity or readability? What if names get really long? 26 | Should names always fully-specified like `math.algebra.quadratics` or just `quadratics`? 27 | If sort order is included in the name, do we expect the users to write "100-science.200-chemistry"? 28 | If sort order is NOT included in the name then it (and other attributes) needs to be looked up elsewhere. 29 | What if a topic belongs under two separate parent topics? statistics.machine_learning or computer_science.machine_learning? 30 | Can names be compatible with hashtags? 31 | How to handle disambiguation (a name that belongs to 2+ things or a thing that has 2+ names? 32 | How about nameless things or topics? 33 | Should names be meaningful or randomized? 34 | Are names properties (like DNS or ENS)? If not, who assigns them? 35 | How can a taxonomy keep up with expanding knowledge? 36 | 37 | 38 | splinks are HTTP URLs modified specially in a few ways: 39 | - Link should indicate the learning media type (eg: book/course/video/game/event etc) 40 | - Link should optionally include content-hash for integrity check and alternat fetch mechanism like IPFS 41 | - Link should optionally include enable easy lookup for its snapshot on places like Wayback machine 42 | - Link should optionally indicate its open-access status: 43 | - Does it require login? 44 | - Does it require payment? 45 | - Does it require ads? 46 | - Links should be backward-compatible and should navigate to primary location in the usual way 47 | 48 | ## topics.json 49 | 50 | `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`. 51 | 52 | `hname` is used as human-readable name and can preserve uppercase. For eg: `ADHD`. 53 | 54 | `parent` should be the name of the parent topic. This makes it possible to show a hierarchical view. If a topic does not have `parent_name`, 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`. 55 | 56 | `rank` is an integer that's used for controlling the ordering in which topics are displayed. 57 | 58 | ## creators.json 59 | 60 | A top-level table that lists well-known experts and their metadata like occupation, links etc. These are references from items and their reviews. 61 | `name` is unique, URL-safe. 62 | `hname` is case-sensitive, allows special characters and may not be unique. 63 | 64 | ## items.json 65 | 66 | `id` should be a unique UUID. It is needed because there is no other natural primary key. This might also be useful later for defining collections of items. 67 | 68 | `description` can contain markdown with multiple lines. 69 | 70 | `links` is an array of strings. Each item in this array is `format`, `url` and optional identifiers separated by `|`. For eg, one of the strings in `links` might: `summary|https://sivers.org/book/Decisive|ipfs:bafykbzaceaejt6z54qnwnl3ccvw2lsdfksbeuwuh4sv77ixj4c3ldeof2c5so?filename=Daniel%20Higginbotham%20-%20Clojure%20for%20the%20Brave%20and%20True-No%20Starch%20Press%20%282015%29.pdf`. 71 | 72 | The use-case for optional identifiers are things like `ipfs:`, `doi:`, `isbn:` or `isbn13:`. 73 | 74 | `topics` is an array of topic names. These should exactly match `topics` table's `name` column. 75 | 76 | `creators` is an array of strings which are references to the `creators` table's `name` column. For eg: `charles_darwin`. 77 | 78 | `difficulty` must be either `null` or one of these: `childlike`, `beginner`, `intermediate`, `advanced`, `research`. 79 | 80 | `rating` is an integer on 0-100 point scale. This is a curated value and should not be simply copied from external sources. 81 | 82 | `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. 83 | 84 | `reviews` is an array of JSON objects that must match this schema as you can see in `schema.sql`: 85 | 86 | ``` 87 | { 88 | "type": "array", 89 | "items": { 90 | "type": "object", 91 | "properties": { 92 | "by_item": {"type": ["string", "null"]}, 93 | "by_creator": {"type": ["string", "null"]}, 94 | "rating": {"type": ["integer", "null"], "minimum": 0, "maximum": 100}, 95 | "blurb": {"type": ["string", "null"]}, 96 | "url": {"type": ["string", "null"]} 97 | } 98 | } 99 | } 100 | ``` 101 | -------------------------------------------------------------------------------- /db/creators.json: -------------------------------------------------------------------------------- 1 | [ 2 | {"name":"first-round-capital","hname":"First Round Capital"}, 3 | {"name":"hacker-news","hname":"Hacker News"}, 4 | {"name":"farnam-street","hname":"Farnam Street"}, 5 | {"name":"designer-news","hname":"Designer News"}, 6 | {"name":"uxtools","hname":"UXTools"}, 7 | {"name":"indie-hackers","hname":"Indie Hackers"}, 8 | {"name":"reddit","hname":"Reddit"}, 9 | {"name":"nobel-prize","hname":"Nobel Prize"}, 10 | {"name":"booker-prize","hname":"Booker Prize"}, 11 | {"name":"hugo-awards","hname":"Hugo Awards"}, 12 | {"name":"elon-musk","hname":"Elon Musk"}, 13 | {"name":"dustin-moskovitz","hname":"Dustin Moskovitz"}, 14 | {"name":"jeff-bezos","hname":"Jeff Bezos"}, 15 | {"name":"bill-gates","hname":"Bill Gates"}, 16 | {"name":"mark-zuckerberg","hname":"Mark Zuckerberg"}, 17 | {"name":"jack-dorsey","hname":"Jack Dorsey"}, 18 | {"name":"tony-hsieh","hname":"Tony Hsieh"}, 19 | {"name":"patrick-collison","hname":"Patrick Collison"}, 20 | {"name":"larry-page","hname":"Larry Page"}, 21 | {"name":"pavel-dunrov","hname":"Pavel Dunrov"}, 22 | {"name":"max-levchin","hname":"Max Levchin"}, 23 | {"name":"richard-branson","hname":"Richard Branson"}, 24 | {"name":"salman-khan","hname":"Salman Khan"}, 25 | {"name":"evan-williams","hname":"Evan Williams"}, 26 | {"name":"mark-cuban","hname":"Mark Cuban"}, 27 | {"name":"steve-jobs","hname":"Steve Jobs"}, 28 | {"name":"kevin-rose","hname":"Kevin Rose"}, 29 | {"name":"reid-hoffman","hname":"Reid Hoffman"}, 30 | {"name":"travis-kalanick","hname":"Travis Kalanick"}, 31 | {"name":"kevin-systrom","hname":"Kevin Systrom"}, 32 | {"name":"daniel-ek","hname":"Daniel Ek"}, 33 | {"name":"ashton-kutcher","hname":"Ashton Kutcher"}, 34 | {"name":"austen-allred","hname":"Austen Allred"}, 35 | {"name":"sheryl-sandberg","hname":"Sheryl Sandberg"}, 36 | {"name":"satya-nadella","hname":"Satya Nadella"}, 37 | {"name":"indra-nooyi","hname":"Indra Nooyi"}, 38 | {"name":"arianna-huffington","hname":"Arianna Huffington"}, 39 | {"name":"marissa-mayer","hname":"Marissa Mayer"}, 40 | {"name":"daymond-john","hname":"Daymond John"}, 41 | {"name":"eric-schmidt","hname":"Eric Schmidt"}, 42 | {"name":"martha-stewart","hname":"Martha Stewart"}, 43 | {"name":"tim-cook","hname":"Tim Cook"}, 44 | {"name":"peter-thiel","hname":"Peter Thiel"}, 45 | {"name":"sam-altman","hname":"Sam Altman"}, 46 | {"name":"warren-buffett","hname":"Warren Buffett"}, 47 | {"name":"marc-andreessen","hname":"Marc Andreessen"}, 48 | {"name":"ben-horowitz","hname":"Ben Horowitz"}, 49 | {"name":"bill-ackman","hname":"Bill Ackman"}, 50 | {"name":"charlie-munger","hname":"Charlie Munger"}, 51 | {"name":"whitney-tilson","hname":"Whitney Tilson"}, 52 | {"name":"naval-ravikant","hname":"Naval Ravikant"}, 53 | {"name":"ray-dalio","hname":"Ray Dalio"}, 54 | {"name":"paul-graham","hname":"Paul Graham"}, 55 | {"name":"keith-rabois","hname":"Keith Rabois"}, 56 | {"name":"carl-sagan","hname":"Carl Sagan"}, 57 | {"name":"albert-einstein","hname":"Albert Einstein"}, 58 | {"name":"richard-dawkins","hname":"Richard Dawkins"}, 59 | {"name":"neil-degrasse-tyson","hname":"Neil deGrasse Tyson"}, 60 | {"name":"don-norman","hname":"Don Norman"}, 61 | {"name":"jakob-nielsen","hname":"Jakob Nielsen"}, 62 | {"name":"steve-krug","hname":"Steve Krug"}, 63 | {"name":"alan-cooper","hname":"Alan Cooper"}, 64 | {"name":"donald-trump","hname":"Donald Trump"}, 65 | {"name":"barack-obama","hname":"Barack Obama"}, 66 | {"name":"hillary-clinton","hname":"Hillary Clinton"}, 67 | {"name":"angela-merkel","hname":"Angela Merkel"}, 68 | {"name":"vladamir-putin","hname":"Vladamir Putin"}, 69 | {"name":"george-bush","hname":"George Bush"}, 70 | {"name":"tony-blair","hname":"Tony Blair"}, 71 | {"name":"bill-clinton","hname":"Bill Clinton"}, 72 | {"name":"tyler-cowen","hname":"Tyler Cowen"}, 73 | {"name":"paul-krugman","hname":"Paul Krugman"}, 74 | {"name":"ellen-degeneres","hname":"Ellen DeGeneres"}, 75 | {"name":"oprah-winfrey","hname":"Oprah Winfrey"}, 76 | {"name":"johnny-depp","hname":"Johnny Depp"}, 77 | {"name":"woody-allen","hname":"Woody Allen"}, 78 | {"name":"anthony-hopkins","hname":"Anthony Hopkins"}, 79 | {"name":"morgan-freeman","hname":"Morgan Freeman"}, 80 | {"name":"nassim-nicholas-taleb","hname":"Nassim Nicholas Taleb"}, 81 | {"name":"seth-godin","hname":"Seth Godin"}, 82 | {"name":"ryan-holiday","hname":"Ryan Holiday"}, 83 | {"name":"jk-rowling","hname":"JK Rowling"}, 84 | {"name":"george-rr-martin","hname":"George RR Martin"}, 85 | {"name":"ernest-hemmingway","hname":"Ernest Hemmingway"}, 86 | {"name":"paulo-coelho","hname":"Paulo Coelho"}, 87 | {"name":"haruki-murakami","hname":"Haruki Murakami"}, 88 | {"name":"chuck-palahniuk","hname":"Chuck Palahniuk"}, 89 | {"name":"stephen-king","hname":"Stephen King"}, 90 | {"name":"nelson-mandela","hname":"Nelson Mandela"}, 91 | {"name":"mahatma-gandhi","hname":"Mahatma Gandhi"}, 92 | {"name":"joseph-stalin","hname":"Joseph Stalin"}, 93 | {"name":"martin-luther-king","hname":"Martin Luther King"}, 94 | {"name":"kevin-kelly","hname":"Kevin Kelly"}, 95 | {"name":"matthew-mcconaughey","hname":"Matthew McConaughey"} 96 | ] -------------------------------------------------------------------------------- /db/export_json.rb: -------------------------------------------------------------------------------- 1 | # This script connects to postgres, and exports all data as JSON files that the app expects 2 | 3 | require './postgres.rb' 4 | require 'neatjson' 5 | 6 | # TODO: print one object on each line 7 | File.open("topics.json","w") do |f| 8 | f.write( 9 | JSON.neat_generate( 10 | JSON.parse(Topic.all.to_json), 11 | wrap: 120 12 | ) 13 | ) 14 | end 15 | 16 | File.open("creators.json","w") do |f| 17 | f.write( 18 | JSON.neat_generate( 19 | JSON.parse(Creator.all.to_json), 20 | wrap: 80 21 | ) 22 | ) 23 | end 24 | 25 | File.open("items.json","w") do |f| 26 | f.write( 27 | JSON.neat_generate( 28 | JSON.parse(Item.all.to_json), 29 | wrap: 80 30 | ) 31 | ) 32 | end 33 | 34 | puts "Everything saved as JSON!" -------------------------------------------------------------------------------- /db/import_json.rb: -------------------------------------------------------------------------------- 1 | # This script imports all data from topics.json, creators.json and items.json into postgres tables 2 | 3 | require './postgres.rb' 4 | 5 | topics = JSON.parse(File.read('topics.json')) 6 | creators = JSON.parse(File.read('creators.json')) 7 | items = JSON.parse(File.read('items.json')) 8 | 9 | puts "import_json: Found #{topics.size} Topics, #{creators.size} Creators, #{items.size} Items in *.json files" 10 | 11 | # TODO: Replace create! calls with create_or_update! 12 | 13 | # TODO: insertion order of topics is important because of the self-referential foreign key parent_name 14 | topics.each do |t| 15 | Topic.create!( 16 | name: t['name'], 17 | hname: (t['hname'] == t['name'] ? nil : t['hname']), 18 | description: t['description'], 19 | image: t['image'], 20 | tags: t['tags'] || [], 21 | 22 | parent: t['parent'], 23 | rank: t['rank'], 24 | roadmap: nil 25 | ) 26 | end 27 | 28 | creators.each do |c| 29 | Creator.create!( 30 | name: c['name'], 31 | hname: (c['hname'] == c['name'] ? nil : c['hname']), 32 | description: c['description'], 33 | image: c['image'], 34 | tags: c['tags'] || [], 35 | links: c['links'] || [], 36 | ) 37 | end 38 | 39 | items.each do |i| 40 | Item.create!( 41 | name: i['name'], 42 | description: i['description'], 43 | image: i['image'], 44 | tags: i['tags'] || [], 45 | 46 | links: i['links'] || [], 47 | 48 | prereqs: i['prereqs'] || [], 49 | topics: i['topics'] || [], 50 | creators: i['creators'] || [], 51 | 52 | year: i['year'], 53 | level: i['level'], 54 | cost: i['cost'], 55 | 56 | rating: i['rating'], 57 | reviews: i['reviews'] || [], 58 | ) 59 | end 60 | 61 | puts "Everything saved in database!" -------------------------------------------------------------------------------- /db/jsonlines.js: -------------------------------------------------------------------------------- 1 | import items_db from './items.json' assert { type: 'json' }; 2 | import topics_db from './topics.json' assert { type: 'json' }; 3 | import creators_db from './creators.json' assert { type: 'json' }; 4 | 5 | export const io_getTopicList = () => { 6 | return [...topics_db]; 7 | } 8 | 9 | export const io_getRandomTopicName = () => { 10 | let randomId = Math.floor(Math.random() * topics_db.length); 11 | return topics_db[randomId].name; 12 | } 13 | 14 | export const io_getTopicByName = (name) => { 15 | return topics_db.filter(t => t.name === name)[0]; 16 | } 17 | 18 | export const io_getRandomItemId = () => { 19 | let randomId = Math.floor(Math.random() * items_db.length); 20 | return items_db[randomId].id; 21 | } 22 | 23 | export const io_getItemsForTopic = (topicname) => { 24 | return items_db.filter(i => i.topics.includes(topicname)) 25 | } 26 | 27 | export const io_getItem = (id) => { 28 | if(!id) return null; 29 | return items_db.filter(t => t.id === id)[0]; 30 | } 31 | 32 | export const io_getItemsForTopicAndFormat = (format, topicname) => { 33 | let results = items_db.filter(i => !topicname || i.topics.includes(topicname)).filter(i => i.links?.join(' ').includes(format + "|")); 34 | return results.slice(0, 100); 35 | } 36 | 37 | export const io_getItemsWithIDs = (ids) => { 38 | let results = items_db.filter(i => ids.includes(i.id)); 39 | // console.log({ids}, {results}); 40 | return results; 41 | } 42 | 43 | export const io_search_items = (query) => { 44 | if(!query) return []; 45 | let items = items_db.filter(i => i.name.toLowerCase().includes(query.toLowerCase())).slice(0,6); 46 | return items; 47 | } 48 | 49 | export const io_search_topics = (query) => { 50 | if(!query) return []; 51 | let topics = topics_db.filter(t => (t.hname || t.name).toLowerCase().includes(query.toLowerCase())).slice(0,6); 52 | return topics; 53 | } -------------------------------------------------------------------------------- /db/postgres.rb: -------------------------------------------------------------------------------- 1 | require 'pg' 2 | require 'active_record' 3 | require 'json' 4 | 5 | # see schema.sql 6 | 7 | # To minimize file size, don't export properties with value = null, '', [], false etc. Handle that in code. 8 | # skip hname if it is same as name 9 | 10 | module CleanJson 11 | def as_json(options={}) 12 | super(options).tap do |json| 13 | json.delete_if{ |k,v| 14 | v.blank? || (k == :hname && v == json[:name]) 15 | }.as_json unless options.try(:delete, :null) 16 | end 17 | end 18 | end 19 | 20 | class ApplicationRecord < ActiveRecord::Base 21 | include CleanJson 22 | self.abstract_class = true 23 | end 24 | 25 | class Topic < ApplicationRecord; end 26 | class Creator < ApplicationRecord; end 27 | class Item < ApplicationRecord 28 | def as_json(options={}) 29 | hash = super(options) 30 | hash["reviews"] = hash["reviews"].map { |r| 31 | r.delete_if { |k,v| v.blank? } 32 | } if hash["reviews"] 33 | hash 34 | end 35 | end 36 | 37 | ActiveRecord::Base.logger = Logger.new(STDERR) 38 | 39 | ActiveRecord::Base.establish_connection( 40 | { adapter: 'postgresql', 41 | database: 'postgres', 42 | host: 'localhost', 43 | username: 'postgres', 44 | password: 'password', 45 | port: 5407 46 | } 47 | ) 48 | 49 | puts "Postgres: Found #{Topic.count} Topics, #{Creator.count} Creators, #{Item.count} Items" -------------------------------------------------------------------------------- /db/schema.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS items; 2 | DROP TABLE IF EXISTS topics; 3 | DROP TABLE IF EXISTS creators; 4 | 5 | CREATE EXTENSION IF NOT EXISTS pgcrypto; 6 | CREATE EXTENSION IF NOT EXISTS pg_jsonschema; -- made by supabase 7 | 8 | -- TODO: Can we store topics/creators/items images in-place (SVG/Base64) rather than as URLs? 9 | 10 | -- TODO: topics.parent_name creates a tree. What if we want a graph or a hypergraph? 11 | -- Should topics have prerequisites globally or only as part of a roadmap? 12 | -- TODO: schema validation for topics.roadmap 13 | CREATE TABLE topics ( 14 | name VARCHAR(255) PRIMARY KEY, -- readable, but url-friendly unique slug, eg: abstract-algebra or nations/india 15 | hname VARCHAR(255), -- human-readable case-preserving name, allow null, can have special chars 16 | description TEXT, -- markdown, not in use currently 17 | image VARCHAR(1024), -- url, not in use currently 18 | tags TEXT[] NOT NULL, -- for future usage 19 | 20 | parent VARCHAR(255), -- create a hierarchy with self-reference on this table 21 | rank INTEGER, -- used for sorting children of a parent 22 | 23 | roadmap TEXT, -- A graph of sub-topics combined with recommended items for each sub-topic as a suggested pathway 24 | 25 | FOREIGN KEY (parent) REFERENCES topics(name) 26 | ); 27 | 28 | CREATE TABLE creators ( 29 | name VARCHAR(255) PRIMARY KEY, -- readable, but url-friendly unique slug eg: bill_gates_1 30 | hname VARCHAR(255), -- human-readable case-preserving name, allow null, can have special chars 31 | description TEXT, -- markdown 32 | image VARCHAR(1024), -- url 33 | tags TEXT[] NOT NULL, -- occupation (investor/author/scientist/celebrity/entrepreneur etc) 34 | 35 | links TEXT[] NOT NULL -- urls of blogs, twitter/activitypub profiles, homepages etc 36 | ); 37 | 38 | 39 | -- items.id is not referenced from anywhere except as unique key for URLs directly linking to items 40 | -- reviews are now stored in-place in items table, so don't require items.id anymore 41 | 42 | -- TODO: How to better model items.topics, items.creators and items.prereqs for many-to-many relationship while enforcing foreign-key constraint? 43 | -- Currently, things will break if topics.name or creators.name is modified without updating items that refer to these 44 | 45 | -- TODO: How to model prerequisites? Are they topics or items or both? 46 | -- Should topics have prerequisites? 47 | -- items.prereqs is a array of free-form strings, expected to hold item IDs or topic names 48 | 49 | -- TODO: use even shorter uuid? 58^4 = 11.3m unique ids with just 4 characters 50 | 51 | CREATE TABLE items ( 52 | id varchar(12) DEFAULT encode(gen_random_bytes(6), 'hex') PRIMARY KEY, -- keeping id so that name does not need to be a unique url-friendly slug 53 | name VARCHAR(1024) NOT NULL, -- human-readable string (like hname) 54 | description TEXT, -- markdown 55 | image VARCHAR(1024), 56 | tags TEXT[] NOT NULL, -- eg: inspirational, educational, challenging, entertaining, visual, interactive, oer, nsfw, isbn/isbn13 57 | 58 | links TEXT[] NOT NULL, -- array of strings of this format: || 59 | 60 | prereqs TEXT[] NOT NULL, -- item IDs or topic names 61 | topics TEXT[] NOT NULL, -- array of topic names (foreign keys, but integrity-check not enforced) 62 | creators TEXT[] NOT NULL, -- array of creator_ids (foreign keys, but integrity-check not enforced) 63 | 64 | year VARCHAR(32), -- how old is this material? 65 | level VARCHAR(32), -- what difficulty level is this appropriate for? childlike/beginner/intermediate/advanced/research 66 | cost TEXT, -- is it free? one-time fees? subscription? 67 | 68 | rating INTEGER, -- scale of 0 to 100, divide by 20 to show on 5-star scale 69 | reviews jsonb DEFAULT '[]' NOT NULL, -- see json schema below. either by_item or by_creator must be present 70 | 71 | CHECK ( 72 | jsonb_matches_schema( 73 | schema := '{ 74 | "type": "array", 75 | "items": { 76 | "type": "object", 77 | "properties": { 78 | "by_item": {"type": ["string", "null"]}, 79 | "by_creator": {"type": ["string", "null"]}, 80 | "rating": {"type": ["integer", "null"], "minimum": 0, "maximum": 100}, 81 | "blurb": {"type": ["string", "null"]}, 82 | "url": {"type": ["string", "null"]} 83 | } 84 | } 85 | }', 86 | instance := reviews 87 | ) 88 | ) 89 | ); 90 | 91 | -- TODO: schema for projects/achievements/skills. How does it map to topics? 92 | -- Are topics actually skills or is a skill actually (topic + level) tuple? 93 | 94 | -- TODO: users table with supabase auth, want_to_learn/finished_learning bookmarks, achievements etc. 95 | -- TODO: social graph 96 | 97 | -- Dump from database to JSON files 98 | -- COPY ( 99 | -- SELECT json_agg(row_to_json(topics)) :: text 100 | -- FROM topics 101 | -- ) to '/Users/eshnil/code/learndb/db/topics.json'; 102 | 103 | -- COPY ( 104 | -- SELECT json_agg(row_to_json(creators)) :: text 105 | -- FROM creators 106 | -- ) to '/Users/eshnil/code/learndb/db/creators.json'; 107 | 108 | -- COPY ( 109 | -- SELECT json_agg(row_to_json(items)) :: text 110 | -- FROM items 111 | -- ) to '/Users/eshnil/code/learndb/db/items.json'; 112 | 113 | -------------------------------------------------------------------------------- /db/zlib_search.rb: -------------------------------------------------------------------------------- 1 | require 'json' 2 | require 'csv' 3 | 4 | # Enrich data in items.json by reading data from zlib etc 5 | 6 | topics = JSON.parse(File.read('topics.json')); 0 7 | items = JSON.parse(File.read('items.json')); 0 8 | puts "#{items.size} items found" 9 | 10 | cols = [:id, :title, :author, :publisher, :extension, :filesize, :language, :year, :pages, :isbn, :ipfs_cid] 11 | 12 | zlib = CSV.read('../../zlib-searcher/zlib_index_books.csv'); 0 13 | puts "#{zlib.size} zlib books" 14 | 15 | libgen = CSV.read('../../zlib-searcher/libgen_index_books.csv'); 0 16 | puts "#{libgen.size} libgen books" 17 | 18 | class String 19 | def fuzzy_match(str) 20 | return true if self.downcase == str.downcase 21 | return false if self.length < 10 22 | return self.downcase.include?(str.downcase) || str.downcase.include?(self.downcase) 23 | end 24 | end 25 | 26 | my_books = items.select { |i| 27 | i["links"].any? {|l| 28 | l.split('|').first == 'book' && 29 | !l.split('|').last.start_with?('ipfs:') 30 | } 31 | }; 0 32 | 33 | puts "#{my_books.size} books in items" 34 | 35 | matches = JSON.parse(File.read('matches.json')) 36 | 37 | my_books[0..10].each do |b| 38 | next if matches[b['id']]&.size.to_i > 0 39 | next if b['name'].size < 10 40 | 41 | found = 42 | zlib.select { |r| r[1].fuzzy_match(b['name']) } + 43 | libgen.select { |r| r[1].fuzzy_match(b['name']) } 44 | 45 | matches[b['id']] = found 46 | 47 | end; 0 48 | 49 | File.open("matches.json","w") do |f| 50 | # save results 51 | f.write(JSON.pretty_generate(matches)) 52 | end 53 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | SkillTree 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 57 | 58 | 59 | 60 |
61 | 62 | 63 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | functions = "netlify/functions/" -------------------------------------------------------------------------------- /netlify/functions/form.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Invoke Netlify Function 5 | 83 | 84 | 85 |

Invoke Netlify Function

86 |
87 | 88 | 89 | 90 | 91 | 92 | 93 |
94 | 95 |
96 |
97 | 98 |
99 | 100 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /netlify/functions/handleMetadata.js: -------------------------------------------------------------------------------- 1 | const fetch = require('node-fetch'); // Import for webscraping in fetchContentFromURL() 2 | import { OpenAIApi, Configuration } from 'openai'; 3 | // const { Configuration, OpenAIApi } = require('openai'); 4 | 5 | // Function to fetch content from URL using a web scraping service 6 | async function fetchContentFromURL(url) { 7 | try { 8 | const response = await fetch(url); 9 | if (!response.ok) { 10 | throw new Error(`HTTP error! status: ${response.status}`); 11 | } 12 | return await response.text(); 13 | } catch (error) { 14 | console.error(`Could not fetch content from URL: ${error}`); 15 | throw error; 16 | } 17 | } 18 | 19 | function simplifyContent(content) { 20 | // Preserve the title tag and its content 21 | let title = content.match(/(.*?)<\/title>/i); 22 | title = title ? title[1] : ''; 23 | 24 | // Extract the body content, if present 25 | let bodyContent = ''; 26 | const bodyMatch = content.match(/([\s\S]*)<\/body>/i); 27 | if (bodyMatch) { 28 | bodyContent = bodyMatch[1]; 29 | } else { 30 | // If no body tag, assume entire content is body 31 | bodyContent = content; 32 | } 33 | 34 | // Remove script and style elements and their content 35 | bodyContent = bodyContent.replace(/.*?<\/script>/gms, ''); 36 | bodyContent = bodyContent.replace(/.*?<\/style>/gms, ''); 37 | 38 | // Remove all remaining HTML tags, except for title, body, h1-h6, p, and a 39 | bodyContent = bodyContent.replace(/<(?!\/?(title|body|h[1-6]|p|a)( [^>]*)?>)([^>]+)>/g, ''); 40 | 41 | // Manually replace common HTML entities 42 | bodyContent = bodyContent 43 | .replace(/&/g, '&') 44 | .replace(/</g, '<') 45 | .replace(/>/g, '>') 46 | .replace(/"/g, '"') 47 | .replace(/'/g, "'"); 48 | 49 | // Remove inline CSS and JavaScript event handlers 50 | bodyContent = bodyContent.replace(/style\s*=\s*'.*?'/gi, ''); 51 | bodyContent = bodyContent.replace(/on\w+\s*=\s*".*?"/gi, ''); 52 | 53 | // Normalize whitespace without removing sentence punctuation 54 | bodyContent = bodyContent.replace(/\s+/g, ' ').trim(); 55 | 56 | // Condense multiple line breaks into a single one 57 | bodyContent = bodyContent.replace(/(\r\n|\r|\n){2,}/g, '\n'); 58 | 59 | // Reconstruct content with title and body 60 | const simplifiedContent = `${title}${bodyContent}`; 61 | return simplifiedContent; 62 | } 63 | 64 | // Placeholder function to perform GPT analysis for media type and topics using Mistral-7b via OpenRouter 65 | async function performGPTAnalysis(simplifiedContent, apiKey) { 66 | // Implement logic to send content to Mistral-7b via OpenRouter for GPT analysis 67 | // Send content and receive GPT analysis response 68 | 69 | // this is the code that we tried to use for the GPT Analysis 70 | // try { 71 | // const configuration = new Configuration({ 72 | // apiKey: apiKey, // Use the provided API key 73 | // baseURL: "https://openrouter.ai/api/v1" // Your custom API endpoint 74 | // }); 75 | 76 | // const openai = new OpenAIApi(configuration); 77 | 78 | // // Using the specified prompt 79 | // const prompt = ` 80 | // Analyze the following text for content categorization: 81 | // Text: "${simplifiedContent}" 82 | // Please provide: 83 | // 1. The most likely media type (e.g., article, book, audio, video, chat, research_paper, wiki, etc.) 84 | // 2. Key topics covered in the text (list up to 5 main topics). 85 | // `; 86 | // const completion = await openai.createCompletion({ 87 | // model: "mistralai/mistral-7b-instruct", 88 | // prompt: prompt, 89 | // max_tokens: 150 // Adjust as needed 90 | // }); 91 | 92 | // //return completion.data.choices[0].text.trim(); 93 | // return inferredMediaType; 94 | // } catch (error) { 95 | // console.error('Error with OpenAI completion:', error); 96 | // throw error; 97 | // } 98 | // however, it gives the error below: 99 | // { "error": "Something went wrong", "details": "Configuration is not a constructor" } 100 | 101 | // Placeholder code 102 | const inferredMediaType = ["article"]; 103 | const extractedTopics = ["topic1", "topic2"]; 104 | return { inferredMediaType, extractedTopics }; 105 | } 106 | 107 | // Placeholder function to map inferred values to predefined formats and topics 108 | function mapInferredValues(mediaType, topics) { 109 | // Implement logic to map inferred media type and topics to predefined formats and topics 110 | // Match inferred values with predefined taxonomy 111 | // Placeholder code 112 | const predefinedMediaType = "Article"; 113 | const predefinedTopics = ["Topic 1", "Topic 2"]; 114 | return { predefinedMediaType, predefinedTopics }; 115 | } 116 | 117 | // Placeholder function to format the response 118 | function formatResponse(predefinedMediaType, predefinedTopics) { 119 | // Implement logic to format the extracted metadata into the desired response structure 120 | // Construct the response object 121 | // Placeholder code 122 | const response = { 123 | format: predefinedMediaType, 124 | topics: predefinedTopics, 125 | }; 126 | return response; 127 | } 128 | 129 | export async function handler(event) { 130 | try { 131 | // Extract URL and API Key from the request body 132 | const { url, apiKey } = JSON.parse(event.body); 133 | 134 | // Validate if URL and API Key are present 135 | if (!url || !apiKey) { 136 | return { 137 | statusCode: 400, 138 | body: JSON.stringify({ error: 'URL and API Key are required' }), 139 | }; 140 | } 141 | 142 | // Step 1: Fetch content from the URL using a web scraping service 143 | const fetchedContent = await fetchContentFromURL(url); 144 | 145 | // Step 2: Simplify the fetched content for GPT analysis 146 | const simplifiedContent = simplifyContent(fetchedContent); 147 | 148 | // Step 3: Perform GPT analysis for media type and topics 149 | const { inferredMediaType, extractedTopics } = await performGPTAnalysis(simplifiedContent, apiKey); 150 | 151 | // Step 4: Map inferred values to predefined formats and topics 152 | const { predefinedMediaType, predefinedTopics } = mapInferredValues(inferredMediaType, extractedTopics); 153 | 154 | // Step 5: Format the response 155 | const formattedResponse = formatResponse(predefinedMediaType, predefinedTopics); 156 | 157 | // Return the formatted response 158 | return { 159 | statusCode: 200, 160 | // returning the output of the simplifyContent function, to test the function 161 | body: JSON.stringify(simplifiedContent), 162 | }; 163 | } catch (error) { 164 | console.error('Error occurred:', error.message); 165 | return { 166 | statusCode: 500, 167 | body: JSON.stringify({ error: 'Something went wrong', details: error.message }), 168 | }; 169 | } 170 | } -------------------------------------------------------------------------------- /netlify/functions/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "handle-metadata-function", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "handle-metadata-function", 9 | "version": "1.0.0", 10 | "dependencies": { 11 | "he": "^1.2.0", 12 | "node-fetch": "^2.7.0", 13 | "openai": "4.20.0" 14 | } 15 | }, 16 | "node_modules/@types/node": { 17 | "version": "18.18.13", 18 | "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.13.tgz", 19 | "integrity": "sha512-vXYZGRrSCreZmq1rEjMRLXJhiy8MrIeVasx+PCVlP414N7CJLHnMf+juVvjdprHyH+XRy3zKZLHeNueOpJCn0g==", 20 | "dependencies": { 21 | "undici-types": "~5.26.4" 22 | } 23 | }, 24 | "node_modules/@types/node-fetch": { 25 | "version": "2.6.9", 26 | "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.9.tgz", 27 | "integrity": "sha512-bQVlnMLFJ2d35DkPNjEPmd9ueO/rh5EiaZt2bhqiSarPjZIuIV6bPQVqcrEyvNo+AfTrRGVazle1tl597w3gfA==", 28 | "dependencies": { 29 | "@types/node": "*", 30 | "form-data": "^4.0.0" 31 | } 32 | }, 33 | "node_modules/abort-controller": { 34 | "version": "3.0.0", 35 | "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", 36 | "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", 37 | "dependencies": { 38 | "event-target-shim": "^5.0.0" 39 | }, 40 | "engines": { 41 | "node": ">=6.5" 42 | } 43 | }, 44 | "node_modules/agentkeepalive": { 45 | "version": "4.5.0", 46 | "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz", 47 | "integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==", 48 | "dependencies": { 49 | "humanize-ms": "^1.2.1" 50 | }, 51 | "engines": { 52 | "node": ">= 8.0.0" 53 | } 54 | }, 55 | "node_modules/asynckit": { 56 | "version": "0.4.0", 57 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 58 | "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" 59 | }, 60 | "node_modules/base-64": { 61 | "version": "0.1.0", 62 | "resolved": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz", 63 | "integrity": "sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA==" 64 | }, 65 | "node_modules/charenc": { 66 | "version": "0.0.2", 67 | "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", 68 | "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==", 69 | "engines": { 70 | "node": "*" 71 | } 72 | }, 73 | "node_modules/combined-stream": { 74 | "version": "1.0.8", 75 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 76 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 77 | "dependencies": { 78 | "delayed-stream": "~1.0.0" 79 | }, 80 | "engines": { 81 | "node": ">= 0.8" 82 | } 83 | }, 84 | "node_modules/crypt": { 85 | "version": "0.0.2", 86 | "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", 87 | "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==", 88 | "engines": { 89 | "node": "*" 90 | } 91 | }, 92 | "node_modules/delayed-stream": { 93 | "version": "1.0.0", 94 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 95 | "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", 96 | "engines": { 97 | "node": ">=0.4.0" 98 | } 99 | }, 100 | "node_modules/digest-fetch": { 101 | "version": "1.3.0", 102 | "resolved": "https://registry.npmjs.org/digest-fetch/-/digest-fetch-1.3.0.tgz", 103 | "integrity": "sha512-CGJuv6iKNM7QyZlM2T3sPAdZWd/p9zQiRNS9G+9COUCwzWFTs0Xp8NF5iePx7wtvhDykReiRRrSeNb4oMmB8lA==", 104 | "dependencies": { 105 | "base-64": "^0.1.0", 106 | "md5": "^2.3.0" 107 | } 108 | }, 109 | "node_modules/event-target-shim": { 110 | "version": "5.0.1", 111 | "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", 112 | "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", 113 | "engines": { 114 | "node": ">=6" 115 | } 116 | }, 117 | "node_modules/form-data": { 118 | "version": "4.0.0", 119 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", 120 | "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", 121 | "dependencies": { 122 | "asynckit": "^0.4.0", 123 | "combined-stream": "^1.0.8", 124 | "mime-types": "^2.1.12" 125 | }, 126 | "engines": { 127 | "node": ">= 6" 128 | } 129 | }, 130 | "node_modules/form-data-encoder": { 131 | "version": "1.7.2", 132 | "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", 133 | "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==" 134 | }, 135 | "node_modules/formdata-node": { 136 | "version": "4.4.1", 137 | "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", 138 | "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", 139 | "dependencies": { 140 | "node-domexception": "1.0.0", 141 | "web-streams-polyfill": "4.0.0-beta.3" 142 | }, 143 | "engines": { 144 | "node": ">= 12.20" 145 | } 146 | }, 147 | "node_modules/formdata-node/node_modules/web-streams-polyfill": { 148 | "version": "4.0.0-beta.3", 149 | "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", 150 | "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", 151 | "engines": { 152 | "node": ">= 14" 153 | } 154 | }, 155 | "node_modules/he": { 156 | "version": "1.2.0", 157 | "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", 158 | "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", 159 | "bin": { 160 | "he": "bin/he" 161 | } 162 | }, 163 | "node_modules/humanize-ms": { 164 | "version": "1.2.1", 165 | "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", 166 | "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", 167 | "dependencies": { 168 | "ms": "^2.0.0" 169 | } 170 | }, 171 | "node_modules/is-buffer": { 172 | "version": "1.1.6", 173 | "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", 174 | "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" 175 | }, 176 | "node_modules/md5": { 177 | "version": "2.3.0", 178 | "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", 179 | "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==", 180 | "dependencies": { 181 | "charenc": "0.0.2", 182 | "crypt": "0.0.2", 183 | "is-buffer": "~1.1.6" 184 | } 185 | }, 186 | "node_modules/mime-db": { 187 | "version": "1.52.0", 188 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 189 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 190 | "engines": { 191 | "node": ">= 0.6" 192 | } 193 | }, 194 | "node_modules/mime-types": { 195 | "version": "2.1.35", 196 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 197 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 198 | "dependencies": { 199 | "mime-db": "1.52.0" 200 | }, 201 | "engines": { 202 | "node": ">= 0.6" 203 | } 204 | }, 205 | "node_modules/ms": { 206 | "version": "2.1.3", 207 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 208 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 209 | }, 210 | "node_modules/node-domexception": { 211 | "version": "1.0.0", 212 | "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", 213 | "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", 214 | "funding": [ 215 | { 216 | "type": "github", 217 | "url": "https://github.com/sponsors/jimmywarting" 218 | }, 219 | { 220 | "type": "github", 221 | "url": "https://paypal.me/jimmywarting" 222 | } 223 | ], 224 | "engines": { 225 | "node": ">=10.5.0" 226 | } 227 | }, 228 | "node_modules/node-fetch": { 229 | "version": "2.7.0", 230 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", 231 | "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", 232 | "dependencies": { 233 | "whatwg-url": "^5.0.0" 234 | }, 235 | "engines": { 236 | "node": "4.x || >=6.0.0" 237 | }, 238 | "peerDependencies": { 239 | "encoding": "^0.1.0" 240 | }, 241 | "peerDependenciesMeta": { 242 | "encoding": { 243 | "optional": true 244 | } 245 | } 246 | }, 247 | "node_modules/openai": { 248 | "version": "4.20.0", 249 | "resolved": "https://registry.npmjs.org/openai/-/openai-4.20.0.tgz", 250 | "integrity": "sha512-VbAYerNZFfIIeESS+OL9vgDkK8Mnri55n+jN0UN/HZeuM0ghGh6nDN6UGRZxslNgyJ7XmY/Ca9DO4YYyvrszGA==", 251 | "dependencies": { 252 | "@types/node": "^18.11.18", 253 | "@types/node-fetch": "^2.6.4", 254 | "abort-controller": "^3.0.0", 255 | "agentkeepalive": "^4.2.1", 256 | "digest-fetch": "^1.3.0", 257 | "form-data-encoder": "1.7.2", 258 | "formdata-node": "^4.3.2", 259 | "node-fetch": "^2.6.7", 260 | "web-streams-polyfill": "^3.2.1" 261 | }, 262 | "bin": { 263 | "openai": "bin/cli" 264 | } 265 | }, 266 | "node_modules/tr46": { 267 | "version": "0.0.3", 268 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", 269 | "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" 270 | }, 271 | "node_modules/undici-types": { 272 | "version": "5.26.5", 273 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", 274 | "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" 275 | }, 276 | "node_modules/web-streams-polyfill": { 277 | "version": "3.2.1", 278 | "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", 279 | "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==", 280 | "engines": { 281 | "node": ">= 8" 282 | } 283 | }, 284 | "node_modules/webidl-conversions": { 285 | "version": "3.0.1", 286 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", 287 | "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" 288 | }, 289 | "node_modules/whatwg-url": { 290 | "version": "5.0.0", 291 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", 292 | "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", 293 | "dependencies": { 294 | "tr46": "~0.0.3", 295 | "webidl-conversions": "^3.0.0" 296 | } 297 | } 298 | } 299 | } 300 | -------------------------------------------------------------------------------- /netlify/functions/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "handle-metadata-function", 3 | "version": "1.0.0", 4 | "description": "A Netlify function to handle metadata extraction and analysis", 5 | "main": "handleMetadata.js", 6 | "scripts": { 7 | "start": "node handleMetadata.js" 8 | }, 9 | "dependencies": { 10 | "he": "^1.2.0", 11 | "node-fetch": "^2.7.0", 12 | "openai": "^4.20.0" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /neutralino.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "applicationId": "org.learnawesome.learndb", 3 | "version": "1.0.0", 4 | "defaultMode": "window", 5 | "port": 0, 6 | "documentRoot": "/static/", 7 | "url": "/", 8 | "enableServer": true, 9 | "enableNativeAPI": true, 10 | "tokenSecurity": "one-time", 11 | "logging": { 12 | "enabled": true, 13 | "writeToLogFile": true 14 | }, 15 | "nativeAllowList": [ 16 | "app.*", 17 | "os.*", 18 | "debug.log" 19 | ], 20 | "globalVariables": { 21 | "TEST1": "Hello", 22 | "TEST2": [ 23 | 2, 24 | 4, 25 | 5 26 | ], 27 | "TEST3": { 28 | "value1": 10, 29 | "value2": {} 30 | } 31 | }, 32 | "modes": { 33 | "window": { 34 | "title": "LearnDB", 35 | "width": 1200, 36 | "height": 800, 37 | "minWidth": 800, 38 | "minHeight": 500, 39 | "fullScreen": false, 40 | "alwaysOnTop": false, 41 | "icon": "/static/icons/appIcon.png", 42 | "enableInspector": true, 43 | "borderless": false, 44 | "maximize": false, 45 | "hidden": false, 46 | "resizable": true, 47 | "exitProcessOnClose": false 48 | }, 49 | "browser": { 50 | "globalVariables": { 51 | "TEST": "Test value browser" 52 | }, 53 | "nativeBlockList": [ 54 | "filesystem.*" 55 | ] 56 | }, 57 | "cloud": { 58 | "url": "/resources/#cloud", 59 | "nativeAllowList": [ 60 | "app.*" 61 | ] 62 | }, 63 | "chrome": { 64 | "width": 1200, 65 | "height": 800, 66 | "args": "--user-agent=\"Neutralinojs chrome mode\"", 67 | "nativeBlockList": [ 68 | "filesystem.*", 69 | "os.*" 70 | ] 71 | } 72 | }, 73 | "cli": { 74 | "binaryName": "learndb", 75 | "resourcesPath": "/static/", 76 | "extensionsPath": "/extensions/", 77 | "clientLibrary": "/static/neutralino.js", 78 | "binaryVersion": "4.7.0", 79 | "clientVersion": "3.6.0" 80 | } 81 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "learndb", 3 | "version": "1.1.0", 4 | "private": true, 5 | "type": "module", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "build": "vite build", 9 | "dev": "vite", 10 | "preview": "vite preview" 11 | }, 12 | "devDependencies": { 13 | "@rgossiaux/svelte-headlessui": "^2.0.0", 14 | "@rgossiaux/svelte-heroicons": "^0.1.2", 15 | "@sveltejs/pancake": "^0.0.18", 16 | "@sveltejs/vite-plugin-svelte": "^5.0.1", 17 | "autoprefixer": "^10.4.16", 18 | "d3-hierarchy": "^3.1.2", 19 | "marked": "^15.0.2", 20 | "postcss": "^8.4.30", 21 | "postcss-load-config": "^6.0.1", 22 | "svelte": "^5.2.10", 23 | "svelte-bricks": "^0.2.1", 24 | "svelte-preprocess": "^6.0.0", 25 | "tailwindcss": "^3.3.3", 26 | "vite": "^6.0.1" 27 | }, 28 | "overrides": { 29 | "@rgossiaux/svelte-headlessui": { 30 | "svelte": "$svelte" 31 | }, 32 | "@rgossiaux/svelte-heroicons": { 33 | "svelte": "$svelte" 34 | } 35 | }, 36 | "dependencies": { 37 | "@tailwindcss/typography": "^0.5.10", 38 | "node-fetch": "^3.3.2", 39 | "openai": "^4.73.1", 40 | "svelvet": "10.0.2" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /postcss.config.cjs: -------------------------------------------------------------------------------- 1 | const tailwindcss = require("tailwindcss"); 2 | const autoprefixer = require("autoprefixer"); 3 | 4 | const config = { 5 | plugins: [ 6 | //Some plugins, like tailwindcss/nesting, need to run before Tailwind, 7 | tailwindcss(), 8 | //But others, like autoprefixer, need to run after, 9 | autoprefixer, 10 | ], 11 | }; 12 | 13 | module.exports = config; 14 | -------------------------------------------------------------------------------- /public/._book-cover-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learn-awesome/learndb/e3b5e2e5df61e8d27259a33a162efb49b6454a07/public/._book-cover-2.png -------------------------------------------------------------------------------- /public/._book-cover-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learn-awesome/learndb/e3b5e2e5df61e8d27259a33a162efb49b6454a07/public/._book-cover-3.png -------------------------------------------------------------------------------- /public/._book-cover-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learn-awesome/learndb/e3b5e2e5df61e8d27259a33a162efb49b6454a07/public/._book-cover-4.png -------------------------------------------------------------------------------- /public/._book-cover-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learn-awesome/learndb/e3b5e2e5df61e8d27259a33a162efb49b6454a07/public/._book-cover-5.png -------------------------------------------------------------------------------- /public/._book-cover-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learn-awesome/learndb/e3b5e2e5df61e8d27259a33a162efb49b6454a07/public/._book-cover-6.png -------------------------------------------------------------------------------- /public/._book-cover-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learn-awesome/learndb/e3b5e2e5df61e8d27259a33a162efb49b6454a07/public/._book-cover-7.png -------------------------------------------------------------------------------- /public/._book-cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learn-awesome/learndb/e3b5e2e5df61e8d27259a33a162efb49b6454a07/public/._book-cover.png -------------------------------------------------------------------------------- /public/._course.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learn-awesome/learndb/e3b5e2e5df61e8d27259a33a162efb49b6454a07/public/._course.jpg -------------------------------------------------------------------------------- /public/._play-solid.svg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learn-awesome/learndb/e3b5e2e5df61e8d27259a33a162efb49b6454a07/public/._play-solid.svg -------------------------------------------------------------------------------- /public/book-cover-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learn-awesome/learndb/e3b5e2e5df61e8d27259a33a162efb49b6454a07/public/book-cover-2.png -------------------------------------------------------------------------------- /public/book-cover-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learn-awesome/learndb/e3b5e2e5df61e8d27259a33a162efb49b6454a07/public/book-cover-3.png -------------------------------------------------------------------------------- /public/book-cover-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learn-awesome/learndb/e3b5e2e5df61e8d27259a33a162efb49b6454a07/public/book-cover-4.png -------------------------------------------------------------------------------- /public/book-cover-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learn-awesome/learndb/e3b5e2e5df61e8d27259a33a162efb49b6454a07/public/book-cover-5.png -------------------------------------------------------------------------------- /public/book-cover-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learn-awesome/learndb/e3b5e2e5df61e8d27259a33a162efb49b6454a07/public/book-cover-6.png -------------------------------------------------------------------------------- /public/book-cover-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learn-awesome/learndb/e3b5e2e5df61e8d27259a33a162efb49b6454a07/public/book-cover-7.png -------------------------------------------------------------------------------- /public/book-cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learn-awesome/learndb/e3b5e2e5df61e8d27259a33a162efb49b6454a07/public/book-cover.png -------------------------------------------------------------------------------- /public/course.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learn-awesome/learndb/e3b5e2e5df61e8d27259a33a162efb49b6454a07/public/course.jpg -------------------------------------------------------------------------------- /public/icons/appIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learn-awesome/learndb/e3b5e2e5df61e8d27259a33a162efb49b6454a07/public/icons/appIcon.png -------------------------------------------------------------------------------- /public/icons/trayIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learn-awesome/learndb/e3b5e2e5df61e8d27259a33a162efb49b6454a07/public/icons/trayIcon.png -------------------------------------------------------------------------------- /public/neutra.js: -------------------------------------------------------------------------------- 1 | function onWindowClose() { 2 | Neutralino.app.exit(); 3 | } 4 | 5 | Neutralino.init(); 6 | Neutralino.events.on("windowClose", onWindowClose); 7 | -------------------------------------------------------------------------------- /public/play-solid.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /scripts/remove_404.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import https from 'https'; 3 | import http from 'http'; 4 | import items from "../db/hello.json" assert {type: "json"}; 5 | 6 | function checkLink(url) { 7 | return new Promise((resolve, reject) => { 8 | let protocol = url.startsWith('https') ? https : http; 9 | protocol.get(url, (res) => { 10 | if (res.statusCode === 404) { 11 | resolve(false); 12 | } else { 13 | resolve(true); 14 | } 15 | }).on('error', (err) => { 16 | reject(err); 17 | }); 18 | }); 19 | } 20 | 21 | async function checkLinks(item, dryRun) { 22 | if (!item.links || item.links.length === 0) { 23 | return; 24 | } 25 | for (let i = 0; i < item.links.length; i++) { 26 | const linkParts = item.links[i].split('|'); 27 | const url = linkParts[1]; 28 | try { 29 | const linkExists = await checkLink(url); 30 | if (!linkExists) { 31 | if (dryRun) { 32 | console.log(`[DRY RUN] Would remove 404 link from ${item.name}: ${url}`); 33 | } else { 34 | item.links.splice(i, 1); 35 | i--; 36 | console.log(`Removed 404 link from ${item.name}: ${url}`); 37 | } 38 | } 39 | } catch (error) { 40 | console.error(`Error checking link ${url}: ${error}`); 41 | } 42 | } 43 | } 44 | 45 | async function checkAllLinks(dryRun) { 46 | for (const item of items) { 47 | await checkLinks(item, dryRun); 48 | } 49 | if (!dryRun) { 50 | fs.writeFileSync('db/hello.json', JSON.stringify(items)); 51 | console.log('Updated hello.json'); 52 | } 53 | } 54 | 55 | const dryRun = process.argv.includes('--dry-run'); 56 | checkAllLinks(dryRun); 57 | -------------------------------------------------------------------------------- /src/._oEmbedProviders.js: -------------------------------------------------------------------------------- 1 | Mac OS X  2��ATTR����S%com.apple.metadata:kMDItemWhereFroms:com.apple.quarantinebplist00�_!https://oembed.com/providers.jsonP /00081;62971420;Firefox;D67E56DB-B95B-4B03-8178-7ECF02BB4B8DThis resource fork intentionally left blank �� -------------------------------------------------------------------------------- /src/AdvancedSearch.svelte: -------------------------------------------------------------------------------- 1 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/App.svelte: -------------------------------------------------------------------------------- 1 | 53 | 54 | 55 | 56 | 57 | {#snippet content()} 58 | 59 | {#if currentView === "/home" || currentView === "/"} 60 | 61 | {:else if currentView === "/map"} 62 | 63 | {:else if currentView === "/topics"} 64 | 65 | {:else if currentView.startsWith("/topic/")} 66 | 67 | {:else if currentView === "/formats"} 68 | 69 | {:else if currentView.startsWith("/format/")} 70 | 71 | {:else if currentView.startsWith("/item/")} 72 | 73 | {:else if currentView == "/randomtopic"} 74 | {#if randomTopicName}{/if} 75 | {:else if currentView == "/randomitem"} 76 | {#if randomItemId}{/if} 77 | {:else if currentView === "/wanttolearn"} 78 | 79 | {:else if currentView === "/finishedlearning"} 80 | 81 | {:else if currentView === "/settings"} 82 | 83 | {:else if currentView.startsWith("/roadmap/")} 84 | 85 | {:else if currentView.startsWith("/roadmaps")} 86 | 87 | {:else if currentView.startsWith("/roadmap-alternate/")} 88 | 89 | {/if} 90 | 91 | 92 | 93 | {/snippet} 94 | 95 | {#snippet nav()} 96 | 97 | 102 | 103 | 104 | 105 | 110 | 111 | 112 | 113 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 |
128 |

Random Topic

129 |
130 |
131 | 132 |
133 | 134 | 135 | 136 |
137 |

Random Item

138 |
139 |
140 | 141 |
142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | {/snippet} 161 |
-------------------------------------------------------------------------------- /src/AppShell.svelte: -------------------------------------------------------------------------------- 1 | 44 | 45 | 46 |
47 | 48 |
49 |
50 | {#if isNavDrawerOpen == false} 51 | 55 | {/if} 56 | 57 | {#if isNavDrawerOpen} 58 | 62 | {/if} 63 | 64 | 68 |
69 | 70 | 71 | 72 | 73 |
74 | {#if showNotificationBell || showProfileMenu} 75 |
76 | {#if showNotificationBell} 77 | 81 | {/if} 82 |
83 | {/if} 84 |
85 |
86 | 87 |
88 | 89 |
90 |
91 |
92 | {@render content?.()} 93 |
94 |
95 |
96 |
97 | 98 | {#if isNavDrawerOpen} 99 | 117 | {/if} 118 | 119 | 120 | 137 |
-------------------------------------------------------------------------------- /src/BookCard.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | {item.name} 10 | 11 | {#if item.rating} 12 | 13 |

14 | 15 |

16 | {/if} 17 | 18 | {#if !item.image} 19 |
20 |

{item.name}

21 | {#if item.creators} 22 |

{item.creators}

23 | {/if} 24 |
25 | {/if} 26 | 27 |
-------------------------------------------------------------------------------- /src/Bookmarks.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 |

{kind == 0 ? 'Want to learn' : 'Finished learning'}

14 | 15 | -------------------------------------------------------------------------------- /src/ButtonGroup.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 | 16 | {#each tabs as tab, i} 17 | 30 | {/each} 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/Creator.svelte: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/FormatDetail.svelte: -------------------------------------------------------------------------------- 1 | 48 | 49 | 56 | 57 |
58 | 59 | All Formats 60 | 61 | {capitalize(format)} 62 | 63 | 64 | 65 | Help us improve this taxonomy 66 | 67 |
68 | 69 | 70 | 71 | {#if format == 'book'} 72 |
73 | {#each filteredItems as item} 74 | 75 | {/each} 76 |
77 | {:else} 78 |
79 | {#each filteredItems as item} 80 | 81 | {/each} 82 |
83 | {/if} 84 | 85 | -------------------------------------------------------------------------------- /src/FormatList.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 |
7 | {#each formats as format} 8 | 9 |
10 | 11 |

{format.name}

12 |
13 | 14 |
15 | {/each} 16 |
-------------------------------------------------------------------------------- /src/GenericCard.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 |
7 |
8 | {item.name} 9 | {#if item.creators}{item.creators}{/if} 10 |
11 |
12 |
13 | -------------------------------------------------------------------------------- /src/Home.svelte: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

5 | 6 | 7 | SkillTree 8 | We're building humanity's universal map for learning! 9 | 10 |

11 | 12 |

A Young Lady's Illustrated Primer (from Neal Stephenson's The Diamond Age) is an old dream of tech makers. SkillTree is a step towards building this and making it accessible to everyone.

13 | 14 |

The Internet has amazing learning resources for anything you can imagine. However, search engines, edtech platforms, and even Wikipedia/Wikiversity don't do a good job of making them discoverable. Projects like Music Genome Project or Book Genome Project have demonstrated how rich metadata can significantly improve users' lives.

15 | 16 |

This is a collection of high-quality learning resources organized by topics & formats and enriched by metadata like difficulty level, assumed prerequisites, reviews by experts and quality tags like visual, interactive, challenging etc. For many books or research papers, there are direct links thanks to projects like InternetArchive, LibGen, Arxiv, SciHub, IPFS etc.

17 | 18 |

This is an open-source project. We collaborate with OpenLibrary, OpenSyllabus and leverage projects like Wikipedia, Arxiv, InternetArchive, IPFS, SciHub for topic taxonomy, standardization of metadata formats etc. There are no user accounts and all bookmarks etc are kept in users' browsers.

19 | 20 |
21 | 22 |
23 |
-------------------------------------------------------------------------------- /src/Icon.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | {#if kind === 'home'} 7 | 8 | 11 | {:else if kind === "menu"} 12 | 13 | 16 | {:else if kind === "close"} 17 | 18 | 21 | {:else if kind === "bell"} 22 | 23 | 26 | {:else if kind === "search"} 27 | 28 | 31 | {:else if kind === "dots"} 32 | 33 | 36 | {:else if kind === "link"} 37 | 38 | 39 | 40 | {/if} -------------------------------------------------------------------------------- /src/ItemCard.svelte: -------------------------------------------------------------------------------- 1 | 15 | 16 | {#if item.links.join(' ').includes('book|') && item.links.join(' ').includes('video|')} 17 | {#if displayType == 'video'} 18 | 19 | {:else} 20 | 21 | {/if} 22 | {:else if item.links.join(' ').includes('book|')} 23 | 24 | {:else if item.links.join(' ').includes('video|')} 25 | 26 | {:else} 27 | 28 | {/if} -------------------------------------------------------------------------------- /src/ItemDetail.svelte: -------------------------------------------------------------------------------- 1 | 56 | 57 | 77 | 78 | {#if item} 79 | 80 |
81 |

82 | {#each item.topics as topicname} 83 | 88 | {/each} 89 |

90 | 91 |
92 |
93 |
94 | {#if item.links.join(' ').includes('wiki|')} 95 | 96 | {:else if item.links.join(' ').includes('video|') && oembed_iframe} 97 | {@html oembed_iframe.replace('width="200"','width="100%"').replace(/height=["'][0-9]+["']/i,'height="400"')} 98 | {:else if item.image} 99 |
100 | {item.name} 101 |
102 | {:else if item.links.join(' ').includes('video')} 103 |
104 |
105 | {item.name} 106 |
107 | 110 |
111 | {:else if !item.links.join(' ').includes('video') && item.links.join(' ').includes('book')} 112 |
113 | {item.name} 114 | 115 |
116 |

{item.name}

117 | {#if item.creators} 118 |

{item.creators}

119 | {/if} 120 |
121 | 122 |
123 | {/if} 124 | 125 |
126 | 127 | 128 |
129 | 130 |
131 |

{item.name}

132 | {#if item.creators} 133 | {item.creators} 134 | {/if} 135 |
136 | 137 |
138 |
139 | 140 |
141 |
142 | {#each item.links as type} 143 | 144 | {type.split("|")[0]} at {get_tld(type.split("|")[1])} 145 | {#if type.split("|")[2] || type.split("|")[0] === 'book'} 146 | window.open(e.detail.item.value, '_blank')}> 147 | 148 | 149 | 150 | 151 | {#if type.split("|")[2] && type.split("|")[2].startsWith('ipfs:')} 152 | Download via IPFS: 153 | Cloudflare 154 | IPFS.io 155 | Pinata 156 | {/if} 157 | 158 | {#if type.split("|")[2] && type.split("|")[2].startsWith('doi:')} 159 | On SciHub 160 | {/if} 161 | 162 | {#if type.split("|")[0] === 'book'} 163 | 164 | Look up on: 165 | LibGen 166 | OpenLibrary 167 | GoodReads 168 | {/if} 169 | 170 | 171 | {/if} 172 | 173 | {/each} 174 |
175 | 176 |
177 |
178 |
179 | 180 | 181 | 182 | {#if item.description} 183 |
184 |
185 |

Description

186 |

{item.description}

187 |
188 | {/if} 189 | 190 | 191 |
192 | 193 | {#if item.genre} 194 | 195 |
196 |
197 |

genre

198 | 199 | 200 | 201 | 202 | 203 | 204 |
205 | {item.genre} 206 |
207 | {/if} 208 | 209 | {#if item.year} 210 |
211 |
212 |

year

213 | {item.year} 214 |
215 |
216 | {/if} 217 | 218 | {#if item.difficulty} 219 |
220 |
221 |

Difficulty

222 | 223 |
224 | {item.difficulty} 225 |
226 | {/if} 227 | 228 | {#if item.creators?.length > 0} 229 |
230 |
231 |

Creator

232 | 233 |
234 | {item.creators} 235 |
236 | {/if} 237 | 238 | 246 | 247 | {#if item.size} 248 |
249 |
250 |

Size

251 | 252 |
253 | {item.size} 254 |
255 | {/if} 256 |
257 | 258 | 259 | {#if reviews?.length > 0} 260 |
261 |
262 |

Reviews

263 |
264 | 265 |
266 | {#each reviews as review} 267 | 268 | {/each} 269 | 270 |
271 |
272 | {/if} 273 | 274 | 275 |
276 | 277 |
278 | {:else} 279 |

loading...

280 | {/if} 281 | -------------------------------------------------------------------------------- /src/ItemList.svelte: -------------------------------------------------------------------------------- 1 | 15 | 16 | 17 |
18 | 19 | 55 | 56 | 57 |
58 | 59 | {#each formats.filter(f => items.filter(x => x.links.join(' ').includes(f.id + '|')).length > 0) as format, i} 60 | 61 | {getFormatDisplayName(format.name)} 62 | 63 | {#if format.id == 'book'} 64 | 65 |
66 | {#each items.filter(x => x.links.join(' ').includes(format.id + '|')) as item} 67 | 68 | {/each} 69 |
70 |
71 | 72 | {:else if format.id == 'video'} 73 | 74 |
75 | {#each items.filter(x => x.links.join(' ').includes(format.id + '|')) as item} 76 | 77 | {/each} 78 |
79 |
80 | 81 | {:else} 82 | 83 |
84 | {#each items.filter(x => x.links.join(' ').includes(format.id + '|')) as item} 85 | 86 | {/each} 87 |
88 |
89 | {/if} 90 | {/each} 91 |
92 |
93 |
94 | 95 | 96 | -------------------------------------------------------------------------------- /src/MasonryItem.svelte: -------------------------------------------------------------------------------- 1 | 15 | 16 | {#if parent && parent[0] !== 'Misc'} 17 | 18 |
19 | {#if typeof(parent[0]) == "string"} 20 | { parent[0] } 21 | {:else} 22 | 28 | 29 | {/if} 30 | 31 |
32 | {#each parent[1].sort((t1,t2) => (t1.name.localeCompare(t2.name))) as child} 33 | 34 | {format_topic_name(child)} 35 | {/each} 36 |
37 |
38 | 39 | {/if} 40 | 41 | {#if parent && parent[0] == 'Misc'} 42 |
43 | {#if typeof(parent[0]) == "string"} 44 | { parent[0] } 45 | {:else} 46 | { format_topic_name(parent[0]) } 47 | {/if} 48 | 49 |
50 | {#each parent[1].sort((t1,t2) => (t1.rank || 100) - (t2.rank || 100)) as child} 51 | {format_topic_name(child)} 52 | {/each} 53 |
54 |
55 | {/if} -------------------------------------------------------------------------------- /src/NavButtonWithLabel.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | 14 |
15 | {@render children?.()} 16 | 17 |
18 |

{label}

19 |
20 |
21 |
22 |
23 | -------------------------------------------------------------------------------- /src/PancakeTreemap.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | {#snippet children({ node })} 12 | {@render children_render?.({ node, })} 13 | {/snippet} 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/Review.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 13 |
14 |

{(review.blurb && (review.blurb.slice(0,10) + "...")) || ""}

15 |

{review.blurb || ""}

16 |
17 | {#if review.rating} 18 | 19 | {/if} 20 | {review.by_creator} 21 |
22 |
-------------------------------------------------------------------------------- /src/Roadmap.svelte: -------------------------------------------------------------------------------- 1 | 175 | 176 | 177 |

{topic.split('_').map(capitalize).join(' ')}

178 |
179 |
180 | {#if roadmap} 181 | 182 | 183 | 192 | 193 | 194 | 195 | 196 | 197 | 203 | 204 | {roadmap.start.label} 209 | 210 | {#each roadmap.blocks as node,i (node.label)} 211 | 212 | 217 | 218 | {node.label} 223 | 224 | 225 | {#each node.left as sec, j} 226 | 227 | 228 | 229 | selectedNode = sec} 234 | width={250} 235 | height={45}> 236 | 237 | 238 | 239 | selectedNode = sec} 243 | x={20+10} 244 | y={30+blockHeight*i+50*j+30} 245 | >{sec.label} 246 | 247 | {/each} 248 | 249 | {#each node.middle as sec,j} 250 | 251 | 252 | 253 | selectedNode = sec} 257 | x={350} y={140+blockHeight*i+50*j} rx={5} 258 | width={250} 259 | height={45}> 260 | 261 | 262 | 263 | selectedNode = sec} 267 | x={350+10} 268 | y={140+blockHeight*i+50*j+30} 269 | >{sec.label} 270 | 271 | {/each} 272 | 273 | {#each node.right as sec, j} 274 | 275 | 276 | 277 | selectedNode = sec} 281 | x={720} y={30+blockHeight*i+50*j} rx={5} 282 | width={250} 283 | height={45}> 284 | 285 | 286 | 287 | selectedNode = sec} 291 | x={720+10} 292 | y={30+blockHeight*i+50*j+30} 293 | >{sec.label} 294 | 295 | {/each} 296 | 297 | 298 | 299 | 300 | {#if i > 0} 301 | 302 | 303 | {/if} 304 | {/each} 305 | 306 | 307 | 312 | 313 | {roadmap.end.label} 318 | 319 | 320 | {:else} 321 |

Coming soon.

322 | {/if} 323 |
324 | 325 | 326 | {#if selectedNode} 327 | {#if $roadmap_progress[topic] && $roadmap_progress[topic][selectedNode.label] === 'done'} 328 | 329 | 330 | saveProgress(topic, selectedNode.label, 'pending')}>Mark as Pending 331 | {:else} 332 | 333 | 334 | saveProgress(topic, selectedNode.label, 'done')}>Mark as Done 335 | {/if} 336 |
337 | {@html marked(selectedNode?.desc || "", { renderer })} 338 |
339 | {/if} 340 | 341 | 342 | selectedNode = null}>Close 343 |
344 | 345 | -------------------------------------------------------------------------------- /src/RoadmapList.svelte: -------------------------------------------------------------------------------- 1 |

Roadmaps

2 | -------------------------------------------------------------------------------- /src/SearchForm.svelte: -------------------------------------------------------------------------------- 1 | 34 | 35 |
36 |
37 | {#if showForm == false} 38 |
39 | 43 |
44 | {/if} 45 | 46 | {#if showForm} 47 |
48 | 52 |
53 | {/if} 54 | 55 | {#if showForm} 56 |
57 | query.text = e.target.value}> 58 | 59 | 60 | 61 | {#if !hideTopic} 62 | query.topic = e.target.value}> 63 | {#each alltopics.sort((a,b) => (a.hname || a.name).localeCompare(b.hname || b.name)) as topic} 64 | {topic.hname || topic.name} 65 | {/each} 66 | 67 | {/if} 68 | 69 |
70 | query.tag = e.target.value} value={query.tag}> 71 | Any tag 72 | Inspirational 73 | Educational 74 | Challenging 75 | Entertaining 76 | Visual 77 | Interactive 78 | Open (no login or pay) 79 | 80 | 81 | query.level = e.target.value} value={query.level}> 82 | Any level 83 | Childlike 84 | Beginner 85 | Intermediate 86 | Advanced 87 | Research 88 | 89 |
90 | 91 | query.sortby = e.target.value} value={query.sortby}> 92 | 93 | Sort by Rating 94 | Sort by Year 95 | Sort by Name 96 | 97 |
98 | {/if} 99 |
100 |
101 | -------------------------------------------------------------------------------- /src/Settings.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 | 7 |
8 |
9 |
10 |
11 |

Settings

12 |

13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | 21 | 22 |
23 | 24 | 30 |
31 | 32 |
33 |
34 |
35 | 36 |
37 |
38 |
39 |
40 |
41 |
42 | 43 | 44 | 45 |
-------------------------------------------------------------------------------- /src/SkillTree.svelte: -------------------------------------------------------------------------------- 1 | 47 | 48 |
49 | 50 | {#if currentParent}
51 | 52 | 55 | 56 |
{/if} 57 | 58 |

{currentChild.name}

59 | 60 |
61 | 62 | {#each siblings(currentParent, currentChild) as sibling} 63 | 66 | {/each} 67 | 68 |
69 | 70 |
71 | 72 |
73 | {#each currentChild.children as grandchild, i (grandchild)} 74 | 77 | {/each} 78 |
-------------------------------------------------------------------------------- /src/TopicDetail.svelte: -------------------------------------------------------------------------------- 1 | 48 | 49 | 50 | 51 |
52 | 53 | 54 | 55 |
56 | 57 | 58 | -------------------------------------------------------------------------------- /src/TopicList.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/TopicMasonryGrid.svelte: -------------------------------------------------------------------------------- 1 | 82 | 83 | 90 |
91 | 92 | 93 | All Topics 94 | 95 | {#if topic} 96 | {#if topic.parent} 97 | 98 | {capitalize(topic.parent.replace('-',' '))} 99 | 100 | {/if} 101 | {#if topic.hname || topic.name} 102 | 103 | {capitalize((topic.hname || topic.name).split('/').reverse()[0])} 104 | 105 | {/if} 106 | {/if} 107 | 108 | {#if topic?.name == "programming-languages/go"} 109 | Check out our syllabus for Golang 110 | {:else} 111 | Help us improve this taxonomy 112 | {/if} 113 |
114 | 115 | {#if parents} 116 | 126 | {#snippet children({ item })} 127 | 128 | {/snippet} 129 | 130 | {/if} 131 | 132 | -------------------------------------------------------------------------------- /src/TreeMap.svelte: -------------------------------------------------------------------------------- 1 | 91 | 92 | 95 | 96 |
97 | 98 | 99 | {#snippet children({ node })} 100 | {#if is_visible(node, selected)} 101 | 102 | 103 |
select(node)} 108 | > 109 |
110 | 111 | window.location.href = "/#/topic/" + node.data.name)}> 112 | {node.data.name.split('/').reverse()[0]} 113 | 114 |
115 |
116 | {/if} 117 | {/snippet} 118 |
119 |
120 |
121 | 122 | -------------------------------------------------------------------------------- /src/TreemapNode.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | {@render children?.({ node, })} 11 | 12 | 13 | {#each (node.children || []) as child} 14 | 15 | {#snippet children({ node })} 16 | {@render children?.({ node, })} 17 | {/snippet} 18 | 19 | {/each} -------------------------------------------------------------------------------- /src/VideoCard.svelte: -------------------------------------------------------------------------------- 1 | 33 | 34 | 35 | 36 |
37 |
38 | 39 | {#if item.image || oEmded_image_ytb_url} 40 | 41 |
42 | {item.name} 43 | 46 | 47 |
48 | 49 | {:else} 50 | 51 |
52 |
53 | 56 |
57 | 58 | 59 | {/if} 60 |
61 | 62 |
63 | {item.name} 64 | {#if item.creators}{item.creators}{/if} 65 |
66 | 67 |
68 |
69 | 70 | -------------------------------------------------------------------------------- /src/app.postcss: -------------------------------------------------------------------------------- 1 | /* Write your global styles here, in PostCSS syntax */ 2 | @tailwind base; 3 | @tailwind components; 4 | @tailwind utilities; 5 | -------------------------------------------------------------------------------- /src/formats.js: -------------------------------------------------------------------------------- 1 | export const formats = [ 2 | {id: "book", name: "Books", image: "https://images.unsplash.com/photo-1524578271613-d550eacf6090?crop=entropy&cs=tinysrgb&fm=jpg&ixlib=rb-1.2.1&q=60&raw_url=true&ixid=MnwxMjA3fDB8MHxzZWFyY2h8MTN8fGJvb2tzfGVufDB8fDB8fA%3D%3D&auto=format&fit=crop&w=300"}, 3 | {id: "audio", name: "Audio", image: "https://images.unsplash.com/photo-1505740420928-5e560c06d30e?crop=entropy&cs=tinysrgb&fm=jpg&ixlib=rb-1.2.1&q=80&raw_url=true&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=400"}, 4 | {id: "video", name: "Videos", image: "https://images.unsplash.com/photo-1611162616475-46b635cb6868?crop=entropy&cs=tinysrgb&fm=jpg&ixlib=rb-1.2.1&q=80&raw_url=true&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=400"}, 5 | {id: "article", name: "Articles", image: "https://images.unsplash.com/photo-1623039405147-547794f92e9e?crop=entropy&cs=tinysrgb&fm=jpg&ixlib=rb-1.2.1&q=80&raw_url=true&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=400"}, 6 | {id: "app", name: "Software", image: "https://images.unsplash.com/photo-1601034913836-a1f43e143611?crop=entropy&cs=tinysrgb&fm=jpg&ixlib=rb-1.2.1&q=60&raw_url=true&ixid=MnwxMjA3fDB8MHxzZWFyY2h8MTV8fGFwcHxlbnwwfHwwfHw%3D&auto=format&fit=crop&w=400"}, 7 | {id: "blog", name: "Blogs", image: "https://images.unsplash.com/photo-1649180554466-0c15f6c70d51?crop=entropy&cs=tinysrgb&fm=jpg&ixlib=rb-1.2.1&q=60&raw_url=true&ixid=MnwxMjA3fDB8MHxzZWFyY2h8MTM2fHx0d2l0dGVyfGVufDB8fDB8fA%3D%3D&auto=format&fit=crop&w=400"}, 8 | {id: "chat", name: "Forums", image: "https://images.unsplash.com/photo-1611746869696-d09bce200020?crop=entropy&cs=tinysrgb&fm=jpg&ixlib=rb-1.2.1&q=80&raw_url=true&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=400"}, 9 | {id: "cheatsheet", name: "Cheatsheets", image: "https://images.unsplash.com/photo-1432888498266-38ffec3eaf0a?crop=entropy&cs=tinysrgb&fm=jpg&ixlib=rb-1.2.1&q=80&raw_url=true&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=400"}, 10 | {id: "code", name: "Code", image: "https://images.unsplash.com/photo-1627398242454-45a1465c2479?crop=entropy&cs=tinysrgb&fm=jpg&ixlib=rb-1.2.1&q=60&raw_url=true&ixid=MnwxMjA3fDB8MHxzZWFyY2h8MzF8fGNvZGV8ZW58MHx8MHx8&auto=format&fit=crop&w=400"}, 11 | {id: "conference", name: "Conferences", image: "https://images.unsplash.com/photo-1477281765962-ef34e8bb0967?ixlib=rb-1.2.1&raw_url=true&q=80&fm=jpg&crop=entropy&cs=tinysrgb&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=400"}, 12 | {id: "course", name: "Courses", image: "https://images.unsplash.com/photo-1604134967494-8a9ed3adea0d?crop=entropy&cs=tinysrgb&fm=jpg&ixlib=rb-1.2.1&q=80&raw_url=true&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=400"}, 13 | {id: "flashcard", name: "Flashcards", image: "https://images.unsplash.com/photo-1616628188859-7a11abb6fcc9?crop=entropy&cs=tinysrgb&fm=jpg&ixlib=rb-1.2.1&q=80&raw_url=true&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=400"}, 14 | {id: "game", name: "Games", image: "https://images.unsplash.com/photo-1493711662062-fa541adb3fc8?crop=entropy&cs=tinysrgb&fm=jpg&ixlib=rb-1.2.1&q=60&raw_url=true&ixid=MnwxMjA3fDB8MHxzZWFyY2h8MXx8dmlkZW8lMjBnYW1lfGVufDB8fDB8fA%3D%3D&auto=format&fit=crop&w=400"}, 15 | {id: "image", name: "Infographics", image: "https://images.unsplash.com/photo-1551288049-bebda4e38f71?crop=entropy&cs=tinysrgb&fm=jpg&ixlib=rb-1.2.1&q=80&raw_url=true&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=400"}, 16 | {id: "interactive", name: "Interactives", image: "https://images.unsplash.com/photo-1516321497487-e288fb19713f?crop=entropy&cs=tinysrgb&fm=jpg&ixlib=rb-1.2.1&q=80&raw_url=true&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=400"}, 17 | {id: "journal", name: "Journals", image: "https://images.unsplash.com/photo-1516179257071-71a54dbb4853?crop=entropy&cs=tinysrgb&fm=jpg&ixlib=rb-1.2.1&q=60&raw_url=true&ixid=MnwxMjA3fDB8MHxzZWFyY2h8NHx8bWFnYXppbmV8ZW58MHx8MHx8&auto=format&fit=crop&w=400"}, 18 | {id: "learning_plan", name: "Syllabuses", image: "https://images.unsplash.com/photo-1423592707957-3b212afa6733?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2532&q=80"}, 19 | {id: "livestream", name: "Livestreams", image: "https://images.unsplash.com/photo-1607968565043-36af90dde238?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2338&q=80"}, 20 | {id: "meetup", name: "Meetups", image: "https://images.unsplash.com/photo-1591115765373-5207764f72e7?crop=entropy&cs=tinysrgb&fm=jpg&ixlib=rb-1.2.1&q=80&raw_url=true&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=400"}, 21 | {id: "newsletter", name: "Newsletters", image: "https://images.unsplash.com/photo-1466096115517-bceecbfb6fde?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2340&q=80"}, 22 | {id: "people", name: "People", image: "https://images.unsplash.com/photo-1517048676732-d65bc937f952?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2340&q=80"}, 23 | {id: "qna", name: "Q&A forums", image: "https://images.unsplash.com/photo-1544535830-9df3f56fff6a?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1374&q=80"}, 24 | {id: "research_paper", name: "Research", image: "https://images.unsplash.com/photo-1532153955177-f59af40d6472?crop=entropy&cs=tinysrgb&fm=jpg&ixlib=rb-1.2.1&q=80&raw_url=true&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=400"}, 25 | {id: "website", name: "Websites", image: "https://images.unsplash.com/photo-1460925895917-afdab827c52f?ixlib=rb-1.2.1&raw_url=true&q=80&fm=jpg&crop=entropy&cs=tinysrgb&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=400"}, 26 | {id: "wiki", name: "Wikis", image: "https://images.unsplash.com/photo-1566396223585-c8fbf7fa6b6d?crop=entropy&cs=tinysrgb&fm=jpg&ixlib=rb-1.2.1&q=80&raw_url=true&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=400"}, 27 | {id: "thread", name: "Discussion", image: "https://images.unsplash.com/photo-1515187029135-18ee286d815b?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2340&q=80"}, 28 | {id: "project", name: "Projects", image: "https://images.unsplash.com/photo-1620325867502-221cfb5faa5f?crop=entropy&cs=tinysrgb&fm=jpg&ixlib=rb-1.2.1&q=60&raw_url=true&ixid=MnwxMjA3fDB8MHxzZWFyY2h8MXx8cHJvamVjdHxlbnwwfHwwfHw%3D&auto=format&fit=crop&w=400"}, 29 | {id: "webmeet", name: "Meetups", image: "https://images.unsplash.com/photo-1586543354240-2187898bb2e8?crop=entropy&cs=tinysrgb&fm=jpg&ixlib=rb-1.2.1&q=80&raw_url=true&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=400"}, 30 | {id: "webconf", name: "Conferences", image: "https://images.unsplash.com/photo-1586985564150-11ee04838034?crop=entropy&cs=tinysrgb&fm=jpg&ixlib=rb-1.2.1&q=80&raw_url=true&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=400"}, 31 | {id: "thing", name: "Things", image: "https://images.unsplash.com/photo-1416339134316-0e91dc9ded92?ixlib=rb-1.2.1&raw_url=true&q=60&fm=jpg&crop=entropy&cs=tinysrgb&ixid=MnwxMjA3fDB8MHxzZWFyY2h8Nnx8c3R1ZmZ8ZW58MHx8MHx8&auto=format&fit=crop&w=400"}, 32 | {id: "summary", name: "Summaries", image: "https://images.unsplash.com/photo-1462642109801-4ac2971a3a51?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2273&q=80"}, 33 | 34 | ] -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import "./app.postcss"; 2 | import App from "./App.svelte"; 3 | import { mount } from "svelte"; 4 | 5 | mount(App, { 6 | target: document.querySelector("#app"), 7 | }); 8 | -------------------------------------------------------------------------------- /src/persistStore.js: -------------------------------------------------------------------------------- 1 | import { writable } from "svelte/store"; 2 | 3 | export const persistStore = (key, initial)=> { 4 | const persist = localStorage.getItem(key) 5 | const data = persist ? JSON.parse(persist) : initial 6 | 7 | const store = writable(data, () => { 8 | const unsubscribe = store.subscribe(value => { 9 | localStorage.setItem(key, JSON.stringify(value)) 10 | }) 11 | return unsubscribe 12 | }) 13 | return store 14 | } -------------------------------------------------------------------------------- /src/placeholder.ts: -------------------------------------------------------------------------------- 1 | export {}; 2 | 3 | // Don't remove this as typescript compiler expects at least one .ts file -------------------------------------------------------------------------------- /src/roadmapAlt.svelte: -------------------------------------------------------------------------------- 1 | 205 | 206 |
207 | 217 |
218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | {#if selectedNode} 226 | {#if $roadmap_progress[topic] && $roadmap_progress[topic][selectedNode.label] === 'done'} 227 | 228 | 229 | saveProgress(topic, selectedNode.label, 'pending')}>Mark as Pending 230 | {:else} 231 | 232 | 233 | saveProgress(topic, selectedNode.label, 'done')}>Mark as Done 234 | {/if} 235 |
236 | {@html marked(selectedNode? initialNodes[selectedNode-1].data.desc : "", { renderer })} 237 |
238 | {/if} 239 | 240 | 241 | selectedNode = null}>Close 242 |
-------------------------------------------------------------------------------- /src/roadmap_data.js: -------------------------------------------------------------------------------- 1 | export const roadmap_data = { 2 | start: {label: "Computer and Internet Basics"}, 3 | blocks: [ 4 | { 5 | label: "Learn Golang Basics", 6 | group1: [ 7 | { 8 | label: "Basic Syntax", 9 | desc: ` 10 | # Basic Syntax 11 | 12 | Learn about the basic syntax of Go, such as how the go programs are executed, package imports, main function, and so on. Visit the resources listed below 13 | 14 | ### Resources: 15 | - READ: [Go Tutorial: Getting started](https://go.dev/doc/tutorial/getting-started) 16 | - READ: [Go by Example: Hello World](https://gobyexample.com/hello-world) 17 | - READ: [W3schools : Go Syntax](https://www.w3schools.com/go/go_syntax.php) 18 | ` 19 | }, 20 | { 21 | label: "Variables and declaration", 22 | desc: ` 23 | # Variables in Go 24 | 25 | Variable is the name given to a memory location to store a value of a specific [type](https://golangbot.com/types/). Go provides multiple ways to declare and use variables. 26 | 27 | ### Resources: 28 | - READ: [Go Variables](https://go.dev/tour/basics/8) 29 | - READ: [Go by Example: Variables](https://gobyexample.com/variables) 30 | - READ: [w3schools Go variables](https://www.w3schools.com/go/go_variables.php) 31 | ` 32 | }, 33 | { 34 | label: "Data types", 35 | desc: ` 36 | # Data Types 37 | 38 | Go is a statically typed programming language, which means each variable has a type defined at first and can only hold values with that type. There are two categories of types in Go: basics types and composite types. 39 | 40 | To learn more about types in Go, visit these resources : 41 | 42 | ### Resources: 43 | - READ: [Basic data types](https://www.w3schools.com/go/go_data_types.php) 44 | - READ: [Tour of Go: types](https://go.dev/tour/basics/11) 45 | - READ: [Go types with examples](https://golangbyexample.com/all-data-types-in-golang-with-examples/) 46 | ` 47 | }, 48 | ], 49 | group2: [ 50 | { 51 | label: "Conditional statements: if, switch", 52 | desc: ` 53 | # Conditional Statements 54 | 55 | Conditional statements are used to run code only if a certain condition is true; go supports : 56 | 57 | - \`if\` statements 58 | - \`if / else\` statements 59 | - \`switch\` \`case\` statements 60 | 61 | ### Resources: 62 | - READ: [Effective Go: if statement](https://go.dev/doc/effective_go#if) 63 | - READ: [Basic conditional patterns](https://yourbasic.org/golang/if-else-statement/) 64 | - READ: [Go by Example: If-Else](https://gobyexample.com/if-else) 65 | - READ: [Golang programs if else](https://www.golangprograms.com/golang-if-else-statements.html) 66 | - READ: [Golang programs switch case](https://www.golangprograms.com/golang-switch-case-statements.html) 67 | ` 68 | }, 69 | { 70 | label: "Looping: for, range", 71 | desc: ` 72 | # For Loop 73 | 74 | Go has only one looping construct, the \`for\` loop. The basic \`for\` loop has three components separated by semicolons: 75 | 76 | - the init statement: executed before the first iteration 77 | - the condition expression: evaluated before every iteration 78 | - the post statement: executed at the end of every iteration 79 | 80 | ### Resources: 81 | - READ: [For Loop in Golang](https://go.dev/tour/flowcontrol/1) 82 | - READ: [Effective Go: For loop](https://go.dev/doc/effective_go#for) 83 | - READ: [Go by Example: For loop](https://gobyexample.com/for) 84 | - READ: [5 basic for loop patterns](https://yourbasic.org/golang/for-loop/) 85 | 86 | # Range 87 | 88 | \`Range\` is used with \`For Loops\` to iterate over each element in arrays, strings and other data structures . 89 | 90 | ### Resources: 91 | - READ: [Go Ranges](https://go.dev/tour/moretypes/16) 92 | - READ: [Go by Example: Range](https://gobyexample.com/range) 93 | - READ: [Go ranges basic patterns](https://yourbasic.org/golang/for-loop-range-array-slice-map-channel/) 94 | ` 95 | }, 96 | { 97 | label: "Errors, Panic, Recover", 98 | desc: ` 99 | # Errors/Panic/Recover 100 | 101 | In lieu of adding exception handlers, the Go creators exploited Go’s ability to return multiple values. The most commonly used Go technique for issuing errors is to return the error as the last value in a return. 102 | 103 | A panic typically means something went unexpectedly wrong. Mostly used to fail fast on errors that shouldn’t occur during normal operation, or that we aren’t prepared to handle gracefully. 104 | 105 | Panic recovery in Go depends on a feature of the language called deferred functions. Go has the ability to guarantee the execution of a function at the moment its parent function returns. This happens regardless of whether the reason for the parent function’s return is a return statement, the end of the function block, or a panic. 106 | 107 | ### Resources: 108 | - READ: [Error handling and Go](https://go.dev/blog/error-handling-and-go) 109 | - READ: [Go Defer, Panic and Recover](https://go.dev/blog/defer-panic-and-recover) 110 | - READ: [Effective error handling in Go](https://earthly.dev/blog/golang-errors/) 111 | ` 112 | }, 113 | ], 114 | group3: [ 115 | { 116 | label: "Functions, multiple/named returns", 117 | desc: ` 118 | # Functions 119 | 120 | Discover how functions work in Go, the list of resources below will cover : 121 | 122 | - How to define and call functions in Go? 123 | - Named returns in Go? 124 | - Handle multiple return types. 125 | - Different types of functions in Go. 126 | 127 | ### Resources: 128 | - READ: [Go by Example: Functions](https://gobyexample.com/functions) 129 | - READ: [Functions in go](https://www.golangprograms.com/go-language/functions.html) 130 | 131 | ` 132 | }, 133 | { 134 | label: "Packages, imports and exports", 135 | desc: ` 136 | # Packages 137 | 138 | Packages are the most powerful part of the Go language. The purpose of a package is to design and maintain a large number of programs by grouping related features together into single units so that they can be easy to maintain and understand and independent of the other package programs. This modularity allows them to share and reuse. In Go language, every package is defined with a different name and that name is close to their functionality like “strings” package and it contains methods and functions that only related to strings. 139 | 140 | ### Resources: 141 | - READ: [How to create a package in Go](https://www.golang-book.com/books/intro/11) 142 | - READ: [How to manage external dependencies in Go](https://go.dev/doc/modules/managing-dependencies) 143 | - EXPLORE: [Go Packages explorer](https://pkg.go.dev/) 144 | - Official Website: [Standard library](https://pkg.go.dev/std) 145 | - READ: [Packages in Golang](https://www.geeksforgeeks.org/packages-in-golang/) 146 | - READ: [Go Packages](https://www.programiz.com/golang/packages) 147 | 148 | ` 149 | }, 150 | { 151 | label: "Type casting, Type inference", 152 | desc: ` 153 | # Type Casting 154 | 155 | Go doesn't support automatic type conversion, but it allows type casting, which is the process of explicitly changing the variable type. To learn more about typecasting, visit these resources : 156 | 157 | ### Resources: 158 | - READ: [Geeks for Geeks: Type casting](https://www.geeksforgeeks.org/type-casting-or-type-conversion-in-golang/) 159 | - READ: [Tour of Go: Type Casting Basics](https://go.dev/tour/basics/13) 160 | - READ: [Go Docs: Type Casting](https://golangdocs.com/type-casting-in-golang) 161 | 162 | # Type Inference 163 | 164 | Type inference gives go the capability to detect the type of a value without being explicitly indicated , hence the possibility to declare variables without providing its type at first 165 | 166 | ### Resources: 167 | - READ: [Go Variables: Type Inference](https://www.callicoder.com/golang-variables-zero-values-type-inference/#type-inference) 168 | - READ: [Tour of Go: Type Inference](https://go.dev/tour/basics/14) 169 | ` 170 | }, 171 | { 172 | label: "Arrays, Slices, Maps", 173 | desc: ` 174 | # Arrays 175 | 176 | In Go an \`array\` is a collection of elements of the same type with a **fixed** size defined when the array is created. 177 | 178 | ### Resources: 179 | - Official Website: [Go Arrays](https://go.dev/tour/moretypes/6) 180 | - READ: [Effective Go: Arrays](https://go.dev/doc/effective_go#arrays) 181 | - WATCH: [Learn Go Programming - Arrays (by freeCodeCamp on YouTube)](https://youtu.be/YS4e4q9oBaU?t=6473) 182 | 183 | # Slices 184 | 185 | Slices are similar to arrays but are more powerful and flexible. Like arrays, slices are also used to store multiple values of the same type in a single variable. However, unlike arrays, the length of a slice can grow and shrink as you see fit. 186 | 187 | ### Resources: 188 | 189 | - Official Website: [Go Slices](https://go.dev/tour/moretypes/7) 190 | - READ: [Effective Go: Slices](https://go.dev/doc/effective_go#slices) 191 | - READ: [Slices in Go](https://www.w3schools.com/go/go_slices.php) 192 | - WATCH: [Learn Go Programming - Slices (by freeCodeCamp on YouTube)](https://youtu.be/YS4e4q9oBaU?t=6473) 193 | 194 | # Maps 195 | 196 | Maps are the data structure in Go, where we use whenever we want to have mappings between key:value pairs. They have flexibility in terms of removing or adding elements into them. Maps do not allow duplicate entries while data are kept unordered. 197 | 198 | ### Resources: 199 | 200 | - Official Website: [Go Maps](https://go.dev/tour/moretypes/19) 201 | - READ: [Effective Go: Maps](https://go.dev/doc/effective_go#maps) 202 | - READ: [Maps in Go](https://www.w3schools.com/go/go_maps.php) 203 | - WATCH: [Golang Tutorial #15 - Maps (by Tech With Tim on YouTube)](https://www.youtube.com/watch?v=yJE2RC37BF4) 204 | ` 205 | }, 206 | { 207 | label: "make(), structs", 208 | desc: ` 209 | # Make 210 | 211 | Golang's built-in function make, helps us create and initialize slices, maps and channels, depending on the arguments that are provided to the function. 212 | 213 | ### Resources: 214 | - READ: [Effective Go: Allocation with make](https://go.dev/doc/effective_go#allocation_make) 215 | - READ: [Create a slice with make](https://www.golangprograms.com/how-to-create-slice-using-make-function-in-golang.html) 216 | - READ: [Create a map with make](https://www.golangprograms.com/golang-package-examples/how-to-create-map-using-the-make-function-in-go.html) 217 | - READ: [Create a channel with make](https://www.programiz.com/golang/channel#channel) 218 | 219 | # Structs 220 | 221 | Structs are user-defined types that help us create a collection of data describing a single entity. 222 | 223 | ### Resources: 224 | - Official Website: [Go Structs](https://go.dev/tour/moretypes/2) 225 | - READ: [Go by Example: Structs](https://gobyexample.com/structs) 226 | - WATCH: [Structs in Go](https://www.youtube.com/watch?v=NMTN543WVQY) 227 | ` 228 | }, 229 | ] 230 | }, 231 | 232 | 233 | 234 | { 235 | label: "Go Deeper", 236 | group1: [ 237 | { 238 | label: "Go Modules", 239 | desc: ` 240 | # Modules 241 | 242 | Go modules are a group of related packages that are versioned and distributed together. They specify the requirements of our project, list all the required dependencies, and help us keep track of the specific versions of installed dependencies. 243 | 244 | Modules are identified by a module path that is declared in the first line of the go.mod file in our project. 245 | 246 | ### Resources: 247 | - Official Website: [Go Modules](https://go.dev/blog/using-go-modules) 248 | - WATCH: [Go Modules](https://www.youtube.com/watch?v=9cV1KESTJRc) 249 | - READ: [DigitalOcean: How to use Go Modules](https://www.digitalocean.com/community/tutorials/how-to-use-go-modules) 250 | - WATCH: [Go Modules Explained in 5 Minutes (by Golang Dojo on YouTube)](https://youtu.be/7xSxIwWJ9R4) 251 | - READ: [How to create a module in Go](https://go.dev/doc/tutorial/create-module) 252 | - READ: [How to use modules in Go](https://go.dev/blog/using-go-modules) 253 | - READ: [How to modify existing projects to use Go modules](https://jfrog.com/blog/converting-projects-for-go-modules/) 254 | ` 255 | }, 256 | { 257 | label: "Marshalling and unmarshalling JSON", 258 | desc: ` 259 | # Working with json 260 | 261 | JSON (JavaScript Object Notation) is a simple data interchange format. Syntactically it resembles the objects and lists of JavaScript. It is most commonly used for communication between web back-ends and JavaScript programs running in the browser, but it is used in many other places, too. 262 | 263 | ### Resources: 264 | - Official Website: [JSON](https://go.dev/blog/json) 265 | - READ: [Guide to JSON in Golang](https://www.sohamkamani.com/golang/json/) 266 | - READ: [JSON to GO](https://mholt.github.io/json-to-go/) 267 | ` 268 | }, 269 | ], 270 | group2: [ 271 | { 272 | label: "Types, type assertions, switches", 273 | desc: ` 274 | # Types and type assertions 275 | 276 | Type assertions in Golang provide access to the exact type of variable of an interface. 277 | 278 | ### Resources: 279 | - Official Website: [Types Assertions](https://go.dev/tour/methods/15) 280 | - READ: [Type Assertion](https://www.geeksforgeeks.org/type-assertions-in-golang/) 281 | 282 | ` 283 | }, 284 | { 285 | label: "Interfaces, context", 286 | desc: ` 287 | # Interfaces 288 | 289 | An interface in Go, is a type that defines a set of methods. If we have a type (e.g. struct) that implements that set of methods, then we have a type that implements this interface. 290 | 291 | ### Resources: 292 | 293 | - Official Website: [Go Interfaces](https://go.dev/tour/methods/9) 294 | - READ: [Effective Go: Interfaces](https://go.dev/doc/effective_go#interfaces) 295 | - READ: [Go by Example: Interfaces](https://gobyexample.com/interfaces) 296 | - WATCH: [Golang Tutorial #22 - Interfaces (by Tech With Tim on YouTube)](https://www.youtube.com/watch?v=lh_Uv2imp14) 297 | - WATCH: [Learn Go Interfaces](https://www.youtube.com/watch?v=KB3ysH8cupY) 298 | - WATCH: [Understanding Go Interfaces](https://www.youtube.com/watch?v=qJKQZKGZgf0) 299 | 300 | # Context 301 | 302 | The \`context\` package provides a standard way to solve the problem of managing the state during a request. The package satisfies the need for request-scoped data and provides a standardized way to handle: Deadlines, Cancellation Signals, etc. 303 | 304 | ### Resources: 305 | - Official Website: [Go Context](https://pkg.go.dev/context) 306 | - READ: [Go by Example: Context](https://gobyexample.com/context) 307 | - READ: [Digital Ocean: How to Use Contexts in Go](https://www.digitalocean.com/community/tutorials/how-to-use-contexts-in-go) 308 | - WATCH: [Context in Go](https://www.youtube.com/watch?v=LSzR0VEraWw) 309 | - WATCH: [Understanding Contexts in Go](https://youtu.be/h2RdcrMLQAo) 310 | 311 | ` 312 | }, 313 | ], 314 | group3: [ 315 | { 316 | label: "Goroutines, channels", 317 | desc: ` 318 | # Goroutines 319 | 320 | Goroutines allow us to write concurrent programs in Go. Things like web servers handling thousands of requests or a website rendering new pages while also concurrently making network requests are a few example of concurrency. 321 | 322 | In Go, each of these concurrent tasks are called \`Goroutines\`. 323 | 324 | ### Resources: 325 | - Official Website: [Goroutines](https://go.dev/tour/concurrency/1) 326 | - READ: [Effective Go: Goroutines](https://go.dev/doc/effective_go#goroutines) 327 | - READ: [Goroutines in Golang](https://www.geeksforgeeks.org/goroutines-concurrency-in-golang) 328 | - WATCH: [GoRoutines](https://www.youtube.com/watch?v=LvgVSSpwND8) 329 | - WATCH: [Understanding Concurrency](https://www.youtube.com/watch?v=V-0ifUKCkBI) 330 | - READ: [Go by Example: Goroutines](https://gobyexample.com/goroutines) 331 | - WATCH: [Golang Goroutine Basics You MUST Learn! (by Golang Dojo on YouTube)](https://youtu.be/oHIbeTmmTaA) 332 | 333 | # Channels 334 | 335 | Channels are the pipes that connect concurrent goroutines. You can send values into channels from one goroutine and receive those values into another goroutine. 336 | 337 | Channels are a typed conduit through which you can send and receive values with the channel operator, \`<-\` . 338 | 339 | ### Resources: 340 | - Official Website: [Channels](https://go.dev/tour/concurrency/2) 341 | - READ: [Effective Go: Channels](https://go.dev/doc/effective_go#channels) 342 | - READ: [Go by Example: Channels](https://gobyexample.com/channels) 343 | - READ: [Channels in Golang](https://golangbot.com/channels/) 344 | - WATCH: [Channels](https://www.youtube.com/watch?v=e4bu9g-bYtg) 345 | - READ: [GeeksForGeeks: Channel in Golang](https://www.geeksforgeeks.org/channel-in-golang/) 346 | - WATCH: [Golang Channel Basics You must Know!](https://youtu.be/LgCmPHqAuf4) 347 | 348 | ` 349 | }, 350 | { 351 | label: "Buffer, Select", 352 | desc: ` 353 | # Buffer 354 | 355 | The \`buffer\` belongs to the byte package of the Go language, and we can use these package to manipulate the byte of the string. 356 | 357 | ### Resources: 358 | - Official Website: [Buffer Examples](https://pkg.go.dev/bytes#example-Buffer) 359 | - READ: [Buffer](https://www.educba.com/golang-buffer/) 360 | - WATCH: [Buffers in Golang](https://www.youtube.com/watch?v=NoDRq6Twkts) 361 | 362 | # Select 363 | 364 | The \`select\` statement lets a goroutine wait on multiple communication operations. 365 | 366 | A \`select\` blocks until one of its cases can run, then it executes that case. It chooses one at random if multiple are ready. The \`select\` statement is just like switch statement, but in the select statement, case statement refers to communication, i.e. sent or receive operation on the channel. 367 | 368 | ### Resources: 369 | - Official Website: [Select](https://go.dev/tour/concurrency/5) 370 | - READ: [Go by Example: Select](https://gobyexample.com/select) 371 | - READ: [Select in Golang](https://www.geeksforgeeks.org/select-statement-in-go-language/) 372 | - WATCH: [Select Statement](https://www.youtube.com/watch?v=1c7ttSJDMAI) 373 | 374 | ` 375 | }, 376 | { 377 | label: "Mutex", 378 | desc: ` 379 | # Mutex 380 | 381 | Go allows us to run code concurrently using goroutines. However, when concurrent processes access the same piece of data, it can lead to [race conditions](https://www.sohamkamani.com/golang/data-races/). Mutexes are data structures provided by the [sync](https://pkg.go.dev/sync/) package. They can help us place a lock on different sections of data so that only one goroutine can access it at a time. 382 | 383 | - READ: [Using a Mutex in Go with Examples](https://www.sohamkamani.com/golang/mutex/) 384 | 385 | ` 386 | }, 387 | ] 388 | }, 389 | 390 | { 391 | label: "Applications", 392 | group1: [ 393 | { 394 | label: "Logging", 395 | desc: ` 396 | # Logging 397 | 398 | Go has built-in features to make it easier for programmers to implement logging. Third parties have also built additional tools to make logging easier. 399 | 400 | ### Resources: 401 | - READ: [Logging in Go: Choosing a System and Using it](https://www.honeybadger.io/blog/golang-logging/) 402 | - READ: [Logging in Golang – How to Start](https://www.loggly.com/use-cases/logging-in-golang-how-to-start/) 403 | ` 404 | }, 405 | { 406 | label: "Building CLIs", 407 | desc: ` 408 | # Building CLI Applications 409 | 410 | Command line interfaces (CLIs), unlike graphical user interfaces (GUIs), are text-only. Cloud and infrastructure applications are primarily CLI-based due to their easy automation and remote capabilities. 411 | 412 | Go applications are built into a single self contained binary making installing Go applications trivial; specifically, programs written in Go run on any system without requiring any existing libraries, runtimes, or dependencies. And programs written in Go have an immediate startup time—similar to C or C++ but unobtainable with other programming languages. 413 | 414 | ### Resources: 415 | - READ: [Command-line Interfaces (CLIs)](https://go.dev/solutions/clis) 416 | ` 417 | }, 418 | ], 419 | group2: [ 420 | { 421 | label: "ORMs", 422 | desc: ` 423 | # ORMs 424 | 425 | Object–relational mapping (ORM, O/RM, and O/R mapping tool) in computer science is a programming technique for converting data between type systems using object-oriented programming languages. This creates, in effect, a "virtual object database", hence a layer of abstraction, that can be used from within the programming language. 426 | 427 | Most common ORM library in Go is [GORM](https://gorm.io/). 428 | ` 429 | }, 430 | { 431 | label: "Web frameworks", 432 | desc: ` 433 | # Web Frameworks 434 | 435 | There are several famous web frameworks for Go. Most common ones being: 436 | 437 | * Beego 438 | * Gin 439 | * Revel 440 | * Echo 441 | 442 | ### Resources: 443 | - READ: [Comparison of Web Frameworks](https://github.com/diyan/go-web-framework-comparison) 444 | 445 | ` 446 | }, 447 | ], 448 | group3: [ 449 | { 450 | label: "Real time communication", 451 | desc: ` 452 | # Real time communication 453 | 454 | Work in progress. 455 | ` 456 | }, 457 | { 458 | label: "API Clients", 459 | desc: ` 460 | # API Clients 461 | 462 | An API client is a set of tools and protocols that operate from an application on a computer. They help you to bypass some operations when developing a web application rather than reinventing the wheel every time. Using a client API is a great way to speed up the development process. 463 | 464 | ### Resources: 465 | - READ: [API Clients](https://rapidapi.com/blog/api-glossary/client/) 466 | 467 | ` 468 | }, 469 | { 470 | label: "Tools for Microservices", 471 | desc: ` 472 | # Microservices 473 | 474 | Microservices are an architectural approach to software development that allows the creation of a distributed application from deployable services that allow communication through a well-defined API. Being a solution to monoliths. 475 | 476 | ### Resources: 477 | 478 | - READ: [Introduction to microservices](https://developer.ibm.com/learningpaths/get-started-application-modernization/intro-microservices/introduction/) 479 | - READ: [Microservice Patterns and Resources by Chris Richardson](https://microservices.io/index.html) 480 | - READ: [Microservices AntiPatterns and Pitfalls - Mark Richards](https://www.oreilly.com/content/microservices-antipatterns-and-pitfalls/) 481 | - READ: [Building Microservices, 2nd Edition - Sam Newman](https://samnewman.io/books/building_microservices_2nd_edition/) 482 | 483 | ` 484 | }, 485 | ] 486 | }, 487 | ], 488 | edges: [ 489 | {path: "M 10,30 A 20,20 0,0,1 50,30 A 20,20 0,0,1 90,30 Q 90,60 50,90 Q 10,60 10,30 z", style: "primary"}, 490 | {path: "M 10,10 L 90,90 V 10 H 50", style: "secondary"}, 491 | ], 492 | end: {label: "Now go make cool things with Golang!"} 493 | } -------------------------------------------------------------------------------- /src/stores.js: -------------------------------------------------------------------------------- 1 | import { persistStore } from "./persistStore"; 2 | 3 | // {item_id: integer} 0 = want to learn, 1 = finished 4 | export const bookmarks = persistStore('bookmarks', {}) 5 | export const roadmap_progress = persistStore('roadmap_progress', {}) -------------------------------------------------------------------------------- /src/utility.js: -------------------------------------------------------------------------------- 1 | // function for random book cover 2 | 3 | export function randomCover(itemid){ 4 | let images = [ 5 | '/book-cover.png', 6 | '/book-cover-2.png', 7 | '/book-cover-3.png', 8 | '/book-cover-4.png', 9 | '/book-cover-5.png', 10 | '/book-cover-6.png', 11 | '/book-cover-7.png', 12 | 13 | ] 14 | return images[itemid.charCodeAt(0) % images.length]; 15 | } -------------------------------------------------------------------------------- /svelte.config.js: -------------------------------------------------------------------------------- 1 | import preprocess from "svelte-preprocess"; 2 | 3 | const config = { 4 | preprocess: [ 5 | preprocess({ 6 | postcss: true, 7 | }), 8 | ], 9 | }; 10 | 11 | export default config; 12 | -------------------------------------------------------------------------------- /tailwind.config.cjs: -------------------------------------------------------------------------------- 1 | const config = { 2 | content: [ 3 | "./src/**/*.{html,js,svelte,ts}", 4 | "./index.html" 5 | ], 6 | 7 | theme: { 8 | extend: { 9 | colors: { 10 | primary: '#1e3a8a', //blue-900 11 | primary_light: '#FAFAFA', //neutral-50 12 | neutral_light: '#e5e5e5', // neutral-200 13 | neutral_dark: '#262626', // neutral-800 14 | secondary: '#6B21A8', // purple-800 15 | // primary_medium: '#60A5FA', //blue-400 16 | gradOne: '#DBEAFE', //blue-100 17 | gradTwo: '#F3E8FF', //purple-100 18 | }, 19 | }, 20 | fontFamily: { 21 | sans: ['Gentium Plus', 'sans'], 22 | serif: ['Libre Franklin', 'serif'] 23 | } 24 | }, 25 | 26 | plugins: [ 27 | require('@tailwindcss/typography'), 28 | ], 29 | }; 30 | 31 | module.exports = config; 32 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import { svelte } from '@sveltejs/vite-plugin-svelte' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [ 7 | svelte() 8 | ], 9 | }) --------------------------------------------------------------------------------