first commit

This is the first commit.

With stuff on several lines.
This commit is contained in:
Christian Lawson-Perfect 2025-02-08 14:48:40 +00:00
commit 899087ed49
11 changed files with 1101 additions and 0 deletions

7
.gitignore vendored Normal file
View file

@ -0,0 +1,7 @@
.make.*
dist/code-editor.mjs
dist/think-editor.js
elm-stuff
jj/status
log
error.txt

2
.watchmakerc Normal file
View file

@ -0,0 +1,2 @@
extensions:
- .elm

20
Makefile Normal file
View file

@ -0,0 +1,20 @@
DIRNAME=$(notdir $(CURDIR))
PROD_DIR=/srv/think.somethingorotherwhatever.com/thinks/static/thinks
ELMS=$(wildcard src/*.elm)
deploy: $(PROD_DIR)/think-editor.js $(PROD_DIR)/load-think-editor.mjs $(PROD_DIR)/think-editor.css
build: dist/think-editor.js
dist/think-editor.js: src/App.elm $(ELMS)
-elm make $< --output=$@ 2> error.txt
@cat error.txt
$(PROD_DIR)/%: dist/%
cp $< $@
upload: app.js index.html style.css
rsync -avz . clpland:~/domains/somethingorotherwhatever.com/html/$(DIRNAME)
@echo "Uploaded to https://somethingorotherwhatever.com/$(DIRNAME)"

1
README.txt Normal file
View file

@ -0,0 +1 @@
The Elm app for the think editor.

26
dist/load-think-editor.mjs vendored Normal file
View file

@ -0,0 +1,26 @@
import './code-editor.mjs';
export default async function init_app() {
const flags = JSON.parse(document.getElementById('think-editor-data').textContent);
const packages = await (await fetch('https://elm-package-list.think.somethingorotherwhatever.com/elm-packages.json')).json();
flags.elm_packages = packages;
flags.csrf_token = document.getElementById('csrftoken')?.textContent || '';
const app = Elm.App.init({node: document.body, flags});
app.ports.reload_preview.subscribe(() => {
console.log('reload preview');
const iframe = document.getElementById('preview-frame');
if(iframe) {
const src = iframe.src;
iframe.src = "";
setTimeout(() => {
iframe.src = src;
},10);
}
})
app.ports.show_modal.subscribe(id => {
console.log(id);
document.getElementById(id).showModal()
})
}

243
dist/think-editor.css vendored Normal file
View file

@ -0,0 +1,243 @@
:root {
--spacing: 1em;
--half-spacing: calc(0.5 * var(--spacing));
--double-spacing: calc(2 * var(--spacing));
--editor-size: 50%;
}
* {
box-sizing: border-box;
}
body {
color-scheme: light dark;
font-family: sans-serif;
display: grid;
grid-template-rows: auto 1fr;
min-height: 100vh;
margin: 0;
padding: var(--half-spacing);
& > header {
padding: var(--spacing);
& h1 {
margin: 0;
}
}
}
@media (prefers-color-scheme: dark) {
body {
background: black;
color: white;
}
}
header {
& #think-controls {
display: flex;
gap: var(--spacing);
margin: 0;
}
}
#editor-size-input + output {
width: 5em;
}
.file-path {
font-family: monospace;
}
.think-editor {
display: grid;
gap: var(--spacing);
height: 100%;
overflow: hidden;
--col-1-width: auto;
grid-template:
"nav editor preview" min-content
"log editor preview" 1fr
/ var(--col-1-width) var(--editor-size) var(--preview-size)
;
&:has(#main-nav[open], #log[open]) {
--col-1-width: 20em;
}
& > #main-nav {
grid-area: nav;
}
& > #log {
grid-area: log;
width: 100%;
overflow: auto;
}
& .dragging {
background: red;
}
& summary {
background: #eee;
}
& > #main-nav > nav {
display: flex;
flex-direction: column;
gap: var(--spacing);
& #file-tree {
margin: 0;
overflow: auto;
flex-grow: 1;
& > li {
margin-top: var(--half-spacing);
& > a {
text-decoration: none;
}
}
& .dir {
font-weight: bold;
}
& .dir + .file {
margin-top: var(--spacing);
}
}
& form {
display: grid;
grid-template-columns: 1fr 6em;
align-content: start;
align-items: start;
}
& #make-log {
& > pre {
max-width: 20em;
overflow: auto;
}
}
}
& #editor {
overflow-x: hidden;
flex-grow: 1;
flex-basis: var(--editor-size);
max-height: 85vh;
grid-area: editor;
& #editor-controls {
position: sticky;
top: 0;
display: flex;
gap: var(--spacing);
justify-content: space-between;
& > details {
text-align: right;
& > summary {
user-select: none;
}
& button {
margin: var(--half-spacing) 0;
}
}
background: white;
z-index: 1;
}
& #code-editor {
display: block;
max-width: 50vw;
padding-bottom: 10em;
}
}
& #preview {
display: flex;
flex-direction: column;
grid-area: preview;
&[open] {
flex-grow: 1;
flex-shrink: 1;
flex-basis: calc(100% - var(--editor-size));
}
& > summary {
text-align: right;
}
& > iframe {
width: 100%;
height: calc(100% - 3em);
border: none;
}
&[closed] > iframe {
display: none;
}
}
}
#file-form {
overflow: auto;
max-width: 100%;
}
@media (max-width: 100ch) {
html {
font-size: min(3vw, 16px);
}
body {
grid-template-columns: calc(100svw - var(--spacing));
}
.think-editor {
overflow: visible;
grid-template:
"nav"
"log"
"editor"
"preview"
;
& > * ~ * {
border-top: medium solid #888;
margin-top: var(--spacing);
}
& nav {
flex-direction: row;
flex-wrap: wrap;
& form {
flex-grow: 1;
}
& #file-tree {
max-height: 7em;
}
}
& #editor {
overflow: auto;
& #code-editor {
max-width: none;
}
}
& #preview {
height: 100vh;
}
}
}

30
elm.json Normal file
View file

@ -0,0 +1,30 @@
{
"type": "application",
"source-directories": [
"src"
],
"elm-version": "0.19.1",
"dependencies": {
"direct": {
"elm/browser": "1.0.2",
"elm/core": "1.0.5",
"elm/file": "1.0.5",
"elm/html": "1.0.0",
"elm/http": "2.0.0",
"elm/json": "1.1.3",
"elm/url": "1.0.0",
"elm-community/json-extra": "4.3.0"
},
"indirect": {
"elm/bytes": "1.0.8",
"elm/parser": "1.1.0",
"elm/time": "1.0.0",
"elm/virtual-dom": "1.0.3",
"rtfeldman/elm-iso8601-date-strings": "1.1.4"
}
},
"test-dependencies": {
"direct": {},
"indirect": {}
}
}

39
index.html Normal file
View file

@ -0,0 +1,39 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Elm app by clp</title>
<link rel="stylesheet" href="dist/think-editor.css">
</head>
<body>
<p>Think editor test page</p>
<p>It's loading!</p>
<script id="think-editor-data" type="application/json">
{
"preview_url": "https://somethingorotherwhatever.com/tiny-elvis/",
"slug": "loaded-thing",
"files": [
{"path": ".", "is_dir": true, "name": ".."},
{"path": "src/poo.elm", "is_dir": false, "name": "poo.elm"}
],
"file_path": "src/poo.elm",
"is_dir": false,
"file_content": "this\nis\nmy\nfile",
"csrf_token": "arg",
"elm_packages": [
{
"name": "poo",
"summary": "poop",
"version": "1",
"license": "p"
}
]
}
</script>
<script src="dist/think-editor.js"></script>
<script src="test.mjs" type="module"></script>
</body>
</html>

21
show-error.mjs Normal file
View file

@ -0,0 +1,21 @@
export default fetch('/error.txt').then(r=>{
if(r.ok) {
return r.text();
} else {
throw('');
}
}).then(text => {
if(!text) {
return false;
}
document.body.innerHTML = '';
const error_show = document.createElement('pre');
error_show.setAttribute('id','build-error');
error_show.style.background = 'black';
error_show.style.color = 'white';
error_show.style.padding = '1em';
error_show.style['font-size'] = '16px';
error_show.textContent = text;
document.body.appendChild(error_show);
return true;
}).catch(e => false);

702
src/App.elm Normal file
View file

@ -0,0 +1,702 @@
port module App exposing (..)
import Browser
import Browser.Navigation
import File exposing (File)
import Html as H exposing (Html)
import Html.Attributes as HA
import Html.Events as HE
import Http exposing (Error(..))
import Json.Decode as JD
import Json.Decode.Extra exposing (andMap)
import Json.Encode as JE
import Process
import Task
import Url.Builder as UB
port reload_preview : () -> Cmd msg
port show_modal : String -> Cmd msg
delayMsg : Float -> msg -> Cmd msg
delayMsg delay msg =
Task.perform (always msg) (Process.sleep delay)
main = Browser.document
{ init = init
, update = update
, subscriptions = subscriptions
, view = view
}
type alias FileInfo =
{ name : String
, is_dir : Bool
, path : String
}
type alias ElmPackage =
{ name : String
, summary : String
, version : String
, license : String
}
decode_elm_package =
JD.map4
ElmPackage
(JD.field "name" JD.string)
(JD.field "summary" JD.string)
(JD.field "version" JD.string)
(JD.field "license" JD.string)
file_info name = { name = name, path = name, is_dir = False }
dir_info name = { name = name, path = name, is_dir = True }
file_edit_url f = (UB.relative [] [UB.string "path" f.path])
type Log
= NotLoaded
| HttpError Http.Error
| MakeResult String String
| MakeError String
| CommandResult String String
type alias CommandResult = Result String { stdout : String, stderr : String }
type alias Model =
{ show_preview : Bool
, show_log : Bool
, editor_size : Float
, content_changed : Bool
, log : Log
, command_to_run : String
, selected_package : String
, jj_status : String
, commit_message : String
, csrf_token : String
, preview_url : String
, slug : String
, files : List FileInfo
, file_path : String
, file_content : String
, is_dir : Bool
, elm_packages : List ElmPackage
}
init_model : Model
init_model =
{ show_preview = True
, show_log = False
, editor_size = 0.5
, content_changed = False
, log = NotLoaded
, command_to_run = ""
, selected_package = ""
, jj_status = ""
, commit_message = ""
, csrf_token = ""
, preview_url = ""
, slug = "not-a-real-thing"
, files = []
, file_path = "Oops!"
, file_content = "The editor has not loaded successfully."
, is_dir = False
, elm_packages = []
}
type DropFileAction
= Upload
| Content
type Msg
= SetFileContent String
| SaveContent String
| FileSaved String (Result Http.Error CommandResult)
| SetLog (Result Http.Error String)
| ReloadLog
| ReloadPreview
| SetEditorSize Float
| TogglePreview Bool
| ToggleLog Bool
| DropFiles DropFileAction (List File)
| NoOp
| UploadFile File String
| FileUploaded File
| SetCommand String
| RunCommand String
| ReceiveCommandResult (Result Http.Error CommandResult)
| SelectPackage String
| ShowCommitModal
| ReceiveJJStatus String
| SetCommitMessage String
| JJCommit
| JJCommitResponse (Result Http.Error CommandResult)
init : JE.Value -> (Model, Cmd Msg)
init flags = (load_flags flags, reload_log)
load_flags =
JD.decodeValue
( JD.succeed
(Model
init_model.show_preview
init_model.show_log
init_model.editor_size
init_model.content_changed
init_model.log
init_model.command_to_run
init_model.selected_package
init_model.jj_status
init_model.commit_message
)
|> andMap (JD.field "csrf_token" JD.string)
|> andMap (JD.field "preview_url" JD.string)
|> andMap (JD.field "slug" JD.string)
|> andMap (JD.field "files" (JD.list decode_file_info))
|> andMap (JD.field "file_path" JD.string)
|> andMap (JD.field "file_content" JD.string)
|> andMap (JD.field "is_dir" JD.bool)
|> andMap (JD.oneOf [JD.field "elm_packages" (JD.list decode_elm_package), JD.succeed []])
)
>> Result.withDefault init_model
decode_file_info =
JD.map3
FileInfo
(JD.field "name" JD.string)
(JD.field "is_dir" JD.bool)
(JD.field "path" JD.string)
decode_command_response =aq
JD.oneOf
[ JD.field "error" JD.string |> JD.map Err
, JD.map2 (\stdout stderr -> Ok { stdout = stdout, stderr = stderr })
(JD.field "stdout" JD.string)
(JD.field "stderr" JD.string)
]
set_log : Log -> Model -> Model
set_log log model = { model | log = log, show_log = True }
nocmd model = (model, Cmd.none)
update : Msg -> Model -> (Model, Cmd Msg)
update msg model = case msg of
SetFileContent content -> ({ model | file_content = content, content_changed = True }, delayMsg 1000 (SaveContent content))
SaveContent content ->
if content == model.file_content then
( model
, Http.post
{ url = "save-file"
, body = Http.multipartBody
[ Http.stringPart "path" model.file_path
, Http.stringPart "content" model.file_content
, Http.stringPart "csrfmiddlewaretoken" model.csrf_token
]
, expect = Http.expectJson (FileSaved content) decode_command_response
}
)
else
(model, Cmd.none)
FileSaved content response -> case response of
Ok (Ok res) -> ({ model | content_changed = content /= model.file_content, log = MakeResult res.stdout res.stderr }, delayMsg 1 ReloadPreview)
Ok (Err errmsg) -> ({ model | content_changed = content /= model.file_content, log = MakeError errmsg }, delayMsg 1 ReloadPreview)
Err err -> { model | log = HttpError err } |> nocmd
ReloadLog ->
(model
, reload_log
)
SetLog response -> case response of
Ok log ->
{ model | log = (MakeResult log "") } |> nocmd
Err errmsg -> model |> set_log (HttpError errmsg) |> nocmd
ReloadPreview -> (model, reload_preview ())
SetEditorSize size -> { model | editor_size = size } |> nocmd
TogglePreview show -> { model | show_preview = show } |> nocmd
ToggleLog show -> { model | show_log = show } |> nocmd
DropFiles action files -> case List.head files of
Just file -> case action of
Content -> (model, Task.perform SetFileContent (File.toString file))
Upload -> (model, Task.perform (UploadFile file) (File.toString file))
Nothing -> (model, Cmd.none)
UploadFile file contents ->
( model
, Http.post
{ url = "save-file"
, body = Http.multipartBody
[ Http.stringPart "path" (File.name file)
, Http.stringPart "content" contents
, Http.stringPart "csrfmiddlewaretoken" model.csrf_token
]
, expect = Http.expectWhatever
(\r -> case r of
Ok _ -> FileUploaded file
Err _ -> NoOp
)
}
)
FileUploaded file ->
let
name = File.name file
url = file_edit_url { name = name, path = name, is_dir = False }
in
(model, Browser.Navigation.load url)
SetCommand cmd -> { model | command_to_run = cmd } |> nocmd
RunCommand cmd ->
(model
, Http.post
{ url = "run-command"
, body = Http.multipartBody
[ Http.stringPart "command" cmd
, Http.stringPart "csrfmiddlewaretoken" model.csrf_token
]
, expect = Http.expectJson ReceiveCommandResult decode_command_response
}
)
SelectPackage name -> { model | selected_package = name } |> nocmd
ReceiveCommandResult response -> case response of
Ok (Ok res) -> model |> set_log (MakeResult res.stdout res.stderr) |> nocmd
Ok (Err errmsg) -> model |> set_log (MakeError errmsg) |> nocmd
Err err -> model |> set_log (HttpError err) |> nocmd
ShowCommitModal -> (model, Cmd.batch [fetch_jj_status, show_modal "commit-modal"])
SetCommitMessage message -> { model | commit_message = message } |> nocmd
ReceiveJJStatus status -> { model | jj_status = status, commit_message = "" } |> nocmd
JJCommit ->
( model
, Http.post
{ url = "jj/commit"
, body = Http.multipartBody
[ Http.stringPart "message" model.commit_message
, Http.stringPart "csrfmiddlewaretoken" model.csrf_token
]
, expect = Http.expectJson JJCommitResponse
decode_command_response
}
)
JJCommitResponse response -> case response of
Ok (Ok res) -> { model | log = MakeResult res.stdout res.stderr } |> nocmd
Ok (Err errmsg) -> { model | log = MakeError errmsg } |> nocmd
Err err -> { model | log = HttpError err } |> nocmd
NoOp -> (model, Cmd.none)
reload_log =
Http.get
{ url = "log"
, expect = Http.expectString SetLog
}
fetch_jj_status =
Http.get
{ url = "jj/status"
, expect = Http.expectJson
(\r -> case r of
Ok response -> ReceiveJJStatus response
Err _ -> NoOp
)
(JD.field "status" JD.string)
}
subscriptions model = Sub.none
link url text =
H.a
[ HA.href url]
[ H.text text ]
form : Model -> List (H.Attribute Msg) -> List (Html Msg) -> Html Msg
form model attrs children =
H.form
attrs
( children
++ [ H.input
[ HA.type_ "hidden"
, HA.name "csrfmiddlewaretoken"
, HA.value model.csrf_token
]
[]
]
)
as_percentage : Float -> String
as_percentage amount = (String.fromInt <| round (100 * amount)) ++ "%"
view : Model -> Browser.Document Msg
view model =
{
title = model.file_path ++ " - " ++ model.slug ++ " - Thinks",
body =
[ header model
, H.main_
[ HA.classList
[ ("think-editor", True)
]
, HA.attribute "style" <| ("--editor-size: " ++ (String.fromFloat model.editor_size)++"fr"++";--preview-size: "++(String.fromFloat (1-model.editor_size))++"fr")
]
[ main_nav model
, log_pane model
, if model.is_dir then H.text "" else editor_pane model
, preview_pane model
]
, commit_modal model
]
}
header model =
H.header
[]
[ link "/" "thinks"
, H.h1
[]
[ H.text model.slug ]
, H.p
[ HA.id "think-controls" ]
[ H.a
[ HA.target "preview"
, HA.href model.preview_url
]
[ H.text "Preview" ]
, link "rename" "Rename"
, link "delete" "Delete"
, link ("/new/"++model.slug) "Remix"
, H.label
[ HA.for "editor-size-input"
]
[ H.text "File editor size" ]
, H.input
[ HA.type_ "range"
, HA.id "editor-size-input"
, HA.min "0"
, HA.max "1"
, HA.step "0.05"
, HA.value <| String.fromFloat model.editor_size
, HE.on "input" (JD.at ["target", "valueAsNumber"] JD.float |> JD.map SetEditorSize)
, HA.list "size-values"
]
[]
, H.output
[ HA.for "editor-size-input" ]
[ H.text <| as_percentage model.editor_size ]
, H.datalist
[ HA.id "size-values" ]
[ H.option [ HA.value "0.5" ] [] ]
]
]
main_nav model =
H.details
[ HA.attribute "open" ""
, HA.id "main-nav"
, HE.preventDefaultOn "drop" (JD.at ["dataTransfer", "files"] (JD.list File.decoder) |> JD.map (\f -> (DropFiles Upload f, True)) )
, HE.preventDefaultOn "dragover" (JD.succeed (NoOp,True))
]
[ H.summary
[]
[ H.text "Files" ]
, H.nav
[]
[ H.ul
[ HA.id "file-tree" ]
(List.map (\f ->
H.li
[ HA.classList
[ ("dir", f.is_dir)
, ("file", not f.is_dir)
, ("file-path", True)
]
]
[ link (file_edit_url f) f.name ]
) model.files
)
, form model
[ HA.id "file-form"
, HA.method "GET"
, HA.action "edit"
]
[ H.input
[ HA.attribute "aria-labelledby" "new-file-button"
, HA.id "new-file-path"
, HA.type_ "text"
, HA.name "path"
, HA.value <| (if model.is_dir then model.file_path else String.join "/" <| List.reverse <| List.drop 1 <| List.reverse <| String.split "/" model.file_path)++"/"
]
[]
, H.button
[ HA.id "new-file-button"
, HA.type_ "submit"
]
[ H.text "New file" ]
]
, form model
[ HE.onSubmit <| RunCommand model.command_to_run
]
[ H.input
[ HA.attribute "aria-labelledby" "run-command-button"
, HE.onInput SetCommand
, HA.name "command"
, HA.value model.command_to_run
]
[]
, H.button
[ HA.id "run-command-button"
, HA.type_ "submit"
]
[ H.text "Run" ]
]
, if String.right 4 model.file_path == ".elm" then
form model
[ HE.onSubmit <| case model.selected_package of
"" -> NoOp
p -> RunCommand <| "bash -c \"echo 'Y' | elm install " ++ p ++ "\""
]
[ H.input
[ HA.list "elm-packages"
, HE.onInput SelectPackage
, HA.value <| model.selected_package
]
[]
, H.node "datalist"
[ HA.id "elm-packages"]
(List.map (\p -> H.option [HA.value p.name] [H.text p.name]) model.elm_packages)
, H.button
[ HA.id "install-package-button"
, HA.type_ "submit"
]
[ H.text "Install" ]
]
else
H.text ""
, H.button
[ HA.id "start-commit-button"
, HA.type_ "button"
, HE.onClick ShowCommitModal
]
[ H.text "Commit" ]
]
]
editor_pane model =
H.section
[ HA.id "editor"
, HE.preventDefaultOn "drop" (JD.at ["dataTransfer", "files"] (JD.list File.decoder) |> JD.map (\f -> (DropFiles Content f, True)) )
, HE.preventDefaultOn "dragover" (JD.succeed (NoOp,True))
]
[ H.nav
[ HA.id "editor-controls" ]
[ H.span
[ HA.id "file-path"
, HA.class "file-path"
]
[ H.text model.file_path ]
, H.span
[ HA.id "file-changed-status"
, HA.classList
[ ("changed", model.content_changed) ]
]
[ H.text <| if model.content_changed then "changed" else "saved" ]
, H.details
[]
[ H.summary
[]
[ H.text "Actions" ]
, form model
[ HA.method "POST"
, HA.action "delete-file"
]
[ H.input
[ HA.type_ "hidden"
, HA.name "path"
, HA.value model.file_path
]
[]
, H.button
[ HA.type_ "submit" ]
[ H.text "Delete" ]
]
, form model
[ HA.method "POST"
, HA.action "rename-file"
]
[ H.input
[ HA.type_ "hidden"
, HA.name "path"
, HA.value model.file_path
]
[]
, H.input
[ HA.type_ "text"
, HA.name "newpath"
, HA.value model.file_path
]
[]
, H.button
[ HA.type_ "submit" ]
[ H.text "Rename" ]
]
]
]
, form model
[ HA.id "file-form"
, HA.method "POST"
, HA.action "save-file"
]
[ H.node "code-editor"
[ HE.on "change" (JD.at ["target", "value"] JD.string |> JD.map SetFileContent)
, HA.attribute "content" model.file_content
]
[ H.text model.file_content ]
, H.input
[ HA.name "path"
, HA.value model.file_path
, HA.type_ "hidden"
]
[]
, H.input
[ HA.name "content"
, HA.value model.file_content
, HA.type_ "hidden"
]
[]
]
]
log_pane : Model -> Html Msg
log_pane model =
H.details
([HA.id "log"
, HE.on "toggle" (JD.at ["target", "open"] JD.bool |> JD.map ToggleLog)
]++(if model.show_log then [ HA.attribute "open" ""] else [])
)
[ H.summary
[]
[ H.text "Log" ]
, case model.log of
NotLoaded -> H.text "Not loaded"
MakeResult stdout stderr ->
H.dl
[]
[ H.dt [] [H.text "stdout" ]
, H.dd [] [H.pre [] [ H.text stdout ]]
, H.dt [] [H.text "stderr" ]
, H.dd [] [H.pre [] [ H.text stderr ]]
]
CommandResult stdout stderr ->
H.dl
[]
[ H.dt [] [H.text "stdout" ]
, H.dd [] [H.pre [] [ H.text stdout ]]
, H.dt [] [H.text "stderr" ]
, H.dd [] [H.pre [] [ H.text stderr ]]
]
MakeError err ->
H.div
[]
[ H.h2 [] [ H.text "Error"]
, H.pre [] [H.text err]
]
HttpError err ->
let
text = case err of
BadUrl url -> "Bad URL " ++ url
Timeout -> "Timeout"
NetworkError -> "Network error"
BadStatus code -> "Bad response code " ++ (String.fromInt code)
BadBody body -> "Bad body " ++ body
in
H.div
[]
[ H.h2 [] [ H.text "Network error"]
, H.pre [] [H.text text]
]
]
preview_pane model =
H.details
[ HA.attribute "open" ""
, HA.id "preview"
, HE.on "toggle" (JD.at ["target", "open"] JD.bool |> JD.map TogglePreview)
]
( [ H.summary
[]
[ H.text "Preview"
]
]
++ if model.show_preview then
[ H.iframe
[ HA.id "preview-frame"
, HA.src model.preview_url
]
[]
]
else
[]
)
commit_modal model =
let
view_status status =
H.tr
[]
[ H.td [] [ H.text <| status.path ] ]
in
H.node "dialog"
[ HA.id "commit-modal" ]
[ H.h2 [] [H.text "Commit"]
, H.pre [ HA.id "jj-status" ] [H.text model.jj_status]
, H.form
[ HE.on "submit"
( JD.at ["submitter","value"] JD.string
|> JD.map (\v -> case v of
"cancel" -> NoOp
_ -> JJCommit
)
)
, HA.method "dialog"
]
[ H.textarea
[ HA.value model.commit_message
, HE.onInput SetCommitMessage
]
[]
, H.button
[ HA.value "cancel"
, HA.attribute "formmethod" "dialog"
]
[ H.text "Cancel"]
, H.button
[ HA.type_ "submit"
]
[ H.text "Commit"]
]
]

10
test.mjs Normal file
View file

@ -0,0 +1,10 @@
import show_error from './show-error.mjs';
import init_app from './dist/load-think-editor.mjs';
(async () => {
const compilation_error = await show_error;
if(compilation_error) {
return;
}
init_app();
})()