todomvc
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -19,3 +19,4 @@ src/Revision.newt
|
|||||||
bootstrap/serializer.js
|
bootstrap/serializer.js
|
||||||
/newt-vscode-lsp/src/newt.js
|
/newt-vscode-lsp/src/newt.js
|
||||||
/playground/src/newt.js
|
/playground/src/newt.js
|
||||||
|
.vite
|
||||||
|
|||||||
@@ -1426,7 +1426,7 @@ updateRec ctx fc clauses arg ty = do
|
|||||||
getTele Nothing (VPi _ _ _ _ a b) = do
|
getTele Nothing (VPi _ _ _ _ a b) = do
|
||||||
a <- forceType ctx.env a
|
a <- forceType ctx.env a
|
||||||
getTele (Just $ RVar fc "$ru") a
|
getTele (Just $ RVar fc "$ru") a
|
||||||
getTele Nothing v = error fc "Expected a pi type, got \{show v}"
|
getTele Nothing v = error fc "Expected \{show v}, missing argument to record update."
|
||||||
getTele (Just tm) v = error (getFC tm) "Expected a record type, got \{show v}"
|
getTele (Just tm) v = error (getFC tm) "Expected a record type, got \{show v}"
|
||||||
|
|
||||||
infer : Context -> Raw -> M (Tm × Val)
|
infer : Context -> Raw -> M (Tm × Val)
|
||||||
|
|||||||
@@ -282,8 +282,6 @@ instance Eq String where
|
|||||||
instance Eq Char where
|
instance Eq Char where
|
||||||
a == b = eqChar a b
|
a == b = eqChar a b
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
ptype Array : U → U
|
ptype Array : U → U
|
||||||
pfunc listToArray : ∀ a. List a → Array a := `
|
pfunc listToArray : ∀ a. List a → Array a := `
|
||||||
(a, l) => {
|
(a, l) => {
|
||||||
|
|||||||
113
src/Web/Spruce.newt
Normal file
113
src/Web/Spruce.newt
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
module Web.Spruce
|
||||||
|
|
||||||
|
-- Spruce is not Elm
|
||||||
|
|
||||||
|
-- TODO better story for import..
|
||||||
|
-- also, we'll probably want to expose something with subscriptions / dispatch / ...
|
||||||
|
|
||||||
|
import Prelude
|
||||||
|
|
||||||
|
ptype Element
|
||||||
|
pfunc getElementById : String → Element := `(id) => document.getElementById(id)`
|
||||||
|
|
||||||
|
-- Make this align with spruce.ts
|
||||||
|
data Attr msg = SAttr String String | MAttr String msg | VAttr String (String → msg)
|
||||||
|
data VNode msg = TNode String | ENode String (List $ Attr msg) (List $ VNode msg)
|
||||||
|
|
||||||
|
text : ∀ msg. String → VNode msg
|
||||||
|
text = TNode
|
||||||
|
|
||||||
|
tag : ∀ msg. String → List (Attr msg)→ List (VNode msg) → VNode msg
|
||||||
|
tag = ENode
|
||||||
|
|
||||||
|
tag_ : ∀ msg. String → List (VNode msg)→ VNode msg
|
||||||
|
tag_ tag es = ENode tag Nil es
|
||||||
|
|
||||||
|
pfunc runApp : ∀ msg model. Element → model → (update : msg → model → model) → (view : model → VNode msg) → Unit := `
|
||||||
|
(_msg, _model, node, init, update, view) => {
|
||||||
|
function replace(parent, node, child) {
|
||||||
|
if (node) {
|
||||||
|
parent.insertBefore(child, node);
|
||||||
|
parent.removeChild(node);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
parent.appendChild(child);
|
||||||
|
}
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
// patch node, possibly returning a new node
|
||||||
|
function patch(parentNode, node, vnode) {
|
||||||
|
if (vnode.tag == 0) {
|
||||||
|
if (node && node.nodeType == 3) {
|
||||||
|
node.nodeValue = vnode.h1;
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
return replace(parentNode, node, document.createTextNode(vnode.h1));
|
||||||
|
}
|
||||||
|
let el;
|
||||||
|
if (node instanceof Element && node.tagName.toLowerCase() == vnode.h1) {
|
||||||
|
el = node;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
el = document.createElement(vnode.h1);
|
||||||
|
}
|
||||||
|
// update node here
|
||||||
|
let has = {};
|
||||||
|
for (let attrs = vnode.h2; attrs.tag == 1; attrs = attrs.h2) {
|
||||||
|
let attr = attrs.h1;
|
||||||
|
if (attr.tag == 0) {
|
||||||
|
let key = attr.h1;
|
||||||
|
has[key] = true;
|
||||||
|
if (key in el)
|
||||||
|
el[key] = attr.h2;
|
||||||
|
else
|
||||||
|
el.setAttribute(key, attr.h2);
|
||||||
|
} else {
|
||||||
|
let key = attr.h1.slice(2).toLowerCase();
|
||||||
|
has[key] = true;
|
||||||
|
let events = el.events || (el.events = {});
|
||||||
|
if (!events[key])
|
||||||
|
el.addEventListener(key, listener);
|
||||||
|
events[key] = attr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let i = 0; i < el.attributes.length;) {
|
||||||
|
let attr = el.attributes[i];
|
||||||
|
if (!has[attr.name]) {
|
||||||
|
el.removeAttribute(attr.name);
|
||||||
|
} else {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (el.events) {
|
||||||
|
for (let key of Object.keys(el.events)) {
|
||||||
|
if (!has[key]) delete el.events[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let i = 0;
|
||||||
|
for (let kids = vnode.h3; kids.tag == 1; kids = kids.h2) {
|
||||||
|
patch(el, el.childNodes[i++], kids.h1);
|
||||||
|
}
|
||||||
|
while (el.childNodes[i]) el.removeChild(el.childNodes[i]);
|
||||||
|
return node == el ? node : replace(parentNode, node, el);
|
||||||
|
}
|
||||||
|
let model = init;
|
||||||
|
let vdom = view(model);
|
||||||
|
let listener = (ev) => {
|
||||||
|
console.log('listener', ev, ev.target);
|
||||||
|
let target = ev.target;
|
||||||
|
for (;;) {
|
||||||
|
if (!target) return;
|
||||||
|
if (target.events?.[ev.type]) break;
|
||||||
|
target = target.parentElement;
|
||||||
|
}
|
||||||
|
const attr = target.events[ev.type];
|
||||||
|
let action = attr.tag == 2 ? attr.h2(ev.target.value) : attr.h2;
|
||||||
|
model = update(action)(model);
|
||||||
|
vdom = view(model);
|
||||||
|
node = patch(node.parentNode, node, vdom);
|
||||||
|
};
|
||||||
|
node = patch(node.parentNode, node, vdom);
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
`
|
||||||
2
todomvc/.gitignore
vendored
Normal file
2
todomvc/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
public/app.js
|
||||||
|
public/dist
|
||||||
23
todomvc/README.md
Normal file
23
todomvc/README.md
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# Newt "Spruce" framework • [TodoMVC](http://todomvc.com)
|
||||||
|
|
||||||
|
## Implementation
|
||||||
|
|
||||||
|
An implementation of TodoMVC, using the official CSS and DOM structure. I threw together an Elm-like framework that I'm calling "spruce" for now. It's lacking features, but works.
|
||||||
|
|
||||||
|
I don't have a good story for javascript imports yet, so I'm embedding the DOM patcher in [Spruce.newt](../src/Web/Spruce.newt).
|
||||||
|
|
||||||
|
## Dev
|
||||||
|
|
||||||
|
If you background vite, you can rerun the newt command and reload the browser. Hot reload is not implemented.
|
||||||
|
|
||||||
|
```
|
||||||
|
newt src/Todo.newt -o public/app.js
|
||||||
|
vite public
|
||||||
|
```
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
```
|
||||||
|
newt src/Todo.newt -o public/app.js
|
||||||
|
vite build --minify public
|
||||||
|
```
|
||||||
397
todomvc/css-license.txt
Normal file
397
todomvc/css-license.txt
Normal file
@@ -0,0 +1,397 @@
|
|||||||
|
Creative Commons Attribution 4.0 International (CC-BY-4.0)
|
||||||
|
|
||||||
|
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
|
||||||
|
|
||||||
|
=======================================================================
|
||||||
|
|
||||||
|
Creative Commons Corporation ("Creative Commons") is not a law firm and
|
||||||
|
does not provide legal services or legal advice. Distribution of
|
||||||
|
Creative Commons public licenses does not create a lawyer-client or
|
||||||
|
other relationship. Creative Commons makes its licenses and related
|
||||||
|
information available on an "as-is" basis. Creative Commons gives no
|
||||||
|
warranties regarding its licenses, any material licensed under their
|
||||||
|
terms and conditions, or any related information. Creative Commons
|
||||||
|
disclaims all liability for damages resulting from their use to the
|
||||||
|
fullest extent possible.
|
||||||
|
|
||||||
|
Using Creative Commons Public Licenses
|
||||||
|
|
||||||
|
Creative Commons public licenses provide a standard set of terms and
|
||||||
|
conditions that creators and other rights holders may use to share
|
||||||
|
original works of authorship and other material subject to copyright
|
||||||
|
and certain other rights specified in the public license below. The
|
||||||
|
following considerations are for informational purposes only, are not
|
||||||
|
exhaustive, and do not form part of our licenses.
|
||||||
|
|
||||||
|
Considerations for licensors: Our public licenses are
|
||||||
|
intended for use by those authorized to give the public
|
||||||
|
permission to use material in ways otherwise restricted by
|
||||||
|
copyright and certain other rights. Our licenses are
|
||||||
|
irrevocable. Licensors should read and understand the terms
|
||||||
|
and conditions of the license they choose before applying it.
|
||||||
|
Licensors should also secure all rights necessary before
|
||||||
|
applying our licenses so that the public can reuse the
|
||||||
|
material as expected. Licensors should clearly mark any
|
||||||
|
material not subject to the license. This includes other CC-
|
||||||
|
licensed material, or material used under an exception or
|
||||||
|
limitation to copyright. More considerations for licensors:
|
||||||
|
wiki.creativecommons.org/Considerations_for_licensors
|
||||||
|
|
||||||
|
Considerations for the public: By using one of our public
|
||||||
|
licenses, a licensor grants the public permission to use the
|
||||||
|
licensed material under specified terms and conditions. If
|
||||||
|
the licensor's permission is not necessary for any reason--for
|
||||||
|
example, because of any applicable exception or limitation to
|
||||||
|
copyright--then that use is not regulated by the license. Our
|
||||||
|
licenses grant only permissions under copyright and certain
|
||||||
|
other rights that a licensor has authority to grant. Use of
|
||||||
|
the licensed material may still be restricted for other
|
||||||
|
reasons, including because others have copyright or other
|
||||||
|
rights in the material. A licensor may make special requests,
|
||||||
|
such as asking that all changes be marked or described.
|
||||||
|
Although not required by our licenses, you are encouraged to
|
||||||
|
respect those requests where reasonable. More considerations
|
||||||
|
for the public:
|
||||||
|
wiki.creativecommons.org/Considerations_for_licensees
|
||||||
|
|
||||||
|
=======================================================================
|
||||||
|
|
||||||
|
Creative Commons Attribution 4.0 International Public License
|
||||||
|
|
||||||
|
By exercising the Licensed Rights (defined below), You accept and agree
|
||||||
|
to be bound by the terms and conditions of this Creative Commons
|
||||||
|
Attribution 4.0 International Public License ("Public License"). To the
|
||||||
|
extent this Public License may be interpreted as a contract, You are
|
||||||
|
granted the Licensed Rights in consideration of Your acceptance of
|
||||||
|
these terms and conditions, and the Licensor grants You such rights in
|
||||||
|
consideration of benefits the Licensor receives from making the
|
||||||
|
Licensed Material available under these terms and conditions.
|
||||||
|
|
||||||
|
|
||||||
|
Section 1 -- Definitions.
|
||||||
|
|
||||||
|
a. Adapted Material means material subject to Copyright and Similar
|
||||||
|
Rights that is derived from or based upon the Licensed Material
|
||||||
|
and in which the Licensed Material is translated, altered,
|
||||||
|
arranged, transformed, or otherwise modified in a manner requiring
|
||||||
|
permission under the Copyright and Similar Rights held by the
|
||||||
|
Licensor. For purposes of this Public License, where the Licensed
|
||||||
|
Material is a musical work, performance, or sound recording,
|
||||||
|
Adapted Material is always produced where the Licensed Material is
|
||||||
|
synched in timed relation with a moving image.
|
||||||
|
|
||||||
|
b. Adapter's License means the license You apply to Your Copyright
|
||||||
|
and Similar Rights in Your contributions to Adapted Material in
|
||||||
|
accordance with the terms and conditions of this Public License.
|
||||||
|
|
||||||
|
c. Copyright and Similar Rights means copyright and/or similar rights
|
||||||
|
closely related to copyright including, without limitation,
|
||||||
|
performance, broadcast, sound recording, and Sui Generis Database
|
||||||
|
Rights, without regard to how the rights are labeled or
|
||||||
|
categorized. For purposes of this Public License, the rights
|
||||||
|
specified in Section 2(b)(1)-(2) are not Copyright and Similar
|
||||||
|
Rights.
|
||||||
|
|
||||||
|
d. Effective Technological Measures means those measures that, in the
|
||||||
|
absence of proper authority, may not be circumvented under laws
|
||||||
|
fulfilling obligations under Article 11 of the WIPO Copyright
|
||||||
|
Treaty adopted on December 20, 1996, and/or similar international
|
||||||
|
agreements.
|
||||||
|
|
||||||
|
e. Exceptions and Limitations means fair use, fair dealing, and/or
|
||||||
|
any other exception or limitation to Copyright and Similar Rights
|
||||||
|
that applies to Your use of the Licensed Material.
|
||||||
|
|
||||||
|
f. Licensed Material means the artistic or literary work, database,
|
||||||
|
or other material to which the Licensor applied this Public
|
||||||
|
License.
|
||||||
|
|
||||||
|
g. Licensed Rights means the rights granted to You subject to the
|
||||||
|
terms and conditions of this Public License, which are limited to
|
||||||
|
all Copyright and Similar Rights that apply to Your use of the
|
||||||
|
Licensed Material and that the Licensor has authority to license.
|
||||||
|
|
||||||
|
h. Licensor means the individual(s) or entity(ies) granting rights
|
||||||
|
under this Public License.
|
||||||
|
|
||||||
|
i. Share means to provide material to the public by any means or
|
||||||
|
process that requires permission under the Licensed Rights, such
|
||||||
|
as reproduction, public display, public performance, distribution,
|
||||||
|
dissemination, communication, or importation, and to make material
|
||||||
|
available to the public including in ways that members of the
|
||||||
|
public may access the material from a place and at a time
|
||||||
|
individually chosen by them.
|
||||||
|
|
||||||
|
j. Sui Generis Database Rights means rights other than copyright
|
||||||
|
resulting from Directive 96/9/EC of the European Parliament and of
|
||||||
|
the Council of 11 March 1996 on the legal protection of databases,
|
||||||
|
as amended and/or succeeded, as well as other essentially
|
||||||
|
equivalent rights anywhere in the world.
|
||||||
|
|
||||||
|
k. You means the individual or entity exercising the Licensed Rights
|
||||||
|
under this Public License. Your has a corresponding meaning.
|
||||||
|
|
||||||
|
|
||||||
|
Section 2 -- Scope.
|
||||||
|
|
||||||
|
a. License grant.
|
||||||
|
|
||||||
|
1. Subject to the terms and conditions of this Public License,
|
||||||
|
the Licensor hereby grants You a worldwide, royalty-free,
|
||||||
|
non-sublicensable, non-exclusive, irrevocable license to
|
||||||
|
exercise the Licensed Rights in the Licensed Material to:
|
||||||
|
|
||||||
|
a. reproduce and Share the Licensed Material, in whole or
|
||||||
|
in part; and
|
||||||
|
|
||||||
|
b. produce, reproduce, and Share Adapted Material.
|
||||||
|
|
||||||
|
2. Exceptions and Limitations. For the avoidance of doubt, where
|
||||||
|
Exceptions and Limitations apply to Your use, this Public
|
||||||
|
License does not apply, and You do not need to comply with
|
||||||
|
its terms and conditions.
|
||||||
|
|
||||||
|
3. Term. The term of this Public License is specified in Section
|
||||||
|
6(a).
|
||||||
|
|
||||||
|
4. Media and formats; technical modifications allowed. The
|
||||||
|
Licensor authorizes You to exercise the Licensed Rights in
|
||||||
|
all media and formats whether now known or hereafter created,
|
||||||
|
and to make technical modifications necessary to do so. The
|
||||||
|
Licensor waives and/or agrees not to assert any right or
|
||||||
|
authority to forbid You from making technical modifications
|
||||||
|
necessary to exercise the Licensed Rights, including
|
||||||
|
technical modifications necessary to circumvent Effective
|
||||||
|
Technological Measures. For purposes of this Public License,
|
||||||
|
simply making modifications authorized by this Section 2(a)
|
||||||
|
(4) never produces Adapted Material.
|
||||||
|
|
||||||
|
5. Downstream recipients.
|
||||||
|
|
||||||
|
a. Offer from the Licensor -- Licensed Material. Every
|
||||||
|
recipient of the Licensed Material automatically
|
||||||
|
receives an offer from the Licensor to exercise the
|
||||||
|
Licensed Rights under the terms and conditions of this
|
||||||
|
Public License.
|
||||||
|
|
||||||
|
b. No downstream restrictions. You may not offer or impose
|
||||||
|
any additional or different terms or conditions on, or
|
||||||
|
apply any Effective Technological Measures to, the
|
||||||
|
Licensed Material if doing so restricts exercise of the
|
||||||
|
Licensed Rights by any recipient of the Licensed
|
||||||
|
Material.
|
||||||
|
|
||||||
|
6. No endorsement. Nothing in this Public License constitutes or
|
||||||
|
may be construed as permission to assert or imply that You
|
||||||
|
are, or that Your use of the Licensed Material is, connected
|
||||||
|
with, or sponsored, endorsed, or granted official status by,
|
||||||
|
the Licensor or others designated to receive attribution as
|
||||||
|
provided in Section 3(a)(1)(A)(i).
|
||||||
|
|
||||||
|
b. Other rights.
|
||||||
|
|
||||||
|
1. Moral rights, such as the right of integrity, are not
|
||||||
|
licensed under this Public License, nor are publicity,
|
||||||
|
privacy, and/or other similar personality rights; however, to
|
||||||
|
the extent possible, the Licensor waives and/or agrees not to
|
||||||
|
assert any such rights held by the Licensor to the limited
|
||||||
|
extent necessary to allow You to exercise the Licensed
|
||||||
|
Rights, but not otherwise.
|
||||||
|
|
||||||
|
2. Patent and trademark rights are not licensed under this
|
||||||
|
Public License.
|
||||||
|
|
||||||
|
3. To the extent possible, the Licensor waives any right to
|
||||||
|
collect royalties from You for the exercise of the Licensed
|
||||||
|
Rights, whether directly or through a collecting society
|
||||||
|
under any voluntary or waivable statutory or compulsory
|
||||||
|
licensing scheme. In all other cases the Licensor expressly
|
||||||
|
reserves any right to collect such royalties.
|
||||||
|
|
||||||
|
|
||||||
|
Section 3 -- License Conditions.
|
||||||
|
|
||||||
|
Your exercise of the Licensed Rights is expressly made subject to the
|
||||||
|
following conditions.
|
||||||
|
|
||||||
|
a. Attribution.
|
||||||
|
|
||||||
|
1. If You Share the Licensed Material (including in modified
|
||||||
|
form), You must:
|
||||||
|
|
||||||
|
a. retain the following if it is supplied by the Licensor
|
||||||
|
with the Licensed Material:
|
||||||
|
|
||||||
|
i. identification of the creator(s) of the Licensed
|
||||||
|
Material and any others designated to receive
|
||||||
|
attribution, in any reasonable manner requested by
|
||||||
|
the Licensor (including by pseudonym if
|
||||||
|
designated);
|
||||||
|
|
||||||
|
ii. a copyright notice;
|
||||||
|
|
||||||
|
iii. a notice that refers to this Public License;
|
||||||
|
|
||||||
|
iv. a notice that refers to the disclaimer of
|
||||||
|
warranties;
|
||||||
|
|
||||||
|
v. a URI or hyperlink to the Licensed Material to the
|
||||||
|
extent reasonably practicable;
|
||||||
|
|
||||||
|
b. indicate if You modified the Licensed Material and
|
||||||
|
retain an indication of any previous modifications; and
|
||||||
|
|
||||||
|
c. indicate the Licensed Material is licensed under this
|
||||||
|
Public License, and include the text of, or the URI or
|
||||||
|
hyperlink to, this Public License.
|
||||||
|
|
||||||
|
2. You may satisfy the conditions in Section 3(a)(1) in any
|
||||||
|
reasonable manner based on the medium, means, and context in
|
||||||
|
which You Share the Licensed Material. For example, it may be
|
||||||
|
reasonable to satisfy the conditions by providing a URI or
|
||||||
|
hyperlink to a resource that includes the required
|
||||||
|
information.
|
||||||
|
|
||||||
|
3. If requested by the Licensor, You must remove any of the
|
||||||
|
information required by Section 3(a)(1)(A) to the extent
|
||||||
|
reasonably practicable.
|
||||||
|
|
||||||
|
4. If You Share Adapted Material You produce, the Adapter's
|
||||||
|
License You apply must not prevent recipients of the Adapted
|
||||||
|
Material from complying with this Public License.
|
||||||
|
|
||||||
|
|
||||||
|
Section 4 -- Sui Generis Database Rights.
|
||||||
|
|
||||||
|
Where the Licensed Rights include Sui Generis Database Rights that
|
||||||
|
apply to Your use of the Licensed Material:
|
||||||
|
|
||||||
|
a. for the avoidance of doubt, Section 2(a)(1) grants You the right
|
||||||
|
to extract, reuse, reproduce, and Share all or a substantial
|
||||||
|
portion of the contents of the database;
|
||||||
|
|
||||||
|
b. if You include all or a substantial portion of the database
|
||||||
|
contents in a database in which You have Sui Generis Database
|
||||||
|
Rights, then the database in which You have Sui Generis Database
|
||||||
|
Rights (but not its individual contents) is Adapted Material; and
|
||||||
|
|
||||||
|
c. You must comply with the conditions in Section 3(a) if You Share
|
||||||
|
all or a substantial portion of the contents of the database.
|
||||||
|
|
||||||
|
For the avoidance of doubt, this Section 4 supplements and does not
|
||||||
|
replace Your obligations under this Public License where the Licensed
|
||||||
|
Rights include other Copyright and Similar Rights.
|
||||||
|
|
||||||
|
|
||||||
|
Section 5 -- Disclaimer of Warranties and Limitation of Liability.
|
||||||
|
|
||||||
|
a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
|
||||||
|
EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
|
||||||
|
AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
|
||||||
|
ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
|
||||||
|
IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
|
||||||
|
WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
|
||||||
|
ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
|
||||||
|
KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
|
||||||
|
ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
|
||||||
|
|
||||||
|
b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
|
||||||
|
TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
|
||||||
|
NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
|
||||||
|
INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
|
||||||
|
COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
|
||||||
|
USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
|
||||||
|
ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
|
||||||
|
DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
|
||||||
|
IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
|
||||||
|
|
||||||
|
c. The disclaimer of warranties and limitation of liability provided
|
||||||
|
above shall be interpreted in a manner that, to the extent
|
||||||
|
possible, most closely approximates an absolute disclaimer and
|
||||||
|
waiver of all liability.
|
||||||
|
|
||||||
|
|
||||||
|
Section 6 -- Term and Termination.
|
||||||
|
|
||||||
|
a. This Public License applies for the term of the Copyright and
|
||||||
|
Similar Rights licensed here. However, if You fail to comply with
|
||||||
|
this Public License, then Your rights under this Public License
|
||||||
|
terminate automatically.
|
||||||
|
|
||||||
|
b. Where Your right to use the Licensed Material has terminated under
|
||||||
|
Section 6(a), it reinstates:
|
||||||
|
|
||||||
|
1. automatically as of the date the violation is cured, provided
|
||||||
|
it is cured within 30 days of Your discovery of the
|
||||||
|
violation; or
|
||||||
|
|
||||||
|
2. upon express reinstatement by the Licensor.
|
||||||
|
|
||||||
|
For the avoidance of doubt, this Section 6(b) does not affect any
|
||||||
|
right the Licensor may have to seek remedies for Your violations
|
||||||
|
of this Public License.
|
||||||
|
|
||||||
|
c. For the avoidance of doubt, the Licensor may also offer the
|
||||||
|
Licensed Material under separate terms or conditions or stop
|
||||||
|
distributing the Licensed Material at any time; however, doing so
|
||||||
|
will not terminate this Public License.
|
||||||
|
|
||||||
|
d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
|
||||||
|
License.
|
||||||
|
|
||||||
|
|
||||||
|
Section 7 -- Other Terms and Conditions.
|
||||||
|
|
||||||
|
a. The Licensor shall not be bound by any additional or different
|
||||||
|
terms or conditions communicated by You unless expressly agreed.
|
||||||
|
|
||||||
|
b. Any arrangements, understandings, or agreements regarding the
|
||||||
|
Licensed Material not stated herein are separate from and
|
||||||
|
independent of the terms and conditions of this Public License.
|
||||||
|
|
||||||
|
|
||||||
|
Section 8 -- Interpretation.
|
||||||
|
|
||||||
|
a. For the avoidance of doubt, this Public License does not, and
|
||||||
|
shall not be interpreted to, reduce, limit, restrict, or impose
|
||||||
|
conditions on any use of the Licensed Material that could lawfully
|
||||||
|
be made without permission under this Public License.
|
||||||
|
|
||||||
|
b. To the extent possible, if any provision of this Public License is
|
||||||
|
deemed unenforceable, it shall be automatically reformed to the
|
||||||
|
minimum extent necessary to make it enforceable. If the provision
|
||||||
|
cannot be reformed, it shall be severed from this Public License
|
||||||
|
without affecting the enforceability of the remaining terms and
|
||||||
|
conditions.
|
||||||
|
|
||||||
|
c. No term or condition of this Public License will be waived and no
|
||||||
|
failure to comply consented to unless expressly agreed to by the
|
||||||
|
Licensor.
|
||||||
|
|
||||||
|
d. Nothing in this Public License constitutes or may be interpreted
|
||||||
|
as a limitation upon, or waiver of, any privileges and immunities
|
||||||
|
that apply to the Licensor or You, including from the legal
|
||||||
|
processes of any jurisdiction or authority.
|
||||||
|
|
||||||
|
|
||||||
|
=======================================================================
|
||||||
|
|
||||||
|
Creative Commons is not a party to its public
|
||||||
|
licenses. Notwithstanding, Creative Commons may elect to apply one of
|
||||||
|
its public licenses to material it publishes and in those instances
|
||||||
|
will be considered the “Licensor.” The text of the Creative Commons
|
||||||
|
public licenses is dedicated to the public domain under the CC0 Public
|
||||||
|
Domain Dedication. Except for the limited purpose of indicating that
|
||||||
|
material is shared under a Creative Commons public license or as
|
||||||
|
otherwise permitted by the Creative Commons policies published at
|
||||||
|
creativecommons.org/policies, Creative Commons does not authorize the
|
||||||
|
use of the trademark "Creative Commons" or any other trademark or logo
|
||||||
|
of Creative Commons without its prior written consent including,
|
||||||
|
without limitation, in connection with any unauthorized modifications
|
||||||
|
to any of its public licenses or any other arrangements,
|
||||||
|
understandings, or agreements concerning use of licensed material. For
|
||||||
|
the avoidance of doubt, this paragraph does not form part of the
|
||||||
|
public licenses.
|
||||||
|
|
||||||
|
Creative Commons may be contacted at creativecommons.org.
|
||||||
13
todomvc/public/index.html
Normal file
13
todomvc/public/index.html
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Newt • TodoMVC</title>
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="main"></div>
|
||||||
|
<script src="app.js" type=module></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
393
todomvc/public/style.css
Normal file
393
todomvc/public/style.css
Normal file
@@ -0,0 +1,393 @@
|
|||||||
|
@charset 'utf-8';
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
background: none;
|
||||||
|
font-size: 100%;
|
||||||
|
vertical-align: baseline;
|
||||||
|
font-family: inherit;
|
||||||
|
font-weight: inherit;
|
||||||
|
color: inherit;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||||
|
line-height: 1.4em;
|
||||||
|
background: #f5f5f5;
|
||||||
|
color: #111111;
|
||||||
|
min-width: 230px;
|
||||||
|
max-width: 550px;
|
||||||
|
margin: 0 auto;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
font-weight: 300;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todoapp {
|
||||||
|
background: #fff;
|
||||||
|
margin: 130px 0 40px 0;
|
||||||
|
position: relative;
|
||||||
|
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2),
|
||||||
|
0 25px 50px 0 rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.todoapp input::-webkit-input-placeholder {
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 400;
|
||||||
|
color: rgba(0, 0, 0, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.todoapp input::-moz-placeholder {
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 400;
|
||||||
|
color: rgba(0, 0, 0, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.todoapp input::input-placeholder {
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 400;
|
||||||
|
color: rgba(0, 0, 0, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.todoapp h1 {
|
||||||
|
position: absolute;
|
||||||
|
top: -140px;
|
||||||
|
width: 100%;
|
||||||
|
font-size: 80px;
|
||||||
|
font-weight: 200;
|
||||||
|
text-align: center;
|
||||||
|
color: #b83f45;
|
||||||
|
-webkit-text-rendering: optimizeLegibility;
|
||||||
|
-moz-text-rendering: optimizeLegibility;
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
}
|
||||||
|
|
||||||
|
.new-todo,
|
||||||
|
.edit {
|
||||||
|
position: relative;
|
||||||
|
margin: 0;
|
||||||
|
width: 100%;
|
||||||
|
font-size: 24px;
|
||||||
|
font-family: inherit;
|
||||||
|
font-weight: inherit;
|
||||||
|
line-height: 1.4em;
|
||||||
|
color: inherit;
|
||||||
|
padding: 6px;
|
||||||
|
border: 1px solid #999;
|
||||||
|
box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
|
||||||
|
box-sizing: border-box;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
.new-todo {
|
||||||
|
padding: 16px 16px 16px 60px;
|
||||||
|
height: 65px;
|
||||||
|
border: none;
|
||||||
|
background: rgba(0, 0, 0, 0.003);
|
||||||
|
box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03);
|
||||||
|
}
|
||||||
|
|
||||||
|
.main {
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
border-top: 1px solid #e6e6e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-all {
|
||||||
|
width: 1px;
|
||||||
|
height: 1px;
|
||||||
|
border: none; /* Mobile Safari */
|
||||||
|
opacity: 0;
|
||||||
|
position: absolute;
|
||||||
|
right: 100%;
|
||||||
|
bottom: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-all + label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 45px;
|
||||||
|
height: 65px;
|
||||||
|
font-size: 0;
|
||||||
|
position: absolute;
|
||||||
|
top: -65px;
|
||||||
|
left: -0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-all + label:before {
|
||||||
|
content: '❯';
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 22px;
|
||||||
|
color: #949494;
|
||||||
|
padding: 10px 27px 10px 27px;
|
||||||
|
-webkit-transform: rotate(90deg);
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-all:checked + label:before {
|
||||||
|
color: #484848;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-list {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-list li {
|
||||||
|
position: relative;
|
||||||
|
font-size: 24px;
|
||||||
|
border-bottom: 1px solid #ededed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-list li:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-list li.editing {
|
||||||
|
border-bottom: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-list li.editing .edit {
|
||||||
|
display: block;
|
||||||
|
width: calc(100% - 43px);
|
||||||
|
padding: 12px 16px;
|
||||||
|
margin: 0 0 0 43px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-list li.editing .view {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-list li .toggle {
|
||||||
|
text-align: center;
|
||||||
|
width: 40px;
|
||||||
|
/* auto, since non-WebKit browsers doesn't support input styling */
|
||||||
|
height: auto;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
margin: auto 0;
|
||||||
|
border: none; /* Mobile Safari */
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-list li .toggle {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-list li .toggle + label {
|
||||||
|
/*
|
||||||
|
Firefox requires `#` to be escaped - https://bugzilla.mozilla.org/show_bug.cgi?id=922433
|
||||||
|
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/
|
||||||
|
*/
|
||||||
|
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%23949494%22%20stroke-width%3D%223%22/%3E%3C/svg%3E');
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-list li .toggle:checked + label {
|
||||||
|
background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%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%2359A193%22%20stroke-width%3D%223%22%2F%3E%3Cpath%20fill%3D%22%233EA390%22%20d%3D%22M72%2025L42%2071%2027%2056l-4%204%2020%2020%2034-52z%22%2F%3E%3C%2Fsvg%3E');
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-list li label {
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
padding: 15px 15px 15px 60px;
|
||||||
|
display: block;
|
||||||
|
line-height: 1.2;
|
||||||
|
transition: color 0.4s;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #484848;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-list li.completed label {
|
||||||
|
color: #949494;
|
||||||
|
text-decoration: line-through;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-list li .destroy {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 10px;
|
||||||
|
bottom: 0;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
margin: auto 0;
|
||||||
|
font-size: 30px;
|
||||||
|
color: #949494;
|
||||||
|
transition: color 0.2s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-list li .destroy:hover,
|
||||||
|
.todo-list li .destroy:focus {
|
||||||
|
color: #C18585;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-list li .destroy:after {
|
||||||
|
content: '×';
|
||||||
|
display: block;
|
||||||
|
height: 100%;
|
||||||
|
line-height: 1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-list li:hover .destroy {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-list li .edit {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-list li.editing:last-child {
|
||||||
|
margin-bottom: -1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
padding: 10px 15px;
|
||||||
|
height: 20px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 15px;
|
||||||
|
border-top: 1px solid #e6e6e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer:before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 50px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2),
|
||||||
|
0 8px 0 -3px #f6f6f6,
|
||||||
|
0 9px 1px -3px rgba(0, 0, 0, 0.2),
|
||||||
|
0 16px 0 -6px #f6f6f6,
|
||||||
|
0 17px 2px -6px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-count {
|
||||||
|
float: left;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-count strong {
|
||||||
|
font-weight: 300;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filters {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filters li {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filters li a {
|
||||||
|
color: inherit;
|
||||||
|
margin: 3px;
|
||||||
|
padding: 3px 7px;
|
||||||
|
text-decoration: none;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filters li a:hover {
|
||||||
|
border-color: #DB7676;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filters li a.selected {
|
||||||
|
border-color: #CE4646;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clear-completed,
|
||||||
|
html .clear-completed:active {
|
||||||
|
float: right;
|
||||||
|
position: relative;
|
||||||
|
line-height: 19px;
|
||||||
|
text-decoration: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clear-completed:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
margin: 65px auto 0;
|
||||||
|
color: #4d4d4d;
|
||||||
|
font-size: 11px;
|
||||||
|
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info p {
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info a {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Hack to remove background from Mobile Safari.
|
||||||
|
Can't use it globally since it destroys checkboxes in Firefox
|
||||||
|
*/
|
||||||
|
@media screen and (-webkit-min-device-pixel-ratio:0) {
|
||||||
|
.toggle-all,
|
||||||
|
.todo-list li .toggle {
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-list li .toggle {
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 430px) {
|
||||||
|
.footer {
|
||||||
|
height: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filters {
|
||||||
|
bottom: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:focus,
|
||||||
|
.toggle:focus + label,
|
||||||
|
.toggle-all:focus + label {
|
||||||
|
box-shadow: 0 0 2px 2px #CF7D7D;
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
190
todomvc/src/Todo.newt
Normal file
190
todomvc/src/Todo.newt
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
module Todo
|
||||||
|
|
||||||
|
import Prelude
|
||||||
|
import Web.Spruce
|
||||||
|
|
||||||
|
data FilterState = All | Active | Completed
|
||||||
|
derive Eq FilterState
|
||||||
|
data Msg
|
||||||
|
= Toggle Nat
|
||||||
|
| Remove Nat
|
||||||
|
-- TODO behavior varies by implementation, but probably should do return / esc / blur
|
||||||
|
| StartEdit Nat
|
||||||
|
| EndEdit Nat String
|
||||||
|
| Change String
|
||||||
|
| Filter FilterState
|
||||||
|
| Clear
|
||||||
|
| ToggleAll
|
||||||
|
|
||||||
|
record Item where
|
||||||
|
checked : Bool
|
||||||
|
text : String
|
||||||
|
|
||||||
|
data EditState = NoEdit | Edit Nat
|
||||||
|
|
||||||
|
record Model where
|
||||||
|
items : List Item
|
||||||
|
newText : String
|
||||||
|
filter : FilterState
|
||||||
|
edit : EditState
|
||||||
|
|
||||||
|
ElCon : U
|
||||||
|
ElCon = List (Attr Msg) → List (VNode Msg) → VNode Msg
|
||||||
|
|
||||||
|
-- Attributes
|
||||||
|
|
||||||
|
onChange : (String → Msg) → Attr Msg
|
||||||
|
onChange = VAttr "onChange"
|
||||||
|
|
||||||
|
onClick : Msg → Attr Msg
|
||||||
|
onClick action = MAttr "OnClick" action
|
||||||
|
|
||||||
|
className : String → Attr Msg
|
||||||
|
className v = SAttr "class" v
|
||||||
|
|
||||||
|
checkbox : Bool → Nat → VNode Msg
|
||||||
|
checkbox checked ix =
|
||||||
|
tag "input" [ className "toggle"
|
||||||
|
, SAttr "checked" (ite checked "true" "")
|
||||||
|
, onChange (\ _ => Toggle ix)
|
||||||
|
, SAttr "type" "checkbox" ] []
|
||||||
|
|
||||||
|
-- Elements
|
||||||
|
|
||||||
|
section : ElCon
|
||||||
|
section = tag "section"
|
||||||
|
|
||||||
|
header : ElCon
|
||||||
|
header = tag "header"
|
||||||
|
|
||||||
|
footer : ElCon
|
||||||
|
footer = tag "footer"
|
||||||
|
|
||||||
|
div : ElCon
|
||||||
|
div = tag "div"
|
||||||
|
|
||||||
|
ul : ElCon
|
||||||
|
ul = tag "ul"
|
||||||
|
|
||||||
|
li : ElCon
|
||||||
|
li = tag "li"
|
||||||
|
|
||||||
|
span : ElCon
|
||||||
|
span = tag "span"
|
||||||
|
|
||||||
|
id_ : String → Attr Msg
|
||||||
|
id_ id = SAttr "id" id
|
||||||
|
|
||||||
|
itemView : FilterState → EditState → Nat × Item → Maybe (VNode Msg)
|
||||||
|
itemView filter estate (ix , item) =
|
||||||
|
if exclude filter item then Nothing else
|
||||||
|
Just $ li [ className (getCName estate) ]
|
||||||
|
[ div [ className "view", MAttr "onDblClick" (StartEdit ix) ]
|
||||||
|
[ checkbox item.checked ix
|
||||||
|
, tag "label" [] [text item.text]
|
||||||
|
, tag "button" [ className "destroy", MAttr "onClick" (Remove ix)] []
|
||||||
|
]
|
||||||
|
, tag "input" [ className "edit"
|
||||||
|
, onChange (EndEdit ix)
|
||||||
|
, SAttr "value" item.text ] [ ]
|
||||||
|
]
|
||||||
|
where
|
||||||
|
getCName : EditState → String
|
||||||
|
getCName NoEdit = ite item.checked "completed" ""
|
||||||
|
getCName (Edit n) = if n == ix then "editing" else if item.checked then "completed" else ""
|
||||||
|
|
||||||
|
exclude : FilterState → Item → Bool
|
||||||
|
exclude All _ = False
|
||||||
|
exclude Active (MkItem checked text) = checked
|
||||||
|
exclude Completed (MkItem checked text) = not checked
|
||||||
|
|
||||||
|
footerView : Model → VNode Msg
|
||||||
|
-- we don't have undefined/empty at the moment..
|
||||||
|
footerView (MkModel Nil _ _ _) = div [] []
|
||||||
|
footerView (MkModel items _ select _) =
|
||||||
|
let count = length' $ filter (\ item => not item.checked) items
|
||||||
|
ccount = length' $ filter (\ item => item.checked) items
|
||||||
|
label = text $ if count == 1 then " item left" else " items left"
|
||||||
|
in footer [className "footer"]
|
||||||
|
[ span [ className "todo-count" ] [ tag "strong" [] [ text $ show count ], label ]
|
||||||
|
, ul [ className "filters" ]
|
||||||
|
[ li [] [ tag "a" [ getClass All, onClick (Filter All)] [ text "All" ]]
|
||||||
|
, li [] [ tag "a" [ getClass Active, onClick (Filter Active)] [ text "Active" ]]
|
||||||
|
, li [] [ tag "a" [ getClass Completed, onClick (Filter Completed)] [ text "Completed" ]]
|
||||||
|
]
|
||||||
|
, (if ccount == 0 then div [] [] else tag "button" [ className "clear-completed", onClick Clear ] [ text "Clear completed" ])
|
||||||
|
]
|
||||||
|
where
|
||||||
|
getClass : FilterState → Attr Msg
|
||||||
|
getClass x = if x == select then className "selected" else className ""
|
||||||
|
|
||||||
|
listView : Model → VNode Msg
|
||||||
|
listView (MkModel Nil _ _ _) = div [] []
|
||||||
|
listView (MkModel items newText select estate) =
|
||||||
|
let count = length' items
|
||||||
|
acount = length' $ (filter (\ item => not item.checked) items)
|
||||||
|
in tag "section" [ className "main" ]
|
||||||
|
[ tag "input" [ id_ "toggle-all"
|
||||||
|
, className "toggle-all"
|
||||||
|
, onClick ToggleAll
|
||||||
|
, SAttr "type" "checkbox"] []
|
||||||
|
-- accessibility
|
||||||
|
, tag "label" [ SAttr "for" "toggle-all"] [ text "Mark all as complete" ]
|
||||||
|
, ul [ className "todo-list" ] (mapMaybe (itemView select estate) (enumerate items))
|
||||||
|
]
|
||||||
|
view : Model → VNode Msg
|
||||||
|
view model =
|
||||||
|
section [ className "todoapp" ]
|
||||||
|
[ header [ className "header" ]
|
||||||
|
[ tag "h1" [] [ text "todos" ]
|
||||||
|
, tag "input" [ className "new-todo"
|
||||||
|
, SAttr "placeholder" "What needs to be done?"
|
||||||
|
, SAttr "value" model.newText
|
||||||
|
, VAttr "onChange" Change
|
||||||
|
, SAttr "autofocus" "true"] []]
|
||||||
|
, listView model
|
||||||
|
, footerView model
|
||||||
|
]
|
||||||
|
|
||||||
|
isEmpty : ∀ a. List a → Bool
|
||||||
|
isEmpty Nil = True
|
||||||
|
isEmpty _ = False
|
||||||
|
|
||||||
|
update : Msg → Model → Model
|
||||||
|
update Clear model = { items $= filter (\ item => not item.checked) } model
|
||||||
|
update (StartEdit ix) model = { edit := Edit ix } model
|
||||||
|
update (EndEdit ix text) model = { edit := NoEdit; items $= edit ix } model
|
||||||
|
where
|
||||||
|
edit : Nat → List Item → List Item
|
||||||
|
edit Z (item :: items) = the Item { text := text } item :: items
|
||||||
|
edit (S k) (item :: items) = item :: edit k items
|
||||||
|
edit _ Nil = Nil
|
||||||
|
|
||||||
|
update ToggleAll model =
|
||||||
|
let checked = not $ isEmpty $ filter (\item => not item.checked) model.items
|
||||||
|
in { items $= map (the (Item → Item) { checked := checked }) } model
|
||||||
|
|
||||||
|
update (Toggle ix) model = { items $= toggle ix } model
|
||||||
|
where
|
||||||
|
toggleItem : Item → Item
|
||||||
|
toggleItem item = { checked := not item.checked } item
|
||||||
|
|
||||||
|
toggle : Nat → List Item → List Item
|
||||||
|
toggle _ Nil = Nil
|
||||||
|
toggle Z (item :: rest) = toggleItem item :: rest
|
||||||
|
toggle (S k) (item :: rest) = item :: toggle k rest
|
||||||
|
|
||||||
|
update (Filter filter) model = { filter := filter } model
|
||||||
|
update (Remove ix) model = {items $= delete ix } model
|
||||||
|
where
|
||||||
|
delete : Nat → List Item → List Item
|
||||||
|
delete _ Nil = Nil
|
||||||
|
delete Z (item :: rest) = rest
|
||||||
|
delete (S k) (item :: rest) = item :: delete k rest
|
||||||
|
|
||||||
|
update (Change text) model = { items := (snoc model.items $ MkItem False text) } model
|
||||||
|
|
||||||
|
main : IO Unit
|
||||||
|
main = pure $ runApp (getElementById "main") (MkModel [] "" All NoEdit) update view
|
||||||
|
|
||||||
|
|
||||||
1
todomvc/src/Web
Symbolic link
1
todomvc/src/Web
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../../src/Web
|
||||||
1
web/.gitignore
vendored
1
web/.gitignore
vendored
@@ -1 +0,0 @@
|
|||||||
dist/main.js
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
# Web framework experiment
|
|
||||||
|
|
||||||
Work in progress of a DOM-patching web framework, like Elm.
|
|
||||||
|
|
||||||
In the root, you can do:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
newt src/Todo.newt -o dist/main.js
|
|
||||||
esbuild dist/main.js --bundle --servedir=web --outdir=web --alias:fs=./dist/empty.js --watch
|
|
||||||
```
|
|
||||||
1
web/dist/empty.js
vendored
1
web/dist/empty.js
vendored
@@ -1 +0,0 @@
|
|||||||
export default {}
|
|
||||||
11
web/dist/index.html
vendored
11
web/dist/index.html
vendored
@@ -1,11 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Counter</title>
|
|
||||||
<link rel="stylesheet" href="style.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="main"></div>
|
|
||||||
<script src="main.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
126
web/dist/spruce.ts
vendored
126
web/dist/spruce.ts
vendored
@@ -1,126 +0,0 @@
|
|||||||
// We're going to do the VDom thing, directly off of newt data
|
|
||||||
|
|
||||||
type Attrib<Msg> =
|
|
||||||
| { tag: 0; h1: string; h2: string }
|
|
||||||
| { tag: 1; h1: string; h2: Msg }
|
|
||||||
| { tag: 2; h1: string; h2: (_: string) => Msg };
|
|
||||||
|
|
||||||
type List<A> = { tag: 0 } | { tag: 1; h1: A; h2: List<A> };
|
|
||||||
|
|
||||||
type VNode<Msg> =
|
|
||||||
| { tag: 0; h1: string } // TNode
|
|
||||||
| { tag: 1; h1: string; h2: List<Attrib<Msg>>; h3: List<VNode<Msg>> };
|
|
||||||
|
|
||||||
type FancyElement<Msg> = Element & { events?: Record<string, Attrib<Msg> > };
|
|
||||||
|
|
||||||
// the pfunc will have two dummy arguments in front, and I dunno no node?
|
|
||||||
export function runapp<Model, Msg>(
|
|
||||||
node: Node,
|
|
||||||
init: Model,
|
|
||||||
update: (_: Msg) => (_: Model) => Model,
|
|
||||||
view: (_: Model) => VNode<Msg>,
|
|
||||||
) {
|
|
||||||
function replace(parent: Node, node: Node | undefined, child: Node) {
|
|
||||||
if (node) {
|
|
||||||
parent.insertBefore(child, node);
|
|
||||||
parent.removeChild(node);
|
|
||||||
} else {
|
|
||||||
parent.appendChild(child);
|
|
||||||
}
|
|
||||||
return child;
|
|
||||||
}
|
|
||||||
// patch node, possibly returning a new node
|
|
||||||
function patch<Msg>(
|
|
||||||
parentNode: Node,
|
|
||||||
node: Node | undefined,
|
|
||||||
vnode: VNode<Msg>,
|
|
||||||
): Node {
|
|
||||||
if (vnode.tag == 0) {
|
|
||||||
if (node && node.nodeType == 3) {
|
|
||||||
node.nodeValue = vnode.h1;
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
return replace(parentNode, node, document.createTextNode(vnode.h1));
|
|
||||||
}
|
|
||||||
let el: FancyElement<Msg>;
|
|
||||||
if (node instanceof Element && node.tagName.toLowerCase() == vnode.h1) {
|
|
||||||
el = node;
|
|
||||||
} else {
|
|
||||||
el = document.createElement(vnode.h1);
|
|
||||||
}
|
|
||||||
// update node here
|
|
||||||
let has: Record<string, boolean> = {};
|
|
||||||
for (let attrs = vnode.h2; attrs.tag == 1; attrs = attrs.h2) {
|
|
||||||
let attr = attrs.h1;
|
|
||||||
if (attr.tag == 0) {
|
|
||||||
let key = attr.h1;
|
|
||||||
has[key] = true;
|
|
||||||
if (key in el)
|
|
||||||
(el as any)[key] = attr.h2
|
|
||||||
else
|
|
||||||
el.setAttribute(key, attr.h2);
|
|
||||||
} else {
|
|
||||||
// onBlah
|
|
||||||
let key = attr.h1.slice(2).toLowerCase();
|
|
||||||
has[key] = true;
|
|
||||||
let events = el.events || (el.events = {});
|
|
||||||
if (!events[key]) el.addEventListener(key, listener);
|
|
||||||
events[key] = attr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// remove attrs, not efficient..
|
|
||||||
for (let i = 0; i < el.attributes.length; ) {
|
|
||||||
let attr = el.attributes[i];
|
|
||||||
if (!has[attr.name]) {
|
|
||||||
el.removeAttribute(attr.name);
|
|
||||||
} else {
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (el.events) {
|
|
||||||
for (let key of Object.keys(el.events)) {
|
|
||||||
if (!has[key]) delete el.events[key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let i = 0;
|
|
||||||
for (let kids = vnode.h3; kids.tag == 1; kids = kids.h2) {
|
|
||||||
patch(el, el.childNodes[i++], kids.h1);
|
|
||||||
}
|
|
||||||
while (el.childNodes[i]) el.removeChild(el.childNodes[i]);
|
|
||||||
|
|
||||||
return node == el ? node : replace(parentNode, node, el);
|
|
||||||
}
|
|
||||||
let model = init;
|
|
||||||
let vdom = view(model);
|
|
||||||
let listener = (ev: Event) => {
|
|
||||||
let target = ev.target as FancyElement<Msg>;
|
|
||||||
if (!target.events) return;
|
|
||||||
const attr = target.events[ev.type]
|
|
||||||
let action
|
|
||||||
if (attr.tag === 2) {
|
|
||||||
// probably need to pass back the event
|
|
||||||
// we'll want to deal with keypress, change, ...
|
|
||||||
let action = attr.h2(ev.target!.value)
|
|
||||||
model = update(action)(model);
|
|
||||||
} else if (attr.tag === 1) {
|
|
||||||
model = update(attr.h2)(model);
|
|
||||||
}
|
|
||||||
vdom = view(model);
|
|
||||||
node = patch(node.parentNode!, node, vdom);
|
|
||||||
};
|
|
||||||
node = patch(node.parentNode!, node, vdom);
|
|
||||||
}
|
|
||||||
[{
|
|
||||||
"resource": "/Users/dunham/prj/newt/src/Web/spruce.ts",
|
|
||||||
"owner": "typescript",
|
|
||||||
"code": "2349",
|
|
||||||
"severity": 8,
|
|
||||||
"message": "This expression is not callable.\n Not all constituents of type '((_: string) => Msg) | (Msg & Function)' are callable.\n Type 'Msg & Function' has no call signatures.",
|
|
||||||
"source": "ts",
|
|
||||||
"startLineNumber": 101,
|
|
||||||
"startColumn": 16,
|
|
||||||
"endLineNumber": 101,
|
|
||||||
"endColumn": 19,
|
|
||||||
"modelVersionId": 289,
|
|
||||||
"origin": "extHost1"
|
|
||||||
}]
|
|
||||||
13
web/dist/style.css
vendored
13
web/dist/style.css
vendored
@@ -1,13 +0,0 @@
|
|||||||
.item {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
.item > * {
|
|
||||||
vertical-align: middle;
|
|
||||||
padding-right: 10px;
|
|
||||||
}
|
|
||||||
.item > div {
|
|
||||||
display: inline;
|
|
||||||
width: 100px;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
module Spruce
|
|
||||||
|
|
||||||
-- Spruce is not Elm
|
|
||||||
|
|
||||||
import Prelude
|
|
||||||
|
|
||||||
ptype Element
|
|
||||||
pfunc getElementById : String → Element := `(id) => document.getElementById(id)`
|
|
||||||
|
|
||||||
-- Make this align with spruce.ts
|
|
||||||
data Attr msg = SAttr String String | MAttr String msg | VAttr String (String → msg)
|
|
||||||
data VNode msg = TNode String | ENode String (List $ Attr msg) (List $ VNode msg)
|
|
||||||
|
|
||||||
text : ∀ msg. String → VNode msg
|
|
||||||
text = TNode
|
|
||||||
|
|
||||||
tag : ∀ msg. String → List (Attr msg)→ List (VNode msg) → VNode msg
|
|
||||||
tag = ENode
|
|
||||||
|
|
||||||
tag_ : ∀ msg. String → List (VNode msg)→ VNode msg
|
|
||||||
tag_ tag es = ENode tag Nil es
|
|
||||||
|
|
||||||
-- TODO better story for import..
|
|
||||||
-- also, we'll probably want to expose something with subscriptions / dispatch / ...
|
|
||||||
pfunc runApp : ∀ msg model. Element → model → (update : msg → model → model) → (view : model → VNode msg) → Unit :=
|
|
||||||
`(_msg, _model, el, init, update, view) => {
|
|
||||||
require('./spruce').runapp(el,init,update,view);
|
|
||||||
return 0;
|
|
||||||
}`
|
|
||||||
|
|
||||||
-- App
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
module Todo
|
|
||||||
|
|
||||||
import Prelude
|
|
||||||
import Spruce
|
|
||||||
|
|
||||||
data Msg
|
|
||||||
= Toggle Nat
|
|
||||||
| Remove Nat
|
|
||||||
| Change String
|
|
||||||
|
|
||||||
record Item where
|
|
||||||
checked : Bool
|
|
||||||
text : String
|
|
||||||
|
|
||||||
record Model where
|
|
||||||
items : List Item
|
|
||||||
edit : String
|
|
||||||
|
|
||||||
ElCon : U
|
|
||||||
ElCon = List (Attr Msg) → List (VNode Msg) → VNode Msg
|
|
||||||
|
|
||||||
-- Attributes
|
|
||||||
|
|
||||||
onChange : Msg → Attr Msg
|
|
||||||
onChange = MAttr "onChange"
|
|
||||||
|
|
||||||
onClick : Msg → Attr Msg
|
|
||||||
onClick action = MAttr "OnClick" action
|
|
||||||
|
|
||||||
className : String → Attr Msg
|
|
||||||
className v = SAttr "class" v
|
|
||||||
|
|
||||||
checkbox : Bool → Nat → VNode Msg
|
|
||||||
checkbox checked ix =
|
|
||||||
tag "input" [ SAttr "checked" (ite checked "true" ""), onChange (Toggle ix), SAttr "type" "checkbox" ] []
|
|
||||||
|
|
||||||
-- Elements
|
|
||||||
|
|
||||||
div : ElCon
|
|
||||||
div = tag "div"
|
|
||||||
|
|
||||||
button : String → Msg → VNode Msg
|
|
||||||
button label action = tag "button" [ onClick action ] [ text label ]
|
|
||||||
|
|
||||||
|
|
||||||
itemView : Nat × Item → VNode Msg
|
|
||||||
itemView (ix , item) =
|
|
||||||
div [ className "item" ]
|
|
||||||
[ checkbox item.checked ix
|
|
||||||
, div [] [text item.text]
|
|
||||||
, button "x" (Remove ix)
|
|
||||||
]
|
|
||||||
|
|
||||||
view : Model → VNode Msg
|
|
||||||
view model =
|
|
||||||
div []
|
|
||||||
[ div [] (map itemView $ enumerate model.items)
|
|
||||||
, tag "input" [ SAttr "value" model.edit, VAttr "onChange" Change ] []
|
|
||||||
]
|
|
||||||
|
|
||||||
update : Msg → Model → Model
|
|
||||||
update (Toggle ix) model = { items $= toggle ix } model
|
|
||||||
where
|
|
||||||
toggleItem : Item → Item
|
|
||||||
toggleItem item = { checked := not item.checked } item
|
|
||||||
|
|
||||||
toggle : Nat → List Item → List Item
|
|
||||||
toggle _ Nil = Nil
|
|
||||||
toggle Z (item :: rest) = toggleItem item :: rest
|
|
||||||
toggle (S k) (item :: rest) = item :: toggle k rest
|
|
||||||
|
|
||||||
update (Remove ix) model = {items $= delete ix } model
|
|
||||||
where
|
|
||||||
delete : Nat → List Item → List Item
|
|
||||||
delete _ Nil = Nil
|
|
||||||
delete Z (item :: rest) = rest
|
|
||||||
delete (S k) (item :: rest) = item :: delete k rest
|
|
||||||
|
|
||||||
update (Change text) model = { items := (snoc model.items $ MkItem False text) } model
|
|
||||||
|
|
||||||
main : IO Unit
|
|
||||||
main = pure $ runApp (getElementById "main") (MkModel [] "") update view
|
|
||||||
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user