├── .github └── workflows │ └── clojure.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── doc └── intro.md ├── project.clj ├── resources ├── config.edn ├── parser.ebnf └── templates │ ├── about.edn │ ├── contact.edn │ ├── graph-page-example.edn │ ├── index.edn │ ├── mind.edn │ ├── now.edn │ ├── post-example.edn │ ├── posts.edn │ └── templates.clj ├── src └── static_roam │ ├── core.clj │ ├── database.clj │ ├── html_generation.clj │ ├── parser.clj │ ├── templating.clj │ └── utils.clj └── test └── static_roam ├── core_test.clj └── parser_test.clj /.github/workflows/clojure.yml: -------------------------------------------------------------------------------- 1 | name: Clojure CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Install dependencies 17 | run: lein deps 18 | - name: Run tests 19 | run: lein test 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | .hgignore 11 | .hg/ 12 | *.#* 13 | *.html 14 | output.json 15 | /assets 16 | output.txt 17 | sample-db.edn 18 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. This change log follows the conventions of [keepachangelog.com](http://keepachangelog.com/). 3 | 4 | ## [Unreleased] 5 | ### Changed 6 | - Add a new arity to `make-widget-async` to provide a different widget shape. 7 | 8 | ## [0.1.1] - 2020-03-19 9 | ### Changed 10 | - Documentation on how to make the widgets. 11 | 12 | ### Removed 13 | - `make-widget-sync` - we're all async, all the time. 14 | 15 | ### Fixed 16 | - Fixed widget maker to keep working when daylight savings switches over. 17 | 18 | ## 0.1.0 - 2020-03-19 19 | ### Added 20 | - Files from the new template. 21 | - Widget maker public API - `make-widget-sync`. 22 | 23 | [Unreleased]: https://github.com/your-name/roaman-life-clj/compare/0.1.1...HEAD 24 | [0.1.1]: https://github.com/your-name/roaman-life-clj/compare/0.1.0...0.1.1 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC 2 | LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM 3 | CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 4 | 5 | 1. DEFINITIONS 6 | 7 | "Contribution" means: 8 | 9 | a) in the case of the initial Contributor, the initial code and 10 | documentation distributed under this Agreement, and 11 | 12 | b) in the case of each subsequent Contributor: 13 | 14 | i) changes to the Program, and 15 | 16 | ii) additions to the Program; 17 | 18 | where such changes and/or additions to the Program originate from and are 19 | distributed by that particular Contributor. A Contribution 'originates' from 20 | a Contributor if it was added to the Program by such Contributor itself or 21 | anyone acting on such Contributor's behalf. Contributions do not include 22 | additions to the Program which: (i) are separate modules of software 23 | distributed in conjunction with the Program under their own license 24 | agreement, and (ii) are not derivative works of the Program. 25 | 26 | "Contributor" means any person or entity that distributes the Program. 27 | 28 | "Licensed Patents" mean patent claims licensable by a Contributor which are 29 | necessarily infringed by the use or sale of its Contribution alone or when 30 | combined with the Program. 31 | 32 | "Program" means the Contributions distributed in accordance with this 33 | Agreement. 34 | 35 | "Recipient" means anyone who receives the Program under this Agreement, 36 | including all Contributors. 37 | 38 | 2. GRANT OF RIGHTS 39 | 40 | a) Subject to the terms of this Agreement, each Contributor hereby grants 41 | Recipient a non-exclusive, worldwide, royalty-free copyright license to 42 | reproduce, prepare derivative works of, publicly display, publicly perform, 43 | distribute and sublicense the Contribution of such Contributor, if any, and 44 | such derivative works, in source code and object code form. 45 | 46 | b) Subject to the terms of this Agreement, each Contributor hereby grants 47 | Recipient a non-exclusive, worldwide, royalty-free patent license under 48 | Licensed Patents to make, use, sell, offer to sell, import and otherwise 49 | transfer the Contribution of such Contributor, if any, in source code and 50 | object code form. This patent license shall apply to the combination of the 51 | Contribution and the Program if, at the time the Contribution is added by the 52 | Contributor, such addition of the Contribution causes such combination to be 53 | covered by the Licensed Patents. The patent license shall not apply to any 54 | other combinations which include the Contribution. No hardware per se is 55 | licensed hereunder. 56 | 57 | c) Recipient understands that although each Contributor grants the licenses 58 | to its Contributions set forth herein, no assurances are provided by any 59 | Contributor that the Program does not infringe the patent or other 60 | intellectual property rights of any other entity. Each Contributor disclaims 61 | any liability to Recipient for claims brought by any other entity based on 62 | infringement of intellectual property rights or otherwise. As a condition to 63 | exercising the rights and licenses granted hereunder, each Recipient hereby 64 | assumes sole responsibility to secure any other intellectual property rights 65 | needed, if any. For example, if a third party patent license is required to 66 | allow Recipient to distribute the Program, it is Recipient's responsibility 67 | to acquire that license before distributing the Program. 68 | 69 | d) Each Contributor represents that to its knowledge it has sufficient 70 | copyright rights in its Contribution, if any, to grant the copyright license 71 | set forth in this Agreement. 72 | 73 | 3. REQUIREMENTS 74 | 75 | A Contributor may choose to distribute the Program in object code form under 76 | its own license agreement, provided that: 77 | 78 | a) it complies with the terms and conditions of this Agreement; and 79 | 80 | b) its license agreement: 81 | 82 | i) effectively disclaims on behalf of all Contributors all warranties and 83 | conditions, express and implied, including warranties or conditions of title 84 | and non-infringement, and implied warranties or conditions of merchantability 85 | and fitness for a particular purpose; 86 | 87 | ii) effectively excludes on behalf of all Contributors all liability for 88 | damages, including direct, indirect, special, incidental and consequential 89 | damages, such as lost profits; 90 | 91 | iii) states that any provisions which differ from this Agreement are offered 92 | by that Contributor alone and not by any other party; and 93 | 94 | iv) states that source code for the Program is available from such 95 | Contributor, and informs licensees how to obtain it in a reasonable manner on 96 | or through a medium customarily used for software exchange. 97 | 98 | When the Program is made available in source code form: 99 | 100 | a) it must be made available under this Agreement; and 101 | 102 | b) a copy of this Agreement must be included with each copy of the Program. 103 | 104 | Contributors may not remove or alter any copyright notices contained within 105 | the Program. 106 | 107 | Each Contributor must identify itself as the originator of its Contribution, 108 | if any, in a manner that reasonably allows subsequent Recipients to identify 109 | the originator of the Contribution. 110 | 111 | 4. COMMERCIAL DISTRIBUTION 112 | 113 | Commercial distributors of software may accept certain responsibilities with 114 | respect to end users, business partners and the like. While this license is 115 | intended to facilitate the commercial use of the Program, the Contributor who 116 | includes the Program in a commercial product offering should do so in a 117 | manner which does not create potential liability for other Contributors. 118 | Therefore, if a Contributor includes the Program in a commercial product 119 | offering, such Contributor ("Commercial Contributor") hereby agrees to defend 120 | and indemnify every other Contributor ("Indemnified Contributor") against any 121 | losses, damages and costs (collectively "Losses") arising from claims, 122 | lawsuits and other legal actions brought by a third party against the 123 | Indemnified Contributor to the extent caused by the acts or omissions of such 124 | Commercial Contributor in connection with its distribution of the Program in 125 | a commercial product offering. The obligations in this section do not apply 126 | to any claims or Losses relating to any actual or alleged intellectual 127 | property infringement. In order to qualify, an Indemnified Contributor must: 128 | a) promptly notify the Commercial Contributor in writing of such claim, and 129 | b) allow the Commercial Contributor to control, and cooperate with the 130 | Commercial Contributor in, the defense and any related settlement 131 | negotiations. The Indemnified Contributor may participate in any such claim 132 | at its own expense. 133 | 134 | For example, a Contributor might include the Program in a commercial product 135 | offering, Product X. That Contributor is then a Commercial Contributor. If 136 | that Commercial Contributor then makes performance claims, or offers 137 | warranties related to Product X, those performance claims and warranties are 138 | such Commercial Contributor's responsibility alone. Under this section, the 139 | Commercial Contributor would have to defend claims against the other 140 | Contributors related to those performance claims and warranties, and if a 141 | court requires any other Contributor to pay any damages as a result, the 142 | Commercial Contributor must pay those damages. 143 | 144 | 5. NO WARRANTY 145 | 146 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON 147 | AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER 148 | EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR 149 | CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A 150 | PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the 151 | appropriateness of using and distributing the Program and assumes all risks 152 | associated with its exercise of rights under this Agreement , including but 153 | not limited to the risks and costs of program errors, compliance with 154 | applicable laws, damage to or loss of data, programs or equipment, and 155 | unavailability or interruption of operations. 156 | 157 | 6. DISCLAIMER OF LIABILITY 158 | 159 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY 160 | CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, 161 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION 162 | LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 163 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 164 | ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE 165 | EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY 166 | OF SUCH DAMAGES. 167 | 168 | 7. GENERAL 169 | 170 | If any provision of this Agreement is invalid or unenforceable under 171 | applicable law, it shall not affect the validity or enforceability of the 172 | remainder of the terms of this Agreement, and without further action by the 173 | parties hereto, such provision shall be reformed to the minimum extent 174 | necessary to make such provision valid and enforceable. 175 | 176 | If Recipient institutes patent litigation against any entity (including a 177 | cross-claim or counterclaim in a lawsuit) alleging that the Program itself 178 | (excluding combinations of the Program with other software or hardware) 179 | infringes such Recipient's patent(s), then such Recipient's rights granted 180 | under Section 2(b) shall terminate as of the date such litigation is filed. 181 | 182 | All Recipient's rights under this Agreement shall terminate if it fails to 183 | comply with any of the material terms or conditions of this Agreement and 184 | does not cure such failure in a reasonable period of time after becoming 185 | aware of such noncompliance. If all Recipient's rights under this Agreement 186 | terminate, Recipient agrees to cease use and distribution of the Program as 187 | soon as reasonably practicable. However, Recipient's obligations under this 188 | Agreement and any licenses granted by Recipient relating to the Program shall 189 | continue and survive. 190 | 191 | Everyone is permitted to copy and distribute copies of this Agreement, but in 192 | order to avoid inconsistency the Agreement is copyrighted and may only be 193 | modified in the following manner. The Agreement Steward reserves the right to 194 | publish new versions (including revisions) of this Agreement from time to 195 | time. No one other than the Agreement Steward has the right to modify this 196 | Agreement. The Eclipse Foundation is the initial Agreement Steward. The 197 | Eclipse Foundation may assign the responsibility to serve as the Agreement 198 | Steward to a suitable separate entity. Each new version of the Agreement will 199 | be given a distinguishing version number. The Program (including 200 | Contributions) may always be distributed subject to the version of the 201 | Agreement under which it was received. In addition, after a new version of 202 | the Agreement is published, Contributor may elect to distribute the Program 203 | (including its Contributions) under the new version. Except as expressly 204 | stated in Sections 2(a) and 2(b) above, Recipient receives no rights or 205 | licenses to the intellectual property of any Contributor under this 206 | Agreement, whether expressly, by implication, estoppel or otherwise. All 207 | rights in the Program not expressly granted under this Agreement are 208 | reserved. 209 | 210 | This Agreement is governed by the laws of the State of New York and the 211 | intellectual property laws of the United States of America. No party to this 212 | Agreement will bring a legal action under this Agreement more than one year 213 | after the cause of action arose. Each party waives its rights to a jury trial 214 | in any resulting litigation. 215 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Static-Roam 2 | ## A static-site generator for [Roam Research](https://roamresearch.com/) 3 | [![Clojars Project](https://clojars.org/static-roam/static-roam/latest-version.svg)](https://clojars.org/static-roam) 4 | 5 | Usage: `lein run /path/to/export/zip /path/to/output/dir degree`. You must use the JSON export from Roam, not the Markdown export. 6 | 7 | ## Background 8 | 9 | With Roam, there are no limits on how you can connect notes. This is one of Roam's greatest strengths. However, it's its greatest weakness when it comes to sharing notes publicly. Since any note can be connected to any other block, things you'd be alright with being publicly known get connected with ideas that you'd like to keep private. Personally, I think there are great benefits that can be seen from sharing notes publicly that are structured in the ways Roam allows. Static-Roam is an early attempt at solving this. 10 | 11 | The basic goal is to allow a user to share notes they are fine with being seen publicly while minimizing private information exposure. It works by having the user specify **entry points**. Entry points are simply where you want Static-Roam to *start* including pages/blocks from your Roam database. You then specify a **degree** which is how far away from the entry points you are alright with Static-Roam going. The name comes from "degree of separation" as in how everyone on Earth is 6 degrees of separation from one another. The reasoning behind the degree approach is that the farther away you get from entry points (which, remember, you *explicitly* said you're alright with being public), the less likely it is that those pages contain only information you'd be alright with being public. You can also specify "all" as the degree to just include every block and page in your Roam graph. 12 | 13 | There is also the concepts of the **explicit exclude** and the **explicit include**. Explicit exclude is used for pages/blocks that are within the specified degree but you still don't want to include them. Explicit include is used if there is a page outside of the degree range but you still want to include (say you want to send someone a link to just that page). I began to implement these but never finished (and probably won't) 14 | 15 | ## How To Use 16 | 17 | So the basic flow goes like this: 18 | 19 | 1. You specify the entry points for your graph. You do this by having `#EntryPoint` as the text of the *first* child block of the page/block you want to use as an entry point. 20 | 2. Run `lein run /path/to/export/zip /path/to/output/dir degree` from within the directory of this project. `degree` is of course how far away from the entry points you want to be included in the generated static site. 21 | 22 | ## Future Development 23 | 24 | Static-Roam is used in production at [Roaman Pub](https://roaman.pub/) currently. However, I have basically put Static-Roam aside in favor of working on a revamped, much prettier, much more user friendly version of Roaman Pub that will likely be open source. So this project is basically just in maintenance mode until the new Roaman Pub is done. Once it's done, Static-Roam will likely receive no further development from me. 25 | 26 | -------------------------------------------------------------------------------- /doc/intro.md: -------------------------------------------------------------------------------- 1 | # Introduction to roaman-life-clj 2 | 3 | TODO: write [great documentation](http://jacobian.org/writing/what-to-write/) 4 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject static-roam "0.0.22-alpha" 2 | :description "A static-site generator for Roam Research" 3 | :url "https://github.com/TomLisankie/static-roam" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | :dependencies [[org.clojure/clojure "1.10.0"] 7 | [me.raynes/fs "1.4.6"] 8 | [org.clojure/data.json "1.0.0"] 9 | [hiccup "1.0.5"] 10 | [stasis "2.5.0"] 11 | [instaparse "1.4.10"] 12 | [datascript "1.0.3"]] 13 | :resource-paths ["resources"] 14 | :main static-roam.core) 15 | -------------------------------------------------------------------------------- /resources/config.edn: -------------------------------------------------------------------------------- 1 | { 2 | :site-title "Thomas Lisankie" 3 | :twitter-username "TomLisankie" 4 | :github-username "TomLisankie" 5 | :entry-point-tags ["EntryPoint" "Post"] 6 | :include-tagged ["Include"] 7 | :include-refs [] 8 | :exclude-tagged ["Exclude"] 9 | :exclude-refs ["high"] 10 | ;; Keep in mind, all of these are only operating on blocks that we've already marked as included: 11 | :template-info {"about" 12 | { 13 | :folder "/about" 14 | :index true 15 | :tagged-as "About" 16 | } 17 | "contact" 18 | { 19 | :folder "/contact" 20 | :index true 21 | :tagged-as "Contact" 22 | } 23 | "graph-page-example" 24 | { 25 | :folder "/nodes" 26 | } 27 | "index" 28 | { 29 | :folder "/" 30 | :index true 31 | } 32 | "mind" 33 | { 34 | :folder "/mind" 35 | :index true 36 | } 37 | "now" 38 | { 39 | :folder "/now" 40 | :index true 41 | :tagged-as "Now" 42 | } 43 | "post-example" 44 | { 45 | :folder "/nodes" 46 | :tagged-as "Post" 47 | } 48 | "posts" 49 | { 50 | :folder "/posts" 51 | :index true 52 | :tagged-as "Post" 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /resources/parser.ebnf: -------------------------------------------------------------------------------- 1 | (* Parser for block content *) 2 | block = (syntax-in-block / any-char)* 3 | 4 | = (todo | done | image | alias | block-embed | hashtag | page-link | block-ref | metadata-tag | code-line | youtube | bold | italic | strikethrough | highlight | bare-url) (* add query later*) 5 | alias = #"\[(.*?)\]\(.*?\)" 6 | image = #"!\[(.*?)\]\(.*?\)" 7 | page-link = #"\[\[.*?\]\]" 8 | hashtag = #"\#..*?(?=\s|$)" | #"\#\[\[.*?\]\]" 9 | block-ref = #"\(\(([\w\s\d]+)\)\)" 10 | metadata-tag = #"^.+?::" 11 | code-line = #"\`.*?\`" 12 | (* query = #"\{\{query: .*?:.*?\}\}" | #"\{\{[[query]]: .*?:.*?\}\}" *) 13 | youtube = #"\{\{youtube: .*?\}\}" | #"\{\{\[\[youtube\]\]: .*?:.*?\}\}" 14 | bold = #"\*\*.*?\*\*" 15 | italic = #"\_\_.*?\_\_" 16 | strikethrough = #"\~\~.*?\~\~" 17 | highlight = #"\^\^.*?\^\^" 18 | todo = #"\{\{\[\[TODO\]\]\}\}" 19 | done = #"\{\{\[\[DONE\]\]\}\}" 20 | block-embed = #"\{\{embed: .*?\}\}" | #"\{\{\[\[embed\]\]: .*?\}\}" 21 | bare-url = #"(https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|www\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9]+\.[^\s]{2,}|www\.[a-zA-Z0-9]+\.[^\s]{2,})" 22 | 23 | any-chars = #'\w+|\W+' 24 | = #'\w|\W' -------------------------------------------------------------------------------- /resources/templates/about.edn: -------------------------------------------------------------------------------- 1 | [:html {:lang "en-US"} 2 | [:head 3 | [:title "Thomas Lisankie"] 4 | [:meta {:name "viewport" :content "width=device-width, initial-scale=1, maximum-scale=1"}] 5 | [:link {:rel "shortcut icon" :href "../assets/favicon.ico" :type "image/x-icon"}] 6 | [:meta {:charset "utf-8"}] 7 | [:link {:rel "stylesheet" :type "text/css" :href "../assets/style.css"}] 8 | [:link {:rel "preconnect" :href "https://fonts.gstatic.com"}] 9 | [:link {:href "https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,400;0,600;0,700;1,400;1,600;1,700&display=swap" :rel "stylesheet"}]] 10 | [:body 11 | [:header 12 | [:h1 {:id "site-title-link"} 13 | [:a {:href "../"} "Thomas Lisankie"]] 14 | [:nav {:id "nav-bar"} 15 | [:input {:type "checkbox" :name "nav-trigger" :id "nav-trigger"}] 16 | [:label {:for "nav-trigger"} 17 | [:svg {:viewBox "0 0 100 80" :width "40" :height "40"} 18 | [:rect {:width "100" :height "20"}] 19 | [:rect {:y "30" :width "100" :height "20"}] 20 | [:rect {:y "60" :width "100" :height "20"}]]] 21 | [:div {:id "nav-links"} 22 | [:a {:class "nav-bar-link" :href "../about"} "About"] 23 | [:span {:class "link-separator"} " | "] 24 | [:a {:class "nav-bar-link" :href "../now"} "Now"] 25 | [:span {:class "link-separator"} " | "] 26 | [:a {:class "nav-bar-link" :href "../posts"} "Posts"] 27 | [:span {:class "link-separator"} " | "] 28 | [:a {:class "nav-bar-link" :href "../mind"} "My Mind"] 29 | [:span {:class "link-separator"} " | "] 30 | [:a {:class "nav-bar-link" :href "../contact"} "Contact"] 31 | [:span {:class "link-separator"} " | "] 32 | [:a {:class "social-icon" :href "https://twitter.com/TomLisankie"} 33 | [:img {:src "../assets/twitter.svg" :width "20" :height "20"}]] 34 | " " 35 | [:a {:class "social-icon" :href "https://github.com/TomLisankie"} 36 | [:img {:src "../assets/github.svg" :width "20" :height "20"}]]]]] 37 | [:main {:id "content"} 38 | [:section {:id "about-content"} 39 | [:div 40 | [:h1 "About Me"]] 41 | [:ul {:style "list-style-type:none; padding-left:0;" :sr-content {:page "About"}} 42 | [:li {:style " padding-bottom:1em;"} 43 | "fsfsadffasdfsd"] 44 | [:li {:style " padding-bottom:1em;"} 45 | "fsfsadffasdfsd"]] 46 | [:p "The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog."] 47 | [:p "The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. "]]] 48 | [:footer 49 | [:center 50 | [:div {:class "rc-webring"} 51 | [:a {:href "https://webring.recurse.com"} 52 | [:img {:src "https://webring.recurse.com/icon.png" :width "20" :height "20"} 53 | "RC Webring"]]] 54 | [:div 55 | "Want to become a better programmer? " 56 | [:a {:href "https://www.recurse.com/scout/click?t=1a34fc98998afcb8a3ea183e9aa2b564"} "Join the Recurse Center!"]]]]]] 57 | -------------------------------------------------------------------------------- /resources/templates/contact.edn: -------------------------------------------------------------------------------- 1 | [:html {:lang "en-US"} 2 | [:head 3 | [:title "Thomas Lisankie"] 4 | [:meta {:name "viewport" :content "width=device-width, initial-scale=1, maximum-scale=1"}] 5 | [:link {:rel "shortcut icon" :href "../assets/favicon.ico" :type "image/x-icon"}] 6 | [:meta {:charset "utf-8"}] 7 | [:link {:rel "stylesheet" :type "text/css" :href "../assets/style.css"}] 8 | [:link {:rel "preconnect" :href "https://fonts.gstatic.com"}] 9 | [:link {:href "https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,400;0,600;0,700;1,400;1,600;1,700&display=swap" :rel "stylesheet"}]] 10 | [:body 11 | [:header 12 | [:h1 {:id "site-title-link"} 13 | [:a {:href "../"} "Thomas Lisankie"]] 14 | [:nav {:id "nav-bar"} 15 | [:input {:type "checkbox" :name "nav-trigger" :id "nav-trigger"}] 16 | [:label {:for "nav-trigger"} 17 | [:svg {:viewBox "0 0 100 80" :width "40" :height "40"} 18 | [:rect {:width "100" :height "20"}] 19 | [:rect {:y "30" :width "100" :height "20"}] 20 | [:rect {:y "60" :width "100" :height "20"}]]] 21 | [:div {:id "nav-links"} 22 | [:a {:class "nav-bar-link" :href "../about"} "About"] 23 | [:span {:class "link-separator"} " | "] 24 | [:a {:class "nav-bar-link" :href "../now"} "Now"] 25 | [:span {:class "link-separator"} " | "] 26 | [:a {:class "nav-bar-link" :href "../posts"} "Posts"] 27 | [:span {:class "link-separator"} " | "] 28 | [:a {:class "nav-bar-link" :href "../mind"} "My Mind"] 29 | [:span {:class "link-separator"} " | "] 30 | [:a {:class "nav-bar-link" :href "../contact"} "Contact"] 31 | [:span {:class "link-separator"} " | "] 32 | [:a {:class "social-icon" :href "https://twitter.com/TomLisankie"} 33 | [:img {:src "../assets/twitter.svg" :width "20" :height "20"}]] 34 | " " 35 | [:a {:class "social-icon" :href "https://github.com/TomLisankie"} 36 | [:img {:src "../assets/github.svg" :width "20" :height "20"}]]]]] 37 | [:main {:id "content"} 38 | [:section {:id "contact-content"} 39 | [:div 40 | [:h1 "Contact Me"]] 41 | [:p "Here are a couple of ways to contact me:"] 42 | [:ul 43 | [:li 44 | "Email: hello (you know the symbol) tomlisankie.com"] 45 | [:li 46 | [:a {:href "https://twitter.com/TomLisankie"} 47 | "Twitter"]]]]] 48 | [:footer 49 | [:center 50 | [:div {:class "rc-webring"} 51 | [:a {:href "https://webring.recurse.com"} 52 | [:img {:src "https://webring.recurse.com/icon.png" :width "20" :height "20"} 53 | "RC Webring"]]] 54 | [:div 55 | "Want to become a better programmer? " 56 | [:a {:href "https://www.recurse.com/scout/click?t=1a34fc98998afcb8a3ea183e9aa2b564"} "Join the Recurse Center!"]]]]]] 57 | -------------------------------------------------------------------------------- /resources/templates/graph-page-example.edn: -------------------------------------------------------------------------------- 1 | [:html {:lang "en-US"} 2 | [:head 3 | [:title "Thomas Lisankie"] 4 | [:meta {:name "viewport" :content "width=device-width, initial-scale=1, maximum-scale=1"}] 5 | [:link {:rel "shortcut icon" :href "../assets/favicon.ico" :type "image/x-icon"}] 6 | [:meta {:charset "utf-8"}] 7 | [:link {:rel "stylesheet" :type "text/css" :href "../assets/style.css"}] 8 | [:link {:rel "preconnect" :href "https://fonts.gstatic.com"}] 9 | [:link {:href "https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,400;0,600;0,700;1,400;1,600;1,700&display=swap" :rel "stylesheet"}]] 10 | [:body 11 | [:header 12 | [:h1 {:id "site-title-link"} 13 | [:a {:href "../"} "Thomas Lisankie"]] 14 | [:nav {:id "nav-bar"} 15 | [:input {:type "checkbox" :name "nav-trigger" :id "nav-trigger"}] 16 | [:label {:for "nav-trigger"} 17 | [:svg {:viewBox "0 0 100 80" :width "40" :height "40"} 18 | [:rect {:width "100" :height "20"}] 19 | [:rect {:y "30" :width "100" :height "20"}] 20 | [:rect {:y "60" :width "100" :height "20"}]]] 21 | [:div {:id "nav-links"} 22 | [:a {:class "nav-bar-link" :href "../about"} "About"] 23 | [:span {:class "link-separator"} " | "] 24 | [:a {:class "nav-bar-link" :href "../now"} "Now"] 25 | [:span {:class "link-separator"} " | "] 26 | [:a {:class "nav-bar-link" :href "../posts"} "Posts"] 27 | [:span {:class "link-separator"} " | "] 28 | [:a {:class "nav-bar-link" :href "../mind"} "My Mind"] 29 | [:span {:class "link-separator"} " | "] 30 | [:a {:class "nav-bar-link" :href "../contact"} "Contact"] 31 | [:span {:class "link-separator"} " | "] 32 | [:a {:class "social-icon" :href "https://twitter.com/TomLisankie"} 33 | [:img {:src "../assets/twitter.svg" :width "20" :height "20"}]] 34 | " " 35 | [:a {:class "social-icon" :href "https://github.com/TomLisankie"} 36 | [:img {:src "../assets/github.svg" :width "20" :height "20"}]]]]] 37 | [:main {:id "graph-page-content"} 38 | [:section {:id "nav-bar-page"} 39 | [:div 40 | [:h1 "A Page From My Mind"]] 41 | [:ul {:class "graph-page-ul"} 42 | [:li {:class "graph-page-li"} 43 | "Some example text. Some example text. Some example text. Some example text. Some example text. Some example text. Some example text. Some example text. Some example text. Some example text. Some example text. Some example text.ffffffffff "] 44 | [:li {:class "graph-page-li"} 45 | "More example text!"] 46 | [:ul {:class "graph-page-ul"} 47 | [:li {:class "graph-page-li"} 48 | "This is a child of the previous block"] 49 | [:ul {:class "graph-page-ul"} 50 | [:li {:class "graph-page-li"} 51 | "This is a child of that child"]] 52 | [:li {:class "graph-page-li"} 53 | "This is another child"]] 54 | [:li {:class "graph-page-li"} 55 | "Another top-level block"] 56 | [:li {:class "graph-page-li"} 57 | "The final block here"]]] 58 | [:div {:class "linked-references"} 59 | [:h3 {:style "padding-left: 1em;"} "Linked References"] 60 | [:ul 61 | [:li {:class "graph-page-li"} 62 | "Hello"] 63 | [:li {:class "graph-page-li"} 64 | "Yes"] 65 | [:li {:class "graph-page-li"} 66 | "this is"] 67 | [:li {:class "graph-page-li"} 68 | "dog"]]]] 69 | [:footer 70 | [:center 71 | [:div {:class "rc-webring"} 72 | [:a {:href "https://webring.recurse.com"} 73 | [:img {:src "https://webring.recurse.com/icon.png" :width "20" :height "20"} 74 | "RC Webring"]]] 75 | [:script {:async true :defer true :src "https://www.recurse-scout.com/loader.js?t=1a34fc98998afcb8a3ea183e9aa2b564"}]]]]] 76 | -------------------------------------------------------------------------------- /resources/templates/index.edn: -------------------------------------------------------------------------------- 1 | [:html {:lang "en-US"} 2 | [:head 3 | [:title "Thomas Lisankie"] 4 | [:meta {:name "viewport" :content "width=device-width, initial-scale=1, maximum-scale=1"}] 5 | [:link {:rel "shortcut icon" :href "./assets/favicon.ico" :type "image/x-icon"}] 6 | [:meta {:charset "utf-8"}] 7 | [:link {:rel "stylesheet" :type "text/css" :href "./assets/style.css"}] 8 | [:link {:rel "preconnect" :href "https://fonts.gstatic.com"}] 9 | [:link {:href "https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,400;0,600;0,700;1,400;1,600;1,700&display=swap" :rel "stylesheet"}]] 10 | [:body 11 | [:header 12 | [:h1 {:id "site-title-link"} 13 | [:a {:href "./"} "Thomas Lisankie"]] 14 | [:nav {:id "nav-bar"} 15 | [:input {:type "checkbox" :name "nav-trigger" :id "nav-trigger"}] 16 | [:label {:for "nav-trigger"} 17 | [:svg {:viewBox "0 0 100 80" :width "40" :height "40"} 18 | [:rect {:width "100" :height "20"}] 19 | [:rect {:y "30" :width "100" :height "20"}] 20 | [:rect {:y "60" :width "100" :height "20"}]]] 21 | [:div {:id "nav-links"} 22 | [:a {:class "nav-bar-link" :href "./about"} "About"] 23 | [:span {:class "link-separator"} " | "] 24 | [:a {:class "nav-bar-link" :href "./now"} "Now"] 25 | [:span {:class "link-separator"} " | "] 26 | [:a {:class "nav-bar-link" :href "./posts"} "Posts"] 27 | [:span {:class "link-separator"} " | "] 28 | [:a {:class "nav-bar-link" :href "./mind"} "My Mind"] 29 | [:span {:class "link-separator"} " | "] 30 | [:a {:class "nav-bar-link" :href "./contact"} "Contact"] 31 | [:span {:class "link-separator"} " | "] 32 | [:a {:class "social-icon" :href "https://twitter.com/TomLisankie"} 33 | [:img {:src "./assets/twitter.svg" :width "20" :height "20"}]] 34 | " " 35 | [:a {:class "social-icon" :href "https://github.com/TomLisankie"} 36 | [:img {:src "./assets/github.svg" :width "20" :height "20"}]]]]] 37 | [:main {:id "homepage"} 38 | [:section {:id "about-me"} 39 | [:div 40 | [:h2 "Me In 10 Seconds"] 41 | [:p "I'm a software developer. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog."]] 42 | [:div 43 | [:h2 "Me In 10 Minutes"] 44 | [:p 45 | "Check out the " 46 | [:a {:href "./about"} 47 | "about"] 48 | " page"]]] 49 | [:section {:id "posts-home"} 50 | [:h2 "Posts"] 51 | [:ul 52 | [:li [:a {:href "./nodes/post-example.html"} 53 | "What I'm Doing in Austin"]] 54 | [:li "A New Start"] 55 | [:li "What is an IP Address Really?"]]] 56 | [:section {:id "entry-points-home"} 57 | [:h2 "Entry Points into my Mind"] 58 | [:ul 59 | [:li [:a {:href "./nodes/graph-page-example.html"} 60 | "What's been on my mind"]] 61 | [:li "The Network is the metaphor of this age"]]]] 62 | [:footer 63 | [:center 64 | [:div {:class "rc-webring"} 65 | [:a {:href "https://webring.recurse.com"} 66 | [:img {:src "https://webring.recurse.com/icon.png" :width "20" :height "20"} 67 | "RC Webring"]]] 68 | [:div 69 | "Want to become a better programmer? " 70 | [:a {:href "https://www.recurse.com/scout/click?t=1a34fc98998afcb8a3ea183e9aa2b564"} "Join the Recurse Center!"]]]]]] 71 | -------------------------------------------------------------------------------- /resources/templates/mind.edn: -------------------------------------------------------------------------------- 1 | [:html {:lang "en-US"} 2 | [:head 3 | [:title "Thomas Lisankie"] 4 | [:meta {:name "viewport" :content "width=device-width, initial-scale=1, maximum-scale=1"}] 5 | [:link {:rel "shortcut icon" :href "../assets/favicon.ico" :type "image/x-icon"}] 6 | [:meta {:charset "utf-8"}] 7 | [:link {:rel "stylesheet" :type "text/css" :href "../assets/style.css"}] 8 | [:link {:rel "preconnect" :href "https://fonts.gstatic.com"}] 9 | [:link {:href "https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,400;0,600;0,700;1,400;1,600;1,700&display=swap" :rel "stylesheet"}]] 10 | [:body 11 | [:header 12 | [:h1 {:id "site-title-link"} 13 | [:a {:href "../"} "Thomas Lisankie"]] 14 | [:nav {:id "nav-bar"} 15 | [:input {:type "checkbox" :name "nav-trigger" :id "nav-trigger"}] 16 | [:label {:for "nav-trigger"} 17 | [:svg {:viewBox "0 0 100 80" :width "40" :height "40"} 18 | [:rect {:width "100" :height "20"}] 19 | [:rect {:y "30" :width "100" :height "20"}] 20 | [:rect {:y "60" :width "100" :height "20"}]]] 21 | [:div {:id "nav-links"} 22 | [:a {:class "nav-bar-link" :href "../about"} "About"] 23 | [:span {:class "link-separator"} " | "] 24 | [:a {:class "nav-bar-link" :href "../now"} "Now"] 25 | [:span {:class "link-separator"} " | "] 26 | [:a {:class "nav-bar-link" :href "../posts"} "Posts"] 27 | [:span {:class "link-separator"} " | "] 28 | [:a {:class "nav-bar-link" :href "../mind"} "My Mind"] 29 | [:span {:class "link-separator"} " | "] 30 | [:a {:class "nav-bar-link" :href "../contact"} "Contact"] 31 | [:span {:class "link-separator"} " | "] 32 | [:a {:class "social-icon" :href "https://twitter.com/TomLisankie"} 33 | [:img {:src "../assets/twitter.svg" :width "20" :height "20"}]] 34 | " " 35 | [:a {:class "social-icon" :href "https://github.com/TomLisankie"} 36 | [:img {:src "../assets/github.svg" :width "20" :height "20"}]]]]] 37 | [:main {:id "content"} 38 | [:section {:id "entry-point-list"} 39 | [:div 40 | [:h1 "Entry Points into my mind"]] 41 | [:ul {:id "entry-points"} 42 | [:li 43 | [:a {:href "../nodes/graph-page-example.html"} 44 | "What's been on my mind"]] 45 | [:li 46 | [:a {:href "../nodes/graph-page-example.html"} 47 | "The Network is the metaphor of this age"]] 48 | [:li 49 | [:a {:href "../nodes/graph-page-example.html"} 50 | "The Pseudonymity Sacrament"]] 51 | [:li 52 | [:a {:href "../nodes/graph-page-example.html"} 53 | "What's been on my mind"]] 54 | [:li 55 | [:a {:href "../nodes/graph-page-example.html"} 56 | "The Network is the metaphor of this age"]] 57 | [:li 58 | [:a {:href "../nodes/graph-page-example.html"} 59 | "The Pseudonymity Sacrament"]]]]] 60 | [:footer 61 | [:center 62 | [:div {:class "rc-webring"} 63 | [:a {:href "https://webring.recurse.com"} 64 | [:img {:src "https://webring.recurse.com/icon.png" :width "20" :height "20"} 65 | "RC Webring"]]] 66 | [:div 67 | "Want to become a better programmer? " 68 | [:a {:href "https://www.recurse.com/scout/click?t=1a34fc98998afcb8a3ea183e9aa2b564"} "Join the Recurse Center!"]]]]]] 69 | -------------------------------------------------------------------------------- /resources/templates/now.edn: -------------------------------------------------------------------------------- 1 | [:html {:lang "en-US"} 2 | [:head 3 | [:title "Thomas Lisankie"] 4 | [:meta {:name "viewport" :content "width=device-width, initial-scale=1, maximum-scale=1"}] 5 | [:link {:rel "shortcut icon" :href "../assets/favicon.ico" :type "image/x-icon"}] 6 | [:meta {:charset "utf-8"}] 7 | [:link {:rel "stylesheet" :type "text/css" :href "../assets/style.css"}] 8 | [:link {:rel "preconnect" :href "https://fonts.gstatic.com"}] 9 | [:link {:href "https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,400;0,600;0,700;1,400;1,600;1,700&display=swap" :rel "stylesheet"}]] 10 | [:body 11 | [:header 12 | [:h1 {:id "site-title-link"} 13 | [:a {:href "../"} "Thomas Lisankie"]] 14 | [:nav {:id "nav-bar"} 15 | [:input {:type "checkbox" :name "nav-trigger" :id "nav-trigger"}] 16 | [:label {:for "nav-trigger"} 17 | [:svg {:viewBox "0 0 100 80" :width "40" :height "40"} 18 | [:rect {:width "100" :height "20"}] 19 | [:rect {:y "30" :width "100" :height "20"}] 20 | [:rect {:y "60" :width "100" :height "20"}]]] 21 | [:div {:id "nav-links"} 22 | [:a {:class "nav-bar-link" :href "../about"} "About"] 23 | [:span {:class "link-separator"} " | "] 24 | [:a {:class "nav-bar-link" :href "../now"} "Now"] 25 | [:span {:class "link-separator"} " | "] 26 | [:a {:class "nav-bar-link" :href "../posts"} "Posts"] 27 | [:span {:class "link-separator"} " | "] 28 | [:a {:class "nav-bar-link" :href "../mind"} "My Mind"] 29 | [:span {:class "link-separator"} " | "] 30 | [:a {:class "nav-bar-link" :href "../contact"} "Contact"] 31 | [:span {:class "link-separator"} " | "] 32 | [:a {:class "social-icon" :href "https://twitter.com/TomLisankie"} 33 | [:img {:src "../assets/twitter.svg" :width "20" :height "20"}]] 34 | " " 35 | [:a {:class "social-icon" :href "https://github.com/TomLisankie"} 36 | [:img {:src "../assets/github.svg" :width "20" :height "20"}]]]]] 37 | [:main {:id "content"} 38 | [:section {:id "now-content"} 39 | [:div 40 | [:h1 "What I'm Up to These Days"]] 41 | [:p "The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. "]]] 42 | [:footer 43 | [:center 44 | [:div {:class "rc-webring"} 45 | [:a {:href "https://webring.recurse.com"} 46 | [:img {:src "https://webring.recurse.com/icon.png" :width "20" :height "20"} 47 | "RC Webring"]]] 48 | [:div 49 | "Want to become a better programmer? " 50 | [:a {:href "https://www.recurse.com/scout/click?t=1a34fc98998afcb8a3ea183e9aa2b564"} "Join the Recurse Center!"]]]]]] 51 | -------------------------------------------------------------------------------- /resources/templates/post-example.edn: -------------------------------------------------------------------------------- 1 | [:html {:lang "en-US"} 2 | [:head 3 | [:title "Thomas Lisankie"] 4 | [:meta {:name "viewport" :content "width=device-width, initial-scale=1, maximum-scale=1"}] 5 | [:link {:rel "shortcut icon" :href "../assets/favicon.ico" :type "image/x-icon"}] 6 | [:meta {:charset "utf-8"}] 7 | [:link {:rel "stylesheet" :type "text/css" :href "../assets/style.css"}] 8 | [:link {:rel "preconnect" :href "https://fonts.gstatic.com"}] 9 | [:link {:href "https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,400;0,600;0,700;1,400;1,600;1,700&display=swap" :rel "stylesheet"}]] 10 | [:body 11 | [:header 12 | [:h1 {:id "site-title-link"} 13 | [:a {:href "../"} "Thomas Lisankie"]] 14 | [:nav {:id "nav-bar"} 15 | [:input {:type "checkbox" :name "nav-trigger" :id "nav-trigger"}] 16 | [:label {:for "nav-trigger"} 17 | [:svg {:viewBox "0 0 100 80" :width "40" :height "40"} 18 | [:rect {:width "100" :height "20"}] 19 | [:rect {:y "30" :width "100" :height "20"}] 20 | [:rect {:y "60" :width "100" :height "20"}]]] 21 | [:div {:id "nav-links"} 22 | [:a {:class "nav-bar-link" :href "../about"} "About"] 23 | [:span {:class "link-separator"} " | "] 24 | [:a {:class "nav-bar-link" :href "../now"} "Now"] 25 | [:span {:class "link-separator"} " | "] 26 | [:a {:class "nav-bar-link" :href "../posts"} "Posts"] 27 | [:span {:class "link-separator"} " | "] 28 | [:a {:class "nav-bar-link" :href "../mind"} "My Mind"] 29 | [:span {:class "link-separator"} " | "] 30 | [:a {:class "nav-bar-link" :href "../contact"} "Contact"] 31 | [:span {:class "link-separator"} " | "] 32 | [:a {:class "social-icon" :href "https://twitter.com/TomLisankie"} 33 | [:img {:src "../assets/twitter.svg" :width "20" :height "20"}]] 34 | " " 35 | [:a {:class "social-icon" :href "https://github.com/TomLisankie"} 36 | [:img {:src "../assets/github.svg" :width "20" :height "20"}]]]]] 37 | [:main {:id "content"} 38 | [:article {:id "post-content"} 39 | [:div 40 | [:h1 "What is an IP Address Really?"]] 41 | [:p "The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. "] 42 | [:p "The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. "]]] 43 | [:footer 44 | [:center 45 | [:div {:class "rc-webring"} 46 | [:a {:href "https://webring.recurse.com"} 47 | [:img {:src "https://webring.recurse.com/icon.png" :width "20" :height "20"} 48 | "RC Webring"]]] 49 | [:div 50 | "Want to become a better programmer? " 51 | [:a {:href "https://www.recurse.com/scout/click?t=1a34fc98998afcb8a3ea183e9aa2b564"} "Join the Recurse Center!"]]]]]] 52 | -------------------------------------------------------------------------------- /resources/templates/posts.edn: -------------------------------------------------------------------------------- 1 | [:html {:lang "en-US"} 2 | [:head 3 | [:title "Thomas Lisankie"] 4 | [:meta {:name "viewport" :content "width=device-width, initial-scale=1, maximum-scale=1"}] 5 | [:link {:rel "shortcut icon" :href "../assets/favicon.ico" :type "image/x-icon"}] 6 | [:meta {:charset "utf-8"}] 7 | [:link {:rel "stylesheet" :type "text/css" :href "../assets/style.css"}] 8 | [:link {:rel "preconnect" :href "https://fonts.gstatic.com"}] 9 | [:link {:href "https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,400;0,600;0,700;1,400;1,600;1,700&display=swap" :rel "stylesheet"}]] 10 | [:body 11 | [:header 12 | [:h1 {:id "site-title-link"} 13 | [:a {:href "../"} "Thomas Lisankie"]] 14 | [:nav {:id "nav-bar"} 15 | [:input {:type "checkbox" :name "nav-trigger" :id "nav-trigger"}] 16 | [:label {:for "nav-trigger"} 17 | [:svg {:viewBox "0 0 100 80" :width "40" :height "40"} 18 | [:rect {:width "100" :height "20"}] 19 | [:rect {:y "30" :width "100" :height "20"}] 20 | [:rect {:y "60" :width "100" :height "20"}]]] 21 | [:div {:id "nav-links"} 22 | [:a {:class "nav-bar-link" :href "../about"} "About"] 23 | [:span {:class "link-separator"} " | "] 24 | [:a {:class "nav-bar-link" :href "../now"} "Now"] 25 | [:span {:class "link-separator"} " | "] 26 | [:a {:class "nav-bar-link" :href "../posts"} "Posts"] 27 | [:span {:class "link-separator"} " | "] 28 | [:a {:class "nav-bar-link" :href "../mind"} "My Mind"] 29 | [:span {:class "link-separator"} " | "] 30 | [:a {:class "nav-bar-link" :href "../contact"} "Contact"] 31 | [:span {:class "link-separator"} " | "] 32 | [:a {:class "social-icon" :href "https://twitter.com/TomLisankie"} 33 | [:img {:src "../assets/twitter.svg" :width "20" :height "20"}]] 34 | " " 35 | [:a {:class "social-icon" :href "https://github.com/TomLisankie"} 36 | [:img {:src "../assets/github.svg" :width "20" :height "20"}]]]]] 37 | [:main {:id "content"} 38 | [:section {:id "post-list"} 39 | [:div 40 | [:h1 "Posts / Essays / Prose / etc."]] 41 | [:ul {:id "posts"} 42 | [:li 43 | [:a {:href "../nodes/post-example.html"} 44 | "What I'm Doing in Austin"]] 45 | [:li 46 | [:a {:href "../nodes/post-example.html"} 47 | "A New Start"]] 48 | [:li 49 | [:a {:href "../nodes/post-example.html"} 50 | "What is an IP Address Really?"]] 51 | [:li 52 | [:a {:href "../nodes/post-example.html"} 53 | "What I'm Doing in Austin"]] 54 | [:li 55 | [:a {:href "../nodes/post-example.html"} 56 | "A New Start"]] 57 | [:li 58 | [:a {:href "../nodes/post-example.html"} 59 | "What is an IP Address Really?"]]]]] 60 | [:footer 61 | [:center 62 | [:div {:class "rc-webring"} 63 | [:a {:href "https://webring.recurse.com"} 64 | [:img {:src "https://webring.recurse.com/icon.png" :width "20" :height "20"} 65 | "RC Webring"]]] 66 | [:div 67 | "Want to become a better programmer? " 68 | [:a {:href "https://www.recurse.com/scout/click?t=1a34fc98998afcb8a3ea183e9aa2b564"} "Join the Recurse Center!"]]]]]] 69 | -------------------------------------------------------------------------------- /resources/templates/templates.clj: -------------------------------------------------------------------------------- 1 | (ns templates) 2 | 3 | (def general-site-info 4 | { 5 | "Site Title" "Thomas Lisankie" 6 | "Nav Bar Pages" ["About", "Now", "Posts", "My Mind", "Contact"] 7 | "Twitter Profile" "TomLisankie" 8 | "GitHub Profile" "TomLisankie" 9 | }) 10 | 11 | (defn head 12 | [site-title] 13 | [:head 14 | [:title site-title] 15 | [:meta {:name "viewport" :content "width=device-width, initial-scale=1, maximum-scale=1"}] 16 | [:link {:rel "shortcut icon" :href "./assets/favicon.ico" :type "image/x-icon"}] 17 | [:meta {:charset "utf-8"}] 18 | [:link {:rel "stylesheet" :type "text/css" :href "./assets/style.css"}] 19 | [:link {:rel "preconnect" :href "https://fonts.gstatic.com"}] 20 | [:link {:href "https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,400;0,600;0,700;1,400;1,600;1,700&display=swap" :rel "stylesheet"}]]) 21 | 22 | (defn header 23 | [site-title nav-bar-pages twitter-username github-username] 24 | [:header 25 | [:h1 {:id "site-title-link"} 26 | [:a {:href "./"} site-title]] 27 | [:nav {:id "nav-bar"} 28 | [:input {:type "checkbox" :name "nav-trigger" :id "nav-trigger"}] 29 | [:label {:for "nav-trigger"} 30 | [:svg {:viewBox "0 0 100 80" :width "40" :height "40"} 31 | [:rect {:width "100" :height "20"}] 32 | [:rect {:y "30" :width "100" :height "20"}] 33 | [:rect {:y "60" :width "100" :height "20"}]]] 34 | [:div {:id "nav-links"} 35 | [:a {:class "nav-bar-link" :href "./about"} "About"] 36 | [:span {:class "link-separator"} " | "] 37 | [:a {:class "nav-bar-link" :href "./now"} "Now"] 38 | [:span {:class "link-separator"} " | "] 39 | [:a {:class "nav-bar-link" :href "./posts"} "Posts"] 40 | [:span {:class "link-separator"} " | "] 41 | [:a {:class "nav-bar-link" :href "./mind"} "My Mind"] 42 | [:span {:class "link-separator"} " | "] 43 | [:a {:class "nav-bar-link" :href "./contact"} "Contact"] 44 | [:span {:class "link-separator"} " | "] 45 | [:a {:class "social-icon" :href (str "https://twitter.com/" twitter-username)} 46 | [:img {:src "./assets/twitter.svg" :width "20" :height "20"}]] 47 | " " 48 | [:a {:class "social-icon" :href (str "https://github.com/" github-username)} 49 | [:img {:src "./assets/github.svg" :width "20" :height "20"}]]]]]) 50 | 51 | (defn footer 52 | [] 53 | [:footer 54 | [:center 55 | [:div {:class "rc-webring"} 56 | [:a {:href "https://webring.recurse.com"} 57 | [:img {:src "https://webring.recurse.com/icon.png" :width "20" :height "20"} 58 | "RC Webring"]]] 59 | [:div 60 | "Want to become a better programmer? " 61 | [:a {:href "https://www.recurse.com/scout/click?t=1a34fc98998afcb8a3ea183e9aa2b564"} "Join the Recurse Center!"]]]]) 62 | 63 | (defn index 64 | [head header footer site-title me-in-10-secs me-in-10-mins posts entry-points] 65 | [:html {:lang "en-US"} 66 | (head site-title) 67 | [:body 68 | (header site-title) 69 | [:main {:id "homepage"} 70 | [:section {:id "about-me"} 71 | [:div 72 | [:h2 "Me In 10 Seconds"] 73 | [:p "I'm a software developer. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog."]] 74 | [:div 75 | [:h2 "Me In 10 Minutes"] 76 | [:p 77 | "Check out the " 78 | [:a {:href "./about"} 79 | "about"] 80 | " page"]]] 81 | [:section {:id "posts-home"} 82 | [:h2 "Posts"] 83 | [:ul 84 | [:li [:a {:href "./nodes/post-example.html"} 85 | "What I'm Doing in Austin"]] 86 | [:li "A New Start"] 87 | [:li "What is an IP Address Really?"]]] 88 | [:section {:id "entry-points-home"} 89 | [:h2 "Entry Points into my Mind"] 90 | [:ul 91 | [:li [:a {:href "./nodes/graph-page-example.html"} 92 | "What's been on my mind"]] 93 | [:li "The Network is the metaphor of this age"]]]] 94 | (footer)]]) 95 | 96 | (defn about 97 | [head header footer about-content] 98 | [:html {:lang "en-US"} 99 | (head) 100 | [:body 101 | (header) 102 | [:main {:id "content"} 103 | [:section {:id "about-content"} 104 | [:div 105 | [:h1 "About Me"]] 106 | [:ul {:style "list-style-type:none; padding-left:0;" :sr-content {:page "About"}} 107 | [:li {:style " padding-bottom:1em;"} 108 | "fsfsadffasdfsd"] 109 | [:li {:style " padding-bottom:1em;"} 110 | "fsfsadffasdfsd"]] 111 | [:p "The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog."] 112 | [:p "The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. "]]] 113 | (footer)]]) 114 | 115 | (defn contact 116 | [head header footer contact-content] 117 | [:html {:lang "en-US"} 118 | (head) 119 | [:body 120 | (header) 121 | [:main {:id "content"} 122 | [:section {:id "contact-content"} 123 | [:div 124 | [:h1 "Contact Me"]] 125 | [:p "Here are a couple of ways to contact me:"] 126 | [:ul 127 | [:li 128 | "Email: hello (you know the symbol) tomlisankie.com"] 129 | [:li 130 | [:a {:href "https://twitter.com/TomLisankie"} 131 | "Twitter"]]]]] 132 | (footer)]]) 133 | 134 | (defn graph-page 135 | [head header footer page-title page-children linked-references] 136 | [:html {:lang "en-US"} 137 | (head) 138 | [:body 139 | (header) 140 | [:main {:id "graph-page-content"} 141 | [:section {:id "nav-bar-page"} 142 | [:div 143 | [:h1 page-title]] 144 | [:ul {:class "graph-page-ul"} 145 | [:li {:class "graph-page-li"} 146 | "Some example text. Some example text. Some example text. Some example text. Some example text. Some example text. Some example text. Some example text. Some example text. Some example text. Some example text. Some example text.ffffffffff "] 147 | [:li {:class "graph-page-li"} 148 | "More example text!"] 149 | [:ul {:class "graph-page-ul"} 150 | [:li {:class "graph-page-li"} 151 | "This is a child of the previous block"] 152 | [:ul {:class "graph-page-ul"} 153 | [:li {:class "graph-page-li"} 154 | "This is a child of that child"]] 155 | [:li {:class "graph-page-li"} 156 | "This is another child"]] 157 | [:li {:class "graph-page-li"} 158 | "Another top-level block"] 159 | [:li {:class "graph-page-li"} 160 | "The final block here"]]] 161 | [:div {:class "linked-references"} 162 | [:h3 {:style "padding-left: 1em;"} "Linked References"] 163 | [:ul 164 | [:li {:class "graph-page-li"} 165 | "Hello"] 166 | [:li {:class "graph-page-li"} 167 | "Yes"] 168 | [:li {:class "graph-page-li"} 169 | "this is"] 170 | [:li {:class "graph-page-li"} 171 | "dog"]]]] 172 | (footer)]]) 173 | 174 | (defn now 175 | [head header footer now-content] 176 | [:html {:lang "en-US"} 177 | (head) 178 | [:body 179 | (header) 180 | [:main {:id "content"} 181 | [:section {:id "now-content"} 182 | [:div 183 | [:h1 "What I'm Up to These Days"]] 184 | [:p "The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. "]]] 185 | (footer)]]) 186 | 187 | (defn mind 188 | [head header footer entry-points] 189 | [:html {:lang "en-US"} 190 | (head) 191 | [:body 192 | (header) 193 | [:main {:id "content"} 194 | [:section {:id "entry-point-list"} 195 | [:div 196 | [:h1 "Entry Points into my mind"]] 197 | [:ul {:id "entry-points"} 198 | [:li 199 | [:a {:href "../nodes/graph-page-example.html"} 200 | "What's been on my mind"]] 201 | [:li 202 | [:a {:href "../nodes/graph-page-example.html"} 203 | "The Network is the metaphor of this age"]] 204 | [:li 205 | [:a {:href "../nodes/graph-page-example.html"} 206 | "The Pseudonymity Sacrament"]] 207 | [:li 208 | [:a {:href "../nodes/graph-page-example.html"} 209 | "What's been on my mind"]] 210 | [:li 211 | [:a {:href "../nodes/graph-page-example.html"} 212 | "The Network is the metaphor of this age"]] 213 | [:li 214 | [:a {:href "../nodes/graph-page-example.html"} 215 | "The Pseudonymity Sacrament"]]]]] 216 | (footer)]]) 217 | 218 | (defn post 219 | [head header footer post-title post-content] 220 | [:html {:lang "en-US"} 221 | (head) 222 | [:body 223 | (header) 224 | [:main {:id "content"} 225 | [:article {:id "post-content"} 226 | [:div 227 | [:h1 post-title]] 228 | [:p "The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. "] 229 | [:p "The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. "]]] 230 | (footer)]]) 231 | 232 | (defn posts 233 | [head header footer posts-list] 234 | [:html {:lang "en-US"} 235 | (head) 236 | [:body 237 | (header) 238 | [:main {:id "content"} 239 | [:section {:id "post-list"} 240 | [:div 241 | [:h1 "Posts / Essays / Prose / etc."]] 242 | [:ul {:id "posts"} 243 | [:li 244 | [:a {:href "../nodes/post-example.html"} 245 | "What I'm Doing in Austin"]] 246 | [:li 247 | [:a {:href "../nodes/post-example.html"} 248 | "A New Start"]] 249 | [:li 250 | [:a {:href "../nodes/post-example.html"} 251 | "What is an IP Address Really?"]] 252 | [:li 253 | [:a {:href "../nodes/post-example.html"} 254 | "What I'm Doing in Austin"]] 255 | [:li 256 | [:a {:href "../nodes/post-example.html"} 257 | "A New Start"]] 258 | [:li 259 | [:a {:href "../nodes/post-example.html"} 260 | "What is an IP Address Really?"]]]]] 261 | (footer)]]) 262 | -------------------------------------------------------------------------------- /src/static_roam/core.clj: -------------------------------------------------------------------------------- 1 | (ns static-roam.core 2 | (:require [static-roam.utils :as utils] 3 | [static-roam.database :as database] 4 | [static-roam.parser :as parser] 5 | [static-roam.templating :as templating] 6 | [static-roam.html-generation :as html-gen] 7 | [clojure.pprint :as pprint] 8 | [clojure.java.io :as io] 9 | [clojure.edn :as edn-utils])) 10 | 11 | (defn generate-static-roam! 12 | [path-to-zip output-dir degree] 13 | (println "---Loading export---") 14 | (let [roam-db-conn (time (utils/create-roam-edn-db-from-zip path-to-zip)) 15 | config (edn-utils/read-string (slurp (io/resource "config.edn")))] 16 | (println "---Determining content to include---") 17 | (time (database/determine-which-content-to-include roam-db-conn degree config)) 18 | (println "---Parsing---") 19 | (time (parser/parse-entities-in-db-to-hiccup roam-db-conn)) 20 | (println "---Making templates---") 21 | (let [templates (time 22 | (templating/generate-templates 23 | roam-db-conn 24 | (:template-info 25 | config)))] 26 | (println "---Generating templates---") 27 | (time (html-gen/generate-site templates output-dir))))) 28 | 29 | (defn -main 30 | [path-to-zip output-dir degree] 31 | (generate-static-roam! path-to-zip output-dir (if (nil? (re-find #"\d" degree)) 32 | degree 33 | (Integer. degree)))) 34 | -------------------------------------------------------------------------------- /src/static_roam/database.clj: -------------------------------------------------------------------------------- 1 | (ns static-roam.database 2 | (:require [static-roam.parser :as parser] 3 | [static-roam.utils :as utils] 4 | [clojure.string :as str-utils] 5 | [datascript.core :as ds] 6 | [clojure.pprint :as pprint])) 7 | 8 | (defn- get-block-id 9 | [block-json] 10 | (if (:title block-json) 11 | (:title block-json) 12 | (:uid block-json))) 13 | 14 | (defn get-properties-for-block-id 15 | [block-id block-map] 16 | (get block-map block-id)) 17 | 18 | (defn- get-block-properties 19 | [block-json] 20 | { 21 | :children (map :uid (:children block-json)) 22 | :content (:string block-json (:title block-json)) 23 | :heading (:heading block-json -1) 24 | :text-align (:text-align block-json "") 25 | :entry-point (parser/entry-point? block-json) 26 | :page (if (:title block-json) 27 | true 28 | false) 29 | }) 30 | 31 | (defn- generate-block-linking-transaction 32 | [referer-eid reference-id] 33 | {:block/id reference-id 34 | :block/linked-by referer-eid}) 35 | 36 | (defn- create-id-properties-pair 37 | [block-json] 38 | [(get-block-id block-json) (get-block-properties block-json)]) 39 | 40 | (defn- create-id-properties-pairs 41 | [roam-json] 42 | (map 43 | vec 44 | (partition 45 | 2 46 | (flatten 47 | (for [block-json roam-json] 48 | (conj (create-id-properties-pairs (:children block-json)) (create-id-properties-pair block-json))))))) 49 | 50 | (defn- create-block-map-no-links 51 | "Populate database with relevant properties of pages and blocks" 52 | [roam-json] 53 | ;; what I need to happen here is have it so if the block has children, it creates pairs for those as well 54 | ;; forget what I already implemented, I optimized too early. 55 | (into (hash-map) (create-id-properties-pairs roam-json))) 56 | 57 | (defn- clean-content-entities 58 | [string] 59 | (let [content-entities-found 60 | (utils/find-content-entities-in-string string) 61 | hashtags-found 62 | (utils/find-hashtags-in-string string) 63 | metadata-found 64 | (utils/find-metadata-in-string string) 65 | extra-paren-removed 66 | (utils/remove-heading-parens content-entities-found) 67 | cleaned-content-entities 68 | (map utils/remove-double-delimiters extra-paren-removed) 69 | cleaned-hashtags 70 | (map utils/remove-leading-char hashtags-found) 71 | cleaned-metadata 72 | (map utils/remove-double-colon metadata-found) 73 | all-cleaned-entities 74 | (concat cleaned-content-entities cleaned-hashtags cleaned-metadata)] 75 | all-cleaned-entities)) 76 | 77 | ;; sometimes people put references to other page titles inside of the title of another page. So pairs of brackets inside of other brackets when they reference them. This breaks things currently, so I'm removing all those instances here TODO: Fix so this is unnecessary 78 | (defn- filter-broken-references 79 | [pair] 80 | [(first pair) 81 | (filter 82 | #(not (str-utils/includes? % "[")) 83 | (second pair))]) 84 | 85 | (defn- generate-block-id-reference-pair 86 | [pair] 87 | [(first pair) 88 | (clean-content-entities (second pair))]) 89 | 90 | (defn- generate-block-linking-transactions-for-entity-reference-pair 91 | [entity-id-reference-pair] 92 | (let [referer-eid (first entity-id-reference-pair) 93 | reference-ids (second entity-id-reference-pair)] 94 | (map #(generate-block-linking-transaction referer-eid %) reference-ids))) 95 | 96 | (defn- generate-transactions-for-linking-blocks 97 | [entity-id-reference-pairs] 98 | (map generate-block-linking-transactions-for-entity-reference-pair entity-id-reference-pairs)) 99 | 100 | (defn- get-block-id-content-pair 101 | [pair] 102 | [(first pair) (:content (second pair))]) 103 | 104 | (defn- get-block-id-content-pairs 105 | [block-map] 106 | (map get-block-id-content-pair block-map)) 107 | 108 | (defn- add-linked-by-property 109 | [pair] 110 | [(first pair) (assoc (second pair) :linked-by '())]) 111 | 112 | (defn- get-block-id-reference-pairs 113 | [block-map] 114 | (let [block-map-with-linked-by (map add-linked-by-property block-map) 115 | block-id-content-pairs (get-block-id-content-pairs block-map) 116 | block-id-reference-pairs (map generate-block-id-reference-pair block-id-content-pairs)] 117 | block-id-reference-pairs)) 118 | 119 | (defn- get-referenced-referer-pair 120 | [referenced referer] 121 | [referenced referer]) 122 | 123 | (defn- get-referenced-referer-pairs 124 | [referer-referenced-pairs] 125 | (let [referer (first referer-referenced-pairs) 126 | referenced (second referer-referenced-pairs)] 127 | (map #(get-referenced-referer-pair % referer) referenced))) 128 | 129 | (defn- generate-links 130 | [block-id-reference-pairs block-map-no-links] 131 | (let [individual-referenced-referer-pairs (partition 132 | 2 133 | (flatten 134 | (map get-referenced-referer-pairs block-id-reference-pairs))) 135 | grouped-by-referenced (group-by first individual-referenced-referer-pairs) 136 | reference-names-stripped (map (fn [kv] [(first kv) (map second (second kv))]) grouped-by-referenced) 137 | ] 138 | (into (hash-map) reference-names-stripped))) 139 | 140 | (defn- attach-backlinks-to-block 141 | [id-backlinks-map block] 142 | (let [block-id (first block) 143 | block-props (second block)] 144 | [block-id (assoc block-props :linked-by (set (get id-backlinks-map block-id '())))])) 145 | 146 | (defn- attach-backlinks-to-block-map 147 | [links block-map] 148 | (map #(attach-backlinks-to-block links %) block-map)) 149 | 150 | (defn- attach-links-to-block 151 | [id-reference-map block-kv] 152 | (let [block-id (first block-kv) 153 | block-props (second block-kv)] 154 | [block-id (assoc block-props :refers-to (set (get id-reference-map block-id '())))])) 155 | 156 | (defn- attach-links-to-block-map 157 | [block-id-reference-pairs block-map-no-links] 158 | (let [id-reference-map (into (hash-map) block-id-reference-pairs)] 159 | (into (hash-map) (map #(attach-links-to-block id-reference-map %) block-map-no-links)))) 160 | 161 | (defn- generate-links-and-backlinks 162 | [block-map-no-links] 163 | (let [block-id-reference-pairs (get-block-id-reference-pairs block-map-no-links) 164 | broken-references-removed (map filter-broken-references block-id-reference-pairs) 165 | block-map-with-links (attach-links-to-block-map broken-references-removed block-map-no-links) 166 | backlinks (generate-links broken-references-removed block-map-no-links) 167 | block-map-with-backlinks (attach-backlinks-to-block-map backlinks block-map-with-links)] 168 | block-map-with-backlinks)) 169 | 170 | (defn get-linked-references 171 | [block-id block-map] 172 | (let [block-props (get block-map block-id "BLOCK DOESN'T EXIST") 173 | linked-refs (:linked-by block-props)] 174 | linked-refs)) 175 | 176 | (defn- mark-block-as-included 177 | [block-kv] 178 | [(first block-kv) (assoc (second block-kv) :included true)]) 179 | 180 | (defn- entry-point? 181 | [block-kv] 182 | (true? (:entry-point (second block-kv)))) 183 | 184 | (defn- get-entry-point-ids 185 | [block-map] 186 | (into #{} (map first (filter entry-point? block-map)))) 187 | 188 | (defn- get-children-of-block 189 | [block-id block-map] 190 | (let [block-props (get block-map block-id)] 191 | (:children block-props))) 192 | 193 | (defn- get-all-children-of-blocks 194 | [block-ids block-map] 195 | (reduce into #{} (map #(get-children-of-block % block-map) block-ids))) 196 | 197 | (defn- get-references-for-block 198 | [block-id block-map] 199 | (let [block-props (get block-map block-id)] 200 | (:refers-to block-props))) 201 | 202 | (defn- get-references-for-blocks 203 | [block-ids block-map] 204 | (let [children (get-all-children-of-blocks block-ids block-map) 205 | references (reduce into #{} (map #(get-references-for-block % block-map) children))] 206 | (reduce into #{} [children references]))) 207 | 208 | (defn- get-children-recursively 209 | [entity-id block-map] 210 | (let [children (set (get-children-of-block entity-id block-map))] 211 | (if (empty? (flatten (map #(get-children-of-block % block-map) children))) 212 | children 213 | (reduce into children (flatten (map #(get-children-recursively % block-map) children)))))) 214 | 215 | (defn- get-all-children-recursively 216 | [examining block-map] 217 | (map #(get-children-recursively % block-map) examining)) 218 | 219 | (defn- aggregate-children 220 | [children-sets] 221 | (reduce into #{} children-sets)) 222 | 223 | (defn- get-child-content-references 224 | [children block-map] 225 | (map #(get-references-for-block % block-map) children)) 226 | 227 | (defn- aggregate-references 228 | [reference-sets] 229 | (reduce into #{} reference-sets)) 230 | 231 | (defn- generate-included-entities 232 | [included-entities children references] 233 | (reduce into included-entities [children references])) 234 | 235 | (defn- get-content-entity-ids-to-include 236 | [degree block-map] 237 | (let [entry-point-ids (into (get-entry-point-ids block-map) #{"SR Metadata"})] 238 | (loop [entities-to-examine entry-point-ids 239 | children-for-each-entity (get-all-children-recursively entities-to-examine block-map) 240 | all-children-of-examined (aggregate-children children-for-each-entity) 241 | references-for-children (get-child-content-references all-children-of-examined block-map) 242 | all-references-of-children (aggregate-references references-for-children) 243 | included-entities (generate-included-entities entities-to-examine all-children-of-examined all-references-of-children) 244 | current-degree 0 245 | max-degree degree] 246 | (if (>= current-degree max-degree) 247 | included-entities 248 | (let [entities-to-examine all-references-of-children 249 | children-for-each-entity (get-all-children-recursively entities-to-examine block-map) 250 | all-children-of-examined (aggregate-children children-for-each-entity) 251 | references-for-children (get-child-content-references all-children-of-examined block-map) 252 | all-references-of-children (aggregate-references references-for-children) 253 | included-entities (generate-included-entities included-entities all-children-of-examined all-references-of-children) 254 | current-degree (inc current-degree) 255 | max-degree max-degree] 256 | (recur entities-to-examine 257 | children-for-each-entity 258 | all-children-of-examined 259 | references-for-children 260 | all-references-of-children 261 | included-entities 262 | current-degree 263 | max-degree)))))) 264 | 265 | (defn- block-id-included? 266 | [block-id included-block-ids] 267 | (contains? included-block-ids block-id)) 268 | 269 | (defn- mark-block-if-included 270 | [included-block-ids block-kv] 271 | (let [block-id (first block-kv) 272 | block-props (second block-kv)] 273 | (if (block-id-included? block-id included-block-ids) 274 | [block-id (assoc block-props :included true)] 275 | [block-id (assoc block-props :included false)]))) 276 | 277 | (defn- mark-blocks-to-include-as-included 278 | [included-block-ids block-map] 279 | (map #(mark-block-if-included included-block-ids %) block-map)) 280 | 281 | (defn- mark-content-entities-for-inclusion 282 | [degree block-map] 283 | (if (and (int? degree) (>= degree 0)) 284 | (into (hash-map) (mark-blocks-to-include-as-included 285 | (get-content-entity-ids-to-include degree block-map) 286 | block-map)) 287 | (into (hash-map) (map mark-block-as-included block-map)))) 288 | 289 | (defn- give-header 290 | [the-hiccup block-props] 291 | [(keyword (str "h" (:heading block-props))) the-hiccup]) 292 | 293 | (defn- generate-hiccup-if-block-is-included 294 | [block-kv block-map] 295 | (let [block-id (first block-kv) 296 | block-props (second block-kv)] 297 | [block-id 298 | (if (true? (:included block-props)) 299 | (assoc block-props 300 | :hiccup 301 | (let [heading (:heading block-props) 302 | the-hiccup (parser/block-content->hiccup (:content block-props) block-map)] 303 | (if (> (:heading block-props) 0) 304 | (give-header the-hiccup block-props) 305 | the-hiccup))) 306 | block-props)])) 307 | 308 | (defn generate-hiccup-for-included-blocks 309 | [block-map] 310 | (into (hash-map) (filter #(not= nil (:content (second %))) (map #(generate-hiccup-if-block-is-included % block-map) block-map)))) 311 | 312 | (defn replicate-roam-db 313 | [roam-json] 314 | (let [block-map-no-links (create-block-map-no-links roam-json) 315 | block-map-linked-by-and-refers-to (generate-links-and-backlinks block-map-no-links)] 316 | (into (hash-map) block-map-linked-by-and-refers-to))) 317 | 318 | (defn- lkjsafas 319 | [pair block-map] 320 | (let [block-id (first pair) 321 | block-props (second pair) 322 | block-content (:content block-props) 323 | block-embed-found (re-find #"\{\{embed: .*?\}\}|\{\{\[\[embed\]\]: .*?\}\}" block-content)] 324 | (if block-embed-found 325 | (let [block-ref-uncleaned (re-find #"\(\(.*?\)\)|\[\[.*?\]\]" 326 | (second (str-utils/split block-embed-found #":"))) 327 | referenced-block-id (utils/remove-double-delimiters block-ref-uncleaned) 328 | referenced-block-props (get block-map referenced-block-id)] 329 | [block-id 330 | (assoc block-props :children (into (:children block-props) (reverse (:children referenced-block-props))) :content (:content referenced-block-props))]) 331 | pair))) 332 | 333 | (defn- add-children-of-block-embeds 334 | [block-map] 335 | (into 336 | (hash-map) 337 | (map 338 | #(lkjsafas % block-map) 339 | block-map))) 340 | 341 | (defn setup-static-roam-block-map 342 | [roam-json degree] 343 | (let [replicated-roam-block-map (replicate-roam-db roam-json) 344 | blocks-tagged-for-inclusion (mark-content-entities-for-inclusion degree replicated-roam-block-map) 345 | children-of-embeds-added (add-children-of-block-embeds blocks-tagged-for-inclusion) 346 | hiccup-for-included-blocks (generate-hiccup-for-included-blocks children-of-embeds-added)] 347 | hiccup-for-included-blocks)) 348 | 349 | (defn- include-children-of-included-pages 350 | [roam-db]) 351 | 352 | (defn- include-explicitly-included-blocks 353 | [roam-db include-tags] 354 | (let [sr-info-eid (first (first (ds/q '[:find ?eid 355 | :where 356 | [?eid :node/title "Static-Roam Info"]] 357 | @roam-db))) 358 | explicit-include-eids (map #(first (first %)) 359 | (map #(ds/q '[:find ?eid 360 | :in $ ?tag 361 | :where 362 | [?eid :node/title ?tag]] 363 | @roam-db %) 364 | include-tags)) 365 | eids-of-explicit-includes (reduce into [] 366 | (map first 367 | (map #(ds/q '[:find ?parent-eid 368 | :in $ ?explicit-include-tag-eid 369 | :where 370 | [?eid :block/refs sr-info-eid] 371 | [?eid :block/refs ?explicit-include-tag-eid] 372 | [?eid :block/parents ?parent-eid]] 373 | @roam-db %) 374 | explicit-include-eids))) 375 | transactions (vec (map (fn [eid] [:db/add eid :static-roam/included true]) eids-of-explicit-includes)) 376 | ;; TODO include children as well 377 | ] 378 | (ds/transact! roam-db transactions))) 379 | 380 | (defn- exclude-explicitly-excluded-blocks 381 | [roam-db exclude-tags] 382 | (let [sr-info-eid (first (first (ds/q '[:find ?eid 383 | :where 384 | [?eid :node/title "Static-Roam Info"]] 385 | @roam-db))) 386 | explicit-exclude-eid (map #(first (first %)) 387 | (map #(ds/q '[:find ?eid 388 | :in $ ?tag 389 | :where 390 | [?eid :node/title ?tag]] 391 | @roam-db %) 392 | exclude-tags)) 393 | eids-of-explicit-excludes (reduce into [] 394 | (map first 395 | (map #(ds/q '[:find ?parent-eid 396 | :in $ ?explicit-exclude-tag-eid 397 | :where 398 | [?eid :block/refs sr-info-eid] 399 | [?eid :block/refs ?explicit-exclude-tag-eid] 400 | [?eid :block/parents ?parent-eid]] 401 | @roam-db %) 402 | explicit-exclude-eid))) 403 | transactions (vec (map (fn [eid] [:db/add eid :static-roam/included false]) eids-of-explicit-excludes)) 404 | ;; TODO exclude children as well 405 | ] 406 | (ds/transact! roam-db transactions))) 407 | 408 | (defn- mark-all-entities-as-excluded 409 | [roam-db] 410 | (let [eids (map first (ds/q '[:find ?eid :where [?eid]] @roam-db)) 411 | transactions (vec (map (fn [eid] [:db/add eid :static-roam/included false]) eids))] 412 | (ds/transact! roam-db transactions)) ;; might want to make this more nuanced so only entities with :block/string or :node/title get included 413 | ) 414 | 415 | (defn- get-parent-eids 416 | [roam-db sr-info-eid entity-tag-eid] 417 | (let [query-result (ds/q '[:find ?parent-eid 418 | :in $ ?entity-tag-eid ?sr-info-eid 419 | :where 420 | [?eid :block/refs ?sr-info-eid] 421 | [?eid :block/refs ?entity-tag-eid] 422 | [?parent-eid :block/children ?eid]] 423 | @roam-db entity-tag-eid sr-info-eid)] 424 | query-result)) 425 | 426 | (defn- get-all-queries 427 | [roam-db sr-info-eid entity-tag-eids] 428 | (let [nils-removed (filter #(not (nil? %)) entity-tag-eids)] 429 | (if (empty? nils-removed) 430 | [] 431 | (let [query-results (map #(get-parent-eids roam-db sr-info-eid %) 432 | entity-tag-eids)] 433 | query-results)))) 434 | 435 | (defn- get-eids-of-entities-with-tags 436 | [roam-db tags] 437 | (println tags) 438 | (let [sr-info-eid (first (first (ds/q '[:find ?eid 439 | :where 440 | [?eid :node/title "Static-Roam Info"]] 441 | @roam-db))) 442 | entity-tag-eids (map #(first (first %)) 443 | (map #(ds/q '[:find ?eid 444 | :in $ ?tag 445 | :where 446 | [?eid :node/title ?tag]] 447 | @roam-db %) 448 | tags)) 449 | eids-of-entities-with-tags (map first 450 | (reduce into [] 451 | (get-all-queries roam-db sr-info-eid entity-tag-eids)))] 452 | eids-of-entities-with-tags)) 453 | 454 | (defn- get-descendant-eids 455 | [roam-db eid] 456 | ;; This is a very important function and will be used in further steps 457 | (let [eid (if (vector? eid) 458 | (first eid) 459 | eid) 460 | children (map first 461 | (ds/q '[:find ?children-eids 462 | :in $ ?parent-eid 463 | :where 464 | [?parent-eid :block/children ?children-eids]] 465 | @roam-db eid))] 466 | (reduce into children (map #(get-descendant-eids roam-db %) children)))) 467 | 468 | (defn- get-descendant-eids-for-eids 469 | [roam-db eids] 470 | (reduce into #{} (map #(get-descendant-eids roam-db %) eids))) 471 | 472 | (defn- get-refs-for-entity 473 | [roam-db eid] 474 | (map first 475 | (ds/q '[:find ?ref-eids 476 | :in $ ?eid 477 | :where 478 | [?eid :block/refs ?ref-eids]] 479 | @roam-db eid))) 480 | 481 | (defn- get-refs-of-descendants 482 | [roam-db descendant-eids] 483 | (reduce into #{} (map #(get-refs-for-entity roam-db %) descendant-eids))) 484 | 485 | (defn- get-entities-linked-by-descendants 486 | [roam-db eid] 487 | (let [descendant-eids (get-descendant-eids roam-db eid) 488 | refs (get-refs-of-descendants roam-db descendant-eids)] 489 | refs)) 490 | 491 | (defn- get-eids-linked-by-entity 492 | [roam-db eid] 493 | (let [eids-linked (get-entities-linked-by-descendants roam-db eid)] 494 | eids-linked)) 495 | 496 | (defn- get-entities-linked-by 497 | [roam-db eids] 498 | (let [all-eids-linked (reduce into #{} (map #(get-eids-linked-by-entity roam-db %) eids))] 499 | all-eids-linked)) 500 | 501 | (defn- mark-refs-as-included 502 | [roam-db degree entry-point-eids] 503 | (let [eids entry-point-eids 504 | linked-eids (get-entities-linked-by roam-db eids) 505 | transactions (vec (map (fn [eid] [:db/add eid :static-roam/included true]) linked-eids))] 506 | (ds/transact! roam-db transactions) 507 | (when (> degree 0) 508 | (mark-refs-as-included roam-db (dec degree) linked-eids)))) 509 | 510 | (defn- mark-entry-points-and-refs-as-included 511 | [roam-db degree entry-point-tags other-page-tags] 512 | (let [entry-point-eids (get-eids-of-entities-with-tags roam-db (into entry-point-tags (filter (complement nil?) other-page-tags))) 513 | transactions (vec (map (fn [eid] [:db/add eid :static-roam/included true]) entry-point-eids))] 514 | (ds/transact! roam-db transactions) ;; mark entry points as included 515 | (mark-refs-as-included roam-db degree entry-point-eids) 516 | (include-children-of-included-pages roam-db))) 517 | 518 | (defn- get-children-recursively 519 | [roam-db eid] 520 | (let [children-eids (map #(:db/id %) (:block/children (ds/entity (ds/db roam-db) eid)))] 521 | (if (not= 0 (count children-eids)) 522 | (reduce into children-eids (map #(get-children-recursively roam-db %) children-eids)) 523 | nil))) 524 | 525 | (defn- mark-included-entity-children-as-included 526 | [roam-db] 527 | (let [entities-marked-for-inclusion (map first 528 | (ds/q '[:find ?included-eids 529 | :where 530 | [?included-eids :static-roam/included true]] 531 | @roam-db)) 532 | entity-children-seqs (map #(get-children-recursively roam-db %) entities-marked-for-inclusion) 533 | entity-children-eids (filter (complement nil?) (flatten entity-children-seqs)) 534 | transactions (vec (map (fn [eid] [:db/add eid :static-roam/included true]) entity-children-eids))] 535 | (ds/transact! roam-db transactions))) 536 | 537 | (defn- mark-entry-point-and-ref-pages-as-included 538 | [roam-db degree entry-point-tags other-page-tags] 539 | (mark-entry-points-and-refs-as-included roam-db degree entry-point-tags other-page-tags) 540 | (mark-included-entity-children-as-included roam-db)) 541 | 542 | (defn- mark-all-entities-as-included 543 | [roam-db] 544 | (let [eids (map first (ds/q '[:find ?eid :where [?eid]] @roam-db)) 545 | transactions (vec (map (fn [eid] [:db/add eid :static-roam/included true]) eids))] 546 | (ds/transact! roam-db transactions))) 547 | 548 | (defn- include-tagged 549 | [roam-db tagged] 550 | (let [explicitly-included-tag-eids (get-eids-of-entities-with-tags roam-db tagged) 551 | children-of-entities-eids (get-descendant-eids-for-eids roam-db explicitly-included-tag-eids) 552 | parent-transactions (vec (map (fn [eid] [:db/add eid :static-roam/included true]) explicitly-included-tag-eids)) 553 | children-transactions (vec (map (fn [eid] [:db/add eid :static-roam/included true]) children-of-entities-eids))] 554 | (ds/transact! roam-db parent-transactions) 555 | (ds/transact! roam-db children-transactions))) 556 | 557 | (defn- get-eid-of-node-with-title 558 | [roam-db title] 559 | (first 560 | (first 561 | (ds/q '[:find ?eid 562 | :in $ ?title 563 | :where 564 | [?eid :node/title ?title]] 565 | @roam-db title)))) 566 | 567 | (defn- get-eids-of-nodes-with-titles 568 | [roam-db titles] 569 | (map #(get-eid-of-node-with-title roam-db %) titles)) 570 | 571 | (defn- get-eids-of-entities-with-refs-to 572 | [roam-db refs] 573 | (let [ref-eids (get-eids-of-nodes-with-titles roam-db refs) 574 | eids-linking-to-ref (map first 575 | (map #(ds/q '[:find ?eid 576 | :in $ ?ref-eid 577 | :where 578 | [?eid :block/refs ?ref-eid]] 579 | @roam-db %) 580 | ref-eids))] 581 | eids-linking-to-ref)) 582 | 583 | (defn- include-refs 584 | [roam-db refs] 585 | (let [explicitly-included-ref-eids (get-eids-of-entities-with-refs-to roam-db refs) 586 | children-of-entities-eids (get-descendant-eids-for-eids roam-db explicitly-included-ref-eids) 587 | parent-transactions (vec (map (fn [eid] [:db/add eid :static-roam/included true]) explicitly-included-ref-eids)) 588 | children-transactions (vec (map (fn [eid] [:db/add eid :static-roam/included true]) children-of-entities-eids))] 589 | (ds/transact! roam-db parent-transactions) 590 | (ds/transact! roam-db children-transactions))) 591 | 592 | (defn- include-explicitly-included 593 | [roam-db tagged refs] 594 | (include-tagged roam-db tagged) 595 | (include-refs roam-db refs)) 596 | 597 | (defn- exclude-tagged 598 | [roam-db tagged] 599 | (let [explicitly-excluded-tag-eids (get-eids-of-entities-with-tags roam-db tagged) 600 | children-of-entities-eids (get-descendant-eids-for-eids roam-db explicitly-excluded-tag-eids) 601 | parent-transactions (vec (map (fn [eid] [:db/add eid :static-roam/included false]) explicitly-excluded-tag-eids)) 602 | children-transactions (vec (map (fn [eid] [:db/add eid :static-roam/included false]) children-of-entities-eids))] 603 | (ds/transact! roam-db parent-transactions) 604 | (ds/transact! roam-db children-transactions))) 605 | 606 | (defn- exclude-refs 607 | [roam-db refs] 608 | (let [explicitly-excluded-ref-eids (flatten (get-eids-of-entities-with-refs-to roam-db refs)) 609 | children-of-entities-eids (flatten (get-descendant-eids-for-eids roam-db explicitly-excluded-ref-eids)) 610 | parent-transactions (vec (map (fn [eid] [:db/add eid :static-roam/included false]) explicitly-excluded-ref-eids)) 611 | children-transactions (vec (map (fn [eid] [:db/add eid :static-roam/included false]) children-of-entities-eids))] 612 | (ds/transact! roam-db parent-transactions) 613 | (ds/transact! roam-db children-transactions))) 614 | 615 | (defn- exclude-explicitly-excluded 616 | [roam-db tagged refs] 617 | (exclude-tagged roam-db tagged) 618 | (exclude-refs roam-db refs)) 619 | 620 | (defn- retract-all-excluded-entities 621 | [roam-db] 622 | (let [excluded-eids (map first(ds/q '[:find ?eid 623 | :where 624 | [?eid :static-roam/included false]] 625 | @roam-db)) 626 | retraction-transactions (vec (map (fn [eid] [:db.fn/retractEntity eid]) excluded-eids))] 627 | (ds/transact! roam-db retraction-transactions))) 628 | 629 | (defn determine-which-content-to-include 630 | [roam-db degree config] 631 | (mark-all-entities-as-excluded roam-db) 632 | (if (int? degree) 633 | (mark-entry-point-and-ref-pages-as-included roam-db degree (:entry-point-tags config) (map :tagged-as (:template-info config))) 634 | (mark-all-entities-as-included roam-db)) 635 | (include-explicitly-included roam-db (into (into #{} (:include-tagged config)) (filter (complement nil?) (map :tagged-as (vals (:template-info config))))) (:include-refs config)) 636 | (exclude-explicitly-excluded roam-db (:exclude-tagged config) (:exclude-refs config)) 637 | (retract-all-excluded-entities roam-db)) 638 | -------------------------------------------------------------------------------- /src/static_roam/html_generation.clj: -------------------------------------------------------------------------------- 1 | (ns static-roam.html-generation 2 | (:require [clojure.string :as str-utils] 3 | [static-roam.utils :as utils] 4 | [hiccup.core :as hiccup] 5 | [static-roam.templating :as templating] 6 | [stasis.core :as stasis] 7 | [clojure.pprint :as pprint] 8 | [clojure.java.io :as io] 9 | [clojure.edn :as edn-utils])) 10 | 11 | (defn- get-path-html-pair 12 | [path-template-pair] 13 | (let [path (first path-template-pair) 14 | html (hiccup/html (second path-template-pair))] 15 | [path html])) 16 | 17 | (defn- get-path-html-map 18 | [path-template-map] 19 | (into {} (map get-path-html-pair path-template-map))) 20 | 21 | (defn- clean-up-nodes-directory! 22 | [output-dir] 23 | ;; This is necessary to cover the case where a block that was previously included is now excluded. Without this, the block URL will still be valid 24 | (let [nodes-dir-str (str output-dir "/nodes") 25 | nodes-dir (io/file nodes-dir-str)] 26 | (doseq [file (.listFiles nodes-dir)] 27 | (io/delete-file file)))) 28 | 29 | (defn generate-site 30 | [filled-out-templates output-dir] 31 | (clean-up-nodes-directory! output-dir) 32 | (let [path-html-map (get-path-html-map filled-out-templates)] 33 | (stasis/export-pages path-html-map output-dir))) 34 | -------------------------------------------------------------------------------- /src/static_roam/parser.clj: -------------------------------------------------------------------------------- 1 | (ns static-roam.parser 2 | (:require [instaparse.core :as insta :refer [defparser]] 3 | [clojure.string :as str-utils] 4 | [static-roam.utils :as utils] 5 | [datascript.core :as ds] 6 | [clojure.java.io :as io])) 7 | 8 | ;; Modified from Athens: https://github.com/athensresearch/athens/blob/master/src/cljc/athens/parser.cljc 9 | 10 | (declare block-parser) 11 | 12 | ;; Instaparse docs: https://github.com/Engelberg/instaparse#readme 13 | 14 | (defn- combine-adjacent-strings 15 | "In a sequence of strings mixed with other values, returns the same sequence with adjacent strings concatenated. 16 | (If the sequence contains only strings, use clojure.string/join instead.)" 17 | [coll] 18 | (reduce 19 | (fn [elements-so-far elmt] 20 | (if (and (string? elmt) (string? (peek elements-so-far))) 21 | (let [previous-elements (pop elements-so-far) 22 | combined-last-string (str (peek elements-so-far) elmt)] 23 | (conj previous-elements combined-last-string)) 24 | (conj elements-so-far elmt))) 25 | [] 26 | coll)) 27 | 28 | 29 | (defn- transform-to-ast 30 | "Transforms the Instaparse output tree to an abstract syntax tree for SR markup." 31 | [tree] 32 | (insta/transform 33 | {:block (fn [& raw-contents] 34 | ;; use combine-adjacent-strings to collapse individual characters from any-char into one string 35 | (into [:block] (combine-adjacent-strings raw-contents))) 36 | :url-link (fn [text-contents url] 37 | (into [:url-link {:url url}] text-contents)) 38 | :url-link-text-contents (fn [& raw-contents] 39 | (combine-adjacent-strings raw-contents)) 40 | :url-link-url-parts (fn [& chars] 41 | (clojure.string/join chars)) 42 | :any-chars (fn [& chars] 43 | (clojure.string/join chars))} 44 | tree)) 45 | 46 | (def parser-file (io/resource "parser.ebnf")) 47 | 48 | (defparser block-parser 49 | (slurp parser-file)) 50 | 51 | (defn parse-to-ast 52 | "Converts a string of block syntax to an abstract syntax tree for SR markup." 53 | [block-content] 54 | (transform-to-ast (try 55 | (block-parser block-content) 56 | (catch Exception e (str "Exception when parsing content: " block-content))))) 57 | 58 | (def parsed (parse-to-ast "Metadata is here:: According to [[BJ Fogg]], we have [[motivation waves]]. Tie that in with the [[Fogg Behavior Model]] and you find that when people have high motivation, you should ask them to do something big and impactful, because if people are motivated to do more than the task that we ask them to do, it would be a waste for us not to prompt them to do so. On the flip side, if people aren't particularly motivated, we shouldn't ask them to do something hard. ((j598fj6)) This is similar to the premise of #[[difficulty matching]] #yessereebob `this is a line of code` {{query: {and: [[note]] [[January]] }}} {{youtube: https://youtu.be/5iI_0wnwIpU}} [fasfa]([[Hello page]]) **IMPORTANT** __emphasis__ ^^pop out^^ ~~old news~~")) 59 | 60 | (parse-to-ast "(((hello)))") 61 | 62 | (parse-to-ast "[hello]([[Hello]])") 63 | 64 | (parse-to-ast "I hope to combine my expertise in behavioral science and gamification to help users improve their lives through products. Your users hire you to help them achieve some goal, but loving your product is a result of your teamwork with the user.[++]([[The role of the user and the role of the app]]) Both players need to play.") 65 | 66 | (defn remove-n-surrounding-delimiters 67 | "Removes n surrounding characters from both the beginning and end of a string" 68 | [n string] 69 | (if (<= (count string) (* 2 n)) 70 | string 71 | (subs string n (- (count string) n)))) 72 | 73 | (defn remove-double-delimiters 74 | "Removes 2 surrounding characters from both the beginning and end of a string" 75 | [string] 76 | (remove-n-surrounding-delimiters 2 string)) 77 | 78 | (defn- strip-chars 79 | "Removes every character of a given set from a string" 80 | [chars collection] 81 | (reduce str (remove #((set chars) %) collection))) 82 | 83 | (defn- format-hashtag 84 | [hashtag] 85 | (if (= \[ (second hashtag)) 86 | (remove-double-delimiters (subs hashtag 1)) 87 | (subs hashtag 1))) 88 | 89 | (defn- format-alias 90 | [alias-content] 91 | (let [alias-text (remove-n-surrounding-delimiters 1 (re-find #"\[.+?\]" alias-content)) 92 | alias-dest (remove-n-surrounding-delimiters 1 (re-find #"\(.+?\)" alias-content)) 93 | alias-link (if (or (= \( (first alias-dest)) (= \[ (first alias-dest))) 94 | (utils/page-title->html-file-title alias-dest :case-sensitive) 95 | alias-dest)] 96 | [:a {:href alias-link} alias-text])) 97 | 98 | (defn- format-image 99 | [image-ref-content] 100 | (let [alt-text (remove-n-surrounding-delimiters 1 (re-find #"\[.+?\]" image-ref-content)) 101 | image-source (remove-n-surrounding-delimiters 1 (re-find #"\(.+?\)" image-ref-content))] 102 | [:img {:src image-source :alt alt-text :style "max-width: 90%"}])) 103 | 104 | (defn get-youtube-vid-embed 105 | "Returns an iframe for a YouTube embedding" 106 | [string] 107 | [:iframe {:width "560" 108 | :height "315" 109 | :src (str "https://www.youtube-nocookie.com/embed/" 110 | (if (re-find #"\[\[youtube\]\]" string) 111 | (cond 112 | (re-find #"youtube\.com" string) (subs string 47 (- (count string) 2)) 113 | (re-find #"youtu\.be" string) (subs string 32 (- (count string) 2)) 114 | :else "NO VALID ID FOUND") 115 | (cond 116 | (re-find #"youtube\.com" string) (subs string 43 (- (count string) 2)) 117 | (re-find #"youtu\.be" string) (subs string 28 (- (count string) 2)) 118 | :else "NO VALID ID FOUND"))) 119 | :frameborder "0" 120 | :allow "accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" 121 | :allowfullscreen ""}]) 122 | 123 | (defn- make-link-from-url 124 | [string] 125 | [:a {:href string} string]) 126 | 127 | (defn element-vec->hiccup ;; TODO: have code to change behavior if page/block is not included 128 | [ast-ele block-map] 129 | (let [ele-content (second ast-ele)] 130 | (case (first ast-ele) 131 | :metadata-tag [:b [:a {:href (utils/page-title->html-file-title ele-content :case-sensitive)} 132 | (subs ele-content 0 (dec (count ele-content)))]] 133 | :page-link [:a {:href (utils/page-title->html-file-title ele-content :case-sensitive)} 134 | (remove-double-delimiters ele-content)] 135 | :block-ref [:a {:href (utils/page-title->html-file-title ele-content :case-sensitive)} 136 | (:content 137 | (get block-map 138 | (remove-double-delimiters ele-content)))] 139 | :hashtag [:a {:href (utils/page-title->html-file-title ele-content :case-sensitive)} 140 | (format-hashtag ele-content)] 141 | :strikethrough [:s (remove-double-delimiters ele-content)] 142 | :highlight [:mark (remove-double-delimiters ele-content)] 143 | :italic [:i (remove-double-delimiters ele-content)] 144 | :bold [:b (remove-double-delimiters ele-content)] 145 | :alias (format-alias ele-content) 146 | :image (format-image ele-content) 147 | :todo [:input {:type "checkbox" :disabled "disabled"}] 148 | :done [:input {:type "checkbox" :disabled "disabled" :checked "checked"}] 149 | :code-line [:code (remove-n-surrounding-delimiters 1 ele-content)] 150 | :youtube (get-youtube-vid-embed ele-content) 151 | :bare-url (make-link-from-url ele-content) 152 | ast-ele))) 153 | 154 | (defn ele->hiccup 155 | [ele block-map] 156 | (cond 157 | (string? ele) ele 158 | (= ele :block) :div 159 | (vector? ele) (element-vec->hiccup ele block-map))) 160 | 161 | (defn entry-point? 162 | "Determines whether or not a given page is tagged with #EntryPoint in its first child block" 163 | [page] 164 | (if (= (count (:children page)) 0) 165 | false 166 | (if (or (str-utils/includes? 167 | (:string (first (:children page))) "#EntryPoint") 168 | (str-utils/includes? 169 | (:string (first (:children page))) "#Homepage")) 170 | true 171 | false))) 172 | 173 | (defn block-content->hiccup 174 | "Convert Roam markup to Hiccup" 175 | [content block-map] 176 | (vec (map #(ele->hiccup % block-map) (parse-to-ast content)))) 177 | 178 | (defn- get-eid-for-node-title 179 | [roam-db node-title] 180 | (first (first (ds/q '[:find 181 | ?eid 182 | :in $ ?node-title 183 | :where 184 | [?eid :node/title ?node-title]] 185 | @roam-db node-title)))) 186 | 187 | (defn- get-eid-for-block-uid 188 | [roam-db block-uid] 189 | (first (first (ds/q '[:find 190 | ?eid 191 | :in $ ?block-uid 192 | :where 193 | [?eid :block/uid ?block-uid]] 194 | @roam-db block-uid)))) 195 | 196 | (defn- html-file-name-for-uid 197 | [roam-db uid] 198 | (str (:block/uid (ds/entity (ds/db roam-db) (get-eid-for-node-title roam-db uid))) ".html")) 199 | 200 | (defn element-vec->hiccup ;; TODO: have code to change behavior if page/block is not included 201 | [roam-db ast-ele] 202 | (let [ele-content (second ast-ele)] 203 | (case (first ast-ele) 204 | :metadata-tag [:b [:a {:href (html-file-name-for-uid roam-db (subs ele-content 0 (- (count ele-content) 2)))} 205 | (subs ele-content 0 (dec (count ele-content)))]] 206 | :page-link [:a {:href (html-file-name-for-uid roam-db (remove-double-delimiters ele-content))} 207 | (remove-double-delimiters ele-content)] 208 | :block-ref [:a {:href (str (remove-double-delimiters ele-content) ".html")} 209 | (:block/string 210 | (ds/entity (ds/db roam-db) 211 | (get-eid-for-block-uid roam-db (remove-double-delimiters ele-content))))] 212 | :hashtag [:a {:href (html-file-name-for-uid roam-db (format-hashtag ele-content))} 213 | (format-hashtag ele-content)] 214 | :strikethrough [:s (remove-double-delimiters ele-content)] 215 | :highlight [:mark (remove-double-delimiters ele-content)] 216 | :italic [:i (remove-double-delimiters ele-content)] 217 | :bold [:b (remove-double-delimiters ele-content)] 218 | :alias (format-alias ele-content) 219 | :image (format-image ele-content) 220 | :todo [:input {:type "checkbox" :disabled "disabled"}] 221 | :done [:input {:type "checkbox" :disabled "disabled" :checked "checked"}] 222 | :code-line [:code (remove-n-surrounding-delimiters 1 ele-content)] 223 | :youtube (get-youtube-vid-embed ele-content) 224 | :bare-url (make-link-from-url ele-content) 225 | ast-ele))) 226 | 227 | (defn ele->hiccup 228 | [roam-db ele] 229 | (cond 230 | (string? ele) ele 231 | (= ele :block) :div 232 | (vector? ele) (element-vec->hiccup roam-db ele))) 233 | 234 | (defn- roam-markdown->hiccup 235 | [roam-db content-string] 236 | (vec (map #(ele->hiccup roam-db %) (parse-to-ast content-string)))) 237 | 238 | (defn parse-entities-in-db-to-hiccup 239 | [roam-db] 240 | (let [eids (map first (ds/q '[:find ?eid :where [?eid]] @roam-db)) 241 | transactions-seq (map 242 | (fn [eid] 243 | [:db/add 244 | eid 245 | :static-roam/hiccup 246 | (roam-markdown->hiccup roam-db (:block/string (ds/entity (ds/db roam-db) eid)))]) 247 | eids) 248 | transactions (vec 249 | transactions-seq) 250 | transaction-report (ds/transact! roam-db transactions)] 251 | transaction-report)) 252 | -------------------------------------------------------------------------------- /src/static_roam/templating.clj: -------------------------------------------------------------------------------- 1 | (ns static-roam.templating 2 | (:require [static-roam.utils :as utils] 3 | [static-roam.parser :as parser] 4 | [static-roam.database :as database] 5 | [datascript.core :as ds] 6 | [clojure.pprint :as pprint])) 7 | 8 | (defn page-hiccup 9 | [body-hiccup site-title nav-bar-page-dict css-path js-path] 10 | [:html 11 | [:head 12 | [:meta {:charset "utf-8"}] 13 | [:meta {:name "viewport" :content "width=device-width, initial-scale=1.0"}] 14 | [:title site-title] 15 | [:link {:rel "stylesheet" :href css-path}] 16 | [:script {:src js-path}]] 17 | [:body 18 | [:header.site-header 19 | [:div.wrapper 20 | [:a.site-title {:rel "author" :href ".."} site-title] 21 | (into 22 | [:div.nav-links] 23 | (map (fn [pair] [:a.nav-link {:href (first pair)} (second pair)]) nav-bar-page-dict))]] 24 | body-hiccup]]) 25 | 26 | (defn children-of-block-template 27 | [block-id block-map] 28 | (let [properties (database/get-properties-for-block-id block-id block-map)] 29 | [:ul 30 | (if (or (nil? (:hiccup properties)) (= (:content properties) block-id)) 31 | "" 32 | [:li {:onclick (str "location.href='" (utils/page-title->html-file-title block-id :case-sensitive) "'")} (:hiccup properties)]) 33 | (let [children (:children properties)] 34 | (if (not= 0 (count children)) 35 | ;; recurse on each of the children 36 | (map #(children-of-block-template % block-map) children) 37 | ;; otherwise, evaluate to empty div 38 | [:div]))])) 39 | 40 | (defn linked-references-template 41 | [references block-map] 42 | (concat [] 43 | (map 44 | (fn 45 | [r] 46 | (if (nil? (get block-map r)) 47 | "" 48 | [:li 49 | {:onclick 50 | (str "location.href='" 51 | (str "" 52 | (utils/page-title->html-file-title 53 | r 54 | :case-sensitive)) "'" )} 55 | (:hiccup (get block-map r))])) 56 | references))) 57 | 58 | (defn- is-parent 59 | [block-id block-kv] 60 | (if (some #(= block-id %) (:children (second block-kv))) 61 | (first block-kv) 62 | nil)) 63 | 64 | (defn- find-parent 65 | [block-id block-map] 66 | (filter #(not (nil? %)) (map #(is-parent block-id %) block-map))) 67 | 68 | (defn- get-parent 69 | [block-id block-map] 70 | (let [parent-id (first (find-parent block-id block-map))] 71 | (if (nil? parent-id) 72 | "" 73 | [:a {:href (str "" 74 | (utils/page-title->html-file-title 75 | parent-id 76 | :case-sensitive))} 77 | (:content (get block-map parent-id))]))) 78 | 79 | (defn block-page-template 80 | [block-id block-content block-map] 81 | [:div 82 | [:div 83 | [:h3 (get-parent block-id block-map)]] 84 | [:h2 (vec (map #(parser/ele->hiccup % block-map) (parser/parse-to-ast block-content)))] 85 | [:div (children-of-block-template block-id block-map)] 86 | [:div {:style "background-color:lightblue;"} 87 | [:h3 "Linked References"] 88 | (linked-references-template (database/get-linked-references block-id block-map) block-map)]]) 89 | 90 | (defn page-index-hiccup 91 | [link-list css-path js-path] 92 | [:html 93 | [:head 94 | [:meta {:charset "utf-8"}] 95 | [:meta {:name "viewport" :content "width=device-width, initial-scale=1.0"}] 96 | [:link {:rel "stylesheet" :href css-path}] 97 | [:script {:src js-path}]] 98 | [:body link-list]]) 99 | 100 | (defn home-page-hiccup 101 | [link-list title nav-bar-page-dict css-path js-path] 102 | [:html 103 | [:head 104 | [:meta {:charset "utf-8"}] 105 | [:meta {:name "viewport" :content "width=device-width, initial-scale=1.0"}] 106 | [:title title] 107 | [:link {:rel "stylesheet" :href css-path}] 108 | [:script {:src js-path}]] 109 | [:body 110 | [:header.site-header 111 | [:div.wrapper 112 | [:a.site-title {:rel "author" :href "."} title] 113 | (into 114 | [:div.nav-links] 115 | (map (fn [pair] [:a.nav-link {:href (str "./pages" (subs (first pair) 1))} (second pair)]) nav-bar-page-dict))]] 116 | [:main.page-content {:aria-label "Content"} 117 | [:div.wrapper 118 | [:h2.post-list-heading "Entry Points"] 119 | link-list]]]]) 120 | 121 | (defn list-of-page-links 122 | "Generate a Hiccup unordered list of links to pages" 123 | ([page-titles] 124 | (let [page-links (map utils/page-link-from-title page-titles)] 125 | (conj [:ul.post-list ] (map (fn [a] [:li [:h3 a]]) page-links)))) 126 | ([page-titles dir] 127 | (let [page-links (map #(utils/page-link-from-title %) page-titles)] 128 | (conj [:ul.post-list ] (map (fn [a] [:li [:h3 a]]) page-links)))) 129 | ([page-titles dir link-class] 130 | (let [page-links (map #(utils/page-link-from-title dir % link-class) page-titles)] 131 | (conj [:ul.post-list ] (map (fn [a] [:li [:h3 a]]) page-links))))) 132 | 133 | ;; --------------------------------------------------------------------------------------------------------- 134 | 135 | (def general-site-info 136 | { 137 | "Site Title" "Thomas Lisankie" 138 | "Nav Bar Pages" ["About", "Now", "Posts", "My Mind", "Contact"] 139 | "Twitter Profile" "TomLisankie" 140 | "GitHub Profile" "TomLisankie" 141 | }) 142 | 143 | (defn- homepage-head 144 | [page-title] 145 | [:head 146 | [:title page-title] 147 | [:meta {:name "viewport" :content "width=device-width, initial-scale=1, maximum-scale=1"}] 148 | [:link {:rel "shortcut icon" :href "./assets/favicon.ico" :type "image/x-icon"}] 149 | [:meta {:charset "utf-8"}] 150 | [:link {:rel "stylesheet" :type "text/css" :href "./assets/style.css"}] 151 | [:link {:rel "preconnect" :href "https://fonts.gstatic.com"}] 152 | [:link {:href "https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,400;0,600;0,700;1,400;1,600;1,700&display=swap" :rel "stylesheet"}]]) 153 | 154 | (defn- head 155 | [page-title] 156 | [:head 157 | [:title page-title] 158 | [:meta {:name "viewport" :content "width=device-width, initial-scale=1, maximum-scale=1"}] 159 | [:link {:rel "shortcut icon" :href "../assets/favicon.ico" :type "image/x-icon"}] 160 | [:meta {:charset "utf-8"}] 161 | [:link {:rel "stylesheet" :type "text/css" :href "../assets/style.css"}] 162 | [:link {:rel "preconnect" :href "https://fonts.gstatic.com"}] 163 | [:link {:href "https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,400;0,600;0,700;1,400;1,600;1,700&display=swap" :rel "stylesheet"}]]) 164 | 165 | (defn- header 166 | [] 167 | [:header 168 | [:h1 {:id "site-title-link"} 169 | [:a {:href "../"} (get general-site-info "Site Title")]] 170 | [:nav {:id "nav-bar"} 171 | [:input {:type "checkbox" :name "nav-trigger" :id "nav-trigger"}] 172 | [:label {:for "nav-trigger"} 173 | [:svg {:viewBox "0 0 100 80" :width "40" :height "40"} 174 | [:rect {:width "100" :height "20"}] 175 | [:rect {:y "30" :width "100" :height "20"}] 176 | [:rect {:y "60" :width "100" :height "20"}]]] 177 | [:div {:id "nav-links"} 178 | [:a {:class "nav-bar-link" :href "../about"} "About"] 179 | [:span {:class "link-separator"} " | "] 180 | [:a {:class "nav-bar-link" :href "../now"} "Now"] 181 | [:span {:class "link-separator"} " | "] 182 | [:a {:class "nav-bar-link" :href "../posts"} "Posts"] 183 | [:span {:class "link-separator"} " | "] 184 | [:a {:class "nav-bar-link" :href "../mind"} "My Mind"] 185 | [:span {:class "link-separator"} " | "] 186 | [:a {:class "nav-bar-link" :href "../contact"} "Contact"] 187 | [:span {:class "link-separator"} " | "] 188 | [:a {:class "social-icon" :href (str "https://twitter.com/" (get general-site-info "Twitter Profile"))} 189 | [:img {:src "../assets/twitter.svg" :width "20" :height "20"}]] 190 | " " 191 | [:a {:class "social-icon" :href (str "https://github.com/" (get general-site-info "GitHub Profile"))} 192 | [:img {:src "../assets/github.svg" :width "20" :height "20"}]]]]]) 193 | 194 | (defn- homepage-header 195 | [] 196 | [:header 197 | [:h1 {:id "site-title-link"} 198 | [:a {:href "./"} (get general-site-info "Site Title")]] 199 | [:nav {:id "nav-bar"} 200 | [:input {:type "checkbox" :name "nav-trigger" :id "nav-trigger"}] 201 | [:label {:for "nav-trigger"} 202 | [:svg {:viewBox "0 0 100 80" :width "40" :height "40"} 203 | [:rect {:width "100" :height "20"}] 204 | [:rect {:y "30" :width "100" :height "20"}] 205 | [:rect {:y "60" :width "100" :height "20"}]]] 206 | [:div {:id "nav-links"} 207 | [:a {:class "nav-bar-link" :href "./about"} "About"] 208 | [:span {:class "link-separator"} " | "] 209 | [:a {:class "nav-bar-link" :href "./now"} "Now"] 210 | [:span {:class "link-separator"} " | "] 211 | [:a {:class "nav-bar-link" :href "./posts"} "Posts"] 212 | [:span {:class "link-separator"} " | "] 213 | [:a {:class "nav-bar-link" :href "./mind"} "My Mind"] 214 | [:span {:class "link-separator"} " | "] 215 | [:a {:class "nav-bar-link" :href "./contact"} "Contact"] 216 | [:span {:class "link-separator"} " | "] 217 | [:a {:class "social-icon" :href (str "https://twitter.com/" (get general-site-info "Twitter Profile"))} 218 | [:img {:src "./assets/twitter.svg" :width "20" :height "20"}]] 219 | " " 220 | [:a {:class "social-icon" :href (str "https://github.com/" (get general-site-info "GitHub Profile"))} 221 | [:img {:src "./assets/github.svg" :width "20" :height "20"}]]]]]) 222 | 223 | (defn- footer 224 | [] 225 | [:footer 226 | [:center 227 | [:div {:class "rc-webring"} 228 | [:a {:href "https://webring.recurse.com"} 229 | [:img {:src "https://webring.recurse.com/icon.png" :width "20" :height "20"} 230 | "RC Webring"]]] 231 | [:div 232 | "Want to become a better programmer? " 233 | [:a {:href "https://www.recurse.com/scout/click?t=1a34fc98998afcb8a3ea183e9aa2b564"} "Join the Recurse Center!"]]]]) 234 | 235 | (defn- children-of-block-hiccup 236 | [roam-db block-eid top-level] 237 | (let [ul (if top-level 238 | [:ul {:style "list-style-type:none; padding-left:0;"} 239 | [:li (:static-roam/hiccup (ds/entity (ds/db roam-db) block-eid))]] 240 | [:ul 241 | [:li (:static-roam/hiccup (ds/entity (ds/db roam-db) block-eid))]])] 242 | (conj 243 | ul 244 | (let [children-eids (map #(:db/id %) (sort-by :block/order (:block/children (ds/pull (ds/db roam-db) [{:block/children [:db/id :block/order]}] block-eid))))] 245 | (if (not= 0 (count children-eids)) 246 | ;; recurse on each of the children 247 | (map #(children-of-block-hiccup roam-db % false) children-eids) 248 | ;; otherwise, evaluate to empty div 249 | [:div]))))) 250 | 251 | (defn- get-eids-for-tagged-blocks 252 | [roam-db tag] 253 | (let [sr-info-eid (first (first (ds/q '[:find ?eid 254 | :where 255 | [?eid :node/title "Static-Roam Info"]] 256 | @roam-db))) 257 | eid-of-tag (first (first 258 | (ds/q '[:find ?eid 259 | :in $ ?tag 260 | :where 261 | [?eid :node/title ?tag]] 262 | @roam-db tag))) 263 | query-result (ds/q '[:find ?parent-eid 264 | :in $ ?tag-eid ?sr-info-eid 265 | :where 266 | [?eid :block/refs ?sr-info-eid] 267 | [?eid :block/refs ?tag-eid] 268 | [?eid :block/parents ?parent-eid]] 269 | @roam-db eid-of-tag sr-info-eid) 270 | eids-of-tagged-blocks (filter (complement nil?) 271 | (map first 272 | (into [] query-result)))] 273 | eids-of-tagged-blocks)) 274 | 275 | (defn- get-about-content-hiccup 276 | [roam-db] 277 | (let [about-page-eid (first (first (ds/q '[:find ?eid 278 | :where 279 | [?eid :node/title "About Page"]] 280 | @roam-db))) 281 | children-eids (map #(:db/id %) (sort-by :block/order (:block/children (ds/pull (ds/db roam-db) [{:block/children [:db/id :block/order]}] about-page-eid))))] 282 | [:section {:id "about-content"} 283 | [:div 284 | [:h1 "About Me"]] 285 | [:ul {:style "list-style-type:none; padding-left:0;"} 286 | (map #(children-of-block-hiccup roam-db % true) children-eids)]])) 287 | 288 | (defn about-template 289 | [roam-db] 290 | (println "About Template") 291 | (let [about-content (get-about-content-hiccup roam-db)] 292 | {"/about/index.html" 293 | [:html {:lang "en-US"} 294 | (head "About | Thomas Lisankie") 295 | [:body 296 | (header) 297 | [:main {:id "content"} 298 | about-content] 299 | (footer)]]})) 300 | 301 | (defn- get-contact-content-hiccup 302 | [roam-db] 303 | (let [contact-page-eid (first (first (ds/q '[:find ?eid 304 | :where 305 | [?eid :node/title "Contact Page"]] 306 | @roam-db))) 307 | children-eids (map #(:db/id %) (sort-by :block/order (:block/children (ds/pull (ds/db roam-db) [{:block/children [:db/id :block/order]}] contact-page-eid))))] 308 | [:section {:id "contact-content"} 309 | [:div 310 | [:h1 "Contact Me"]] 311 | [:ul {:style "list-style-type:none; padding-left:0;"} 312 | (map #(children-of-block-hiccup roam-db % true) children-eids)]])) 313 | 314 | (defn contact-template 315 | [roam-db] 316 | (let [contact-content (get-contact-content-hiccup roam-db)] 317 | {"/contact/index.html" 318 | [:html {:lang "en-US"} 319 | (head "Contact | Thomas Lisankie") 320 | [:body 321 | (header) 322 | [:main {:id "content"} 323 | contact-content] 324 | (footer)]]})) 325 | 326 | (defn- get-now-content-hiccup 327 | [roam-db] 328 | (let [now-page-eid (first (first (ds/q '[:find ?eid 329 | :where 330 | [?eid :node/title "Now Page"]] 331 | @roam-db))) 332 | children-eids (map #(:db/id %) (sort-by :block/order (:block/children (ds/pull (ds/db roam-db) [{:block/children [:db/id :block/order]}] now-page-eid))))] 333 | [:section {:id "contact-content"} 334 | [:div 335 | [:h1 "What I'm Up To"]] 336 | [:ul {:style "list-style-type:none; padding-left:0;"} 337 | (map #(children-of-block-hiccup roam-db % true) children-eids)]])) 338 | 339 | (defn now-template 340 | [roam-db] 341 | (let [now-content (get-now-content-hiccup roam-db)] 342 | {"/now/index.html" 343 | [:html {:lang "en-US"} 344 | (head "Now | Thomas Lisankie") 345 | [:body 346 | (header) 347 | [:main {:id "content"} 348 | now-content] 349 | (footer)]]})) 350 | 351 | (defn- path-for-post 352 | [roam-db post-eid] 353 | (let [uid (:block/uid (ds/entity (ds/db roam-db) post-eid)) 354 | path (str "/nodes/" uid ".html")] 355 | path)) 356 | 357 | (defn- get-post-content-hiccup 358 | [roam-db post-eid] 359 | (let [children-eids (map #(:db/id %) (sort-by :block/order (:block/children (ds/pull (ds/db roam-db) [{:block/children [:db/id :block/order]}] post-eid)))) 360 | post-title (:node/title (ds/entity (ds/db roam-db) post-eid))] 361 | [:section {:id "post-content"} 362 | [:div 363 | [:h1 post-title]] 364 | [:ul {:style "list-style-type:none; padding-left:0;"} 365 | (map #(children-of-block-hiccup roam-db % true) children-eids)]])) 366 | 367 | (defn- hiccup-for-post 368 | [roam-db post-eid] 369 | (let [post-content (get-post-content-hiccup roam-db post-eid) 370 | post-title (:node/title (ds/entity (ds/db roam-db) post-eid))] 371 | [:html {:lang "en-US"} 372 | (head post-title) 373 | [:body 374 | (header) 375 | [:main {:id "content"} 376 | post-content] 377 | (footer)]])) 378 | 379 | (defn- post-path-hiccup-pairs 380 | [roam-db] 381 | (let [post-eids (get-eids-for-tagged-blocks roam-db "Post")] 382 | (map (fn [post] [(path-for-post roam-db post) (hiccup-for-post roam-db post)]) post-eids))) 383 | 384 | (defn post-template 385 | [roam-db] 386 | (into {} (post-path-hiccup-pairs roam-db))) 387 | 388 | (defn- link-li-ele 389 | [roam-db eid] 390 | (let [page-title (:node/title (ds/entity (ds/db roam-db) eid)) 391 | uid (:block/uid (ds/entity (ds/db roam-db) eid)) 392 | path (str "../nodes/" uid ".html")] 393 | [:li 394 | [:a {:href path} page-title]])) 395 | 396 | (defn- link-li-ele-homepage 397 | [roam-db eid] 398 | (let [page-title (:node/title (ds/entity (ds/db roam-db) eid)) 399 | uid (:block/uid (ds/entity (ds/db roam-db) eid)) 400 | path (str "./nodes/" uid ".html")] 401 | [:li 402 | [:a {:href path} page-title]])) 403 | 404 | (defn- get-links-for-eids 405 | [roam-db eids] 406 | (map #(link-li-ele roam-db %) eids)) 407 | 408 | (defn- get-links-for-eids-homepage 409 | [roam-db eids] 410 | (map #(link-li-ele-homepage roam-db %) eids)) 411 | 412 | (defn- get-page-title-links-for-tagged-homepage 413 | [roam-db tag] 414 | (let [eids-of-tagged-blocks (get-eids-for-tagged-blocks roam-db tag) 415 | link-li-eles (get-links-for-eids-homepage roam-db eids-of-tagged-blocks)] 416 | link-li-eles)) 417 | 418 | (defn- get-page-title-links-for-tagged 419 | [roam-db tag] 420 | (let [eids-of-tagged-blocks (get-eids-for-tagged-blocks roam-db tag) 421 | link-li-eles (get-links-for-eids roam-db eids-of-tagged-blocks)] 422 | link-li-eles)) 423 | 424 | (defn mind-template 425 | [roam-db] 426 | (let [entry-point-list (into [:ul {:id "entry-points"}] (get-page-title-links-for-tagged roam-db "EntryPoint"))] 427 | {"/mind/index.html" 428 | [:html {:lang "en-US"} 429 | (head "My Mind | Thomas Lisankie") 430 | [:body 431 | (header) 432 | [:main {:id "content"} 433 | [:section {:id "entry-point-list"} 434 | [:div 435 | [:h1 "Entry Points into my mind"]] 436 | entry-point-list]] 437 | (footer)]]})) 438 | 439 | (defn posts-index-template 440 | [roam-db] 441 | (let [post-list (into [:ul {:id "posts"}] (get-page-title-links-for-tagged roam-db "Post"))] 442 | {"/posts/index.html" 443 | [:html {:lang "en-US"} 444 | (head "My Mind | Thomas Lisankie") 445 | [:body 446 | (header) 447 | [:main {:id "content"} 448 | [:section {:id "post-list"} 449 | [:div 450 | [:h1 "Posts / Essays / Prose / etc."]] 451 | post-list]] 452 | (footer)]]})) 453 | 454 | (defn- children-of-node-hiccup 455 | [roam-db block-eid] 456 | [:ul 457 | [:li {:onclick (str "location.href='./" (:block/uid (ds/entity (ds/db roam-db) block-eid)) ".html'") 458 | :class "graph-page-li"} 459 | (:static-roam/hiccup (ds/entity (ds/db roam-db) block-eid))] 460 | (let [children-eids (map #(:db/id %) (sort-by :block/order (:block/children (ds/pull (ds/db roam-db) [{:block/children [:db/id :block/order]}] block-eid))))] 461 | (if (not= 0 (count children-eids)) 462 | ;; recurse on each of the children 463 | (map #(children-of-node-hiccup roam-db %) children-eids) 464 | ;; otherwise, evaluate to empty div 465 | [:div]))]) 466 | 467 | (defn- get-parent-link-hiccup 468 | [roam-db node-eid] 469 | (let [parent-eid (first 470 | (first 471 | (ds/q '[:find ?parent-eid 472 | :in $ ?eid 473 | :where 474 | [?parent-eid :block/children ?eid]] 475 | @roam-db node-eid))) 476 | node-title (if (:node/title (ds/entity (ds/db roam-db) parent-eid)) 477 | (:node/title (ds/entity (ds/db roam-db) parent-eid)) 478 | (:block/string (ds/entity (ds/db roam-db) parent-eid))) 479 | node-url (str "./" (:block/uid (ds/entity (ds/db roam-db) parent-eid)) ".html")] 480 | (if (nil? node-title) 481 | [:div] 482 | [:div "⮬" [:a {:href node-url} node-title]]))) 483 | 484 | (defn- get-node-content-hiccup 485 | [roam-db node-eid] 486 | (let [children-eids (map #(:db/id %) (sort-by :block/order (:block/children (ds/pull (ds/db roam-db) [{:block/children [:db/id :block/order]}] node-eid)))) 487 | node-title (if (:node/title (ds/entity (ds/db roam-db) node-eid)) 488 | (:node/title (ds/entity (ds/db roam-db) node-eid)) 489 | (:static-roam/hiccup (ds/entity (ds/db roam-db) node-eid))) 490 | uid (:block/uid (ds/entity (ds/db roam-db) node-eid)) 491 | path (str "./nodes/" uid ".html")] 492 | [:section {:id "post-content"} 493 | [:h3 (get-parent-link-hiccup roam-db node-eid)] 494 | [:div 495 | [:h1 node-title]] 496 | [:ul 497 | (map #(children-of-node-hiccup roam-db %) children-eids)]])) 498 | 499 | (defn- get-linked-ref-hiccup 500 | [roam-db eid] 501 | [:li {:onclick (str "location.href='./" (:block/uid (ds/entity (ds/db roam-db) eid)) ".html'") 502 | :class "graph-page-li"} 503 | (:static-roam/hiccup (ds/entity (ds/db roam-db) eid))]) 504 | 505 | (defn- get-linked-refs-hiccup 506 | [roam-db node-eid] 507 | (let [ref-eids (map first (ds/q '[:find ?linked-ref-eids 508 | :in $ ?eid 509 | :where 510 | [?linked-ref-eids :block/refs ?eid]] @roam-db node-eid))] 511 | (when (= node-eid 17324) 512 | (println ref-eids)) 513 | (map #(get-linked-ref-hiccup roam-db %) ref-eids))) 514 | 515 | (defn- get-linked-references-for-node 516 | [roam-db node-eid] 517 | (into [:ul] (get-linked-refs-hiccup roam-db node-eid))) 518 | 519 | (defn- hiccup-for-node 520 | [roam-db node-eid] 521 | (let [node-content (get-node-content-hiccup roam-db node-eid) 522 | node-title (if (:node/title (ds/entity (ds/db roam-db) node-eid)) 523 | (:node/title (ds/entity (ds/db roam-db) node-eid)) 524 | (:block/string (ds/entity (ds/db roam-db) node-eid)))] 525 | [:html {:lang "en-US"} 526 | (head node-title) 527 | [:body 528 | (header) 529 | [:main {:id "graph-page-content"} 530 | node-content 531 | [:div {:class "linked-references"} 532 | [:h3 {:style "padding-left: 1em;"} "Linked References"] 533 | (get-linked-references-for-node roam-db node-eid)]] 534 | (footer)]])) 535 | 536 | (defn- get-eids-for-included-blocks 537 | [roam-db] 538 | (map first (ds/q '[:find ?eids 539 | :where 540 | [?eids :static-roam/included true]] @roam-db))) 541 | 542 | (defn- node-path-hiccup-pairs 543 | [roam-db] 544 | (let [node-eids (get-eids-for-included-blocks roam-db)] 545 | (map (fn [node] [(path-for-post roam-db node) (hiccup-for-node roam-db node)]) node-eids))) 546 | 547 | (defn graph-page-template 548 | [roam-db] 549 | (into {} (node-path-hiccup-pairs roam-db))) 550 | 551 | (defn homepage-template 552 | [roam-db] 553 | (let [post-list (into [:ul {:id "posts"}] (get-page-title-links-for-tagged-homepage roam-db "Post")) 554 | entry-point-list (into [:ul {:id "entry-points"}] (get-page-title-links-for-tagged-homepage roam-db "EntryPoint"))] 555 | {"/index.html" 556 | [:html {:lang "en-US"} 557 | (homepage-head "Thomas Lisankie") 558 | [:body 559 | (homepage-header) 560 | [:main {:id "homepage"} 561 | [:section {:id "about-me"} 562 | [:div 563 | [:h2 "Me In 10 Seconds"] 564 | [:p "I'm a software developer. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog."]] 565 | [:div 566 | [:h2 "Me In 10 Minutes"] 567 | [:p 568 | "Check out the " 569 | [:a {:href "./about"} 570 | "about"] 571 | " page"]]] 572 | [:section {:id "posts-home"} 573 | [:h2 "Posts"] 574 | post-list] 575 | [:section {:id "entry-points-home"} 576 | [:h2 "Entry Points into my Mind"] 577 | entry-point-list]] 578 | (footer)]]})) 579 | 580 | (defn- create-templates 581 | [roam-db template-info] 582 | (for [template-fn-pair {"about" 583 | about-template 584 | "contact" 585 | contact-template 586 | "graph-page-example" 587 | graph-page-template 588 | "index" 589 | homepage-template 590 | "mind" 591 | mind-template 592 | "now" 593 | now-template 594 | "post-example" 595 | post-template 596 | "posts" 597 | posts-index-template 598 | } 599 | :let [template-name (first template-fn-pair) 600 | template-fn (second template-fn-pair)]] 601 | (template-fn roam-db))) 602 | 603 | (defn- aggregate-templates 604 | [template-maps] 605 | (reduce into {} template-maps)) 606 | 607 | (defn generate-templates 608 | [roam-db template-info] 609 | (let [template-maps (create-templates roam-db template-info) 610 | final-template-map (aggregate-templates template-maps)] 611 | final-template-map)) 612 | -------------------------------------------------------------------------------- /src/static_roam/utils.clj: -------------------------------------------------------------------------------- 1 | (ns static-roam.utils 2 | (:require [me.raynes.fs :as fs] 3 | [clojure.java.io :as io] 4 | [clojure.string :as str-utils] 5 | [clojure.data.json :as json] 6 | [datascript.core :as ds] 7 | [clojure.edn :as edn-utils]) 8 | (:import (java.util.zip ZipFile))) 9 | 10 | (defn unzip-roam-edn 11 | "Takes the path to a zipfile `source` and unzips it to `target-dir`, returning the path of the target file" 12 | [source target-dir] 13 | (str target-dir (with-open [zip (ZipFile. (fs/file source))] 14 | (let [entries (enumeration-seq (.entries zip)) 15 | target-file #(fs/file target-dir (str %)) 16 | database-file-name (.getName (first entries))] 17 | (doseq [entry entries :when (not (.isDirectory ^java.util.zip.ZipEntry entry)) 18 | :let [f (target-file entry)]] 19 | (fs/mkdirs (fs/parent f)) 20 | (io/copy (.getInputStream zip entry) f)) 21 | database-file-name)))) 22 | 23 | (defn create-roam-edn-db-from-zip 24 | [path-to-zip] 25 | (let [edn-path (unzip-roam-edn 26 | path-to-zip 27 | (->> path-to-zip 28 | (#(str-utils/split % #"/")) 29 | drop-last 30 | (str-utils/join "/") (#(str % "/")))) 31 | roam-db (edn-utils/read-string {:readers ds/data-readers} (slurp edn-path)) 32 | roam-db-conn (ds/conn-from-db roam-db)] 33 | roam-db-conn)) 34 | 35 | (defn remove-n-surrounding-delimiters 36 | "Removes n surrounding characters from both the beginning and end of a string" 37 | [n string] 38 | (subs string n (- (count string) n))) 39 | 40 | (defn remove-double-delimiters 41 | "Removes 2 surrounding characters from both the beginning and end of a string" 42 | [string] 43 | (remove-n-surrounding-delimiters 2 string)) 44 | 45 | (defn remove-triple-delimiters 46 | "Removes 3 surrounding characters from both the beginning and end of a string" 47 | [string] 48 | (remove-n-surrounding-delimiters 3 string)) 49 | 50 | (defn strip-chars 51 | "Removes every character of a given set from a string" 52 | [chars collection] 53 | (reduce str (remove #((set chars) %) collection))) 54 | 55 | (defn remove-leading-char 56 | [string] 57 | (subs string 1)) 58 | 59 | (defn remove-double-colon 60 | [string] 61 | (apply str (drop-last 2 string))) 62 | 63 | (defn page-title->html-file-title 64 | "Formats a Roam page title as a name for its corresponding HTML page (including '.html' extension)" 65 | ([string] 66 | {:pre [(string? string)]} 67 | (->> string 68 | (str-utils/lower-case) 69 | (strip-chars #{\( \) \[ \] \? \! \. \@ \# \$ \% \^ \& \* \+ \= \; \: \" \' \/ \\ \, \< \> \~ \` \{ \}}) 70 | (#(str-utils/replace % #"\s" "-")) 71 | (#(str "/" % ".html")))) 72 | ([string case-sensitive?] 73 | {:pre [(string? string)]} 74 | (->> string 75 | (#(if case-sensitive? 76 | % 77 | (str-utils/lower-case %))) 78 | (strip-chars #{\( \) \[ \] \? \! \. \@ \# \$ \% \^ \& \* \+ \= \; \: \" \' \/ \\ \, \< \> \~ \` \{ \}}) 79 | (#(str-utils/replace % #"\s" "-")) 80 | (#(str "./" % ".html"))))) 81 | 82 | (defn html-file-titles 83 | [page-titles] 84 | (let [page-titles-vec (vec page-titles)] 85 | (map #(subs (page-title->html-file-title % :case-sensitive) 1) page-titles-vec))) 86 | 87 | (defn page-link-from-title 88 | "Given a page and a directory for the page to go in, create Hiccup that contains the link to the HTML of that page" 89 | ([dir block-content] 90 | [:a {:href (str dir (page-title->html-file-title block-content :case-sensitive))} block-content]) 91 | ([block-content] 92 | [:a {:href (page-title->html-file-title block-content :case-sensitive)} block-content]) 93 | ([dir block-content link-class] 94 | [:a {:class link-class 95 | :href (str dir (subs (page-title->html-file-title block-content :case-sensitive) 1))} 96 | block-content])) 97 | 98 | (defn find-content-entities-in-string 99 | [string] 100 | (when (not (nil? string)) 101 | (re-seq #"\[\[.*?\]\]|\(\(.*?\)\)" string))) 102 | 103 | (defn find-hashtags-in-string 104 | [string] 105 | (when (not (nil? string)) 106 | (re-seq #"\#..*?(?=\s|$)" string))) 107 | 108 | (defn find-metadata-in-string 109 | [string] 110 | (when (not (nil? string)) 111 | (re-seq #"^.+?::" string))) 112 | 113 | (defn remove-heading-parens 114 | [strings] 115 | (map 116 | #(if (= "(((" (subs % 0 3)) 117 | (subs % 1) 118 | %) 119 | strings)) 120 | -------------------------------------------------------------------------------- /test/static_roam/core_test.clj: -------------------------------------------------------------------------------- 1 | (ns static-roam.core-test 2 | (:require [clojure.test :refer :all] 3 | [clojure.java.io :as io] 4 | [static-roam.core :refer :all])) 5 | 6 | (deftest test-unzip-roam-json-archive 7 | (testing "FIXME, I fail." 8 | (let [zip-dir "/home/thomas/Dropbox/Roam Exports/" 9 | zip-name "roam-test-export.zip" 10 | json-name "thomas.json"] 11 | (is 12 | ;; does it unzip to correct path? 13 | (= 14 | (str zip-dir json-name) 15 | (unzip-roam-json-archive (str zip-dir zip-name) zip-dir))) 16 | (is 17 | ;; does the file it unzipped actually exist? 18 | (.exists (io/as-file (str zip-dir json-name))))))) 19 | -------------------------------------------------------------------------------- /test/static_roam/parser_test.clj: -------------------------------------------------------------------------------- 1 | (ns static-roam.parser-test 2 | (:require [static-roam.parser :refer :all] 3 | [clojure.test :refer :all])) 4 | 5 | (deftest a-test 6 | (testing "FIXME, I fail." 7 | (is (= (parse-to-ast "[[Physics]] #Static-Roam") [:block [:page-link "Physics"] " " [:hashtag "Static-Roam"]])))) 8 | --------------------------------------------------------------------------------