├── CHANGES.md
├── src
├── jsoo_todomvc.mli
├── new_todo.mli
├── storage.mli
├── dune
├── package.json
├── footer.mli
├── todo.mli
├── storage.ml
├── index.html
├── yarn.lock
├── new_todo.ml
├── std.ml
├── footer.ml
├── todo.ml
└── jsoo_todomvc.ml
├── .gitignore
├── Makefile
├── .ocamlformat
├── README.md
├── docs
├── index.html
├── base.css
└── index.css
├── dune-project
├── jsoo_todomvc.opam
└── LICENSE
/CHANGES.md:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/jsoo_todomvc.mli:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/new_todo.mli:
--------------------------------------------------------------------------------
1 | open Std
2 |
3 | val render :
4 | dispatch:([> `Add of Todo.t] -> unit) -> [> Html_types.header] Html.elt
5 |
--------------------------------------------------------------------------------
/src/storage.mli:
--------------------------------------------------------------------------------
1 | open Std
2 |
3 | type t
4 |
5 | val create : unit -> (t, [> `Not_supported of string]) result
6 | val get : t -> Js.js_string Js.t option
7 | val put : t -> Js.js_string Js.t -> unit
8 |
--------------------------------------------------------------------------------
/src/dune:
--------------------------------------------------------------------------------
1 | (executable
2 | (name jsoo_todomvc)
3 | (modes js)
4 | (libraries uuidm js_of_ocaml js_of_ocaml-tyxml react reactiveData
5 | ocplib-json-typed ocplib-json-typed-browser)
6 | (preprocess
7 | (pps js_of_ocaml-ppx)))
8 |
--------------------------------------------------------------------------------
/src/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "src",
3 | "version": "1.0.0",
4 | "main": "index.js",
5 | "license": "MIT",
6 | "private": true,
7 | "dependencies": {
8 | "todomvc-app-css": "^2.3.0",
9 | "todomvc-common": "^1.0.5"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/footer.mli:
--------------------------------------------------------------------------------
1 | open Std
2 |
3 | type t
4 |
5 | val create : unit -> t
6 | val filter_s : t -> filter React.S.t
7 |
8 | val render :
9 | t
10 | -> totals React.S.t
11 | -> dispatch:([> `Clear_completed] -> unit)
12 | -> [> Html_types.footer] Html.elt
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.annot
2 | *.cmo
3 | *.cma
4 | *.cmi
5 | *.a
6 | *.o
7 | *.cmx
8 | *.cmxs
9 | *.cmxa
10 |
11 | # ocamlbuild working directory
12 | _build/
13 |
14 | # ocamlbuild targets
15 | *.byte
16 | *.native
17 |
18 | # oasis generated files
19 | setup.data
20 | setup.log
21 |
22 | # Merlin configuring file for Vim and Emacs
23 | .merlin
24 |
25 | # Dune generated files
26 | *.install
27 |
28 | # Local OPAM switch
29 | _opam/
30 |
31 | # node_modules
32 | node_modules/
33 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | b build:
2 | dune b
3 |
4 | p prod :
5 | dune b --profile release
6 |
7 | f fmt :
8 | dune build @fmt --auto-promote
9 |
10 | c clean :
11 | dune clean
12 |
13 | install:
14 | opam install . --deps-only --working-dir --with-test
15 |
16 | dev-switch :
17 | opam switch create . ocaml-base-compiler.4.10.0 --deps-only --with-test --working-dir
18 |
19 | open :
20 | xdg-open _build/default/src/index.html
21 |
22 | .PHONY: b build p prod f fmt c clean install dev-switch open
23 |
--------------------------------------------------------------------------------
/src/todo.mli:
--------------------------------------------------------------------------------
1 | open Std
2 |
3 | type t
4 |
5 | val create : ?complete:bool -> ?id:Uuidm.t -> string -> t
6 | val complete : t -> bool
7 | val active : t -> bool
8 | val id : t -> Uuidm.t
9 | val set_complete : t -> complete:bool -> t
10 | val json_encoding : (string * bool * string) Json_encoding.encoding
11 | val to_json_value : t -> string * bool * string
12 |
13 | val render :
14 | t
15 | -> dispatch:([> `Update of t | `Destroy of t] -> unit)
16 | -> filter_s:filter React.S.t
17 | -> [> Html_types.li] Html.elt
18 |
--------------------------------------------------------------------------------
/src/storage.ml:
--------------------------------------------------------------------------------
1 | open Std
2 | open Result.O
3 |
4 | type t = Dom_html.storage Js.t
5 |
6 | let key = Js.string "jsoo-todomvc"
7 | let get t = t##getItem key |> Js.Opt.to_option
8 | let put t s = t##setItem key s
9 |
10 | let create () =
11 | let* storage =
12 | Js.Optdef.case
13 | Dom_html.window##.localStorage
14 | (fun () ->
15 | Result.error
16 | (`Not_supported
17 | "HTML5 localStorage functionality not supported by this browser"))
18 | Result.ok
19 | in
20 | Ok storage
21 |
--------------------------------------------------------------------------------
/.ocamlformat:
--------------------------------------------------------------------------------
1 | version=0.15.0
2 | profile = compact
3 | parse-docstrings = true
4 | break-infix = fit-or-vertical
5 | break-fun-decl = fit-or-vertical
6 | align-cases = true
7 | align-constructors-decl = true
8 | align-variants-decl = true
9 | field-space = loose
10 | sequence-blank-line = preserve-one
11 | break-collection-expressions = fit-or-vertical
12 | exp-grouping = preserve
13 | wrap-fun-args = false
14 | type-decl = sparse
15 | let-and = sparse
16 | dock-collection-brackets = false
17 | doc-comments = after-when-possible
18 | break-cases = all
19 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Todo app in js_of_ocaml ⚡
2 |
3 | Todo MVC implemented in js_of_ocaml
4 |
5 | [Running Demo](https://lemaetech.github.io/jsoo_todomvc/)
6 |
7 | ```
8 | git clone git@github.com:bikallem/jsoo_todomvc.git
9 | cd jsoo_todomvc
10 | make install
11 | make build
12 | make open
13 | ```
14 |
15 | ## js_of_ocaml resources
16 |
17 | 1. https://dune.readthedocs.io/en/latest/dune-files.html#js-of-ocaml - configure `js_of_ocaml` compiler in dune
18 |
19 | 2. https://dune.readthedocs.io/en/latest/jsoo.html - getting started with `js_of_ocaml` projects with dune.
20 |
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JSOO TODO app
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JSOO TODO app
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/yarn.lock:
--------------------------------------------------------------------------------
1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2 | # yarn lockfile v1
3 |
4 |
5 | todomvc-app-css@^2.3.0:
6 | version "2.3.0"
7 | resolved "https://registry.yarnpkg.com/todomvc-app-css/-/todomvc-app-css-2.3.0.tgz#cc27f1686ab4c418eef4e790289cc124baca2546"
8 | integrity sha512-RG8hxqoVn8Be3wxyuyHfOSAXiY1Z0N+PYQOe/jxzy3wpU1Obfwd1RF1i/fz/fR+MrYL+Q+BdrUt8SsXdtR7Oow==
9 |
10 | todomvc-common@^1.0.5:
11 | version "1.0.5"
12 | resolved "https://registry.yarnpkg.com/todomvc-common/-/todomvc-common-1.0.5.tgz#8c3e799ac9f1fc1573e0c204f984510826914730"
13 | integrity sha512-D8kEJmxVMQIWwztEdH+WeiAfXRbbSCpgXq4NkYi+gduJ2tr8CNq7sYLfJvjpQ10KD9QxJwig57rvMbV2QAESwQ==
14 |
--------------------------------------------------------------------------------
/src/new_todo.ml:
--------------------------------------------------------------------------------
1 | open Std
2 | open Dom_html
3 | open Html
4 |
5 | let handle_key_down dispatch evt =
6 | let open Opt.O in
7 | if evt##.keyCode = enter_keycode then
8 | (let* target = evt##.target in
9 | let+ input = CoerceTo.input target in
10 | let todo = Js.to_string input##.value |> Todo.create in
11 | (todo, fun () -> input##.value := Js.string ""))
12 | |> fun o ->
13 | Opt.iter o (fun (todo, reset) ->
14 | `Add todo |> dispatch ;
15 | reset ())
16 | else () ;
17 | true
18 |
19 | let render ~dispatch =
20 | header
21 | ~a:[a_class ["header"]]
22 | [ h1 [txt "todos"]
23 | ; input
24 | ~a:
25 | [ a_class ["new-todo"]
26 | ; a_placeholder "What needs to be done?"
27 | ; a_autofocus ()
28 | ; a_onkeydown @@ handle_key_down dispatch ]
29 | () ]
30 |
--------------------------------------------------------------------------------
/dune-project:
--------------------------------------------------------------------------------
1 | (lang dune 2.5)
2 |
3 | (name jsoo_todomvc)
4 |
5 | (version 1.0.0)
6 |
7 | (generate_opam_files true)
8 |
9 | (source
10 | (github bikallem/jsoo_todomvc))
11 |
12 | (license MPL-2.0)
13 |
14 | (authors "Bikal Lem")
15 |
16 | (maintainers "Bikal Lem")
17 |
18 | (package
19 | (name jsoo_todomvc)
20 | (synopsis "TODO MVC app in js_of_ocaml")
21 | (description "Uses js_of_ocaml provided libraries only.")
22 | (depends
23 | (ocaml
24 | (>= 4.10.0))
25 | (dune
26 | (and
27 | :build
28 | (>= 2.5)))
29 | (uuidm
30 | (>= 0.9.7))
31 | (merlin
32 | (and
33 | :dev
34 | (>= 3.3.4)))
35 | (ocamlformat
36 | (and
37 | :dev
38 | (>= 0.14.1)))
39 | (utop
40 | (and
41 | :dev
42 | (>= 2.5.0)))
43 | (opam-lock
44 | (and
45 | :dev
46 | (>= 0.2)))
47 | (ocplib-json-typed
48 | (>= 0.7.1))
49 | (ocplib-json-typed-browser
50 | (>= 0.7.1))
51 | (js_of_ocaml
52 | (>= 3.6.0))
53 | (js_of_ocaml-ppx
54 | (>= 3.6.0))
55 | (js_of_ocaml-lwt
56 | (>= 3.6.0))
57 | (js_of_ocaml-tyxml
58 | (>= 3.6.0))))
59 |
--------------------------------------------------------------------------------
/jsoo_todomvc.opam:
--------------------------------------------------------------------------------
1 | # This file is generated by dune, edit dune-project instead
2 | opam-version: "2.0"
3 | version: "1.0.0"
4 | synopsis: "TODO MVC app in js_of_ocaml"
5 | description: "Uses js_of_ocaml provided libraries only."
6 | maintainer: ["Bikal Lem"]
7 | authors: ["Bikal Lem"]
8 | license: "MPL-2.0"
9 | homepage: "https://github.com/bikallem/jsoo_todomvc"
10 | bug-reports: "https://github.com/bikallem/jsoo_todomvc/issues"
11 | depends: [
12 | "ocaml" {>= "4.10.0"}
13 | "dune" {build & >= "2.5"}
14 | "uuidm" {>= "0.9.7"}
15 | "merlin" {dev & >= "3.3.4"}
16 | "ocamlformat" {dev & >= "0.14.1"}
17 | "utop" {dev & >= "2.5.0"}
18 | "opam-lock" {dev & >= "0.2"}
19 | "ocplib-json-typed" {>= "0.7.1"}
20 | "ocplib-json-typed-browser" {>= "0.7.1"}
21 | "js_of_ocaml" {>= "3.6.0"}
22 | "js_of_ocaml-ppx" {>= "3.6.0"}
23 | "js_of_ocaml-lwt" {>= "3.6.0"}
24 | "js_of_ocaml-tyxml" {>= "3.6.0"}
25 | ]
26 | build: [
27 | ["dune" "subst"] {pinned}
28 | [
29 | "dune"
30 | "build"
31 | "-p"
32 | name
33 | "-j"
34 | jobs
35 | "@install"
36 | "@runtest" {with-test}
37 | "@doc" {with-doc}
38 | ]
39 | ]
40 | dev-repo: "git+https://github.com/bikallem/jsoo_todomvc.git"
41 |
--------------------------------------------------------------------------------
/src/std.ml:
--------------------------------------------------------------------------------
1 | include Js_of_ocaml
2 |
3 | module Opt = struct
4 | include Js_of_ocaml.Js.Opt
5 |
6 | module O = struct
7 | let ( let* ) = bind let ( let+ ) = map let ( >>= ) = bind let ( >>| ) = map
8 | end
9 | end
10 |
11 | module Option = struct
12 | include Option
13 |
14 | let get ~default o =
15 | match o with
16 | | Some a -> a
17 | | None -> default
18 |
19 | module O = struct
20 | let ( let* ) = bind
21 | let ( let+ ) o f = map f o
22 | let ( >>= ) = ( let* )
23 | let ( >>| ) = ( let+ )
24 | end
25 | end
26 |
27 | module Result = struct
28 | include Stdlib.Result
29 |
30 | module O = struct
31 | let ( let* ) = bind
32 | let ( let+ ) o f = map f o
33 | let ( >>= ) = ( let* )
34 | let ( >>| ) = ( let+ )
35 | end
36 | end
37 |
38 | module List = struct
39 | include Stdlib.List
40 |
41 | let fold_right f b l = List.fold_right f l b
42 | end
43 |
44 | module RList = ReactiveData.RList
45 | module Html = Js_of_ocaml_tyxml.Tyxml_js.Html
46 | module Dom_html = Js_of_ocaml.Dom_html
47 | module R = Js_of_ocaml_tyxml.Tyxml_js.R
48 | module To_dom = Js_of_ocaml_tyxml.Tyxml_js.To_dom
49 | module Log = Js_of_ocaml.Firebug
50 |
51 | type totals =
52 | { total : int
53 | ; completed : int
54 | ; remaining : int }
55 |
56 | let[@inline] ( >> ) f g x = g (f x)
57 | let enter_keycode = 13
58 | let esc_keycode = 27
59 |
60 | type filter =
61 | [ `All
62 | | `Active
63 | | `Completed ]
64 |
--------------------------------------------------------------------------------
/src/footer.ml:
--------------------------------------------------------------------------------
1 | open Std
2 | open Html
3 |
4 | type t =
5 | { filter_s : filter React.S.t (** filter change signal. *)
6 | ; change_filter : filter -> unit (** change current filter. *) }
7 |
8 | let filter_s t = t.filter_s
9 |
10 | let current_filter () =
11 | let frag = Url.Current.get_fragment () in
12 | let frag = if String.equal frag "" then "/" else frag in
13 | String.split_on_char '/' frag
14 | |> List.filter (String.equal "" >> not)
15 | |> List.map String.lowercase_ascii
16 | |> function
17 | | "active" :: _ -> `Active
18 | | "completed" :: _ -> `Completed
19 | | []
20 | |_ ->
21 | `All
22 |
23 | let configure_onfilterchange change_filter =
24 | let handle_hashchange (_ : #Dom_html.hashChangeEvent Js.t) =
25 | current_filter () |> change_filter ;
26 | Js._true in
27 | Dom_html.window##.onhashchange := Dom_html.handler @@ handle_hashchange
28 |
29 | let create () =
30 | let filter_s, change_filter = React.S.create @@ current_filter () in
31 | configure_onfilterchange change_filter ;
32 | {filter_s; change_filter}
33 |
34 | let filter_link lbl url filter t =
35 | a
36 | ~a:
37 | [ a_href url
38 | ; R.filter_attrib
39 | (a_class ["selected"])
40 | (React.S.map (( = ) filter) t.filter_s) ]
41 | [txt lbl]
42 |
43 | let render t total_s ~dispatch =
44 | let items_left_txt =
45 | React.S.map
46 | (fun {remaining; _} ->
47 | Printf.sprintf
48 | "%i %s left"
49 | remaining
50 | (if remaining <= 1 then "item" else "items"))
51 | total_s in
52 | footer
53 | ~a:[a_class ["footer"]]
54 | [ span ~a:[a_class ["todo-count"]] [R.Html.txt items_left_txt]
55 | ; ul
56 | ~a:[a_class ["filters"]]
57 | [ li [filter_link "All" "#/" `All t]
58 | ; li [filter_link "Active" "#/active" `Active t]
59 | ; li [filter_link "Completed" "#/completed" `Completed t] ]
60 | ; button
61 | ~a:
62 | [ a_class ["clear-completed"]
63 | ; R.filter_attrib
64 | (a_style "display:none")
65 | (React.S.map (fun {completed; _} -> completed <= 0) total_s)
66 | ; a_onclick (fun _ ->
67 | dispatch @@ `Clear_completed ;
68 | true) ]
69 | [txt "Clear completed"] ]
70 |
--------------------------------------------------------------------------------
/docs/base.css:
--------------------------------------------------------------------------------
1 | hr {
2 | margin: 20px 0;
3 | border: 0;
4 | border-top: 1px dashed #c5c5c5;
5 | border-bottom: 1px dashed #f7f7f7;
6 | }
7 |
8 | .learn a {
9 | font-weight: normal;
10 | text-decoration: none;
11 | color: #b83f45;
12 | }
13 |
14 | .learn a:hover {
15 | text-decoration: underline;
16 | color: #787e7e;
17 | }
18 |
19 | .learn h3,
20 | .learn h4,
21 | .learn h5 {
22 | margin: 10px 0;
23 | font-weight: 500;
24 | line-height: 1.2;
25 | color: #000;
26 | }
27 |
28 | .learn h3 {
29 | font-size: 24px;
30 | }
31 |
32 | .learn h4 {
33 | font-size: 18px;
34 | }
35 |
36 | .learn h5 {
37 | margin-bottom: 0;
38 | font-size: 14px;
39 | }
40 |
41 | .learn ul {
42 | padding: 0;
43 | margin: 0 0 30px 25px;
44 | }
45 |
46 | .learn li {
47 | line-height: 20px;
48 | }
49 |
50 | .learn p {
51 | font-size: 15px;
52 | font-weight: 300;
53 | line-height: 1.3;
54 | margin-top: 0;
55 | margin-bottom: 0;
56 | }
57 |
58 | #issue-count {
59 | display: none;
60 | }
61 |
62 | .quote {
63 | border: none;
64 | margin: 20px 0 60px 0;
65 | }
66 |
67 | .quote p {
68 | font-style: italic;
69 | }
70 |
71 | .quote p:before {
72 | content: '“';
73 | font-size: 50px;
74 | opacity: .15;
75 | position: absolute;
76 | top: -20px;
77 | left: 3px;
78 | }
79 |
80 | .quote p:after {
81 | content: '”';
82 | font-size: 50px;
83 | opacity: .15;
84 | position: absolute;
85 | bottom: -42px;
86 | right: 3px;
87 | }
88 |
89 | .quote footer {
90 | position: absolute;
91 | bottom: -40px;
92 | right: 0;
93 | }
94 |
95 | .quote footer img {
96 | border-radius: 3px;
97 | }
98 |
99 | .quote footer a {
100 | margin-left: 5px;
101 | vertical-align: middle;
102 | }
103 |
104 | .speech-bubble {
105 | position: relative;
106 | padding: 10px;
107 | background: rgba(0, 0, 0, .04);
108 | border-radius: 5px;
109 | }
110 |
111 | .speech-bubble:after {
112 | content: '';
113 | position: absolute;
114 | top: 100%;
115 | right: 30px;
116 | border: 13px solid transparent;
117 | border-top-color: rgba(0, 0, 0, .04);
118 | }
119 |
120 | .learn-bar > .learn {
121 | position: absolute;
122 | width: 272px;
123 | top: 8px;
124 | left: -300px;
125 | padding: 10px;
126 | border-radius: 5px;
127 | background-color: rgba(255, 255, 255, .6);
128 | transition-property: left;
129 | transition-duration: 500ms;
130 | }
131 |
132 | @media (min-width: 899px) {
133 | .learn-bar {
134 | width: auto;
135 | padding-left: 300px;
136 | }
137 |
138 | .learn-bar > .learn {
139 | left: 8px;
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/src/todo.ml:
--------------------------------------------------------------------------------
1 | open Std
2 | open Html
3 | open Dom_html
4 |
5 | type t =
6 | { description : string
7 | ; complete : bool
8 | ; id : Uuidm.t
9 | ; editing_s : bool React.signal
10 | ; set_editing : bool -> unit
11 | ; complete_s : bool React.signal
12 | ; set_complete : bool -> unit }
13 |
14 | let create ?(complete = false) ?(id = Uuidm.create `V4) description =
15 | let editing_s, set_editing = React.S.create false in
16 | let complete_s, set_complete = React.S.create complete in
17 | {description; complete; id; editing_s; set_editing; complete_s; set_complete}
18 |
19 | let json_encoding =
20 | Json_encoding.(
21 | obj3 (req "description" string) (req "complete" bool) (req "id" string))
22 |
23 | let to_json_value t = (t.description, t.complete, Uuidm.to_string t.id)
24 | let complete t = t.complete
25 | let active t = not t.complete
26 | let id t = t.id
27 | let set_complete t ~complete = {t with complete}
28 |
29 | let handle_dblclick t todo_input _ =
30 | t.set_editing true ;
31 | let dom_inp = To_dom.of_input todo_input in
32 | dom_inp##focus ; true
33 |
34 | let todo_input t dispatch =
35 | let open Opt.O in
36 | let reset_input input_elem =
37 | input_elem
38 | >>= CoerceTo.input
39 | |> fun o ->
40 | Opt.iter o (fun input -> input##.value := Js.string t.description) in
41 | let handle_onblur evt =
42 | t.set_editing false ;
43 | reset_input evt##.target ;
44 | true in
45 | let handle_key_down evt =
46 | if evt##.keyCode = enter_keycode then
47 | ( t.set_editing false ;
48 | let* target = evt##.target in
49 | let+ input = CoerceTo.input target in
50 | Js.to_string input##.value )
51 | |> fun o ->
52 | Opt.iter o (fun description -> `Update {t with description} |> dispatch)
53 | else if evt##.keyCode = esc_keycode then (
54 | t.set_editing false ;
55 | reset_input evt##.target )
56 | else () ;
57 | true in
58 | input
59 | ~a:
60 | [ a_id (Uuidm.to_string t.id)
61 | ; a_input_type `Text
62 | ; a_class ["edit"]
63 | ; a_value t.description
64 | ; a_onblur handle_onblur
65 | ; a_onkeydown handle_key_down ]
66 | ()
67 |
68 | let render t ~dispatch ~filter_s =
69 | t.set_complete t.complete ;
70 | let li_cls_attr =
71 | let cls_attr = if complete t then ["completed"] else [] in
72 | R.Html.a_class
73 | @@ React.S.map
74 | (function
75 | | true -> "editing" :: cls_attr
76 | | false -> cls_attr)
77 | t.editing_s in
78 | let complete_toggler =
79 | let handle_onclick (_ : #Dom_html.event Js.t) =
80 | let complete = not t.complete in
81 | `Update {t with complete} |> dispatch ;
82 | t.set_complete complete ;
83 | true in
84 | input
85 | ~a:
86 | [ a_class ["toggle"]
87 | ; a_input_type `Checkbox
88 | ; a_onclick handle_onclick
89 | ; R.filter_attrib (a_checked ()) (React.S.map Fun.id t.complete_s) ]
90 | () in
91 | let todo_input = todo_input t dispatch in
92 | let handle_destroy (_ : #Dom_html.event Js.t) =
93 | dispatch (`Destroy t) ;
94 | true in
95 | li
96 | ~a:
97 | [ li_cls_attr
98 | ; R.filter_attrib
99 | (a_style "display:none")
100 | (React.S.map
101 | (function
102 | | `All -> false
103 | | `Active -> not (active t)
104 | | `Completed -> not (complete t))
105 | filter_s) ]
106 | [ div
107 | ~a:[a_class ["view"]]
108 | [ complete_toggler
109 | ; label
110 | ~a:[a_ondblclick @@ handle_dblclick t todo_input]
111 | [txt t.description]
112 | ; button ~a:[a_class ["destroy"]; a_onclick handle_destroy] [] ]
113 | ; todo_input ]
114 |
--------------------------------------------------------------------------------
/src/jsoo_todomvc.ml:
--------------------------------------------------------------------------------
1 | open Std
2 | open Html
3 |
4 | (*----------------------------------------------------------------------
5 | * Variable Naming Convention :-
6 | * _s : denotes a signal type React.S.t. Signals are dynamic and the
7 | * value it encodes change according to input.
8 | *----------------------------------------------------------------------*)
9 | module Indextbl = Hashtbl.Make (struct
10 | type t = Uuidm.t
11 |
12 | let equal = Uuidm.equal
13 | let hash (u : Uuidm.t) = Hashtbl.hash u
14 | end)
15 |
16 | type t =
17 | { rl : Todo.t RList.t (** Reactive Todo.t list store. *)
18 | ; rh : Todo.t RList.handle (** Reactive Todo.t list handle to 'rl'. *)
19 | ; total_s : totals React.S.t (** Todo list totals change signal. *)
20 | ; index_tbl : int Indextbl.t
21 | (** Tbl: key - Todo.id; value - index in 'rl'. *)
22 | ; dispatch : action -> unit (** Dispatch action. *)
23 | ; storage : Storage.t option (** Browser localStorage. *)
24 | ; mutable markall_complete : bool (** Markall todos as complete. *) }
25 |
26 | and action =
27 | [ `Add of Todo.t
28 | | `Update of Todo.t
29 | | `Destroy of Todo.t
30 | | `Clear_completed
31 | | `Toggle_all of bool ]
32 |
33 | let update_index rl index_tbl =
34 | let todos = RList.value rl in
35 | List.iteri (fun i todo -> Indextbl.replace index_tbl (Todo.id todo) i) todos
36 |
37 | let json_encoding = Json_encoding.(list Todo.json_encoding)
38 |
39 | let to_json t =
40 | Json_repr_browser.(
41 | Json_encoding.construct
42 | json_encoding
43 | (RList.value t.rl |> List.map Todo.to_json_value)
44 | |> js_stringify)
45 |
46 | let of_json s =
47 | Json_repr_browser.(
48 | parse_js_string s
49 | |> Json_encoding.destruct json_encoding
50 | |> List.fold_right
51 | (fun (description, complete, id) (todos, err_buf) ->
52 | match Uuidm.of_string id with
53 | | Some id -> (Todo.create ~complete ~id description :: todos, err_buf)
54 | | None ->
55 | Buffer.add_string err_buf
56 | @@ Printf.sprintf "Unable to decode id : %s" id ;
57 | (todos, err_buf))
58 | ([], Buffer.create 5))
59 |
60 | let update_state t action =
61 | let do_if_index_found todo f =
62 | Todo.id todo |> Indextbl.find_opt t.index_tbl |> Option.iter f in
63 | ( match action with
64 | | `Add todo ->
65 | RList.snoc todo t.rh ;
66 | update_index t.rl t.index_tbl
67 | | `Update todo ->
68 | do_if_index_found todo (fun index -> RList.update todo index t.rh)
69 | | `Destroy todo ->
70 | do_if_index_found todo (fun index -> RList.remove index t.rh) ;
71 | update_index t.rl t.index_tbl
72 | | `Clear_completed ->
73 | RList.value t.rl |> List.filter (Todo.complete >> not) |> RList.set t.rh ;
74 | update_index t.rl t.index_tbl
75 | | `Toggle_all toggle ->
76 | t.markall_complete <- toggle ;
77 | RList.value t.rl
78 | |> List.map (Todo.set_complete ~complete:toggle)
79 | |> RList.set t.rh ;
80 | update_index t.rl t.index_tbl ) ;
81 | Option.iter (fun s -> to_json t |> Storage.put s) t.storage
82 |
83 | let create todos storage =
84 | let calculate_totals rl =
85 | let todos = RList.value rl in
86 | let total = List.length todos in
87 | let remaining = todos |> List.filter (Todo.complete >> not) |> List.length in
88 | let completed = total - remaining in
89 | {total; completed; remaining} in
90 | let rl, rh = RList.create todos in
91 | let index_tbl = Indextbl.create (List.length todos) in
92 | let total_s, set_total = React.S.create @@ calculate_totals rl in
93 | let action_s, dispatch = React.E.create () in
94 | let t =
95 | {rl; rh; index_tbl; total_s; markall_complete = false; dispatch; storage}
96 | in
97 | update_index rl index_tbl ;
98 |
99 | (*---------------------------------------
100 | Attach reactive mappers/observers.
101 | ---------------------------------------*)
102 | let (_ : unit React.E.t) = React.E.map (update_state t) action_s in
103 | let (_ : unit React.event) =
104 | RList.event rl |> React.E.map (fun _ -> set_total @@ calculate_totals rl)
105 | in
106 | let (_ : unit React.signal) =
107 | React.S.map
108 | (fun {total; completed; _} -> t.markall_complete <- total = completed)
109 | t.total_s in
110 | t
111 |
112 | let main_section ({dispatch; _} as t) =
113 | let footer = Footer.create () in
114 | section
115 | ~a:
116 | [ a_class ["main"]
117 | ; R.filter_attrib
118 | (a_style "display:none")
119 | (React.S.map (fun {total; _} -> total = 0) t.total_s) ]
120 | [ input
121 | ~a:
122 | [ a_id "toggle-all"
123 | ; a_class ["toggle-all"]
124 | ; a_input_type `Checkbox
125 | ; R.filter_attrib
126 | (a_checked ())
127 | (React.S.map
128 | (fun {total; completed; _} -> total = completed)
129 | t.total_s)
130 | ; a_onclick (fun _ ->
131 | `Toggle_all (not t.markall_complete) |> dispatch ;
132 | true) ]
133 | ()
134 | ; label ~a:[a_label_for "toggle-all"] [txt "Mark all as complete"]
135 | ; R.Html.ul ~a:[a_class ["todo-list"]]
136 | @@ RList.map
137 | (Todo.render ~dispatch ~filter_s:(Footer.filter_s footer))
138 | t.rl
139 | ; Footer.render footer t.total_s ~dispatch ]
140 |
141 | let info_footer =
142 | footer
143 | ~a:[a_class ["info"]]
144 | [ p [txt "Double click to edit a todo"]
145 | ; p
146 | [ txt "Written by "
147 | ; a ~a:[a_href "http://github.com/bikallem/"] [txt "Bikal Lem"] ]
148 | ; p [txt "Part of "; a ~a:[a_href "http://todomvc.com"] [txt "TodoMVC"]] ]
149 | |> To_dom.of_footer
150 |
151 | let main default_todos (_ : #Dom_html.event Js.t) =
152 | let storage =
153 | match Storage.create () with
154 | | Error (`Not_supported msg) ->
155 | Log.console##log (Js.string msg) ;
156 | None
157 | | Ok storage -> Some storage in
158 | let todos =
159 | (let open Option.O in
160 | let* storage = storage in
161 | let+ json = Storage.get storage in
162 | let todos, err_buf = of_json json in
163 | if Buffer.length err_buf > 0 then
164 | Log.console##log (Js.string @@ Buffer.contents err_buf) ;
165 | todos)
166 | |> Option.get ~default:default_todos in
167 | let ({dispatch; _} as t) = create todos storage in
168 | let todo_app =
169 | section ~a:[a_class ["todoapp"]] [New_todo.render ~dispatch; main_section t]
170 | |> To_dom.of_section in
171 | [todo_app; info_footer]
172 | |> List.iter (fun elem ->
173 | Dom.appendChild (Dom_html.getElementById "app") elem) ;
174 | Js.bool true
175 |
176 | let () =
177 | let todos =
178 | [(true, "Buy a unicorn"); (false, "Eat haagen daz ice-cream, yummy!")]
179 | |> List.map (fun (complete, description) ->
180 | Todo.create ~complete description) in
181 | Dom_html.window##.onload := Dom_html.handler @@ main todos
182 |
--------------------------------------------------------------------------------
/docs/index.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | margin: 0;
4 | padding: 0;
5 | }
6 |
7 | button {
8 | margin: 0;
9 | padding: 0;
10 | border: 0;
11 | background: none;
12 | font-size: 100%;
13 | vertical-align: baseline;
14 | font-family: inherit;
15 | font-weight: inherit;
16 | color: inherit;
17 | -webkit-appearance: none;
18 | appearance: none;
19 | -webkit-font-smoothing: antialiased;
20 | -moz-osx-font-smoothing: grayscale;
21 | }
22 |
23 | body {
24 | font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
25 | line-height: 1.4em;
26 | background: #f5f5f5;
27 | color: #111111;
28 | min-width: 230px;
29 | max-width: 550px;
30 | margin: 0 auto;
31 | -webkit-font-smoothing: antialiased;
32 | -moz-osx-font-smoothing: grayscale;
33 | font-weight: 300;
34 | }
35 |
36 | :focus {
37 | outline: 0;
38 | }
39 |
40 | .hidden {
41 | display: none;
42 | }
43 |
44 | .todoapp {
45 | background: #fff;
46 | margin: 130px 0 40px 0;
47 | position: relative;
48 | box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2),
49 | 0 25px 50px 0 rgba(0, 0, 0, 0.1);
50 | }
51 |
52 | .todoapp input::-webkit-input-placeholder {
53 | font-style: italic;
54 | font-weight: 300;
55 | color: rgba(0, 0, 0, 0.4);
56 | }
57 |
58 | .todoapp input::-moz-placeholder {
59 | font-style: italic;
60 | font-weight: 300;
61 | color: rgba(0, 0, 0, 0.4);
62 | }
63 |
64 | .todoapp input::input-placeholder {
65 | font-style: italic;
66 | font-weight: 300;
67 | color: rgba(0, 0, 0, 0.4);
68 | }
69 |
70 | .todoapp h1 {
71 | position: absolute;
72 | top: -140px;
73 | width: 100%;
74 | font-size: 80px;
75 | font-weight: 200;
76 | text-align: center;
77 | color: #b83f45;
78 | -webkit-text-rendering: optimizeLegibility;
79 | -moz-text-rendering: optimizeLegibility;
80 | text-rendering: optimizeLegibility;
81 | }
82 |
83 | .new-todo,
84 | .edit {
85 | position: relative;
86 | margin: 0;
87 | width: 100%;
88 | font-size: 24px;
89 | font-family: inherit;
90 | font-weight: inherit;
91 | line-height: 1.4em;
92 | color: inherit;
93 | padding: 6px;
94 | border: 1px solid #999;
95 | box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
96 | box-sizing: border-box;
97 | -webkit-font-smoothing: antialiased;
98 | -moz-osx-font-smoothing: grayscale;
99 | }
100 |
101 | .new-todo {
102 | padding: 16px 16px 16px 60px;
103 | border: none;
104 | background: rgba(0, 0, 0, 0.003);
105 | box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03);
106 | }
107 |
108 | .main {
109 | position: relative;
110 | z-index: 2;
111 | border-top: 1px solid #e6e6e6;
112 | }
113 |
114 | .toggle-all {
115 | width: 1px;
116 | height: 1px;
117 | border: none; /* Mobile Safari */
118 | opacity: 0;
119 | position: absolute;
120 | right: 100%;
121 | bottom: 100%;
122 | }
123 |
124 | .toggle-all + label {
125 | width: 60px;
126 | height: 34px;
127 | font-size: 0;
128 | position: absolute;
129 | top: -52px;
130 | left: -13px;
131 | -webkit-transform: rotate(90deg);
132 | transform: rotate(90deg);
133 | }
134 |
135 | .toggle-all + label:before {
136 | content: '❯';
137 | font-size: 22px;
138 | color: #e6e6e6;
139 | padding: 10px 27px 10px 27px;
140 | }
141 |
142 | .toggle-all:checked + label:before {
143 | color: #737373;
144 | }
145 |
146 | .todo-list {
147 | margin: 0;
148 | padding: 0;
149 | list-style: none;
150 | }
151 |
152 | .todo-list li {
153 | position: relative;
154 | font-size: 24px;
155 | border-bottom: 1px solid #ededed;
156 | }
157 |
158 | .todo-list li:last-child {
159 | border-bottom: none;
160 | }
161 |
162 | .todo-list li.editing {
163 | border-bottom: none;
164 | padding: 0;
165 | }
166 |
167 | .todo-list li.editing .edit {
168 | display: block;
169 | width: calc(100% - 43px);
170 | padding: 12px 16px;
171 | margin: 0 0 0 43px;
172 | }
173 |
174 | .todo-list li.editing .view {
175 | display: none;
176 | }
177 |
178 | .todo-list li .toggle {
179 | text-align: center;
180 | width: 40px;
181 | /* auto, since non-WebKit browsers doesn't support input styling */
182 | height: auto;
183 | position: absolute;
184 | top: 0;
185 | bottom: 0;
186 | margin: auto 0;
187 | border: none; /* Mobile Safari */
188 | -webkit-appearance: none;
189 | appearance: none;
190 | }
191 |
192 | .todo-list li .toggle {
193 | opacity: 0;
194 | }
195 |
196 | .todo-list li .toggle + label {
197 | /*
198 | Firefox requires `#` to be escaped - https://bugzilla.mozilla.org/show_bug.cgi?id=922433
199 | IE and Edge requires *everything* to be escaped to render, so we do that instead of just the `#` - https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/7157459/
200 | */
201 | background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23ededed%22%20stroke-width%3D%223%22/%3E%3C/svg%3E');
202 | background-repeat: no-repeat;
203 | background-position: center left;
204 | }
205 |
206 | .todo-list li .toggle:checked + label {
207 | background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23bddad5%22%20stroke-width%3D%223%22/%3E%3Cpath%20fill%3D%22%235dc2af%22%20d%3D%22M72%2025L42%2071%2027%2056l-4%204%2020%2020%2034-52z%22/%3E%3C/svg%3E');
208 | }
209 |
210 | .todo-list li label {
211 | word-break: break-all;
212 | padding: 15px 15px 15px 60px;
213 | display: block;
214 | line-height: 1.2;
215 | transition: color 0.4s;
216 | font-weight: 400;
217 | color: #4d4d4d;
218 | }
219 |
220 | .todo-list li.completed label {
221 | color: #cdcdcd;
222 | text-decoration: line-through;
223 | }
224 |
225 | .todo-list li .destroy {
226 | display: none;
227 | position: absolute;
228 | top: 0;
229 | right: 10px;
230 | bottom: 0;
231 | width: 40px;
232 | height: 40px;
233 | margin: auto 0;
234 | font-size: 30px;
235 | color: #cc9a9a;
236 | margin-bottom: 11px;
237 | transition: color 0.2s ease-out;
238 | }
239 |
240 | .todo-list li .destroy:hover {
241 | color: #af5b5e;
242 | }
243 |
244 | .todo-list li .destroy:after {
245 | content: '×';
246 | }
247 |
248 | .todo-list li:hover .destroy {
249 | display: block;
250 | }
251 |
252 | .todo-list li .edit {
253 | display: none;
254 | }
255 |
256 | .todo-list li.editing:last-child {
257 | margin-bottom: -1px;
258 | }
259 |
260 | .footer {
261 | padding: 10px 15px;
262 | height: 20px;
263 | text-align: center;
264 | font-size: 15px;
265 | border-top: 1px solid #e6e6e6;
266 | }
267 |
268 | .footer:before {
269 | content: '';
270 | position: absolute;
271 | right: 0;
272 | bottom: 0;
273 | left: 0;
274 | height: 50px;
275 | overflow: hidden;
276 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2),
277 | 0 8px 0 -3px #f6f6f6,
278 | 0 9px 1px -3px rgba(0, 0, 0, 0.2),
279 | 0 16px 0 -6px #f6f6f6,
280 | 0 17px 2px -6px rgba(0, 0, 0, 0.2);
281 | }
282 |
283 | .todo-count {
284 | float: left;
285 | text-align: left;
286 | }
287 |
288 | .todo-count strong {
289 | font-weight: 300;
290 | }
291 |
292 | .filters {
293 | margin: 0;
294 | padding: 0;
295 | list-style: none;
296 | position: absolute;
297 | right: 0;
298 | left: 0;
299 | }
300 |
301 | .filters li {
302 | display: inline;
303 | }
304 |
305 | .filters li a {
306 | color: inherit;
307 | margin: 3px;
308 | padding: 3px 7px;
309 | text-decoration: none;
310 | border: 1px solid transparent;
311 | border-radius: 3px;
312 | }
313 |
314 | .filters li a:hover {
315 | border-color: rgba(175, 47, 47, 0.1);
316 | }
317 |
318 | .filters li a.selected {
319 | border-color: rgba(175, 47, 47, 0.2);
320 | }
321 |
322 | .clear-completed,
323 | html .clear-completed:active {
324 | float: right;
325 | position: relative;
326 | line-height: 20px;
327 | text-decoration: none;
328 | cursor: pointer;
329 | }
330 |
331 | .clear-completed:hover {
332 | text-decoration: underline;
333 | }
334 |
335 | .info {
336 | margin: 65px auto 0;
337 | color: #4d4d4d;
338 | font-size: 11px;
339 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
340 | text-align: center;
341 | }
342 |
343 | .info p {
344 | line-height: 1;
345 | }
346 |
347 | .info a {
348 | color: inherit;
349 | text-decoration: none;
350 | font-weight: 400;
351 | }
352 |
353 | .info a:hover {
354 | text-decoration: underline;
355 | }
356 |
357 | /*
358 | Hack to remove background from Mobile Safari.
359 | Can't use it globally since it destroys checkboxes in Firefox
360 | */
361 | @media screen and (-webkit-min-device-pixel-ratio:0) {
362 | .toggle-all,
363 | .todo-list li .toggle {
364 | background: none;
365 | }
366 |
367 | .todo-list li .toggle {
368 | height: 40px;
369 | }
370 | }
371 |
372 | @media (max-width: 430px) {
373 | .footer {
374 | height: 50px;
375 | }
376 |
377 | .filters {
378 | bottom: 10px;
379 | }
380 | }
381 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Mozilla Public License Version 2.0
2 | ==================================
3 |
4 | 1. Definitions
5 | --------------
6 |
7 | 1.1. "Contributor"
8 | means each individual or legal entity that creates, contributes to
9 | the creation of, or owns Covered Software.
10 |
11 | 1.2. "Contributor Version"
12 | means the combination of the Contributions of others (if any) used
13 | by a Contributor and that particular Contributor's Contribution.
14 |
15 | 1.3. "Contribution"
16 | means Covered Software of a particular Contributor.
17 |
18 | 1.4. "Covered Software"
19 | means Source Code Form to which the initial Contributor has attached
20 | the notice in Exhibit A, the Executable Form of such Source Code
21 | Form, and Modifications of such Source Code Form, in each case
22 | including portions thereof.
23 |
24 | 1.5. "Incompatible With Secondary Licenses"
25 | means
26 |
27 | (a) that the initial Contributor has attached the notice described
28 | in Exhibit B to the Covered Software; or
29 |
30 | (b) that the Covered Software was made available under the terms of
31 | version 1.1 or earlier of the License, but not also under the
32 | terms of a Secondary License.
33 |
34 | 1.6. "Executable Form"
35 | means any form of the work other than Source Code Form.
36 |
37 | 1.7. "Larger Work"
38 | means a work that combines Covered Software with other material, in
39 | a separate file or files, that is not Covered Software.
40 |
41 | 1.8. "License"
42 | means this document.
43 |
44 | 1.9. "Licensable"
45 | means having the right to grant, to the maximum extent possible,
46 | whether at the time of the initial grant or subsequently, any and
47 | all of the rights conveyed by this License.
48 |
49 | 1.10. "Modifications"
50 | means any of the following:
51 |
52 | (a) any file in Source Code Form that results from an addition to,
53 | deletion from, or modification of the contents of Covered
54 | Software; or
55 |
56 | (b) any new file in Source Code Form that contains any Covered
57 | Software.
58 |
59 | 1.11. "Patent Claims" of a Contributor
60 | means any patent claim(s), including without limitation, method,
61 | process, and apparatus claims, in any patent Licensable by such
62 | Contributor that would be infringed, but for the grant of the
63 | License, by the making, using, selling, offering for sale, having
64 | made, import, or transfer of either its Contributions or its
65 | Contributor Version.
66 |
67 | 1.12. "Secondary License"
68 | means either the GNU General Public License, Version 2.0, the GNU
69 | Lesser General Public License, Version 2.1, the GNU Affero General
70 | Public License, Version 3.0, or any later versions of those
71 | licenses.
72 |
73 | 1.13. "Source Code Form"
74 | means the form of the work preferred for making modifications.
75 |
76 | 1.14. "You" (or "Your")
77 | means an individual or a legal entity exercising rights under this
78 | License. For legal entities, "You" includes any entity that
79 | controls, is controlled by, or is under common control with You. For
80 | purposes of this definition, "control" means (a) the power, direct
81 | or indirect, to cause the direction or management of such entity,
82 | whether by contract or otherwise, or (b) ownership of more than
83 | fifty percent (50%) of the outstanding shares or beneficial
84 | ownership of such entity.
85 |
86 | 2. License Grants and Conditions
87 | --------------------------------
88 |
89 | 2.1. Grants
90 |
91 | Each Contributor hereby grants You a world-wide, royalty-free,
92 | non-exclusive license:
93 |
94 | (a) under intellectual property rights (other than patent or trademark)
95 | Licensable by such Contributor to use, reproduce, make available,
96 | modify, display, perform, distribute, and otherwise exploit its
97 | Contributions, either on an unmodified basis, with Modifications, or
98 | as part of a Larger Work; and
99 |
100 | (b) under Patent Claims of such Contributor to make, use, sell, offer
101 | for sale, have made, import, and otherwise transfer either its
102 | Contributions or its Contributor Version.
103 |
104 | 2.2. Effective Date
105 |
106 | The licenses granted in Section 2.1 with respect to any Contribution
107 | become effective for each Contribution on the date the Contributor first
108 | distributes such Contribution.
109 |
110 | 2.3. Limitations on Grant Scope
111 |
112 | The licenses granted in this Section 2 are the only rights granted under
113 | this License. No additional rights or licenses will be implied from the
114 | distribution or licensing of Covered Software under this License.
115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a
116 | Contributor:
117 |
118 | (a) for any code that a Contributor has removed from Covered Software;
119 | or
120 |
121 | (b) for infringements caused by: (i) Your and any other third party's
122 | modifications of Covered Software, or (ii) the combination of its
123 | Contributions with other software (except as part of its Contributor
124 | Version); or
125 |
126 | (c) under Patent Claims infringed by Covered Software in the absence of
127 | its Contributions.
128 |
129 | This License does not grant any rights in the trademarks, service marks,
130 | or logos of any Contributor (except as may be necessary to comply with
131 | the notice requirements in Section 3.4).
132 |
133 | 2.4. Subsequent Licenses
134 |
135 | No Contributor makes additional grants as a result of Your choice to
136 | distribute the Covered Software under a subsequent version of this
137 | License (see Section 10.2) or under the terms of a Secondary License (if
138 | permitted under the terms of Section 3.3).
139 |
140 | 2.5. Representation
141 |
142 | Each Contributor represents that the Contributor believes its
143 | Contributions are its original creation(s) or it has sufficient rights
144 | to grant the rights to its Contributions conveyed by this License.
145 |
146 | 2.6. Fair Use
147 |
148 | This License is not intended to limit any rights You have under
149 | applicable copyright doctrines of fair use, fair dealing, or other
150 | equivalents.
151 |
152 | 2.7. Conditions
153 |
154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
155 | in Section 2.1.
156 |
157 | 3. Responsibilities
158 | -------------------
159 |
160 | 3.1. Distribution of Source Form
161 |
162 | All distribution of Covered Software in Source Code Form, including any
163 | Modifications that You create or to which You contribute, must be under
164 | the terms of this License. You must inform recipients that the Source
165 | Code Form of the Covered Software is governed by the terms of this
166 | License, and how they can obtain a copy of this License. You may not
167 | attempt to alter or restrict the recipients' rights in the Source Code
168 | Form.
169 |
170 | 3.2. Distribution of Executable Form
171 |
172 | If You distribute Covered Software in Executable Form then:
173 |
174 | (a) such Covered Software must also be made available in Source Code
175 | Form, as described in Section 3.1, and You must inform recipients of
176 | the Executable Form how they can obtain a copy of such Source Code
177 | Form by reasonable means in a timely manner, at a charge no more
178 | than the cost of distribution to the recipient; and
179 |
180 | (b) You may distribute such Executable Form under the terms of this
181 | License, or sublicense it under different terms, provided that the
182 | license for the Executable Form does not attempt to limit or alter
183 | the recipients' rights in the Source Code Form under this License.
184 |
185 | 3.3. Distribution of a Larger Work
186 |
187 | You may create and distribute a Larger Work under terms of Your choice,
188 | provided that You also comply with the requirements of this License for
189 | the Covered Software. If the Larger Work is a combination of Covered
190 | Software with a work governed by one or more Secondary Licenses, and the
191 | Covered Software is not Incompatible With Secondary Licenses, this
192 | License permits You to additionally distribute such Covered Software
193 | under the terms of such Secondary License(s), so that the recipient of
194 | the Larger Work may, at their option, further distribute the Covered
195 | Software under the terms of either this License or such Secondary
196 | License(s).
197 |
198 | 3.4. Notices
199 |
200 | You may not remove or alter the substance of any license notices
201 | (including copyright notices, patent notices, disclaimers of warranty,
202 | or limitations of liability) contained within the Source Code Form of
203 | the Covered Software, except that You may alter any license notices to
204 | the extent required to remedy known factual inaccuracies.
205 |
206 | 3.5. Application of Additional Terms
207 |
208 | You may choose to offer, and to charge a fee for, warranty, support,
209 | indemnity or liability obligations to one or more recipients of Covered
210 | Software. However, You may do so only on Your own behalf, and not on
211 | behalf of any Contributor. You must make it absolutely clear that any
212 | such warranty, support, indemnity, or liability obligation is offered by
213 | You alone, and You hereby agree to indemnify every Contributor for any
214 | liability incurred by such Contributor as a result of warranty, support,
215 | indemnity or liability terms You offer. You may include additional
216 | disclaimers of warranty and limitations of liability specific to any
217 | jurisdiction.
218 |
219 | 4. Inability to Comply Due to Statute or Regulation
220 | ---------------------------------------------------
221 |
222 | If it is impossible for You to comply with any of the terms of this
223 | License with respect to some or all of the Covered Software due to
224 | statute, judicial order, or regulation then You must: (a) comply with
225 | the terms of this License to the maximum extent possible; and (b)
226 | describe the limitations and the code they affect. Such description must
227 | be placed in a text file included with all distributions of the Covered
228 | Software under this License. Except to the extent prohibited by statute
229 | or regulation, such description must be sufficiently detailed for a
230 | recipient of ordinary skill to be able to understand it.
231 |
232 | 5. Termination
233 | --------------
234 |
235 | 5.1. The rights granted under this License will terminate automatically
236 | if You fail to comply with any of its terms. However, if You become
237 | compliant, then the rights granted under this License from a particular
238 | Contributor are reinstated (a) provisionally, unless and until such
239 | Contributor explicitly and finally terminates Your grants, and (b) on an
240 | ongoing basis, if such Contributor fails to notify You of the
241 | non-compliance by some reasonable means prior to 60 days after You have
242 | come back into compliance. Moreover, Your grants from a particular
243 | Contributor are reinstated on an ongoing basis if such Contributor
244 | notifies You of the non-compliance by some reasonable means, this is the
245 | first time You have received notice of non-compliance with this License
246 | from such Contributor, and You become compliant prior to 30 days after
247 | Your receipt of the notice.
248 |
249 | 5.2. If You initiate litigation against any entity by asserting a patent
250 | infringement claim (excluding declaratory judgment actions,
251 | counter-claims, and cross-claims) alleging that a Contributor Version
252 | directly or indirectly infringes any patent, then the rights granted to
253 | You by any and all Contributors for the Covered Software under Section
254 | 2.1 of this License shall terminate.
255 |
256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all
257 | end user license agreements (excluding distributors and resellers) which
258 | have been validly granted by You or Your distributors under this License
259 | prior to termination shall survive termination.
260 |
261 | ************************************************************************
262 | * *
263 | * 6. Disclaimer of Warranty *
264 | * ------------------------- *
265 | * *
266 | * Covered Software is provided under this License on an "as is" *
267 | * basis, without warranty of any kind, either expressed, implied, or *
268 | * statutory, including, without limitation, warranties that the *
269 | * Covered Software is free of defects, merchantable, fit for a *
270 | * particular purpose or non-infringing. The entire risk as to the *
271 | * quality and performance of the Covered Software is with You. *
272 | * Should any Covered Software prove defective in any respect, You *
273 | * (not any Contributor) assume the cost of any necessary servicing, *
274 | * repair, or correction. This disclaimer of warranty constitutes an *
275 | * essential part of this License. No use of any Covered Software is *
276 | * authorized under this License except under this disclaimer. *
277 | * *
278 | ************************************************************************
279 |
280 | ************************************************************************
281 | * *
282 | * 7. Limitation of Liability *
283 | * -------------------------- *
284 | * *
285 | * Under no circumstances and under no legal theory, whether tort *
286 | * (including negligence), contract, or otherwise, shall any *
287 | * Contributor, or anyone who distributes Covered Software as *
288 | * permitted above, be liable to You for any direct, indirect, *
289 | * special, incidental, or consequential damages of any character *
290 | * including, without limitation, damages for lost profits, loss of *
291 | * goodwill, work stoppage, computer failure or malfunction, or any *
292 | * and all other commercial damages or losses, even if such party *
293 | * shall have been informed of the possibility of such damages. This *
294 | * limitation of liability shall not apply to liability for death or *
295 | * personal injury resulting from such party's negligence to the *
296 | * extent applicable law prohibits such limitation. Some *
297 | * jurisdictions do not allow the exclusion or limitation of *
298 | * incidental or consequential damages, so this exclusion and *
299 | * limitation may not apply to You. *
300 | * *
301 | ************************************************************************
302 |
303 | 8. Litigation
304 | -------------
305 |
306 | Any litigation relating to this License may be brought only in the
307 | courts of a jurisdiction where the defendant maintains its principal
308 | place of business and such litigation shall be governed by laws of that
309 | jurisdiction, without reference to its conflict-of-law provisions.
310 | Nothing in this Section shall prevent a party's ability to bring
311 | cross-claims or counter-claims.
312 |
313 | 9. Miscellaneous
314 | ----------------
315 |
316 | This License represents the complete agreement concerning the subject
317 | matter hereof. If any provision of this License is held to be
318 | unenforceable, such provision shall be reformed only to the extent
319 | necessary to make it enforceable. Any law or regulation which provides
320 | that the language of a contract shall be construed against the drafter
321 | shall not be used to construe this License against a Contributor.
322 |
323 | 10. Versions of the License
324 | ---------------------------
325 |
326 | 10.1. New Versions
327 |
328 | Mozilla Foundation is the license steward. Except as provided in Section
329 | 10.3, no one other than the license steward has the right to modify or
330 | publish new versions of this License. Each version will be given a
331 | distinguishing version number.
332 |
333 | 10.2. Effect of New Versions
334 |
335 | You may distribute the Covered Software under the terms of the version
336 | of the License under which You originally received the Covered Software,
337 | or under the terms of any subsequent version published by the license
338 | steward.
339 |
340 | 10.3. Modified Versions
341 |
342 | If you create software not governed by this License, and you want to
343 | create a new license for such software, you may create and use a
344 | modified version of this License if you rename the license and remove
345 | any references to the name of the license steward (except to note that
346 | such modified license differs from this License).
347 |
348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary
349 | Licenses
350 |
351 | If You choose to distribute Source Code Form that is Incompatible With
352 | Secondary Licenses under the terms of this version of the License, the
353 | notice described in Exhibit B of this License must be attached.
354 |
355 | Exhibit A - Source Code Form License Notice
356 | -------------------------------------------
357 |
358 | This Source Code Form is subject to the terms of the Mozilla Public
359 | License, v. 2.0. If a copy of the MPL was not distributed with this
360 | file, You can obtain one at http://mozilla.org/MPL/2.0/.
361 |
362 | If it is not possible or desirable to put the notice in a particular
363 | file, then You may include the notice in a location (such as a LICENSE
364 | file in a relevant directory) where a recipient would be likely to look
365 | for such a notice.
366 |
367 | You may add additional accurate notices of copyright ownership.
368 |
369 | Exhibit B - "Incompatible With Secondary Licenses" Notice
370 | ---------------------------------------------------------
371 |
372 | This Source Code Form is "Incompatible With Secondary Licenses", as
373 | defined by the Mozilla Public License, v. 2.0.
374 |
--------------------------------------------------------------------------------