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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 K$0+X8(*FmmD03(P literal 0 HcmV?d00001