├── res
├── scaffold
│ ├── blog
│ │ ├── src
│ │ │ ├── assets
│ │ │ │ ├── app.js
│ │ │ │ ├── images
│ │ │ │ │ ├── logo.png
│ │ │ │ │ ├── header_01.jpg
│ │ │ │ │ ├── header_02.jpg
│ │ │ │ │ └── github.svg
│ │ │ │ ├── markdown.css
│ │ │ │ └── counter.css
│ │ │ ├── pages
│ │ │ │ ├── external.yaml
│ │ │ │ ├── index.md
│ │ │ │ ├── articles
│ │ │ │ │ ├── 001.md
│ │ │ │ │ ├── 003.md
│ │ │ │ │ └── 002.md
│ │ │ │ ├── about.md
│ │ │ │ └── installation.md
│ │ │ ├── Siteelm
│ │ │ │ ├── Html
│ │ │ │ │ └── Attributes.elm
│ │ │ │ ├── Html.elm
│ │ │ │ └── Page.elm
│ │ │ ├── Dynamic
│ │ │ │ └── Counter.elm
│ │ │ └── Static
│ │ │ │ ├── Page.elm
│ │ │ │ ├── Article.elm
│ │ │ │ ├── View.elm
│ │ │ │ └── Home.elm
│ │ ├── siteelm.yaml
│ │ └── elm.json
│ ├── .gitignore
│ └── basic
│ │ ├── src
│ │ ├── pages
│ │ │ ├── other.yaml
│ │ │ ├── sub
│ │ │ │ ├── a.md
│ │ │ │ └── b.md
│ │ │ ├── articles
│ │ │ │ ├── 01.md
│ │ │ │ └── 02.md
│ │ │ └── index.md
│ │ ├── Siteelm
│ │ │ ├── Html
│ │ │ │ └── Attributes.elm
│ │ │ ├── Html.elm
│ │ │ └── Page.elm
│ │ ├── assets
│ │ │ ├── images
│ │ │ │ ├── github.svg
│ │ │ │ ├── header.svg
│ │ │ │ └── logo.svg
│ │ │ └── style.css
│ │ ├── Static
│ │ │ ├── View.elm
│ │ │ ├── Sub.elm
│ │ │ └── Basic.elm
│ │ └── Dynamic
│ │ │ └── Counter.elm
│ │ ├── siteelm.yaml
│ │ └── elm.json
└── img
│ ├── siteelm.svg
│ └── about.svg
├── main.js
├── .gitignore
├── .github
└── ISSUE_TEMPLATE
│ ├── feature_request.md
│ └── bug_report.md
├── Makefile
├── tsconfig.json
├── src
├── server.ts
├── generator
│ ├── snippet.ts
│ ├── elmtojs.ts
│ └── jstohtml.ts
├── initializer.ts
├── watcher.ts
├── index.ts
├── config.ts
└── generator.ts
├── README.md
├── package.json
└── LICENSE
/res/scaffold/blog/src/assets/app.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/main.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | require('./lib/index.js')
3 |
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.swp
2 | node_modules
3 | .DS_Store
4 | Thumbs.db
5 | lib
6 |
--------------------------------------------------------------------------------
/res/scaffold/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | /*/**/dist
3 | /*/**/elm-stuff
4 | !Lib
5 |
--------------------------------------------------------------------------------
/res/scaffold/blog/src/assets/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nikueater/siteelm/HEAD/res/scaffold/blog/src/assets/images/logo.png
--------------------------------------------------------------------------------
/res/scaffold/basic/src/pages/other.yaml:
--------------------------------------------------------------------------------
1 | - name: Unagi
2 | price: 2000
3 | - name: Hamo
4 | price: 1000
5 | - name: Hamachi
6 | price: 500
7 |
--------------------------------------------------------------------------------
/res/scaffold/basic/src/pages/sub/a.md:
--------------------------------------------------------------------------------
1 | ---
2 | module: Static.Sub
3 | title: A
4 | ---
5 |
6 | ### about
7 |
8 | this is a **sub** page _"A"_
9 |
--------------------------------------------------------------------------------
/res/scaffold/basic/src/pages/sub/b.md:
--------------------------------------------------------------------------------
1 | ---
2 | module: Static.Sub
3 | title: B
4 | ---
5 |
6 | ### about
7 |
8 | this is a **sub** page _"B"_
9 |
--------------------------------------------------------------------------------
/res/scaffold/blog/src/assets/images/header_01.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nikueater/siteelm/HEAD/res/scaffold/blog/src/assets/images/header_01.jpg
--------------------------------------------------------------------------------
/res/scaffold/blog/src/assets/images/header_02.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nikueater/siteelm/HEAD/res/scaffold/blog/src/assets/images/header_02.jpg
--------------------------------------------------------------------------------
/res/scaffold/blog/src/pages/external.yaml:
--------------------------------------------------------------------------------
1 | - name: Unagi
2 | price: 2000
3 | - name: Hamo
4 | price: 1000
5 | - name: Hamachi
6 | price: 500
7 |
--------------------------------------------------------------------------------
/res/scaffold/basic/src/pages/articles/01.md:
--------------------------------------------------------------------------------
1 | ---
2 | module: Static.Sub
3 | date: 2020-01-02
4 | title: Article(2020-01-02)
5 | ---
6 |
7 | ### Sample Article
8 |
9 | 2020/01/02
10 |
--------------------------------------------------------------------------------
/res/scaffold/basic/src/pages/articles/02.md:
--------------------------------------------------------------------------------
1 | ---
2 | module: Static.Sub
3 | date: 2020-01-04
4 | title: Article(2020-01-04)
5 | ---
6 |
7 | ### Sample Article
8 |
9 | 2020/01/04
10 |
--------------------------------------------------------------------------------
/res/scaffold/blog/src/assets/markdown.css:
--------------------------------------------------------------------------------
1 | .md {
2 | margin: 0;
3 | padding: 1rem 0;
4 | }
5 |
6 | .md * {
7 | margin: 0;
8 | }
9 |
10 | .md h1 {
11 | padding: 0;
12 | font-size: 2rem;
13 | }
14 |
15 | .md p {
16 | padding: 0.5rem 0;
17 | }
18 |
19 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: enhancement
6 | assignees: ''
7 |
8 | ---
9 |
10 | ## overview
11 |
12 | ## goal
13 |
14 | - [ ] things satisfied after this
15 |
16 | ## idea
17 |
--------------------------------------------------------------------------------
/res/scaffold/blog/src/pages/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | module: Static.Home
3 | title: siteelm introduction
4 | articles:
5 | preamblesIn: ./articles
6 | products:
7 | external: ./external.yaml
8 | ---
9 |
10 | # Welcome!
11 |
12 | Siteelm is a simple static site generator for Elm.
13 | And this is its blog template.
14 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 |
2 | all: library
3 |
4 |
5 | library:
6 | npm run build
7 |
8 | demo:
9 | npm install -g elm siteelm
10 | npm install
11 | npm run build
12 | cd res/scaffold/blog; siteelm make -o
13 |
14 | demo-basic:
15 | cd res/scaffold/basic; siteelm server
16 |
17 | demo-blog:
18 | cd res/scaffold/blog; siteelm server
19 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2019",
4 | "module": "commonjs",
5 | "moduleResolution": "node",
6 | "outDir": "lib",
7 | "strict": true,
8 | "allowSyntheticDefaultImports": true,
9 | "declaration": false,
10 | "esModuleInterop": true,
11 | "sourceMap": false
12 | },
13 | "include": ["src/**/*"],
14 | "exclude": ["test/**/*"]
15 | }
16 |
--------------------------------------------------------------------------------
/res/scaffold/basic/siteelm.yaml:
--------------------------------------------------------------------------------
1 | build:
2 | dist_dir: ./dist
3 | contents:
4 | src_dir: ./src/pages
5 | index: ./src/pages/index.md
6 | exclude:
7 | - ./**/*.yaml
8 | assets:
9 | src_dir: ./src/assets
10 | static_elm:
11 | src_dir: ./src/Static
12 | exclude:
13 | - ./**/View.elm
14 | dynamic_elm:
15 | src_dir: ./src/Dynamic
16 |
17 |
--------------------------------------------------------------------------------
/res/scaffold/blog/siteelm.yaml:
--------------------------------------------------------------------------------
1 | build:
2 | dist_dir: ./dist
3 | contents:
4 | src_dir: ./src/pages
5 | index: ./src/pages/index.md
6 | exclude:
7 | - ./**/*.yaml
8 | assets:
9 | src_dir: ./src/assets
10 | static_elm:
11 | src_dir: ./src/Static
12 | exclude:
13 | - ./**/View.elm
14 | dynamic_elm:
15 | src_dir: ./src/Dynamic
16 |
17 |
--------------------------------------------------------------------------------
/res/scaffold/blog/src/pages/articles/001.md:
--------------------------------------------------------------------------------
1 | ---
2 | module: Static.Article
3 | date: 2020-05-01
4 | title: The concept of Siteelm
5 | ---
6 |
7 | Though I said "Siteelm is a simple static site generator," because I develop this for my work, there are some unique features.
8 |
9 | It's good to consider Siteelm is not for only blogs. In other words, in some respects this isn't optimized for blogs.
10 |
11 | The reason I made this was "I want to write my corporate site with Elm."
12 |
--------------------------------------------------------------------------------
/res/scaffold/blog/src/pages/about.md:
--------------------------------------------------------------------------------
1 | ---
2 | module: Static.Page
3 | title: About this blog
4 | image: /images/header_02.jpg
5 | ---
6 |
7 | ## outline
8 |
9 | This is _the Siteelm blog template_.
10 | While the title contains the word _blog_, it's just a demonstration.
11 |
12 | ## rights
13 |
14 | You can change everything in this template.
15 | Of cource you can remove the footer.
16 | And you can also use assets like image resources if you want to.
17 |
18 | However the logo of Siteelm, you cannot modify it.
19 |
--------------------------------------------------------------------------------
/res/scaffold/blog/src/pages/installation.md:
--------------------------------------------------------------------------------
1 | ---
2 | module: Static.Page
3 | title: Installation
4 | image: /images/header_02.jpg
5 | ---
6 |
7 | ## initialize a project
8 |
9 | ```shell
10 | % npm init -y
11 | % npm install -D elm siteelm
12 | % npx siteelm init
13 | ```
14 |
15 | Then you'll see a scaffold in the directory.
16 |
17 | ## develop
18 |
19 | ```shell
20 | % npx siteelm server -d
21 | ```
22 |
23 | Then access "http://localhost:3000/"
24 | The server supports file watching and auto reloading. If you don't need the server, use _siteelm watch_ instead.
25 |
26 |
27 | ## build
28 |
29 | ```shell
30 | % npx siteelm make -o
31 | ```
32 |
--------------------------------------------------------------------------------
/res/scaffold/basic/elm.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "application",
3 | "source-directories": [
4 | "src"
5 | ],
6 | "elm-version": "0.19.1",
7 | "dependencies": {
8 | "direct": {
9 | "elm/browser": "1.0.2",
10 | "elm/core": "1.0.4",
11 | "elm/html": "1.0.0",
12 | "elm/json": "1.1.3",
13 | "elm-explorations/markdown": "1.0.0"
14 | },
15 | "indirect": {
16 | "elm/time": "1.0.0",
17 | "elm/url": "1.0.0",
18 | "elm/virtual-dom": "1.0.2"
19 | }
20 | },
21 | "test-dependencies": {
22 | "direct": {},
23 | "indirect": {}
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/res/scaffold/basic/src/Siteelm/Html/Attributes.elm:
--------------------------------------------------------------------------------
1 | module Siteelm.Html.Attributes exposing (charset, content, data, rel, role)
2 |
3 | import Html exposing (Attribute)
4 | import Html.Attributes exposing (attribute)
5 |
6 |
7 | content : String -> Attribute msg
8 | content =
9 | attribute "content"
10 |
11 |
12 | charset : String -> Attribute msg
13 | charset =
14 | attribute "charset"
15 |
16 |
17 | rel : String -> Attribute msg
18 | rel =
19 | attribute "rel"
20 |
21 |
22 | role : String -> Attribute msg
23 | role =
24 | attribute "role"
25 |
26 |
27 | data : String -> String -> Attribute msg
28 | data name value =
29 | attribute (String.join "-" [ "data", name ]) value
30 |
--------------------------------------------------------------------------------
/res/scaffold/blog/src/Siteelm/Html/Attributes.elm:
--------------------------------------------------------------------------------
1 | module Siteelm.Html.Attributes exposing (charset, content, data, rel, role)
2 |
3 | import Html exposing (Attribute)
4 | import Html.Attributes exposing (attribute)
5 |
6 |
7 | content : String -> Attribute msg
8 | content =
9 | attribute "content"
10 |
11 |
12 | charset : String -> Attribute msg
13 | charset =
14 | attribute "charset"
15 |
16 |
17 | rel : String -> Attribute msg
18 | rel =
19 | attribute "rel"
20 |
21 |
22 | role : String -> Attribute msg
23 | role =
24 | attribute "role"
25 |
26 |
27 | data : String -> String -> Attribute msg
28 | data name value =
29 | attribute (String.join "-" [ "data", name ]) value
30 |
--------------------------------------------------------------------------------
/res/scaffold/basic/src/assets/images/github.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/res/scaffold/blog/src/assets/images/github.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/res/scaffold/basic/src/Static/View.elm:
--------------------------------------------------------------------------------
1 | module Static.View exposing (header)
2 |
3 | import Html exposing (Html, a, div, img)
4 | import Html.Attributes exposing (alt, class, height, href, src, width)
5 | import Siteelm.Html.Attributes exposing (role)
6 |
7 |
8 | header : Html Never
9 | header =
10 | div [ class "header" ]
11 | [ div [ class "stripe" ]
12 | [ a [ href "https://github.com/nikueater/siteelm" ]
13 | [ img [ src "/images/github.svg", width 48, height 48, alt "git hub" ] []
14 | ]
15 | ]
16 | , a [ href "/", role "banner" ]
17 | [ img [ class "logo", src "/images/logo.svg", width 320, height 160, alt "siteelm" ] []
18 | ]
19 | ]
20 |
--------------------------------------------------------------------------------
/res/scaffold/basic/src/pages/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | module: Static.Basic
3 | title: HOME
4 | products:
5 | - name: Apple
6 | price: 100
7 | - name: Banana
8 | price: 150
9 | recommends:
10 | external: ./other.yaml
11 | articles:
12 | preamblesIn: ./articles
13 | ---
14 | ### Markdown
15 | - [sub page (A)](/sub/a)
16 | - [sub page (B)](/sub/b)
17 |
18 |
19 | ```elm
20 | type alias Preamble =
21 | { "title": "String"
22 | , "name": "String"
23 | }
24 |
25 | decoder : Decoder Preamble
26 | decoder =
27 | D.map2 Preamble
28 | (field "title" string)
29 | (field "name" string)
30 |
31 | main : Page Preamble
32 | main =
33 | page
34 | { decoder = decoder
35 | , head = head
36 | , body = body
37 | }
38 | .
39 | .
40 | .
41 |
42 | ```
43 |
--------------------------------------------------------------------------------
/res/scaffold/blog/src/pages/articles/003.md:
--------------------------------------------------------------------------------
1 | ---
2 | module: Static.Article
3 | date: 2020-05-03
4 | title: Loading external YAML files
5 | ---
6 |
7 |
8 | The "external" property enables you to load YAML files in the preamble section.
9 | It's useful when you want to share specific data among articles, for example, a list of pinned articles.
10 |
11 |
12 | ### page/recommendations.yaml
13 |
14 | ```yaml
15 | - name: eel
16 | url: /sushi/eel
17 | price: 150
18 | - name: salmon
19 | url: /sushi/salmon
20 | price: 100
21 | - name: mackerel
22 | url: /sushi/mackerel
23 | price: 120
24 | ```
25 |
26 | ### page/20200501.md
27 |
28 | ```markdown
29 | ---
30 | module: Static.Dialy
31 | specialties:
32 | external: ./recommendations.yaml
33 |
34 | ---
35 |
36 | Today we bought ingredients below...
37 |
38 | ```
39 |
--------------------------------------------------------------------------------
/res/scaffold/blog/src/assets/counter.css:
--------------------------------------------------------------------------------
1 | .counter * {
2 | box-sizing: border-box;
3 | }
4 |
5 | .counter {
6 | width: 320px;
7 | height: 96px;
8 | display: flex;
9 | align-items: stretch;
10 | }
11 |
12 | .counter > * {
13 | flex: 1;
14 | display: flex;
15 | justify-content: center;
16 | align-items: center;
17 | font-weight: bold;
18 | font-size: 20px;
19 | color: #ffffff;
20 | }
21 |
22 | .counter > div {
23 | background-color: #58637a;
24 | font-family: Questrial, sans-serif;
25 | }
26 |
27 | .counter > button {
28 | border: none;
29 | background-color: #3db7Cf;
30 | font-family: Questrial, sans-serif;
31 | padding: 0;
32 | }
33 |
34 | .counter > *:first-child {
35 | border-radius: 80px 0 0 80px;
36 | }
37 |
38 | .counter > *:last-child {
39 | border-radius: 0 80px 80px 0;
40 | }
41 |
42 |
43 |
--------------------------------------------------------------------------------
/src/server.ts:
--------------------------------------------------------------------------------
1 | import express from 'express'
2 | import expressWs from 'express-ws'
3 | import {Config} from './config'
4 | import watchAll from './watcher'
5 | import generateAll from './generator'
6 |
7 | const server = (config: Config) => {
8 | generateAll(config, {isServer: true})
9 | //
10 | const ews = expressWs(express())
11 | const app = ews.app
12 | app.set('port', 3000)
13 | app.use(express.static(config.build.dist_dir))
14 | app.ws('/', () => {
15 | console.log('auto reloader connected')
16 | })
17 | // start a server
18 | app.listen(app.get('port'), () => {
19 | console.log(`running server localhost:${app.get('port')}`)
20 |
21 | // start watching
22 | const wss = ews.getWss()
23 | watchAll(config, () => {
24 | wss.clients.forEach((c)=>{
25 | c.send(JSON.stringify({reload: true}))
26 | })
27 | })
28 | })
29 | }
30 |
31 | export default server;
32 |
--------------------------------------------------------------------------------
/res/scaffold/blog/elm.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "application",
3 | "source-directories": [
4 | "src"
5 | ],
6 | "elm-version": "0.19.1",
7 | "dependencies": {
8 | "direct": {
9 | "NoRedInk/elm-json-decode-pipeline": "1.0.0",
10 | "elm/browser": "1.0.2",
11 | "elm/core": "1.0.5",
12 | "elm/html": "1.0.0",
13 | "elm/json": "1.1.3",
14 | "elm/time": "1.0.0",
15 | "elm-explorations/markdown": "1.0.0",
16 | "mdgriffith/elm-ui": "1.1.5",
17 | "rtfeldman/elm-iso8601-date-strings": "1.1.3"
18 | },
19 | "indirect": {
20 | "avh4/elm-color": "1.0.0",
21 | "elm/parser": "1.1.0",
22 | "elm/url": "1.0.0",
23 | "elm/virtual-dom": "1.0.2",
24 | "ianmackenzie/elm-units": "2.5.0"
25 | }
26 | },
27 | "test-dependencies": {
28 | "direct": {},
29 | "indirect": {}
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Desktop (please complete the following information):**
27 | - OS: [e.g. iOS]
28 | - Browser [e.g. chrome, safari]
29 | - Version [e.g. 22]
30 |
31 | **Smartphone (please complete the following information):**
32 | - Device: [e.g. iPhone6]
33 | - OS: [e.g. iOS8.1]
34 | - Browser [e.g. stock browser, safari]
35 | - Version [e.g. 22]
36 |
37 | **Additional context**
38 | Add any other context about the problem here.
39 |
--------------------------------------------------------------------------------
/res/scaffold/basic/src/assets/images/header.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/generator/snippet.ts:
--------------------------------------------------------------------------------
1 |
2 | export const staticElmInitCode = (moduleName: string, flags: object): string => {
3 | return `var app = Elm.${moduleName}.init({flags:${JSON.stringify(flags)}});
4 | `
5 | }
6 |
7 | export const dynamicElmInitCode = (moduleName: string, flags: string, key: string): string => {
8 | const objName = moduleName.replace('.', '')
9 | return `
10 | window.app = window.app || {}
11 | var ns = document.querySelectorAll('div[data-elm-module="${moduleName}"][data-unique-key="${key}"]')
12 | for(var i=0;i 0 ? ('_' + i) : '')
14 | window.app[name] = Elm.${moduleName}.init({node:ns[i],flags:${flags}})
15 | }
16 | `
17 | }
18 |
19 | export const autoReloaderCode = ():string => {
20 | return `(function(){
21 | var host = (window.location.origin).replace(/^http/, 'ws');
22 | var ws = new WebSocket(host);
23 | ws.onmessage = function(e) {
24 | if(JSON.parse(e.data).reload) {
25 | window.location.reload()
26 | }
27 | }
28 | }())`
29 | }
30 |
--------------------------------------------------------------------------------
/res/scaffold/basic/src/Dynamic/Counter.elm:
--------------------------------------------------------------------------------
1 | module Dynamic.Counter exposing (main)
2 |
3 | import Browser
4 | import Html exposing (Html, button, div, text)
5 | import Html.Attributes exposing (class)
6 | import Html.Events exposing (onClick)
7 |
8 |
9 | type alias Model =
10 | { value : Int
11 | }
12 |
13 |
14 | type Msg
15 | = Inc
16 | | Dec
17 |
18 |
19 | main : Program Model Model Msg
20 | main =
21 | Browser.element
22 | { init = \flags -> ( flags, Cmd.none )
23 | , update = update
24 | , view = view
25 | , subscriptions = always Sub.none
26 | }
27 |
28 |
29 | update : Msg -> Model -> ( Model, Cmd Msg )
30 | update msg model =
31 | case msg of
32 | Inc ->
33 | ( { model | value = model.value + 1 }, Cmd.none )
34 |
35 | Dec ->
36 | ( { model | value = model.value - 1 }, Cmd.none )
37 |
38 |
39 | view : Model -> Html Msg
40 | view model =
41 | div [ class "counter" ]
42 | [ button [ onClick Dec ] [ text "-1" ]
43 | , div [ class "value" ] [ text <| String.fromInt model.value ]
44 | , button [ onClick Inc ] [ text "+1" ]
45 | ]
46 |
--------------------------------------------------------------------------------
/res/scaffold/blog/src/Dynamic/Counter.elm:
--------------------------------------------------------------------------------
1 | module Dynamic.Counter exposing (main)
2 |
3 | import Browser
4 | import Html exposing (Html, button, div, text)
5 | import Html.Attributes exposing (class)
6 | import Html.Events exposing (onClick)
7 |
8 |
9 | type alias Model =
10 | { value : Int
11 | }
12 |
13 |
14 | type Msg
15 | = Inc
16 | | Dec
17 |
18 |
19 | main : Program Model Model Msg
20 | main =
21 | Browser.element
22 | { init = \flags -> ( flags, Cmd.none )
23 | , update = update
24 | , view = view
25 | , subscriptions = always Sub.none
26 | }
27 |
28 |
29 | update : Msg -> Model -> ( Model, Cmd Msg )
30 | update msg model =
31 | case msg of
32 | Inc ->
33 | ( { model | value = model.value + 1 }, Cmd.none )
34 |
35 | Dec ->
36 | ( { model | value = model.value - 1 }, Cmd.none )
37 |
38 |
39 | view : Model -> Html Msg
40 | view model =
41 | div [ class "counter" ]
42 | [ button [ onClick Dec ] [ text "-1" ]
43 | , div [ class "value" ] [ text <| String.fromInt model.value ]
44 | , button [ onClick Inc ] [ text "+1" ]
45 | ]
46 |
--------------------------------------------------------------------------------
/res/scaffold/blog/src/pages/articles/002.md:
--------------------------------------------------------------------------------
1 | ---
2 | module: Static.Article
3 | date: 2020-05-02
4 | title: Dynamic Element
5 | ---
6 |
7 | _"Siteelm.Html.dynamic"_ is one of the ways to embed dynamic components in the static layout.
8 | For example, when you want to put something interactive, you can use it.
9 |
10 | (Currently you cannot use elm-ui for dynamic elements when you use it in static layouts because class names in both elements are duplicated.)
11 |
12 | ### Dynamic/Counter.elm
13 |
14 | ```elm
15 | module Dynamic.Counter exposing (main)
16 |
17 | type alias Model =
18 | { value : Int
19 | }
20 |
21 | main : Program Model Model Msg
22 | main =
23 | Browser.element
24 | { init = \flags -> ( flags, Cmd.none )
25 | , update = update
26 | , view = view
27 | , subscriptions = \_ -> Sub.none
28 | }
29 | ...
30 | ```
31 |
32 | ### siteelm.yaml
33 |
34 | ```yaml
35 | build:
36 | dynamic_elm:
37 | src_dir: ./src/Dynamic
38 | ```
39 |
40 | ### Static/Layout.elm
41 |
42 | ```elm
43 | import Siteelm.Html as Html
44 |
45 | counter : Html Never
46 | counter =
47 | Html.dynamic
48 | { moduleName = "Dynamic.Counter"
49 | , flags = "{value: 100}"
50 | }
51 | ```
52 |
--------------------------------------------------------------------------------
/res/scaffold/basic/src/Siteelm/Html.elm:
--------------------------------------------------------------------------------
1 | module Siteelm.Html exposing (body, dynamic, head, html, link, meta, script, title)
2 |
3 | import Html exposing (Attribute, Html, node, text)
4 | import Html.Attributes exposing (attribute)
5 | import Siteelm.Html.Attributes exposing (data)
6 |
7 |
8 | meta : List (Attribute msg) -> Html msg
9 | meta attrs =
10 | node "meta" attrs []
11 |
12 |
13 | link : List (Attribute msg) -> Html msg
14 | link attrs =
15 | node "link" attrs []
16 |
17 |
18 | script : String -> String -> Html msg
19 | script src code =
20 | node "siteelm-custom" [ data "tag" "script", attribute "src" src ] [ text code ]
21 |
22 |
23 | title : List (Attribute msg) -> String -> Html msg
24 | title attrs s =
25 | node "title" attrs [ Html.text s ]
26 |
27 |
28 | head : List (Attribute msg) -> List (Html msg) -> Html msg
29 | head attrs es =
30 | node "head" attrs es
31 |
32 |
33 | html : List (Attribute msg) -> List (Html msg) -> Html msg
34 | html attrs es =
35 | node "html" attrs es
36 |
37 |
38 | body : List (Attribute msg) -> List (Html msg) -> Html msg
39 | body attrs es =
40 | node "body" attrs es
41 |
42 |
43 | dynamic : { moduleName : String, flags : String } -> Html msg
44 | dynamic { moduleName, flags } =
45 | Html.div
46 | [ data "elm-module" moduleName
47 | , data "flags" flags
48 | ]
49 | []
50 |
--------------------------------------------------------------------------------
/res/scaffold/blog/src/Siteelm/Html.elm:
--------------------------------------------------------------------------------
1 | module Siteelm.Html exposing (body, dynamic, head, html, link, meta, script, title)
2 |
3 | import Html exposing (Attribute, Html, node, text)
4 | import Html.Attributes exposing (attribute)
5 | import Siteelm.Html.Attributes exposing (data)
6 |
7 |
8 | meta : List (Attribute msg) -> Html msg
9 | meta attrs =
10 | node "meta" attrs []
11 |
12 |
13 | link : List (Attribute msg) -> Html msg
14 | link attrs =
15 | node "link" attrs []
16 |
17 |
18 | script : String -> String -> Html msg
19 | script src code =
20 | node "siteelm-custom" [ data "tag" "script", attribute "src" src ] [ text code ]
21 |
22 |
23 | title : List (Attribute msg) -> String -> Html msg
24 | title attrs s =
25 | node "title" attrs [ Html.text s ]
26 |
27 |
28 | head : List (Attribute msg) -> List (Html msg) -> Html msg
29 | head attrs es =
30 | node "head" attrs es
31 |
32 |
33 | html : List (Attribute msg) -> List (Html msg) -> Html msg
34 | html attrs es =
35 | node "html" attrs es
36 |
37 |
38 | body : List (Attribute msg) -> List (Html msg) -> Html msg
39 | body attrs es =
40 | node "body" attrs es
41 |
42 |
43 | dynamic : { moduleName : String, flags : String } -> Html msg
44 | dynamic { moduleName, flags } =
45 | Html.div
46 | [ data "elm-module" moduleName
47 | , data "flags" flags
48 | ]
49 | []
50 |
--------------------------------------------------------------------------------
/res/scaffold/blog/src/Static/Page.elm:
--------------------------------------------------------------------------------
1 | module Static.Page exposing (main)
2 |
3 | import Element exposing (..)
4 | import Html exposing (Html)
5 | import Html.Attributes exposing (class)
6 | import Json.Decode as Decode exposing (Decoder, string)
7 | import Json.Decode.Pipeline exposing (required)
8 | import Markdown
9 | import Siteelm.Page exposing (Page, page)
10 | import Static.View exposing (head, headerImage, pageLayout)
11 |
12 |
13 | main : Page Preamble
14 | main =
15 | page
16 | { decoder = preambleDecoder
17 | , head = head
18 | , body = body
19 | }
20 |
21 |
22 | type alias Preamble =
23 | { title : String
24 | , image : String
25 | }
26 |
27 |
28 | preambleDecoder : Decoder Preamble
29 | preambleDecoder =
30 | Decode.succeed Preamble
31 | |> required "title" string
32 | |> required "image" string
33 |
34 |
35 | body : Preamble -> String -> List (Html Never)
36 | body preamble markdown =
37 | pageLayout <|
38 | column
39 | [ width fill
40 | , spacing 8
41 | , clipX
42 | ]
43 | [ headerImage
44 | { src = preamble.image
45 | , title = preamble.title
46 | }
47 | , paragraph
48 | [ width fill
49 | , paddingXY 0 20
50 | ]
51 | [ html (Markdown.toHtml [ class "md" ] markdown) ]
52 | ]
53 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | demo: https://siteelm.netlify.com/ ([source](/res/scaffold/basic))
5 |
6 | ## about
7 | It's just another static site generator for Elm, but has some features.
8 |
9 | - you can write YAML in preamble sections
10 | - in a preamble, you can load external YAML files
11 | - it's easy to mix dynamic Elm components
12 | - simple rules to use
13 |
14 | ## concept
15 | to make a template, what you need to do is writing preamble models, their decoder, and two functions return Html Never (for HEAD and BODY)
16 |
17 |
18 |
19 |
20 | ## usage
21 | ### install
22 | ```sh
23 | % npm install -g elm
24 | % npm install -g siteelm
25 | ```
26 | or if you'd like to do everything locally,
27 | ```sh
28 | % npm install -D elm siteelm
29 | ```
30 | in this case, type _"% npx siteelm 〜"_ instead of _"% siteelm 〜"_
31 |
32 | ### initialize project
33 | ```sh
34 | % mkdir mysite
35 | % cd mysite
36 | % siteelm init
37 | ```
38 | Then you'll see a scaffold in the directory.
39 |
40 |
41 | ### developing
42 | ```sh
43 | % cd mysite
44 | % siteelm server -d
45 | ```
46 | Then access "http://localhost:3000/"
47 | The server supports file watching and auto reloading.
48 | If you don't need the server, use `siteelm watch` instead.
49 |
50 | ### building
51 | ```sh
52 | % cd mysite
53 | % siteelm make -o
54 | ```
55 |
--------------------------------------------------------------------------------
/res/scaffold/basic/src/Static/Sub.elm:
--------------------------------------------------------------------------------
1 | module Static.Sub exposing (main)
2 |
3 | import Html exposing (Html, a, div, h2, nav, text)
4 | import Html.Attributes exposing (class, href)
5 | import Json.Decode as D exposing (Decoder)
6 | import Markdown
7 | import Siteelm.Html as Html
8 | import Siteelm.Html.Attributes exposing (charset, rel)
9 | import Siteelm.Page exposing (Page, page)
10 | import Static.View as View
11 |
12 |
13 | main : Page Preamble
14 | main =
15 | page
16 | { decoder = preambleDecoder
17 | , head = viewHead
18 | , body = viewBody
19 | }
20 |
21 |
22 | type alias Preamble =
23 | { title : String
24 | }
25 |
26 |
27 | preambleDecoder : Decoder Preamble
28 | preambleDecoder =
29 | D.map Preamble
30 | (D.field "title" D.string)
31 |
32 |
33 | viewHead : Preamble -> String -> List (Html Never)
34 | viewHead preamble _ =
35 | [ Html.meta [ charset "utf-8" ]
36 | , Html.title [] (preamble.title ++ " | sample site")
37 | , Html.link [ rel "stylesheet", href "/style.css" ]
38 | , Html.link [ rel "stylesheet", href "https://fonts.googleapis.com/css?family=Questrial&display=swap" ]
39 | ]
40 |
41 |
42 | viewBody : Preamble -> String -> List (Html Never)
43 | viewBody preamble body =
44 | [ View.header
45 | , div [ class "main" ]
46 | [ nav []
47 | [ a [ href "/", class "prev" ] [ text "home" ]
48 | ]
49 | , h2 [] [ text preamble.title ]
50 | , div [ class "inner" ]
51 | [ Markdown.toHtml [] body
52 | ]
53 | ]
54 | ]
55 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "siteelm",
3 | "version": "0.3.2",
4 | "description": "a static site generator for Elm",
5 | "bin": {
6 | "siteelm": "main.js"
7 | },
8 | "engines": {
9 | "node": ">=12.2"
10 | },
11 | "files": [
12 | "README.md",
13 | "LICENSE",
14 | "main.js",
15 | "package.json",
16 | "lib",
17 | "res"
18 | ],
19 | "repository": {
20 | "type": "git",
21 | "url": "git+https://github.com/nikueater/siteelm.git"
22 | },
23 | "homepage": "https://github.com/nikueater/siteelm",
24 | "scripts": {
25 | "build": "npx tsc",
26 | "test": "echo \"Error: no test specified\" && exit 1"
27 | },
28 | "keywords": [
29 | "elm",
30 | "static site generator"
31 | ],
32 | "author": "nikueater",
33 | "license": "Apache-2.0",
34 | "dependencies": {
35 | "commander": "^3.0.2",
36 | "express": "^4.17.1",
37 | "express-ws": "^4.0.0",
38 | "fs-extra": "^8.1.0",
39 | "glob": "^7.1.6",
40 | "html-minifier": "^4.0.0",
41 | "js-yaml": "^3.13.1",
42 | "jsdom": "^16.2.2",
43 | "parsimmon": "^1.13.0",
44 | "prompts": "^2.3.2",
45 | "tmp": "^0.1.0",
46 | "traverse": "^0.6.6",
47 | "watch": "^1.0.2"
48 | },
49 | "devDependencies": {
50 | "@types/express": "^4.17.6",
51 | "@types/express-ws": "^3.0.0",
52 | "@types/fs-extra": "^8.1.0",
53 | "@types/glob": "^7.1.1",
54 | "@types/html-minifier": "^3.5.3",
55 | "@types/js-yaml": "^3.12.3",
56 | "@types/jsdom": "^12.2.4",
57 | "@types/node": "^12.12.37",
58 | "@types/parsimmon": "^1.10.1",
59 | "@types/prompts": "^2.0.7",
60 | "@types/tmp": "^0.1.0",
61 | "@types/traverse": "^0.6.32",
62 | "@types/watch": "^1.0.1",
63 | "typescript": "^3.8.3"
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/initializer.ts:
--------------------------------------------------------------------------------
1 | import path from 'path'
2 | import fs from 'fs-extra'
3 | import glob from 'glob'
4 | import prompts from 'prompts'
5 |
6 | const scaffoldDir = path.join(__dirname, '../res/scaffold')
7 | let scaffoldName = 'basic'
8 |
9 | /**
10 | * just copy a scaffold
11 | */
12 | const initialize = async () => {
13 | const whitelist = [
14 | './package.json',
15 | './package-lock.json',
16 | './node_modules/**/*',
17 | ]
18 | const files = glob.sync('./**/*',{ignore: whitelist, dot: false, nodir: true})
19 | console.log(files)
20 |
21 | if(files.length) {
22 | console.error('this directory is not empty!')
23 | console.error('(if you need, you can put only "package.json", "package-lock.json", and "node_modules")')
24 | return
25 | }
26 |
27 | console.log(`choose the template`)
28 | console.log(`[0] basic (default)`)
29 | console.log(`[1] blog`)
30 | let template = -1
31 | while(![0, 1].includes(template)) {
32 | const res = await prompts({
33 | 'type': 'number',
34 | 'name': 'template',
35 | 'message': 'template:'
36 | })
37 | template = res.template ?? 0
38 | }
39 | switch(template) {
40 | case 0:
41 | scaffoldName = 'basic'
42 | break
43 | case 1:
44 | scaffoldName = 'blog'
45 | break
46 | }
47 |
48 | console.log(`copying the scaffold "${scaffoldName}..."`)
49 | const scaffold = path.join(scaffoldDir, scaffoldName)
50 | fs.copySync(scaffold, '.')
51 | console.log('done!')
52 | console.log('------------')
53 | console.log(' siteelm server: run a server for developing')
54 | console.log(' siteelm make: generate a site')
55 | console.log('------------')
56 | }
57 |
58 | export default initialize
59 |
--------------------------------------------------------------------------------
/src/watcher.ts:
--------------------------------------------------------------------------------
1 | import path from 'path'
2 | import watch, {FileOrFiles} from 'watch'
3 | import {Config} from './config'
4 | import fs, {Stats} from 'fs'
5 | import generateAll, {convertOnlyContentFiles, copyAssets} from './generator'
6 |
7 | const watchAll = (config: Config, onChange: () => void) => {
8 | var elmdirs : string[] = []
9 | if(fs.existsSync('./elm.json')) {
10 | const json = JSON.parse(fs.readFileSync('./elm.json', 'utf-8')) as any
11 | elmdirs = (json['source-directories'] || []) as (string[])
12 | }
13 | // watch directories(generate all)
14 | const dirs = [
15 | elmdirs ? '' : (config.build.static_elm.src_dir || ''),
16 | elmdirs ? '' : (config.build.dynamic_elm.src_dir || ''),
17 | ].concat(elmdirs).filter(x => x !== '')
18 |
19 |
20 |
21 | // start watching
22 | dirs.forEach(x => {
23 | console.log(`WATCH: ${x}`)
24 | watch.watchTree(x, {interval: 1.5} , (files: FileOrFiles) => {
25 | if(typeof files === "string" && path.extname(files) === '.elm') {
26 | generateAll(config, {isServer: true})
27 | if(onChange) {
28 | onChange()
29 | }
30 | }
31 | })
32 | })
33 |
34 | // watch assets
35 | watch.watchTree(config.build.assets.src_dir, () => {
36 | console.log("ASSETS CHANGED")
37 | copyAssets(config)
38 | })
39 |
40 | // watch the content dir
41 | var ignoreOnce = true
42 | watch.watchTree(config.build.contents.src_dir, {interval: 1.5}, () =>{
43 | if(!ignoreOnce) {
44 | convertOnlyContentFiles(config)
45 | if(onChange) {
46 | onChange()
47 | }
48 | } else {
49 | ignoreOnce = false
50 | }
51 | })
52 | }
53 |
54 | export default watchAll;
55 |
--------------------------------------------------------------------------------
/res/scaffold/basic/src/Siteelm/Page.elm:
--------------------------------------------------------------------------------
1 | module Siteelm.Page exposing (Page, page)
2 |
3 | import Browser
4 | import Html exposing (Html)
5 | import Json.Decode exposing (Decoder, decodeString)
6 | import Siteelm.Html as Html
7 |
8 |
9 | {-| Generate a Program for static page. You need to give a decoder for your
10 | preamble model and a view function which takes the preamble and
11 | plain text body (e.g. markdown text).
12 | -}
13 | page :
14 | { decoder : Decoder a
15 | , head : a -> String -> List (Html Never)
16 | , body : a -> String -> List (Html Never)
17 | }
18 | -> Page a
19 | page { decoder, head, body } =
20 | Browser.document
21 | { init = \f -> ( decode decoder f, Cmd.none )
22 | , update = \_ m -> ( m, Cmd.none )
23 | , view = \m -> { title = "", body = [ renderPage head body m ] }
24 | , subscriptions = always Sub.none
25 | }
26 |
27 |
28 | type alias Page a =
29 | Program Flags (Model a) Never
30 |
31 |
32 | type alias Model a =
33 | { preamble : Maybe a
34 | , body : String
35 | }
36 |
37 |
38 | type alias Flags =
39 | { preamble : String
40 | , body : String
41 | }
42 |
43 |
44 | decode : Decoder a -> Flags -> Model a
45 | decode decoder flags =
46 | let
47 | preamble =
48 | flags.preamble
49 | |> decodeString decoder
50 | |> Result.toMaybe
51 | in
52 | { preamble = preamble
53 | , body = flags.body
54 | }
55 |
56 |
57 | renderPage : (a -> String -> List (Html Never)) -> (a -> String -> List (Html Never)) -> Model a -> Html Never
58 | renderPage head body model =
59 | case model.preamble of
60 | Just p ->
61 | Html.html []
62 | [ Html.head [] <| head p model.body
63 | , Html.body [] <| body p model.body
64 | ]
65 |
66 | Nothing ->
67 | Html.text ""
68 |
--------------------------------------------------------------------------------
/res/scaffold/blog/src/Siteelm/Page.elm:
--------------------------------------------------------------------------------
1 | module Siteelm.Page exposing (Page, page)
2 |
3 | import Browser
4 | import Html exposing (Html)
5 | import Json.Decode exposing (Decoder, decodeString)
6 | import Siteelm.Html as Html
7 |
8 |
9 | {-| Generate a Program for static page. You need to give a decoder for your
10 | preamble model and a view function which takes the preamble and
11 | plain text body (e.g. markdown text).
12 | -}
13 | page :
14 | { decoder : Decoder a
15 | , head : a -> String -> List (Html Never)
16 | , body : a -> String -> List (Html Never)
17 | }
18 | -> Page a
19 | page { decoder, head, body } =
20 | Browser.document
21 | { init = \f -> ( decode decoder f, Cmd.none )
22 | , update = \_ m -> ( m, Cmd.none )
23 | , view = \m -> { title = "", body = [ renderPage head body m ] }
24 | , subscriptions = always Sub.none
25 | }
26 |
27 |
28 | type alias Page a =
29 | Program Flags (Model a) Never
30 |
31 |
32 | type alias Model a =
33 | { preamble : Maybe a
34 | , body : String
35 | }
36 |
37 |
38 | type alias Flags =
39 | { preamble : String
40 | , body : String
41 | }
42 |
43 |
44 | decode : Decoder a -> Flags -> Model a
45 | decode decoder flags =
46 | let
47 | preamble =
48 | flags.preamble
49 | |> decodeString decoder
50 | |> Result.toMaybe
51 | in
52 | { preamble = preamble
53 | , body = flags.body
54 | }
55 |
56 |
57 | renderPage : (a -> String -> List (Html Never)) -> (a -> String -> List (Html Never)) -> Model a -> Html Never
58 | renderPage head body model =
59 | case model.preamble of
60 | Just p ->
61 | Html.html []
62 | [ Html.head [] <| head p model.body
63 | , Html.body [] <| body p model.body
64 | ]
65 |
66 | Nothing ->
67 | Html.text ""
68 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import program from 'commander'
2 | import readConfigFrom from './config'
3 | import server from './server'
4 | import generateAll, {copyAssets} from './generator'
5 | import initialize from './initializer'
6 | import watchAll from './watcher'
7 |
8 | const version = '0.3.2'
9 |
10 | program
11 | .version(version, '-v, --version')
12 | .option('-o, --optimize', 'use optimization')
13 | .option('-d, --draft', 'not to ignore drafts')
14 |
15 |
16 | // "siteelm make"
17 | program
18 | .command('make')
19 | .action(async () => {
20 | const option = {
21 | optimize: program.optimize,
22 | withDraft: program.draft
23 | }
24 | const config = readConfigFrom('./siteelm.yaml', option)
25 | const result = await generateAll(config)
26 | if(!result) {
27 | process.exitCode = 1
28 | }
29 | copyAssets(config)
30 | })
31 |
32 | // "siteelm server"
33 | program
34 | .command('server')
35 | .action(() => {
36 | const option = {
37 | optimize: program.optimize,
38 | withDraft: program.draft
39 | }
40 | const config = readConfigFrom('./siteelm.yaml', option)
41 | server(config)
42 | })
43 |
44 | // "siteelm watch"
45 | program
46 | .command('watch')
47 | .action(async () => {
48 | const option = {
49 | optimize: program.optimize,
50 | withDraft: program.draft
51 | }
52 | const config = readConfigFrom('./siteelm.yaml', option)
53 | const result = await generateAll(config)
54 | if(!result) {
55 | process.exitCode = 1
56 | }
57 | watchAll(config, () => {})
58 | })
59 |
60 | // "siteelm init"
61 | program
62 | .command('init')
63 | .action(async () => {
64 | await initialize()
65 | })
66 |
67 | program.parse(process.argv)
68 |
69 |
--------------------------------------------------------------------------------
/res/scaffold/blog/src/Static/Article.elm:
--------------------------------------------------------------------------------
1 | module Static.Article exposing (main)
2 |
3 | import Element exposing (..)
4 | import Element.Font as Font
5 | import Html exposing (Html)
6 | import Html.Attributes exposing (class)
7 | import Iso8601
8 | import Json.Decode as Decode exposing (Decoder, string)
9 | import Json.Decode.Pipeline exposing (required)
10 | import Markdown
11 | import Siteelm.Page exposing (Page, page)
12 | import Static.View exposing (head, pageLayout)
13 | import Time exposing (Posix)
14 |
15 |
16 | main : Page Preamble
17 | main =
18 | page
19 | { decoder = preambleDecoder
20 | , head = head
21 | , body = body
22 | }
23 |
24 |
25 | type alias Preamble =
26 | { title : String
27 | , date : Posix
28 | }
29 |
30 |
31 | preambleDecoder : Decoder Preamble
32 | preambleDecoder =
33 | Decode.succeed Preamble
34 | |> required "title" string
35 | |> required "date" Iso8601.decoder
36 |
37 |
38 | body : Preamble -> String -> List (Html Never)
39 | body preamble markdown =
40 | let
41 | date =
42 | preamble.date
43 | |> Iso8601.fromTime
44 | |> String.split "T"
45 | |> List.head
46 | |> Maybe.withDefault ""
47 | in
48 | pageLayout <|
49 | column
50 | [ width (maximum 720 fill)
51 | , centerX
52 | , paddingXY 20 0
53 | , spacing 8
54 | ]
55 | [ paragraph
56 | [ Font.size 36
57 | , Font.bold
58 | , centerX
59 | , clipX
60 | , Font.center
61 | ]
62 | [ text preamble.title
63 | ]
64 | , el
65 | [ Font.size 14
66 | , Font.bold
67 | , centerX
68 | ]
69 | (text date)
70 | , paragraph
71 | [ width fill
72 | , paddingXY 0 20
73 | ]
74 | [ html (Markdown.toHtml [ class "md" ] markdown) ]
75 | ]
76 |
--------------------------------------------------------------------------------
/src/config.ts:
--------------------------------------------------------------------------------
1 | import fs from 'fs'
2 | import yaml from 'js-yaml'
3 |
4 | export interface Config {
5 | build: {
6 | dist_dir: string
7 | contents: {
8 | src_dir: string
9 | index: string
10 | exclude?: string[]
11 | draft?: boolean
12 | }
13 | assets: {
14 | src_dir: string
15 | }
16 | elm: {
17 | command?: string
18 | optimize?: boolean
19 | }
20 | static_elm: {
21 | src_dir: string
22 | exclude?: string[]
23 | }
24 | dynamic_elm: {
25 | src_dir: string
26 | exclude?: string[]
27 | }
28 | }
29 | }
30 |
31 |
32 | /**
33 | * @param file config file
34 | * @param option additional options
35 | * @returns object
36 | */
37 | const readConfigFrom = (file: string, option?: {optimize: boolean, withDraft: boolean}): Config => {
38 | const yml = fs.readFileSync(file, 'utf-8')
39 | var conf = yaml.safeLoad(yml)
40 | const opt = option || {optimize: false, withDraft: false}
41 | if(!conf.build.elm) {
42 | conf.build.elm = {}
43 | }
44 | if(fs.existsSync('./package.json') && !conf.build.elm.command) {
45 | conf.build.elm.command = 'npx elm'
46 | }
47 | if(typeof conf.build.elm.optimize !== 'boolean') {
48 | conf.build.elm.optimize = opt.optimize
49 | }
50 | if(typeof conf.build.dist_dir !== 'string') {
51 | conf.build.dist_dir = './dist'
52 | }
53 | if(typeof conf.build.assets.src_dir !== 'string') {
54 | conf.build.assets.src_dir = './assets'
55 | }
56 | if(typeof conf.build.contents.draft !== 'boolean') {
57 | conf.build.contents.draft = opt.withDraft
58 | }
59 | if(!conf.build.static_elm.src_dir || !conf.build.dynamic_elm.src_dir) {
60 | const elm_json = JSON.parse(fs.readFileSync('./elm.json', 'utf-8'))
61 | if(!conf.build.static_elm.src_dir) {
62 | conf.build.static_elm.src_dir = elm_json['source-directories']
63 | }
64 | if(!conf.build.dynamic_elm.src_dir) {
65 | conf.build.dynamic_elm.src_dir = elm_json['source-directories']
66 | }
67 | }
68 | return conf
69 | }
70 |
71 | export default readConfigFrom;
72 |
--------------------------------------------------------------------------------
/res/img/siteelm.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/res/img/about.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/res/scaffold/basic/src/assets/style.css:
--------------------------------------------------------------------------------
1 | * {
2 | box-sizing: border-box;
3 | }
4 |
5 | html{
6 | width: 100%;
7 | background-color: #ffffff;
8 | margin: 0;
9 | padding: 0;
10 | }
11 |
12 | body {
13 | font-family: Questrial, sans-serif;
14 | font-size: 20px;
15 | color: #242424;
16 | margin: 0;
17 | padding: 0;
18 | text-decoration-skip-ink: none;
19 | }
20 |
21 | .main {
22 | max-width: 720px;
23 | margin: auto;
24 | padding-bottom: 96px;
25 | }
26 |
27 | h2 {
28 | background-color: #58637a;
29 | color: #ffffff;
30 | margin: 0;
31 | height: 3rem;
32 | line-height: 3rem;
33 | padding: 0 1rem;
34 | border-radius: 1.5rem;
35 | }
36 |
37 | img {
38 | display: block;
39 | }
40 |
41 | li {
42 | margin-bottom: 0.5rem;
43 | }
44 |
45 | a.prev {
46 | background-color: #faab00;
47 | padding: 0.25rem 1rem;
48 | margin: 0.25rem 1rem;
49 | display: inline-block;
50 | font-size: 1rem;
51 | font-weight: bold;
52 | height: 24px;
53 | width: 72px;
54 | color: #ffffff;
55 | position: relative;
56 | border-radius: 0 12px 12px 0;
57 | }
58 |
59 | a.prev:before {
60 | content: '';
61 | border: 12px solid transparent;
62 | border-right: 12px solid #faab00;
63 | position: absolute;
64 | top: 50%;
65 | left: -24px;
66 | margin-top: -12px;
67 | }
68 | .header {
69 | text-align: center;
70 | }
71 |
72 | .header img.logo {
73 | margin: 12px auto 28px auto;
74 | }
75 |
76 | .stripe {
77 | width: 100%;
78 | background-color: #58637a;
79 | height: 48px;
80 | margin-bottom: 32px;
81 | text-align: left;
82 | padding: 0 1rem;
83 | }
84 |
85 | .inner {
86 | padding: 0.5rem 1.5rem;
87 | }
88 |
89 | ul.products {
90 | padding: 0;
91 | margin: 1rem 0;
92 | list-style: none;
93 | display: flex;
94 | }
95 |
96 | ul.products li {
97 | margin: 0.2rem;
98 | width: 162px;
99 | height: 100px;
100 | background-color: #3db7Cf;
101 | padding: 10px;
102 | display: flex;
103 | }
104 |
105 | ul.products li > div {
106 | display: flex;
107 | align-items: center;
108 | justify-content: center;
109 | }
110 |
111 | ul.products li > div:first-child {
112 | flex: 1.618;
113 | background-color: #ffffff;
114 | border-radius: 2px;
115 | color: #58637a;
116 | font-size: 1rem;
117 | }
118 |
119 | ul.products li > div:nth-child(2) {
120 | flex: 1;
121 | color: #ffffff;
122 | margin-left: 10px;
123 | font-size: 1.0rem;
124 | }
125 |
126 | .counter {
127 | width: 320px;
128 | height: 160px;
129 | padding: 2em;
130 | margin: auto;
131 | display: flex;
132 | align-items: stretch;
133 | }
134 |
135 | .counter > * {
136 | flex: 1;
137 | display: flex;
138 | justify-content: center;
139 | align-items: center;
140 | font-weight: bold;
141 | font-size: 20px;
142 | color: #ffffff;
143 | }
144 |
145 | .counter > div {
146 | background-color: #58637a;
147 | font-family: Questrial, sans-serif;
148 | }
149 |
150 | .counter > button {
151 | border: none;
152 | background-color: #3db7Cf;
153 | font-family: Questrial, sans-serif;
154 | padding: 0;
155 | }
156 |
157 | .counter > *:first-child {
158 | border-radius: 80px 0 0 80px;
159 | }
160 |
161 | .counter > *:last-child {
162 | border-radius: 0 80px 80px 0;
163 | }
164 |
165 | code {
166 | font-size: 14px!important;
167 | }
168 |
--------------------------------------------------------------------------------
/res/scaffold/basic/src/assets/images/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/generator/elmtojs.ts:
--------------------------------------------------------------------------------
1 | import glob from 'glob'
2 | import fs from 'fs'
3 | import tmp from 'tmp'
4 | import {spawn} from 'child_process'
5 | import readline from 'readline'
6 | import path from 'path'
7 | import {Config} from '../config'
8 |
9 | /**
10 | * @param config Config
11 | * @returns a raw javascript code string
12 | */
13 | export const compileStaticElmWith = async (config: Config): Promise => {
14 | const tmpFile = tmp.fileSync({postfix: '.js'})
15 | const srcDir = config.build.static_elm.src_dir || ''
16 | const exclude = config.build.static_elm.exclude || []
17 | const r = await compileElmWith(config, srcDir, exclude, tmpFile.name)
18 | const code = r ? fs.readFileSync(tmpFile.name, 'utf-8') : ''
19 | tmpFile.removeCallback()
20 | return code
21 | }
22 |
23 | /**
24 | * @param config Config
25 | * @returns output file path (absolute in the site)
26 | */
27 | export const compileDynamicElmWith = async (config: Config): Promise => {
28 | const fName = 'dynamic.js'
29 | const outFile = path.join(config.build.dist_dir, fName)
30 | const srcDir = config.build.dynamic_elm.src_dir || ''
31 | const exclude = config.build.dynamic_elm.exclude || []
32 | const r = await compileElmWith(config, srcDir, exclude, outFile)
33 | return r ? `/${path.relative(config.build.dist_dir, outFile)}` : ''
34 | }
35 |
36 | /**
37 | * @param config Config
38 | * @param srcDir source directory
39 | * @param exclude glob patterns to ignore
40 | * @param output a file name to output
41 | * @returns succeeded or not
42 | */
43 | const compileElmWith = async (config: Config, srcDir: string, exclude: string[], output: string): Promise => {
44 | const globOption = {ignore: exclude}
45 | const elmFiles =
46 | glob.sync(`${srcDir}/**/*.elm`, globOption)
47 | if(elmFiles.length === 0) {
48 | return true
49 | }
50 |
51 | console.log(`Elm: ${srcDir} (${elmFiles.join(', ')})`)
52 |
53 | // considering "elm" and "npx elm"
54 | const command = (config.build.elm.command || 'elm').split(' ')
55 | const args = [
56 | command.slice(1),
57 | ["make"],
58 | elmFiles,
59 | [config.build.elm.optimize ? "--optimize" : ""],
60 | [`--output=${output}`]
61 | ]
62 | .flat()
63 | .filter((x: string) => x.length > 0)
64 | const r = await promiseSpawn(command[0], args)
65 | return r === 0
66 | }
67 |
68 | const promiseSpawn = (command: string, args: string[]) =>
69 | new Promise((resolve, reject) => {
70 | const sp = spawn(command, args, {stdio: 'pipe'})
71 | var msgOk: string[] = []
72 | var msgNg: string[] = []
73 | readline
74 | .createInterface({input: sp.stdout, terminal: false})
75 | .on('line', data => {
76 | msgOk.push(data)
77 | })
78 |
79 | readline
80 | .createInterface({input: sp.stderr, terminal: false})
81 | .on('line', data => {
82 | msgNg.push(data)
83 | })
84 |
85 | sp.on('close', code => {
86 | switch (code) {
87 | case 0:
88 | formatPrintChildProcsssStdOut(msgOk)
89 | resolve(0)
90 | break
91 | default:
92 | formatPrintChildProcsssStdOut(msgNg)
93 | reject(code)
94 | break
95 | }
96 | })
97 | })
98 |
99 | const formatPrintChildProcsssStdOut = (stdout: string[]): void => {
100 | //var duplicate: string[] = []
101 | stdout
102 | .filter(x => typeof x === 'string' && x.trim() !== '')
103 | .forEach(x => {
104 | console.log(` elm: ${x}`)
105 | })
106 | }
107 |
108 |
109 |
--------------------------------------------------------------------------------
/src/generator.ts:
--------------------------------------------------------------------------------
1 | import fs from 'fs-extra'
2 | import path from 'path'
3 | import glob from 'glob'
4 | import jsToHtmlWith from './generator/jstohtml'
5 | import {Config} from './config'
6 | import {compileStaticElmWith, compileDynamicElmWith} from './generator/elmtojs'
7 |
8 | class Cache {
9 | staticCode: string = ""
10 | dynamicCode: string = ""
11 | autoReload: boolean = false
12 | option?: {isServer?: boolean}
13 | constructor(staticCode: string, dynamicCode: string, autoReload: boolean, option?: {isServer?: boolean} ) {
14 | this.staticCode = staticCode
15 | this.dynamicCode = dynamicCode
16 | this.autoReload = autoReload
17 | this.option = option
18 | }
19 | }
20 | var cache = new Cache("", "", false)
21 |
22 |
23 | /**
24 | * main function for generating the site
25 | * @param config
26 | * @param isServer
27 | */
28 | const generateAll = async (config: Config, option?: {isServer?: boolean}): Promise => {
29 | console.log(`START: ${(new Date).toISOString()}`)
30 | // 1. generate static pages
31 | const elm = await compileStaticElmWith(config)
32 | const appjs = await compileDynamicElmWith(config)
33 | const contentFiles =
34 | glob.sync(`${config.build.contents.src_dir}/**/*`, {ignore: config.build.contents.exclude || [], nodir: true})
35 | const autoReload = (option || {}).isServer || false
36 | var result: {ok: string[], ng: string[]} = {ok: [], ng: []}
37 | contentFiles.forEach(x => {
38 | const r = convertAndSave(x, config, elm, appjs, autoReload)
39 | if (r) {
40 | result.ok.push(x)
41 | } else {
42 | result.ng.push(x)
43 | }
44 | })
45 |
46 | // 3. show result
47 | console.log('RESULT:')
48 | console.log(` OK: ${result.ok.length}`)
49 | result.ng.forEach((x, i) => {
50 | console.log(` NG(${i+1}): ${x}`)
51 | })
52 |
53 | // cache
54 | cache = new Cache(elm, appjs, autoReload, option)
55 |
56 | return (result.ok.length > 0 && result.ng.length === 0)
57 | }
58 |
59 | /**
60 | * main function for generating the site
61 | * @param config
62 | */
63 | const copyAssets = (config: Config) => {
64 | fs.copySync(config.build.assets.src_dir, config.build.dist_dir)
65 | }
66 |
67 | /**
68 | * using the cache, convert a content file to a static html and save it
69 | * @param file path of a content file
70 | * @param config
71 | */
72 | const convertOnlyContentFiles = (config: Config): void => {
73 | if(!cache.staticCode) {
74 | console.log("Build All")
75 | generateAll(config, cache.option)
76 | } else {
77 | var result: {ok: string[], ng: string[]} = {ok: [], ng: []}
78 | const contentFiles =
79 | glob.sync(`${config.build.contents.src_dir}/**/*`, {ignore: config.build.contents.exclude || [], nodir: true})
80 | contentFiles.forEach(x => {
81 | const r = convertAndSave(x, config, cache.staticCode, cache.dynamicCode, cache.autoReload)
82 | if (r) {
83 | result.ok.push(x)
84 | } else {
85 | result.ng.push(x)
86 | }
87 | })
88 | // 3. show result
89 | console.log('RESULT:')
90 | console.log(` OK: ${result.ok.length}`)
91 | result.ng.forEach((x, i) => {
92 | console.log(` NG(${i+1}): ${x}`)
93 | })
94 | }
95 | }
96 |
97 |
98 | /**
99 | * create a path for saving
100 | * @param file path of a content file
101 | * @param config
102 | */
103 | const savePathFor = (file: string, config: Config): string => {
104 | if (file === config.build.contents.index) {
105 | return path.join(config.build.dist_dir, 'index.html')
106 | } else {
107 | const r = path.relative(config.build.contents.src_dir, file)
108 | const p = path.parse(r)
109 | return path.join(config.build.dist_dir, p.dir, p.name, 'index.html')
110 | }
111 | }
112 |
113 | /**
114 | * convert a content file to a static html and save it
115 | * @param file path of a content file
116 | * @param config
117 | * @param elmcode a raw javascript code string
118 | * @param appjs path for the dynamic elm code
119 | * @param autoReloader enable auto reloading
120 | */
121 | const convertAndSave = (file: string, config: Config, elmcode: string, appjs: string, autoReloader: boolean): boolean => {
122 | console.log(`> ${file}`)
123 | const savePath = savePathFor(file, config)
124 | const draft = config.build.contents.draft || false
125 | const html = jsToHtmlWith(file, config.build.contents.src_dir, elmcode, appjs, draft, autoReloader, config.build.contents.exclude || [])
126 | if(html !== '') {
127 | // console.log(`< ${savePath}`)
128 | fs.ensureFileSync(savePath)
129 | fs.writeFileSync(savePath, html)
130 | return true
131 | } else {
132 | console.log('error: check if the preamble is wrong form or head and body output nothing.')
133 | console.log('ERROR: Failed to convert!')
134 | return false
135 | }
136 | }
137 |
138 | export {generateAll as default, convertOnlyContentFiles, copyAssets}
139 |
--------------------------------------------------------------------------------
/res/scaffold/basic/src/Static/Basic.elm:
--------------------------------------------------------------------------------
1 | module Static.Basic exposing (main)
2 |
3 | import Html exposing (Html, a, div, h2, img, li, text, ul)
4 | import Html.Attributes exposing (alt, class, href, name, src)
5 | import Json.Decode as D exposing (Decoder)
6 | import Markdown
7 | import Siteelm.Html as Html
8 | import Siteelm.Html.Attributes exposing (charset, content, rel)
9 | import Siteelm.Page exposing (Page, page)
10 | import Static.View as View
11 |
12 |
13 | main : Page Preamble
14 | main =
15 | page
16 | { decoder = preambleDecoder
17 | , head = viewHead
18 | , body = viewBody
19 | }
20 |
21 |
22 | {-| Preamble is what you write on the head of the content files.
23 | -}
24 | type alias Preamble =
25 | { title : String
26 | , products : List Product
27 | , recommends : List Product
28 | , articles : List Article
29 | }
30 |
31 |
32 | type alias Product =
33 | { name : String
34 | , price : Int
35 | }
36 |
37 |
38 | type alias Article =
39 | { url : String
40 | , title : String
41 | , date : String
42 | }
43 |
44 |
45 | {-| Preamble is passed as a JSON string. So it requires a decoder.
46 | -}
47 | preambleDecoder : Decoder Preamble
48 | preambleDecoder =
49 | D.map4 Preamble
50 | (D.field "title" D.string)
51 | (D.field "products" (D.list productDecoder))
52 | (D.field "recommends" (D.list productDecoder))
53 | (D.field "articles" (D.list articleDecoder))
54 |
55 |
56 | productDecoder : Decoder Product
57 | productDecoder =
58 | D.map2 Product
59 | (D.field "name" D.string)
60 | (D.field "price" D.int)
61 |
62 |
63 | articleDecoder : Decoder Article
64 | articleDecoder =
65 | D.map3 Article
66 | (D.field "url" D.string)
67 | (D.field "title" D.string)
68 | (D.field "date" D.string)
69 |
70 |
71 | {-| Make contents inside the _head_ tag.
72 | -}
73 | viewHead : Preamble -> String -> List (Html Never)
74 | viewHead preamble _ =
75 | [ Html.meta [ charset "utf-8" ]
76 | , Html.title [] (preamble.title ++ " | siteelm")
77 | , Html.meta [ name "description", content "this is a simple static site generator for elm" ]
78 | , Html.link [ rel "stylesheet", href "/style.css" ]
79 | , Html.link [ rel "stylesheet", href "https://fonts.googleapis.com/css?family=Questrial&display=swap" ]
80 | , Html.link [ rel "stylesheet", href "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.15.10/styles/default.min.css" ]
81 | , Html.script "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.15.10/highlight.min.js" ""
82 | , Html.script "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.15.10/languages/elm.min.js" ""
83 | , Html.script "" "hljs.initHighlightingOnLoad();"
84 | ]
85 |
86 |
87 | {-| Make contents inside the _body_ tag. The parameter "body" is usually something like markdown.
88 | -}
89 | viewBody : Preamble -> String -> List (Html Never)
90 | viewBody preamble body =
91 | [ View.header
92 | , div
93 | [ class "main" ]
94 | [ img [ src "/images/header.svg", alt "" ] []
95 | , div []
96 | [ h2 [] [ text "body text" ]
97 | , div [ class "inner" ]
98 | [ text "since the body text is just a string, give it to a markdown parser"
99 | , Markdown.toHtml [] body
100 | ]
101 | ]
102 | , div []
103 | [ h2 [] [ text "dynamic components" ]
104 | , div [ class "inner" ]
105 | [ text "with Browser.element, dynamic contents can be embedded"
106 | , Html.dynamic
107 | { moduleName = "Dynamic.Counter"
108 | , flags = "{value: 100}"
109 | }
110 | ]
111 | ]
112 | , div []
113 | [ h2 [] [ text "preamble" ]
114 | , div [ class "inner" ]
115 | [ text "you can write YAML in the preamble section"
116 | , viewProducts preamble.products
117 | , text "external YAML files can be imported"
118 | , viewProducts preamble.recommends
119 | ]
120 | ]
121 | , div []
122 | [ h2 [] [ text "get preambles in a directory" ]
123 | , div [ class "inner" ]
124 | [ div []
125 | [ text "Use \"preamblesIn\" parameter to get preambles of files in a specified directory."
126 | ]
127 | , div []
128 | [ text "Be aware that there are some parameters which Siteelm automatically sets in an preamble. At the moment, a property \"url\" is that."
129 | ]
130 | , ul []
131 | (List.map
132 | viewArticle
133 | preamble.articles
134 | )
135 | ]
136 | ]
137 | ]
138 | ]
139 |
140 |
141 | viewArticle : Article -> Html Never
142 | viewArticle article =
143 | li []
144 | [ a [ href article.url ]
145 | [ text article.title
146 | ]
147 | ]
148 |
149 |
150 | viewProducts : List Product -> Html Never
151 | viewProducts =
152 | ul [ class "products" ]
153 | << List.map
154 | (\x ->
155 | li []
156 | [ div [] [ text x.name ]
157 | , div [] [ text ("¥" ++ String.fromInt x.price) ]
158 | ]
159 | )
160 |
--------------------------------------------------------------------------------
/res/scaffold/blog/src/Static/View.elm:
--------------------------------------------------------------------------------
1 | module Static.View exposing (head, headerImage, pageLayout)
2 |
3 | import Element exposing (..)
4 | import Element.Background as Bg
5 | import Element.Font as Font
6 | import Html exposing (Html)
7 | import Html.Attributes exposing (href, name)
8 | import Siteelm.Html as Html
9 | import Siteelm.Html.Attributes exposing (charset, content, rel)
10 |
11 |
12 | maxWidth : Int
13 | maxWidth =
14 | 900
15 |
16 |
17 | bgColor : Color
18 | bgColor =
19 | rgb255 232 217 188
20 |
21 |
22 | head : { a | title : String } -> String -> List (Html Never)
23 | head preamble _ =
24 | [ Html.meta [ charset "utf-8" ]
25 | , Html.title [] (preamble.title ++ " | siteelm blog")
26 | , Html.meta [ name "description", content "siteelm blog template" ]
27 | , Html.meta [ name "viewport", content "width=device-width,initial-scale=1" ]
28 | , Html.link [ rel "stylesheet", href "/markdown.css" ]
29 | , Html.link [ rel "stylesheet", href "/counter.css" ]
30 | , Html.link [ rel "stylesheet", href "https://fonts.googleapis.com/css?family=Questrial&display=swap" ]
31 | , Html.link [ rel "stylesheet", href "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.15.10/styles/default.min.css" ]
32 | , Html.script "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.15.10/highlight.min.js" ""
33 | , Html.script "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.15.10/languages/elm.min.js" ""
34 | , Html.script "" "hljs.initHighlightingOnLoad();"
35 | ]
36 |
37 |
38 | pageLayout : Element Never -> List (Html Never)
39 | pageLayout child =
40 | List.singleton <|
41 | layout
42 | [ width fill
43 | , height fill
44 | , Font.color (rgb255 66 66 66)
45 | ]
46 | (column
47 | [ width fill
48 | , height fill
49 | ]
50 | [ header
51 | , el
52 | [ width fill
53 | , height fill
54 | , Bg.color bgColor
55 | , paddingXY 0 20
56 | ]
57 | (el
58 | [ width (maximum maxWidth fill)
59 | , centerX
60 | ]
61 | child
62 | )
63 | , footer
64 | ]
65 | )
66 |
67 |
68 | header : Element Never
69 | header =
70 | let
71 | content ( url, name ) =
72 | link
73 | [ alignRight
74 | , paddingXY 8 0
75 | ]
76 | { url = url
77 | , label = text name
78 | }
79 | in
80 | column
81 | [ width (maximum maxWidth fill)
82 | , centerX
83 | ]
84 | [ row
85 | [ width fill ]
86 | [ link []
87 | { url = "/"
88 | , label =
89 | image
90 | [ width (px 128)
91 | , height (px 96)
92 | ]
93 | { src = "/images/logo.png"
94 | , description = "siteelm"
95 | }
96 | }
97 | , wrappedRow
98 | [ Font.bold
99 | , Font.size 14
100 | , padding 0
101 | , width fill
102 | ]
103 | (List.map content
104 | [ ( "/about", "ABOUT" )
105 | , ( "/installation", "INSTALLATION" )
106 | ]
107 | ++ [ link []
108 | { url = "https://github.com/nikueater/siteelm"
109 | , label =
110 | image
111 | [ width (px 48)
112 | , height (px 48)
113 | ]
114 | { src = "/images/github.svg"
115 | , description = "github"
116 | }
117 | }
118 | ]
119 | )
120 | ]
121 | ]
122 |
123 |
124 | footer : Element Never
125 | footer =
126 | el
127 | [ width fill
128 | , Bg.color bgColor
129 | , paddingXY 0 8
130 | ]
131 | (row
132 | [ centerX
133 | , Font.size 12
134 | ]
135 | [ text "powered by "
136 | , link [ Font.color (rgb255 80 80 250) ]
137 | { url = "https://github.com/nikueater/siteelm"
138 | , label = text "siteelm"
139 | }
140 | ]
141 | )
142 |
143 |
144 | headerImage : { title : String, src : String } -> Element Never
145 | headerImage { title, src } =
146 | el
147 | [ width (maximum maxWidth fill)
148 | , height (px 300)
149 | , clipX
150 | , clipY
151 | ]
152 | <|
153 | el
154 | [ width fill
155 | , height fill
156 | , padding 16
157 | , clipX
158 | , behindContent
159 | (image
160 | [ height (maximum 300 fill)
161 | , centerX
162 | , clipX
163 | ]
164 | { src = src
165 | , description = ""
166 | }
167 | )
168 | , Font.color (rgb255 250 250 250)
169 | , Font.size 32
170 | , Font.bold
171 | ]
172 | (el [ centerY ] (text title))
173 |
--------------------------------------------------------------------------------
/res/scaffold/blog/src/Static/Home.elm:
--------------------------------------------------------------------------------
1 | module Static.Home exposing (main)
2 |
3 | import Element exposing (..)
4 | import Element.Background as Bg
5 | import Element.Border as Border
6 | import Element.Font as Font
7 | import Html exposing (Html)
8 | import Html.Attributes exposing (class)
9 | import Iso8601
10 | import Json.Decode as Decode exposing (Decoder, int, list, string)
11 | import Json.Decode.Pipeline exposing (optional, required)
12 | import Markdown
13 | import Siteelm.Html as Html
14 | import Siteelm.Page exposing (Page, page)
15 | import Static.View exposing (head, headerImage, pageLayout)
16 | import Time exposing (Posix)
17 |
18 |
19 | main : Page Preamble
20 | main =
21 | page
22 | { decoder = preambleDecoder
23 | , head = head
24 | , body = body
25 | }
26 |
27 |
28 | {-| Preambles is json decodable data in the head part of your md files.
29 | When you pass a directory path to "preamblesIn," preambles sections of all files in the directory will be passed.
30 | Also you can give a path to external yaml files with an "external" property.
31 | -}
32 | type alias Preamble =
33 | { title : String
34 | , articles : List Article
35 | , products : List Product
36 | }
37 |
38 |
39 | {-| The field "url" is automatically given by Siteelm even though you don't write it.
40 | -}
41 | type alias Article =
42 | { url : String
43 | , title : String
44 | , date : Posix
45 | }
46 |
47 |
48 | type alias Product =
49 | { name : String
50 | , price : Int
51 | }
52 |
53 |
54 | preambleDecoder : Decoder Preamble
55 | preambleDecoder =
56 | Decode.succeed Preamble
57 | |> required "title" string
58 | |> optional "articles" (list articleDecoder) []
59 | |> optional "products" (list productDecoder) []
60 |
61 |
62 | articleDecoder : Decoder Article
63 | articleDecoder =
64 | Decode.succeed Article
65 | |> required "url" string
66 | |> required "title" string
67 | |> required "date" Iso8601.decoder
68 |
69 |
70 | productDecoder : Decoder Product
71 | productDecoder =
72 | Decode.succeed Product
73 | |> required "name" string
74 | |> required "price" int
75 |
76 |
77 | body : Preamble -> String -> List (Html Never)
78 | body preamble markdown =
79 | pageLayout <|
80 | column
81 | [ width fill, clipX ]
82 | [ headerImage
83 | { src = "images/header_01.jpg"
84 | , title = preamble.title
85 | }
86 | , paragraph []
87 | [ html (Markdown.toHtml [ class "md" ] markdown)
88 | ]
89 | , articles preamble.articles
90 | , demo preamble
91 | ]
92 |
93 |
94 | articles : List Article -> Element Never
95 | articles items =
96 | column
97 | [ width fill
98 | , height fill
99 | ]
100 | (items
101 | |> List.sortWith
102 | (\a b ->
103 | case compare (Time.posixToMillis a.date) (Time.posixToMillis b.date) of
104 | LT ->
105 | GT
106 |
107 | _ ->
108 | LT
109 | )
110 | |> List.map article
111 | )
112 |
113 |
114 | article : Article -> Element Never
115 | article item =
116 | let
117 | date =
118 | item.date
119 | |> Iso8601.fromTime
120 | |> String.split "T"
121 | |> List.head
122 | |> Maybe.withDefault ""
123 | in
124 | link
125 | [ width fill
126 | , Border.widthEach
127 | { top = 0
128 | , left = 0
129 | , right = 0
130 | , bottom = 1
131 | }
132 | , Border.dashed
133 | ]
134 | { url = item.url
135 | , label =
136 | row
137 | [ spacing 8
138 | , paddingEach
139 | { top = 10
140 | , left = 0
141 | , right = 0
142 | , bottom = 0
143 | }
144 | ]
145 | [ el
146 | [ Font.size 16
147 | , alignBottom
148 | ]
149 | (text date)
150 | , el
151 | [ Font.size 20
152 | , alignBottom
153 | , Font.bold
154 | , Font.color (rgb255 80 80 250)
155 | ]
156 | (text item.title)
157 | ]
158 | }
159 |
160 |
161 | demo : Preamble -> Element Never
162 | demo preamble =
163 | let
164 | caption title =
165 | el
166 | [ Font.size 24
167 | , Font.bold
168 | ]
169 | (text title)
170 |
171 | product p =
172 | row
173 | [ width (px 200)
174 | , height (px 100)
175 | , Bg.color (rgb255 61 183 207)
176 | , padding 6
177 | ]
178 | [ el
179 | [ width fill
180 | , height fill
181 | , centerX
182 | , Font.color
183 | (rgb 1 1 1)
184 | , Bg.color (rgb255 255 255 255)
185 | , Border.rounded 8
186 | ]
187 | (el
188 | [ Font.color (rgb255 61 187 207)
189 | , centerX
190 | , centerY
191 | ]
192 | (text p.name)
193 | )
194 | , el
195 | [ width fill
196 | , height fill
197 | ]
198 | (el
199 | [ Font.color (rgb255 255 255 255)
200 | , centerX
201 | , centerY
202 | ]
203 | (text ("¥" ++ String.fromInt p.price))
204 | )
205 | ]
206 | in
207 | column
208 | [ paddingXY 0 20
209 | , width fill
210 | , spacing 8
211 | , Font.size 16
212 | ]
213 | [ el
214 | [ Font.size 32
215 | , Font.bold
216 | ]
217 | (text "Demo")
218 | , caption "preamble"
219 | , paragraph
220 | [ width fill ]
221 | [ text "You can write YAML in the preamble section. And external YAML files can be imported."
222 | , wrappedRow
223 | [ spacing 8
224 | , centerX
225 | ]
226 | (List.map product preamble.products)
227 | ]
228 | , caption "dynamic element"
229 | , paragraph [ width fill ]
230 | [ text "With Browser.element, dynamic contents can be embedded."
231 | , html <|
232 | Html.dynamic
233 | { moduleName = "Dynamic.Counter"
234 | , flags = "{value: 100}"
235 | }
236 | ]
237 | ]
238 |
--------------------------------------------------------------------------------
/src/generator/jstohtml.ts:
--------------------------------------------------------------------------------
1 | import fs from 'fs'
2 | import glob from 'glob'
3 | import {JSDOM, VirtualConsole} from 'jsdom'
4 | import P from 'parsimmon'
5 | import yaml from 'js-yaml'
6 | import path from 'path'
7 | import {minify} from 'html-minifier'
8 | import traverse from 'traverse'
9 | import {staticElmInitCode, dynamicElmInitCode, autoReloaderCode} from './snippet'
10 |
11 | interface Preamble {
12 | module: string
13 | url: string
14 | draft?: boolean
15 | }
16 |
17 | class ConvertError implements Error {
18 | public name = 'ConvertError'
19 | constructor(public message: string) {}
20 | toString():string {
21 | return `${this.name}: ${this.message}`
22 | }
23 | }
24 | class InvalidPreambleError extends ConvertError { name = 'Preamble' }
25 |
26 | /**
27 | * @param source a file name for creating flags
28 | * @param elmcode a raw javascript code string
29 | * @param appjs the path for the dynamic elm code
30 | * @param root root directory of pages
31 | * @param withDraft flag for not ignoring drafts
32 | * @param autoReloader
33 | * @param excludes which are excluded by indexing
34 | * @returns void
35 | */
36 | const jsToHtmlWith = (sourcePath: string, srcDir: string, elmcode: string, appjsPath: string, withDraft: boolean, autoReloader: boolean, excludes: string[]): string => {
37 | try {
38 | // create flags
39 | const document = parseDocument(fs.readFileSync(sourcePath, 'utf-8'))
40 | const p = parsePreamble(document[0], sourcePath, `${srcDir}/*`, excludes)
41 | const flags = {
42 | preamble: JSON.stringify(p),
43 | body: document[1]
44 | }
45 | if(p.draft == true && !withDraft) {
46 | return ''
47 | }
48 | // generate a DOM
49 | const vc = new VirtualConsole()
50 | vc.on('info', (x: string) => {
51 | console.log(`info: ${x}`)
52 | })
53 | vc.on('warn', (x: string) => {
54 | if(x.startsWith('Compiled in DEV mode.')) { return }
55 | console.log(`warn: ${x}`)
56 | })
57 | vc.on('error', (x: string) => {
58 | console.log(`error: ${x}`)
59 | })
60 | const dom = new JSDOM('', {runScripts: 'outside-only', virtualConsole: vc})
61 | dom.window.eval(elmcode)
62 | dom.window.eval(staticElmInitCode(p.module, flags))
63 | const body = unescapeScripts(dom).window.document.body.innerHTML
64 |
65 | // formatting
66 | var ds = new JSDOM(body, {runScripts: 'outside-only'})
67 | const head = ds.window.document.querySelector('head')
68 | if(ds.window.document.body.innerHTML === '') {
69 | return ''
70 | }
71 | if(head) {
72 | ds.window.document.querySelectorAll('style').forEach(x => {
73 | const styleParent = x.parentNode
74 | head.appendChild(x)
75 | if(styleParent?.parentNode && !styleParent.hasChildNodes()) {
76 | styleParent.parentNode.removeChild(styleParent)
77 | }
78 | })
79 | // add dynamic elm elements
80 | if(appjsPath !== '') {
81 | ds = embedDynamicComponents(ds, appjsPath)
82 | }
83 | }
84 |
85 | // auto reloader
86 | if (autoReloader) {
87 | const s = ds.window.document.createElement('script')
88 | s.textContent = autoReloaderCode()
89 | ds.window.document.body.appendChild(s)
90 | }
91 | const html = `\n${ds.serialize()}`
92 | // turn the DOM into string and save it
93 | return minify(html, {minifyCSS: true, minifyJS: true})
94 | } catch(e) {
95 | console.error('error:')
96 | console.error(e.toString())
97 | }
98 | return ''
99 | }
100 |
101 | /**
102 | * @param source a string which has a preamble wrapped with "---" and a free text
103 | * @returns a preamble and a body
104 | */
105 | const parseDocument = (source: string): string[] => {
106 | const delimiter = P.string("---").skip(P.optWhitespace)
107 | var ls = ""
108 | const mbody = P.takeWhile(c => {
109 | const result = ls !== "\n---"
110 | ls = (ls + c).slice(-4)
111 | return result
112 | })
113 | const matter = delimiter.then(mbody).map(x => x.replace(/(---)$/, ''))
114 | const content = P.all.map(x => x.trim())
115 | const doc = P.seq(matter, content).parse(source)
116 | return 'value' in doc ? doc.value : []
117 | }
118 |
119 | /**
120 | * @param p yaml format string
121 | * @param source path of the current source file
122 | * @param root root directory of pages
123 | * @param excludes which are exclued by indexing
124 | * @param processed list of files have been parsed
125 | * @returns preamble interface data
126 | */
127 | const parsePreamble = (p: string, source: string, root: string, excludes: string[], processed: string[] = []): Preamble => {
128 | const yml = yaml.safeLoad(p)
129 | const preamble = ((x: any): Preamble => x)(yml)
130 | if(typeof preamble.module !== 'string') {
131 | throw new InvalidPreambleError('no "module"')
132 | }
133 | if(typeof preamble.draft !== 'boolean') {
134 | preamble.draft = false
135 | }
136 | if(preamble.url) {
137 | throw new InvalidPreambleError("you can't use \"url\" at the top level")
138 | }
139 | let dir = path.dirname(root)
140 | let rel = path.relative(dir, source)
141 | let ext = path.extname(rel)
142 | let file = path.basename(rel, ext)
143 | let url = path.join(path.dirname(rel), path.basename(rel, ext))
144 | if(file == 'index') {
145 | url = path.dirname(rel)
146 | }
147 | if(url == '.') {
148 | url = ''
149 | }
150 | preamble.url = `/${url}`
151 | return parseYaml(preamble, source, root, excludes, processed)
152 | }
153 |
154 | const parseYaml = (preamble: Preamble, source: string, root: string, excludes: string[], processed: string[]): Preamble => {
155 | // walk through all element to detect special values
156 | processed.push(path.normalize(source))
157 | traverse(preamble).forEach(function(x) {
158 | switch(this.key) {
159 | case 'external':
160 | const dir = path.dirname(source)
161 | const file = x || ''
162 | const newSource = path.normalize(path.join(dir, file))
163 | const y = fs.readFileSync(newSource, 'utf-8')
164 | const value = yaml.safeLoad(y)
165 | if(this.parent) {
166 | if(Object.keys(this.parent.node).length === 1) {
167 | this.parent.update(value)
168 | } else {
169 | throw new InvalidPreambleError('"external" cannot have siblings')
170 | }
171 | }
172 | preamble = parseYaml(preamble, newSource, root, excludes, processed)
173 | break
174 | case 'preamblesIn':
175 | const contentDir = path.normalize(path.join(path.dirname(source), x))
176 | const contentFiles =
177 | glob.sync(path.join(contentDir, '*'), {ignore: excludes || [], nodir: true})
178 | var ps : any[] = []
179 | contentFiles
180 | .map(x => path.normalize(x))
181 | .filter(x => !processed.includes(x))
182 | .forEach(x => {
183 | const document = parseDocument(fs.readFileSync(x, 'utf-8'))
184 | ps.push(parsePreamble(document[0], x, root, excludes))
185 | })
186 | if(this.parent) {
187 | this.parent.update(ps)
188 | }
189 | break
190 | }
191 | })
192 | return preamble
193 | }
194 |
195 | /**
196 | * @param dom JSDOM
197 | * @param appjs path for a js file from elm
198 | */
199 | const embedDynamicComponents = (dom: JSDOM, appjs: string): JSDOM => {
200 | const script = dom.window.document.createElement('script')
201 | const head = dom.window.document.querySelector('head')
202 | if(!head) {
203 | return dom
204 | }
205 | script.src = appjs
206 | head.appendChild(script)
207 | var treateds: string[] = []
208 | Array.from(dom.window.document.querySelectorAll('div[data-elm-module]'))
209 | .map(target => {
210 | const flags = target.getAttribute('data-flags') || '{}'
211 | const uniqueKey = Buffer.from(flags).toString('base64')
212 | target.setAttribute('data-unique-key', uniqueKey)
213 | return target
214 | })
215 | .forEach(x => {
216 | const modName = x.getAttribute('data-elm-module') || ''
217 | const flags = x.getAttribute('data-flags') || '{}'
218 | const uniqueKey = x.getAttribute('data-unique-key') || ''
219 | const treatedKey = [modName, uniqueKey].join('-')
220 | if(treateds.includes(treatedKey)) {
221 | return
222 | } else {
223 | treateds.push(treatedKey)
224 | }
225 | const script = dom.window.document.createElement('script')
226 | script.textContent = dynamicElmInitCode(modName, flags, uniqueKey)
227 | dom.window.document.body.appendChild(script)
228 | })
229 | return dom
230 | }
231 |
232 | /**
233 | * @param dom
234 | */
235 | const unescapeScripts = (dom: JSDOM): JSDOM => {
236 | const customs =
237 | dom.window.document.querySelectorAll('siteelm-custom[data-tag="script"]') || []
238 |
239 | customs.forEach((x) => {
240 | const parent = x.parentElement
241 | if(!parent) {
242 | return
243 | }
244 | const script = dom.window.document.createElement('script')
245 | const attrs = x.attributes
246 | script.textContent = x.textContent
247 | for(var i = 0; i < attrs.length; i++) {
248 | const attr = attrs.item(i)
249 | if(attr && attr.nodeValue && !attr.nodeName.startsWith('data-')) {
250 | script.setAttribute(attr.nodeName, attr.nodeValue)
251 | }
252 | }
253 | parent.insertBefore(script, x.nextSibling)
254 | parent.removeChild(x)
255 | })
256 | return dom
257 | }
258 |
259 |
260 | export default jsToHtmlWith
261 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | Copyright 2019 nikueater
179 |
180 | Licensed under the Apache License, Version 2.0 (the "License");
181 | you may not use this file except in compliance with the License.
182 | You may obtain a copy of the License at
183 |
184 | http://www.apache.org/licenses/LICENSE-2.0
185 |
186 | Unless required by applicable law or agreed to in writing, software
187 | distributed under the License is distributed on an "AS IS" BASIS,
188 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
189 | See the License for the specific language governing permissions and
190 | limitations under the License.
191 |
--------------------------------------------------------------------------------