This commit is contained in:
Galen Wolfe-Pauly 2016-01-27 11:10:33 -08:00
parent 2e72056fb5
commit 0c9da59537
21 changed files with 0 additions and 4091 deletions

View File

@ -1,34 +0,0 @@
::
::
:::: /hook/hymn/fab/talk/pub/
::
|%
++ cdnj
|= a=wall ^- marl
%+ turn a
|= lib=tape
;script(type "text/javascript", src "//cdnjs.cloudflare.com/ajax/libs/{lib}");
--
::
::::
::
^- manx
;html
;head
;meta(charset "utf-8");
;* %- cdnj :~
"jquery/2.1.1/jquery.js"
"lodash.js/2.4.1/lodash.min.js"
"react/0.13.1/react.js"
==
;script(type "text/javascript", src "/~/as/own/~/at/home/lib/urb.js");
;meta(name "viewport", content "width=device-width, height=device-height, ".
"initial-scale=1.0, user-scalable=0, minimum-scale=1.0, maximum-scale=1.0");
;link(type "text/css", rel "stylesheet", href "/home/pub/work/src/css/main.css");
;title: Work
==
;body
;div#c;
;script(type "text/javascript", src "/home/pub/work/src/js/main.js");
==
==

View File

@ -1,84 +0,0 @@
@font-face {
font-family: "bau";
src: url("http://storage.googleapis.com/urbit-extra/bau.woff");
font-weight: 400;
font-style: normal;
}
@font-face {
font-family: "bau";
src: url("http://storage.googleapis.com/urbit-extra/bau-italic.woff");
font-weight: 400;
font-style: italic;
}
@font-face {
font-family: "bau";
src: url("http://storage.googleapis.com/urbit-extra/bau-medium.woff");
font-weight: 500;
font-style: normal;
}
@font-face {
font-family: "bau";
src: url("http://storage.googleapis.com/urbit-extra/bau-mediumitalic.woff");
font-weight: 500;
font-style: italic;
}
@font-face {
font-family: "bau";
src: url("http://storage.googleapis.com/urbit-extra/bau-bold.woff");
font-weight: 600;
font-style: normal;
}
@font-face {
font-family: "bau";
src: url("http://storage.googleapis.com/urbit-extra/bau-bolditalic.woff");
font-weight: 600;
font-style: italic;
}
@font-face {
font-family: "bau";
src: url("http://storage.googleapis.com/urbit-extra/bau-super.woff");
font-weight: 600;
font-style: normal;
}
@font-face {
font-family: "bau";
src: url("http://storage.googleapis.com/urbit-extra/bau-superitalic.woff");
font-weight: 600;
font-style: italic;
}
@font-face {
font-family: "scp";
src: url("http://storage.googleapis.com/urbit-extra/scp-extralight.woff");
font-weight: 200;
font-style: normal;
}
@font-face {
font-family: "scp";
src: url("http://storage.googleapis.com/urbit-extra/scp-light.woff");
font-weight: 300;
font-style: normal;
}
@font-face {
font-family: "scp";
src: url("http://storage.googleapis.com/urbit-extra/scp-regular.woff");
font-weight: 400;
font-style: normal;
}
@font-face {
font-family: "scp";
src: url("http://storage.googleapis.com/urbit-extra/scp-medium.woff");
font-weight: 500;
font-style: normal;
}
@font-face {
font-family: "scp";
src: url("http://storage.googleapis.com/urbit-extra/scp-bold.woff");
font-weight: 600;
font-style: normal;
}
@font-face {
font-family: "scp";
src: url("http://storage.googleapis.com/urbit-extra/scp-black.woff");
font-weight: 700;
font-style: normal;
}

View File

@ -1,503 +0,0 @@
@font-face {
font-family: "bau";
src: url("http://storage.googleapis.com/urbit-extra/bau.woff");
font-weight: 400;
font-style: normal;
}
@font-face {
font-family: "bau";
src: url("http://storage.googleapis.com/urbit-extra/bau-italic.woff");
font-weight: 400;
font-style: italic;
}
@font-face {
font-family: "bau";
src: url("http://storage.googleapis.com/urbit-extra/bau-medium.woff");
font-weight: 500;
font-style: normal;
}
@font-face {
font-family: "bau";
src: url("http://storage.googleapis.com/urbit-extra/bau-mediumitalic.woff");
font-weight: 500;
font-style: italic;
}
@font-face {
font-family: "bau";
src: url("http://storage.googleapis.com/urbit-extra/bau-bold.woff");
font-weight: 600;
font-style: normal;
}
@font-face {
font-family: "bau";
src: url("http://storage.googleapis.com/urbit-extra/bau-bolditalic.woff");
font-weight: 600;
font-style: italic;
}
@font-face {
font-family: "bau";
src: url("http://storage.googleapis.com/urbit-extra/bau-super.woff");
font-weight: 600;
font-style: normal;
}
@font-face {
font-family: "bau";
src: url("http://storage.googleapis.com/urbit-extra/bau-superitalic.woff");
font-weight: 600;
font-style: italic;
}
@font-face {
font-family: "scp";
src: url("http://storage.googleapis.com/urbit-extra/scp-extralight.woff");
font-weight: 200;
font-style: normal;
}
@font-face {
font-family: "scp";
src: url("http://storage.googleapis.com/urbit-extra/scp-light.woff");
font-weight: 300;
font-style: normal;
}
@font-face {
font-family: "scp";
src: url("http://storage.googleapis.com/urbit-extra/scp-regular.woff");
font-weight: 400;
font-style: normal;
}
@font-face {
font-family: "scp";
src: url("http://storage.googleapis.com/urbit-extra/scp-medium.woff");
font-weight: 500;
font-style: normal;
}
@font-face {
font-family: "scp";
src: url("http://storage.googleapis.com/urbit-extra/scp-bold.woff");
font-weight: 600;
font-style: normal;
}
@font-face {
font-family: "scp";
src: url("http://storage.googleapis.com/urbit-extra/scp-black.woff");
font-weight: 700;
font-style: normal;
}
html,
body,
input,
label,
button {
font-family: "bau";
font-size: 18px;
}
#c {
position: absolute;
top: 0rem;
left: 50%;
width: 34rem;
margin-left: -17rem;
margin-bottom: 12rem;
}
h1 {
font-weight: 500;
}
input,
button,
.input {
outline: none;
border: 0;
}
h1.leader:after {
content: "—";
margin-left: 0.6rem;
}
.ctrl .sort label,
.ctrl .filter label {
text-transform: capitalize;
font-size: 1rem;
letter-spacing: 0.07rem;
}
.item .status {
text-transform: uppercase;
font-size: 0.7rem;
letter-spacing: 0.07rem;
}
.ctrl {
margin-top: 2rem;
}
.ctrl h1 {
font-size: 1rem;
}
.ctrl .sorts,
.ctrl .filters {
margin-left: -0.3rem;
}
.ctrl .sorts {
margin-top: 1rem;
}
.ctrl .sort,
.ctrl .filter label {
font-weight: 500;
}
.ctrl .filter {
line-height: 2rem;
margin-right: 1rem;
vertical-align: middle;
}
.ctrl .sort {
border: 0;
cursor: pointer;
background-color: #000;
color: #fff;
margin-right: 1rem;
}
.ctrl .filter label {
margin-right: 0.3rem;
}
.ctrl .sort label,
.ctrl .filter label {
line-height: 2rem;
}
.ctrl .sort.s-1 label,
.ctrl .sort.s--1 label {
margin-right: 0.3rem;
cursor: pointer;
}
.input-bool {
opacity: 0.3;
}
.input-bool.true,
.input-bool.false {
opacity: 1;
}
.input-bool,
.item .done {
width: 1rem;
height: 1rem;
border: 0.2rem solid #ccc;
padding: 0;
outline: none;
background-color: #fff;
}
.input-bool.true,
.item .done-true {
background-color: #ccc;
}
.items {
margin-top: 2rem;
margin-left: -2rem;
}
.item {
display: block;
min-height: 3rem;
max-height: 3rem;
margin-bottom: 1.5rem;
width: 36rem;
overflow: hidden;
background-color: #fff;
transition: max-height 200ms linear;
}
.item .description,
.item .discussion {
transition: visibility 0s linear 200ms;
visibility: hidden;
}
.item.expand {
max-height: 24rem;
transition: max-height 200ms linear;
}
.item.expand .description,
.item.expand .discussion {
transition: visibility 0s;
visibility: visible;
}
.item .expand {
margin-left: 2rem;
cursor: pointer;
transform-origin: 6px 12px;
transition: transform 200ms linear;
-webkit-transform-origin: 6px 12px;
}
.item.expand .expand {
transform: rotate(90deg);
-webkit-transform: rotate(90deg);
transition: transform 200ms linear;
}
.item .header {
display: block;
width: 100%;
height: 1rem;
overflow: hidden;
white-space: nowrap;
}
.item .sort,
.item .title,
.item .date,
.item .tags,
.item .comment {
line-height: 2rem;
}
.item .sort,
.item .date {
font-family: 'scp';
}
.item .creator,
.item .audience,
.item .date,
.item .ship {
font-size: 0.7rem;
}
.item .sort {
opacity: 0;
width: 1rem;
height: 0.2rem;
background-color: #e0e0e0;
border-top: 0.2rem solid #ccc;
border-bottom: 0.2rem solid #ccc;
margin: 0 0.5rem;
margin-top: -0.6rem;
}
.item:hover .sort {
opacity: 1;
}
.item .done {
margin-top: 0.5rem;
}
.item .creator,
.item .audience,
.item .date,
.item .ship {
text-transform: uppercase;
height: 1rem;
letter-spacing: 0.07rem;
}
.item .creator {
max-width: 7rem;
margin: 0 1rem 0 2rem;
}
.item .label {
font-style: italic;
color: #555;
}
.item:hover .action-true .label,
.item .action {
display: none;
}
.item:hover .action-true .action {
display: inline-block;
}
.item .audience {
margin-left: 2.2rem;
width: 24rem;
}
.item .audience .input {
background-color: transparent;
}
.item .title,
.item .date,
.item .tags {
overflow: hidden;
white-space: nowrap;
height: 2rem;
}
.item .title {
width: 16rem;
margin-left: 0.5rem;
}
.item .date,
.item .tags {
max-width: 6rem;
min-width: 6rem;
}
.item .description,
.item .discussion {
line-height: 2rem;
margin: 0.5rem 0 0.5rem 2rem;
}
.item .discussion {
max-height: 9rem;
overflow-x: hidden;
overflow-y: scroll;
}
.item .description textarea {
min-width: 32rem;
min-height: 6rem;
}
.item .comp {
width: 3rem;
opacity: 0;
}
.item .comp .a {
display: block;
font-size: 0.7rem;
font-weight: 500;
line-height: 0.9rem;
}
.item:hover .comp {
opacity: 1;
}
.item .submit {
font-size: 0.7rem;
font-weight: 500;
display: inline-block;
background-color: #b9b9b9;
letter-spacing: 0.07rem;
color: #fff;
border: 0;
padding: 0.6rem 0.3rem;
margin-top: 0.5rem;
}
.comment {
margin-bottom: 1rem;
}
.comment .date,
.comment .ship {
max-width: 12rem;
margin-right: 1rem;
}
.comment .body {
margin-top: 1rem;
}
.comment.new .input {
max-width: 31rem;
margin-top: 1rem;
}
/* global */
.top {
vertical-align: top;
}
.ib {
display: inline-block;
}
.hidden {
display: none;
}
.a {
display: inline;
cursor: pointer;
text-decoration: underline;
}
.input {
padding: 0 0.6rem 0 0.3rem;
background-color: #ececec;
border: 0;
font: inherit;
resize: none;
-webkit-user-select: text;
user-select: text;
}
textarea:focus,
.input:focus {
background-color: #e6e6e6;
}
.invalid .input {
color: #f00;
}
.caret.left {
border-left: 12px solid #000;
border-top: 9px solid transparent;
border-right: 9px solid transparent;
border-bottom: 9px solid transparent;
margin-top: 0.4rem;
}
.s-1 .caret,
.caret.down {
border-left: 6px solid transparent;
border-top: 6px solid #fff;
border-right: 6px solid transparent;
border-bottom: 6px solid transparent;
}
.s--1 .caret,
.caret.up {
border-left: 6px solid transparent;
border-top: 6px solid transparent;
border-right: 6px solid transparent;
border-bottom: 6px solid #fff;
}
.hr1 {
height: 0.2rem;
width: 6rem;
background-color: #ccc;
margin-left: 2.2rem;
}
.hr2 {
height: 0.1rem;
width: 3rem;
background-color: #ccc;
}
@media only screen and (min-device-width: 320px) and (max-device-width: 480px) {
#c {
width: 100%;
margin: 0;
left: 0.6rem;
}
h1 {
font-size: 1rem;
}
input,
textarea {
-webkit-appearance: none;
border-radius: 0;
}
.input {
padding: 0 0.3rem 0 0.1rem;
}
.ctrl {
margin-top: 1rem;
}
.ctrl .sorts,
.ctrl .filters {
margin-left: 0;
}
.ctrl .filters .done label {
display: none;
}
.ctrl .sort {
padding: 0.2rem 0.3rem;
}
.ctrl .filter {
line-height: 1.3rem;
}
.ctrl .sort label,
.ctrl .filter label {
font-size: 0.7rem;
letter-spacing: 0.05rem;
line-height: 1.3rem;
}
.items {
margin-left: 0;
}
.item {
width: auto;
}
.item .sort {
display: none;
}
.item .title,
.item .date,
.item .tags {
min-width: 4rem;
max-width: 6rem;
overflow: hidden;
margin-left: 0.3rem;
}
.item .expand {
float: right;
margin-right: 0.6rem;
}
.item .description,
.item .discussion {
margin: 0.5rem 0;
}
.item .description textarea {
min-width: 18rem;
margin: 0;
}
.item .comment.new {
max-width: 18rem;
}
.item .submit {
line-height: 1.6rem;
letter-spacing: 0;
}
}

View File

@ -1,352 +0,0 @@
//
// fonts first
//
@import 'fonts'
html
body
input
label
button
font-family "bau"
font-size 18px
#c
position absolute
top 0rem
left 50%
width 34rem
margin-left -17rem
margin-bottom 12rem
h1
font-weight 500
input
button
.input
outline none
border 0
h1.leader:after
content ""
margin-left .6rem
.ctrl .sort label
.ctrl .filter label
text-transform capitalize
font-size 1rem
letter-spacing .07rem
.item .status
text-transform uppercase
font-size .7rem
letter-spacing .07rem
.ctrl
margin-top 2rem
h1
font-size 1rem
.sorts
.filters
margin-left -.3rem
.sorts
margin-top 1rem
.sort
.filter label
font-weight 500
.filter
line-height 2rem
margin-right 1rem
vertical-align middle
.sort
border 0
cursor pointer
background-color #000
color #fff
margin-right 1rem
.filter label
margin-right .3rem
.sort label
.filter label
line-height 2rem
.sort.s-1
.sort.s--1
label
margin-right .3rem
cursor pointer
.input-bool
opacity .3
.input-bool.true
.input-bool.false
opacity 1
.input-bool
.item .done
width 1rem
height 1rem
border .2rem solid #ccc
padding 0
outline none
background-color #fff
.input-bool.true
.item .done-true
background-color #ccc
.items
margin-top 2rem
margin-left -2rem
.item
display block
min-height 3rem
max-height 3rem
margin-bottom 1.5rem
width 36rem
overflow hidden
background-color #fff
transition max-height 200ms linear
.description
.discussion
transition visibility 0s linear 200ms
visibility hidden
&.expand
max-height 24rem
transition max-height 200ms linear
.description
.discussion
transition visibility 0s
visibility visible
.expand
margin-left 2rem
cursor pointer
transform-origin 6px 12px
transition transform 200ms linear
-webkit-transform-origin 6px 12px
&.expand .expand
transform rotate(90deg)
-webkit-transform rotate(90deg)
transition transform 200ms linear
.header
display block
width 100%
height 1rem
overflow hidden
white-space nowrap
.sort
.title
.date
.tags
.comment
line-height 2rem
.sort
.date
font-family 'scp'
.creator
.audience
.date
.ship
font-size .7rem
.sort
opacity 0
width 1rem
height .2rem
background-color #e0e0e0
border-top .2rem solid #ccc
border-bottom .2rem solid #ccc
margin 0 .5rem
margin-top -.6rem
&:hover .sort
opacity 1
.done
margin-top .5rem
.creator
.audience
.date
.ship
text-transform uppercase
height 1rem
letter-spacing .07rem
.creator
max-width 7rem
margin 0 1rem 0 2rem
.label
font-style italic
color #555
&:hover .action-true .label
.action
display none
&:hover .action-true .action
display inline-block
.audience
margin-left 2.2rem
width 24rem
.audience .input
background-color transparent
.title
.date
.tags
overflow hidden
white-space nowrap
height 2rem
.title
width 16rem
margin-left .5rem
.date
.tags
max-width 6rem
min-width 6rem
.description
.discussion
line-height 2rem
margin .5rem 0 .5rem 2rem
.discussion
max-height 9rem
overflow-x hidden
overflow-y scroll
.description textarea
min-width 32rem
min-height 6rem
.comp
width 3rem
opacity 0
.a
display block
font-size .7rem
font-weight 500
line-height .9rem
&:hover .comp
opacity 1
.submit
font-size .7rem
font-weight 500
display inline-block
background-color #b9b9b9
letter-spacing .07rem
color #fff
border 0
padding .6rem .3rem
margin-top .5rem
.comment
margin-bottom 1rem
.date
.ship
max-width 12rem
margin-right 1rem
.body
margin-top 1rem
&.new .input
max-width 31rem
margin-top 1rem
/* global */
.top
vertical-align top
.ib
display inline-block
.hidden
display none
.a
display inline
cursor pointer
text-decoration underline
.input
padding 0 .6rem 0 .3rem
background-color #ececec
border 0
font inherit
resize none
-webkit-user-select text
user-select text
textarea:focus
.input:focus
background-color #e6e6e6
.invalid .input
color red
.caret.left
border-left 12px solid #000
border-top 9px solid transparent
border-right 9px solid transparent
border-bottom 9px solid transparent
margin-top .4rem
.s-1 .caret
.caret.down
border-left 6px solid transparent
border-top 6px solid #fff
border-right 6px solid transparent
border-bottom 6px solid transparent
.s--1 .caret
.caret.up
border-left 6px solid transparent
border-top 6px solid transparent
border-right 6px solid transparent
border-bottom 6px solid #fff
.hr1
height .2rem
width 6rem
background-color #ccc
margin-left 2.2rem
.hr2
height .1rem
width 3rem
background-color #ccc
@import 'mobile'

View File

@ -1,86 +0,0 @@
/* computers --------*/
// @media only screen and (min-width: 1024px)
/* laptops / small screens ----------- */
// @media only screen and (max-width: 1170px)
/* tablets + phones ----------- */
// @media only screen and (min-width: 320px) and (max-width: 1024px)
/* phones portrait and landscape ----------- */
@media only screen and (min-device-width: 320px) and (max-device-width: 480px)
#c
width 100%
margin 0
left .6rem
h1
font-size 1rem
input
textarea
-webkit-appearance none
border-radius 0
.input
padding 0 .3rem 0 .1rem
.ctrl
margin-top 1rem
.ctrl .sorts
.ctrl .filters
margin-left 0
.ctrl .filters .done label
display none
.ctrl .sort
padding .2rem .3rem
.ctrl .filter
line-height 1.3rem
.ctrl .sort label
.ctrl .filter label
font-size .7rem
letter-spacing .05rem
line-height 1.3rem
.items
margin-left 0
.item
width auto
.item .sort
display none
.item .title
.item .date
.item .tags
min-width 4rem
max-width 6rem
overflow hidden
margin-left .3rem
.item .expand
float right
margin-right .6rem
.item .description
.item .discussion
margin .5rem 0
.item .description textarea
min-width 18rem
margin 0
.item .comment.new
max-width 18rem
.item .submit
line-height 1.6rem
letter-spacing 0

View File

@ -1,78 +0,0 @@
Dispatcher = require '../dispatcher/Dispatcher.coffee'
Persistence = require '../persistence/Persistence.coffee'
{uuid32} = require '../util.coffee'
module.exports =
newItem: ({before,after},_item={}) ->
item =
date_created: Date.now()
date_modified: Date.now()
creator: window.urb.ship
version: -1
id: _item.id ? uuid32()
date_due: _item.date_due ? null
done: _item.done ? null
doer: _item.doer ? null
tags: _item.tags ? []
title: _item.title ? ''
description: _item.description ? ''
discussion: _item.discussion ? []
audience: _item.audience ?
[window.util.talk.mainStationPath window.urb.ship]
if item.date_due or item.title or item.description
item.version++
Persistence.put new:item
Dispatcher.handleViewAction {type:'newItem', before, after, item}
setItem: ({id,version},key,val) ->
version += 1
key = key.split('_').join '-'
set = "#{key}": val
Persistence.put old:{id,dif:{set}}
Dispatcher.handleViewAction {type:'updateItem',id,version,key,val}
ownItem: ({id,version},act) ->
version += 1
Persistence.put old:{id,version,dif:doer:"#{act}":null}
removeItem: ({id,version}) ->
if version >= 0
Persistence.put old:{id,dif:set:audience:[]}
Dispatcher.handleViewAction {type:'archiveItem',id}
setAudience: ({id},to) ->
Persistence.put old:{id,dif:set:audience:to}
Dispatcher.handleViewAction {type:'setAudienece',id,to}
addComment: ({id,version},val) ->
version += 1
Persistence.put old:{id,version,dif:add:comment:val}
setFilter: (key,val,filters) ->
Dispatcher.handleViewAction {type:'setFilter', key,val}
filters[key] = val
Persistence.setLocal 'filters',filters
setSort: (key,val,sorts) ->
Dispatcher.handleViewAction {type:'setSort',key,val}
sorts[key] = val
Persistence.setLocal 'sorts',sorts
moveItem: (list,to,from) ->
Persistence.put {sort:list}
Dispatcher.handleViewAction {type:'moveItems',list,to,from}
getLocal: (key)->
Persistence.getLocal key,(e,r) ->
new Error(e) if e
return if r is null
obj =
type:"load#{key[0].toUpperCase()+key.slice(1)}"
obj[key] = r
Dispatcher.handleServerAction obj
listenList: (type)->
Persistence.subscribe type, (err,d)->
if d?
{sort,tasks} = d.data
Dispatcher.handleServerAction {type:"getData",sort,tasks}

View File

@ -1,95 +0,0 @@
recl = React.createClass
rece = React.createElement
{div,textarea} = React.DOM
WorkActions = require '../actions/WorkActions.coffee'
module.exports = recl
displayName: 'Field'
getInitialState: -> invalid:no
shouldComponentUpdate: (props)->
while @oldValue?.length
if @oldValue[0] is props.defaultValue
return false
else @oldValue.shift()
true
render: ->
className = "#{@props.className ? @props._key} field ib"
if @state.invalid then className += " invalid"
props = _.extend {}, @props, {
@onKeyUp
ref:'input'
defaultValue: @props.render @props.defaultValue
className: 'input ib'
}
div {className},
if @props.textarea then (textarea props)
else
props.contentEditable = true
props.dangerouslySetInnerHTML =
__html: $('<div>').text(props.defaultValue).html()
(div props)
onKeyUp: (e) ->
$t = $(e.target).closest '.field'
_val = @getVal()
if @props.item.ghost and _val is ""
return
val = @parse _val
unless @validate val
@setState invalid:yes
return
@setState invalid:no
unless @equal @props.defaultValue, val
@oldValue ?= []
@oldValue.push val
if @to then clearTimeout @to
@to = setTimeout =>
{item,_key,index} = @props
if item.version >= 0
WorkActions.setItem item, _key, val
else WorkActions.newItem {},
id: item.id
tags: item.tags
audience: item.audience
"#{_key}": val
,1000
getVal: ->
if @props.textarea
$(@refs.input.getDOMNode()).val()
else $(@refs.input.getDOMNode()).text()
parse: (text)-> switch @props._key
when 'tags' then text.trim().split(" ")
when 'audience' then text.trim().split(" ").map (a) -> "~#{a}".toLowerCase()
when 'date_due'
d = text.slice(1).replace(/\./g, "-")
return NaN if d.length < 8
new Date(d).valueOf()
else text
equal: (vol=(@parse ""),val) -> switch @props._key
when 'tags', 'audience'
(_.xor(vol,val).length is 0)
when 'date_due'
vol.valueOf() is val
else vol is val
validate: (val) -> switch @props._key
when 'date_due'
!isNaN(val)
when 'audience'
for a in val
[ship,station,rest...] = a.split("/")
return no unless (rest.length is 0) and ship and station
return no if ship[0] isnt "~"
return no if ship < 3
return no if station < 3
yes
else yes

View File

@ -1,60 +0,0 @@
recl = React.createClass
rece = React.createElement
{div,h1,label,button} = React.DOM
module.exports = recl
onClick: (e) ->
switch @props.filters['done']
when null
b = true
when true
b = false
when false
b = null
@props.onChange 'done',b
onKeyDown: (e) ->
if e.keyCode is 13
e.stopPropagation()
e.preventDefault()
onKeyUp: (e) -> @change e
onBlur: (e) -> @change e
change: (e) ->
$t = $(e.target).closest('.filter')
txt = $t.find('.input').text().trim()
key = $t.attr('data-key')
if txt.length is 0 then txt = null
else switch key
when 'creator' then txt = "~#{txt}"
when 'audience' then txt = txt.split " "
when 'tags' then txt = [txt]
@props.onChange key,txt
fields: [ {filter:'done', key:'done', title: ''},
{filter:'owned', key:'creator', title: 'Owner:'},
{filter:'doer', key:'doer', title: 'Doer:'}
{filter:'tag', key:'tags', title: 'Tag:'},
{filter:'channel', key:'audience', title: 'Audience:'},
]
render: ->
(div {className:'filters'}, @fields.map ({filter,key,title})=>
txt = @props.filters[key]
txt = txt?.replace(/\~/g,"") if key is 'creator'
(div {key, 'data-key':key, className:"#{filter} filter ib"},
(label {}, title)
switch filter
when 'done'
(button {
className:'input-bool ib '+@props.filters[key],
@onClick
},"")
else
(div {
contentEditable:true
className:'input ib'
@onKeyDown
@onKeyUp
@onBlur
},txt)
))

View File

@ -1,162 +0,0 @@
recl = React.createClass
rece = React.createElement
{div,textarea,button} = React.DOM
WorkActions = require '../actions/WorkActions.coffee'
Field = require './FieldComponent.coffee'
module.exports = recl
displayName: 'Item'
onDragStart: (e) ->
unless @props.draggable
e.preventDefault()
return
$t = $(e.target)
@dragged = $t.closest('.item')
e.dataTransfer.effectAllowed = 'move'
e.dataTransfer.setData 'text/html',e.currentTarget
@props._dragStart e,@
onDragEnd: (e) -> @props._dragEnd e,@
onKeyDown: (e) ->
@props.title_keyDown e,@
kc = e.keyCode
switch kc
# tab - expand
when 9
if @state.expand is false
@setState {expand:true}
# esc - collapse
when 27
@setState {expand:false}
if (kc is 9 and @state.expand is false) or (kc is 27)
e.preventDefault()
return
onFocus: (e) -> @props._focus e,@
_markDone: (e) ->
WorkActions.setItem @props.item,'done',(not (@props.item.done is true))
getStatus: -> switch @props.item.doer
when window.urb.ship then "owned"
when undefined then ""
when null then "available"
else "taken: ~"+@props.item.doer
getAction: -> switch @props.item.doer
when null
action = "claim"
when window.urb.ship
action = "release"
else ""
_changeStatus: (e) ->
return if @props.item.status is 'released'
if @props.item.status is 'accepted' and
@formatCreator(@props.item.creator) isnt window.urb.ship
return
WorkActions.ownItem @props.item,@getAction()
_submitComment: (e) ->
$input = $(e.target).closest('.item').find('.comment .input')
val = $input.text()
return if val.length is 0
WorkActions.addComment @props.item,val
$input.text('')
formatDate: (d,l) ->
unless d
return ""
_d = "~#{d.getFullYear()}.#{(d.getMonth()+1)}.#{d.getDate()}"
if l
_d += "..#{d.getHours()}.#{d.getMinutes()}.#{d.getSeconds()}"
_d
formatCreator: (o="") -> o.replace /\~/g,""
formatAudience: (a=[]) -> @formatCreator a.join(" ")
getInitialState: -> {expand:false}
renderField: (_key,props,render=_.identity)->
{item,index} = @props
defaultValue = item[_key]
rece Field, $.extend props, {render,_key,defaultValue,item,index}
renderTopField: (key,props,format)->
_props = _.extend props,{className:"#{props.className ? key} top"}
@renderField key,_props,format
componentDidMount: ->
setInterval =>
$('.new.comment .date').text @formatDate new Date
, 1000
render: ->
itemClass = 'item'
if @state.expand then itemClass += ' expand'
discussion = _.clone @props.item.discussion ? []
discussion.reverse()
status = @getStatus()
action = @getAction()
(div {
className:itemClass
draggable:true
@onDragStart,@onDragEnd
},
(div {
className:'header'
},
(div {className:'creator ib'}, @formatCreator(@props.item.creator))
(div {
className:'status ib action-'+(action.length > 0)
'data-key':'status'
onClick:@_changeStatus
},
(div {className:'label'}, status)
(div {className:'action a'}, action)
)
(@renderField 'audience', {}, @formatAudience)
)
(div {className:'sort ib'}, '')
(button {className:'done ib done-'+(@props.item.done is true), onClick:@_markDone}, '')
(@renderTopField 'title', {@onFocus,@onKeyDown})
(@renderTopField 'date_due', {className:'date'}, @formatDate)
(@renderTopField 'tags', {}, (tags=[])-> tags.join(" "))
(div {
className:'expand ib',
onClick: (e) => @setState {expand:!@state.expand}
}, (div {className:'caret left'},"")
)
(@renderField 'description',textarea: yes)
(div {className:"hr"},"")
if discussion?
(div {className:"discussion"},
(div {className:"comments"}, discussion.map (slug) =>
(div {className:'comment',key:slug.date},
(div {className:'hr2'},"")
(div {className:'ship ib'}, slug.ship)
(div {className:'date ib'}, @formatDate slug.date,true)
(div {className:'body'}, slug.body)
)
),
(div {className:'new comment'},
(div {className:'hr2'},"")
(div {className:'ship ib'}, window.urb.ship)
(div {className:'date ib'}, @formatDate new Date)
(div {
contentEditable:true,
className:'input'})
(button {className:'submit',onClick:@_submitComment},'Post')
)
)
)

View File

@ -1,175 +0,0 @@
recl = React.createClass
rece = React.createElement
{div,h1,input,textarea} = React.DOM
WorkStore = require '../stores/WorkStore.coffee'
WorkActions = require '../actions/WorkActions.coffee'
ItemComponent = require './ItemComponent.coffee'
ListeningComponent = require './ListeningComponent.coffee'
FilterComponent = require './FilterComponent.coffee'
SortComponent = require './SortComponent.coffee'
module.exports = recl
displayName: 'List'
stateFromStore: -> window.canSort = WorkStore.canSort(); {
list:WorkStore.getList()
noNew:WorkStore.noNew()
canSort:WorkStore.canSort()
fulllist:WorkStore.getFullList()
sorts:WorkStore.getSorts()
filters:WorkStore.getFilters()
expand:false
updated:WorkStore.getUpdated()
}
getInitialState: -> @stateFromStore()
_onChangeStore: -> @setState @stateFromStore()
alias: ->
@$el = $ @getDOMNode()
@$items = @$el.find('.items').children()
_focus: (e,i) -> @setState {selected:i.props.index}
_dragStart: (e,i) -> @dragged = i.dragged
_dragEnd: (e,i) ->
from = Number @dragged.closest('.item-wrap').attr('data-index')
to = Number @over.closest('.item-wrap').attr('data-index')
if from<to then to--
if @drop is 'after' then to++
sort = _.clone @state.list
sort.splice to, 0, sort.splice(from,1)[0]
list = (id for {id,version} in sort when (version ? -1) >= 0)
WorkActions.moveItem list, to, from
@dragged.removeClass 'hidden'
@placeholder.remove()
_dragOver: (e,i) ->
e.preventDefault()
$t = $(e.target).closest('.item')
if $t.hasClass 'placeholder' then return
if $t.length is 0 then return
@over = $t
if not @dragged.hasClass('hidden') then @dragged.addClass 'hidden'
if (e.clientY - $t[0].offsetTop) < ($t[0].offsetHeight / 2)
@drop = 'before'
@placeholder.insertBefore $t
else
@drop = 'after'
@placeholder.insertAfter $t
title_keyDown: (e,i) -> switch e.keyCode
# enter - add new
when 13
e.preventDefault()
return if @state.noNew
{item} = i.props
after = null; before = null
if window.getSelection().getRangeAt(0).endOffset is 0
ins = @state.selected
before = item.id
else
after = item.id
ins = @state.selected+1 # XX consolidate
@setState {selected:ins,select:true}
unless item.ghost
{tags,audience} = item
item = {tags,audience}
WorkActions.newItem {before,after}, item
# backspace - remove if at 0
when 8
if (window.getSelection().getRangeAt(0).endOffset is 0)
e.preventDefault()
if (e.target.innerText.length is 0)
if @state.selected isnt 0
@setState {selected:@state.selected-1,select:"end"}
WorkActions.removeItem i.props.item
else if ({index} = i.props; index > 0) and
(prev = @state.list[i.props.index - 1]; prev.version < 0)
WorkActions.removeItem prev
# up
when 38
e.preventDefault()
last = @state.selected-1
if last<0 then last = @state.list.length-1
@$items.eq(last).find('.title .input').focus()
@setState {select:"end"}
# down
when 40
e.preventDefault()
next = @state.selected+1
if next is @state.list.length then next = 0
@$items.eq(next).find('.title .input').focus()
@setState {select:"end"}
_changeListening: ->
_changeFilter: (key,val) -> WorkActions.setFilter key,val,@state.filters
_changeSort: (key,val) -> WorkActions.setSort key,val,@state.sorts
componentDidMount: ->
@placeholder = $ "<div class='item placeholder'><div class='sort'>x</div></div>"
WorkStore.addChangeListener @_onChangeStore
WorkActions.listenList @props.list
WorkActions.getLocal 'filters'
WorkActions.getLocal 'sorts'
@alias()
componentDidUpdate: (_props,_state)->
@alias()
if @state.select
$title = @$items.eq(@state.selected).find('.title .input')
if @state.selected?
$title.focus()
if @state.select is "end"
r = window.getSelection().getRangeAt(0)
r.setStart $title[0],0
r.setEnd $title[0],0
s = window.getSelection()
s.removeAllRanges()
s.addRange r
@setState {select:false}
render: ->
(div {},
(div {className:'ctrl'},
rece(ListeningComponent, {
listening:@state.listening
onChange:@_changeListening
})
(div {className:'transforms'},
rece(FilterComponent, {
filters:@state.filters
onChange:@_changeFilter
})
rece(SortComponent, {
sorts:@state.sorts
onChange:@_changeSort
})
)
)
(div {
className:'items'
onDragOver:@_dragOver
}, _.map @state.list,(item,index) =>
className = "item-wrap"
key = item.id
draggable = @state.canSort
if item.ghost
className += " ghost"
draggable = false
(div {className,key,'data-index':index},
rece(ItemComponent,{
item
index
draggable
@_focus
@title_keyDown
@_dragStart
@_dragEnd})
)
)
)

View File

@ -1,7 +0,0 @@
recl = React.createClass
rece = React.createElement
{div,h1,input,textarea} = React.DOM
module.exports = recl
render: ->
(div {className:'listening'}, "")

View File

@ -1,27 +0,0 @@
recl = React.createClass
rece = React.createElement
{div,h1,button,label} = React.DOM
module.exports = recl
onClick: (e) ->
$t = $(e.target).closest '.sort'
key = $t.attr('data-key')
sor = Number $t.attr 'data-state'
if sor is 0 then sor = 1
else if sor is 1 then sor = -1
else if sor is -1 then sor = 0
@props.onChange key,sor
render: ->
(div {className:'sorts'}, _.map @props.sorts,(state,key) =>
(button {
key
@onClick
'data-key':key
'data-state':state
className:"sort s-#{state}"
},
(label {}, key)
(div {className:'caret ib'}, '')
)
)

View File

@ -1,12 +0,0 @@
recl = React.createClass
rece = React.createElement
{div,h1} = React.DOM
ListComponent = require './ListComponent.coffee'
module.exports = recl
render: ->
(div {},
(h1 {className:'leader'}, "Work")
(rece(ListComponent,{list:'upcoming'}))
)

View File

@ -1,13 +0,0 @@
Dispatcher = require('flux').Dispatcher
module.exports = _.merge new Dispatcher(), {
handleServerAction: (action) ->
@dispatch
source: 'server'
action: action
handleViewAction: (action) ->
@dispatch
source: 'view'
action: action
}

View File

@ -1,5 +0,0 @@
WorkComponent = require './components/WorkComponent.coffee'
window.util = _.extend window.util || {}, require './util.coffee'
$ ->
React.render React.createElement(WorkComponent),$('#c')[0]

File diff suppressed because it is too large Load Diff

View File

@ -1,17 +0,0 @@
{
"name": "urbit-work",
"version": "0.0.0",
"repository": {
"type":"git",
"url":"https://github.com/urbit/urbit"
},
"description": "urbit work frontend",
"main": "main.js",
"dependencies": {
"coffeeify": "~0.7.0",
"flux": "~2.0.1",
"lodash": "~2.4.1",
"moment-timezone": "~0.2.4",
"object-assign": "^1.0.0"
}
}

View File

@ -1,21 +0,0 @@
urb.appl = 'work'
listeners = {}
cache = null
urb.bind "/repo", (err,dat)->
if err
document.write err
else
cache = dat
(cb null,dat) for k,cb of listeners
module.exports =
put: (update,cb) -> urb.send(update,{mark:'work-command'},cb)
subscribe: (key,cb) ->
listeners[key] = cb
(cb null,cache) if cache?
setLocal: (key,val) -> window.localStorage.setItem key,JSON.stringify val
getLocal: (key,cb) ->
try
out = JSON.parse window.localStorage.getItem key
cb null,out
catch e
cb e

View File

@ -1,153 +0,0 @@
EventEmitter = require('events').EventEmitter
assign = require 'object-assign'
Dispatcher = require '../dispatcher/Dispatcher.coffee'
{uuid32} = require '../util.coffee'
_tasks = {}
_list = []
_listening = []
_updated = Date.now()
_filters =
done:null
creator:null
doer:null
audience:null
tags:null
_sorts =
sort:0
title:0
creator:0
date_due:0
_ghost = id:uuid32()
WorkStore = assign {},EventEmitter.prototype,{
emitChange: -> @emit 'change'
addChangeListener: (cb) -> @on 'change', cb
removeChangeListener: (cb) -> @removeListener "change", cb
getData: ({sort,tasks})->
sort.map (id,index)=>
unless _tasks[id]
_list.splice index, 0, id
if !tasks[id]
console.log "lost", id
else if !_tasks[id] or tasks[id].version > _tasks[id].version
_tasks[id] = @itemFromData tasks[id], index
_updated = Date.now()
getUpdated: -> _updated
getFullList: -> _list
getList: (key) ->
list = []
for id in _list
task = _tasks[id]
if !task? or task.archived
continue
add = true
for _k,_v of _filters
if _v is null then continue
c = task[_k]
add = switch _k
when 'tags', 'audience'
_.intersection(c,_v).length isnt 0
when 'creator'
c is _v.replace(/\~/g, "")
when 'done'
!!c is _v
else c is _v
break unless add
if add
list.push task
if _.uniq(_.values(_sorts)).length > 1
for k,v of _sorts
if v isnt 0
break
list = _.sortBy list,k,k
if v is -1 then list.reverse()
unless @noNew()
ghost = $.extend {ghost:true,version:-1}, _ghost
if _filters.tags then ghost.tags = _filters.tags
if _filters.audience then ghost.audience = _filters.audience
list.push ghost
list
newItem: ({before,after,item}) ->
if before
index = _list.indexOf before
if index is -1 then index = null
if after
index = 1 + _list.indexOf after
if index is 0 then index = null
index ?= _list.length
if item.id is _ghost.id
_ghost.id = uuid32()
unless _tasks[item.id]?
_list.splice index,0,item.id
else if _tasks[item.id].version >= 0
throw new Error "Collision: already have #{item.id}"
_tasks[item.id] = @itemFromData item, index
loadFilters: ({filters}) ->
console.log 'filters'
console.log filters
_filters = filters
getFilters: -> _filters
setFilter: ({key,val}) ->
_filters[key] = val
loadSorts: ({sorts}) ->
console.log 'load sorts'
console.log sorts
_sorts = sorts
getSorts: -> _sorts
setSort: ({key,val}) ->
for k,v of _sorts
_sorts[k] = 0
_sorts[key] = val
canSort: ->
for k,v of _sorts
if k is "sort" and v is 1
return true
else if v isnt 0
return false
true
noNew: ->
(_filters.done is true) or
_filters.creator? and _filters.owner isnt urb.ship
itemFromData: (item,index=0)->
_item = _.extend {sort:index}, item
_item.date_modified = new Date item.date_modified
_item.date_created = new Date item.date_created
_item.date_due = new Date item.date_due if item.date_due?
_item.done = new Date item.done if item.done?
_item.discussion = item.discussion.map ({ship,body,date}) ->
{ship,body,date: new Date date}
_item
moveItems: ({list,to,from}) ->
_tasks[_list[from]].sort = _tasks[_list[to]].sort
_list = list
setAudience: ({id,to})-> _tasks[id].audience = to
archiveItem: ({id})-> _tasks[id].archived = true
updateItem: ({id,version,key,val})->
_tasks[id].version = version
_tasks[id].done = val if key is 'done'
}
WorkStore.setMaxListeners 100
WorkStore.dispatchToken = Dispatcher.register (p) ->
a = p.action
if WorkStore[a.type]
WorkStore[a.type] a
WorkStore.emitChange()
module.exports = WorkStore

View File

@ -1,38 +0,0 @@
module.exports =
uuid32: ->
vals = for i in [0..5]
str = Math.ceil(Math.random()*10000000).toString(32)
("00000"+str).substr(-5,5)
vals.unshift Math.ceil(Math.random()*8)
"0v" + vals.join '.'
getScroll: ->
@writingPosition = $('#c').outerHeight(true)+$('#c').offset().top-$(window).height()
setScroll: ->
window.util.getScroll()
$(window).scrollTop($("#c").height())
isScrolling: ->
if not window.util.writingPosition
window.util.getScroll()
return ($(window).scrollTop()+$('#writing').outerHeight() < window.util.writingPosition)
checkScroll: ->
if window.util.isScrolling()
$('body').addClass 'scrolling'
else
$('body').removeClass 'scrolling'
talk:
mainStations: ["court","floor","porch"]
mainStationPath: (user) -> "~#{user}/#{window.util.talk.mainStation(user)}"
mainStation: (user) ->
if not user then user = window.urb.user
switch user.length
when 3
return "court"
when 6
return "floor"
when 13
return "porch"

View File

@ -1,8 +0,0 @@
0v0_~1999.1.1 2~1999.1.2 ~1999.5.20
Tagged!
Yoooo
~fyr>~zod
Testin
~doznec ~2015.1.3
how long has
this been around?