A Democratic Social Network. Currently available at https://democraticnet.de The Test Server is available at https://test.democraticnet.de
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
DemNet/patch.diff

14544 lines
421 KiB

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index a04b04fd..d23e81f2 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -14,7 +14,7 @@ deploy_to_test_server:
- echo "$SSH_PRIVATE_KEY_TESTING" | ssh-add -
- echo "$SSH_KNOWN_HOSTS_TEST" > ~/.ssh/known_hosts
script:
- - ssh root@$TESTING_HOST /root/DemNet/update-server
+ - ssh root@$TESTING_HOST /root/DemNet/update-server $CI_COMMIT_REF_NAME
testing:
stage: test
@@ -45,6 +45,6 @@ production:
- echo "$SSH_PRIVATE_KEY_DEPLOYMENT" | ssh-add -
- echo "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts
script:
- - ssh root@$DEPLOYMENT_HOST /root/DemNet/update-server
+ - ssh root@$DEPLOYMENT_HOST /root/DemNet/update-server master
only:
- master
diff --git a/Dockerfile.clock b/Dockerfile.clock
index d4323c2a..48c01395 100644
--- a/Dockerfile.clock
+++ b/Dockerfile.clock
@@ -6,6 +6,8 @@ RUN git config --global user.name "The Users of the Democraticnet"
COPY . /app
WORKDIR /app
+RUN apt update -y
+RUN apt install cron -y
RUN pip3 install -r requirements.txt
-
-CMD ["python3", "clock.py"]
+RUN crontab cron.clock
+CMD ["cron"," -f"]
diff --git a/Server/API.py b/Server/API.py
index d8abae03..68c61efd 100644
--- a/Server/API.py
+++ b/Server/API.py
@@ -252,8 +252,8 @@ class Message(Resource):
except db.DoesNotExist:
response = errors["does not exist"]
except Exception as e:
- raise e
response = errors["unknown error"]
+ raise e
finally:
return jsonify(response)
@@ -333,7 +333,9 @@ class Message(Resource):
raise e
response = errors["unknown error"]
finally:
- return jsonify(response)
+ status = 400 if type(response) == int else 200
+ response = flask.make_response(jsonify(response), status)
+ return response
def delete(self, message_id):
"""Deleting the message if the client is the author.
@@ -464,19 +466,13 @@ class Messages(Resource):
parser.add_argument("is_public", type = lambda x: x == "1")
args = parser.parse_args()
- if args["response_to"]:
- response_to = db.Message.get_by_id(args["response_to"])
- else:
- response_to = None
-
- message = db.Message.create ( title = args["title"]
- , content = args["content"]
- , response_to = response_to
- , publishing_date = datetime.date.today()
- , keywords = args["keywords"].upper() if args["keywords"] != None else None
- , author = db.User.get_by_id(session["username"])
- , is_public = args["is_public"] if args["is_public"] is not None else True
- )
+ author = db.User.get_by_id(session["username"])
+ message = author.publish( args["title"]
+ , args["content"]
+ , response_to = args["response_to"]
+ , keywords = args["keywords"]
+ , is_public = args["is_public"] if args["is_public"] is not None else True
+ )
response = db.message_to_dict(message)
diff --git a/Server/Database.py b/Server/Database.py
index 2c924952..0e6b11d5 100644
--- a/Server/Database.py
+++ b/Server/Database.py
@@ -9,6 +9,7 @@ import peeweedbevolve
from typing import List, Dict
from urllib.parse import urlparse
import requests, json
+import base64
from Crypto.Hash import SHA256
from Crypto.Random import get_random_bytes
@@ -100,17 +101,25 @@ class User(BaseModel):
, content : str
, response_to : int = None
, keywords : str = ""
+ , **kwargs
):
if response_to != None:
response_to = Message.get_by_id(int(response_to))
- return Message.create ( author = self
- , title = title
- , content = content
- , response_to = response_to
- , publishing_date = datetime.date.today()
- , keywords = keywords
- )
+ message = Message.create( author = self
+ , title = title
+ , content = content
+ , response_to = response_to
+ , publishing_date = datetime.date.today()
+ , keywords = keywords
+ , **kwargs
+ )
+ m = message_to_dict(message)
+ m = { k : m[k] for k in m if k in ["title", "content", "keywords", "author"] }
+ m = json.dumps(m)
+ message.hash = base64.b64encode(SHA256.new(data = m.encode("utf-8")).digest(), altchars = b"-_").decode()
+ message.save()
+ return message
def respond(self, title : str, content : str, response_to : int = None, **kwargs):
return self.publish(title, content, response_to = response_to, **kwargs)
@@ -231,6 +240,7 @@ class Message(BaseModel):
keywords = TextField(null = True)
is_public = BooleanField(default = True, null = True)
views = IntegerField(null = True, default = 0)
+ hash = FixedCharField(max_length = 44, null = True)
def match_index(self, search : str) -> int:
"""Create a numerical value expressing how much
@@ -271,6 +281,7 @@ def message_to_dict(message : Message):
}
, "is_public" : bool(message.is_public or message.is_public == None)
, "views" : message.views
+ , "hash" : message.hash
}
class File(BaseModel):
diff --git a/clock.py b/clock.py
index 2a544e1c..754ffa59 100644
--- a/clock.py
+++ b/clock.py
@@ -133,12 +133,7 @@ def run():
past its voting phase,
the election will be counted and the result enforced.
"""
- print("Starting scheduler")
-
- while True:
- print("Running jobs")
- check_count_and_create_election()
- time.sleep(24*60**2) # 1 day
+ check_count_and_create_election()
if __name__ == "__main__":
run()
diff --git a/cron.clock b/cron.clock
new file mode 100644
index 00000000..068916b3
--- /dev/null
+++ b/cron.clock
@@ -0,0 +1 @@
+0 0 * * * python3 clock.py
diff --git a/docker-compose.yml b/docker-compose.yml
index 60b1f8fc..38e4d3ad 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -96,6 +96,7 @@ services:
- files:/files:rw
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./static:/static:ro
+ - ./help:/help:ro
- ./cert.pem:/cert.pem
- ./privkey.pem:/privkey.pem
ports:
diff --git a/help/markdown.html b/help/markdown.html
new file mode 100644
index 00000000..80529ad2
--- /dev/null
+++ b/help/markdown.html
@@ -0,0 +1,110 @@
+<!DOCTYPE html>
+<!DOCTYPE html>
+<html lang="en" dir="ltr">
+ <head>
+ <meta charset="utf-8">
+ <title>Markdown Guide | DemNet Help</title>
+ </head>
+ <body>
+ <header>
+ <a href="/">DemNet</a>
+ </header>
+ <h1>Markdown Guide</h1>
+ <p>
+ Markdown is a plain text format with some
+ text formatting abilities added.
+ </p>
+
+ <h3>Headers</h3>
+ <pre>
+# H1
+## H2
+...
+###### H6
+ </pre>
+ <p>
+ Formats as:
+ </p>
+ <h1>H1</h1>
+ <h2>H2</h2>
+ ...
+ <h6>H6</h6>
+
+ <h3>Italics, Bolds & Code</h3>
+ Italics, Bolds and Code can be formatted like this:
+ <br>
+ <pre>
+*Italics* with astrisks
+**Bold** with double astrisks
+~~Strikethrough~~ with two tildes
+
+`Inline Code` with backticks
+
+```python
+def foo():
+ bar()
+```
+Code Blocks can be formatted with three backticks
+at the beginning and end. You can specify a language
+for Syntax Highlighting after the first backticks.
+ </pre>
+
+
+ <h3>Lists</h3>
+ Ordered and unordered Lists are supported:
+ <pre>
+1. First ordered list item
+2. Second item
+
+- Unordered Item
+- Another unordered Item
+* Astriks, + and - can be used for unordered lists.
+ </pre>
+
+ <ol>
+ <li>First ordered list item</li>
+ <li>Second item</li>
+ </ol>
+
+ <ul>
+ <li>Unordered Item</li>
+ <li>Another unordered Item</li>
+ <li>Astriks, + and - can be used for unordered lists.</li>
+ </ul>
+
+ <h3>Links</h3>
+ Links can be created:
+ <pre>
+[Link Text](url)
+[DemNet](https://democraticnet.de)
+ </pre>
+
+ <a href="url">Link Text</a>
+ <a href="https://democraticnet.de">DemNet</a>
+
+ <h3>Images</h3>
+ You can insert an image, if you have its URL:
+ <pre>
+![Alt Text](url-to-image-file)
+![Christian Cross](https://upload.wikimedia.org/wikipedia/commons/8/87/Christian_cross.svg)
+ </pre>
+ <img src="url-to-image-file" alt="Alt Text">
+ <img src="https://upload.wikimedia.org/wikipedia/commons/8/87/Christian_cross.svg" alt="Christian Cross" width=150 height=150>
+
+ <h3>Videos</h3>
+ Videos are not supported by markdown by default.
+ But DemNet supports it.
+ Just like images, you only need the URL of the Image File (MP4, OGG, WebM)
+ and if you have multiple ones of those, you can use them all and seperate them
+ by commas:
+ <pre>
+!vid[Alt Text](url)
+!vid[Alt Text](url1,url2,url3)
+ </pre>
+
+ <footer>
+ <a href="https://democraticnet.de">DemNet</a>
+ <a href="https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet">A Markdown Cheat Sheet to look up</a>
+ </footer>
+ </body>
+</html>
diff --git a/main.py b/main.py
index 93b86f15..e40a8d45 100644
--- a/main.py
+++ b/main.py
@@ -263,14 +263,18 @@ def index():
else:
return response
-@app.route("/read/<int:message_id>", methods=["GET"])
+@app.route("/read/<message_id>", methods=["GET"])
@login_required
def read(message_id : str):
"""Renders the post with message_id
in read.html and returns that.
"""
try:
- message = Message.get_by_id(message_id)
+ try:
+ message = Message.get_by_id(int(message_id))
+ except ValueError:
+ message = Message.get(Message.hash == message_id)
+
logged_in = session.get("authenticated", default = False)
username = session.get("username", default = None)
is_author = logged_in and username == message.author.name
@@ -394,11 +398,16 @@ def publish():
"""
try:
response_to = request.values.get("response_to", None)
+ message = request.values.get("message", None)
+
+ if message is not None:
+ message = Message.get_by_id(int(message))
if request.method == "GET":
response = render_template( "publish.html"
, response_to = Message.get_by_id(int(response_to)) if response_to != None else None
, accepted_file_types = ",".join(accepted_file_types)
+ , message = message
)
else:
if response_to:
diff --git a/nginx.conf b/nginx.conf
index a8afa545..5bc0060d 100644
--- a/nginx.conf
+++ b/nginx.conf
@@ -51,6 +51,9 @@ http {
root /;
}
+ location /help/ {
+ root /;
+ }
location = /favicon.ico {
alias /static/favicon.ico;
diff --git a/output/message.html b/output/message.html
index f436cce3..d866e266 100644
--- a/output/message.html
+++ b/output/message.html
@@ -8,7 +8,8 @@ And a link to the post.
{% macro small_message(message) -%}
{% import "user.html" as user %}
<div class="dropdown">
- <a class="message" href="/read/{{message.id}}">
+ {% set link = message.id if message.hash is none else message.hash %}
+ <a class="message" href="/read/{{link}}">
<div class="dropbtn">
<h2> {{ message.title }} </h2>
</div>
@@ -31,6 +32,10 @@ also show all the responses to this message.
{% macro long_message(message, level = 1, authenticated = false) -%}
{% import "user.html" as user %}
<h{{level}}>{{ message.title }}</h{{level}}>
+{% if session.get("username", None) == message.author.name %}
+<a href="/publish?message={{ message.id }}">Edit</a>
+{% endif %}
+
<p>
{{message.content|markdown}}
</p>
@@ -48,11 +53,16 @@ also show all the responses to this message.
<p><b>Views: {{ message.views }}</b></p>
{% endif %}
-<!-- Make a response -->
+<!-- Respond -->
+
{% if authenticated and level <= 2 %}
<a href="/publish?response_to={{ message.id }}">Respond to this message</a>
{% endif %}
-
+<br>
+{% set keywords = "" if message.keywords is none else message.keywords.replace('#',',') %}
+<a href="https://twitter.com/intent/tweet?url={{ request.base_url | urlencode }}&text=Read%20what%20I%20found%3A%20{{ message.title | urlencode }}&hashtags=demnet,{{ keywords }}">
+ Share on Twitter
+</a>
{% if message.responses %}
{% if level == 1 %}
<hr>
diff --git a/output/publish.html b/output/publish.html
index 7f1869f8..755d241a 100644
--- a/output/publish.html
+++ b/output/publish.html
@@ -2,32 +2,34 @@
<html lang="en" dir="ltr">
<head>
<title>Write | DemNet</title>
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/simplemde/latest/simplemde.min.css">
<link rel="stylesheet" href="/static/styles/publish.css">
- <script src="https://cdn.jsdelivr.net/simplemde/latest/simplemde.min.js"></script>
- <script src="/static/scripts/purify.min.js" charset="utf-8"></script>
- <script src="https://unpkg.com/showdown@1.9.1/dist/showdown.min.js"></script>
+ <script src="/static/scripts/publish.js" charset="utf-8"></script>
{% include 'header.html' %}
</head>
<body>
{% include 'navigation.html' %}
{% import 'message.html' as m %}
{% if response_to %}
+ <div hidden id="response_to">{{ response_to.id if response_to.hash is none else response_to.hash }}</div>
+ <div hidden id="response_to_id">{{ response_to.id }}</div>
{{ m.long_message(response_to, level=4) }}
{% endif %}
- <form class="writing" action="/publish" method="post">
- <input type="text" name="title" id="title" placeholder="Title" onchange="save_post()">
- <br>
- <textarea name="content" id="editor" placeholder="Content"></textarea>
- <input type="text" id="keywords" name="keywords" placeholder="#onetopic#anothertopic" onchange="save_post()">
- {% if response_to %}
- <input type="hidden" name="response_to" value="{{ response_to.id }}">
- {% endif %}
- <br>
- <a href="/files_dashboard" id="upload_files_button">Upload Files</a>
- <br>
- <input type="submit" value="Publish">
- </form>
+ {% if message is none or message.author.name == session.get("username", None)%}
+ <div id="message-id" hidden>
+ {{ message.id if message != None else '-1' }}
+ </div>
+ <div id="editor">
+ </div>
+
+ <br>
+ <a href="/files_dashboard" id="upload_files_button">Upload Files</a>
+ <a href="/help/markdown.html">Markdown is Supported</a>
+ <br>
+
+ <b>Preview:</b>
+ <div id="output">
+
+ </div>
<style>
#upload_files_button {
padding-right: 45%;
@@ -35,69 +37,74 @@
}
</style>
<script type="text/javascript">
- let title = document.getElementById("title");
- let content = document.getElementById("content");
- let keywords = document.getElementById("keywords");
+ if(!localStorage.getItem("editor-title") || !localStorage.getItem("editor-content")) {
+ localStorage["editor-title"] = ""
+ localStorage["editor-content"] = ""
+ localStorage["editor-keywords"] = ""
+ }
+ let response_to = document.getElementById("response_to")
+ let response_to_id = document.getElementById("response_to_id")
+ let message = document.getElementById("message-id").innerHTML
+ let output = document.getElementById("output")
- const sanitizer = DOMPurify.sanitize;
+ message = message === "" ? -1 : parseInt(message)
+ let app = Elm.Editor.init({ node: document.getElementById("editor")
+ , flags: [message, response_to_id === null ? -1 : parseInt(response_to_id.innerHTML)]
+ })
- function video_ext() {
- return [{ type: "lang"
- , regex: /!vid\[(.*)\]\(([\w -_:,]+)\)/g
- , replace: (src) => {
- let match = src.match(/!vid\[(.+)\]\(([\w -_:,]+)\)/);
- let alt = match[1];
- let urls = match[2].split(",").map(sanitizer);
- let srcs = "";
- for(url of urls) {
- srcs += `<source src=${url}>`;
- }
- return `<video controls>${srcs}${parser(alt)}</video>`;
- }
- }]
- }
+ let has_cached = localStorage["editor-title"] !== "" || localStorage["editor-content"] !== "" || localStorage["editor-keywords"] !== ""
+ if(message === -1 && has_cached) {
+ app.ports.storage
+ .send({ title: localStorage["editor-title"]
+ , content: localStorage["editor-content"]
+ , keywords: localStorage["editor-keywords"]
+ })
+ } else {
+ let title = `${message}-title`
+ let content = `${message}-content`
+ let keywords = `${message}-keywords`
- const converter = new showdown.Converter({ extensions : [video_ext]});
+ has_cached = title in localStorage|| content in localStorage || keywords in localStorage
- function parser(text) {
- return sanitizer(converter.makeHtml(text));
+ if(has_cached) {
+ app.ports.storage
+ .send({ title: localStorage[title]
+ , content: localStorage[content]
+ , keywords: localStorage[keywords]
+ })
}
+ }
- const video_icon = { name: "video"
- , action: editor => editor.codemirror.doc.replaceSelection("!vid[](http://)")
- , className: "fa fa-film"
- , title: "Video"
- }
-
-
- const simplemde = new SimpleMDE({ element : document.getElementById("editor")
- , spellChecker : false
- , previewRender : parser
- , toolbar : ["bold", "italic", "heading", "|", "quote", "unordered-list", "ordered-list", "|", "link", "image", video_icon, "|", "preview", "side-by-side", "fullscreen", "guide"]
- })
-
-
- if(!localStorage.getItem("editor-title") || !localStorage.getItem("editor-content")) {
- localStorage["editor-title"] = ""
- localStorage["editor-content"] = ""
- localStorage["editor-keywords"] = ""
+ app.ports.save.subscribe(model => {
+ console.log(model)
+ if(typeof(model.id) == typeof(1)) {
+ localStorage[`${model.id}-title`] = model.title
+ localStorage[`${model.id}-content`] = model.content
+ localStorage[`${model.id}-keywords`] = model.keywords
+ } else {
+ localStorage["editor-title"] = model.title
+ localStorage["editor-content"] = model.content
+ localStorage["editor-keywords"] = model.keywords
}
+ })
- title.value = localStorage["editor-title"]
- keywords.value = localStorage["editor-keywords"]
- simplemde.value(localStorage["editor-content"])
-
- function save_post() {
- localStorage["editor-title"] = title.value;
- localStorage["editor-keywords"] = keywords.value;
- localStorage["editor-content"] = simplemde.value();
+ app.ports.redirect.subscribe(_ => {
+ if (response_to === null) {
+ window.location.href = "/"
+ } else {
+ window.location.href = "/read/" + response_to.innerHTML
}
+ })
+
+ app.ports.showMd.subscribe(html => {
+ output.innerHTML = html
+ })
- simplemde.codemirror.on ( "change"
- , save_post
- )
</script>
+ {% else %}
+ <b>You are not the author of this message. You may not edit it.</b>
+ {% endif %}
{% include 'footer.html' %}
</body>
</html>
diff --git a/output/read.html b/output/read.html
index 0206fb47..4ce3a67e 100644
--- a/output/read.html
+++ b/output/read.html
@@ -3,7 +3,8 @@
<head>
<title>{{ message.title }} | DemNet</title>
{% include 'header.html' %}
- {% if message.keywords %}
+ {% set has_no_keywords = message.keywords is none %}
+ {% if has_no_keywords is false %}
<meta name="description" content="{{ message.keywords.replace('#', ',') }}">
{% endif %}
</head>
diff --git a/output/vote_index.html b/output/vote_index.html
deleted file mode 100644
index cc7b6d3f..00000000
--- a/output/vote_index.html
+++ /dev/null
@@ -1,37 +0,0 @@
-<!DOCTYPE html>
-<html lang="en" dir="ltr">
- <head>
- <title>Votes</title>
- {% include 'header.html' %}
- </head>
- <body>
- {% include 'navigation.html' %}
- <h1>Elections</h1>
- <a href="/vote/create">Have a problem? Propose a vote.</a>
- <h2> Open for voting </h2>
- {% for election in stage_2_elections %}
- <h3>{{ election.title }}</h3>
- <p>{{ election.description|markdown }}</p>
- <a href="/vote/{{ election.id }}">Vote</a>
- {% endfor %}
- <hr>
- <h2>Make proposal to a problem</h2>
- {% for election in stage_1_elections %}
- <h3>{{ election.title }}</h3>
- <p>
- {{election.description|markdown }}
- </p>
- <table>
- <tr>
- <td>Voting opens on</td><td>{{ election.openning_ballot_date.strftime("%d.%m.%Y") }}</td>
- </tr>
- <tr>
- <td>Voting closes on</td><td>{{ election.closing_date.strftime("%d.%m.%Y") }}</td>
- </tr>
- </table>
- <a href="/vote/{{ election.id }}/info">Information</a>
- <a href="/vote/{{ election.id }}/propose">Propose solution</a>
- {% endfor %}
- {% include 'footer.html' %}
- </body>
-</html>
diff --git a/src/Editor.elm b/src/Editor.elm
new file mode 100644
index 00000000..bc979bec
--- /dev/null
+++ b/src/Editor.elm
@@ -0,0 +1,196 @@
+port module Editor exposing (..)
+import Browser
+import Json.Decode as D
+import Http
+import Html exposing (Html)
+import Element exposing (Element, column, width, height, fill, fillPortion, centerY, centerX, spacing, text)
+import Element.Input as Input
+import Element.Border as Border
+import Element.Background as Background
+import Element.Font as Font
+import Url.Builder as Builder
+
+-- MAIN
+main =
+ Browser.element
+ { init = init
+ , view = view
+ , update = update
+ , subscriptions = subscriptions
+ }
+
+
+-- MODEL
+type alias Model
+ = { title : String
+ , content : String
+ , keywords : String
+ , id : Maybe Int
+ , response_to : Maybe Int
+ , message : String
+ }
+
+decoder : D.Decoder Model
+decoder =
+ D.map5 (\t c k i r -> Model t c (Maybe.withDefault "" k) i r "")
+ (D.field "title" D.string)
+ (D.field "content" D.string)
+ (D.field "keywords" <| D.nullable D.string)
+ (D.field "id" <| D.nullable D.int)
+ (D.field "response_to" <| D.nullable D.int)
+
+init : (Int, Int) -> (Model, Cmd Msg)
+init (id, response_to) = ({ title = ""
+ , content = ""
+ , keywords = ""
+ , id = if id == -1 then Nothing else Just id
+ , response_to = if response_to == -1 then Nothing else Just response_to
+ , message = ""
+ }
+ , if id /= -1
+ then fetch id
+ else Cmd.none
+ )
+-- PORTS
+port storage : ({ title : String, content : String, keywords : String } -> msg) -> Sub msg
+port save : Model -> Cmd msg
+port redirect : () -> Cmd msg
+port showMd : String -> Cmd msg
+
+-- UPDATE
+type Field
+ = Title
+ | Content
+ | Keywords
+
+type Msg
+ = Change Field String
+ | Upload
+ | Uploaded (Result Http.Error ())
+ | Fetched (Result Http.Error Model)
+ | Store { title : String, content : String, keywords : String }
+ | RecvMd String (Result Http.Error String)
+
+
+update : Msg -> Model -> (Model, Cmd Msg)
+update msg model
+ = case msg of
+ Change field new ->
+ let new_model = case field of
+ Title -> { model | title = new }
+ Content -> { model | content = new }
+ Keywords -> { model | keywords = new }
+ in (new_model, Cmd.batch [save new_model, markdown new_model])
+
+ Upload ->
+ (model, upload model )
+
+ Uploaded result ->
+ case result of
+ Ok () -> (model, redirect ())
+ Err _ -> ({ model | message = "Couldn't upload. An Error occured" }, Cmd.none)
+
+ Fetched result ->
+ case result of
+ Ok new_model -> (new_model, Cmd.none)
+ Err _ -> ({ model | message = "Couldn't fetch. An Error occured" }, Cmd.none)
+
+ Store message ->
+ ({ model | title = message.title, content = message.content, keywords = message.keywords }
+ , Cmd.none)
+
+ RecvMd md result ->
+ case result of
+ Ok html -> (model, if md == model.content then showMd html else Cmd.none)
+ Err _ -> (model, Cmd.none)
+
+-- SUBSCRIPTIONS
+subscriptions : Model -> Sub Msg
+subscriptions _ = storage Store
+
+
+-- VIEW
+button_color = Element.rgb255 86 90 96
+button_font_color = Element.rgb255 255 255 255
+
+view : Model -> Html Msg
+view model =
+ Element.layout []
+ <| column [ width fill, height fill, Element.paddingXY 50 0, spacing 10]
+ [ Element.el [Font.size 12] <| text model.message
+ , Input.spellChecked [Font.size 24,Input.focusedOnLoad]
+ { onChange = Change Title
+ , text = model.title
+ , placeholder = Just << Input.placeholder [] <| text "Title ..."
+ , label = Input.labelLeft [] <| text "Title"
+ }
+ , Input.multiline [height <| fillPortion 6, width <| fillPortion 2]
+ { onChange = Change Content
+ , text = model.content
+ , placeholder = Just << Input.placeholder [] <| text "Content ..."
+ , label = Input.labelAbove [] <| text "Content"
+ , spellcheck = True
+ }
+ , Input.text []
+ { onChange = Change Keywords
+ , text = model.keywords
+ , placeholder = Just << Input.placeholder [] <| text "#topic#other topic"
+ , label = Input.labelLeft [] <| text "Keywords"
+ }
+ , Input.button
+ [ Background.color button_color
+ , Border.color button_color
+ , Border.width 10
+ , Font.color button_font_color
+ , centerX
+ ]
+ { onPress = Just Upload
+ , label = text <| if model.id == Nothing then "Publish" else "Publish Changes"
+ }
+ ]
+
+-- HTTP
+upload : Model -> Cmd Msg
+upload model
+ = let path = ["api", "v0", "message"]
+ query_ = [ Builder.string "title" model.title
+ , Builder.string "content" model.content
+ , Builder.string "keywords" model.keywords
+ ]
+ query = case model.response_to of
+ Just r -> query_ ++ [Builder.int "response_to" r]
+ Nothing -> query_
+ in case model.id of
+ Just id ->
+ Http.request
+ { method = "PUT"
+ , headers = []
+ , url = Builder.relative (path ++ [String.fromInt id]) query
+ , body = Http.emptyBody
+ , expect = Http.expectWhatever Uploaded
+ , timeout = Nothing
+ , tracker = Nothing
+ }
+ Nothing ->
+ Http.post
+ { url = Builder.relative path query
+ , body = Http.emptyBody
+ , expect = Http.expectWhatever Uploaded
+ }
+
+fetch : Int -> Cmd Msg
+fetch id
+ = let path = ["api", "v0", "message", String.fromInt id]
+ query = []
+ in Http.get
+ { url = Builder.relative path query
+ , expect = Http.expectJson Fetched decoder
+ }
+
+markdown : Model -> Cmd Msg
+markdown model
+ = let source = "# " ++ model.title ++ "\n" ++ model.content
+ in Http.get
+ { url = Builder.relative ["api", "v0", "markdown"] [Builder.string "source" source]
+ , expect = Http.expectJson (RecvMd model.content) (D.field "html" D.string)
+ }
diff --git a/static/scripts/publish.js b/static/scripts/publish.js
new file mode 100644
index 00000000..fb7fa9aa
--- /dev/null
+++ b/static/scripts/publish.js
@@ -0,0 +1,13603 @@
+(function(scope){
+'use strict';
+
+function F(arity, fun, wrapper) {
+ wrapper.a = arity;
+ wrapper.f = fun;
+ return wrapper;
+}
+
+function F2(fun) {
+ return F(2, fun, function(a) { return function(b) { return fun(a,b); }; })
+}
+function F3(fun) {
+ return F(3, fun, function(a) {
+ return function(b) { return function(c) { return fun(a, b, c); }; };
+ });
+}
+function F4(fun) {
+ return F(4, fun, function(a) { return function(b) { return function(c) {
+ return function(d) { return fun(a, b, c, d); }; }; };
+ });
+}
+function F5(fun) {
+ return F(5, fun, function(a) { return function(b) { return function(c) {
+ return function(d) { return function(e) { return fun(a, b, c, d, e); }; }; }; };
+ });
+}
+function F6(fun) {
+ return F(6, fun, function(a) { return function(b) { return function(c) {
+ return function(d) { return function(e) { return function(f) {
+ return fun(a, b, c, d, e, f); }; }; }; }; };
+ });
+}
+function F7(fun) {
+ return F(7, fun, function(a) { return function(b) { return function(c) {
+ return function(d) { return function(e) { return function(f) {
+ return function(g) { return fun(a, b, c, d, e, f, g); }; }; }; }; }; };
+ });
+}
+function F8(fun) {
+ return F(8, fun, function(a) { return function(b) { return function(c) {
+ return function(d) { return function(e) { return function(f) {
+ return function(g) { return function(h) {
+ return fun(a, b, c, d, e, f, g, h); }; }; }; }; }; }; };
+ });
+}
+function F9(fun) {
+ return F(9, fun, function(a) { return function(b) { return function(c) {
+ return function(d) { return function(e) { return function(f) {
+ return function(g) { return function(h) { return function(i) {
+ return fun(a, b, c, d, e, f, g, h, i); }; }; }; }; }; }; }; };
+ });
+}
+
+function A2(fun, a, b) {
+ return fun.a === 2 ? fun.f(a, b) : fun(a)(b);
+}
+function A3(fun, a, b, c) {
+ return fun.a === 3 ? fun.f(a, b, c) : fun(a)(b)(c);
+}
+function A4(fun, a, b, c, d) {
+ return fun.a === 4 ? fun.f(a, b, c, d) : fun(a)(b)(c)(d);
+}
+function A5(fun, a, b, c, d, e) {
+ return fun.a === 5 ? fun.f(a, b, c, d, e) : fun(a)(b)(c)(d)(e);
+}
+function A6(fun, a, b, c, d, e, f) {
+ return fun.a === 6 ? fun.f(a, b, c, d, e, f) : fun(a)(b)(c)(d)(e)(f);
+}
+function A7(fun, a, b, c, d, e, f, g) {
+ return fun.a === 7 ? fun.f(a, b, c, d, e, f, g) : fun(a)(b)(c)(d)(e)(f)(g);
+}
+function A8(fun, a, b, c, d, e, f, g, h) {
+ return fun.a === 8 ? fun.f(a, b, c, d, e, f, g, h) : fun(a)(b)(c)(d)(e)(f)(g)(h);
+}
+function A9(fun, a, b, c, d, e, f, g, h, i) {
+ return fun.a === 9 ? fun.f(a, b, c, d, e, f, g, h, i) : fun(a)(b)(c)(d)(e)(f)(g)(h)(i);
+}
+
+
+
+
+var _JsArray_empty = [];
+
+function _JsArray_singleton(value)
+{
+ return [value];
+}
+
+function _JsArray_length(array)
+{
+ return array.length;
+}
+
+var _JsArray_initialize = F3(function(size, offset, func)
+{
+ var result = new Array(size);
+
+ for (var i = 0; i < size; i++)
+ {
+ result[i] = func(offset + i);
+ }
+
+ return result;
+});
+
+var _JsArray_initializeFromList = F2(function (max, ls)
+{
+ var result = new Array(max);
+
+ for (var i = 0; i < max && ls.b; i++)
+ {
+ result[i] = ls.a;
+ ls = ls.b;
+ }
+
+ result.length = i;
+ return _Utils_Tuple2(result, ls);
+});
+
+var _JsArray_unsafeGet = F2(function(index, array)
+{
+ return array[index];
+});
+
+var _JsArray_unsafeSet = F3(function(index, value, array)
+{
+ var length = array.length;
+ var result = new Array(length);
+
+ for (var i = 0; i < length; i++)
+ {
+ result[i] = array[i];
+ }
+
+ result[index] = value;
+ return result;
+});
+
+var _JsArray_push = F2(function(value, array)
+{
+ var length = array.length;
+ var result = new Array(length + 1);
+
+ for (var i = 0; i < length; i++)
+ {
+ result[i] = array[i];
+ }
+
+ result[length] = value;
+ return result;
+});
+
+var _JsArray_foldl = F3(function(func, acc, array)
+{
+ var length = array.length;
+
+ for (var i = 0; i < length; i++)
+ {
+ acc = A2(func, array[i], acc);
+ }
+
+ return acc;
+});
+
+var _JsArray_foldr = F3(function(func, acc, array)
+{
+ for (var i = array.length - 1; i >= 0; i--)
+ {
+ acc = A2(func, array[i], acc);
+ }
+
+ return acc;
+});
+
+var _JsArray_map = F2(function(func, array)
+{
+ var length = array.length;
+ var result = new Array(length);
+
+ for (var i = 0; i < length; i++)
+ {
+ result[i] = func(array[i]);
+ }
+
+ return result;
+});
+
+var _JsArray_indexedMap = F3(function(func, offset, array)
+{
+ var length = array.length;
+ var result = new Array(length);
+
+ for (var i = 0; i < length; i++)
+ {
+ result[i] = A2(func, offset + i, array[i]);
+ }
+
+ return result;
+});
+
+var _JsArray_slice = F3(function(from, to, array)
+{
+ return array.slice(from, to);
+});
+
+var _JsArray_appendN = F3(function(n, dest, source)
+{
+ var destLen = dest.length;
+ var itemsToCopy = n - destLen;
+
+ if (itemsToCopy > source.length)
+ {
+ itemsToCopy = source.length;
+ }
+
+ var size = destLen + itemsToCopy;
+ var result = new Array(size);
+
+ for (var i = 0; i < destLen; i++)
+ {
+ result[i] = dest[i];
+ }
+
+ for (var i = 0; i < itemsToCopy; i++)
+ {
+ result[i + destLen] = source[i];
+ }
+
+ return result;
+});
+
+
+
+// LOG
+
+var _Debug_log = F2(function(tag, value)
+{
+ return value;
+});
+
+var _Debug_log_UNUSED = F2(function(tag, value)
+{
+ console.log(tag + ': ' + _Debug_toString(value));
+ return value;
+});
+
+
+// TODOS
+
+function _Debug_todo(moduleName, region)
+{
+ return function(message) {
+ _Debug_crash(8, moduleName, region, message);
+ };
+}
+
+function _Debug_todoCase(moduleName, region, value)
+{
+ return function(message) {
+ _Debug_crash(9, moduleName, region, value, message);
+ };
+}
+
+
+// TO STRING
+
+function _Debug_toString(value)
+{
+ return '<internals>';
+}
+
+function _Debug_toString_UNUSED(value)
+{
+ return _Debug_toAnsiString(false, value);
+}
+
+function _Debug_toAnsiString(ansi, value)
+{
+ if (typeof value === 'function')
+ {
+ return _Debug_internalColor(ansi, '<function>');
+ }
+
+ if (typeof value === 'boolean')
+ {
+ return _Debug_ctorColor(ansi, value ? 'True' : 'False');
+ }
+
+ if (typeof value === 'number')
+ {
+ return _Debug_numberColor(ansi, value + '');
+ }
+
+ if (value instanceof String)
+ {
+ return _Debug_charColor(ansi, "'" + _Debug_addSlashes(value, true) + "'");
+ }
+
+ if (typeof value === 'string')
+ {
+ return _Debug_stringColor(ansi, '"' + _Debug_addSlashes(value, false) + '"');
+ }
+
+ if (typeof value === 'object' && '$' in value)
+ {
+ var tag = value.$;
+
+ if (typeof tag === 'number')
+ {
+ return _Debug_internalColor(ansi, '<internals>');
+ }
+
+ if (tag[0] === '#')
+ {
+ var output = [];
+ for (var k in value)
+ {
+ if (k === '$') continue;
+ output.push(_Debug_toAnsiString(ansi, value[k]));
+ }
+ return '(' + output.join(',') + ')';
+ }
+
+ if (tag === 'Set_elm_builtin')
+ {
+ return _Debug_ctorColor(ansi, 'Set')
+ + _Debug_fadeColor(ansi, '.fromList') + ' '
+ + _Debug_toAnsiString(ansi, $elm$core$Set$toList(value));
+ }
+
+ if (tag === 'RBNode_elm_builtin' || tag === 'RBEmpty_elm_builtin')
+ {
+ return _Debug_ctorColor(ansi, 'Dict')
+ + _Debug_fadeColor(ansi, '.fromList') + ' '
+ + _Debug_toAnsiString(ansi, $elm$core$Dict$toList(value));
+ }
+
+ if (tag === 'Array_elm_builtin')
+ {
+ return _Debug_ctorColor(ansi, 'Array')
+ + _Debug_fadeColor(ansi, '.fromList') + ' '
+ + _Debug_toAnsiString(ansi, $elm$core$Array$toList(value));
+ }
+
+ if (tag === '::' || tag === '[]')
+ {
+ var output = '[';
+
+ value.b && (output += _Debug_toAnsiString(ansi, value.a), value = value.b)
+
+ for (; value.b; value = value.b) // WHILE_CONS
+ {
+ output += ',' + _Debug_toAnsiString(ansi, value.a);
+ }
+ return output + ']';
+ }
+
+ var output = '';
+ for (var i in value)
+ {
+ if (i === '$') continue;
+ var str = _Debug_toAnsiString(ansi, value[i]);
+ var c0 = str[0];
+ var parenless = c0 === '{' || c0 === '(' || c0 === '[' || c0 === '<' || c0 === '"' || str.indexOf(' ') < 0;
+ output += ' ' + (parenless ? str : '(' + str + ')');
+ }
+ return _Debug_ctorColor(ansi, tag) + output;
+ }
+
+ if (typeof DataView === 'function' && value instanceof DataView)
+ {
+ return _Debug_stringColor(ansi, '<' + value.byteLength + ' bytes>');
+ }
+
+ if (typeof File !== 'undefined' && value instanceof File)
+ {
+ return _Debug_internalColor(ansi, '<' + value.name + '>');
+ }
+
+ if (typeof value === 'object')
+ {
+ var output = [];
+ for (var key in value)
+ {
+ var field = key[0] === '_' ? key.slice(1) : key;
+ output.push(_Debug_fadeColor(ansi, field) + ' = ' + _Debug_toAnsiString(ansi, value[key]));
+ }
+ if (output.length === 0)
+ {
+ return '{}';
+ }
+ return '{ ' + output.join(', ') + ' }';
+ }
+
+ return _Debug_internalColor(ansi, '<internals>');
+}
+
+function _Debug_addSlashes(str, isChar)
+{
+ var s = str
+ .replace(/\\/g, '\\\\')
+ .replace(/\n/g, '\\n')
+ .replace(/\t/g, '\\t')
+ .replace(/\r/g, '\\r')
+ .replace(/\v/g, '\\v')
+ .replace(/\0/g, '\\0');
+
+ if (isChar)
+ {
+ return s.replace(/\'/g, '\\\'');
+ }
+ else
+ {
+ return s.replace(/\"/g, '\\"');
+ }
+}
+
+function _Debug_ctorColor(ansi, string)
+{
+ return ansi ? '\x1b[96m' + string + '\x1b[0m' : string;
+}
+
+function _Debug_numberColor(ansi, string)
+{
+ return ansi ? '\x1b[95m' + string + '\x1b[0m' : string;
+}
+
+function _Debug_stringColor(ansi, string)
+{
+ return ansi ? '\x1b[93m' + string + '\x1b[0m' : string;
+}
+
+function _Debug_charColor(ansi, string)
+{
+ return ansi ? '\x1b[92m' + string + '\x1b[0m' : string;
+}
+
+function _Debug_fadeColor(ansi, string)
+{
+ return ansi ? '\x1b[37m' + string + '\x1b[0m' : string;
+}
+
+function _Debug_internalColor(ansi, string)
+{
+ return ansi ? '\x1b[36m' + string + '\x1b[0m' : string;
+}
+
+function _Debug_toHexDigit(n)
+{
+ return String.fromCharCode(n < 10 ? 48 + n : 55 + n);
+}
+
+
+// CRASH
+
+
+function _Debug_crash(identifier)
+{
+ throw new Error('https://github.com/elm/core/blob/1.0.0/hints/' + identifier + '.md');
+}
+
+
+function _Debug_crash_UNUSED(identifier, fact1, fact2, fact3, fact4)
+{
+ switch(identifier)
+ {
+ case 0:
+ throw new Error('What node should I take over? In JavaScript I need something like:\n\n Elm.Main.init({\n node: document.getElementById("elm-node")\n })\n\nYou need to do this with any Browser.sandbox or Browser.element program.');
+
+ case 1:
+ throw new Error('Browser.application programs cannot handle URLs like this:\n\n ' + document.location.href + '\n\nWhat is the root? The root of your file system? Try looking at this program with `elm reactor` or some other server.');
+
+ case 2:
+ var jsonErrorString = fact1;
+ throw new Error('Problem with the flags given to your Elm program on initialization.\n\n' + jsonErrorString);
+
+ case 3:
+ var portName = fact1;
+ throw new Error('There can only be one port named `' + portName + '`, but your program has multiple.');
+
+ case 4:
+ var portName = fact1;
+ var problem = fact2;
+ throw new Error('Trying to send an unexpected type of value through port `' + portName + '`:\n' + problem);
+
+ case 5:
+ throw new Error('Trying to use `(==)` on functions.\nThere is no way to know if functions are "the same" in the Elm sense.\nRead more about this at https://package.elm-lang.org/packages/elm/core/latest/Basics#== which describes why it is this way and what the better version will look like.');
+
+ case 6:
+ var moduleName = fact1;
+ throw new Error('Your page is loading multiple Elm scripts with a module named ' + moduleName + '. Maybe a duplicate script is getting loaded accidentally? If not, rename one of them so I know which is which!');
+
+ case 8:
+ var moduleName = fact1;
+ var region = fact2;
+ var message = fact3;
+ throw new Error('TODO in module `' + moduleName + '` ' + _Debug_regionToString(region) + '\n\n' + message);
+
+ case 9:
+ var moduleName = fact1;
+ var region = fact2;
+ var value = fact3;
+ var message = fact4;
+ throw new Error(
+ 'TODO in module `' + moduleName + '` from the `case` expression '
+ + _Debug_regionToString(region) + '\n\nIt received the following value:\n\n '
+ + _Debug_toString(value).replace('\n', '\n ')
+ + '\n\nBut the branch that handles it says:\n\n ' + message.replace('\n', '\n ')
+ );
+
+ case 10:
+ throw new Error('Bug in https://github.com/elm/virtual-dom/issues');
+
+ case 11:
+ throw new Error('Cannot perform mod 0. Division by zero error.');
+ }
+}
+
+function _Debug_regionToString(region)
+{
+ if (region.a1.an === region.bj.an)
+ {
+ return 'on line ' + region.a1.an;
+ }
+ return 'on lines ' + region.a1.an + ' through ' + region.bj.an;
+}
+
+
+
+// EQUALITY
+
+function _Utils_eq(x, y)
+{
+ for (
+ var pair, stack = [], isEqual = _Utils_eqHelp(x, y, 0, stack);
+ isEqual && (pair = stack.pop());
+ isEqual = _Utils_eqHelp(pair.a, pair.b, 0, stack)
+ )
+ {}
+
+ return isEqual;
+}
+
+function _Utils_eqHelp(x, y, depth, stack)
+{
+ if (x === y)
+ {
+ return true;
+ }
+
+ if (typeof x !== 'object' || x === null || y === null)
+ {
+ typeof x === 'function' && _Debug_crash(5);
+ return false;
+ }
+
+ if (depth > 100)
+ {
+ stack.push(_Utils_Tuple2(x,y));
+ return true;
+ }
+
+ /**_UNUSED/
+ if (x.$ === 'Set_elm_builtin')
+ {
+ x = $elm$core$Set$toList(x);
+ y = $elm$core$Set$toList(y);
+ }
+ if (x.$ === 'RBNode_elm_builtin' || x.$ === 'RBEmpty_elm_builtin')
+ {
+ x = $elm$core$Dict$toList(x);
+ y = $elm$core$Dict$toList(y);
+ }
+ //*/
+
+ /**/
+ if (x.$ < 0)
+ {
+ x = $elm$core$Dict$toList(x);
+ y = $elm$core$Dict$toList(y);
+ }
+ //*/
+
+ for (var key in x)
+ {
+ if (!_Utils_eqHelp(x[key], y[key], depth + 1, stack))
+ {
+ return false;
+ }
+ }
+ return true;
+}
+
+var _Utils_equal = F2(_Utils_eq);
+var _Utils_notEqual = F2(function(a, b) { return !_Utils_eq(a,b); });
+
+
+
+// COMPARISONS
+
+// Code in Generate/JavaScript.hs, Basics.js, and List.js depends on
+// the particular integer values assigned to LT, EQ, and GT.
+
+function _Utils_cmp(x, y, ord)
+{
+ if (typeof x !== 'object')
+ {
+ return x === y ? /*EQ*/ 0 : x < y ? /*LT*/ -1 : /*GT*/ 1;
+ }
+
+ /**_UNUSED/
+ if (x instanceof String)
+ {
+ var a = x.valueOf();
+ var b = y.valueOf();
+ return a === b ? 0 : a < b ? -1 : 1;
+ }
+ //*/
+
+ /**/
+ if (typeof x.$ === 'undefined')
+ //*/
+ /**_UNUSED/
+ if (x.$[0] === '#')
+ //*/
+ {
+ return (ord = _Utils_cmp(x.a, y.a))
+ ? ord
+ : (ord = _Utils_cmp(x.b, y.b))
+ ? ord
+ : _Utils_cmp(x.c, y.c);
+ }
+
+ // traverse conses until end of a list or a mismatch
+ for (; x.b && y.b && !(ord = _Utils_cmp(x.a, y.a)); x = x.b, y = y.b) {} // WHILE_CONSES
+ return ord || (x.b ? /*GT*/ 1 : y.b ? /*LT*/ -1 : /*EQ*/ 0);
+}
+
+var _Utils_lt = F2(function(a, b) { return _Utils_cmp(a, b) < 0; });
+var _Utils_le = F2(function(a, b) { return _Utils_cmp(a, b) < 1; });
+var _Utils_gt = F2(function(a, b) { return _Utils_cmp(a, b) > 0; });
+var _Utils_ge = F2(function(a, b) { return _Utils_cmp(a, b) >= 0; });
+
+var _Utils_compare = F2(function(x, y)
+{
+ var n = _Utils_cmp(x, y);
+ return n < 0 ? $elm$core$Basics$LT : n ? $elm$core$Basics$GT : $elm$core$Basics$EQ;
+});
+
+
+// COMMON VALUES
+
+var _Utils_Tuple0 = 0;
+var _Utils_Tuple0_UNUSED = { $: '#0' };
+
+function _Utils_Tuple2(a, b) { return { a: a, b: b }; }
+function _Utils_Tuple2_UNUSED(a, b) { return { $: '#2', a: a, b: b }; }
+
+function _Utils_Tuple3(a, b, c) { return { a: a, b: b, c: c }; }
+function _Utils_Tuple3_UNUSED(a, b, c) { return { $: '#3', a: a, b: b, c: c }; }
+
+function _Utils_chr(c) { return c; }
+function _Utils_chr_UNUSED(c) { return new String(c); }
+
+
+// RECORDS
+
+function _Utils_update(oldRecord, updatedFields)
+{
+ var newRecord = {};
+
+ for (var key in oldRecord)
+ {
+ newRecord[key] = oldRecord[key];
+ }
+
+ for (var key in updatedFields)
+ {
+ newRecord[key] = updatedFields[key];
+ }
+
+ return newRecord;
+}
+
+
+// APPEND
+
+var _Utils_append = F2(_Utils_ap);
+
+function _Utils_ap(xs, ys)
+{
+ // append Strings
+ if (typeof xs === 'string')
+ {
+ return xs + ys;
+ }
+
+ // append Lists
+ if (!xs.b)
+ {
+ return ys;
+ }
+ var root = _List_Cons(xs.a, ys);
+ xs = xs.b
+ for (var curr = root; xs.b; xs = xs.b) // WHILE_CONS
+ {
+ curr = curr.b = _List_Cons(xs.a, ys);
+ }
+ return root;
+}
+
+
+
+var _List_Nil = { $: 0 };
+var _List_Nil_UNUSED = { $: '[]' };
+
+function _List_Cons(hd, tl) { return { $: 1, a: hd, b: tl }; }
+function _List_Cons_UNUSED(hd, tl) { return { $: '::', a: hd, b: tl }; }
+
+
+var _List_cons = F2(_List_Cons);
+
+function _List_fromArray(arr)
+{
+ var out = _List_Nil;
+ for (var i = arr.length; i--; )
+ {
+ out = _List_Cons(arr[i], out);
+ }
+ return out;
+}
+
+function _List_toArray(xs)
+{
+ for (var out = []; xs.b; xs = xs.b) // WHILE_CONS
+ {
+ out.push(xs.a);
+ }
+ return out;
+}
+
+var _List_map2 = F3(function(f, xs, ys)
+{
+ for (var arr = []; xs.b && ys.b; xs = xs.b, ys = ys.b) // WHILE_CONSES
+ {
+ arr.push(A2(f, xs.a, ys.a));
+ }
+ return _List_fromArray(arr);
+});
+
+var _List_map3 = F4(function(f, xs, ys, zs)
+{
+ for (var arr = []; xs.b && ys.b && zs.b; xs = xs.b, ys = ys.b, zs = zs.b) // WHILE_CONSES
+ {
+ arr.push(A3(f, xs.a, ys.a, zs.a));
+ }
+ return _List_fromArray(arr);
+});
+
+var _List_map4 = F5(function(f, ws, xs, ys, zs)
+{
+ for (var arr = []; ws.b && xs.b && ys.b && zs.b; ws = ws.b, xs = xs.b, ys = ys.b, zs = zs.b) // WHILE_CONSES
+ {
+ arr.push(A4(f, ws.a, xs.a, ys.a, zs.a));
+ }
+ return _List_fromArray(arr);
+});
+
+var _List_map5 = F6(function(f, vs, ws, xs, ys, zs)
+{
+ for (var arr = []; vs.b && ws.b && xs.b && ys.b && zs.b; vs = vs.b, ws = ws.b, xs = xs.b, ys = ys.b, zs = zs.b) // WHILE_CONSES
+ {
+ arr.push(A5(f, vs.a, ws.a, xs.a, ys.a, zs.a));
+ }
+ return _List_fromArray(arr);
+});
+
+var _List_sortBy = F2(function(f, xs)
+{
+ return _List_fromArray(_List_toArray(xs).sort(function(a, b) {
+ return _Utils_cmp(f(a), f(b));
+ }));
+});
+
+var _List_sortWith = F2(function(f, xs)
+{
+ return _List_fromArray(_List_toArray(xs).sort(function(a, b) {
+ var ord = A2(f, a, b);
+ return ord === $elm$core$Basics$EQ ? 0 : ord === $elm$core$Basics$LT ? -1 : 1;
+ }));
+});
+
+
+
+// MATH
+
+var _Basics_add = F2(function(a, b) { return a + b; });
+var _Basics_sub = F2(function(a, b) { return a - b; });
+var _Basics_mul = F2(function(a, b) { return a * b; });
+var _Basics_fdiv = F2(function(a, b) { return a / b; });
+var _Basics_idiv = F2(function(a, b) { return (a / b) | 0; });
+var _Basics_pow = F2(Math.pow);
+
+var _Basics_remainderBy = F2(function(b, a) { return a % b; });
+
+// https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/divmodnote-letter.pdf
+var _Basics_modBy = F2(function(modulus, x)
+{
+ var answer = x % modulus;
+ return modulus === 0
+ ? _Debug_crash(11)
+ :
+ ((answer > 0 && modulus < 0) || (answer < 0 && modulus > 0))
+ ? answer + modulus
+ : answer;
+});
+
+
+// TRIGONOMETRY
+
+var _Basics_pi = Math.PI;
+var _Basics_e = Math.E;
+var _Basics_cos = Math.cos;
+var _Basics_sin = Math.sin;
+var _Basics_tan = Math.tan;
+var _Basics_acos = Math.acos;
+var _Basics_asin = Math.asin;
+var _Basics_atan = Math.atan;
+var _Basics_atan2 = F2(Math.atan2);
+
+
+// MORE MATH
+
+function _Basics_toFloat(x) { return x; }
+function _Basics_truncate(n) { return n | 0; }
+function _Basics_isInfinite(n) { return n === Infinity || n === -Infinity; }
+
+var _Basics_ceiling = Math.ceil;
+var _Basics_floor = Math.floor;
+var _Basics_round = Math.round;
+var _Basics_sqrt = Math.sqrt;
+var _Basics_log = Math.log;
+var _Basics_isNaN = isNaN;
+
+
+// BOOLEANS
+
+function _Basics_not(bool) { return !bool; }
+var _Basics_and = F2(function(a, b) { return a && b; });
+var _Basics_or = F2(function(a, b) { return a || b; });
+var _Basics_xor = F2(function(a, b) { return a !== b; });
+
+
+
+var _String_cons = F2(function(chr, str)
+{
+ return chr + str;
+});
+
+function _String_uncons(string)
+{
+ var word = string.charCodeAt(0);
+ return !isNaN(word)
+ ? $elm$core$Maybe$Just(
+ 0xD800 <= word && word <= 0xDBFF
+ ? _Utils_Tuple2(_Utils_chr(string[0] + string[1]), string.slice(2))
+ : _Utils_Tuple2(_Utils_chr(string[0]), string.slice(1))
+ )
+ : $elm$core$Maybe$Nothing;
+}
+
+var _String_append = F2(function(a, b)
+{
+ return a + b;
+});
+
+function _String_length(str)
+{
+ return str.length;
+}
+
+var _String_map = F2(function(func, string)
+{
+ var len = string.length;
+ var array = new Array(len);
+ var i = 0;
+ while (i < len)
+ {
+ var word = string.charCodeAt(i);
+ if (0xD800 <= word && word <= 0xDBFF)
+ {
+ array[i] = func(_Utils_chr(string[i] + string[i+1]));
+ i += 2;
+ continue;
+ }
+ array[i] = func(_Utils_chr(string[i]));
+ i++;
+ }
+ return array.join('');
+});
+
+var _String_filter = F2(function(isGood, str)
+{
+ var arr = [];
+ var len = str.length;
+ var i = 0;
+ while (i < len)
+ {
+ var char = str[i];
+ var word = str.charCodeAt(i);
+ i++;
+ if (0xD800 <= word && word <= 0xDBFF)
+ {
+ char += str[i];
+ i++;
+ }
+
+ if (isGood(_Utils_chr(char)))
+ {
+ arr.push(char);
+ }
+ }
+ return arr.join('');
+});
+
+function _String_reverse(str)
+{
+ var len = str.length;
+ var arr = new Array(len);
+ var i = 0;
+ while (i < len)
+ {
+ var word = str.charCodeAt(i);
+ if (0xD800 <= word && word <= 0xDBFF)
+ {
+ arr[len - i] = str[i + 1];
+ i++;
+ arr[len - i] = str[i - 1];
+ i++;
+ }
+ else
+ {
+ arr[len - i] = str[i];
+ i++;
+ }
+ }
+ return arr.join('');
+}
+
+var _String_foldl = F3(function(func, state, string)
+{
+ var len = string.length;
+ var i = 0;
+ while (i < len)
+ {
+ var char = string[i];
+ var word = string.charCodeAt(i);
+ i++;
+ if (0xD800 <= word && word <= 0xDBFF)
+ {
+ char += string[i];
+ i++;
+ }
+ state = A2(func, _Utils_chr(char), state);
+ }
+ return state;
+});
+
+var _String_foldr = F3(function(func, state, string)
+{
+ var i = string.length;
+ while (i--)
+ {
+ var char = string[i];
+ var word = string.charCodeAt(i);
+ if (0xDC00 <= word && word <= 0xDFFF)
+ {
+ i--;
+ char = string[i] + char;
+ }
+ state = A2(func, _Utils_chr(char), state);
+ }
+ return state;
+});
+
+var _String_split = F2(function(sep, str)
+{
+ return str.split(sep);
+});
+
+var _String_join = F2(function(sep, strs)
+{
+ return strs.join(sep);
+});
+
+var _String_slice = F3(function(start, end, str) {
+ return str.slice(start, end);
+});
+
+function _String_trim(str)
+{
+ return str.trim();
+}
+
+function _String_trimLeft(str)
+{
+ return str.replace(/^\s+/, '');
+}
+
+function _String_trimRight(str)
+{
+ return str.replace(/\s+$/, '');
+}
+
+function _String_words(str)
+{
+ return _List_fromArray(str.trim().split(/\s+/g));
+}
+
+function _String_lines(str)
+{
+ return _List_fromArray(str.split(/\r\n|\r|\n/g));
+}
+
+function _String_toUpper(str)
+{
+ return str.toUpperCase();
+}
+
+function _String_toLower(str)
+{
+ return str.toLowerCase();
+}
+
+var _String_any = F2(function(isGood, string)
+{
+ var i = string.length;
+ while (i--)
+ {
+ var char = string[i];
+ var word = string.charCodeAt(i);
+ if (0xDC00 <= word && word <= 0xDFFF)
+ {
+ i--;
+ char = string[i] + char;
+ }
+ if (isGood(_Utils_chr(char)))
+ {
+ return true;
+ }
+ }
+ return false;
+});
+
+var _String_all = F2(function(isGood, string)
+{
+ var i = string.length;
+ while (i--)
+ {
+ var char = string[i];
+ var word = string.charCodeAt(i);
+ if (0xDC00 <= word && word <= 0xDFFF)
+ {
+ i--;
+ char = string[i] + char;
+ }
+ if (!isGood(_Utils_chr(char)))
+ {
+ return false;
+ }
+ }
+ return true;
+});
+
+var _String_contains = F2(function(sub, str)
+{
+ return str.indexOf(sub) > -1;
+});
+
+var _String_startsWith = F2(function(sub, str)
+{
+ return str.indexOf(sub) === 0;
+});
+
+var _String_endsWith = F2(function(sub, str)
+{
+ return str.length >= sub.length &&
+ str.lastIndexOf(sub) === str.length - sub.length;
+});
+
+var _String_indexes = F2(function(sub, str)
+{
+ var subLen = sub.length;
+
+ if (subLen < 1)
+ {
+ return _List_Nil;
+ }
+
+ var i = 0;
+ var is = [];
+
+ while ((i = str.indexOf(sub, i)) > -1)
+ {
+ is.push(i);
+ i = i + subLen;
+ }
+
+ return _List_fromArray(is);
+});
+
+
+// TO STRING
+
+function _String_fromNumber(number)
+{
+ return number + '';
+}
+
+
+// INT CONVERSIONS
+
+function _String_toInt(str)
+{
+ var total = 0;
+ var code0 = str.charCodeAt(0);
+ var start = code0 == 0x2B /* + */ || code0 == 0x2D /* - */ ? 1 : 0;
+
+ for (var i = start; i < str.length; ++i)
+ {
+ var code = str.charCodeAt(i);
+ if (code < 0x30 || 0x39 < code)
+ {
+ return $elm$core$Maybe$Nothing;
+ }
+ total = 10 * total + code - 0x30;
+ }
+
+ return i == start
+ ? $elm$core$Maybe$Nothing
+ : $elm$core$Maybe$Just(code0 == 0x2D ? -total : total);
+}
+
+
+// FLOAT CONVERSIONS
+
+function _String_toFloat(s)
+{
+ // check if it is a hex, octal, or binary number
+ if (s.length === 0 || /[\sxbo]/.test(s))
+ {
+ return $elm$core$Maybe$Nothing;
+ }
+ var n = +s;
+ // faster isNaN check
+ return n === n ? $elm$core$Maybe$Just(n) : $elm$core$Maybe$Nothing;
+}
+
+function _String_fromList(chars)
+{
+ return _List_toArray(chars).join('');
+}
+
+
+
+
+function _Char_toCode(char)
+{
+ var code = char.charCodeAt(0);
+ if (0xD800 <= code && code <= 0xDBFF)
+ {
+ return (code - 0xD800) * 0x400 + char.charCodeAt(1) - 0xDC00 + 0x10000
+ }
+ return code;
+}
+
+function _Char_fromCode(code)
+{
+ return _Utils_chr(
+ (code < 0 || 0x10FFFF < code)
+ ? '\uFFFD'
+ :
+ (code <= 0xFFFF)
+ ? String.fromCharCode(code)
+ :
+ (code -= 0x10000,
+ String.fromCharCode(Math.floor(code / 0x400) + 0xD800, code % 0x400 + 0xDC00)
+ )
+ );
+}
+
+function _Char_toUpper(char)
+{
+ return _Utils_chr(char.toUpperCase());
+}
+
+function _Char_toLower(char)
+{
+ return _Utils_chr(char.toLowerCase());
+}
+
+function _Char_toLocaleUpper(char)
+{
+ return _Utils_chr(char.toLocaleUpperCase());
+}
+
+function _Char_toLocaleLower(char)
+{
+ return _Utils_chr(char.toLocaleLowerCase());
+}
+
+
+
+/**_UNUSED/
+function _Json_errorToString(error)
+{
+ return $elm$json$Json$Decode$errorToString(error);
+}
+//*/
+
+
+// CORE DECODERS
+
+function _Json_succeed(msg)
+{
+ return {
+ $: 0,
+ a: msg
+ };
+}
+
+function _Json_fail(msg)
+{
+ return {
+ $: 1,
+ a: msg
+ };
+}
+
+function _Json_decodePrim(decoder)
+{
+ return { $: 2, b: decoder };
+}
+
+var _Json_decodeInt = _Json_decodePrim(function(value) {
+ return (typeof value !== 'number')
+ ? _Json_expecting('an INT', value)
+ :
+ (-2147483647 < value && value < 2147483647 && (value | 0) === value)
+ ? $elm$core$Result$Ok(value)
+ :
+ (isFinite(value) && !(value % 1))
+ ? $elm$core$Result$Ok(value)
+ : _Json_expecting('an INT', value);
+});
+
+var _Json_decodeBool = _Json_decodePrim(function(value) {
+ return (typeof value === 'boolean')
+ ? $elm$core$Result$Ok(value)
+ : _Json_expecting('a BOOL', value);
+});
+
+var _Json_decodeFloat = _Json_decodePrim(function(value) {
+ return (typeof value === 'number')
+ ? $elm$core$Result$Ok(value)
+ : _Json_expecting('a FLOAT', value);
+});
+
+var _Json_decodeValue = _Json_decodePrim(function(value) {
+ return $elm$core$Result$Ok(_Json_wrap(value));
+});
+
+var _Json_decodeString = _Json_decodePrim(function(value) {
+ return (typeof value === 'string')
+ ? $elm$core$Result$Ok(value)
+ : (value instanceof String)
+ ? $elm$core$Result$Ok(value + '')
+ : _Json_expecting('a STRING', value);
+});
+
+function _Json_decodeList(decoder) { return { $: 3, b: decoder }; }
+function _Json_decodeArray(decoder) { return { $: 4, b: decoder }; }
+
+function _Json_decodeNull(value) { return { $: 5, c: value }; }
+
+var _Json_decodeField = F2(function(field, decoder)
+{
+ return {
+ $: 6,
+ d: field,
+ b: decoder
+ };
+});
+
+var _Json_decodeIndex = F2(function(index, decoder)
+{
+ return {
+ $: 7,
+ e: index,
+ b: decoder
+ };
+});
+
+function _Json_decodeKeyValuePairs(decoder)
+{
+ return {
+ $: 8,
+ b: decoder
+ };
+}
+
+function _Json_mapMany(f, decoders)
+{
+ return {
+ $: 9,
+ f: f,
+ g: decoders
+ };
+}
+
+var _Json_andThen = F2(function(callback, decoder)
+{
+ return {
+ $: 10,
+ b: decoder,
+ h: callback
+ };
+});
+
+function _Json_oneOf(decoders)
+{
+ return {
+ $: 11,
+ g: decoders
+ };
+}
+
+
+// DECODING OBJECTS
+
+var _Json_map1 = F2(function(f, d1)
+{
+ return _Json_mapMany(f, [d1]);
+});
+
+var _Json_map2 = F3(function(f, d1, d2)
+{
+ return _Json_mapMany(f, [d1, d2]);
+});
+
+var _Json_map3 = F4(function(f, d1, d2, d3)
+{
+ return _Json_mapMany(f, [d1, d2, d3]);
+});
+
+var _Json_map4 = F5(function(f, d1, d2, d3, d4)
+{
+ return _Json_mapMany(f, [d1, d2, d3, d4]);
+});
+
+var _Json_map5 = F6(function(f, d1, d2, d3, d4, d5)
+{
+ return _Json_mapMany(f, [d1, d2, d3, d4, d5]);
+});
+
+var _Json_map6 = F7(function(f, d1, d2, d3, d4, d5, d6)
+{
+ return _Json_mapMany(f, [d1, d2, d3, d4, d5, d6]);
+});
+
+var _Json_map7 = F8(function(f, d1, d2, d3, d4, d5, d6, d7)
+{
+ return _Json_mapMany(f, [d1, d2, d3, d4, d5, d6, d7]);
+});
+
+var _Json_map8 = F9(function(f, d1, d2, d3, d4, d5, d6, d7, d8)
+{
+ return _Json_mapMany(f, [d1, d2, d3, d4, d5, d6, d7, d8]);
+});
+
+
+// DECODE
+
+var _Json_runOnString = F2(function(decoder, string)
+{
+ try
+ {
+ var value = JSON.parse(string);
+ return _Json_runHelp(decoder, value);
+ }
+ catch (e)
+ {
+ return $elm$core$Result$Err(A2($elm$json$Json$Decode$Failure, 'This is not valid JSON! ' + e.message, _Json_wrap(string)));
+ }
+});
+
+var _Json_run = F2(function(decoder, value)
+{
+ return _Json_runHelp(decoder, _Json_unwrap(value));
+});
+
+function _Json_runHelp(decoder, value)
+{
+ switch (decoder.$)
+ {
+ case 2:
+ return decoder.b(value);
+
+ case 5:
+ return (value === null)
+ ? $elm$core$Result$Ok(decoder.c)
+ : _Json_expecting('null', value);
+
+ case 3:
+ if (!_Json_isArray(value))
+ {
+ return _Json_expecting('a LIST', value);
+ }
+ return _Json_runArrayDecoder(decoder.b, value, _List_fromArray);
+
+ case 4:
+ if (!_Json_isArray(value))
+ {
+ return _Json_expecting('an ARRAY', value);
+ }
+ return _Json_runArrayDecoder(decoder.b, value, _Json_toElmArray);
+
+ case 6:
+ var field = decoder.d;
+ if (typeof value !== 'object' || value === null || !(field in value))
+ {
+ return _Json_expecting('an OBJECT with a field named `' + field + '`', value);
+ }
+ var result = _Json_runHelp(decoder.b, value[field]);
+ return ($elm$core$Result$isOk(result)) ? result : $elm$core$Result$Err(A2($elm$json$Json$Decode$Field, field, result.a));
+
+ case 7:
+ var index = decoder.e;
+ if (!_Json_isArray(value))
+ {
+ return _Json_expecting('an ARRAY', value);
+ }
+ if (index >= value.length)
+ {
+ return _Json_expecting('a LONGER array. Need index ' + index + ' but only see ' + value.length + ' entries', value);
+ }
+ var result = _Json_runHelp(decoder.b, value[index]);
+ return ($elm$core$Result$isOk(result)) ? result : $elm$core$Result$Err(A2($elm$json$Json$Decode$Index, index, result.a));
+
+ case 8:
+ if (typeof value !== 'object' || value === null || _Json_isArray(value))
+ {
+ return _Json_expecting('an OBJECT', value);
+ }
+
+ var keyValuePairs = _List_Nil;
+ // TODO test perf of Object.keys and switch when support is good enough
+ for (var key in value)
+ {
+ if (value.hasOwnProperty(key))
+ {
+ var result = _Json_runHelp(decoder.b, value[key]);
+ if (!$elm$core$Result$isOk(result))
+ {
+ return $elm$core$Result$Err(A2($elm$json$Json$Decode$Field, key, result.a));
+ }
+ keyValuePairs = _List_Cons(_Utils_Tuple2(key, result.a), keyValuePairs);
+ }
+ }
+ return $elm$core$Result$Ok($elm$core$List$reverse(keyValuePairs));
+
+ case 9:
+ var answer = decoder.f;
+ var decoders = decoder.g;
+ for (var i = 0; i < decoders.length; i++)
+ {
+ var result = _Json_runHelp(decoders[i], value);
+ if (!$elm$core$Result$isOk(result))
+ {
+ return result;
+ }
+ answer = answer(result.a);
+ }
+ return $elm$core$Result$Ok(answer);
+
+ case 10:
+ var result = _Json_runHelp(decoder.b, value);
+ return (!$elm$core$Result$isOk(result))
+ ? result
+ : _Json_runHelp(decoder.h(result.a), value);
+
+ case 11:
+ var errors = _List_Nil;
+ for (var temp = decoder.g; temp.b; temp = temp.b) // WHILE_CONS
+ {
+ var result = _Json_runHelp(temp.a, value);
+ if ($elm$core$Result$isOk(result))
+ {
+ return result;
+ }
+ errors = _List_Cons(result.a, errors);
+ }
+ return $elm$core$Result$Err($elm$json$Json$Decode$OneOf($elm$core$List$reverse(errors)));
+
+ case 1:
+ return $elm$core$Result$Err(A2($elm$json$Json$Decode$Failure, decoder.a, _Json_wrap(value)));
+
+ case 0:
+ return $elm$core$Result$Ok(decoder.a);
+ }
+}
+
+function _Json_runArrayDecoder(decoder, value, toElmValue)
+{
+ var len = value.length;
+ var array = new Array(len);
+ for (var i = 0; i < len; i++)
+ {
+ var result = _Json_runHelp(decoder, value[i]);
+ if (!$elm$core$Result$isOk(result))
+ {
+ return $elm$core$Result$Err(A2($elm$json$Json$Decode$Index, i, result.a));
+ }
+ array[i] = result.a;
+ }
+ return $elm$core$Result$Ok(toElmValue(array));
+}
+
+function _Json_isArray(value)
+{
+ return Array.isArray(value) || (typeof FileList !== 'undefined' && value instanceof FileList);
+}
+
+function _Json_toElmArray(array)
+{
+ return A2($elm$core$Array$initialize, array.length, function(i) { return array[i]; });
+}
+
+function _Json_expecting(type, value)
+{
+ return $elm$core$Result$Err(A2($elm$json$Json$Decode$Failure, 'Expecting ' + type, _Json_wrap(value)));
+}
+
+
+// EQUALITY
+
+function _Json_equality(x, y)
+{
+ if (x === y)
+ {
+ return true;
+ }
+
+ if (x.$ !== y.$)
+ {
+ return false;
+ }
+
+ switch (x.$)
+ {
+ case 0:
+ case 1:
+ return x.a === y.a;
+
+ case 2:
+ return x.b === y.b;
+
+ case 5:
+ return x.c === y.c;
+
+ case 3:
+ case 4:
+ case 8:
+ return _Json_equality(x.b, y.b);
+
+ case 6:
+ return x.d === y.d && _Json_equality(x.b, y.b);
+
+ case 7:
+ return x.e === y.e && _Json_equality(x.b, y.b);
+
+ case 9:
+ return x.f === y.f && _Json_listEquality(x.g, y.g);
+
+ case 10:
+ return x.h === y.h && _Json_equality(x.b, y.b);
+
+ case 11:
+ return _Json_listEquality(x.g, y.g);
+ }
+}
+
+function _Json_listEquality(aDecoders, bDecoders)
+{
+ var len = aDecoders.length;
+ if (len !== bDecoders.length)
+ {
+ return false;
+ }
+ for (var i = 0; i < len; i++)
+ {
+ if (!_Json_equality(aDecoders[i], bDecoders[i]))
+ {
+ return false;
+ }
+ }
+ return true;
+}
+
+
+// ENCODE
+
+var _Json_encode = F2(function(indentLevel, value)
+{
+ return JSON.stringify(_Json_unwrap(value), null, indentLevel) + '';
+});
+
+function _Json_wrap_UNUSED(value) { return { $: 0, a: value }; }
+function _Json_unwrap_UNUSED(value) { return value.a; }
+
+function _Json_wrap(value) { return value; }
+function _Json_unwrap(value) { return value; }
+
+function _Json_emptyArray() { return []; }
+function _Json_emptyObject() { return {}; }
+
+var _Json_addField = F3(function(key, value, object)
+{
+ object[key] = _Json_unwrap(value);
+ return object;
+});
+
+function _Json_addEntry(func)
+{
+ return F2(function(entry, array)
+ {
+ array.push(_Json_unwrap(func(entry)));
+ return array;
+ });
+}
+
+var _Json_encodeNull = _Json_wrap(null);
+
+
+
+// TASKS
+
+function _Scheduler_succeed(value)
+{
+ return {
+ $: 0,
+ a: value
+ };
+}
+
+function _Scheduler_fail(error)
+{
+ return {
+ $: 1,
+ a: error
+ };
+}
+
+function _Scheduler_binding(callback)
+{
+ return {
+ $: 2,
+ b: callback,
+ c: null
+ };
+}
+
+var _Scheduler_andThen = F2(function(callback, task)
+{
+ return {
+ $: 3,
+ b: callback,
+ d: task
+ };
+});
+
+var _Scheduler_onError = F2(function(callback, task)
+{
+ return {
+ $: 4,
+ b: callback,
+ d: task
+ };
+});
+
+function _Scheduler_receive(callback)
+{
+ return {
+ $: 5,
+ b: callback
+ };
+}
+
+
+// PROCESSES
+
+var _Scheduler_guid = 0;
+
+function _Scheduler_rawSpawn(task)
+{
+ var proc = {
+ $: 0,
+ e: _Scheduler_guid++,
+ f: task,
+ g: null,
+ h: []
+ };
+
+ _Scheduler_enqueue(proc);
+
+ return proc;
+}
+
+function _Scheduler_spawn(task)
+{
+ return _Scheduler_binding(function(callback) {
+ callback(_Scheduler_succeed(_Scheduler_rawSpawn(task)));
+ });
+}
+
+function _Scheduler_rawSend(proc, msg)
+{
+ proc.h.push(msg);
+ _Scheduler_enqueue(proc);
+}
+
+var _Scheduler_send = F2(function(proc, msg)
+{
+ return _Scheduler_binding(function(callback) {
+ _Scheduler_rawSend(proc, msg);
+ callback(_Scheduler_succeed(_Utils_Tuple0));
+ });
+});
+
+function _Scheduler_kill(proc)
+{
+ return _Scheduler_binding(function(callback) {
+ var task = proc.f;
+ if (task.$ === 2 && task.c)
+ {
+ task.c();
+ }
+
+ proc.f = null;
+
+ callback(_Scheduler_succeed(_Utils_Tuple0));
+ });
+}
+
+
+/* STEP PROCESSES
+
+type alias Process =
+ { $ : tag
+ , id : unique_id
+ , root : Task
+ , stack : null | { $: SUCCEED | FAIL, a: callback, b: stack }
+ , mailbox : [msg]
+ }
+
+*/
+
+
+var _Scheduler_working = false;
+var _Scheduler_queue = [];
+
+
+function _Scheduler_enqueue(proc)
+{
+ _Scheduler_queue.push(proc);
+ if (_Scheduler_working)
+ {
+ return;
+ }
+ _Scheduler_working = true;
+ while (proc = _Scheduler_queue.shift())
+ {
+ _Scheduler_step(proc);
+ }
+ _Scheduler_working = false;
+}
+
+
+function _Scheduler_step(proc)
+{
+ while (proc.f)
+ {
+ var rootTag = proc.f.$;
+ if (rootTag === 0 || rootTag === 1)
+ {
+ while (proc.g && proc.g.$ !== rootTag)
+ {
+ proc.g = proc.g.i;
+ }
+ if (!proc.g)
+ {
+ return;
+ }
+ proc.f = proc.g.b(proc.f.a);
+ proc.g = proc.g.i;
+ }
+ else if (rootTag === 2)
+ {
+ proc.f.c = proc.f.b(function(newRoot) {
+ proc.f = newRoot;
+ _Scheduler_enqueue(proc);
+ });
+ return;
+ }
+ else if (rootTag === 5)
+ {
+ if (proc.h.length === 0)
+ {
+ return;
+ }
+ proc.f = proc.f.b(proc.h.shift());
+ }
+ else // if (rootTag === 3 || rootTag === 4)
+ {
+ proc.g = {
+ $: rootTag === 3 ? 0 : 1,
+ b: proc.f.b,
+ i: proc.g
+ };
+ proc.f = proc.f.d;
+ }
+ }
+}
+
+
+
+function _Process_sleep(time)
+{
+ return _Scheduler_binding(function(callback) {
+ var id = setTimeout(function() {
+ callback(_Scheduler_succeed(_Utils_Tuple0));
+ }, time);
+
+ return function() { clearTimeout(id); };
+ });
+}
+
+
+
+
+// PROGRAMS
+
+
+var _Platform_worker = F4(function(impl, flagDecoder, debugMetadata, args)
+{
+ return _Platform_initialize(
+ flagDecoder,
+ args,
+ impl.c5,
+ impl.ec,
+ impl.dS,
+ function() { return function() {} }
+ );
+});
+
+
+
+// INITIALIZE A PROGRAM
+
+
+function _Platform_initialize(flagDecoder, args, init, update, subscriptions, stepperBuilder)
+{
+ var result = A2(_Json_run, flagDecoder, _Json_wrap(args ? args['flags'] : undefined));
+ $elm$core$Result$isOk(result) || _Debug_crash(2 /**_UNUSED/, _Json_errorToString(result.a) /**/);
+ var managers = {};
+ var initPair = init(result.a);
+ var model = initPair.a;
+ var stepper = stepperBuilder(sendToApp, model);
+ var ports = _Platform_setupEffects(managers, sendToApp);
+
+ function sendToApp(msg, viewMetadata)
+ {
+ var pair = A2(update, msg, model);
+ stepper(model = pair.a, viewMetadata);
+ _Platform_enqueueEffects(managers, pair.b, subscriptions(model));
+ }
+
+ _Platform_enqueueEffects(managers, initPair.b, subscriptions(model));
+
+ return ports ? { ports: ports } : {};
+}
+
+
+
+// TRACK PRELOADS
+//
+// This is used by code in elm/browser and elm/http
+// to register any HTTP requests that are triggered by init.
+//
+
+
+var _Platform_preload;
+
+
+function _Platform_registerPreload(url)
+{
+ _Platform_preload.add(url);
+}
+
+
+
+// EFFECT MANAGERS
+
+
+var _Platform_effectManagers = {};
+
+
+function _Platform_setupEffects(managers, sendToApp)
+{
+ var ports;
+
+ // setup all necessary effect managers
+ for (var key in _Platform_effectManagers)
+ {
+ var manager = _Platform_effectManagers[key];
+
+ if (manager.a)
+ {
+ ports = ports || {};
+ ports[key] = manager.a(key, sendToApp);
+ }
+
+ managers[key] = _Platform_instantiateManager(manager, sendToApp);
+ }
+
+ return ports;
+}
+
+
+function _Platform_createManager(init, onEffects, onSelfMsg, cmdMap, subMap)
+{
+ return {
+ b: init,
+ c: onEffects,
+ d: onSelfMsg,
+ e: cmdMap,
+ f: subMap
+ };
+}
+
+
+function _Platform_instantiateManager(info, sendToApp)
+{
+ var router = {
+ g: sendToApp,
+ h: undefined
+ };
+
+ var onEffects = info.c;
+ var onSelfMsg = info.d;
+ var cmdMap = info.e;
+ var subMap = info.f;
+
+ function loop(state)
+ {
+ return A2(_Scheduler_andThen, loop, _Scheduler_receive(function(msg)
+ {
+ var value = msg.a;
+
+ if (msg.$ === 0)
+ {
+ return A3(onSelfMsg, router, value, state);
+ }
+
+ return cmdMap && subMap
+ ? A4(onEffects, router, value.i, value.j, state)
+ : A3(onEffects, router, cmdMap ? value.i : value.j, state);
+ }));
+ }
+
+ return router.h = _Scheduler_rawSpawn(A2(_Scheduler_andThen, loop, info.b));
+}
+
+
+
+// ROUTING
+
+
+var _Platform_sendToApp = F2(function(router, msg)
+{
+ return _Scheduler_binding(function(callback)
+ {
+ router.g(msg);
+ callback(_Scheduler_succeed(_Utils_Tuple0));
+ });
+});
+
+
+var _Platform_sendToSelf = F2(function(router, msg)
+{
+ return A2(_Scheduler_send, router.h, {
+ $: 0,
+ a: msg
+ });
+});
+
+
+
+// BAGS
+
+
+function _Platform_leaf(home)
+{
+ return function(value)
+ {
+ return {
+ $: 1,
+ k: home,
+ l: value
+ };
+ };
+}
+
+
+function _Platform_batch(list)
+{
+ return {
+ $: 2,
+ m: list
+ };
+}
+
+
+var _Platform_map = F2(function(tagger, bag)
+{
+ return {
+ $: 3,
+ n: tagger,
+ o: bag
+ }
+});
+
+
+
+// PIPE BAGS INTO EFFECT MANAGERS
+//
+// Effects must be queued!
+//
+// Say your init contains a synchronous command, like Time.now or Time.here
+//
+// - This will produce a batch of effects (FX_1)
+// - The synchronous task triggers the subsequent `update` call
+// - This will produce a batch of effects (FX_2)
+//
+// If we just start dispatching FX_2, subscriptions from FX_2 can be processed
+// before subscriptions from FX_1. No good! Earlier versions of this code had
+// this problem, leading to these reports:
+//
+// https://github.com/elm/core/issues/980
+// https://github.com/elm/core/pull/981
+// https://github.com/elm/compiler/issues/1776
+//
+// The queue is necessary to avoid ordering issues for synchronous commands.
+
+
+// Why use true/false here? Why not just check the length of the queue?
+// The goal is to detect "are we currently dispatching effects?" If we
+// are, we need to bail and let the ongoing while loop handle things.
+//
+// Now say the queue has 1 element. When we dequeue the final element,
+// the queue will be empty, but we are still actively dispatching effects.
+// So you could get queue jumping in a really tricky category of cases.
+//
+var _Platform_effectsQueue = [];
+var _Platform_effectsActive = false;
+
+
+function _Platform_enqueueEffects(managers, cmdBag, subBag)
+{
+ _Platform_effectsQueue.push({ p: managers, q: cmdBag, r: subBag });
+
+ if (_Platform_effectsActive) return;
+
+ _Platform_effectsActive = true;
+ for (var fx; fx = _Platform_effectsQueue.shift(); )
+ {
+ _Platform_dispatchEffects(fx.p, fx.q, fx.r);
+ }
+ _Platform_effectsActive = false;
+}
+
+
+function _Platform_dispatchEffects(managers, cmdBag, subBag)
+{
+ var effectsDict = {};
+ _Platform_gatherEffects(true, cmdBag, effectsDict, null);
+ _Platform_gatherEffects(false, subBag, effectsDict, null);
+
+ for (var home in managers)
+ {
+ _Scheduler_rawSend(managers[home], {
+ $: 'fx',
+ a: effectsDict[home] || { i: _List_Nil, j: _List_Nil }
+ });
+ }
+}
+
+
+function _Platform_gatherEffects(isCmd, bag, effectsDict, taggers)
+{
+ switch (bag.$)
+ {
+ case 1:
+ var home = bag.k;
+ var effect = _Platform_toEffect(isCmd, home, taggers, bag.l);
+ effectsDict[home] = _Platform_insert(isCmd, effect, effectsDict[home]);
+ return;
+
+ case 2:
+ for (var list = bag.m; list.b; list = list.b) // WHILE_CONS
+ {
+ _Platform_gatherEffects(isCmd, list.a, effectsDict, taggers);
+ }
+ return;
+
+ case 3:
+ _Platform_gatherEffects(isCmd, bag.o, effectsDict, {
+ s: bag.n,
+ t: taggers
+ });
+ return;
+ }
+}
+
+
+function _Platform_toEffect(isCmd, home, taggers, value)
+{
+ function applyTaggers(x)
+ {
+ for (var temp = taggers; temp; temp = temp.t)
+ {
+ x = temp.s(x);
+ }
+ return x;
+ }
+
+ var map = isCmd
+ ? _Platform_effectManagers[home].e
+ : _Platform_effectManagers[home].f;
+
+ return A2(map, applyTaggers, value)
+}
+
+
+function _Platform_insert(isCmd, newEffect, effects)
+{
+ effects = effects || { i: _List_Nil, j: _List_Nil };
+
+ isCmd
+ ? (effects.i = _List_Cons(newEffect, effects.i))
+ : (effects.j = _List_Cons(newEffect, effects.j));
+
+ return effects;
+}
+
+
+
+// PORTS
+
+
+function _Platform_checkPortName(name)
+{
+ if (_Platform_effectManagers[name])
+ {
+ _Debug_crash(3, name)
+ }
+}
+
+
+
+// OUTGOING PORTS
+
+
+function _Platform_outgoingPort(name, converter)
+{
+ _Platform_checkPortName(name);
+ _Platform_effectManagers[name] = {
+ e: _Platform_outgoingPortMap,
+ u: converter,
+ a: _Platform_setupOutgoingPort
+ };
+ return _Platform_leaf(name);
+}
+
+
+var _Platform_outgoingPortMap = F2(function(tagger, value) { return value; });
+
+
+function _Platform_setupOutgoingPort(name)
+{
+ var subs = [];
+ var converter = _Platform_effectManagers[name].u;
+
+ // CREATE MANAGER
+
+ var init = _Process_sleep(0);
+
+ _Platform_effectManagers[name].b = init;
+ _Platform_effectManagers[name].c = F3(function(router, cmdList, state)
+ {
+ for ( ; cmdList.b; cmdList = cmdList.b) // WHILE_CONS
+ {
+ // grab a separate reference to subs in case unsubscribe is called
+ var currentSubs = subs;
+ var value = _Json_unwrap(converter(cmdList.a));
+ for (var i = 0; i < currentSubs.length; i++)
+ {
+ currentSubs[i](value);
+ }
+ }
+ return init;
+ });
+
+ // PUBLIC API
+
+ function subscribe(callback)
+ {
+ subs.push(callback);
+ }
+
+ function unsubscribe(callback)
+ {
+ // copy subs into a new array in case unsubscribe is called within a
+ // subscribed callback
+ subs = subs.slice();
+ var index = subs.indexOf(callback);
+ if (index >= 0)
+ {
+ subs.splice(index, 1);
+ }
+ }
+
+ return {
+ subscribe: subscribe,
+ unsubscribe: unsubscribe
+ };
+}
+
+
+
+// INCOMING PORTS
+
+
+function _Platform_incomingPort(name, converter)
+{
+ _Platform_checkPortName(name);
+ _Platform_effectManagers[name] = {
+ f: _Platform_incomingPortMap,
+ u: converter,
+ a: _Platform_setupIncomingPort
+ };
+ return _Platform_leaf(name);
+}
+
+
+var _Platform_incomingPortMap = F2(function(tagger, finalTagger)
+{
+ return function(value)
+ {
+ return tagger(finalTagger(value));
+ };
+});
+
+
+function _Platform_setupIncomingPort(name, sendToApp)
+{
+ var subs = _List_Nil;
+ var converter = _Platform_effectManagers[name].u;
+
+ // CREATE MANAGER
+
+ var init = _Scheduler_succeed(null);
+
+ _Platform_effectManagers[name].b = init;
+ _Platform_effectManagers[name].c = F3(function(router, subList, state)
+ {
+ subs = subList;
+ return init;
+ });
+
+ // PUBLIC API
+
+ function send(incomingValue)
+ {
+ var result = A2(_Json_run, converter, _Json_wrap(incomingValue));
+
+ $elm$core$Result$isOk(result) || _Debug_crash(4, name, result.a);
+
+ var value = result.a;
+ for (var temp = subs; temp.b; temp = temp.b) // WHILE_CONS
+ {
+ sendToApp(temp.a(value));
+ }
+ }
+
+ return { send: send };
+}
+
+
+
+// EXPORT ELM MODULES
+//
+// Have DEBUG and PROD versions so that we can (1) give nicer errors in
+// debug mode and (2) not pay for the bits needed for that in prod mode.
+//
+
+
+function _Platform_export(exports)
+{
+ scope['Elm']
+ ? _Platform_mergeExportsProd(scope['Elm'], exports)
+ : scope['Elm'] = exports;
+}
+
+
+function _Platform_mergeExportsProd(obj, exports)
+{
+ for (var name in exports)
+ {
+ (name in obj)
+ ? (name == 'init')
+ ? _Debug_crash(6)
+ : _Platform_mergeExportsProd(obj[name], exports[name])
+ : (obj[name] = exports[name]);
+ }
+}
+
+
+function _Platform_export_UNUSED(exports)
+{
+ scope['Elm']
+ ? _Platform_mergeExportsDebug('Elm', scope['Elm'], exports)
+ : scope['Elm'] = exports;
+}
+
+
+function _Platform_mergeExportsDebug(moduleName, obj, exports)
+{
+ for (var name in exports)
+ {
+ (name in obj)
+ ? (name == 'init')
+ ? _Debug_crash(6, moduleName)
+ : _Platform_mergeExportsDebug(moduleName + '.' + name, obj[name], exports[name])
+ : (obj[name] = exports[name]);
+ }
+}
+
+
+
+
+// HELPERS
+
+
+var _VirtualDom_divertHrefToApp;
+
+var _VirtualDom_doc = typeof document !== 'undefined' ? document : {};
+
+
+function _VirtualDom_appendChild(parent, child)
+{
+ parent.appendChild(child);
+}
+
+var _VirtualDom_init = F4(function(virtualNode, flagDecoder, debugMetadata, args)
+{
+ // NOTE: this function needs _Platform_export available to work
+
+ /**/
+ var node = args['node'];
+ //*/
+ /**_UNUSED/
+ var node = args && args['node'] ? args['node'] : _Debug_crash(0);
+ //*/
+
+ node.parentNode.replaceChild(
+ _VirtualDom_render(virtualNode, function() {}),
+ node
+ );
+
+ return {};
+});
+
+
+
+// TEXT
+
+
+function _VirtualDom_text(string)
+{
+ return {
+ $: 0,
+ a: string
+ };
+}
+
+
+
+// NODE
+
+
+var _VirtualDom_nodeNS = F2(function(namespace, tag)
+{
+ return F2(function(factList, kidList)
+ {
+ for (var kids = [], descendantsCount = 0; kidList.b; kidList = kidList.b) // WHILE_CONS
+ {
+ var kid = kidList.a;
+ descendantsCount += (kid.b || 0);
+ kids.push(kid);
+ }
+ descendantsCount += kids.length;
+
+ return {
+ $: 1,
+ c: tag,
+ d: _VirtualDom_organizeFacts(factList),
+ e: kids,
+ f: namespace,
+ b: descendantsCount
+ };
+ });
+});
+
+
+var _VirtualDom_node = _VirtualDom_nodeNS(undefined);
+
+
+
+// KEYED NODE
+
+
+var _VirtualDom_keyedNodeNS = F2(function(namespace, tag)
+{
+ return F2(function(factList, kidList)
+ {
+ for (var kids = [], descendantsCount = 0; kidList.b; kidList = kidList.b) // WHILE_CONS
+ {
+ var kid = kidList.a;
+ descendantsCount += (kid.b.b || 0);
+ kids.push(kid);
+ }
+ descendantsCount += kids.length;
+
+ return {
+ $: 2,
+ c: tag,
+ d: _VirtualDom_organizeFacts(factList),
+ e: kids,
+ f: namespace,
+ b: descendantsCount
+ };
+ });
+});
+
+
+var _VirtualDom_keyedNode = _VirtualDom_keyedNodeNS(undefined);
+
+
+
+// CUSTOM
+
+
+function _VirtualDom_custom(factList, model, render, diff)
+{
+ return {
+ $: 3,
+ d: _VirtualDom_organizeFacts(factList),
+ g: model,
+ h: render,
+ i: diff
+ };
+}
+
+
+
+// MAP
+
+
+var _VirtualDom_map = F2(function(tagger, node)
+{
+ return {
+ $: 4,
+ j: tagger,
+ k: node,
+ b: 1 + (node.b || 0)
+ };
+});
+
+
+
+// LAZY
+
+
+function _VirtualDom_thunk(refs, thunk)
+{
+ return {
+ $: 5,
+ l: refs,
+ m: thunk,
+ k: undefined
+ };
+}
+
+var _VirtualDom_lazy = F2(function(func, a)
+{
+ return _VirtualDom_thunk([func, a], function() {
+ return func(a);
+ });
+});
+
+var _VirtualDom_lazy2 = F3(function(func, a, b)
+{
+ return _VirtualDom_thunk([func, a, b], function() {
+ return A2(func, a, b);
+ });
+});
+
+var _VirtualDom_lazy3 = F4(function(func, a, b, c)
+{
+ return _VirtualDom_thunk([func, a, b, c], function() {
+ return A3(func, a, b, c);
+ });
+});
+
+var _VirtualDom_lazy4 = F5(function(func, a, b, c, d)
+{
+ return _VirtualDom_thunk([func, a, b, c, d], function() {
+ return A4(func, a, b, c, d);
+ });
+});
+
+var _VirtualDom_lazy5 = F6(function(func, a, b, c, d, e)
+{
+ return _VirtualDom_thunk([func, a, b, c, d, e], function() {
+ return A5(func, a, b, c, d, e);
+ });
+});
+
+var _VirtualDom_lazy6 = F7(function(func, a, b, c, d, e, f)
+{
+ return _VirtualDom_thunk([func, a, b, c, d, e, f], function() {
+ return A6(func, a, b, c, d, e, f);
+ });
+});
+
+var _VirtualDom_lazy7 = F8(function(func, a, b, c, d, e, f, g)
+{
+ return _VirtualDom_thunk([func, a, b, c, d, e, f, g], function() {
+ return A7(func, a, b, c, d, e, f, g);
+ });