{"id":17335747,"url":"https://github.com/thsutton/demo-site","last_synced_at":"2025-03-27T07:23:01.493Z","repository":{"id":66890022,"uuid":"213614718","full_name":"thsutton/demo-site","owner":"thsutton","description":null,"archived":false,"fork":false,"pushed_at":"2019-10-08T10:35:33.000Z","size":66,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-02-01T12:25:56.967Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"CSS","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/thsutton.png","metadata":{"files":{"readme":"README.md","changelog":"ChangeLog.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-10-08T10:35:30.000Z","updated_at":"2019-10-08T10:35:35.000Z","dependencies_parsed_at":"2023-05-13T00:15:35.752Z","dependency_job_id":null,"html_url":"https://github.com/thsutton/demo-site","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":"ChrisPenner/slick-template","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thsutton%2Fdemo-site","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thsutton%2Fdemo-site/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thsutton%2Fdemo-site/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thsutton%2Fdemo-site/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/thsutton","download_url":"https://codeload.github.com/thsutton/demo-site/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245798754,"owners_count":20674017,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-15T15:12:02.406Z","updated_at":"2025-03-27T07:23:01.486Z","avatar_url":"https://github.com/thsutton.png","language":"CSS","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Slick Template\n\nA cloneable template for building a static site with [slick](https://github.com/ChrisPenner/slick)!\n\nFor an example of what the site will look like, check out [my blog](https://chrispenner.ca)!\n\nGet up and running with slick in no time! \n\nHere's all you need to do to have your own site:\n\n1. Click the green `Use this template` button at the top of this page (next to \"clone\")\n2. Clone your new site repo\n3. Edit your `siteMeta` inside `Main.hs`\n4. Add some awesome blog posts in `site/posts/` by copying the sample post there\n4. run `stack build`; `stack exec build-site`\n5. Serve your `docs` directory by enabling Github Pages in your repository's settings\n6. ...?\n7. Profit!\n\nIf you want a quick tool for serving your file system during development I recommend using `serve`:\n\n```shell\n$ npm install -g serve\n$ serve docs\n```\n\nThen navigate to the port which is serving (usually http://localhost:3000 or http://localhost:5000 )\n\n\n---\n\n# Tips\n\n* Everything is just html, css, and javascript! Edit things to your heart's content!\n* Templates are in `site/tempates`; they use the [Mustache](https://mustache.github.io/) template language.\n* You'll need to delete your `.shake` directory when you edit `Main.hs` to avoid stale build caches.\n* Slick is good at **updating** and **creating** files, but it doesn't delete stale files. When in doubt you can delete your whole output directory.\n\n# Caching guide\n\nShake takes care of most of the tricky parts, but there're still a few things you need to know.\n\nCache-busting in Slick works using [`Development.Shake.Forward`](https://hackage.haskell.org/package/shake/docs/Development-Shake-Forward.html). The idea is that you can wrap actions with [`cacheAction`](https://hackage.haskell.org/package/shake-0.18.3/docs/Development-Shake-Forward.html#v:cacheAction), providing an unique identifier for each time it runs. Shake will track any dependencies which are triggered during the first run of that action and can use them to detect when that particular action must be re-run. Typically you'll want to cache an action for each \"thing\" you have to load, e.g. when you load a post, or when you build a page. You can also nest these caches if you like.\n\nWhen using `cacheAction` Shake will automatically serialize and store the results of that action to disk so that on a later build it can simply 'hydrate' that asset without running the command. For this reason, your data models should probably implement `Binary`. Here's an example data model:\n\n```haskell\n{-# LANGUAGE DeriveGeneric #-}\n{-# LANGUAGE DeriveAnyClass #-}\n\nimport Data.Aeson (ToJSON, FromJSON)\nimport Development.Shake.Classes (Binary)\nimport GHC.Generics (Generic)\n\n-- | Data for a blog post\ndata Post =\n    Post { title   :: String\n         , author  :: String\n         , content :: String\n         , url     :: String\n         , date    :: String\n         , image   :: Maybe String\n         }\n    deriving (Generic, Eq, Ord, Show, FromJSON, ToJSON, Binary)\n```\n\nIf you need to run arbitrary shell commands you can use [`cache`](https://hackage.haskell.org/package/shake-0.18.3/docs/Development-Shake-Forward.html#v:cache); it will do its best to track file use during the run of the command and cache-bust on that; results may vary. It's likely better to use explicit tracking commands like `readFile'` when possible, (or even just use `readFile'` on the files you depend on, then throw away the results. It's equivalent to explicitly depending on the file contents).\n\nShake has many dependency tracking combinators available; whenever possible you should use the shake variants of these (e.g. `copyFileChanged`, `readFile'`, `writeFile'`, etc.). This will allow shake to detect when and what it needs to rebuild.\n\nNote: You'll likely need to delete `.shake` in your working directory after editing your `Main.hs` file as shake can get confused if rules change without it noticing.\n\n## Example\n\nHere's a FULL static website! This is the [`Main.hs`](app/Main.hs) for this template repo.\n\n```haskell\n{-# LANGUAGE DeriveGeneric #-}\n{-# LANGUAGE DeriveAnyClass #-}\n{-# LANGUAGE OverloadedStrings #-}\n\nmodule Main where\n\nimport           Control.Lens\nimport           Control.Monad\nimport           Data.Aeson                 as A\nimport           Data.Aeson.Lens\nimport           Development.Shake\nimport           Development.Shake.Classes (Binary)\nimport           Development.Shake.Forward\nimport           Development.Shake.FilePath\nimport           GHC.Generics               (Generic)\nimport           Slick\n\nimport qualified Data.Text                  as T\n\n\noutputFolder :: FilePath\noutputFolder = \"docs/\"\n\n-- | Data for the index page\ndata IndexInfo =\n  IndexInfo\n    { posts :: [Post]\n    } deriving (Generic, Show, FromJSON, ToJSON)\n\n-- | Data for a blog post\ndata Post =\n    Post { title   :: String\n         , author  :: String\n         , content :: String\n         , url     :: String\n         , date    :: String\n         , image   :: Maybe String\n         }\n    deriving (Generic, Eq, Ord, Show, FromJSON, ToJSON, Binary)\n\n-- | given a list of posts this will build a table of contents\nbuildIndex :: [Post] -\u003e Action ()\nbuildIndex posts' = do\n  indexT \u003c- compileTemplate' \"site/templates/index.html\"\n  let indexInfo = IndexInfo {posts = posts'}\n      indexHTML = T.unpack $ substitute indexT (toJSON indexInfo)\n  writeFile' (outputFolder \u003c/\u003e \"index.html\") indexHTML\n\n-- | Find and build all posts\nbuildPosts :: Action [Post]\nbuildPosts = do\n  pPaths \u003c- getDirectoryFiles \".\" [\"site/posts//*.md\"]\n  forP pPaths buildPost\n\n-- | Load a post, process metadata, write it to output, then return the post object\n-- Detects changes to either post content or template\nbuildPost :: FilePath -\u003e Action Post\nbuildPost srcPath = cacheAction (\"build\" :: T.Text, srcPath) $ do\n  liftIO . putStrLn $ \"Rebuilding post: \" \u003c\u003e srcPath\n  postContent \u003c- readFile' srcPath\n  -- load post content and metadata as JSON blob\n  postData \u003c- markdownToHTML . T.pack $ postContent\n  let postUrl = T.pack . dropDirectory1 $ srcPath -\u003c.\u003e \"html\"\n      withPostUrl = _Object . at \"url\" ?~ String postUrl\n  -- Add additional metadata we've been able to compute\n  let fullPostData = withPostUrl $ postData\n  template \u003c- compileTemplate' \"site/templates/post.html\"\n  writeFile' (outputFolder \u003c/\u003e T.unpack postUrl) . T.unpack $ substitute template fullPostData\n  -- Convert the metadata into a Post object\n  convert fullPostData\n\n-- | Copy all static files from the listed folders to their destination\ncopyStaticFiles :: Action ()\ncopyStaticFiles = do\n    filepaths \u003c- getDirectoryFiles \"./site/\" [\"images//*\", \"css//*\", \"js//*\"]\n    void $ forP filepaths $ \\filepath -\u003e\n        copyFileChanged (\"site\" \u003c/\u003e filepath) (outputFolder \u003c/\u003e filepath)\n\n-- | Specific build rules for the Shake system\n--   defines workflow to build the website\nbuildRules :: Action ()\nbuildRules = do\n  allPosts \u003c- buildPosts\n  buildIndex allPosts\n  copyStaticFiles\n\nmain :: IO ()\nmain = slick buildRules\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthsutton%2Fdemo-site","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fthsutton%2Fdemo-site","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthsutton%2Fdemo-site/lists"}