├── .editorconfig ├── .gitignore ├── README.md ├── bs-config.js ├── dist ├── index.html ├── index.html.gz ├── main.css ├── main.css.gz ├── main.js ├── main.js.gz ├── robots.txt └── service-worker.js ├── elm-package.json ├── html-minifier.json ├── package.json ├── postcss.json └── src ├── elm ├── Main.elm ├── Pages │ ├── About.elm │ └── Home.elm ├── Routes.elm └── Sample.elm ├── index.jade ├── robots.txt └── sass └── main.sass /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.{json,js,elm,scss,sass,jade}] 2 | charset = utf-8 3 | indent_style = spaces 4 | indent_size = 4 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | elm-stuff 4 | .DS_Store 5 | npm-debug.log 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # elm-skeleton 2 | 3 | A simple skeleton for getting started building Elm web apps. 4 | 5 | ## Features 6 | 7 | - Automatic compilation of Jade, SASS, and Elm files 8 | - Automatic compression for HTML, CSS, and Javascript files 9 | - Automatic gzip compression with `zopfli` for all files 10 | - Automatic offline caching using Service Workers and `sw-precache` 11 | - BrowserSync to see changes immediately 12 | - Easy deployment to GitHub pages with `npm run gh-pages` 13 | - EditorConfig config for style consistency 14 | 15 | ## Getting started 16 | 17 | ```shell 18 | git clone https://github.com/liamcurry/elm-skeleton.git 19 | cd elm-skeleton 20 | elm package install -y 21 | npm install 22 | npm start 23 | ``` 24 | 25 | ## TODO 26 | 27 | - Compress images 28 | - Spritesheets? 29 | - npm script for S3 deployment 30 | -------------------------------------------------------------------------------- /bs-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | files: ['dist/*.(html|css|js)'], 3 | server: { 4 | baseDir: 'dist', 5 | middleware: [ 6 | require('connect-history-api-fallback')() 7 | ] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /dist/index.html: -------------------------------------------------------------------------------- 1 | Hello World -------------------------------------------------------------------------------- /dist/index.html.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liamcurry/elm-skeleton/2f3f392a532959e2b10a2b6ba3183abbebb04181/dist/index.html.gz -------------------------------------------------------------------------------- /dist/main.css: -------------------------------------------------------------------------------- 1 | body{font-family:Helvetica,Arial,sans-serif} -------------------------------------------------------------------------------- /dist/main.css.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liamcurry/elm-skeleton/2f3f392a532959e2b10a2b6ba3183abbebb04181/dist/main.css.gz -------------------------------------------------------------------------------- /dist/main.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liamcurry/elm-skeleton/2f3f392a532959e2b10a2b6ba3183abbebb04181/dist/main.js.gz -------------------------------------------------------------------------------- /dist/robots.txt: -------------------------------------------------------------------------------- 1 | # www.robotstxt.org/ 2 | # 3 | # Allow crawling of all content 4 | User-agent: * 5 | Disallow: 6 | -------------------------------------------------------------------------------- /dist/service-worker.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 Google Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // This generated service worker JavaScript will precache your site's resources. 18 | // The code needs to be saved in a .js file at the top-level of your site, and registered 19 | // from your pages in order to be used. See 20 | // https://github.com/googlechrome/sw-precache/blob/master/demo/app/js/service-worker-registration.js 21 | // for an example of how you can register this script and handle various service worker events. 22 | 23 | /* eslint-env worker, serviceworker */ 24 | /* eslint-disable indent, no-unused-vars, no-multiple-empty-lines, max-nested-callbacks, space-before-function-paren */ 25 | 'use strict'; 26 | 27 | 28 | 29 | /* eslint-disable quotes, comma-spacing */ 30 | var PrecacheConfig = [["index.html","f13d0796802940396a038b3238c26ade"],["index_conflict-20160120-120728.html","3a2ad70f85816692500df3eb3b076473"],["main.css","3784f937272fe166b1bb44d18fa44648"],["main.js","fce92ca5910ca9a41ff2de0beb0f6cb1"],["robots.txt","4f3bab8dfdc0cb924c86221dbda7c74d"],["service-worker_conflict-20160120-120806.js","1b61ee19f459d4836ef3b3bc24b06d3b"],["service-worker_conflict-20160120-123831.js","10619507ff8084b51547fe4e90cda246"]]; 31 | /* eslint-enable quotes, comma-spacing */ 32 | var CacheNamePrefix = 'sw-precache-v1-sw-precache-' + (self.registration ? self.registration.scope : '') + '-'; 33 | 34 | 35 | var IgnoreUrlParametersMatching = [/^utm_/]; 36 | 37 | 38 | 39 | var addDirectoryIndex = function (originalUrl, index) { 40 | var url = new URL(originalUrl); 41 | if (url.pathname.slice(-1) === '/') { 42 | url.pathname += index; 43 | } 44 | return url.toString(); 45 | }; 46 | 47 | var getCacheBustedUrl = function (url, now) { 48 | now = now || Date.now(); 49 | 50 | var urlWithCacheBusting = new URL(url); 51 | urlWithCacheBusting.search += (urlWithCacheBusting.search ? '&' : '') + 'sw-precache=' + now; 52 | 53 | return urlWithCacheBusting.toString(); 54 | }; 55 | 56 | var populateCurrentCacheNames = function (precacheConfig, 57 | cacheNamePrefix, baseUrl) { 58 | var absoluteUrlToCacheName = {}; 59 | var currentCacheNamesToAbsoluteUrl = {}; 60 | 61 | precacheConfig.forEach(function(cacheOption) { 62 | var absoluteUrl = new URL(cacheOption[0], baseUrl).toString(); 63 | var cacheName = cacheNamePrefix + absoluteUrl + '-' + cacheOption[1]; 64 | currentCacheNamesToAbsoluteUrl[cacheName] = absoluteUrl; 65 | absoluteUrlToCacheName[absoluteUrl] = cacheName; 66 | }); 67 | 68 | return { 69 | absoluteUrlToCacheName: absoluteUrlToCacheName, 70 | currentCacheNamesToAbsoluteUrl: currentCacheNamesToAbsoluteUrl 71 | }; 72 | }; 73 | 74 | var stripIgnoredUrlParameters = function (originalUrl, 75 | ignoreUrlParametersMatching) { 76 | var url = new URL(originalUrl); 77 | 78 | url.search = url.search.slice(1) // Exclude initial '?' 79 | .split('&') // Split into an array of 'key=value' strings 80 | .map(function(kv) { 81 | return kv.split('='); // Split each 'key=value' string into a [key, value] array 82 | }) 83 | .filter(function(kv) { 84 | return ignoreUrlParametersMatching.every(function(ignoredRegex) { 85 | return !ignoredRegex.test(kv[0]); // Return true iff the key doesn't match any of the regexes. 86 | }); 87 | }) 88 | .map(function(kv) { 89 | return kv.join('='); // Join each [key, value] array into a 'key=value' string 90 | }) 91 | .join('&'); // Join the array of 'key=value' strings into a string with '&' in between each 92 | 93 | return url.toString(); 94 | }; 95 | 96 | 97 | var mappings = populateCurrentCacheNames(PrecacheConfig, CacheNamePrefix, self.location); 98 | var AbsoluteUrlToCacheName = mappings.absoluteUrlToCacheName; 99 | var CurrentCacheNamesToAbsoluteUrl = mappings.currentCacheNamesToAbsoluteUrl; 100 | 101 | function deleteAllCaches() { 102 | return caches.keys().then(function(cacheNames) { 103 | return Promise.all( 104 | cacheNames.map(function(cacheName) { 105 | return caches.delete(cacheName); 106 | }) 107 | ); 108 | }); 109 | } 110 | 111 | self.addEventListener('install', function(event) { 112 | var now = Date.now(); 113 | 114 | event.waitUntil( 115 | caches.keys().then(function(allCacheNames) { 116 | return Promise.all( 117 | Object.keys(CurrentCacheNamesToAbsoluteUrl).filter(function(cacheName) { 118 | return allCacheNames.indexOf(cacheName) === -1; 119 | }).map(function(cacheName) { 120 | var urlWithCacheBusting = getCacheBustedUrl(CurrentCacheNamesToAbsoluteUrl[cacheName], 121 | now); 122 | 123 | return caches.open(cacheName).then(function(cache) { 124 | var request = new Request(urlWithCacheBusting, {credentials: 'same-origin'}); 125 | return fetch(request).then(function(response) { 126 | if (response.ok) { 127 | return cache.put(CurrentCacheNamesToAbsoluteUrl[cacheName], response); 128 | } 129 | 130 | console.error('Request for %s returned a response with status %d, so not attempting to cache it.', 131 | urlWithCacheBusting, response.status); 132 | // Get rid of the empty cache if we can't add a successful response to it. 133 | return caches.delete(cacheName); 134 | }); 135 | }); 136 | }) 137 | ).then(function() { 138 | return Promise.all( 139 | allCacheNames.filter(function(cacheName) { 140 | return cacheName.indexOf(CacheNamePrefix) === 0 && 141 | !(cacheName in CurrentCacheNamesToAbsoluteUrl); 142 | }).map(function(cacheName) { 143 | return caches.delete(cacheName); 144 | }) 145 | ); 146 | }); 147 | }).then(function() { 148 | if (typeof self.skipWaiting === 'function') { 149 | // Force the SW to transition from installing -> active state 150 | self.skipWaiting(); 151 | } 152 | }) 153 | ); 154 | }); 155 | 156 | if (self.clients && (typeof self.clients.claim === 'function')) { 157 | self.addEventListener('activate', function(event) { 158 | event.waitUntil(self.clients.claim()); 159 | }); 160 | } 161 | 162 | self.addEventListener('message', function(event) { 163 | if (event.data.command === 'delete_all') { 164 | console.log('About to delete all caches...'); 165 | deleteAllCaches().then(function() { 166 | console.log('Caches deleted.'); 167 | event.ports[0].postMessage({ 168 | error: null 169 | }); 170 | }).catch(function(error) { 171 | console.log('Caches not deleted:', error); 172 | event.ports[0].postMessage({ 173 | error: error 174 | }); 175 | }); 176 | } 177 | }); 178 | 179 | 180 | self.addEventListener('fetch', function(event) { 181 | if (event.request.method === 'GET') { 182 | var urlWithoutIgnoredParameters = stripIgnoredUrlParameters(event.request.url, 183 | IgnoreUrlParametersMatching); 184 | 185 | var cacheName = AbsoluteUrlToCacheName[urlWithoutIgnoredParameters]; 186 | var directoryIndex = 'index.html'; 187 | if (!cacheName && directoryIndex) { 188 | urlWithoutIgnoredParameters = addDirectoryIndex(urlWithoutIgnoredParameters, directoryIndex); 189 | cacheName = AbsoluteUrlToCacheName[urlWithoutIgnoredParameters]; 190 | } 191 | 192 | var navigateFallback = ''; 193 | // Ideally, this would check for event.request.mode === 'navigate', but that is not widely 194 | // supported yet: 195 | // https://code.google.com/p/chromium/issues/detail?id=540967 196 | // https://bugzilla.mozilla.org/show_bug.cgi?id=1209081 197 | if (!cacheName && navigateFallback && event.request.headers.has('accept') && 198 | event.request.headers.get('accept').includes('text/html')) { 199 | var navigateFallbackUrl = new URL(navigateFallback, self.location); 200 | cacheName = AbsoluteUrlToCacheName[navigateFallbackUrl.toString()]; 201 | } 202 | 203 | if (cacheName) { 204 | event.respondWith( 205 | // Rely on the fact that each cache we manage should only have one entry, and return that. 206 | caches.open(cacheName).then(function(cache) { 207 | return cache.keys().then(function(keys) { 208 | return cache.match(keys[0]).then(function(response) { 209 | if (response) { 210 | return response; 211 | } 212 | // If for some reason the response was deleted from the cache, 213 | // raise and exception and fall back to the fetch() triggered in the catch(). 214 | throw Error('The cache ' + cacheName + ' is empty.'); 215 | }); 216 | }); 217 | }).catch(function(e) { 218 | console.warn('Couldn\'t serve response for "%s" from cache: %O', event.request.url, e); 219 | return fetch(event.request); 220 | }) 221 | ); 222 | } 223 | } 224 | }); 225 | 226 | -------------------------------------------------------------------------------- /elm-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0", 3 | "summary": "helpful summary of your project, less than 80 characters", 4 | "repository": "https://github.com/user/project.git", 5 | "license": "BSD3", 6 | "source-directories": [ 7 | "src/elm/" 8 | ], 9 | "exposed-modules": [], 10 | "dependencies": { 11 | "elm-lang/core": "3.0.0 <= v < 4.0.0", 12 | "etaque/elm-route-parser": "2.1.0 <= v < 3.0.0", 13 | "etaque/elm-transit-router": "1.0.1 <= v < 2.0.0", 14 | "etaque/elm-transit-style": "1.0.1 <= v < 2.0.0", 15 | "evancz/elm-effects": "2.0.1 <= v < 3.0.0", 16 | "evancz/elm-html": "4.0.2 <= v < 5.0.0", 17 | "evancz/elm-http": "3.0.0 <= v < 4.0.0", 18 | "evancz/elm-markdown": "2.0.0 <= v < 3.0.0", 19 | "evancz/start-app": "2.0.2 <= v < 3.0.0", 20 | "evancz/task-tutorial": "1.0.3 <= v < 2.0.0" 21 | }, 22 | "elm-version": "0.16.0 <= v < 0.17.0" 23 | } -------------------------------------------------------------------------------- /html-minifier.json: -------------------------------------------------------------------------------- 1 | { 2 | "removeComments": true, 3 | "removeCommentsFromCDATA": false, 4 | "removeCDATASectionsFromCDATA": false, 5 | "collapseWhitespace": true, 6 | "conservativeCollapse": false, 7 | "collapseBooleanAttributes": true, 8 | "removeAttributeQuotes": true, 9 | "removeRedundantAttributes": true, 10 | "useShortDoctype": false, 11 | "removeEmptyAttributes": false, 12 | "removeOptionalTags": true, 13 | "removeEmptyElements": false, 14 | "removeScriptTypeAttributes": true, 15 | "removeStyleLinkTypeAttributes": true, 16 | "lint": false, 17 | "keepClosingSlash": false, 18 | "caseSensitive": false, 19 | "minifyJS": true, 20 | "minifyCSS": true, 21 | "ignoreCustomComments": [], 22 | "processScripts": [] 23 | } 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "elm-skeleton", 3 | "version": "1.0.0", 4 | "description": "A basic skeleton for getting started building Elm web apps", 5 | "main": "elm.js", 6 | "scripts": { 7 | "clean": "rm -rf dist/* build/*", 8 | "build-js": "elm-make --output build/main.js --warn src/elm/Main.elm && uglifyjs build/main.js > dist/main.js", 9 | "postbuild-js": "zopfli dist/main.js", 10 | "build-css": "node-sass --include-path node_modules src/sass/main.sass | postcss -c postcss.json > dist/main.css", 11 | "postbuild-css": "zopfli dist/main.css", 12 | "build-html": "jade < src/index.jade > dist/index.html", 13 | "postbuild-html": "zopfli dist/index.html", 14 | "build-cache": "sw-precache --root=dist --static-file-globs='dist/**/!(service-worker).@(css|js|html|txt)'", 15 | "build-copy": "cp src/robots.txt dist", 16 | "prebuild": "npm run clean", 17 | "build": "npm run build-js && npm run build-css && npm run build-html && npm run build-cache && npm run build-copy", 18 | "watch-js": "nodemon -w src/elm/ -e elm --exec 'npm run build-js && npm run build-cache'", 19 | "watch-css": "nodemon -w src/sass/ -e sass --exec 'npm run build-css && npm run build-cache'", 20 | "watch-html": "nodemon -w src/ -e jade --exec 'npm run build-html && npm run build-cache'", 21 | "watch": "npm run build-copy && parallelshell 'npm run watch-js' 'npm run watch-css' 'npm run watch-html'", 22 | "serve": "browser-sync start --config=bs-config.js", 23 | "start": "parallelshell 'npm run watch' 'npm run serve'", 24 | "gh-pages": "git subtree push --prefix dist origin gh-pages" 25 | }, 26 | "author": { 27 | "name": "Liam Curry", 28 | "url": "http://liamcurry.com" 29 | }, 30 | "license": "ISC", 31 | "devDependencies": { 32 | "autoprefixer": "^6.3.1", 33 | "browser-sync": "^2.11.0", 34 | "connect-history-api-fallback": "^1.1.0", 35 | "cssnano": "^3.4.0", 36 | "html-minifier": "^1.0.0", 37 | "jade": "^1.11.0", 38 | "node-sass": "^3.4.2", 39 | "node-zopfli": "^1.4.0", 40 | "nodemon": "^1.8.1", 41 | "parallelshell": "^2.0.0", 42 | "postcss-cli": "^2.4.0", 43 | "sw-precache": "^2.3.0", 44 | "uglify-js": "^2.6.1" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /postcss.json: -------------------------------------------------------------------------------- 1 | { 2 | "use": ["autoprefixer", "cssnano"], 3 | "autoprefixer": { 4 | "browsers": "> 5%" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/elm/Main.elm: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | import Pages.Home as Home 4 | import Pages.About as About 5 | import Routes exposing (..) 6 | import Html exposing (..) 7 | import Html.Attributes exposing (..) 8 | import Task exposing (..) 9 | import Effects exposing (Effects, Never) 10 | import Signal exposing (message) 11 | import StartApp 12 | import TransitRouter exposing (WithRoute, getTransition) 13 | import TransitStyle 14 | 15 | 16 | type alias Model = WithRoute Route 17 | { homeModel : Home.Model 18 | , aboutModel : About.Model 19 | } 20 | 21 | 22 | type Action 23 | = NoOp 24 | | HomeAction Home.Action 25 | | AboutAction About.Action 26 | | RouterAction (TransitRouter.Action Route) 27 | 28 | 29 | initialModel : Model 30 | initialModel = 31 | { transitRouter = TransitRouter.empty EmptyRoute 32 | , homeModel = Home.init 33 | , aboutModel = About.init 34 | } 35 | 36 | 37 | actions : Signal Action 38 | actions = 39 | Signal.map RouterAction TransitRouter.actions 40 | 41 | 42 | mountRoute : Route -> Route -> Model -> (Model, Effects Action) 43 | mountRoute prevRoute route model = 44 | case route of 45 | Home -> 46 | (model, Effects.none) 47 | About -> 48 | (model, Effects.none) 49 | EmptyRoute -> 50 | (model, Effects.none) 51 | 52 | 53 | routerConfig : TransitRouter.Config Route Action Model 54 | routerConfig = 55 | { mountRoute = mountRoute 56 | , getDurations = \_ _ _ -> (50, 200) 57 | , actionWrapper = RouterAction 58 | , routeDecoder = decode 59 | } 60 | 61 | 62 | init : String -> (Model, Effects Action) 63 | init path = 64 | TransitRouter.init routerConfig path initialModel 65 | 66 | 67 | update : Action -> Model -> (Model, Effects Action) 68 | update action model = 69 | case action of 70 | NoOp -> 71 | (model, Effects.none) 72 | 73 | HomeAction homeAction -> 74 | let (model', effects) = Home.update homeAction model.homeModel 75 | in ( { model | homeModel = model' } 76 | , Effects.map HomeAction effects ) 77 | 78 | AboutAction aboutAction -> 79 | let (model', effects) = About.update aboutAction model.aboutModel 80 | in ( { model | aboutModel = model' } 81 | , Effects.map AboutAction effects ) 82 | 83 | RouterAction routeAction -> 84 | TransitRouter.update routerConfig routeAction model 85 | 86 | 87 | -- Main view/layout functions 88 | 89 | menu : Signal.Address Action -> Model -> Html 90 | menu address model = 91 | header 92 | [ class "navbar navbar-default" ] 93 | [ div 94 | [ class "container" ] 95 | [ div 96 | [ class "navbar-header" ] 97 | [ div 98 | [ class "navbar-brand" ] 99 | [ a (linkAttrs Home) [ text "Home" ] 100 | ] 101 | ] 102 | , ul 103 | [ class "nav navbar-nav" ] 104 | [ li [] [ a (linkAttrs About) [ text "About" ] ] ] 105 | ] 106 | ] 107 | 108 | 109 | contentView : Signal.Address Action -> Model -> Html 110 | contentView address model = 111 | case (TransitRouter.getRoute model) of 112 | Home -> 113 | Home.view (Signal.forwardTo address HomeAction) model.homeModel 114 | 115 | About -> 116 | About.view (Signal.forwardTo address AboutAction) model.aboutModel 117 | 118 | EmptyRoute -> 119 | text "Empty WHAT ?" 120 | 121 | 122 | view : Signal.Address Action -> Model -> Html 123 | view address model = 124 | div 125 | [ class "container-fluid" ] 126 | [ menu address model 127 | , div 128 | [ class "content" 129 | , style (TransitStyle.fadeSlideLeft 100 (getTransition model)) 130 | ] 131 | [ contentView address model ] 132 | ] 133 | 134 | 135 | -- wiring up start app 136 | 137 | app : StartApp.App Model 138 | app = 139 | StartApp.start 140 | { init = init initialPath 141 | , update = update 142 | , view = view 143 | , inputs = [actions] 144 | } 145 | 146 | 147 | main : Signal Html 148 | main = 149 | app.html 150 | 151 | 152 | port tasks : Signal (Task.Task Never ()) 153 | port tasks = 154 | app.tasks 155 | 156 | 157 | port initialPath : String 158 | -------------------------------------------------------------------------------- /src/elm/Pages/About.elm: -------------------------------------------------------------------------------- 1 | module Pages.About where 2 | 3 | import Signal 4 | import Html exposing (div, text, Html) 5 | import Effects exposing (Effects) 6 | 7 | 8 | type alias Model = () 9 | 10 | 11 | init : Model 12 | init = () 13 | 14 | 15 | type Action 16 | = NoOp 17 | | Show 18 | 19 | 20 | update : Action -> Model -> (Model, Effects Action) 21 | update action model = 22 | case action of 23 | NoOp -> 24 | ( model 25 | , Effects.none) 26 | Show -> 27 | update action model -- Until we actually show something different to a no op ! 28 | 29 | 30 | 31 | view : Signal.Address Action -> Model -> Html 32 | view address model = 33 | div [] [ text "This is the about page" ] 34 | -------------------------------------------------------------------------------- /src/elm/Pages/Home.elm: -------------------------------------------------------------------------------- 1 | module Pages.Home where 2 | 3 | import Signal 4 | import Html exposing (div, text, Html) 5 | import Effects exposing (Effects) 6 | 7 | 8 | type alias Model = () 9 | 10 | 11 | init : Model 12 | init = () 13 | 14 | 15 | type Action 16 | = NoOp 17 | | Show 18 | 19 | 20 | update : Action -> Model -> (Model, Effects Action) 21 | update action model = 22 | case action of 23 | NoOp -> 24 | ( model 25 | , Effects.none) 26 | Show -> 27 | update action model -- Until we actually show something different to a no op ! 28 | 29 | 30 | 31 | view : Signal.Address Action -> Model -> Html 32 | view address model = 33 | div [] [ text "This is the super home page" ] 34 | -------------------------------------------------------------------------------- /src/elm/Routes.elm: -------------------------------------------------------------------------------- 1 | module Routes where 2 | 3 | import Effects exposing (Effects) 4 | import RouteParser exposing (..) 5 | import TransitRouter 6 | import Html exposing (Attribute) 7 | import Html.Attributes exposing (href) 8 | import Json.Decode as Json 9 | import Html.Events exposing (on, onClick, onWithOptions) 10 | import Signal 11 | 12 | 13 | type Route 14 | = Home 15 | | About 16 | | EmptyRoute 17 | 18 | 19 | routeParsers : List (Matcher Route) 20 | routeParsers = 21 | [ static Home "/" 22 | , static About "/about" 23 | ] 24 | 25 | 26 | decode : String -> Route 27 | decode path = 28 | RouteParser.match routeParsers path 29 | |> Maybe.withDefault EmptyRoute 30 | 31 | 32 | encode : Route -> String 33 | encode route = 34 | case route of 35 | Home -> "/" 36 | About -> "/about" 37 | EmptyRoute -> "" 38 | 39 | 40 | redirect : Route -> Effects () 41 | redirect route = 42 | encode route 43 | |> Signal.send TransitRouter.pushPathAddress 44 | |> Effects.task 45 | 46 | 47 | clickAttr : Route -> Attribute 48 | clickAttr route = 49 | on "click" Json.value (\_ -> Signal.message TransitRouter.pushPathAddress <| encode route) 50 | 51 | 52 | linkAttrs : Route -> List Attribute 53 | linkAttrs route = 54 | let 55 | path = encode route 56 | in 57 | [ href path 58 | , onWithOptions 59 | "click" 60 | { stopPropagation = True, preventDefault = True } 61 | Json.value 62 | (\_ -> Signal.message TransitRouter.pushPathAddress path) 63 | ] 64 | -------------------------------------------------------------------------------- /src/elm/Sample.elm: -------------------------------------------------------------------------------- 1 | module Sample where 2 | 3 | import Effects exposing (Effects) 4 | import Html exposing (..) 5 | import TransitRouter 6 | 7 | 8 | type Action 9 | = NoOp 10 | | RouterAction TransitRouter.Action 11 | 12 | 13 | init : (Model, Effects Action) 14 | init = 15 | ( { } 16 | , Effects.none 17 | ) 18 | 19 | 20 | update : Action -> Model -> (Model, Effects Action) 21 | update action model = 22 | case action of 23 | NoOp -> 24 | ( model, Effects.none ) 25 | 26 | 27 | view : Signal.Address Action -> Model -> Html 28 | view address model = 29 | div [] 30 | [ text "Hello World!" ] 31 | -------------------------------------------------------------------------------- /src/index.jade: -------------------------------------------------------------------------------- 1 | doctype 2 | 3 | head 4 | meta(charset='utf-8') 5 | meta(http-equiv='x-ua-compatible', content='ie=edge') 6 | title Hello World 7 | meta(name='description', content='') 8 | meta(name='viewport', content='width=device-width, initial-scale=1') 9 | link(rel='stylesheet', href='main.css') 10 | 11 | body 12 | script(src='main.js') 13 | 14 | script. 15 | var app = Elm.fullscreen(Elm.Main, { 16 | initialPath: location.pathname || '/' 17 | }) 18 | 19 | // 20 | script. 21 | if ('serviceWorker' in navigator) { 22 | navigator.serviceWorker.register('service-worker.js') 23 | } 24 | 25 | 26 | // Google Analytics: change UA-XXXXX-X to be your site's ID 27 | script. 28 | (function(b,o,i,l,e,r){b.GoogleAnalyticsObject=l;b[l]||(b[l]= 29 | function(){(b[l].q=b[l].q||[]).push(arguments)});b[l].l=+new Date; 30 | e=o.createElement(i);r=o.getElementsByTagName(i)[0]; 31 | e.src='https://www.google-analytics.com/analytics.js'; 32 | r.parentNode.insertBefore(e,r)}(window,document,'script','ga')); 33 | ga('create','UA-XXXXX-X','auto');ga('send','pageview'); 34 | -------------------------------------------------------------------------------- /src/robots.txt: -------------------------------------------------------------------------------- 1 | # www.robotstxt.org/ 2 | # 3 | # Allow crawling of all content 4 | User-agent: * 5 | Disallow: 6 | -------------------------------------------------------------------------------- /src/sass/main.sass: -------------------------------------------------------------------------------- 1 | body 2 | font-family: Helvetica, Arial, sans-serif 3 | --------------------------------------------------------------------------------