From 7a01b001ccc4a7bda3da92486903540e3f9754fd Mon Sep 17 00:00:00 2001 From: Joris Date: Thu, 13 Feb 2020 18:54:18 +0100 Subject: Set up bucklescript --- .bsb.lock | 1 + .gitignore | 7 +- .ocamlformat | 1 + .tmuxinator.yml | 6 +- Makefile | 51 ++++------- README.md | 6 ++ bsconfig.json | 22 +++++ content/plats/riz-vinaigre-casserole.md | 14 +-- dev | 15 ++-- package-lock.json | 150 ++++++++++++++++++++++++++++++++ package.json | 14 +++ rollup.config.js | 13 +++ shell.nix | 23 ++--- src/arrayUtils.ml | 8 ++ src/dom.ts | 54 ------------ src/domUtils.ml | 37 ++++++++ src/main.ml | 63 ++++++++++++++ src/main.ts | 71 --------------- src/number.ml | 57 ++++++++++++ src/number.ts | 66 -------------- static/main.css | 3 +- 21 files changed, 418 insertions(+), 264 deletions(-) create mode 100644 .bsb.lock create mode 100644 .ocamlformat create mode 100644 bsconfig.json create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 rollup.config.js create mode 100644 src/arrayUtils.ml delete mode 100644 src/dom.ts create mode 100644 src/domUtils.ml create mode 100644 src/main.ml delete mode 100644 src/main.ts create mode 100644 src/number.ml delete mode 100644 src/number.ts 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 = []): 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[] { - 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('.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; } -- cgit v1.2.3