├── .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 |
--------------------------------------------------------------------------------