From 2bfa91eca1e21cd13f55d6c64bc00225fb4a1284 Mon Sep 17 00:00:00 2001 From: Cale Gibbard Date: Tue, 7 Feb 2023 13:16:26 -0500 Subject: [PATCH] Initial commit --- .gitignore | 16 +++++++ .obelisk/impl/default.nix | 2 + .obelisk/impl/github.json | 8 ++++ .obelisk/impl/thunk.nix | 12 +++++ README.md | 6 +++ backend/backend.cabal | 29 +++++++++++++ backend/frontend.jsexe | 1 + backend/frontendJs/frontend.jsexe | 1 + backend/src-bin/main.hs | 6 +++ backend/src/Backend.hs | 10 +++++ backend/static | 1 + cabal.project | 3 ++ common/common.cabal | 15 +++++++ common/src/Common/Api.hs | 4 ++ common/src/Common/Route.hs | 48 ++++++++++++++++++++ config/common/example | 1 + config/common/route | 1 + config/readme.md | 9 ++++ default.nix | 70 ++++++++++++++++++++++++++++++ dep/botan/default.nix | 2 + dep/botan/github.json | 8 ++++ dep/botan/thunk.nix | 12 +++++ dep/chkhs/default.nix | 2 + dep/chkhs/git.json | 8 ++++ dep/chkhs/thunk.nix | 17 ++++++++ dep/nix-thunk/default.nix | 2 + dep/nix-thunk/github.json | 7 +++ dep/nix-thunk/thunk.nix | 12 +++++ frontend/frontend.cabal | 36 +++++++++++++++ frontend/src-bin/main.hs | 10 +++++ frontend/src/Frontend.hs | 47 ++++++++++++++++++++ static/main.css | 3 ++ static/obelisk.jpg | Bin 0 -> 17351 bytes 33 files changed, 409 insertions(+) create mode 100644 .gitignore create mode 100644 .obelisk/impl/default.nix create mode 100644 .obelisk/impl/github.json create mode 100644 .obelisk/impl/thunk.nix create mode 100644 README.md create mode 100644 backend/backend.cabal create mode 120000 backend/frontend.jsexe create mode 120000 backend/frontendJs/frontend.jsexe create mode 100644 backend/src-bin/main.hs create mode 100644 backend/src/Backend.hs create mode 120000 backend/static create mode 100644 cabal.project create mode 100644 common/common.cabal create mode 100644 common/src/Common/Api.hs create mode 100644 common/src/Common/Route.hs create mode 100644 config/common/example create mode 100644 config/common/route create mode 100644 config/readme.md create mode 100644 default.nix create mode 100644 dep/botan/default.nix create mode 100644 dep/botan/github.json create mode 100644 dep/botan/thunk.nix create mode 100644 dep/chkhs/default.nix create mode 100644 dep/chkhs/git.json create mode 100644 dep/chkhs/thunk.nix create mode 100644 dep/nix-thunk/default.nix create mode 100644 dep/nix-thunk/github.json create mode 100644 dep/nix-thunk/thunk.nix create mode 100644 frontend/frontend.cabal create mode 100644 frontend/src-bin/main.hs create mode 100644 frontend/src/Frontend.hs create mode 100644 static/main.css create mode 100644 static/obelisk.jpg diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fa49107 --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +.attr-cache +.cabal-sandbox +*.hi +*.o +cabal.project.local +cabal.sandbox.config +ctags +dist-newstyle/ +dist/ +ghcid-output.txt +profile/ +result +result-* +tags +TAGS +static.out diff --git a/.obelisk/impl/default.nix b/.obelisk/impl/default.nix new file mode 100644 index 0000000..2b4d4ab --- /dev/null +++ b/.obelisk/impl/default.nix @@ -0,0 +1,2 @@ +# DO NOT HAND-EDIT THIS FILE +import (import ./thunk.nix) \ No newline at end of file diff --git a/.obelisk/impl/github.json b/.obelisk/impl/github.json new file mode 100644 index 0000000..d40cc9c --- /dev/null +++ b/.obelisk/impl/github.json @@ -0,0 +1,8 @@ +{ + "owner": "obsidiansystems", + "repo": "obelisk", + "branch": "cg/user-nixpkgs-overlays", + "private": false, + "rev": "c1c1e7e0aedefdf39497f4dda91cda76df1a05a9", + "sha256": "1xsp20j12sg6mg9wl640b709ksl3zs3bbkylm559gyvfbvll74p6" +} diff --git a/.obelisk/impl/thunk.nix b/.obelisk/impl/thunk.nix new file mode 100644 index 0000000..20f2d28 --- /dev/null +++ b/.obelisk/impl/thunk.nix @@ -0,0 +1,12 @@ +# DO NOT HAND-EDIT THIS FILE +let fetch = { private ? false, fetchSubmodules ? false, owner, repo, rev, sha256, ... }: + if !fetchSubmodules && !private then builtins.fetchTarball { + url = "https://github.com/${owner}/${repo}/archive/${rev}.tar.gz"; inherit sha256; + } else (import (builtins.fetchTarball { + url = "https://github.com/NixOS/nixpkgs/archive/3aad50c30c826430b0270fcf8264c8c41b005403.tar.gz"; + sha256 = "0xwqsf08sywd23x0xvw4c4ghq0l28w2ki22h0bdn766i16z9q2gr"; +}) {}).fetchFromGitHub { + inherit owner repo rev sha256 fetchSubmodules private; + }; + json = builtins.fromJSON (builtins.readFile ./github.json); +in fetch json \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..124beda --- /dev/null +++ b/README.md @@ -0,0 +1,6 @@ +# This repo + +This is an example showing how to override a nixpkgs package, as well as include a Haskell package dependency not on Hackage. + +See primarily the overrides in default.nix. + diff --git a/backend/backend.cabal b/backend/backend.cabal new file mode 100644 index 0000000..25f6903 --- /dev/null +++ b/backend/backend.cabal @@ -0,0 +1,29 @@ +name: backend +version: 0.1 +cabal-version: >= 1.8 +build-type: Simple + +library + hs-source-dirs: src + if impl(ghcjs) + buildable: False + build-depends: base + , common + , frontend + , obelisk-backend + , obelisk-route + exposed-modules: + Backend + ghc-options: -Wall -Wredundant-constraints -Wincomplete-uni-patterns -Wincomplete-record-updates -O -fno-show-valid-hole-fits + +executable backend + main-is: main.hs + hs-source-dirs: src-bin + ghc-options: -Wall -Wredundant-constraints -Wincomplete-uni-patterns -Wincomplete-record-updates -O -threaded -fno-show-valid-hole-fits + if impl(ghcjs) + buildable: False + build-depends: base + , backend + , common + , frontend + , obelisk-backend diff --git a/backend/frontend.jsexe b/backend/frontend.jsexe new file mode 120000 index 0000000..3a9cb6f --- /dev/null +++ b/backend/frontend.jsexe @@ -0,0 +1 @@ +../frontend-js/bin/frontend.jsexe \ No newline at end of file diff --git a/backend/frontendJs/frontend.jsexe b/backend/frontendJs/frontend.jsexe new file mode 120000 index 0000000..af9b8f4 --- /dev/null +++ b/backend/frontendJs/frontend.jsexe @@ -0,0 +1 @@ +../../frontend-js/bin/frontend.jsexe \ No newline at end of file diff --git a/backend/src-bin/main.hs b/backend/src-bin/main.hs new file mode 100644 index 0000000..00f1994 --- /dev/null +++ b/backend/src-bin/main.hs @@ -0,0 +1,6 @@ +import Backend +import Frontend +import Obelisk.Backend + +main :: IO () +main = runBackend backend frontend diff --git a/backend/src/Backend.hs b/backend/src/Backend.hs new file mode 100644 index 0000000..5842ce9 --- /dev/null +++ b/backend/src/Backend.hs @@ -0,0 +1,10 @@ +module Backend where + +import Common.Route +import Obelisk.Backend + +backend :: Backend BackendRoute FrontendRoute +backend = Backend + { _backend_run = \serve -> serve $ const $ return () + , _backend_routeEncoder = fullRouteEncoder + } diff --git a/backend/static b/backend/static new file mode 120000 index 0000000..4dab164 --- /dev/null +++ b/backend/static @@ -0,0 +1 @@ +../static \ No newline at end of file diff --git a/cabal.project b/cabal.project new file mode 100644 index 0000000..fe0438b --- /dev/null +++ b/cabal.project @@ -0,0 +1,3 @@ +optional-packages: + * +write-ghc-environment-files: never diff --git a/common/common.cabal b/common/common.cabal new file mode 100644 index 0000000..82b8991 --- /dev/null +++ b/common/common.cabal @@ -0,0 +1,15 @@ +name: common +version: 0.1 +cabal-version: >= 1.2 +build-type: Simple + +library + hs-source-dirs: src + build-depends: base + , obelisk-route + , mtl + , text + exposed-modules: + Common.Api + Common.Route + ghc-options: -Wall -Wredundant-constraints -Wincomplete-uni-patterns -Wincomplete-record-updates -O -fno-show-valid-hole-fits diff --git a/common/src/Common/Api.hs b/common/src/Common/Api.hs new file mode 100644 index 0000000..1ea3df0 --- /dev/null +++ b/common/src/Common/Api.hs @@ -0,0 +1,4 @@ +module Common.Api where + +commonStuff :: String +commonStuff = "Here is a string defined in Common.Api" diff --git a/common/src/Common/Route.hs b/common/src/Common/Route.hs new file mode 100644 index 0000000..17dadae --- /dev/null +++ b/common/src/Common/Route.hs @@ -0,0 +1,48 @@ +{-# LANGUAGE ConstraintKinds #-} +{-# LANGUAGE EmptyCase #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE GADTs #-} +{-# LANGUAGE KindSignatures #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE TypeFamilies #-} +module Common.Route where + +{- -- You will probably want these imports for composing Encoders. +import Prelude hiding (id, (.)) +import Control.Category +-} + +import Data.Text (Text) +import Data.Functor.Identity + +import Obelisk.Route +import Obelisk.Route.TH + +data BackendRoute :: * -> * where + -- | Used to handle unparseable routes. + BackendRoute_Missing :: BackendRoute () + -- You can define any routes that will be handled specially by the backend here. + -- i.e. These do not serve the frontend, but do something different, such as serving static files. + +data FrontendRoute :: * -> * where + FrontendRoute_Main :: FrontendRoute () + -- This type is used to define frontend routes, i.e. ones for which the backend will serve the frontend. + +fullRouteEncoder + :: Encoder (Either Text) Identity (R (FullRoute BackendRoute FrontendRoute)) PageName +fullRouteEncoder = mkFullRouteEncoder + (FullRoute_Backend BackendRoute_Missing :/ ()) + (\case + BackendRoute_Missing -> PathSegment "missing" $ unitEncoder mempty) + (\case + FrontendRoute_Main -> PathEnd $ unitEncoder mempty) + +concat <$> mapM deriveRouteComponent + [ ''BackendRoute + , ''FrontendRoute + ] diff --git a/config/common/example b/config/common/example new file mode 100644 index 0000000..e368f70 --- /dev/null +++ b/config/common/example @@ -0,0 +1 @@ +This string comes from config/common/example \ No newline at end of file diff --git a/config/common/route b/config/common/route new file mode 100644 index 0000000..99298a3 --- /dev/null +++ b/config/common/route @@ -0,0 +1 @@ +http://localhost:8000 \ No newline at end of file diff --git a/config/readme.md b/config/readme.md new file mode 100644 index 0000000..7ca5a54 --- /dev/null +++ b/config/readme.md @@ -0,0 +1,9 @@ +### Config + +Obelisk projects should contain a config folder with the following subfolders: common, frontend, and backend. + +Things that should never be transmitted to the frontend belong in backend/ (e.g., email credentials) + +Frontend-only configuration belongs in frontend/. + +Shared configuration files (e.g., the route config) belong in common/ diff --git a/default.nix b/default.nix new file mode 100644 index 0000000..fd923d6 --- /dev/null +++ b/default.nix @@ -0,0 +1,70 @@ +{ system ? builtins.currentSystem +, nix-thunk ? import ./dep/nix-thunk {} +, obelisk ? import ./.obelisk/impl { + inherit system; + iosSdkVersion = "13.2"; + # This argument (which is new, but was easy to add to Obelisk, thankfully), allows one to specify nixpkgs overlays. + # We'll use it to ensure we have the appropriate version of botan. + nixpkgsOverlays = + [ + (self: super: rec { + botan2 = + (super.botan2.overrideAttrs (old: { + # Get rid of some patches that nixpkgs was applying to botan2 and which didn't apply to the branch. + patches = []; + # The --with-openssl flag didn't work for some reason, might need further figuring out. + configurePhase = builtins.replaceStrings [ "--with-openssl" ] [ " " ] old.configurePhase; + # Here, we use nix-thunk to get the source of the correct branch of the botan repo via a thunk. + # Nix thunks are essentially references to git repositories that can be unpacked to their + # source in-place when working on the project, or packed up into a few small files. + # After installing the nix-thunk command from https://github.com/obsidiansystems/nix-thunk + # you can run: + # nix-thunk unpack dep/botan + # from the top level of this repo to clone a copy of the botan git repo at the appropriate + # commit, and work on it from there. Similarly, + # nix-thunk pack dep/botan + # will pack it back up again, provided that the changes have been pushed somewhere. + # Note: there's a bug in the current version of Obelisk which occasionally gives it some trouble + # if certain repos are unpacked. If you have any trouble running an ob command + # (ob run, ob repl, etc.) with a thunk unpacked, try adding the flag --no-interpret dep + # and hopefully that will sort it out. + src = nix-thunk.thunkSource ./dep/botan; + })).override (old: { + # Also, turn on debugging. + extraConfigureFlags = "--debug-mode"; + }); + # For whatever reason, it seems callCabal2nix below wants to use the botan package rather than botan2. + # We could override the pkgconfigDepends of the resulting package, but this is easier. + botan = self.botan2; + }) + ]; + + # You must accept the Android Software Development Kit License Agreement at + # https://developer.android.com/studio/terms in order to build Android apps. + # Uncomment and set this to `true` to indicate your acceptance: + # config.android_sdk.accept_license = false; + + # In order to use Let's Encrypt for HTTPS deployments you must accept + # their terms of service at https://letsencrypt.org/repository/. + # Uncomment and set this to `true` to indicate your acceptance: + # terms.security.acme.acceptTerms = false; + } +}: +with obelisk; +project ./. ({ pkgs, hackGet, ... }: { + android.applicationId = "systems.obsidian.obelisk.examples.minimal"; + android.displayName = "Obelisk Minimal Example"; + ios.bundleIdentifier = "systems.obsidian.obelisk.examples.minimal"; + ios.bundleName = "Obelisk Minimal Example"; + overrides = self: super: + with pkgs.haskell.lib; { + # Here, we get the tahoe-chk package from the chk.hs thunk, and use callCabal2nix to get a nix derivation to build it. + tahoe-chk = self.callCabal2nix "tahoe-chk" (nix-thunk.thunkSource ./dep/chkhs) {}; + # We also ended up needing an override of the base32 library, which we obtain from Hackage. + base32 = self.callHackageDirect { + pkg = "base32"; + ver = "0.2.2.0"; + sha256 = "1qx7n2jyb9h1082434r90hfrjw5fab2j1yg0qzxh856fpksbh811"; + }; + }; +}) diff --git a/dep/botan/default.nix b/dep/botan/default.nix new file mode 100644 index 0000000..2b4d4ab --- /dev/null +++ b/dep/botan/default.nix @@ -0,0 +1,2 @@ +# DO NOT HAND-EDIT THIS FILE +import (import ./thunk.nix) \ No newline at end of file diff --git a/dep/botan/github.json b/dep/botan/github.json new file mode 100644 index 0000000..2b9bc13 --- /dev/null +++ b/dep/botan/github.json @@ -0,0 +1,8 @@ +{ + "owner": "PrivateStorageio", + "repo": "botan", + "branch": "3190.ffi-zfec", + "private": false, + "rev": "f9e20aae412f5b482334181885489a15bb8a4fd8", + "sha256": "0qcz9cxh0lymj09166wf9mxv9q2bhr3sllh71380dhq90ikaib7j" +} diff --git a/dep/botan/thunk.nix b/dep/botan/thunk.nix new file mode 100644 index 0000000..20f2d28 --- /dev/null +++ b/dep/botan/thunk.nix @@ -0,0 +1,12 @@ +# DO NOT HAND-EDIT THIS FILE +let fetch = { private ? false, fetchSubmodules ? false, owner, repo, rev, sha256, ... }: + if !fetchSubmodules && !private then builtins.fetchTarball { + url = "https://github.com/${owner}/${repo}/archive/${rev}.tar.gz"; inherit sha256; + } else (import (builtins.fetchTarball { + url = "https://github.com/NixOS/nixpkgs/archive/3aad50c30c826430b0270fcf8264c8c41b005403.tar.gz"; + sha256 = "0xwqsf08sywd23x0xvw4c4ghq0l28w2ki22h0bdn766i16z9q2gr"; +}) {}).fetchFromGitHub { + inherit owner repo rev sha256 fetchSubmodules private; + }; + json = builtins.fromJSON (builtins.readFile ./github.json); +in fetch json \ No newline at end of file diff --git a/dep/chkhs/default.nix b/dep/chkhs/default.nix new file mode 100644 index 0000000..2b4d4ab --- /dev/null +++ b/dep/chkhs/default.nix @@ -0,0 +1,2 @@ +# DO NOT HAND-EDIT THIS FILE +import (import ./thunk.nix) \ No newline at end of file diff --git a/dep/chkhs/git.json b/dep/chkhs/git.json new file mode 100644 index 0000000..5e6537c --- /dev/null +++ b/dep/chkhs/git.json @@ -0,0 +1,8 @@ +{ + "url": "https://gitlab.com/exarkun/chk.hs.git", + "rev": "46bdc9a1c235a26ab1cabdad7eceee36dccf4cdd", + "sha256": "0yykb9vyv0wci65nzq0aw0slw5njrzrbkrchpypd9vskvh7p8im0", + "private": false, + "fetchSubmodules": false, + "branch": "master" +} diff --git a/dep/chkhs/thunk.nix b/dep/chkhs/thunk.nix new file mode 100644 index 0000000..3e23b43 --- /dev/null +++ b/dep/chkhs/thunk.nix @@ -0,0 +1,17 @@ +# DO NOT HAND-EDIT THIS FILE +let fetch = {url, rev, branch ? null, sha256 ? null, fetchSubmodules ? false, private ? false, ...}: + let realUrl = let firstChar = builtins.substring 0 1 url; in + if firstChar == "/" then /. + url + else if firstChar == "." then ./. + url + else url; + in if !fetchSubmodules && private then builtins.fetchGit { + url = realUrl; inherit rev; + ${if branch == null then null else "ref"} = branch; + } else (import (builtins.fetchTarball { + url = "https://github.com/NixOS/nixpkgs/archive/3aad50c30c826430b0270fcf8264c8c41b005403.tar.gz"; + sha256 = "0xwqsf08sywd23x0xvw4c4ghq0l28w2ki22h0bdn766i16z9q2gr"; +}) {}).fetchgit { + url = realUrl; inherit rev sha256; + }; + json = builtins.fromJSON (builtins.readFile ./git.json); +in fetch json \ No newline at end of file diff --git a/dep/nix-thunk/default.nix b/dep/nix-thunk/default.nix new file mode 100644 index 0000000..2b4d4ab --- /dev/null +++ b/dep/nix-thunk/default.nix @@ -0,0 +1,2 @@ +# DO NOT HAND-EDIT THIS FILE +import (import ./thunk.nix) \ No newline at end of file diff --git a/dep/nix-thunk/github.json b/dep/nix-thunk/github.json new file mode 100644 index 0000000..42dcedf --- /dev/null +++ b/dep/nix-thunk/github.json @@ -0,0 +1,7 @@ +{ + "owner": "obsidiansystems", + "repo": "nix-thunk", + "private": false, + "rev": "0982911d78fbd9932c3ed8104a930f313f49b69b", + "sha256": "130n8mzywbvyqd6aap6yk98zynq3r3kpj2hfzdym26plwz0rr3bj" +} diff --git a/dep/nix-thunk/thunk.nix b/dep/nix-thunk/thunk.nix new file mode 100644 index 0000000..20f2d28 --- /dev/null +++ b/dep/nix-thunk/thunk.nix @@ -0,0 +1,12 @@ +# DO NOT HAND-EDIT THIS FILE +let fetch = { private ? false, fetchSubmodules ? false, owner, repo, rev, sha256, ... }: + if !fetchSubmodules && !private then builtins.fetchTarball { + url = "https://github.com/${owner}/${repo}/archive/${rev}.tar.gz"; inherit sha256; + } else (import (builtins.fetchTarball { + url = "https://github.com/NixOS/nixpkgs/archive/3aad50c30c826430b0270fcf8264c8c41b005403.tar.gz"; + sha256 = "0xwqsf08sywd23x0xvw4c4ghq0l28w2ki22h0bdn766i16z9q2gr"; +}) {}).fetchFromGitHub { + inherit owner repo rev sha256 fetchSubmodules private; + }; + json = builtins.fromJSON (builtins.readFile ./github.json); +in fetch json \ No newline at end of file diff --git a/frontend/frontend.cabal b/frontend/frontend.cabal new file mode 100644 index 0000000..ba6e5e0 --- /dev/null +++ b/frontend/frontend.cabal @@ -0,0 +1,36 @@ +name: frontend +version: 0.1 +cabal-version: >= 1.8 +build-type: Simple + +library + hs-source-dirs: src + build-depends: base + , common + , obelisk-frontend + , obelisk-route + , jsaddle + , reflex-dom-core + , obelisk-executable-config-lookup + , obelisk-generated-static + , text + exposed-modules: + Frontend + ghc-options: -Wall -Wredundant-constraints -Wincomplete-uni-patterns -Wincomplete-record-updates -O -fno-show-valid-hole-fits + +executable frontend + main-is: main.hs + hs-source-dirs: src-bin + build-depends: base + , common + , obelisk-frontend + , obelisk-route + , reflex-dom + , obelisk-generated-static + , frontend + ghc-options: -threaded -O -Wall -Wredundant-constraints -Wincomplete-uni-patterns -Wincomplete-record-updates -fno-show-valid-hole-fits + if impl(ghcjs) + ghc-options: -dedupe + cpp-options: -DGHCJS_BROWSER + if os(darwin) + ghc-options: -dynamic diff --git a/frontend/src-bin/main.hs b/frontend/src-bin/main.hs new file mode 100644 index 0000000..9408dc3 --- /dev/null +++ b/frontend/src-bin/main.hs @@ -0,0 +1,10 @@ +import Frontend +import Common.Route +import Obelisk.Frontend +import Obelisk.Route.Frontend +import Reflex.Dom + +main :: IO () +main = do + let Right validFullEncoder = checkEncoder fullRouteEncoder + run $ runFrontend validFullEncoder frontend diff --git a/frontend/src/Frontend.hs b/frontend/src/Frontend.hs new file mode 100644 index 0000000..b545378 --- /dev/null +++ b/frontend/src/Frontend.hs @@ -0,0 +1,47 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE TemplateHaskell #-} + +module Frontend where + +import Control.Monad +import qualified Data.Text as T +import qualified Data.Text.Encoding as T +import Language.Javascript.JSaddle (eval, liftJSM) + +import Obelisk.Frontend +import Obelisk.Configs +import Obelisk.Route +import Obelisk.Generated.Static + +import Reflex.Dom.Core + +import Common.Api +import Common.Route + + +-- This runs in a monad that can be run on the client or the server. +-- To run code in a pure client or pure server context, use one of the +-- `prerender` functions. +frontend :: Frontend (R FrontendRoute) +frontend = Frontend + { _frontend_head = do + el "title" $ text "Obelisk Minimal Example" + elAttr "link" ("href" =: $(static "main.css") <> "type" =: "text/css" <> "rel" =: "stylesheet") blank + , _frontend_body = do + el "h1" $ text "Welcome to Obelisk!" + el "p" $ text $ T.pack commonStuff + + -- `prerender` and `prerender_` let you choose a widget to run on the server + -- during prerendering and a different widget to run on the client with + -- JavaScript. The following will generate a `blank` widget on the server and + -- print "Hello, World!" on the client. + prerender_ blank $ liftJSM $ void $ eval ("console.log('Hello, World!')" :: T.Text) + + elAttr "img" ("src" =: $(static "obelisk.jpg")) blank + el "div" $ do + exampleConfig <- getConfig "common/example" + case exampleConfig of + Nothing -> text "No config file found in config/common/example" + Just s -> text $ T.decodeUtf8 s + return () + } diff --git a/static/main.css b/static/main.css new file mode 100644 index 0000000..57c2d53 --- /dev/null +++ b/static/main.css @@ -0,0 +1,3 @@ +p { + color: red; +} \ No newline at end of file diff --git a/static/obelisk.jpg b/static/obelisk.jpg new file mode 100644 index 0000000000000000000000000000000000000000..68c8682d0809b5265fef807bdb8d46c9829e55ed GIT binary patch literal 17351 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 H{{J@tt0;3Q literal 0 HcmV?d00001