aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.bsb.lock1
-rw-r--r--.gitignore7
-rw-r--r--.ocamlformat1
-rw-r--r--.tmuxinator.yml6
-rw-r--r--Makefile51
-rw-r--r--README.md6
-rw-r--r--bsconfig.json22
-rw-r--r--content/plats/riz-vinaigre-casserole.md14
-rwxr-xr-xdev15
-rw-r--r--package-lock.json150
-rw-r--r--package.json14
-rw-r--r--rollup.config.js13
-rw-r--r--shell.nix23
-rw-r--r--src/arrayUtils.ml8
-rw-r--r--src/dom.ts54
-rw-r--r--src/domUtils.ml37
-rw-r--r--src/main.ml63
-rw-r--r--src/main.ts71
-rw-r--r--src/number.ml57
-rw-r--r--src/number.ts66
-rw-r--r--static/main.css3
21 files changed, 418 insertions, 264 deletions
diff --git a/.bsb.lock b/.bsb.lock
new file mode 100644
index 0000000..c9ded0e
--- /dev/null
+++ b/.bsb.lock
@@ -0,0 +1 @@
+13414 \ No newline at end of file
diff --git a/.gitignore b/.gitignore
index dab6f5e..05a3294 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,6 @@
static/main.js
-public
-.build
+node_modules/
+lib/
+.merlin
+*.bs.js
+public/
diff --git a/.ocamlformat b/.ocamlformat
new file mode 100644
index 0000000..df48a53
--- /dev/null
+++ b/.ocamlformat
@@ -0,0 +1 @@
+version=0.12
diff --git a/.tmuxinator.yml b/.tmuxinator.yml
index e445c6c..ca9122c 100644
--- a/.tmuxinator.yml
+++ b/.tmuxinator.yml
@@ -8,5 +8,7 @@ windows:
panes:
- zola:
- zola serve
- - ts:
- - ./dev watch-ts
+ - ocaml:
+ - ./dev watch-ocaml
+ - js:
+ - ./dev watch-js
diff --git a/Makefile b/Makefile
index 21b2356..240bde1 100644
--- a/Makefile
+++ b/Makefile
@@ -1,45 +1,24 @@
-BUILD_DIRECTORY = ".build"
+export PATH := node_modules/.bin:$(PATH)
build: static/main.js
@echo "Building site"
- zola build
+ @zola build
-static/main.js: $(shell find src)
+static/main.js: node_modules $(shell find src \( -type d -o \( -type f -a -regex ".*\.ml" \) \))
@echo "Building $@"
-ifeq ($(DEV_MODE), true)
- @tsc \
- --strict \
- --noUnusedLocals \
- --noUnusedParameters \
- --noImplicitReturns \
- --outDir $(BUILD_DIRECTORY) \
- --module es2015 \
- src/main.ts
- @rollup \
- --input $(BUILD_DIRECTORY)/main.js \
- --file $@ \
- --format iife
-else
- @tsc \
- --strict \
- --noUnusedLocals \
- --noUnusedParameters \
- --noImplicitReturns \
- --outDir $(BUILD_DIRECTORY) \
- --module es2015 \
- src/main.ts
- @rollup \
- --input $(BUILD_DIRECTORY)/main.js \
- --file $@ \
- --format iife
- @terser $@ \
- --output $@ \
- --compress \
- --mangle
-endif
+ @bsb -make-world
+ @rollup --config rollup.config.js
+ @terser $@ --output $@ --compress --mangle
+
+node_modules: package.json
+ @bsb -init init
+ @mv init/node_modules .
+ @rm -rf init
+ @npm install
+ @touch -c node_modules
clean:
@echo "Cleaning"
- @rm -rf $(BUILD_DIRECTORY)
- @rm -rf public
@rm -f static/main.js
+ @rm -rf node_modules lib
+ @find src -name '*.bs.js' -exec rm {} \;
diff --git a/README.md b/README.md
index 9ce01f6..f9d3246 100644
--- a/README.md
+++ b/README.md
@@ -21,3 +21,9 @@ Later, stop the environment with:
```bash
nix-shell --run ./deploy
```
+
+## Bucklescript links
+
+- [Documentation](https://bucklescript.github.io/docs/en/interop-overview)
+- [Ocaml std API](https://caml.inria.fr/pub/docs/manual-ocaml-4.02/stdlib.html)
+- [Libraries](https://bucklescript.github.io/bucklescript/api/index.html)
diff --git a/bsconfig.json b/bsconfig.json
new file mode 100644
index 0000000..ff68748
--- /dev/null
+++ b/bsconfig.json
@@ -0,0 +1,22 @@
+{
+ "name": "cooking",
+ "version": "0.1.0",
+ "sources": {
+ "dir" : "src",
+ "subdirs" : true
+ },
+ "package-specs": {
+ "module": "es6",
+ "in-source": true
+ },
+ "suffix": ".bs.js",
+ "bs-dependencies": [
+ "bs-webapi"
+ ],
+ "warnings": {
+ "number": "+A-42-40",
+ "error": "+A-40"
+ },
+ "bsc-flags": ["-bs-super-errors"],
+ "refmt": 3
+}
diff --git a/content/plats/riz-vinaigre-casserole.md b/content/plats/riz-vinaigre-casserole.md
index 67f66ac..281f584 100644
--- a/content/plats/riz-vinaigre-casserole.md
+++ b/content/plats/riz-vinaigre-casserole.md
@@ -17,10 +17,8 @@ Recette
Préparation du riz
------------------
-1. Verser dans un saladier :
- 1. un peu de sel ;
- 2. le riz ;
- 3. de l’eau jusqu’au dessus du niveau du riz.
+1. Verser dans un saladier : un peu de sel, le riz, et de l’eau jusqu’au dessus
+ du niveau du riz.
2. Remuer le riz en le soulevant.
3. Tant que l’eau se trouble, changer l’eau et remuer à nouveau.
4. Égoutter le riz.
@@ -28,15 +26,11 @@ Préparation du riz
Chauffe du riz
--------------
-1. Verser dans une casserole :
- 1. de l’eau ;
- 2. la préparation de riz.
+1. Verser dans une casserole de l’eau et la préparation de riz.
2. Poser un couvercle sur la casserole et le garder durant tout le temps de
chauffe.
3. Porter l’eau à ébullition.
-4. Chauffer :
- 1. 2 minutes à feu fort ;
- 1. puis 10 minutes à feu doux.
+4. Chauffer 2 minutes à feu fort, puis 10 minutes à feu doux.
5. Sortir la casserole du feu et attendre 10 minutes.
Mélange final
diff --git a/dev b/dev
index 36849ac..d0acad0 100755
--- a/dev
+++ b/dev
@@ -2,27 +2,26 @@
cd "$(dirname $0)"
CMD="$1"
PROJECT="cooking"
-export DEV_MODE=true
if [ "$CMD" = "start" ]; then
- nix-shell --run "tmuxinator local"
+ nix-shell --run "make node_modules && tmuxinator local"
elif [ "$CMD" = "stop" ]; then
nix-shell --run "tmux kill-session -t $PROJECT"
-elif [ "$CMD" = "watch-ts" ]; then
+elif [ "$CMD" = "watch-ocaml" ]; then
- rm -f static/main.js
+ bsb -make-world -w
- while true; do
- find src | entr -d -s "clear && make static/main.js"
- done
+elif [ "$CMD" = "watch-js" ]; then
+
+ node_modules/.bin/rollup --watch --config rollup.config.js
else
- echo "Usage: $0 start|stop|watch-frontend"
+ echo "Usage: $0 start|stop|watch-ocaml|watch-js"
exit 1
fi
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..e061338
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,150 @@
+{
+ "name": "cooking",
+ "version": "0.1.0",
+ "lockfileVersion": 1,
+ "requires": true,
+ "dependencies": {
+ "@rollup/plugin-node-resolve": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-7.1.1.tgz",
+ "integrity": "sha512-14ddhD7TnemeHE97a4rLOhobfYvUVcaYuqTnL8Ti7Jxi9V9Jr5LY7Gko4HZ5k4h4vqQM0gBQt6tsp9xXW94WPA==",
+ "dev": true,
+ "requires": {
+ "@rollup/pluginutils": "^3.0.6",
+ "@types/resolve": "0.0.8",
+ "builtin-modules": "^3.1.0",
+ "is-module": "^1.0.0",
+ "resolve": "^1.14.2"
+ }
+ },
+ "@rollup/pluginutils": {
+ "version": "3.0.8",
+ "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.0.8.tgz",
+ "integrity": "sha512-rYGeAc4sxcZ+kPG/Tw4/fwJODC3IXHYDH4qusdN/b6aLw5LPUbzpecYbEJh4sVQGPFJxd2dBU4kc1H3oy9/bnw==",
+ "dev": true,
+ "requires": {
+ "estree-walker": "^1.0.1"
+ }
+ },
+ "@types/estree": {
+ "version": "0.0.42",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.42.tgz",
+ "integrity": "sha512-K1DPVvnBCPxzD+G51/cxVIoc2X8uUVl1zpJeE6iKcgHMj4+tbat5Xu4TjV7v2QSDbIeAfLi2hIk+u2+s0MlpUQ==",
+ "dev": true
+ },
+ "@types/node": {
+ "version": "13.7.1",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-13.7.1.tgz",
+ "integrity": "sha512-Zq8gcQGmn4txQEJeiXo/KiLpon8TzAl0kmKH4zdWctPj05nWwp1ClMdAVEloqrQKfaC48PNLdgN/aVaLqUrluA==",
+ "dev": true
+ },
+ "@types/resolve": {
+ "version": "0.0.8",
+ "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz",
+ "integrity": "sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ==",
+ "dev": true,
+ "requires": {
+ "@types/node": "*"
+ }
+ },
+ "acorn": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.0.tgz",
+ "integrity": "sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ==",
+ "dev": true
+ },
+ "bs-platform": {
+ "version": "7.0.1",
+ "dev": true
+ },
+ "bs-webapi": {
+ "version": "0.15.7",
+ "resolved": "https://registry.npmjs.org/bs-webapi/-/bs-webapi-0.15.7.tgz",
+ "integrity": "sha512-Mu8H+9DRIPK6VuqgzyUp5FoeBLSlhOqQcqc6G05At019kIZowQU783MoSedTbt9OctE9N1yO91DoDzMTWHspdg==",
+ "dev": true
+ },
+ "buffer-from": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
+ "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==",
+ "dev": true
+ },
+ "builtin-modules": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.1.0.tgz",
+ "integrity": "sha512-k0KL0aWZuBt2lrxrcASWDfwOLMnodeQjodT/1SxEQAXsHANgo6ZC/VEaSEHCXt7aSTZ4/4H5LKa+tBXmW7Vtvw==",
+ "dev": true
+ },
+ "commander": {
+ "version": "2.20.3",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+ "dev": true
+ },
+ "estree-walker": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz",
+ "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==",
+ "dev": true
+ },
+ "is-module": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz",
+ "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=",
+ "dev": true
+ },
+ "path-parse": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
+ "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
+ "dev": true
+ },
+ "resolve": {
+ "version": "1.15.1",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.1.tgz",
+ "integrity": "sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==",
+ "dev": true,
+ "requires": {
+ "path-parse": "^1.0.6"
+ }
+ },
+ "rollup": {
+ "version": "1.31.0",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-1.31.0.tgz",
+ "integrity": "sha512-9C6ovSyNeEwvuRuUUmsTpJcXac1AwSL1a3x+O5lpmQKZqi5mmrjauLeqIjvREC+yNRR8fPdzByojDng+af3nVw==",
+ "dev": true,
+ "requires": {
+ "@types/estree": "*",
+ "@types/node": "*",
+ "acorn": "^7.1.0"
+ }
+ },
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true
+ },
+ "source-map-support": {
+ "version": "0.5.16",
+ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.16.tgz",
+ "integrity": "sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==",
+ "dev": true,
+ "requires": {
+ "buffer-from": "^1.0.0",
+ "source-map": "^0.6.0"
+ }
+ },
+ "terser": {
+ "version": "4.6.3",
+ "resolved": "https://registry.npmjs.org/terser/-/terser-4.6.3.tgz",
+ "integrity": "sha512-Lw+ieAXmY69d09IIc/yqeBqXpEQIpDGZqT34ui1QWXIUpR2RjbqEkT8X7Lgex19hslSqcWM5iMN2kM11eMsESQ==",
+ "dev": true,
+ "requires": {
+ "commander": "^2.20.0",
+ "source-map": "~0.6.1",
+ "source-map-support": "~0.5.12"
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..97e41ba
--- /dev/null
+++ b/package.json
@@ -0,0 +1,14 @@
+{
+ "name": "cooking",
+ "version": "0.1.0",
+ "keywords": [],
+ "author": "Joris Guyonvarch",
+ "license": "MIT",
+ "devDependencies": {
+ "@rollup/plugin-node-resolve": "^7.1.1",
+ "bs-platform": "^7.0.1",
+ "bs-webapi": "^0.15.7",
+ "rollup": "^1.31.0",
+ "terser": "^4.6.3"
+ }
+}
diff --git a/rollup.config.js b/rollup.config.js
new file mode 100644
index 0000000..39b16d6
--- /dev/null
+++ b/rollup.config.js
@@ -0,0 +1,13 @@
+import resolve from '@rollup/plugin-node-resolve';
+
+export default {
+ input: 'src/main.bs.js',
+ output: {
+ name: 'cooking',
+ file: 'static/main.js',
+ format: 'iife'
+ },
+ plugins: [
+ resolve()
+ ]
+};
diff --git a/shell.nix b/shell.nix
index 02dacb8..e07f28f 100644
--- a/shell.nix
+++ b/shell.nix
@@ -1,23 +1,18 @@
with import (builtins.fetchTarball {
- url = https://github.com/NixOS/nixpkgs/archive/19.09.tar.gz;
- sha256 = "0mhqhq21y5vrr1f30qd2bvydv4bbbslvyzclhw0kdxmkgg3z4c92";
-}) {
- overlays = [
- (self: super: {
- nodePackages = import ./nix/nodePackages {};
- })
- ];
-};
-
+ # https://github.com/NixOS/nixpkgs/commit/77752c6c086512a7c1eb066edcef731696fa2a8e
+ name = "nixpkgs-20-12-2019";
+ url = https://github.com/nixos/nixpkgs/archive/77752c6c086512a7c1eb066edcef731696fa2a8e.tar.gz;
+ sha256 = "1sb6c9pzq4rjc8sj41qw01b38119xk91pmlxwl80fqhg8mj1ai8r";
+}) {};
mkShell {
buildInputs = [
- entr
- nodePackages.rollup
- nodePackages.terser
- nodePackages.typescript
+ bs-platform
+ nodejs
tmux
tmuxinator
zola
+ python
+ ocamlformat
];
}
diff --git a/src/arrayUtils.ml b/src/arrayUtils.ml
new file mode 100644
index 0000000..75319d8
--- /dev/null
+++ b/src/arrayUtils.ml
@@ -0,0 +1,8 @@
+let flatMap (f : 'a -> 'b option) (xs : 'a Js.Array.t) : 'b Js.Array.t =
+ xs |> Js.Array.map f
+ |> Js.Array.filter (fun maybe ->
+ match maybe with Some _ -> true | None -> false)
+ |> Js.Array.map (fun maybe ->
+ match maybe with
+ | Some x -> x
+ | None -> Js.Exn.raiseError "Unexpected None")
diff --git a/src/dom.ts b/src/dom.ts
deleted file mode 100644
index 6b1c803..0000000
--- a/src/dom.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-type Attribute = string | boolean | ((e: Event) => void)
-
-type Child = Element | string
-
-export function h(tag: string, attrs: {[key: string]: Attribute}, children: Array<Child> = []): Element {
- let element = document.createElement(tag)
-
- for (let name in attrs) {
- let value = attrs[name]
- if (typeof value === 'boolean') {
- if (value) {
- element.setAttribute(name, name)
- }
- } else if (typeof value === 'function') {
- (element as any)[name] = (e: Event) => {
- (value as ((e: Event) => void))(e)
- }
- } else {
- element.setAttribute(name, value)
- }
- }
-
- children.forEach(child => {
- if (typeof child === 'string') {
- element.appendChild(document.createTextNode(child))
- } else {
- element.appendChild(child)
- }
- })
-
- return element
-}
-
-export function toggleClassName(node: Element, className: string) {
- if (node.className === className) {
- node.className = ''
- } else {
- node.className = className
- }
-}
-
-export function nodeListToArray(nodeList: NodeListOf<HTMLElement>): HTMLElement[] {
- const xs: HTMLElement[] = [];
- nodeList.forEach(node => xs.push(node))
- return xs
-}
-
-export function replace(node: Node, replacement: Node) {
- const parentNode = node.parentNode
-
- if (parentNode) {
- parentNode.replaceChild(replacement, node)
- }
-}
diff --git a/src/domUtils.ml b/src/domUtils.ml
new file mode 100644
index 0000000..282ac12
--- /dev/null
+++ b/src/domUtils.ml
@@ -0,0 +1,37 @@
+open Webapi.Dom
+
+let toggleClassName (element : Dom.element) (className : string) : unit =
+ Element.setClassName element
+ (if Element.className element == className then "" else className)
+
+type child = TextChild of string | ElemChild of Dom.element
+
+let h (tag : string) (attributes : (string * string) Js.Array.t)
+ (children : child Js.Array.t) : Dom.element =
+ let element = Document.createElement tag document in
+ let () =
+ attributes
+ |> Js.Array.forEach (fun a -> Element.setAttribute (fst a) (snd a) element)
+ in
+ let () =
+ children
+ |> Js.Array.forEach (fun c ->
+ match c with
+ | TextChild t ->
+ Element.appendChild (Document.createTextNode t document) element
+ | ElemChild e -> Element.appendChild e element)
+ in
+ element
+
+external replace_child : Dom.node -> Dom.element -> Dom.element -> unit
+ = "replaceChild"
+ [@@bs.send]
+
+let replace (element : Dom.element) (replacement : Dom.element) : unit =
+ match Element.parentNode element with
+ | Some parent -> replace_child parent replacement element
+ | _ -> ()
+
+external value : Dom.eventTarget -> string option = "value" [@@bs.get]
+
+external setValue : Dom.element -> string -> unit = "value" [@@bs.set]
diff --git a/src/main.ml b/src/main.ml
new file mode 100644
index 0000000..a2174f2
--- /dev/null
+++ b/src/main.ml
@@ -0,0 +1,63 @@
+open Webapi.Dom
+
+(* Set up inputs for the ingredients *)
+
+type ingredient = { quantity : float; element : Dom.element }
+
+let ingredients : ingredient Js.Array.t =
+ document
+ |> Document.querySelectorAll ".g-Recipe__Content ul > li"
+ |> NodeList.toArray
+ |> ArrayUtils.flatMap (fun node ->
+ Belt.Option.map (Element.ofNode node) (fun e -> ("li", e)))
+ |> Js.Array.concat
+ ( match Document.querySelector ".g-Recipe__Content h1" document with
+ | Some element -> [| ("h1", element) |]
+ | _ -> [||] )
+ |> ArrayUtils.flatMap (fun (tag, element) ->
+ Belt.Option.map
+ (Number.parseInsideText (Element.innerHTML element))
+ (fun parsed ->
+ let created = Number.createElement tag parsed in
+ let () = DomUtils.replace element created.element in
+ { quantity = parsed.number; element = created.numberInput }))
+
+(* Update ingredients amounts *)
+
+let () =
+ ingredients
+ |> Js.Array.forEach (fun ingredient ->
+ Element.addEventListener "input"
+ (fun e ->
+ Belt.Option.forEach
+ (DomUtils.value (Event.target e))
+ (fun numberStr ->
+ Belt.Option.forEach (Number.parse numberStr) (fun parsed ->
+ let factor = parsed.number /. ingredient.quantity in
+ ingredients
+ |> Js.Array.forEach (fun otherIngredient ->
+ if ingredient.element != otherIngredient.element
+ then
+ DomUtils.setValue otherIngredient.element
+ (Number.prettyPrint
+ (factor *. otherIngredient.quantity))
+ else ()))))
+ ingredient.element)
+
+(* Set up done marks for steps *)
+
+let () =
+ document
+ |> Document.querySelectorAll ".g-Recipe__Content ol > li"
+ |> NodeList.toArray
+ |> Js.Array.forEach (fun node ->
+ match Element.ofNode node with
+ | Some element ->
+ Element.addEventListener "click"
+ (fun e ->
+ let () =
+ DomUtils.toggleClassName element "g-Recipe__Completed"
+ in
+ Event.stopPropagation e)
+ element
+ | _ -> ())
diff --git a/src/main.ts b/src/main.ts
deleted file mode 100644
index 184d26d..0000000
--- a/src/main.ts
+++ /dev/null
@@ -1,71 +0,0 @@
-import * as number from './number'
-import * as dom from './dom'
-
-/* Set up inputs for the ingredients */
-
-const itemEntries =
- dom.nodeListToArray(document.querySelectorAll('.g-Recipe__Content ul > li'))
- .map(itemNode => ({ name: 'li', node: itemNode }))
-
-const h1 = document.querySelector<HTMLElement>('.g-Recipe__Content h1')
-
-if (h1 !== null) {
- itemEntries.push({ name: 'h1', node: h1 })
-}
-
-const inputs = setupInputs(itemEntries)
-
-inputs.map(input => {
- input.node.oninput = e => {
- if (e.target !==null) {
- const parsed = number.parse((e.target as HTMLInputElement).value)
-
- if (parsed !== undefined) {
- const factor = parsed.number / input.number
- inputs.map(input2 => {
- if (input.node !== input2.node) {
- input2.node.value = number.prettyPrint(input2.number * factor)
- }
- })
- }
- }
- }
-})
-
-interface InputTag {
- name: string;
- node: HTMLElement;
-}
-
-interface InputResult {
- number: number,
- node: HTMLInputElement
-}
-
-function setupInputs(tags: InputTag[]): InputResult[] {
- const res: InputResult[] = []
-
- tags.forEach(tag => {
- const parsed = number.parseInsideText(tag.node.innerText)
-
- if (parsed !== undefined) {
- const numberNode = number.node(tag.name, parsed)
- dom.replace(tag.node, numberNode.node)
- res.push({
- number: parsed.number,
- node: numberNode.numberInput
- })
- }
- })
-
- return res
-}
-
-/* Set up done marks for steps */
-
-dom.nodeListToArray(document.querySelectorAll('.g-Recipe__Content ol > li')).forEach(todo => {
- todo.onclick = e => {
- dom.toggleClassName(todo, 'g-Recipe__Completed')
- e.stopPropagation()
- }
-})
diff --git a/src/number.ml b/src/number.ml
new file mode 100644
index 0000000..cdd9ef8
--- /dev/null
+++ b/src/number.ml
@@ -0,0 +1,57 @@
+type parseInsideTextResult = { before : string; number : float; after : string }
+
+let execRegex (regex : Js.Re.t) (str : string) : string option Js.Array.t =
+ match Js.Re.exec_ regex str with
+ | Some result -> Js.Array.map Js.toOption (Js.Re.captures result)
+ | None -> [||]
+
+let parseInsideText (str : string) : parseInsideTextResult option =
+ match execRegex [%re "/^([^\\d]*)(\\d+)((\\.|,)(\\d+))?(.*)/"] str with
+ | [| _; Some before; Some intPart; _; _; decPart; Some after |] ->
+ Some
+ {
+ before;
+ number =
+ Js.Float.fromString
+ ( intPart
+ ^ Belt.Option.mapWithDefault decPart "" (fun str -> "." ^ str) );
+ after;
+ }
+ | _ -> None
+
+type parseResult = { number : float; remaining : string }
+
+let parse (str : string) : parseResult option =
+ match parseInsideText str with
+ | Some parseResult ->
+ if parseResult.before == "" then
+ Some { number = parseResult.number; remaining = parseResult.after }
+ else None
+ | _ -> None
+
+type numberElement = { element : Dom.element; numberInput : Dom.element }
+
+let prettyPrint (number : float) : string =
+ let strNumber = Js.Float.toString number in
+ match Js.String.split "." strNumber with
+ | [| intPart; decPart |] ->
+ intPart ^ "," ^ Js.String.slice ~from:0 ~to_:2 decPart
+ | _ -> strNumber
+
+let createElement (tag : string) (content : parseInsideTextResult) :
+ numberElement =
+ let numberInput =
+ DomUtils.h "input"
+ [| ("class", "g-Number"); ("value", prettyPrint content.number) |]
+ [| DomUtils.TextChild "" |]
+ in
+ {
+ element =
+ DomUtils.h tag [||]
+ [|
+ DomUtils.TextChild content.before;
+ DomUtils.ElemChild numberInput;
+ DomUtils.TextChild content.after;
+ |];
+ numberInput;
+ }
diff --git a/src/number.ts b/src/number.ts
deleted file mode 100644
index 6663329..0000000
--- a/src/number.ts
+++ /dev/null
@@ -1,66 +0,0 @@
-import { h } from './dom'
-
-interface ParseInsideTextResult {
- before: string;
- number: number;
- after: string;
-}
-
-export function parseInsideText(str: string): ParseInsideTextResult | undefined {
- let res = str.match(/^([^\d]*)(\d+)((\.|,)(\d+))?(.*)/)
-
- if (res !== null && res.length === 7) {
- return {
- before: res[1],
- number: parseFloat(res[2] + '.' + res[5]),
- after: res[6]
- }
- } else {
- return undefined;
- }
-}
-
-interface ParseResult {
- number: number;
- remaining: string;
-}
-
-export function parse(str: string): ParseResult | undefined {
- let res = str.match(/^(\d+)((\.|,)(\d+))?(.*$)/)
-
- if (res !== null && res.length === 6) {
- return {
- number: parseFloat(res[1] + '.' + res[4]),
- remaining: res[5]
- }
- } else {
- return undefined;
- }
-}
-
-export interface Node {
- node: Element;
- numberInput: HTMLInputElement;
-}
-
-export function node(tag: string, content: ParseInsideTextResult): Node {
- let numberInput = h('input', {
- 'class': 'g-Number',
- 'value': prettyPrint(content.number)
- }) as HTMLInputElement
-
- return {
- node: h(tag, {}, [content.before, numberInput, content.after]),
- numberInput: numberInput
- }
-}
-
-export function prettyPrint(n: number): string {
- const xs = n.toString().split('.')
-
- if (xs.length == 2) {
- return xs[0] + ',' + xs[1].substring(0, 2)
- } else {
- return xs[0]
- }
-}
diff --git a/static/main.css b/static/main.css
index 7595b24..9d01154 100644
--- a/static/main.css
+++ b/static/main.css
@@ -132,7 +132,8 @@ body {
}
.g-Recipe__Content ol > li {
- padding-left: 3rem;
+ padding: 0 0 1rem 3rem;
+ margin-bottom: 0rem;
position: relative;
text-align: justify;
}