From d846ddb62202249cb28420189a479bf5a9fc9d56 Mon Sep 17 00:00:00 2001 From: Derek Lemon Date: Tue, 19 Dec 2017 14:29:06 -0700 Subject: [PATCH] Initial, Cant compile on windows --- rss/gopherjs/actions/actions.go | 45 +++ rss/gopherjs/components/filterbutton.go | 42 ++ rss/gopherjs/components/itemview.go | 117 ++++++ rss/gopherjs/components/pageview.go | 208 ++++++++++ rss/gopherjs/dispatcher/dispatcher.go | 29 ++ rss/gopherjs/example.go | 47 +++ .../node_modules/todomvc-app-css/index.css | 378 ++++++++++++++++++ .../node_modules/todomvc-app-css/package.json | 67 ++++ .../node_modules/todomvc-app-css/readme.md | 28 ++ .../node_modules/todomvc-common/base.css | 141 +++++++ .../node_modules/todomvc-common/base.js | 249 ++++++++++++ .../node_modules/todomvc-common/package.json | 63 +++ .../node_modules/todomvc-common/readme.md | 15 + rss/gopherjs/store/model/model.go | 21 + rss/gopherjs/store/store.go | 85 ++++ rss/gopherjs/store/storeutil/storeutil.go | 40 ++ 16 files changed, 1575 insertions(+) create mode 100644 rss/gopherjs/actions/actions.go create mode 100644 rss/gopherjs/components/filterbutton.go create mode 100644 rss/gopherjs/components/itemview.go create mode 100644 rss/gopherjs/components/pageview.go create mode 100644 rss/gopherjs/dispatcher/dispatcher.go create mode 100644 rss/gopherjs/example.go create mode 100644 rss/gopherjs/node_modules/todomvc-app-css/index.css create mode 100644 rss/gopherjs/node_modules/todomvc-app-css/package.json create mode 100644 rss/gopherjs/node_modules/todomvc-app-css/readme.md create mode 100644 rss/gopherjs/node_modules/todomvc-common/base.css create mode 100644 rss/gopherjs/node_modules/todomvc-common/base.js create mode 100644 rss/gopherjs/node_modules/todomvc-common/package.json create mode 100644 rss/gopherjs/node_modules/todomvc-common/readme.md create mode 100644 rss/gopherjs/store/model/model.go create mode 100644 rss/gopherjs/store/store.go create mode 100644 rss/gopherjs/store/storeutil/storeutil.go diff --git a/rss/gopherjs/actions/actions.go b/rss/gopherjs/actions/actions.go new file mode 100644 index 0000000..ba058e0 --- /dev/null +++ b/rss/gopherjs/actions/actions.go @@ -0,0 +1,45 @@ +package actions + +import "github.com/drekle/go/rss/gopherjs/store/model" + +// ReplaceItems is an action that replaces all items with the specified ones. +type ReplaceItems struct { + Items []*model.Item +} + +// AddItem is an action which adds a single item with the specified title. +type AddItem struct { + Title string +} + +// DestroyItem is an action which destroys the item specified by the index. +type DestroyItem struct { + Index int +} + +// SetTitle is an action which specifies the title of an existing item. +type SetTitle struct { + Index int + Title string +} + +// SetCompleted is an action which specifies the completed state of an existing +// item. +type SetCompleted struct { + Index int + Completed bool +} + +// SetAllCompleted is an action which marks all existing items as being +// completed or not. +type SetAllCompleted struct { + Completed bool +} + +// ClearCompleted is an action which clears the completed items. +type ClearCompleted struct{} + +// SetFilter is an action which sets the filter for the viewed items. +type SetFilter struct { + Filter model.FilterState +} diff --git a/rss/gopherjs/components/filterbutton.go b/rss/gopherjs/components/filterbutton.go new file mode 100644 index 0000000..00e4434 --- /dev/null +++ b/rss/gopherjs/components/filterbutton.go @@ -0,0 +1,42 @@ +package components + +import ( + "github.com/drekle/go/rss/gopherjs/actions" + "github.com/drekle/go/rss/gopherjs/dispatcher" + "github.com/drekle/go/rss/gopherjs/store" + "github.com/drekle/go/rss/gopherjs/store/model" + "github.com/gopherjs/vecty" + "github.com/gopherjs/vecty/elem" + "github.com/gopherjs/vecty/event" + "github.com/gopherjs/vecty/prop" +) + +// FilterButton is a vecty.Component which allows the user to select a filter +// state. +type FilterButton struct { + vecty.Core + + Label string `vecty:"prop"` + Filter model.FilterState `vecty:"prop"` +} + +func (b *FilterButton) onClick(event *vecty.Event) { + dispatcher.Dispatch(&actions.SetFilter{ + Filter: b.Filter, + }) +} + +// Render implements the vecty.Component interface. +func (b *FilterButton) Render() vecty.ComponentOrHTML { + return elem.ListItem( + elem.Anchor( + vecty.Markup( + vecty.MarkupIf(store.Filter == b.Filter, vecty.Class("selected")), + prop.Href("#"), + event.Click(b.onClick).PreventDefault(), + ), + + vecty.Text(b.Label), + ), + ) +} diff --git a/rss/gopherjs/components/itemview.go b/rss/gopherjs/components/itemview.go new file mode 100644 index 0000000..fd0ff04 --- /dev/null +++ b/rss/gopherjs/components/itemview.go @@ -0,0 +1,117 @@ +package components + +import ( + "github.com/drekle/go/rss/gopherjs/actions" + "github.com/drekle/go/rss/gopherjs/dispatcher" + "github.com/drekle/go/rss/gopherjs/store/model" + "github.com/gopherjs/vecty" + "github.com/gopherjs/vecty/elem" + "github.com/gopherjs/vecty/event" + "github.com/gopherjs/vecty/prop" + "github.com/gopherjs/vecty/style" +) + +// ItemView is a vecty.Component which represents a single item in the TODO +// list. +type ItemView struct { + vecty.Core + + Index int `vecty:"prop"` + Item *model.Item `vecty:"prop"` + editing bool + editTitle string + input *vecty.HTML +} + +// Key implements the vecty.Keyer interface. +func (p *ItemView) Key() interface{} { + return p.Index +} + +func (p *ItemView) onDestroy(event *vecty.Event) { + dispatcher.Dispatch(&actions.DestroyItem{ + Index: p.Index, + }) +} + +func (p *ItemView) onToggleCompleted(event *vecty.Event) { + dispatcher.Dispatch(&actions.SetCompleted{ + Index: p.Index, + Completed: event.Target.Get("checked").Bool(), + }) +} + +func (p *ItemView) onStartEdit(event *vecty.Event) { + p.editing = true + p.editTitle = p.Item.Title + vecty.Rerender(p) + p.input.Node().Call("focus") +} + +func (p *ItemView) onEditInput(event *vecty.Event) { + p.editTitle = event.Target.Get("value").String() + vecty.Rerender(p) +} + +func (p *ItemView) onStopEdit(event *vecty.Event) { + p.editing = false + vecty.Rerender(p) + dispatcher.Dispatch(&actions.SetTitle{ + Index: p.Index, + Title: p.editTitle, + }) +} + +// Render implements the vecty.Component interface. +func (p *ItemView) Render() vecty.ComponentOrHTML { + p.input = elem.Input( + vecty.Markup( + vecty.Class("edit"), + prop.Value(p.editTitle), + event.Input(p.onEditInput), + ), + ) + + return elem.ListItem( + vecty.Markup( + vecty.ClassMap{ + "completed": p.Item.Completed, + "editing": p.editing, + }, + ), + + elem.Div( + vecty.Markup( + vecty.Class("view"), + ), + + elem.Input( + vecty.Markup( + vecty.Class("toggle"), + prop.Type(prop.TypeCheckbox), + prop.Checked(p.Item.Completed), + event.Change(p.onToggleCompleted), + ), + ), + elem.Label( + vecty.Markup( + event.DoubleClick(p.onStartEdit), + ), + vecty.Text(p.Item.Title), + ), + elem.Button( + vecty.Markup( + vecty.Class("destroy"), + event.Click(p.onDestroy), + ), + ), + ), + elem.Form( + vecty.Markup( + style.Margin(style.Px(0)), + event.Submit(p.onStopEdit).PreventDefault(), + ), + p.input, + ), + ) +} diff --git a/rss/gopherjs/components/pageview.go b/rss/gopherjs/components/pageview.go new file mode 100644 index 0000000..556b0fc --- /dev/null +++ b/rss/gopherjs/components/pageview.go @@ -0,0 +1,208 @@ +package components + +import ( + "strconv" + + "github.com/drekle/go/rss/gopherjs/actions" + "github.com/drekle/go/rss/gopherjs/dispatcher" + "github.com/drekle/go/rss/gopherjs/store" + "github.com/drekle/go/rss/gopherjs/store/model" + "github.com/gopherjs/vecty" + "github.com/gopherjs/vecty/elem" + "github.com/gopherjs/vecty/event" + "github.com/gopherjs/vecty/prop" + "github.com/gopherjs/vecty/style" +) + +// PageView is a vecty.Component which represents the entire page. +type PageView struct { + vecty.Core + + Items []*model.Item `vecty:"prop"` + newItemTitle string +} + +func (p *PageView) onNewItemTitleInput(event *vecty.Event) { + p.newItemTitle = event.Target.Get("value").String() + vecty.Rerender(p) +} + +func (p *PageView) onAdd(event *vecty.Event) { + dispatcher.Dispatch(&actions.AddItem{ + Title: p.newItemTitle, + }) + p.newItemTitle = "" + vecty.Rerender(p) +} + +func (p *PageView) onClearCompleted(event *vecty.Event) { + dispatcher.Dispatch(&actions.ClearCompleted{}) +} + +func (p *PageView) onToggleAllCompleted(event *vecty.Event) { + dispatcher.Dispatch(&actions.SetAllCompleted{ + Completed: event.Target.Get("checked").Bool(), + }) +} + +// Render implements the vecty.Component interface. +func (p *PageView) Render() vecty.ComponentOrHTML { + return elem.Body( + elem.Section( + vecty.Markup( + vecty.Class("todoapp"), + ), + + p.renderHeader(), + vecty.If(len(store.Items) > 0, + p.renderItemList(), + p.renderFooter(), + ), + ), + + p.renderInfo(), + ) +} + +func (p *PageView) renderHeader() *vecty.HTML { + return elem.Header( + vecty.Markup( + vecty.Class("header"), + ), + + elem.Heading1( + vecty.Text("todos"), + ), + elem.Form( + vecty.Markup( + style.Margin(style.Px(0)), + event.Submit(p.onAdd).PreventDefault(), + ), + + elem.Input( + vecty.Markup( + vecty.Class("new-todo"), + prop.Placeholder("What needs to be done?"), + prop.Autofocus(true), + prop.Value(p.newItemTitle), + event.Input(p.onNewItemTitleInput), + ), + ), + ), + ) +} + +func (p *PageView) renderFooter() *vecty.HTML { + count := store.ActiveItemCount() + var itemsLeftText = " items left" + if count == 1 { + itemsLeftText = " item left" + } + + return elem.Footer( + vecty.Markup( + vecty.Class("footer"), + ), + + elem.Span( + vecty.Markup( + vecty.Class("todo-count"), + ), + + elem.Strong( + vecty.Text(strconv.Itoa(count)), + ), + vecty.Text(itemsLeftText), + ), + + elem.UnorderedList( + vecty.Markup( + vecty.Class("filters"), + ), + &FilterButton{Label: "All", Filter: model.All}, + vecty.Text(" "), + &FilterButton{Label: "Active", Filter: model.Active}, + vecty.Text(" "), + &FilterButton{Label: "Completed", Filter: model.Completed}, + ), + + vecty.If(store.CompletedItemCount() > 0, + elem.Button( + vecty.Markup( + vecty.Class("clear-completed"), + event.Click(p.onClearCompleted), + ), + vecty.Text("Clear completed ("+strconv.Itoa(store.CompletedItemCount())+")"), + ), + ), + ) +} + +func (p *PageView) renderInfo() *vecty.HTML { + return elem.Footer( + vecty.Markup( + vecty.Class("info"), + ), + + elem.Paragraph( + vecty.Text("Double-click to edit a todo"), + ), + elem.Paragraph( + vecty.Text("Created by "), + elem.Anchor( + vecty.Markup( + prop.Href("http://github.com/neelance"), + ), + vecty.Text("Richard Musiol"), + ), + ), + elem.Paragraph( + vecty.Text("Part of "), + elem.Anchor( + vecty.Markup( + prop.Href("http://todomvc.com"), + ), + vecty.Text("TodoMVC"), + ), + ), + ) +} + +func (p *PageView) renderItemList() *vecty.HTML { + var items vecty.List + for i, item := range store.Items { + if (store.Filter == model.Active && item.Completed) || (store.Filter == model.Completed && !item.Completed) { + continue + } + items = append(items, &ItemView{Index: i, Item: item}) + } + + return elem.Section( + vecty.Markup( + vecty.Class("main"), + ), + + elem.Input( + vecty.Markup( + vecty.Class("toggle-all"), + prop.ID("toggle-all"), + prop.Type(prop.TypeCheckbox), + prop.Checked(store.CompletedItemCount() == len(store.Items)), + event.Change(p.onToggleAllCompleted), + ), + ), + elem.Label( + vecty.Markup( + prop.For("toggle-all"), + ), + vecty.Text("Mark all as complete"), + ), + + elem.UnorderedList( + vecty.Markup( + vecty.Class("todo-list"), + ), + items, + ), + ) +} diff --git a/rss/gopherjs/dispatcher/dispatcher.go b/rss/gopherjs/dispatcher/dispatcher.go new file mode 100644 index 0000000..f4f0e55 --- /dev/null +++ b/rss/gopherjs/dispatcher/dispatcher.go @@ -0,0 +1,29 @@ +package dispatcher + +// ID is a unique identifier representing a registered callback function. +type ID int + +var idCounter ID +var callbacks = make(map[ID]func(action interface{})) + +// Dispatch dispatches the given action to all registered callbacks. +func Dispatch(action interface{}) { + for _, c := range callbacks { + c(action) + } +} + +// Register registers the callback to handle dispatched actions, the returned +// ID may be used to unregister the callback later. +func Register(callback func(action interface{})) ID { + idCounter++ + id := idCounter + callbacks[id] = callback + return id +} + +// Unregister unregisters the callback previously registered via a call to +// Register. +func Unregister(id ID) { + delete(callbacks, id) +} diff --git a/rss/gopherjs/example.go b/rss/gopherjs/example.go new file mode 100644 index 0000000..bd7dbf0 --- /dev/null +++ b/rss/gopherjs/example.go @@ -0,0 +1,47 @@ +package main + +import ( + "encoding/json" + + "github.com/drekle/go/rss/gopherjs/actions" + "github.com/drekle/go/rss/gopherjs/components" + "github.com/drekle/go/rss/gopherjs/dispatcher" + "github.com/drekle/go/rss/gopherjs/store" + "github.com/drekle/go/rss/gopherjs/store/model" + "github.com/gopherjs/gopherjs/js" + "github.com/gopherjs/vecty" +) + +func main() { + attachLocalStorage() + + vecty.SetTitle("GopherJS • TodoMVC") + vecty.AddStylesheet("node_modules/todomvc-common/base.css") + vecty.AddStylesheet("node_modules/todomvc-app-css/index.css") + p := &components.PageView{} + store.Listeners.Add(p, func() { + p.Items = store.Items + vecty.Rerender(p) + }) + vecty.RenderBody(p) +} + +func attachLocalStorage() { + store.Listeners.Add(nil, func() { + data, err := json.Marshal(store.Items) + if err != nil { + println("failed to store items: " + err.Error()) + } + js.Global.Get("localStorage").Set("items", string(data)) + }) + + if data := js.Global.Get("localStorage").Get("items"); data != js.Undefined { + var items []*model.Item + if err := json.Unmarshal([]byte(data.String()), &items); err != nil { + println("failed to load items: " + err.Error()) + } + dispatcher.Dispatch(&actions.ReplaceItems{ + Items: items, + }) + } +} diff --git a/rss/gopherjs/node_modules/todomvc-app-css/index.css b/rss/gopherjs/node_modules/todomvc-app-css/index.css new file mode 100644 index 0000000..ba79a58 --- /dev/null +++ b/rss/gopherjs/node_modules/todomvc-app-css/index.css @@ -0,0 +1,378 @@ +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-font-smoothing: antialiased; + font-smoothing: antialiased; +} + +body { + font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif; + line-height: 1.4em; + background: #f5f5f5; + color: #4d4d4d; + min-width: 230px; + max-width: 550px; + margin: 0 auto; + -webkit-font-smoothing: antialiased; + -moz-font-smoothing: antialiased; + font-smoothing: antialiased; + font-weight: 300; +} + +button, +input[type="checkbox"] { + outline: none; +} + +.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: 300; + color: #e6e6e6; +} + +.todoapp input::-moz-placeholder { + font-style: italic; + font-weight: 300; + color: #e6e6e6; +} + +.todoapp input::input-placeholder { + font-style: italic; + font-weight: 300; + color: #e6e6e6; +} + +.todoapp h1 { + position: absolute; + top: -155px; + width: 100%; + font-size: 100px; + font-weight: 100; + text-align: center; + color: rgba(175, 47, 47, 0.15); + -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; + border: 0; + outline: none; + 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-font-smoothing: antialiased; + font-smoothing: antialiased; +} + +.new-todo { + padding: 16px 16px 16px 60px; + 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; +} + +label[for='toggle-all'] { + display: none; +} + +.toggle-all { + position: absolute; + top: -55px; + left: -12px; + width: 60px; + height: 34px; + text-align: center; + border: none; /* Mobile Safari */ +} + +.toggle-all:before { + content: '❯'; + font-size: 22px; + color: #e6e6e6; + padding: 10px 27px 10px 27px; +} + +.toggle-all:checked:before { + color: #737373; +} + +.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: 506px; + padding: 13px 17px 12px 17px; + 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:after { + content: url('data:image/svg+xml;utf8,'); +} + +.todo-list li .toggle:checked:after { + content: url('data:image/svg+xml;utf8,'); +} + +.todo-list li label { + white-space: pre; + word-break: break-word; + padding: 15px 60px 15px 15px; + margin-left: 45px; + display: block; + line-height: 1.2; + transition: color 0.4s; +} + +.todo-list li.completed label { + color: #d9d9d9; + 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: #cc9a9a; + margin-bottom: 11px; + transition: color 0.2s ease-out; +} + +.todo-list li .destroy:hover { + color: #af5b5e; +} + +.todo-list li .destroy:after { + content: '×'; +} + +.todo-list li:hover .destroy { + display: block; +} + +.todo-list li .edit { + display: none; +} + +.todo-list li.editing:last-child { + margin-bottom: -1px; +} + +.footer { + color: #777; + padding: 10px 15px; + height: 20px; + text-align: center; + 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.selected, +.filters li a:hover { + border-color: rgba(175, 47, 47, 0.1); +} + +.filters li a.selected { + border-color: rgba(175, 47, 47, 0.2); +} + +.clear-completed, +html .clear-completed:active { + float: right; + position: relative; + line-height: 20px; + text-decoration: none; + cursor: pointer; + position: relative; +} + +.clear-completed:hover { + text-decoration: underline; +} + +.info { + margin: 65px auto 0; + color: #bfbfbf; + font-size: 10px; + 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; + } + + .toggle-all { + -webkit-transform: rotate(90deg); + transform: rotate(90deg); + -webkit-appearance: none; + appearance: none; + } +} + +@media (max-width: 430px) { + .footer { + height: 50px; + } + + .filters { + bottom: 10px; + } +} diff --git a/rss/gopherjs/node_modules/todomvc-app-css/package.json b/rss/gopherjs/node_modules/todomvc-app-css/package.json new file mode 100644 index 0000000..0aa3fe6 --- /dev/null +++ b/rss/gopherjs/node_modules/todomvc-app-css/package.json @@ -0,0 +1,67 @@ +{ + "name": "todomvc-app-css", + "version": "2.0.1", + "description": "CSS for TodoMVC apps", + "license": "CC-BY-4.0", + "repository": { + "type": "git", + "url": "https://github.com/tastejs/todomvc-app-css" + }, + "author": { + "name": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com" + }, + "files": [ + "index.css" + ], + "keywords": [ + "todomvc", + "tastejs", + "app", + "todo", + "template", + "css", + "style", + "stylesheet" + ], + "gitHead": "f1bb1aa9b19888f339055418374a9b3a2d4c6fc5", + "bugs": { + "url": "https://github.com/tastejs/todomvc-app-css/issues" + }, + "homepage": "https://github.com/tastejs/todomvc-app-css", + "_id": "todomvc-app-css@2.0.1", + "scripts": {}, + "_shasum": "f64d50b744a8a83c1151a08055b88f3aa5ccb052", + "_from": "todomvc-app-css@*", + "_npmVersion": "2.5.1", + "_nodeVersion": "0.12.0", + "_npmUser": { + "name": "sindresorhus", + "email": "sindresorhus@gmail.com" + }, + "maintainers": [ + { + "name": "sindresorhus", + "email": "sindresorhus@gmail.com" + }, + { + "name": "addyosmani", + "email": "addyosmani@gmail.com" + }, + { + "name": "passy", + "email": "phartig@rdrei.net" + }, + { + "name": "stephenplusplus", + "email": "sawchuk@gmail.com" + } + ], + "dist": { + "shasum": "f64d50b744a8a83c1151a08055b88f3aa5ccb052", + "tarball": "http://registry.npmjs.org/todomvc-app-css/-/todomvc-app-css-2.0.1.tgz" + }, + "directories": {}, + "_resolved": "https://registry.npmjs.org/todomvc-app-css/-/todomvc-app-css-2.0.1.tgz" +} diff --git a/rss/gopherjs/node_modules/todomvc-app-css/readme.md b/rss/gopherjs/node_modules/todomvc-app-css/readme.md new file mode 100644 index 0000000..6ddbebf --- /dev/null +++ b/rss/gopherjs/node_modules/todomvc-app-css/readme.md @@ -0,0 +1,28 @@ +# todomvc-app-css + +> CSS for TodoMVC apps + +![](screenshot.png) + + +## Install + + +``` +$ npm install --save todomvc-app-css +``` + + +## Getting started + +```html + +``` + +See the [TodoMVC app template](https://github.com/tastejs/todomvc-app-template). + + + +## License + +Creative Commons License
This work by Sindre Sorhus is licensed under a Creative Commons Attribution 4.0 International License. diff --git a/rss/gopherjs/node_modules/todomvc-common/base.css b/rss/gopherjs/node_modules/todomvc-common/base.css new file mode 100644 index 0000000..da65968 --- /dev/null +++ b/rss/gopherjs/node_modules/todomvc-common/base.css @@ -0,0 +1,141 @@ +hr { + margin: 20px 0; + border: 0; + border-top: 1px dashed #c5c5c5; + border-bottom: 1px dashed #f7f7f7; +} + +.learn a { + font-weight: normal; + text-decoration: none; + color: #b83f45; +} + +.learn a:hover { + text-decoration: underline; + color: #787e7e; +} + +.learn h3, +.learn h4, +.learn h5 { + margin: 10px 0; + font-weight: 500; + line-height: 1.2; + color: #000; +} + +.learn h3 { + font-size: 24px; +} + +.learn h4 { + font-size: 18px; +} + +.learn h5 { + margin-bottom: 0; + font-size: 14px; +} + +.learn ul { + padding: 0; + margin: 0 0 30px 25px; +} + +.learn li { + line-height: 20px; +} + +.learn p { + font-size: 15px; + font-weight: 300; + line-height: 1.3; + margin-top: 0; + margin-bottom: 0; +} + +#issue-count { + display: none; +} + +.quote { + border: none; + margin: 20px 0 60px 0; +} + +.quote p { + font-style: italic; +} + +.quote p:before { + content: '“'; + font-size: 50px; + opacity: .15; + position: absolute; + top: -20px; + left: 3px; +} + +.quote p:after { + content: '”'; + font-size: 50px; + opacity: .15; + position: absolute; + bottom: -42px; + right: 3px; +} + +.quote footer { + position: absolute; + bottom: -40px; + right: 0; +} + +.quote footer img { + border-radius: 3px; +} + +.quote footer a { + margin-left: 5px; + vertical-align: middle; +} + +.speech-bubble { + position: relative; + padding: 10px; + background: rgba(0, 0, 0, .04); + border-radius: 5px; +} + +.speech-bubble:after { + content: ''; + position: absolute; + top: 100%; + right: 30px; + border: 13px solid transparent; + border-top-color: rgba(0, 0, 0, .04); +} + +.learn-bar > .learn { + position: absolute; + width: 272px; + top: 8px; + left: -300px; + padding: 10px; + border-radius: 5px; + background-color: rgba(255, 255, 255, .6); + transition-property: left; + transition-duration: 500ms; +} + +@media (min-width: 899px) { + .learn-bar { + width: auto; + padding-left: 300px; + } + + .learn-bar > .learn { + left: 8px; + } +} diff --git a/rss/gopherjs/node_modules/todomvc-common/base.js b/rss/gopherjs/node_modules/todomvc-common/base.js new file mode 100644 index 0000000..3c6723f --- /dev/null +++ b/rss/gopherjs/node_modules/todomvc-common/base.js @@ -0,0 +1,249 @@ +/* global _ */ +(function () { + 'use strict'; + + /* jshint ignore:start */ + // Underscore's Template Module + // Courtesy of underscorejs.org + var _ = (function (_) { + _.defaults = function (object) { + if (!object) { + return object; + } + for (var argsIndex = 1, argsLength = arguments.length; argsIndex < argsLength; argsIndex++) { + var iterable = arguments[argsIndex]; + if (iterable) { + for (var key in iterable) { + if (object[key] == null) { + object[key] = iterable[key]; + } + } + } + } + return object; + } + + // By default, Underscore uses ERB-style template delimiters, change the + // following template settings to use alternative delimiters. + _.templateSettings = { + evaluate : /<%([\s\S]+?)%>/g, + interpolate : /<%=([\s\S]+?)%>/g, + escape : /<%-([\s\S]+?)%>/g + }; + + // When customizing `templateSettings`, if you don't want to define an + // interpolation, evaluation or escaping regex, we need one that is + // guaranteed not to match. + var noMatch = /(.)^/; + + // Certain characters need to be escaped so that they can be put into a + // string literal. + var escapes = { + "'": "'", + '\\': '\\', + '\r': 'r', + '\n': 'n', + '\t': 't', + '\u2028': 'u2028', + '\u2029': 'u2029' + }; + + var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; + + // JavaScript micro-templating, similar to John Resig's implementation. + // Underscore templating handles arbitrary delimiters, preserves whitespace, + // and correctly escapes quotes within interpolated code. + _.template = function(text, data, settings) { + var render; + settings = _.defaults({}, settings, _.templateSettings); + + // Combine delimiters into one regular expression via alternation. + var matcher = new RegExp([ + (settings.escape || noMatch).source, + (settings.interpolate || noMatch).source, + (settings.evaluate || noMatch).source + ].join('|') + '|$', 'g'); + + // Compile the template source, escaping string literals appropriately. + var index = 0; + var source = "__p+='"; + text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { + source += text.slice(index, offset) + .replace(escaper, function(match) { return '\\' + escapes[match]; }); + + if (escape) { + source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; + } + if (interpolate) { + source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; + } + if (evaluate) { + source += "';\n" + evaluate + "\n__p+='"; + } + index = offset + match.length; + return match; + }); + source += "';\n"; + + // If a variable is not specified, place data values in local scope. + if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; + + source = "var __t,__p='',__j=Array.prototype.join," + + "print=function(){__p+=__j.call(arguments,'');};\n" + + source + "return __p;\n"; + + try { + render = new Function(settings.variable || 'obj', '_', source); + } catch (e) { + e.source = source; + throw e; + } + + if (data) return render(data, _); + var template = function(data) { + return render.call(this, data, _); + }; + + // Provide the compiled function source as a convenience for precompilation. + template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}'; + + return template; + }; + + return _; + })({}); + + if (location.hostname === 'todomvc.com') { + (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ + (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), + m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) + })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); + ga('create', 'UA-31081062-1', 'auto'); + ga('send', 'pageview'); + } + /* jshint ignore:end */ + + function redirect() { + if (location.hostname === 'tastejs.github.io') { + location.href = location.href.replace('tastejs.github.io/todomvc', 'todomvc.com'); + } + } + + function findRoot() { + var base = location.href.indexOf('examples/'); + return location.href.substr(0, base); + } + + function getFile(file, callback) { + if (!location.host) { + return console.info('Miss the info bar? Run TodoMVC from a server to avoid a cross-origin error.'); + } + + var xhr = new XMLHttpRequest(); + + xhr.open('GET', findRoot() + file, true); + xhr.send(); + + xhr.onload = function () { + if (xhr.status === 200 && callback) { + callback(xhr.responseText); + } + }; + } + + function Learn(learnJSON, config) { + if (!(this instanceof Learn)) { + return new Learn(learnJSON, config); + } + + var template, framework; + + if (typeof learnJSON !== 'object') { + try { + learnJSON = JSON.parse(learnJSON); + } catch (e) { + return; + } + } + + if (config) { + template = config.template; + framework = config.framework; + } + + if (!template && learnJSON.templates) { + template = learnJSON.templates.todomvc; + } + + if (!framework && document.querySelector('[data-framework]')) { + framework = document.querySelector('[data-framework]').dataset.framework; + } + + this.template = template; + + if (learnJSON.backend) { + this.frameworkJSON = learnJSON.backend; + this.frameworkJSON.issueLabel = framework; + this.append({ + backend: true + }); + } else if (learnJSON[framework]) { + this.frameworkJSON = learnJSON[framework]; + this.frameworkJSON.issueLabel = framework; + this.append(); + } + + this.fetchIssueCount(); + } + + Learn.prototype.append = function (opts) { + var aside = document.createElement('aside'); + aside.innerHTML = _.template(this.template, this.frameworkJSON); + aside.className = 'learn'; + + if (opts && opts.backend) { + // Remove demo link + var sourceLinks = aside.querySelector('.source-links'); + var heading = sourceLinks.firstElementChild; + var sourceLink = sourceLinks.lastElementChild; + // Correct link path + var href = sourceLink.getAttribute('href'); + sourceLink.setAttribute('href', href.substr(href.lastIndexOf('http'))); + sourceLinks.innerHTML = heading.outerHTML + sourceLink.outerHTML; + } else { + // Localize demo links + var demoLinks = aside.querySelectorAll('.demo-link'); + Array.prototype.forEach.call(demoLinks, function (demoLink) { + if (demoLink.getAttribute('href').substr(0, 4) !== 'http') { + demoLink.setAttribute('href', findRoot() + demoLink.getAttribute('href')); + } + }); + } + + document.body.className = (document.body.className + ' learn-bar').trim(); + document.body.insertAdjacentHTML('afterBegin', aside.outerHTML); + }; + + Learn.prototype.fetchIssueCount = function () { + var issueLink = document.getElementById('issue-count-link'); + if (issueLink) { + var url = issueLink.href.replace('https://github.com', 'https://api.github.com/repos'); + var xhr = new XMLHttpRequest(); + xhr.open('GET', url, true); + xhr.onload = function (e) { + var parsedResponse = JSON.parse(e.target.responseText); + if (parsedResponse instanceof Array) { + var count = parsedResponse.length; + if (count !== 0) { + issueLink.innerHTML = 'This app has ' + count + ' open issues'; + document.getElementById('issue-count').style.display = 'inline'; + } + } + }; + xhr.send(); + } + }; + + redirect(); + getFile('learn.json', Learn); +})(); diff --git a/rss/gopherjs/node_modules/todomvc-common/package.json b/rss/gopherjs/node_modules/todomvc-common/package.json new file mode 100644 index 0000000..2ef2ff9 --- /dev/null +++ b/rss/gopherjs/node_modules/todomvc-common/package.json @@ -0,0 +1,63 @@ +{ + "name": "todomvc-common", + "version": "1.0.2", + "description": "Common TodoMVC utilities used by our apps", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/tastejs/todomvc-common" + }, + "author": { + "name": "TasteJS" + }, + "main": "base.js", + "files": [ + "base.js", + "base.css" + ], + "keywords": [ + "todomvc", + "tastejs", + "util", + "utilities" + ], + "gitHead": "e82d0c79e01687ce7407df786cc784ad82166cb3", + "bugs": { + "url": "https://github.com/tastejs/todomvc-common/issues" + }, + "homepage": "https://github.com/tastejs/todomvc-common", + "_id": "todomvc-common@1.0.2", + "scripts": {}, + "_shasum": "eb3ab61281ac74809f5869c917c7b08bc84234e0", + "_from": "todomvc-common@*", + "_npmVersion": "2.7.4", + "_nodeVersion": "0.12.2", + "_npmUser": { + "name": "sindresorhus", + "email": "sindresorhus@gmail.com" + }, + "dist": { + "shasum": "eb3ab61281ac74809f5869c917c7b08bc84234e0", + "tarball": "http://registry.npmjs.org/todomvc-common/-/todomvc-common-1.0.2.tgz" + }, + "maintainers": [ + { + "name": "sindresorhus", + "email": "sindresorhus@gmail.com" + }, + { + "name": "addyosmani", + "email": "addyosmani@gmail.com" + }, + { + "name": "passy", + "email": "phartig@rdrei.net" + }, + { + "name": "stephenplusplus", + "email": "sawchuk@gmail.com" + } + ], + "directories": {}, + "_resolved": "https://registry.npmjs.org/todomvc-common/-/todomvc-common-1.0.2.tgz" +} diff --git a/rss/gopherjs/node_modules/todomvc-common/readme.md b/rss/gopherjs/node_modules/todomvc-common/readme.md new file mode 100644 index 0000000..7a5de51 --- /dev/null +++ b/rss/gopherjs/node_modules/todomvc-common/readme.md @@ -0,0 +1,15 @@ +# todomvc-common + +> Common TodoMVC utilities used by our apps + + +## Install + +``` +$ npm install --save todomvc-common +``` + + +## License + +MIT © [TasteJS](http://tastejs.com) diff --git a/rss/gopherjs/store/model/model.go b/rss/gopherjs/store/model/model.go new file mode 100644 index 0000000..62f93b9 --- /dev/null +++ b/rss/gopherjs/store/model/model.go @@ -0,0 +1,21 @@ +package model + +// Item represents a single TODO item in the store. +type Item struct { + Title string + Completed bool +} + +// FilterState represents a viewing filter for TODO items in the store. +type FilterState int + +const ( + // All is a FilterState which shows all items. + All FilterState = iota + + // Active is a FilterState which shows only non-completed items. + Active + + // Completed is a FilterState which shows only completed items. + Completed +) diff --git a/rss/gopherjs/store/store.go b/rss/gopherjs/store/store.go new file mode 100644 index 0000000..5f3290e --- /dev/null +++ b/rss/gopherjs/store/store.go @@ -0,0 +1,85 @@ +package store + +import ( + "github.com/drekle/go/rss/gopherjs/actions" + "github.com/drekle/go/rss/gopherjs/dispatcher" + "github.com/drekle/go/rss/gopherjs/store/model" + "github.com/drekle/go/rss/gopherjs/store/storeutil" +) + +var ( + // Items represents all of the TODO items in the store. + Items []*model.Item + + // Filter represents the active viewing filter for items. + Filter = model.All + + // Listeners is the listeners that will be invoked when the store changes. + Listeners = storeutil.NewListenerRegistry() +) + +func init() { + dispatcher.Register(onAction) +} + +// ActiveItemCount returns the current number of items that are not completed. +func ActiveItemCount() int { + return count(false) +} + +// CompletedItemCount returns the current number of items that are completed. +func CompletedItemCount() int { + return count(true) +} + +func count(completed bool) int { + count := 0 + for _, item := range Items { + if item.Completed == completed { + count++ + } + } + return count +} + +func onAction(action interface{}) { + switch a := action.(type) { + case *actions.ReplaceItems: + Items = a.Items + + case *actions.AddItem: + Items = append(Items, &model.Item{Title: a.Title, Completed: false}) + + case *actions.DestroyItem: + copy(Items[a.Index:], Items[a.Index+1:]) + Items = Items[:len(Items)-1] + + case *actions.SetTitle: + Items[a.Index].Title = a.Title + + case *actions.SetCompleted: + Items[a.Index].Completed = a.Completed + + case *actions.SetAllCompleted: + for _, item := range Items { + item.Completed = a.Completed + } + + case *actions.ClearCompleted: + var activeItems []*model.Item + for _, item := range Items { + if !item.Completed { + activeItems = append(activeItems, item) + } + } + Items = activeItems + + case *actions.SetFilter: + Filter = a.Filter + + default: + return // don't fire listeners + } + + Listeners.Fire() +} diff --git a/rss/gopherjs/store/storeutil/storeutil.go b/rss/gopherjs/store/storeutil/storeutil.go new file mode 100644 index 0000000..3419eb3 --- /dev/null +++ b/rss/gopherjs/store/storeutil/storeutil.go @@ -0,0 +1,40 @@ +// Package storeutil contains a ListenerRegistry type. +package storeutil + +// ListenerRegistry is a listener registry. +// The zero value is unfit for use; use NewListenerRegistry to create an instance. +type ListenerRegistry struct { + listeners map[interface{}]func() +} + +// NewListenerRegistry creates a listener registry. +func NewListenerRegistry() *ListenerRegistry { + return &ListenerRegistry{ + listeners: make(map[interface{}]func()), + } +} + +// Add adds listener with key to the registry. +// key may be nil, then an arbitrary unused key is assigned. +// It panics if a listener with same key is already present. +func (r *ListenerRegistry) Add(key interface{}, listener func()) { + if key == nil { + key = new(int) + } + if _, ok := r.listeners[key]; ok { + panic("duplicate listener key") + } + r.listeners[key] = listener +} + +// Remove removes a listener with key from the registry. +func (r *ListenerRegistry) Remove(key interface{}) { + delete(r.listeners, key) +} + +// Fire invokes all listeners in the registry. +func (r *ListenerRegistry) Fire() { + for _, l := range r.listeners { + l() + } +}