33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/css/Menu.css:
--------------------------------------------------------------------------------
1 | .menu {
2 | background-color: white; padding: 10px 0px; position: absolute; top: 0px; max-height: 0px; overflow: hidden; transform: translate(-100%, -30px); pointer-events: none;
3 | box-shadow: 0px 2px 8px rgba(0,0,0,0.1); border-radius: 2px; opacity: 0; transition: opacity 0.2s ease-out, transform 1s ease-out, max-height 0.2s ease-in-out; z-index: 99;
4 | display: inline-block; z-index: 999; margin-top: 50px;
5 | }
6 | .menu-right { left: 100% }
7 | .menu.visible { opacity: 1; max-height: 350px; transform: translate(-100%, 0px); transition: opacity 0.1s ease-out, transform 0.3s ease-out, max-height 0.3s ease-in-out; pointer-events: all }
8 |
9 | .menu-item { display: block; text-decoration: none; color: black; padding: 6px 24px; transition: all 0.2s; border-bottom: none; font-weight: normal; padding-left: 32px; white-space: nowrap; font-size: 16px }
10 | .menu-item-separator { margin-top: 3px; margin-bottom: 3px; border-top: 1px solid #eee }
11 |
12 | .menu-item:hover { background-color: #F6F6F6; transition: none; color: inherit; cursor: pointer; color: black; text-decoration: none }
13 | .menu-item:active, .menu-item:focus { background-color: #AF3BFF; color: white; transition: none }
14 | .menu-item.selected:before {
15 | content: "L"; display: inline-block; transform: rotateZ(45deg) scaleX(-1); line-height: 15px;
16 | font-weight: bold; position: absolute; margin-left: -14px; font-size: 12px; margin-top: 2px;
17 | }
--------------------------------------------------------------------------------
/content.json:
--------------------------------------------------------------------------------
1 | {
2 | "address": "1uPLoaDwKzP6MCGoVzw48r4pxawRBdmQc",
3 | "address_index": 89091714,
4 | "background-color": "#FFF",
5 | "clone_root": "template-new",
6 | "cloned_from": "1HeLLo4uzjaLetFx6NH3PMwFP3qbRbTf3D",
7 | "description": "",
8 | "files": {
9 | "css/all.css": {
10 | "sha512": "dfc5bf0f5bfb30c56a0a32adc400b42ccd90a9a795ed122f0310330846c7a174",
11 | "size": 170152
12 | },
13 | "dbschema.json": {
14 | "sha512": "596519604f8d34d750da86ada7ab618499b9257e2db30af546725d8784f70e3d",
15 | "size": 978
16 | },
17 | "index.html": {
18 | "sha512": "86db9821b7bb669135416cefc255f57ea9f703e3c4061c184a4bb05751e8ef5d",
19 | "size": 1124
20 | },
21 | "js/all.js": {
22 | "sha512": "d5eb71fd4717f2622a7081cad90783b4e9db91c08a49fcdf66037d933c56ff7e",
23 | "size": 125731
24 | }
25 | },
26 | "ignore": "((js|css)/(?!all.(js|css))|data/.*)",
27 | "includes": {
28 | "data/users/content.json": {
29 | "signers": [],
30 | "signers_required": 1
31 | }
32 | },
33 | "inner_path": "content.json",
34 | "modified": 1507240116,
35 | "postmessage_nonce_security": true,
36 | "signers_sign": "G92h5e3FmtEMZgSdewI3esFkw8ny/m9KuSQ0IwFgVMcafUaepy2HF0oiCYmYd2CVmZHGF45WAxBz6zxJEU1AngE=",
37 | "signs": {
38 | "1uPLoaDwKzP6MCGoVzw48r4pxawRBdmQc": "HKVyKiAdLbBa/3cwQ/aT50hHjIKI6dUKEkuEOZlymY+uU3Y1tEcsEzRMB39rEsvm/1CjXh+e02DWXRpVGm4fOaA="
39 | },
40 | "signs_required": 1,
41 | "title": "ZeroUp",
42 | "translate": ["js/all.js"],
43 | "zeronet_version": "0.6.0"
44 | }
--------------------------------------------------------------------------------
/js/utils/Autosize.coffee:
--------------------------------------------------------------------------------
1 | class Autosize extends Class
2 | constructor: (@attrs={}) ->
3 | @node = null
4 |
5 | @attrs.classes ?= {}
6 | @attrs.classes.loading = false
7 | @attrs.oninput = @handleInput
8 | @attrs.onkeydown = @handleKeydown
9 | @attrs.afterCreate = @storeNode
10 | @attrs.rows = 1
11 | @attrs.disabled = false
12 |
13 | @property 'loading',
14 | get: -> @attrs.classes.loading
15 | set: (loading) ->
16 | @attrs.classes.loading = loading
17 | @node.value = @attrs.value
18 | @autoHeight()
19 | Page.projector.scheduleRender()
20 |
21 | storeNode: (node) =>
22 | @node = node
23 | if @attrs.focused
24 | node.focus()
25 | setTimeout =>
26 | @autoHeight()
27 |
28 | setValue: (value=null) =>
29 | @attrs.value = value
30 | if @node
31 | @node.value = value
32 | @autoHeight()
33 | Page.projector.scheduleRender()
34 |
35 | autoHeight: =>
36 | height_before = @node.style.height
37 | if height_before
38 | @node.style.height = "0px"
39 | h = @node.offsetHeight
40 | scrollh = @node.scrollHeight
41 | @node.style.height = height_before
42 | if scrollh > h
43 | anime({targets: @node, height: scrollh, scrollTop: 0})
44 | else
45 | @node.style.height = height_before
46 |
47 | handleInput: (e=null) =>
48 | @attrs.value = e.target.value
49 | RateLimit 300, @autoHeight
50 |
51 | handleKeydown: (e=null) =>
52 | if e.which == 13 and not e.shiftKey and @attrs.onsubmit and @attrs.value.trim()
53 | @attrs.onsubmit()
54 | setTimeout ( =>
55 | @autoHeight()
56 | ), 100
57 | return false
58 |
59 | render: (body=null) =>
60 | if body and @attrs.value == undefined
61 | @setValue(body)
62 | if @loading
63 | attrs = clone(@attrs)
64 | #attrs.value = "Submitting..."
65 | attrs.disabled = true
66 | h("textarea.autosize", attrs)
67 | else
68 | h("textarea.autosize", @attrs)
69 |
70 | window.Autosize = Autosize
--------------------------------------------------------------------------------
/js/utils/Editable.coffee:
--------------------------------------------------------------------------------
1 | class Editable extends Class
2 | constructor: (@type, @handleSave, @handleDelete) ->
3 | @node = null
4 | @editing = false
5 | @render_function = null
6 | @empty_text = "Click here to edit this field"
7 |
8 | storeNode: (node) =>
9 | @node = node
10 |
11 | handleEditClick: (e) =>
12 | @editing = true
13 | @field_edit = new Autosize({focused: 1, style: "height: 0px"})
14 | return false
15 |
16 | handleCancelClick: =>
17 | @editing = false
18 | return false
19 |
20 | handleDeleteClick: =>
21 | Page.cmd "wrapperConfirm", ["Are you sure?", "Delete"], =>
22 | @field_edit.loading = true
23 | @handleDelete (res) =>
24 | @field_edit.loading = false
25 | return false
26 |
27 | handleSaveClick: =>
28 | @field_edit.loading = true
29 | @handleSave @field_edit.attrs.value, (res) =>
30 | @field_edit.loading = false
31 | if res
32 | @editing = false
33 | return false
34 |
35 | render: (body) =>
36 | if @editing
37 | return h("div.editable.editing", {exitAnimation: Animation.slideUp},
38 | @field_edit.render(body),
39 | h("div.editablebuttons",
40 | h("a.link.cancel", {href: "#Cancel", onclick: @handleCancelClick, tabindex: "-1"}, "Cancel"),
41 | if @handleDelete
42 | h("a.button.button-submit.button-small.button-outline", {href: "#Delete", onclick: @handleDeleteClick, tabindex: "-1"}, "Delete")
43 | h("a.button.button-submit.button-small", {href: "#Save", onclick: @handleSaveClick}, "Save")
44 | )
45 | )
46 | else
47 | return h("div.editable", {enterAnimation: Animation.slideDown},
48 | h("a.icon.icon-edit", {key: @node, href: "#Edit", onclick: @handleEditClick}),
49 | if not body
50 | h(@type, h("span.empty", {onclick: @handleEditClick}, @empty_text))
51 | else if @render_function
52 | h(@type, {innerHTML: @render_function(body)})
53 | else
54 | h(@type, body)
55 | )
56 |
57 | window.Editable = Editable
--------------------------------------------------------------------------------
/js/lib/Promise.coffee:
--------------------------------------------------------------------------------
1 | class Promise
2 | @join: (tasks...) ->
3 | num_uncompleted = tasks.length
4 | args = new Array(num_uncompleted)
5 | promise = new Promise()
6 |
7 | for task, task_id in tasks
8 | ((task_id) ->
9 | task.then(() ->
10 | args[task_id] = Array.prototype.slice.call(arguments)
11 | num_uncompleted--
12 | if num_uncompleted == 0
13 | for callback in promise.callbacks
14 | callback.apply(promise, args)
15 | )
16 | )(task_id)
17 |
18 | return promise
19 |
20 | constructor: ->
21 | @resolved = false
22 | @end_promise = null
23 | @result = null
24 | @callbacks = []
25 |
26 | resolve: ->
27 | if @resolved
28 | return false
29 | @resolved = true
30 | @data = arguments
31 | if not arguments.length
32 | @data = [true]
33 | @result = @data[0]
34 | for callback in @callbacks
35 | back = callback.apply callback, @data
36 | if @end_promise and back and back.then
37 | back.then (back_res) =>
38 | @end_promise.resolve(back_res)
39 |
40 | fail: ->
41 | @resolve(false)
42 |
43 | then: (callback) ->
44 | if @resolved == true
45 | return callback.apply callback, @data
46 |
47 | @callbacks.push callback
48 |
49 | @end_promise = new Promise()
50 | return @end_promise
51 |
52 | window.Promise = Promise
53 |
54 | ###
55 | s = Date.now()
56 | log = (text) ->
57 | console.log Date.now()-s, Array.prototype.slice.call(arguments).join(", ")
58 |
59 | log "Started"
60 |
61 | cmd = (query) ->
62 | p = new Promise()
63 | setTimeout ( ->
64 | p.resolve query+" Result"
65 | ), 100
66 | return p
67 |
68 |
69 | back = cmd("SELECT * FROM message").then (res) ->
70 | log res
71 | p = new Promise()
72 | setTimeout ( ->
73 | p.resolve("DONE parsing SELECT")
74 | ), 100
75 | return p
76 | .then (res) ->
77 | log "Back of messages", res
78 | return cmd("SELECT * FROM users")
79 | .then (res) ->
80 | log "End result", res
81 |
82 | log "Query started", back
83 |
84 |
85 | q1 = cmd("SELECT * FROM anything")
86 | q2 = cmd("SELECT * FROM something")
87 |
88 | Promise.join(q1, q2).then (res1, res2) ->
89 | log res1, res2
90 | ###
--------------------------------------------------------------------------------
/js/utils/Menu.coffee:
--------------------------------------------------------------------------------
1 | class Menu
2 | constructor: ->
3 | @visible = false
4 | @items = []
5 | @node = null
6 |
7 | show: =>
8 | window.visible_menu?.hide()
9 | @visible = true
10 | window.visible_menu = @
11 |
12 | hide: =>
13 | @visible = false
14 |
15 | toggle: =>
16 | if @visible
17 | @hide()
18 | else
19 | @show()
20 | Page.projector.scheduleRender()
21 |
22 |
23 | addItem: (title, cb, selected=false) ->
24 | @items.push([title, cb, selected])
25 |
26 |
27 | storeNode: (node) =>
28 | @node = node
29 | # Animate visible
30 | if @visible
31 | node.className = node.className.replace("visible", "")
32 | setTimeout (->
33 | node.className += " visible"
34 | ), 10
35 |
36 | handleClick: (e) =>
37 | keep_menu = false
38 | for item in @items
39 | [title, cb, selected] = item
40 | if title == e.target.textContent
41 | keep_menu = cb(item)
42 | if keep_menu != true
43 | @hide()
44 | return false
45 |
46 | renderItem: (item) =>
47 | [title, cb, selected] = item
48 | if typeof(selected) == "function"
49 | selected = selected()
50 | if title == "---"
51 | h("div.menu-item-separator")
52 | else
53 | if typeof(cb) == "string" # Url
54 | href = cb
55 | onclick = true
56 | else # Callback
57 | href = "#"+title
58 | onclick = @handleClick
59 | h("a.menu-item", {href: href, onclick: onclick, key: title, classes: {"selected": selected}}, [title])
60 |
61 | render: (class_name="") =>
62 | if @visible or @node
63 | h("div.menu#{class_name}", {classes: {"visible": @visible}, afterCreate: @storeNode}, @items.map(@renderItem))
64 |
65 | window.Menu = Menu
66 |
67 | # Hide menu on outside click
68 | document.body.addEventListener "mouseup", (e) ->
69 | if not window.visible_menu or not window.visible_menu.node
70 | return false
71 | if e.target != window.visible_menu.node.parentNode and e.target.parentNode != window.visible_menu.node and e.target.parentNode != window.visible_menu.node.parentNode and e.target.parentNode != window.visible_menu.node and e.target.parentNode.parentNode != window.visible_menu.node.parentNode
72 | window.visible_menu.hide()
73 | Page.projector.scheduleRender()
--------------------------------------------------------------------------------
/css/Button.css:
--------------------------------------------------------------------------------
1 | .button {
2 | margin-top: 4px; border: 1px solid hsla(236,100%,79%,1); color: #5d68ff; border-radius: 33px; display: inline-block; text-decoration: none;
3 | font-size: 19px; font-weight: lighter; text-align: center; transition: all 0.3s; padding: 8px 30px; background-position: -200px center;
4 | }
5 | .button:hover { background-color: #5d68ff; color: #F6F7F8; text-decoration: none; border-color: #5d68ff; transition: none }
6 | .button:hover .icon { background-color: #FFF; transition: none }
7 | .button:focus { transition: all 0.3s }
8 | .button:active { transform: translateY(1px); transition: all 0.3s, transform none; box-shadow: inset 0px 5px 7px -3px rgba(212, 212, 212, 0.41); outline: none; transition: none }
9 |
10 | .button.loading {
11 | color: rgba(0,0,0,0) !important; background: url(../img/loading.gif) no-repeat center center !important; border-color: rgba(0,0,0,0) !important;
12 | transition: all 0.5s ease-out; pointer-events: none; transition-delay: 0.5s
13 | }
14 |
15 | /* Follow */
16 | .button-follow { width: 32px; line-height: 32px; padding: 0px; border: 1px solid #aaa; color: #999; padding-left: 1px; padding-bottom: 1px; }
17 | .button-follow:hover { background-color: rgba(255,255,255,0.3) !important; border-color: #2ecc71 !important; color: #2ecc71 }
18 | .button-follow-big { padding-left: 25px; float: none; border: 1px solid #2ecc71; color: #2ecc71; min-width: 100px; }
19 | .button-follow-big .icon-follow { margin-right: 10px; display: inline-block; transition: transform 0.3s ease-in-out }
20 | .button-follow-big:hover { border-color: #2ecc71 !important; color: #2ecc71; background-color: white; text-decoration: underline; }
21 |
22 | /* Submit */
23 | .button-submit {
24 | padding: 12px 30px; border-radius: 3px; margin-top: 11px; background-color: #5d68ff; /*box-shadow: 0px 1px 4px rgba(93, 104, 255, 0.41);*/
25 | border: none; border-bottom: 2px solid #4952c7; font-weight: bold; color: #ffffff; font-size: 12px; text-transform: uppercase; margin-left: 10px;
26 | }
27 | .button-submit:hover { color: white; background-color: #6d78ff }
28 |
29 | .button-small { padding: 7px 20px; margin-left: 10px }
30 | .button-outline { background-color: white; border: 1px solid #EEE; border-bottom: 2px solid #EEE; color: #AAA; }
31 | .button-outline:hover { background-color: white; border: 1px solid #CCC; border-bottom: 2px solid #CCC; color: #777 }
32 |
--------------------------------------------------------------------------------
/js/lib/RateLimitCb.coffee:
--------------------------------------------------------------------------------
1 | last_time = {}
2 | calling = {}
3 | call_after_interval = {}
4 |
5 | # Rate limit function call and don't allow to run in parallel (until callback is called)
6 | window.RateLimitCb = (interval, fn, args=[]) ->
7 | cb = -> # Callback when function finished
8 | left = interval - (Date.now() - last_time[fn]) # Time life until next call
9 | # console.log "CB, left", left, "Calling:", calling[fn]
10 | if left <= 0 # No time left from rate limit interval
11 | delete last_time[fn]
12 | if calling[fn] # Function called within interval
13 | RateLimitCb(interval, fn, calling[fn])
14 | delete calling[fn]
15 | else # Time left from rate limit interval
16 | setTimeout (->
17 | delete last_time[fn]
18 | if calling[fn] # Function called within interval
19 | RateLimitCb(interval, fn, calling[fn])
20 | delete calling[fn]
21 | ), left
22 | if last_time[fn] # Function called within interval
23 | calling[fn] = args # Schedule call and update arguments
24 | else # Not called within interval, call instantly
25 | last_time[fn] = Date.now()
26 | fn.apply(this, [cb, args...])
27 |
28 | window.RateLimit = (interval, fn) ->
29 | if not calling[fn]
30 | call_after_interval[fn] = false
31 | fn() # First call is not delayed
32 | calling[fn] = setTimeout (->
33 | if call_after_interval[fn]
34 | fn()
35 | delete calling[fn]
36 | delete call_after_interval[fn]
37 | ), interval
38 | else # Called within iterval, delay the call
39 | call_after_interval[fn] = true
40 |
41 | ###
42 | window.s = Date.now()
43 | window.load = (done, num) ->
44 | console.log "Loading #{num}...", Date.now()-window.s
45 | setTimeout (-> done()), 1000
46 |
47 | RateLimit 500, window.load, [0] # Called instantly
48 | RateLimit 500, window.load, [1]
49 | setTimeout (-> RateLimit 500, window.load, [300]), 300
50 | setTimeout (-> RateLimit 500, window.load, [600]), 600 # Called after 1000ms
51 | setTimeout (-> RateLimit 500, window.load, [1000]), 1000
52 | setTimeout (-> RateLimit 500, window.load, [1200]), 1200 # Called after 2000ms
53 | setTimeout (-> RateLimit 500, window.load, [3000]), 3000 # Called after 3000ms
54 | ###
--------------------------------------------------------------------------------
/js/Bg.coffee:
--------------------------------------------------------------------------------
1 | class Bg extends Class
2 | constructor: (@bg_elem) ->
3 | @item_types = ["video", "gamepad", "ipod", "image", "file"]
4 |
5 | window.onresize = @handleResize
6 | @handleResize()
7 |
8 | @randomizePosition()
9 | setTimeout ( =>
10 | @randomizeAnimation()
11 | ), 10
12 | @log "inited"
13 |
14 | handleResize: =>
15 | @width = window.innerWidth
16 | @height = window.innerHeight
17 |
18 |
19 | randomizePosition: ->
20 | for item in @bg_elem.querySelectorAll(".bgitem")
21 | top = (Math.random() * @height * 0.8)
22 | left = (Math.random() * @width * 0.8)
23 | if Math.random() > 0.8
24 | [left, top] = @getRandomOutpos()
25 |
26 | rotate = 45 - (Math.random() * 90)
27 | scale = 0.5 + Math.min(0.5, Math.random())
28 |
29 | item.style.transform = "TranslateX(#{left}px) TranslateY(#{top}px) rotateZ(#{rotate}deg) scale(#{scale})"
30 |
31 | getRandomOutpos: ->
32 | # Find new pos
33 | rand = Math.random()
34 | if rand < 0.25 # Out right
35 | left = @width + 100
36 | top = @height * Math.random()
37 | else if rand < 0.5 # Out bottom
38 | left = @width * Math.random()
39 | top = @height + 100
40 | else if rand < 0.75 # Out left
41 | left = -100
42 | top = @height * Math.random()
43 | else # Out top
44 | left = @width * Math.random()
45 | top = -100
46 | return [left, top]
47 |
48 | randomizeAnimation: ->
49 | return false
50 | for item in @bg_elem.querySelectorAll(".bgitem")
51 | item.style.visibility = "visible"
52 | interval = 30 + (Math.random() * 60)
53 | item.style.transition = "all #{interval}s linear"
54 | [left, top] = @getRandomOutpos()
55 |
56 | rotate = 360 - (Math.random() * 720)
57 | scale = 0.5 + Math.min(0.5, Math.random())
58 |
59 | item.style.transform = "TranslateX(#{left}px) TranslateY(#{top}px) rotateZ(#{rotate}deg) scale(#{scale})"
60 |
61 | bg = @
62 | item.addEventListener "transitionend", (e) ->
63 | if e.propertyName == "transform"
64 | bg.repositionItem(this)
65 |
66 | repositionItem: (item) =>
67 | [left, top] = @getRandomOutpos()
68 | rotate = 360 - (Math.random() * 720)
69 | scale = 0.5 + Math.min(0.5, Math.random())
70 |
71 | # @bg_elem.removeChild(item) # Avoid animation
72 | item.style.transform = "TranslateX(#{left}px) TranslateY(#{top}px) rotateZ(#{rotate}deg) scale(#{scale})"
73 | # @bg_elem.appendChild(item) # Re-enable animation
74 |
75 | # [target_left, target_top] = [500, 500]
76 | # target_rotate = 180 - (Math.random() * 360)
77 | # item.style.transform = "TranslateX(#{target_left}px) TranslateY(#{target_top}px) rotateZ(#{target_rotate}deg)"
78 |
79 | window.Bg = Bg
80 |
--------------------------------------------------------------------------------
/js/utils/ZeroFrame.coffee:
--------------------------------------------------------------------------------
1 | class ZeroFrame extends Class
2 | constructor: (url) ->
3 | @queue = []
4 | @url = url
5 | @waiting_cb = {}
6 | @history_state = {}
7 | @wrapper_nonce = document.location.href.replace(/.*wrapper_nonce=([A-Za-z0-9]+).*/, "$1")
8 | @connect()
9 | @next_message_id = 1
10 | @init()
11 | @ready = false
12 |
13 |
14 | init: ->
15 | @
16 |
17 | connect: ->
18 | @target = window.parent
19 | window.addEventListener("message", @onMessage, false)
20 | @send({"cmd": "innerReady"})
21 |
22 | # Save scrollTop
23 | window.addEventListener "beforeunload", (e) =>
24 | @log "Save scrollTop", window.pageYOffset
25 | @history_state["scrollTop"] = window.pageYOffset
26 | @cmd "wrapperReplaceState", [@history_state, null]
27 |
28 | # Restore scrollTop
29 | @cmd "wrapperGetState", [], (state) =>
30 | @handleState(state)
31 |
32 | handleState: (state) ->
33 | @history_state = state if state?
34 | @log "Restore scrollTop", state, window.pageYOffset
35 | if window.pageYOffset == 0 and state
36 | window.scroll(window.pageXOffset, state.scrollTop)
37 |
38 |
39 | onMessage: (e) =>
40 | message = e.data
41 | cmd = message.cmd
42 | if cmd == "response"
43 | if @waiting_cb[message.to]?
44 | @waiting_cb[message.to](message.result)
45 | else
46 | @log "Websocket callback not found:", message
47 | else if cmd == "wrapperReady" # Wrapper inited later
48 | @send({"cmd": "innerReady"})
49 | else if cmd == "ping"
50 | @response message.id, "pong"
51 | else if cmd == "wrapperOpenedWebsocket"
52 | @onOpenWebsocket()
53 | @ready = true
54 | @processQueue()
55 | else if cmd == "wrapperClosedWebsocket"
56 | @onCloseWebsocket()
57 | else if cmd == "wrapperPopState"
58 | @handleState(message.params.state)
59 | @onRequest cmd, message.params
60 | else
61 | @onRequest cmd, message.params
62 |
63 | processQueue: ->
64 | for [cmd, params, cb] in @queue
65 | @cmd(cmd, params, cb)
66 | @queue = []
67 |
68 | onRequest: (cmd, message) =>
69 | @log "Unknown request", message
70 |
71 |
72 | response: (to, result) ->
73 | @send {"cmd": "response", "to": to, "result": result}
74 |
75 |
76 | cmd: (cmd, params={}, cb=null) ->
77 | if @ready
78 | @send {"cmd": cmd, "params": params}, cb
79 | else
80 | @queue.push([cmd, params, cb])
81 |
82 | send: (message, cb=null) ->
83 | message.wrapper_nonce = @wrapper_nonce
84 | message.id = @next_message_id
85 | @next_message_id += 1
86 | @target.postMessage(message, "*")
87 | if cb
88 | @waiting_cb[message.id] = cb
89 |
90 |
91 | onOpenWebsocket: =>
92 | @log "Websocket open"
93 |
94 |
95 | onCloseWebsocket: =>
96 | @log "Websocket close"
97 |
98 |
99 |
100 | window.ZeroFrame = ZeroFrame
--------------------------------------------------------------------------------
/js/Uploader.coffee:
--------------------------------------------------------------------------------
1 | class Uploader extends Class
2 | constructor: ->
3 | @
4 |
5 | renderSpeed: =>
6 | """
7 |
24 | """
25 |
26 | randomBase2: (len) =>
27 | return (Math.random()).toString(2).slice(2,len)
28 |
29 | handleFinishUpload: =>
30 | Page.state.page = "list"
31 | Page.projector.scheduleRender()
32 | setTimeout ( =>
33 | Page.list.update()
34 | ), 1000
35 | return false
36 |
37 | render: =>
38 | file_info = Page.selector.file_info
39 | # Efficient updating svg is not possible in maquette, so manupulated DOM directly
40 | dash_offset = Math.max(2390 - (486 * file_info.speed / 1024 / 1024 / 100), 1770) + Math.random() * 10
41 | if dash_offset != @last_dash_offset
42 | @last_dash_offset = dash_offset
43 | setTimeout (=>
44 | document.getElementById("speed_current")?.style.strokeDashoffset = dash_offset # 2390 - 1770
45 | ), 1
46 |
47 |
48 | h("div.Uploader", {classes: {hidden: Page.state.page != "uploader"}}, [
49 | h("div.speed", {innerHTML: @renderSpeed()})
50 | h("div.status", [
51 | h("div.icon.icon-file-empty.file-fg", {style: "clip: rect(0px 100px #{114*file_info.percent}px 0px)"}, [
52 | @randomBase2(13), h("br"), @randomBase2(13), h("br"), @randomBase2(13), h("br"), @randomBase2(40), @randomBase2(40), @randomBase2(40), @randomBase2(24)
53 | ]),
54 | h("div.icon.icon-file-empty.file-bg"),
55 | h("div.percent", {style: "transform: translateY(#{114*file_info.percent}px"}, [
56 | Math.round(file_info.percent * 100),
57 | h("span.post", "% \u25B6")
58 | ]),
59 | h("div.name", file_info.name),
60 | h("div.size", Text.formatSize(file_info.size)),
61 | if file_info.status == "done"
62 | h("div.message.message-done", "File uploaded in #{((file_info.updated - file_info.started) / 1000).toFixed(1)}s @ #{Text.formatSize(file_info.speed)}/s!")
63 | else if file_info.speed
64 | h("div.message", "Hashing @ #{Text.formatSize(file_info.speed)}/s...")
65 | else
66 | h("div.message", "Opening file...")
67 | h("a.button-big.button-finish", {href: "?List", onclick: @handleFinishUpload, classes: {visible: file_info.status == "done"}}, "Finish upload \u00BB")
68 | ])
69 | ])
70 |
71 | window.Uploader = Uploader
--------------------------------------------------------------------------------
/js/ZeroUp.coffee:
--------------------------------------------------------------------------------
1 | window.h = maquette.h
2 |
3 | class ZeroUp extends ZeroFrame
4 | init: ->
5 | @bg = new Bg($("#Bg"))
6 | @state = {}
7 | @state.page = "list"
8 | @on_site_info = new Promise()
9 | @on_loaded = new Promise()
10 |
11 |
12 | createProjector: ->
13 | @projector = maquette.createProjector()
14 |
15 | @list = new List()
16 | @selector = new Selector()
17 | @uploader = new Uploader()
18 |
19 | if base.href.indexOf("?") == -1
20 | @route("")
21 | else
22 | url = base.href.replace(/.*?\?/, "")
23 | @route(url)
24 | @history_state["url"] = url
25 |
26 | @projector.replace($("#List"), @list.render)
27 | @projector.replace($("#Uploader"), @uploader.render)
28 |
29 | setPage: (page_name) ->
30 | @state.page = page_name
31 | @projector.scheduleRender()
32 |
33 | setSiteInfo: (site_info) ->
34 | @site_info = site_info
35 |
36 | onOpenWebsocket: =>
37 | @updateSiteInfo()
38 | @cmd "serverInfo", {}, (@server_info) =>
39 | if @server_info.rev < 3090
40 | @cmd "wrapperNotification", ["error", "This site requires ZeroNet 0.6.0"]
41 |
42 | updateSiteInfo: =>
43 | @cmd "siteInfo", {}, (site_info) =>
44 | @address = site_info.address
45 | @setSiteInfo(site_info)
46 | @on_site_info.resolve()
47 |
48 | onRequest: (cmd, params) ->
49 | if cmd == "setSiteInfo" # Site updated
50 | @setSiteInfo(params)
51 | if params.event?[0] in ["file_done", "file_delete", "peernumber_updated"]
52 | RateLimit 1000, =>
53 | @list.need_update = true
54 | Page.projector.scheduleRender()
55 | else if cmd == "wrapperPopState" # Site updated
56 | if params.state
57 | if not params.state.url
58 | params.state.url = params.href.replace /.*\?/, ""
59 | @on_loaded.resolved = false
60 | document.body.className = ""
61 | window.scroll(window.pageXOffset, params.state.scrollTop or 0)
62 | @route(params.state.url or "")
63 | else
64 | @log "Unknown command", cmd, params
65 |
66 | # Route site urls
67 | route: (query) ->
68 | @params = Text.queryParse(query)
69 | @log "Route", @params
70 |
71 | @content = @list
72 | if @params.url
73 | @list.type = @params.url
74 | @content.limit = 10
75 | @content.need_update = true
76 |
77 | @projector.scheduleRender()
78 |
79 | setUrl: (url, mode="push") ->
80 | url = url.replace(/.*?\?/, "")
81 | @log "setUrl", @history_state["url"], "->", url
82 | if @history_state["url"] == url
83 | @content.update()
84 | return false
85 | @history_state["url"] = url
86 | if mode == "replace"
87 | @cmd "wrapperReplaceState", [@history_state, "", url]
88 | else
89 | @cmd "wrapperPushState", [@history_state, "", url]
90 | @route url
91 | return false
92 |
93 | handleLinkClick: (e) =>
94 | if e.which == 2
95 | # Middle click dont do anything
96 | return true
97 | else
98 | @log "save scrollTop", window.pageYOffset
99 | @history_state["scrollTop"] = window.pageYOffset
100 | @cmd "wrapperReplaceState", [@history_state, null]
101 |
102 | window.scroll(window.pageXOffset, 0)
103 | @history_state["scrollTop"] = 0
104 |
105 | @on_loaded.resolved = false
106 | document.body.className = ""
107 |
108 | @setUrl e.currentTarget.search
109 | return false
110 |
111 |
112 | # Add/remove/change parameter to current site url
113 | createUrl: (key, val) ->
114 | params = JSON.parse(JSON.stringify(@params)) # Clone
115 | if typeof key == "Object"
116 | vals = key
117 | for key, val of keys
118 | params[key] = val
119 | else
120 | params[key] = val
121 | return "?"+Text.queryEncode(params)
122 |
123 | returnFalse: ->
124 | return false
125 |
126 |
127 | window.Page = new ZeroUp()
128 | window.Page.createProjector()
--------------------------------------------------------------------------------
/js/List.coffee:
--------------------------------------------------------------------------------
1 | class List extends Class
2 | constructor: ->
3 | @item_list = new ItemList(File, "id")
4 | @files = @item_list.items
5 | @need_update = true
6 | @loaded = false
7 | @type = "Popular"
8 | @limit = 10
9 |
10 | needFile: =>
11 | @log args
12 | return false
13 |
14 | update: =>
15 | @log "update"
16 | @loaded = false
17 |
18 | if @type == "Popular"
19 | order = "peer"
20 | else
21 | order = "date_added"
22 |
23 | if @search
24 | wheres = "WHERE file.title LIKE :search OR file.file_name LIKE :search"
25 | params = {search: "%#{@search}%"}
26 | else
27 | wheres = ""
28 | params = ""
29 |
30 | Page.cmd "dbQuery", ["SELECT * FROM file LEFT JOIN json USING (json_id) #{wheres} ORDER BY date_added DESC", params], (files_res) =>
31 | orderby = "time_downloaded DESC, peer DESC"
32 | if @type == "My"
33 | orderby = "is_downloaded DESC"
34 | else if @type == "Latest"
35 | orderby = "is_downloaded DESC, time_added DESC"
36 | else if @type == "Seeding"
37 | orderby = "is_downloaded DESC, is_pinned DESC"
38 |
39 | Page.cmd "optionalFileList", {filter: "bigfile", limit: 2000, orderby: orderby}, (stat_res) =>
40 | stats = {}
41 | for stat in stat_res
42 | stats[stat.inner_path] = stat
43 |
44 | for file in files_res
45 | file.id = file.directory + "_" + file.date_added
46 | file.inner_path = "data/users/#{file.directory}/#{file.file_name}"
47 | file.data_inner_path = "data/users/#{file.directory}/data.json"
48 | file.content_inner_path = "data/users/#{file.directory}/content.json"
49 | file.stats = stats[file.inner_path]
50 | file.stats ?= {}
51 | file.stats.peer ?= 0
52 | file.stats.peer_seed ?= 0
53 | file.stats.peer_leech ?= 0
54 |
55 | if order == "peer"
56 | files_res.sort (a,b) ->
57 | return Math.min(5, b.stats["peer_seed"]) + b.stats["peer"] - a.stats["peer"] - Math.min(5, a.stats["peer_seed"])
58 |
59 | if @type == "Seeding"
60 | files_res = (file for file in files_res when file.stats.bytes_downloaded > 0 or file.stats.is_pinned == 1)
61 |
62 | if @type == "My"
63 | files_res = (file for file in files_res when file.directory == Page.site_info.auth_address)
64 |
65 | @item_list.sync(files_res)
66 | @loaded = true
67 | Page.projector.scheduleRender()
68 |
69 | handleMoreClick: =>
70 | @limit += 20
71 | return false
72 |
73 | handleSearchClick: =>
74 | @is_search_active = true
75 | document.querySelector(".input-search").focus()
76 | return false
77 |
78 | handleSearchInput: (e) =>
79 | @search = e.currentTarget.value
80 | @update()
81 | return false
82 |
83 | handleSearchKeyup: (e) =>
84 | if e.keyCode == 27 # Esc
85 | if not @search
86 | @is_search_active = false
87 | e.target.value = ""
88 | @search = ""
89 | @update()
90 | return false
91 |
92 | handleSearchBlur: (e) =>
93 | if not @search
94 | @is_search_active = false
95 |
96 |
97 | render: =>
98 | if @need_update
99 | @update()
100 | @need_update = false
101 |
102 | h("div.List", {ondragenter: document.body.ondragover, ondragover: document.body.ondragover, ondrop: Page.selector.handleFileDrop, classes: {hidden: Page.state.page != "list"}}, [
103 | h("div.list-types", [
104 | h("a.list-type.search", {href: "#Search", onclick: @handleSearchClick, classes: {active: @is_search_active}},
105 | h("div.icon.icon-magnifier"),
106 | h("input.input-search", oninput: @handleSearchInput, onkeyup: @handleSearchKeyup, onblur: @handleSearchBlur)
107 | ),
108 | h("a.list-type", {href: "?Popular", onclick: Page.handleLinkClick, classes: {active: @type == "Popular"}}, "Popular"),
109 | h("a.list-type", {href: "?Latest", onclick: Page.handleLinkClick, classes: {active: @type == "Latest"}}, "Latest"),
110 | h("a.list-type", {href: "?Seeding", onclick: Page.handleLinkClick, classes: {active: @type == "Seeding"}}, "Seeding"),
111 | h("a.list-type", {href: "?My", onclick: Page.handleLinkClick, classes: {active: @type == "My"}}, "My uploads"),
112 | # h("input.filter", {placeholder: "Filter uploads..."})
113 | ]),
114 | h("a.upload", {href: "#", onclick: Page.selector.handleBrowseClick}, [h("div.icon.icon-upload"), h("span.upload-title", "Upload new file")]),
115 | if @files.length then h("div.files", [
116 | h("div.file.header",
117 | h("div.stats", [
118 | h("div.stats-col.peers", "Peers"),
119 | h("div.stats-col.ratio", "Ratio"),
120 | h("div.stats-col.downloaded", "Uploaded")
121 | ])
122 | ),
123 | @files[0..@limit].map (file) =>
124 | file.render()
125 | ])
126 | if @loaded and not @files.length
127 | if @type == "Seeding"
128 | h("h2", "Not seeded files yet :(")
129 | else
130 | h("h2", "No files submitted yet")
131 | if @files.length > @limit
132 | h("a.more.link", {href: "#", onclick: @handleMoreClick}, "Show more...")
133 | ])
134 |
135 | window.List = List
136 |
--------------------------------------------------------------------------------
/js/Selector.coffee:
--------------------------------------------------------------------------------
1 | class Selector extends Class
2 | constructor: ->
3 | @file_info = {}
4 |
5 | document.body.ondragover = (e) =>
6 | if e.dataTransfer.items[0]?.kind == "file"
7 | document.body.classList.add("drag-over")
8 | @preventEvent(e)
9 |
10 | document.body.ondragleave = (e) =>
11 | if not e.pageX
12 | document.body.classList.remove("drag-over")
13 | @preventEvent(e)
14 |
15 | checkContentJson: (cb) =>
16 | inner_path = "data/users/" + Page.site_info.auth_address + "/content.json"
17 | Page.cmd "fileGet", [inner_path, false], (res) =>
18 | if res
19 | res = JSON.parse(res)
20 | res ?= {}
21 | optional_pattern = "(?!data.json)"
22 | if res.optional == optional_pattern
23 | return cb()
24 |
25 | res.optional = optional_pattern
26 | Page.cmd "fileWrite", [inner_path, Text.fileEncode(res)], cb
27 |
28 | registerUpload: (title, file_name, file_size, date_added, cb) =>
29 | inner_path = "data/users/" + Page.site_info.auth_address + "/data.json"
30 | Page.cmd "fileGet", [inner_path, false], (res) =>
31 | if res
32 | res = JSON.parse(res)
33 | res ?= {}
34 | res.file ?= {}
35 | res.file[file_name] = {
36 | title: title,
37 | size: file_size,
38 | date_added: date_added
39 | }
40 | Page.cmd "fileWrite", [inner_path, Text.fileEncode(res)], cb
41 |
42 | handleUploadDone: (file) =>
43 | Page.setUrl("?Latest")
44 | @log "Upload done", file
45 |
46 | uploadFile: (file) =>
47 | if file.size > 200 * 1024 * 1024
48 | Page.cmd("wrapperNotification", ["info", "Maximum file size on this site during the testing period: 200MB"])
49 | return false
50 | if file.size < 10 * 1024 * 1024
51 | Page.cmd("wrapperNotification", ["info", "Minimum file size: 10MB"])
52 | return false
53 | if file.name.split(".").slice(-1)[0] not in ["mp4", "gz", "zip", "webm"]
54 | Page.cmd("wrapperNotification", ["info", "Only mp4, webm, tar.gz, zip files allowed on this site"])
55 | debugger
56 | return false
57 |
58 | @file_info = {}
59 | @checkContentJson (res) =>
60 | file_name = file.name
61 |
62 | # Add timestamp to filename if it has low amount of English characters
63 | if file_name.replace(/[^A-Za-z0-9]/g, "").length < 20
64 | file_name = Time.timestamp() + "-" + file_name
65 |
66 | Page.cmd "bigfileUploadInit", ["data/users/" + Page.site_info.auth_address + "/" + file_name, file.size], (init_res) =>
67 | formdata = new FormData()
68 | formdata.append(file_name, file)
69 |
70 | req = new XMLHttpRequest()
71 | @req = req
72 | @file_info = {size: file.size, name: file_name, type: file.type, url: init_res.url}
73 | req.upload.addEventListener "loadstart", (progress) =>
74 | @log "loadstart", arguments
75 | @file_info.started = progress.timeStamp
76 | Page.setPage("uploader")
77 | req.upload.addEventListener "loadend", =>
78 | @log "loadend", arguments
79 | @file_info.status = "done"
80 | @registerUpload file.name.replace(/\.[^\.]+$/, ""), init_res.file_relative_path, file.size, Time.timestamp(), (res) =>
81 | Page.cmd "siteSign", {inner_path: "data/users/" + Page.site_info.auth_address + "/content.json"}, (res) =>
82 | Page.cmd "sitePublish", {inner_path: "data/users/" + Page.site_info.auth_address + "/content.json", "sign": false}, (res) =>
83 | @handleUploadDone(file)
84 |
85 | req.upload.addEventListener "progress", (progress) =>
86 | @file_info.speed = 1000 * progress.loaded / (progress.timeStamp - @file_info.started)
87 |
88 | @file_info.percent = progress.loaded / progress.total
89 | @file_info.loaded = progress.loaded
90 | @file_info.updated = progress.timeStamp
91 | Page.projector.scheduleRender()
92 | req.addEventListener "load", =>
93 | @log "load", arguments
94 | req.addEventListener "error", =>
95 | @log "error", arguments
96 | req.addEventListener "abort", =>
97 | @log "abort", arguments
98 |
99 | req.withCredentials = true
100 | req.open("POST", init_res.url)
101 | req.send(formdata)
102 |
103 | handleFileDrop: (e) =>
104 | @log "File drop", e
105 | document.body.classList.remove("drag-over")
106 |
107 | if not event.dataTransfer.files[0]
108 | return false
109 | @preventEvent(e)
110 | if Page.site_info.cert_user_id
111 | @uploadFile(event.dataTransfer.files[0])
112 | else
113 | Page.cmd "certSelect", [["zeroid.bit"]], (res) =>
114 | @uploadFile(event.dataTransfer.files[0])
115 |
116 | handleBrowseClick: (e) =>
117 | if Page.site_info.cert_user_id
118 | @handleUploadClick(e)
119 | else
120 | Page.cmd "certSelect", [["zeroid.bit"]], (res) =>
121 | @handleUploadClick(e)
122 |
123 | handleUploadClick: (e) =>
124 | input = document.createElement('input')
125 | document.body.appendChild(input)
126 | input.type = "file"
127 | input.style.visibility = "hidden"
128 | input.onchange = (e) =>
129 | @uploadFile(input.files[0])
130 | input.click()
131 | return false
132 |
133 | preventEvent: (e) =>
134 | e.stopPropagation()
135 | e.preventDefault()
136 |
137 | render: =>
138 | h("div#Selector.Selector", {classes: {hidden: Page.state.page != "selector"}},
139 | h("div.browse", [
140 | h("div.icon.icon-upload"),
141 | h("a.button", {href: "#Browse", onclick: @handleBrowseClick}, "Select file from computer")
142 | ]),
143 | h("div.dropzone", {ondragenter: @preventEvent, ondragover: @preventEvent, ondrop: @handleFileDrop})
144 | )
145 |
146 | window.Selector = Selector
--------------------------------------------------------------------------------
/js/utils/Text.coffee:
--------------------------------------------------------------------------------
1 | class Text
2 | toColor: (text, saturation=30, lightness=50) ->
3 | hash = 0
4 | for i in [0..text.length-1]
5 | hash += text.charCodeAt(i)*i
6 | hash = hash % 1777
7 | return "hsl(" + (hash % 360) + ",#{saturation}%,#{lightness}%)";
8 |
9 |
10 | renderMarked: (text, options={}) =>
11 | if not text
12 | return ""
13 | options["gfm"] = true
14 | options["breaks"] = true
15 | options["sanitize"] = true
16 | options["renderer"] = marked_renderer
17 | text = @fixReply(text)
18 | text = marked(text, options)
19 | text = text.replace(/(@[^\x00-\x1f^\x21-\x2f^\x3a-\x40^\x5b-\x60^\x7b-\x7f]{1,16}):/g, '$1:') # Highlight usernames
20 | return @fixHtmlLinks text
21 |
22 | renderLinks: (text) =>
23 | text = text.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"') # Sanitize html tags
24 | text = text.replace /(https?:\/\/[^\s)]+)/g, (match) ->
25 | return "#{match}" # UnSanitize & -> & in links
26 | text = text.replace(/\n/g, ' ')
27 | text = text.replace(/(@[^\x00-\x1f^\x21-\x2f^\x3a-\x40^\x5b-\x60^\x7b-\x7f]{1,16}):/g, '$1:')
28 | text = @fixHtmlLinks(text)
29 |
30 | return text
31 |
32 | emailLinks: (text) ->
33 | return text.replace(/([a-zA-Z0-9]+)@zeroid.bit/g, "$1@zeroid.bit")
34 |
35 | # Convert zeronet html links to relaitve
36 | fixHtmlLinks: (text) ->
37 | # Fix site links
38 | text = text.replace(/href="http:\/\/(127.0.0.1|localhost):43110\/(Me.ZeroNetwork.bit|1MeFqFfFFGQfa1J3gJyYYUvb5Lksczq7nH)\/\?/gi, 'href="?')
39 | if window.is_proxy
40 | text = text.replace(/href="http:\/\/(127.0.0.1|localhost):43110/gi, 'href="http://zero')
41 | text = text.replace(/http:\/\/zero\/([^\/]+\.bit)/, "http://$1")
42 | else
43 | text = text.replace(/href="http:\/\/(127.0.0.1|localhost):43110/g, 'href="')
44 | # Add no-refresh linking to local links
45 | text = text.replace(/href="\?/g, 'onclick="return Page.handleLinkClick(window.event)" href="?')
46 | return text
47 |
48 |
49 | # Convert a single link to relative
50 | fixLink: (link) ->
51 | if window.is_proxy
52 | back = link.replace(/http:\/\/(127.0.0.1|localhost):43110/, 'http://zero')
53 | return back.replace(/http:\/\/zero\/([^\/]+\.bit)/, "http://$1") # Domain links
54 | else
55 | return link.replace(/http:\/\/(127.0.0.1|localhost):43110/, '')
56 |
57 | toUrl: (text) ->
58 | return text.replace(/[^A-Za-z0-9]/g, "+").replace(/[+]+/g, "+").replace(/[+]+$/, "")
59 |
60 | getSiteUrl: (address) ->
61 | if window.is_proxy
62 | if "." in address # Domain
63 | return "http://"+address+"/"
64 | else
65 | return "http://zero/"+address+"/"
66 | else
67 | return "/"+address+"/"
68 |
69 |
70 | fixReply: (text) ->
71 | return text.replace(/(>.*\n)([^\n>])/gm, "$1\n$2")
72 |
73 | toBitcoinAddress: (text) ->
74 | return text.replace(/[^A-Za-z0-9]/g, "")
75 |
76 |
77 | jsonEncode: (obj) ->
78 | return unescape(encodeURIComponent(JSON.stringify(obj)))
79 |
80 | jsonDecode: (obj) ->
81 | return JSON.parse(decodeURIComponent(escape(obj)))
82 |
83 | fileEncode: (obj) ->
84 | if typeof(obj) == "string"
85 | return btoa(unescape(encodeURIComponent(obj)))
86 | else
87 | return btoa(unescape(encodeURIComponent(JSON.stringify(obj, undefined, '\t'))))
88 |
89 | utf8Encode: (s) ->
90 | return unescape(encodeURIComponent(s))
91 |
92 | utf8Decode: (s) ->
93 | return decodeURIComponent(escape(s))
94 |
95 |
96 | distance: (s1, s2) ->
97 | s1 = s1.toLocaleLowerCase()
98 | s2 = s2.toLocaleLowerCase()
99 | next_find_i = 0
100 | next_find = s2[0]
101 | match = true
102 | extra_parts = {}
103 | for char in s1
104 | if char != next_find
105 | if extra_parts[next_find_i]
106 | extra_parts[next_find_i] += char
107 | else
108 | extra_parts[next_find_i] = char
109 | else
110 | next_find_i++
111 | next_find = s2[next_find_i]
112 |
113 | if extra_parts[next_find_i]
114 | extra_parts[next_find_i] = "" # Extra chars on the end doesnt matter
115 | extra_parts = (val for key, val of extra_parts)
116 | if next_find_i >= s2.length
117 | return extra_parts.length + extra_parts.join("").length
118 | else
119 | return false
120 |
121 |
122 | queryParse: (query) ->
123 | params = {}
124 | parts = query.split('&')
125 | for part in parts
126 | [key, val] = part.split("=")
127 | if val
128 | params[decodeURIComponent(key)] = decodeURIComponent(val)
129 | else
130 | params["url"] = decodeURIComponent(key)
131 | params["urls"] = params["url"].split("/")
132 | return params
133 |
134 | queryEncode: (params) ->
135 | back = []
136 | if params.url
137 | back.push(params.url)
138 | for key, val of params
139 | if not val or key == "url"
140 | continue
141 | back.push("#{encodeURIComponent(key)}=#{encodeURIComponent(val)}")
142 | return back.join("&")
143 |
144 | highlight: (text, search) ->
145 | parts = text.split(RegExp(search, "i"))
146 | back = []
147 | for part, i in parts
148 | back.push(part)
149 | if i < parts.length-1
150 | back.push(h("span.highlight", {key: i}, search))
151 | return back
152 |
153 | sqlIn: (values) ->
154 | return "("+("'#{value}'" for value in values).join(',')+")"
155 |
156 | formatSize: (size) ->
157 | if not size
158 | return "0 KB"
159 | size_mb = size/1024/1024
160 | if size_mb >= 1000
161 | return (size_mb/1024).toFixed(1)+" GB"
162 | else if size_mb >= 100
163 | return size_mb.toFixed(0)+" MB"
164 | else if size/1024 >= 1000
165 | return size_mb.toFixed(2)+" MB"
166 | else
167 | return (size/1024).toFixed(2)+" KB"
168 |
169 |
170 | window.is_proxy = (document.location.host == "zero" or window.location.pathname == "/")
171 | window.Text = new Text()
172 |
--------------------------------------------------------------------------------
/js/File.coffee:
--------------------------------------------------------------------------------
1 | class File
2 | constructor: (row, @item_list) ->
3 | @editable_title = null
4 | @status = "unknown"
5 | @menu = null
6 | @setRow(row)
7 |
8 | getRatioColor: (ratio) ->
9 | ratio_h = Math.min(ratio * 50, 145)
10 | ratio_s = Math.min(ratio * 100, 60)
11 | ratio_l = 80 - Math.min(ratio * 5, 30)
12 |
13 | return "hsl(#{ratio_h}, #{ratio_s}%, #{ratio_l}%)"
14 |
15 | setRow: (@row) ->
16 | @owned = Page.site_info.auth_address == @row.directory
17 | if @owned and not @editable_title
18 | @editable_title = new Editable("div.body", @handleTitleSave, @handleDelete)
19 | @editable_title.empty_text = " "
20 |
21 | if @row.stats.bytes_downloaded >= @row.size
22 | @status = "seeding"
23 | else if @row.stats.is_downloading or @row.stats.is_pinned
24 | @status = "downloading"
25 | else if 0 < @row.stats.bytes_downloaded < @row.size
26 | @status = "partial"
27 | else
28 | @status = "inactive"
29 |
30 | deleteFile: (cb) =>
31 | Page.cmd "optionalFileDelete", @row.inner_path, =>
32 | Page.cmd "optionalFileDelete", @row.inner_path + ".piecemap.msgpack", =>
33 | cb?(true)
34 |
35 | deleteFromContentJson: (cb) =>
36 | Page.cmd "fileGet", @row.content_inner_path, (res) =>
37 | data = JSON.parse(res)
38 | delete data["files_optional"][@row.file_name]
39 | delete data["files_optional"][@row.file_name+ ".piecemap.msgpack"]
40 | Page.cmd "fileWrite", [@row.content_inner_path, Text.fileEncode(data)], (res) =>
41 | cb?(res)
42 |
43 | deleteFromDataJson: (cb) =>
44 | Page.cmd "fileGet", @row.data_inner_path, (res) =>
45 | data = JSON.parse(res)
46 | delete data["file"][@row.file_name]
47 | delete data["file"][@row.file_name+ ".piecemap.msgpack"]
48 | Page.cmd "fileWrite", [@row.data_inner_path, Text.fileEncode(data)], (res) =>
49 | cb?(res)
50 |
51 | handleDelete: (cb) =>
52 | @deleteFile (res) =>
53 | @deleteFromContentJson (res) =>
54 | if not res == "ok"
55 | return cb(false)
56 | @deleteFromDataJson (res) =>
57 | if res == "ok"
58 | Page.cmd "sitePublish", {"inner_path": @row.content_inner_path}
59 | Page.list.update()
60 | cb(true)
61 |
62 | handleTitleSave: (title, cb) =>
63 | Page.cmd "fileGet", @row.data_inner_path, (res) =>
64 | data = JSON.parse(res)
65 | data["file"][@row.file_name]["title"] = title
66 | @row.title = title
67 | Page.cmd "fileWrite", [@row.data_inner_path, Text.fileEncode(data)], (res) =>
68 | if res == "ok"
69 | cb(true)
70 | Page.cmd "sitePublish", {"inner_path": @row.content_inner_path}
71 | else
72 | cb(false)
73 |
74 | handleSeedClick: =>
75 | @status = "downloading"
76 | Page.cmd "fileNeed", @row.inner_path + "|all", (res) =>
77 | console.log res
78 | Page.cmd "optionalFilePin", @row.inner_path
79 | return false
80 |
81 | handleOpenClick: =>
82 | Page.cmd "serverShowdirectory", ["site", @row.inner_path]
83 | return false
84 |
85 | handleMenuClick: =>
86 | if not @menu
87 | @menu = new Menu()
88 | @menu.items = []
89 | @menu.items.push ["Delete file", @handleMenuDeleteClick]
90 | @menu.toggle()
91 | return false
92 |
93 | handleMenuDeleteClick: =>
94 | @deleteFile()
95 | return false
96 |
97 | render: ->
98 | if @row.stats.bytes_downloaded
99 | ratio = @row.stats.uploaded / @row.stats.bytes_downloaded
100 | else
101 | ratio = 0
102 |
103 | ratio_color = @getRatioColor(ratio)
104 |
105 | if @status in ["downloading", "partial"]
106 | style = "box-shadow: inset #{@row.stats.downloaded_percent * 1.5}px 0px 0px #70fcd8"
107 | else
108 | style = ""
109 |
110 | ext = @row.file_name.toLowerCase().replace(/.*\./, "")
111 | if ext in ["mp4", "webm", "ogm"]
112 | type = "video"
113 | else
114 | type = "other"
115 |
116 | peer_num = Math.max((@row.stats.peer_seed + @row.stats.peer_leech) or 0, @row.stats.peer or 0)
117 | low_seeds = @row.stats.peer_seed <= peer_num * 0.1 and @row.stats.peer_leech >= peer_num * 0.2
118 |
119 | h("div.file.#{type}", {key: @row.id},
120 | h("div.stats", [
121 | h("div.stats-col.peers", {title: "Seeder: #{@row.stats.peer_seed}, Leecher: #{@row.stats.peer_leech}"}, [
122 | h("span.value", peer_num),
123 | h("span.icon.icon-profile", {style: if low_seeds then "background: #f57676" else "background: #666"})
124 | ]),
125 | h("div.stats-col.ratio", {title: "Hash id: #{@row.stats.hash_id}"}, h("span.value", {"style": "background-color: #{ratio_color}"}, if ratio >= 10 then ratio.toFixed(0) else ratio.toFixed(1)))
126 | h("div.stats-col.uploaded", "\u2BA5 #{Text.formatSize(@row.stats.uploaded)}")
127 | ])
128 | if type == "video"
129 | h("a.open", {href: @row.inner_path}, "\u203A")
130 | else
131 | h("a.open", {href: @row.inner_path}, h("span.icon.icon-open-new"))
132 |
133 | h("div.left-info", [
134 | if @editable_title?.editing
135 | @editable_title.render(@row.title)
136 | else
137 | h("a.title.link", {href: @row.inner_path, enterAnimation: Animation.slideDown}, @editable_title?.render(@row.title) or @row.title)
138 |
139 | h("div.details", [
140 | if @status in ["inactive", "partial"] and not @row.stats.is_pinned
141 | h("a.add", {href: "#Add", title: "Download and seed", onclick: @handleSeedClick}, "+ seed")
142 |
143 | h("span.size", {classes: {downloading: @status == "downloading", partial: @status == "partial", seeding: @status == "seeding"}, style: style}, [
144 | if @status == "seeding"
145 | h("span", "seeding: ")
146 | if @status == "downloading" or @status == "partial" then [
147 | h("span.downloaded", Text.formatSize(@row.stats.bytes_downloaded)),
148 | " of "
149 | ],
150 | Text.formatSize(@row.size)
151 | ]),
152 | if @status != "inactive"
153 | [
154 | h("a.menu-button", {href: "#Menu", onclick: Page.returnFalse, onmousedown: @handleMenuClick}, "\u22EE")
155 | if @menu then @menu.render(".menu-right")
156 | ]
157 | h("span.detail.added", {title: Time.date(@row.date_added, "long")}, Time.since(@row.date_added)),
158 | h("span.detail.uploader", [
159 | "by ",
160 | h("span.username",
161 | {title: @row.cert_user_id + ": " + @row.directory},
162 | @row.cert_user_id.split("@")[0]
163 | )
164 | ]),
165 | if @status == "seeding"
166 | h("a.detail", h("a.link.filename", {href: "#Open+directory", title: "Open directory", onclick: @handleOpenClick}, @row.file_name))
167 | else
168 | h("a.detail.filename", {title: @row.file_name}, @row.file_name)
169 | ])
170 | ])
171 | )
172 |
173 |
174 | window.File = File
--------------------------------------------------------------------------------
/css/icons.css:
--------------------------------------------------------------------------------
1 | .icon { background-repeat: no-repeat; display: inline-block; width: 32px; height: 32px; }
2 |
3 | .icon-profile { font-size: 8px; top: 0.1em; border-radius: 0.7em 0.7em 0 0; background: #888; width: 1.4em; height: 0.5em; position: relative; display: inline-block; margin-right: 4px; margin-left: 5px; }
4 | .icon-profile::before { position: absolute; content: ""; top: -1em; left: 0.31em; width: 0.8em; height: 0.85em; border-radius: 50%; background: inherit; }
5 |
6 | .icon-upload {
7 | width: 34px; height: 34px;
8 | background-image: url('data:image/svg+xml;utf8,')
9 | }
10 |
11 | .icon-video {
12 | width: 34px; height: 34px;
13 | background-image: url('data:image/svg+xml;utf8,')
14 | }
15 |
16 | .icon-gamepad {
17 | width: 34px; height: 34px;
18 | background-image: url('data:image/svg+xml;utf8,')
19 | }
20 |
21 | .icon-ipod {
22 | width: 34px; height: 34px;
23 | background-image: url('data:image/svg+xml;utf8,')
24 | }
25 |
26 | .icon-image {
27 | width: 34px; height: 34px;
28 | background-image: url('data:image/svg+xml;utf8,')
29 | }
30 |
31 | .icon-file {
32 | width: 34px; height: 34px;
33 | background-image: url('data:image/svg+xml;utf8,')
34 | }
35 |
36 | .icon-file-empty {
37 | width: 114px; height: 114px;
38 | background-image: url('data:image/svg+xml;utf8,')
39 | }
40 |
41 | .icon-open-new {
42 | width: 26px; height: 26px;
43 | background-image: url('data:image/svg+xml;utf8,')
44 | }
45 |
46 | .icon-edit {
47 | width: 16px; height: 16px; background-repeat: no-repeat; background-position: 6px center;
48 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAOVBMVEUAAAC9w8e9w8e9w8e9w8e/xMi9w8e9w8e+w8e9w8e9w8e9w8e9w8e9w8e9w8e+w8e/xMi9w8e9w8fvY4+KAAAAEnRSTlMASPv3WQbwOTCkt4/psX4YDMWr+RRCAAAAUUlEQVQY06XLORKAMAxDUTs7kA3d/7AYGju0UfffjIgoHkxm0vB5bZyxKHx9eX0FJw0Y4bcXKQ4/CTtS5yqp5GFFOjGpVGl00k1pNDIb3Nv9AHC7BOZC4ZjvAAAAAElFTkSuQmCC+d0ckOwyAMRVGHUOO0gUyd+P8f7WApz4Iki9wFmyOEATrXLZcFp5LrGogPOxKp6zfFf9fZ1/I/cY7YZSS3U6S3XFZJmGBwL+FuJX/F1K0wUUlZyZGlXgXESthTEs4B8fh7xoVUDPGYJnsfkCRarKAgz8cAKbpD6pqDPz3XB8K6HdUEeN9NAAAAAElFTkSuQmCC);
49 | }
50 |
51 | .icon-magnifier {
52 | position: absolute; display: inline-block; background: transparent; border-radius: 30px; z-index: 1;
53 | height: 9px; width: 9px; border: 1.5px solid currentColor; margin-top: 13px; pointer-events: none; margin-left: 7px;
54 | }
55 | .icon-magnifier:after { content: ""; height: 1.8px; width: 6px; background: currentColor; position: absolute; top: 10px; left: 7px; transform: rotate(45deg); }
56 |
--------------------------------------------------------------------------------
/js/utils/Animation.coffee:
--------------------------------------------------------------------------------
1 | class Animation
2 | slideDown: (elem, props) ->
3 | h = elem.offsetHeight
4 | cstyle = window.getComputedStyle(elem)
5 | margin_top = cstyle.marginTop
6 | margin_bottom = cstyle.marginBottom
7 | padding_top = cstyle.paddingTop
8 | padding_bottom = cstyle.paddingBottom
9 | border_top_width = cstyle.borderTopWidth
10 | border_bottom_width = cstyle.borderBottomWidth
11 | transition = cstyle.transition
12 |
13 | if window.Animation.shouldScrollFix(elem, props)
14 | # Keep objects in the screen at same position
15 | top_after = document.body.scrollHeight
16 | next_elem = elem.nextSibling
17 | parent = elem.parentNode
18 | parent.removeChild(elem)
19 | top_before = document.body.scrollHeight
20 | console.log("Scrollcorrection down", (top_before - top_after))
21 | window.scrollTo(window.scrollX, window.scrollY - (top_before - top_after))
22 | if next_elem
23 | parent.insertBefore(elem, next_elem)
24 | else
25 | parent.appendChild(elem)
26 | return
27 |
28 | if props.animate_scrollfix and elem.getBoundingClientRect().top > 1600
29 | # console.log "Skip down", elem
30 | return
31 |
32 | elem.style.boxSizing = "border-box"
33 | elem.style.overflow = "hidden"
34 | if not props.animate_noscale
35 | elem.style.transform = "scale(0.6)"
36 | elem.style.opacity = "0"
37 | elem.style.height = "0px"
38 | elem.style.marginTop = "0px"
39 | elem.style.marginBottom = "0px"
40 | elem.style.paddingTop = "0px"
41 | elem.style.paddingBottom = "0px"
42 | elem.style.borderTopWidth = "0px"
43 | elem.style.borderBottomWidth = "0px"
44 | elem.style.transition = "none"
45 |
46 | setTimeout (->
47 | elem.className += " animate-inout"
48 | elem.style.height = h+"px"
49 | elem.style.transform = "scale(1)"
50 | elem.style.opacity = "1"
51 | elem.style.marginTop = margin_top
52 | elem.style.marginBottom = margin_bottom
53 | elem.style.paddingTop = padding_top
54 | elem.style.paddingBottom = padding_bottom
55 | elem.style.borderTopWidth = border_top_width
56 | elem.style.borderBottomWidth = border_bottom_width
57 | ), 1
58 |
59 |
60 | elem.addEventListener "transitionend", ->
61 | elem.classList.remove("animate-inout")
62 | elem.style.transition = elem.style.transform = elem.style.opacity = elem.style.height = null
63 | elem.style.boxSizing = elem.style.marginTop = elem.style.marginBottom = null
64 | elem.style.paddingTop = elem.style.paddingBottom = elem.style.overflow = null
65 | elem.style.borderTopWidth = elem.style.borderBottomWidth = elem.style.overflow = null
66 | elem.removeEventListener "transitionend", arguments.callee, false
67 |
68 | shouldScrollFix: (elem, props) ->
69 | pos = elem.getBoundingClientRect()
70 | if props.animate_scrollfix and window.scrollY > 300 and pos.top < 0 and not document.querySelector(".noscrollfix:hover")
71 | return true
72 | else
73 | return false
74 |
75 | slideDownAnime: (elem, props) ->
76 | cstyle = window.getComputedStyle(elem)
77 | elem.style.overflowY = "hidden"
78 | anime({targets: elem, height: [0, elem.offsetHeight], easing: 'easeInOutExpo'})
79 |
80 | slideUpAnime: (elem, remove_func, props) ->
81 | elem.style.overflowY = "hidden"
82 | anime({targets: elem, height: [elem.offsetHeight, 0], complete: remove_func, easing: 'easeInOutExpo'})
83 |
84 |
85 | slideUp: (elem, remove_func, props) ->
86 | if window.Animation.shouldScrollFix(elem, props) and elem.nextSibling
87 | # Keep objects in the screen at same position
88 | top_after = document.body.scrollHeight
89 | next_elem = elem.nextSibling
90 | parent = elem.parentNode
91 | parent.removeChild(elem)
92 | top_before = document.body.scrollHeight
93 | console.log("Scrollcorrection down", (top_before - top_after))
94 | window.scrollTo(window.scrollX, window.scrollY + (top_before - top_after))
95 | if next_elem
96 | parent.insertBefore(elem, next_elem)
97 | else
98 | parent.appendChild(elem)
99 | remove_func()
100 | return
101 |
102 | if props.animate_scrollfix and elem.getBoundingClientRect().top > 1600
103 | remove_func()
104 | # console.log "Skip up", elem
105 | return
106 |
107 | elem.className += " animate-inout"
108 | elem.style.boxSizing = "border-box"
109 | elem.style.height = elem.offsetHeight+"px"
110 | elem.style.overflow = "hidden"
111 | elem.style.transform = "scale(1)"
112 | elem.style.opacity = "1"
113 | elem.style.pointerEvents = "none"
114 |
115 | setTimeout (->
116 | cstyle = window.getComputedStyle(elem)
117 | elem.style.height = "0px"
118 | elem.style.marginTop = (0-parseInt(cstyle.borderTopWidth)-parseInt(cstyle.borderBottomWidth))+"px"
119 | elem.style.marginBottom = "0px"
120 | elem.style.paddingTop = "0px"
121 | elem.style.paddingBottom = "0px"
122 | elem.style.transform = "scale(0.8)"
123 | elem.style.opacity = "0"
124 | ), 1
125 | elem.addEventListener "transitionend", (e) ->
126 | if e.propertyName == "opacity" or e.elapsedTime >= 0.6
127 | elem.removeEventListener "transitionend", arguments.callee, false
128 | setTimeout ( ->
129 | remove_func()
130 | ), 2000
131 |
132 |
133 | showRight: (elem, props) ->
134 | elem.className += " animate"
135 | elem.style.opacity = 0
136 | elem.style.transform = "TranslateX(-20px) Scale(1.01)"
137 | setTimeout (->
138 | elem.style.opacity = 1
139 | elem.style.transform = "TranslateX(0px) Scale(1)"
140 | ), 1
141 | elem.addEventListener "transitionend", ->
142 | elem.classList.remove("animate")
143 | elem.style.transform = elem.style.opacity = null
144 | elem.removeEventListener "transitionend", arguments.callee, false
145 |
146 |
147 | show: (elem, props) ->
148 | delay = arguments[arguments.length-2]?.delay*1000 or 1
149 | elem.className += " animate"
150 | elem.style.opacity = 0
151 | setTimeout (->
152 | elem.style.opacity = 1
153 | ), delay
154 | elem.addEventListener "transitionend", ->
155 | elem.classList.remove("animate")
156 | elem.style.opacity = null
157 | elem.removeEventListener "transitionend", arguments.callee, false
158 |
159 | hide: (elem, remove_func, props) ->
160 | delay = arguments[arguments.length-2]?.delay*1000 or 1
161 | elem.className += " animate"
162 | setTimeout (->
163 | elem.style.opacity = 0
164 | ), delay
165 | elem.addEventListener "transitionend", (e) ->
166 | if e.propertyName == "opacity"
167 | remove_func()
168 | elem.removeEventListener "transitionend", arguments.callee, false
169 |
170 | addVisibleClass: (elem, props) ->
171 | setTimeout ->
172 | elem.classList.add("visible")
173 |
174 | cloneAnimation: (elem, animation) ->
175 | window.requestAnimationFrame =>
176 | if elem.style.pointerEvents == "none" # Fix if animation called on cloned element
177 | elem = elem.nextSibling
178 | elem.style.position = "relative"
179 | elem.style.zIndex = "2"
180 | clone = elem.cloneNode(true)
181 | cstyle = window.getComputedStyle(elem)
182 | clone.classList.remove("loading")
183 | clone.style.position = "absolute"
184 | clone.style.zIndex = "1"
185 | clone.style.pointerEvents = "none"
186 | clone.style.animation = "none"
187 |
188 | # Check the position difference between original and cloned object
189 | elem.parentNode.insertBefore(clone, elem)
190 | cloneleft = clone.offsetLeft
191 |
192 | clone.parentNode.removeChild(clone) # Remove from dom to avoid animation
193 | clone.style.marginLeft = parseInt(cstyle.marginLeft) + elem.offsetLeft - cloneleft + "px"
194 | elem.parentNode.insertBefore(clone, elem)
195 |
196 | clone.style.animation = "#{animation} 0.8s ease-in-out forwards"
197 | setTimeout ( -> clone.remove() ), 1000
198 |
199 | flashIn: (elem) ->
200 | if elem.offsetWidth > 100
201 | @cloneAnimation(elem, "flash-in-big")
202 | else
203 | @cloneAnimation(elem, "flash-in")
204 |
205 | flashOut: (elem) ->
206 | if elem.offsetWidth > 100
207 | @cloneAnimation(elem, "flash-out-big")
208 | else
209 | @cloneAnimation(elem, "flash-out")
210 |
211 |
212 | window.Animation = new Animation()
--------------------------------------------------------------------------------
/js/lib/anime.min.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Anime v1.0.0
3 | * http://anime-js.com
4 | * JavaScript animation engine
5 | * Copyright (c) 2016 Julian Garnier
6 | * http://juliangarnier.com
7 | * Released under the MIT license
8 | */
9 | (function(r,n){"function"===typeof define&&define.amd?define([],n):"object"===typeof module&&module.exports?module.exports=n():r.anime=n()})(this,function(){var r={duration:1E3,delay:0,loop:!1,autoplay:!0,direction:"normal",easing:"easeOutElastic",elasticity:400,round:!1,begin:void 0,update:void 0,complete:void 0},n="translateX translateY translateZ rotate rotateX rotateY rotateZ scale scaleX scaleY scaleZ skewX skewY".split(" "),e=function(){return{array:function(a){return Array.isArray(a)},object:function(a){return-1<
10 | Object.prototype.toString.call(a).indexOf("Object")},html:function(a){return a instanceof NodeList||a instanceof HTMLCollection},node:function(a){return a.nodeType},svg:function(a){return a instanceof SVGElement},number:function(a){return!isNaN(parseInt(a))},string:function(a){return"string"===typeof a},func:function(a){return"function"===typeof a},undef:function(a){return"undefined"===typeof a},"null":function(a){return"null"===typeof a},hex:function(a){return/(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(a)},
11 | rgb:function(a){return/^rgb/.test(a)},rgba:function(a){return/^rgba/.test(a)},hsl:function(a){return/^hsl/.test(a)},color:function(a){return e.hex(a)||e.rgb(a)||e.rgba(a)||e.hsl(a)}}}(),z=function(){var a={},b={Sine:function(a){return 1-Math.cos(a*Math.PI/2)},Circ:function(a){return 1-Math.sqrt(1-a*a)},Elastic:function(a,b){if(0===a||1===a)return a;var f=1-Math.min(b,998)/1E3,h=a/1-1;return-(Math.pow(2,10*h)*Math.sin(2*(h-f/(2*Math.PI)*Math.asin(1))*Math.PI/f))},Back:function(a){return a*a*(3*a-2)},
12 | Bounce:function(a){for(var b,f=4;a<((b=Math.pow(2,--f))-1)/11;);return 1/Math.pow(4,3-f)-7.5625*Math.pow((3*b-2)/22-a,2)}};["Quad","Cubic","Quart","Quint","Expo"].forEach(function(a,d){b[a]=function(a){return Math.pow(a,d+2)}});Object.keys(b).forEach(function(c){var d=b[c];a["easeIn"+c]=d;a["easeOut"+c]=function(a,b){return 1-d(1-a,b)};a["easeInOut"+c]=function(a,b){return.5>a?d(2*a,b)/2:1-d(-2*a+2,b)/2}});a.linear=function(a){return a};return a}(),u=function(a){return e.string(a)?a:a+""},A=function(a){return a.replace(/([a-z])([A-Z])/g,
13 | "$1-$2").toLowerCase()},B=function(a){if(e.color(a))return!1;try{return document.querySelectorAll(a)}catch(b){return!1}},v=function(a){return a.reduce(function(a,c){return a.concat(e.array(c)?v(c):c)},[])},p=function(a){if(e.array(a))return a;e.string(a)&&(a=B(a)||a);return e.html(a)?[].slice.call(a):[a]},C=function(a,b){return a.some(function(a){return a===b})},N=function(a,b){var c={};a.forEach(function(a){var f=JSON.stringify(b.map(function(b){return a[b]}));c[f]=c[f]||[];c[f].push(a)});return Object.keys(c).map(function(a){return c[a]})},
14 | D=function(a){return a.filter(function(a,c,d){return d.indexOf(a)===c})},w=function(a){var b={},c;for(c in a)b[c]=a[c];return b},t=function(a,b){for(var c in b)a[c]=e.undef(a[c])?b[c]:a[c];return a},O=function(a){a=a.replace(/^#?([a-f\d])([a-f\d])([a-f\d])$/i,function(a,b,c,e){return b+b+c+c+e+e});var b=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(a);a=parseInt(b[1],16);var c=parseInt(b[2],16),b=parseInt(b[3],16);return"rgb("+a+","+c+","+b+")"},P=function(a){a=/hsl\((\d+),\s*([\d.]+)%,\s*([\d.]+)%\)/g.exec(a);
15 | var b=parseInt(a[1])/360,c=parseInt(a[2])/100,d=parseInt(a[3])/100;a=function(a,b,c){0>c&&(c+=1);1c?b:c<2/3?a+(b-a)*(2/3-c)*6:a};if(0==c)c=d=b=d;else var f=.5>d?d*(1+c):d+c-d*c,h=2*d-f,c=a(h,f,b+1/3),d=a(h,f,b),b=a(h,f,b-1/3);return"rgb("+255*c+","+255*d+","+255*b+")"},k=function(a){return/([\+\-]?[0-9|auto\.]+)(%|px|pt|em|rem|in|cm|mm|ex|pc|vw|vh|deg)?/.exec(a)[2]},E=function(a,b,c){return k(b)?b:-1=a.delay&&(a.begin(b),a.begin=void 0);c.current>=b.duration?(a.loop?(c.start=+new Date,"alternate"===a.direction&&y(b,!0),e.number(a.loop)&&
25 | a.loop--,c.raf=requestAnimationFrame(c.tick)):(b.ended=!0,a.complete&&a.complete(b),b.pause()),c.last=0):c.raf=requestAnimationFrame(c.tick)}}};b.seek=function(a){L(b,a/100*b.duration)};b.pause=function(){b.running=!1;cancelAnimationFrame(c.raf);X(b);var a=m.indexOf(b);-1
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU General Public License is a free, copyleft license for
11 | software and other kinds of works.
12 |
13 | The licenses for most software and other practical works are designed
14 | to take away your freedom to share and change the works. By contrast,
15 | the GNU General Public License is intended to guarantee your freedom to
16 | share and change all versions of a program--to make sure it remains free
17 | software for all its users. We, the Free Software Foundation, use the
18 | GNU General Public License for most of our software; it applies also to
19 | any other work released this way by its authors. You can apply it to
20 | your programs, too.
21 |
22 | When we speak of free software, we are referring to freedom, not
23 | price. Our General Public Licenses are designed to make sure that you
24 | have the freedom to distribute copies of free software (and charge for
25 | them if you wish), that you receive source code or can get it if you
26 | want it, that you can change the software or use pieces of it in new
27 | free programs, and that you know you can do these things.
28 |
29 | To protect your rights, we need to prevent others from denying you
30 | these rights or asking you to surrender the rights. Therefore, you have
31 | certain responsibilities if you distribute copies of the software, or if
32 | you modify it: responsibilities to respect the freedom of others.
33 |
34 | For example, if you distribute copies of such a program, whether
35 | gratis or for a fee, you must pass on to the recipients the same
36 | freedoms that you received. You must make sure that they, too, receive
37 | or can get the source code. And you must show them these terms so they
38 | know their rights.
39 |
40 | Developers that use the GNU GPL protect your rights with two steps:
41 | (1) assert copyright on the software, and (2) offer you this License
42 | giving you legal permission to copy, distribute and/or modify it.
43 |
44 | For the developers' and authors' protection, the GPL clearly explains
45 | that there is no warranty for this free software. For both users' and
46 | authors' sake, the GPL requires that modified versions be marked as
47 | changed, so that their problems will not be attributed erroneously to
48 | authors of previous versions.
49 |
50 | Some devices are designed to deny users access to install or run
51 | modified versions of the software inside them, although the manufacturer
52 | can do so. This is fundamentally incompatible with the aim of
53 | protecting users' freedom to change the software. The systematic
54 | pattern of such abuse occurs in the area of products for individuals to
55 | use, which is precisely where it is most unacceptable. Therefore, we
56 | have designed this version of the GPL to prohibit the practice for those
57 | products. If such problems arise substantially in other domains, we
58 | stand ready to extend this provision to those domains in future versions
59 | of the GPL, as needed to protect the freedom of users.
60 |
61 | Finally, every program is threatened constantly by software patents.
62 | States should not allow patents to restrict development and use of
63 | software on general-purpose computers, but in those that do, we wish to
64 | avoid the special danger that patents applied to a free program could
65 | make it effectively proprietary. To prevent this, the GPL assures that
66 | patents cannot be used to render the program non-free.
67 |
68 | The precise terms and conditions for copying, distribution and
69 | modification follow.
70 |
71 | TERMS AND CONDITIONS
72 |
73 | 0. Definitions.
74 |
75 | "This License" refers to version 3 of the GNU General Public License.
76 |
77 | "Copyright" also means copyright-like laws that apply to other kinds of
78 | works, such as semiconductor masks.
79 |
80 | "The Program" refers to any copyrightable work licensed under this
81 | License. Each licensee is addressed as "you". "Licensees" and
82 | "recipients" may be individuals or organizations.
83 |
84 | To "modify" a work means to copy from or adapt all or part of the work
85 | in a fashion requiring copyright permission, other than the making of an
86 | exact copy. The resulting work is called a "modified version" of the
87 | earlier work or a work "based on" the earlier work.
88 |
89 | A "covered work" means either the unmodified Program or a work based
90 | on the Program.
91 |
92 | To "propagate" a work means to do anything with it that, without
93 | permission, would make you directly or secondarily liable for
94 | infringement under applicable copyright law, except executing it on a
95 | computer or modifying a private copy. Propagation includes copying,
96 | distribution (with or without modification), making available to the
97 | public, and in some countries other activities as well.
98 |
99 | To "convey" a work means any kind of propagation that enables other
100 | parties to make or receive copies. Mere interaction with a user through
101 | a computer network, with no transfer of a copy, is not conveying.
102 |
103 | An interactive user interface displays "Appropriate Legal Notices"
104 | to the extent that it includes a convenient and prominently visible
105 | feature that (1) displays an appropriate copyright notice, and (2)
106 | tells the user that there is no warranty for the work (except to the
107 | extent that warranties are provided), that licensees may convey the
108 | work under this License, and how to view a copy of this License. If
109 | the interface presents a list of user commands or options, such as a
110 | menu, a prominent item in the list meets this criterion.
111 |
112 | 1. Source Code.
113 |
114 | The "source code" for a work means the preferred form of the work
115 | for making modifications to it. "Object code" means any non-source
116 | form of a work.
117 |
118 | A "Standard Interface" means an interface that either is an official
119 | standard defined by a recognized standards body, or, in the case of
120 | interfaces specified for a particular programming language, one that
121 | is widely used among developers working in that language.
122 |
123 | The "System Libraries" of an executable work include anything, other
124 | than the work as a whole, that (a) is included in the normal form of
125 | packaging a Major Component, but which is not part of that Major
126 | Component, and (b) serves only to enable use of the work with that
127 | Major Component, or to implement a Standard Interface for which an
128 | implementation is available to the public in source code form. A
129 | "Major Component", in this context, means a major essential component
130 | (kernel, window system, and so on) of the specific operating system
131 | (if any) on which the executable work runs, or a compiler used to
132 | produce the work, or an object code interpreter used to run it.
133 |
134 | The "Corresponding Source" for a work in object code form means all
135 | the source code needed to generate, install, and (for an executable
136 | work) run the object code and to modify the work, including scripts to
137 | control those activities. However, it does not include the work's
138 | System Libraries, or general-purpose tools or generally available free
139 | programs which are used unmodified in performing those activities but
140 | which are not part of the work. For example, Corresponding Source
141 | includes interface definition files associated with source files for
142 | the work, and the source code for shared libraries and dynamically
143 | linked subprograms that the work is specifically designed to require,
144 | such as by intimate data communication or control flow between those
145 | subprograms and other parts of the work.
146 |
147 | The Corresponding Source need not include anything that users
148 | can regenerate automatically from other parts of the Corresponding
149 | Source.
150 |
151 | The Corresponding Source for a work in source code form is that
152 | same work.
153 |
154 | 2. Basic Permissions.
155 |
156 | All rights granted under this License are granted for the term of
157 | copyright on the Program, and are irrevocable provided the stated
158 | conditions are met. This License explicitly affirms your unlimited
159 | permission to run the unmodified Program. The output from running a
160 | covered work is covered by this License only if the output, given its
161 | content, constitutes a covered work. This License acknowledges your
162 | rights of fair use or other equivalent, as provided by copyright law.
163 |
164 | You may make, run and propagate covered works that you do not
165 | convey, without conditions so long as your license otherwise remains
166 | in force. You may convey covered works to others for the sole purpose
167 | of having them make modifications exclusively for you, or provide you
168 | with facilities for running those works, provided that you comply with
169 | the terms of this License in conveying all material for which you do
170 | not control copyright. Those thus making or running the covered works
171 | for you must do so exclusively on your behalf, under your direction
172 | and control, on terms that prohibit them from making any copies of
173 | your copyrighted material outside their relationship with you.
174 |
175 | Conveying under any other circumstances is permitted solely under
176 | the conditions stated below. Sublicensing is not allowed; section 10
177 | makes it unnecessary.
178 |
179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
180 |
181 | No covered work shall be deemed part of an effective technological
182 | measure under any applicable law fulfilling obligations under article
183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
184 | similar laws prohibiting or restricting circumvention of such
185 | measures.
186 |
187 | When you convey a covered work, you waive any legal power to forbid
188 | circumvention of technological measures to the extent such circumvention
189 | is effected by exercising rights under this License with respect to
190 | the covered work, and you disclaim any intention to limit operation or
191 | modification of the work as a means of enforcing, against the work's
192 | users, your or third parties' legal rights to forbid circumvention of
193 | technological measures.
194 |
195 | 4. Conveying Verbatim Copies.
196 |
197 | You may convey verbatim copies of the Program's source code as you
198 | receive it, in any medium, provided that you conspicuously and
199 | appropriately publish on each copy an appropriate copyright notice;
200 | keep intact all notices stating that this License and any
201 | non-permissive terms added in accord with section 7 apply to the code;
202 | keep intact all notices of the absence of any warranty; and give all
203 | recipients a copy of this License along with the Program.
204 |
205 | You may charge any price or no price for each copy that you convey,
206 | and you may offer support or warranty protection for a fee.
207 |
208 | 5. Conveying Modified Source Versions.
209 |
210 | You may convey a work based on the Program, or the modifications to
211 | produce it from the Program, in the form of source code under the
212 | terms of section 4, provided that you also meet all of these conditions:
213 |
214 | a) The work must carry prominent notices stating that you modified
215 | it, and giving a relevant date.
216 |
217 | b) The work must carry prominent notices stating that it is
218 | released under this License and any conditions added under section
219 | 7. This requirement modifies the requirement in section 4 to
220 | "keep intact all notices".
221 |
222 | c) You must license the entire work, as a whole, under this
223 | License to anyone who comes into possession of a copy. This
224 | License will therefore apply, along with any applicable section 7
225 | additional terms, to the whole of the work, and all its parts,
226 | regardless of how they are packaged. This License gives no
227 | permission to license the work in any other way, but it does not
228 | invalidate such permission if you have separately received it.
229 |
230 | d) If the work has interactive user interfaces, each must display
231 | Appropriate Legal Notices; however, if the Program has interactive
232 | interfaces that do not display Appropriate Legal Notices, your
233 | work need not make them do so.
234 |
235 | A compilation of a covered work with other separate and independent
236 | works, which are not by their nature extensions of the covered work,
237 | and which are not combined with it such as to form a larger program,
238 | in or on a volume of a storage or distribution medium, is called an
239 | "aggregate" if the compilation and its resulting copyright are not
240 | used to limit the access or legal rights of the compilation's users
241 | beyond what the individual works permit. Inclusion of a covered work
242 | in an aggregate does not cause this License to apply to the other
243 | parts of the aggregate.
244 |
245 | 6. Conveying Non-Source Forms.
246 |
247 | You may convey a covered work in object code form under the terms
248 | of sections 4 and 5, provided that you also convey the
249 | machine-readable Corresponding Source under the terms of this License,
250 | in one of these ways:
251 |
252 | a) Convey the object code in, or embodied in, a physical product
253 | (including a physical distribution medium), accompanied by the
254 | Corresponding Source fixed on a durable physical medium
255 | customarily used for software interchange.
256 |
257 | b) Convey the object code in, or embodied in, a physical product
258 | (including a physical distribution medium), accompanied by a
259 | written offer, valid for at least three years and valid for as
260 | long as you offer spare parts or customer support for that product
261 | model, to give anyone who possesses the object code either (1) a
262 | copy of the Corresponding Source for all the software in the
263 | product that is covered by this License, on a durable physical
264 | medium customarily used for software interchange, for a price no
265 | more than your reasonable cost of physically performing this
266 | conveying of source, or (2) access to copy the
267 | Corresponding Source from a network server at no charge.
268 |
269 | c) Convey individual copies of the object code with a copy of the
270 | written offer to provide the Corresponding Source. This
271 | alternative is allowed only occasionally and noncommercially, and
272 | only if you received the object code with such an offer, in accord
273 | with subsection 6b.
274 |
275 | d) Convey the object code by offering access from a designated
276 | place (gratis or for a charge), and offer equivalent access to the
277 | Corresponding Source in the same way through the same place at no
278 | further charge. You need not require recipients to copy the
279 | Corresponding Source along with the object code. If the place to
280 | copy the object code is a network server, the Corresponding Source
281 | may be on a different server (operated by you or a third party)
282 | that supports equivalent copying facilities, provided you maintain
283 | clear directions next to the object code saying where to find the
284 | Corresponding Source. Regardless of what server hosts the
285 | Corresponding Source, you remain obligated to ensure that it is
286 | available for as long as needed to satisfy these requirements.
287 |
288 | e) Convey the object code using peer-to-peer transmission, provided
289 | you inform other peers where the object code and Corresponding
290 | Source of the work are being offered to the general public at no
291 | charge under subsection 6d.
292 |
293 | A separable portion of the object code, whose source code is excluded
294 | from the Corresponding Source as a System Library, need not be
295 | included in conveying the object code work.
296 |
297 | A "User Product" is either (1) a "consumer product", which means any
298 | tangible personal property which is normally used for personal, family,
299 | or household purposes, or (2) anything designed or sold for incorporation
300 | into a dwelling. In determining whether a product is a consumer product,
301 | doubtful cases shall be resolved in favor of coverage. For a particular
302 | product received by a particular user, "normally used" refers to a
303 | typical or common use of that class of product, regardless of the status
304 | of the particular user or of the way in which the particular user
305 | actually uses, or expects or is expected to use, the product. A product
306 | is a consumer product regardless of whether the product has substantial
307 | commercial, industrial or non-consumer uses, unless such uses represent
308 | the only significant mode of use of the product.
309 |
310 | "Installation Information" for a User Product means any methods,
311 | procedures, authorization keys, or other information required to install
312 | and execute modified versions of a covered work in that User Product from
313 | a modified version of its Corresponding Source. The information must
314 | suffice to ensure that the continued functioning of the modified object
315 | code is in no case prevented or interfered with solely because
316 | modification has been made.
317 |
318 | If you convey an object code work under this section in, or with, or
319 | specifically for use in, a User Product, and the conveying occurs as
320 | part of a transaction in which the right of possession and use of the
321 | User Product is transferred to the recipient in perpetuity or for a
322 | fixed term (regardless of how the transaction is characterized), the
323 | Corresponding Source conveyed under this section must be accompanied
324 | by the Installation Information. But this requirement does not apply
325 | if neither you nor any third party retains the ability to install
326 | modified object code on the User Product (for example, the work has
327 | been installed in ROM).
328 |
329 | The requirement to provide Installation Information does not include a
330 | requirement to continue to provide support service, warranty, or updates
331 | for a work that has been modified or installed by the recipient, or for
332 | the User Product in which it has been modified or installed. Access to a
333 | network may be denied when the modification itself materially and
334 | adversely affects the operation of the network or violates the rules and
335 | protocols for communication across the network.
336 |
337 | Corresponding Source conveyed, and Installation Information provided,
338 | in accord with this section must be in a format that is publicly
339 | documented (and with an implementation available to the public in
340 | source code form), and must require no special password or key for
341 | unpacking, reading or copying.
342 |
343 | 7. Additional Terms.
344 |
345 | "Additional permissions" are terms that supplement the terms of this
346 | License by making exceptions from one or more of its conditions.
347 | Additional permissions that are applicable to the entire Program shall
348 | be treated as though they were included in this License, to the extent
349 | that they are valid under applicable law. If additional permissions
350 | apply only to part of the Program, that part may be used separately
351 | under those permissions, but the entire Program remains governed by
352 | this License without regard to the additional permissions.
353 |
354 | When you convey a copy of a covered work, you may at your option
355 | remove any additional permissions from that copy, or from any part of
356 | it. (Additional permissions may be written to require their own
357 | removal in certain cases when you modify the work.) You may place
358 | additional permissions on material, added by you to a covered work,
359 | for which you have or can give appropriate copyright permission.
360 |
361 | Notwithstanding any other provision of this License, for material you
362 | add to a covered work, you may (if authorized by the copyright holders of
363 | that material) supplement the terms of this License with terms:
364 |
365 | a) Disclaiming warranty or limiting liability differently from the
366 | terms of sections 15 and 16 of this License; or
367 |
368 | b) Requiring preservation of specified reasonable legal notices or
369 | author attributions in that material or in the Appropriate Legal
370 | Notices displayed by works containing it; or
371 |
372 | c) Prohibiting misrepresentation of the origin of that material, or
373 | requiring that modified versions of such material be marked in
374 | reasonable ways as different from the original version; or
375 |
376 | d) Limiting the use for publicity purposes of names of licensors or
377 | authors of the material; or
378 |
379 | e) Declining to grant rights under trademark law for use of some
380 | trade names, trademarks, or service marks; or
381 |
382 | f) Requiring indemnification of licensors and authors of that
383 | material by anyone who conveys the material (or modified versions of
384 | it) with contractual assumptions of liability to the recipient, for
385 | any liability that these contractual assumptions directly impose on
386 | those licensors and authors.
387 |
388 | All other non-permissive additional terms are considered "further
389 | restrictions" within the meaning of section 10. If the Program as you
390 | received it, or any part of it, contains a notice stating that it is
391 | governed by this License along with a term that is a further
392 | restriction, you may remove that term. If a license document contains
393 | a further restriction but permits relicensing or conveying under this
394 | License, you may add to a covered work material governed by the terms
395 | of that license document, provided that the further restriction does
396 | not survive such relicensing or conveying.
397 |
398 | If you add terms to a covered work in accord with this section, you
399 | must place, in the relevant source files, a statement of the
400 | additional terms that apply to those files, or a notice indicating
401 | where to find the applicable terms.
402 |
403 | Additional terms, permissive or non-permissive, may be stated in the
404 | form of a separately written license, or stated as exceptions;
405 | the above requirements apply either way.
406 |
407 | 8. Termination.
408 |
409 | You may not propagate or modify a covered work except as expressly
410 | provided under this License. Any attempt otherwise to propagate or
411 | modify it is void, and will automatically terminate your rights under
412 | this License (including any patent licenses granted under the third
413 | paragraph of section 11).
414 |
415 | However, if you cease all violation of this License, then your
416 | license from a particular copyright holder is reinstated (a)
417 | provisionally, unless and until the copyright holder explicitly and
418 | finally terminates your license, and (b) permanently, if the copyright
419 | holder fails to notify you of the violation by some reasonable means
420 | prior to 60 days after the cessation.
421 |
422 | Moreover, your license from a particular copyright holder is
423 | reinstated permanently if the copyright holder notifies you of the
424 | violation by some reasonable means, this is the first time you have
425 | received notice of violation of this License (for any work) from that
426 | copyright holder, and you cure the violation prior to 30 days after
427 | your receipt of the notice.
428 |
429 | Termination of your rights under this section does not terminate the
430 | licenses of parties who have received copies or rights from you under
431 | this License. If your rights have been terminated and not permanently
432 | reinstated, you do not qualify to receive new licenses for the same
433 | material under section 10.
434 |
435 | 9. Acceptance Not Required for Having Copies.
436 |
437 | You are not required to accept this License in order to receive or
438 | run a copy of the Program. Ancillary propagation of a covered work
439 | occurring solely as a consequence of using peer-to-peer transmission
440 | to receive a copy likewise does not require acceptance. However,
441 | nothing other than this License grants you permission to propagate or
442 | modify any covered work. These actions infringe copyright if you do
443 | not accept this License. Therefore, by modifying or propagating a
444 | covered work, you indicate your acceptance of this License to do so.
445 |
446 | 10. Automatic Licensing of Downstream Recipients.
447 |
448 | Each time you convey a covered work, the recipient automatically
449 | receives a license from the original licensors, to run, modify and
450 | propagate that work, subject to this License. You are not responsible
451 | for enforcing compliance by third parties with this License.
452 |
453 | An "entity transaction" is a transaction transferring control of an
454 | organization, or substantially all assets of one, or subdividing an
455 | organization, or merging organizations. If propagation of a covered
456 | work results from an entity transaction, each party to that
457 | transaction who receives a copy of the work also receives whatever
458 | licenses to the work the party's predecessor in interest had or could
459 | give under the previous paragraph, plus a right to possession of the
460 | Corresponding Source of the work from the predecessor in interest, if
461 | the predecessor has it or can get it with reasonable efforts.
462 |
463 | You may not impose any further restrictions on the exercise of the
464 | rights granted or affirmed under this License. For example, you may
465 | not impose a license fee, royalty, or other charge for exercise of
466 | rights granted under this License, and you may not initiate litigation
467 | (including a cross-claim or counterclaim in a lawsuit) alleging that
468 | any patent claim is infringed by making, using, selling, offering for
469 | sale, or importing the Program or any portion of it.
470 |
471 | 11. Patents.
472 |
473 | A "contributor" is a copyright holder who authorizes use under this
474 | License of the Program or a work on which the Program is based. The
475 | work thus licensed is called the contributor's "contributor version".
476 |
477 | A contributor's "essential patent claims" are all patent claims
478 | owned or controlled by the contributor, whether already acquired or
479 | hereafter acquired, that would be infringed by some manner, permitted
480 | by this License, of making, using, or selling its contributor version,
481 | but do not include claims that would be infringed only as a
482 | consequence of further modification of the contributor version. For
483 | purposes of this definition, "control" includes the right to grant
484 | patent sublicenses in a manner consistent with the requirements of
485 | this License.
486 |
487 | Each contributor grants you a non-exclusive, worldwide, royalty-free
488 | patent license under the contributor's essential patent claims, to
489 | make, use, sell, offer for sale, import and otherwise run, modify and
490 | propagate the contents of its contributor version.
491 |
492 | In the following three paragraphs, a "patent license" is any express
493 | agreement or commitment, however denominated, not to enforce a patent
494 | (such as an express permission to practice a patent or covenant not to
495 | sue for patent infringement). To "grant" such a patent license to a
496 | party means to make such an agreement or commitment not to enforce a
497 | patent against the party.
498 |
499 | If you convey a covered work, knowingly relying on a patent license,
500 | and the Corresponding Source of the work is not available for anyone
501 | to copy, free of charge and under the terms of this License, through a
502 | publicly available network server or other readily accessible means,
503 | then you must either (1) cause the Corresponding Source to be so
504 | available, or (2) arrange to deprive yourself of the benefit of the
505 | patent license for this particular work, or (3) arrange, in a manner
506 | consistent with the requirements of this License, to extend the patent
507 | license to downstream recipients. "Knowingly relying" means you have
508 | actual knowledge that, but for the patent license, your conveying the
509 | covered work in a country, or your recipient's use of the covered work
510 | in a country, would infringe one or more identifiable patents in that
511 | country that you have reason to believe are valid.
512 |
513 | If, pursuant to or in connection with a single transaction or
514 | arrangement, you convey, or propagate by procuring conveyance of, a
515 | covered work, and grant a patent license to some of the parties
516 | receiving the covered work authorizing them to use, propagate, modify
517 | or convey a specific copy of the covered work, then the patent license
518 | you grant is automatically extended to all recipients of the covered
519 | work and works based on it.
520 |
521 | A patent license is "discriminatory" if it does not include within
522 | the scope of its coverage, prohibits the exercise of, or is
523 | conditioned on the non-exercise of one or more of the rights that are
524 | specifically granted under this License. You may not convey a covered
525 | work if you are a party to an arrangement with a third party that is
526 | in the business of distributing software, under which you make payment
527 | to the third party based on the extent of your activity of conveying
528 | the work, and under which the third party grants, to any of the
529 | parties who would receive the covered work from you, a discriminatory
530 | patent license (a) in connection with copies of the covered work
531 | conveyed by you (or copies made from those copies), or (b) primarily
532 | for and in connection with specific products or compilations that
533 | contain the covered work, unless you entered into that arrangement,
534 | or that patent license was granted, prior to 28 March 2007.
535 |
536 | Nothing in this License shall be construed as excluding or limiting
537 | any implied license or other defenses to infringement that may
538 | otherwise be available to you under applicable patent law.
539 |
540 | 12. No Surrender of Others' Freedom.
541 |
542 | If conditions are imposed on you (whether by court order, agreement or
543 | otherwise) that contradict the conditions of this License, they do not
544 | excuse you from the conditions of this License. If you cannot convey a
545 | covered work so as to satisfy simultaneously your obligations under this
546 | License and any other pertinent obligations, then as a consequence you may
547 | not convey it at all. For example, if you agree to terms that obligate you
548 | to collect a royalty for further conveying from those to whom you convey
549 | the Program, the only way you could satisfy both those terms and this
550 | License would be to refrain entirely from conveying the Program.
551 |
552 | 13. Use with the GNU Affero General Public License.
553 |
554 | Notwithstanding any other provision of this License, you have
555 | permission to link or combine any covered work with a work licensed
556 | under version 3 of the GNU Affero General Public License into a single
557 | combined work, and to convey the resulting work. The terms of this
558 | License will continue to apply to the part which is the covered work,
559 | but the special requirements of the GNU Affero General Public License,
560 | section 13, concerning interaction through a network will apply to the
561 | combination as such.
562 |
563 | 14. Revised Versions of this License.
564 |
565 | The Free Software Foundation may publish revised and/or new versions of
566 | the GNU General Public License from time to time. Such new versions will
567 | be similar in spirit to the present version, but may differ in detail to
568 | address new problems or concerns.
569 |
570 | Each version is given a distinguishing version number. If the
571 | Program specifies that a certain numbered version of the GNU General
572 | Public License "or any later version" applies to it, you have the
573 | option of following the terms and conditions either of that numbered
574 | version or of any later version published by the Free Software
575 | Foundation. If the Program does not specify a version number of the
576 | GNU General Public License, you may choose any version ever published
577 | by the Free Software Foundation.
578 |
579 | If the Program specifies that a proxy can decide which future
580 | versions of the GNU General Public License can be used, that proxy's
581 | public statement of acceptance of a version permanently authorizes you
582 | to choose that version for the Program.
583 |
584 | Later license versions may give you additional or different
585 | permissions. However, no additional obligations are imposed on any
586 | author or copyright holder as a result of your choosing to follow a
587 | later version.
588 |
589 | 15. Disclaimer of Warranty.
590 |
591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
599 |
600 | 16. Limitation of Liability.
601 |
602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
610 | SUCH DAMAGES.
611 |
612 | 17. Interpretation of Sections 15 and 16.
613 |
614 | If the disclaimer of warranty and limitation of liability provided
615 | above cannot be given local legal effect according to their terms,
616 | reviewing courts shall apply local law that most closely approximates
617 | an absolute waiver of all civil liability in connection with the
618 | Program, unless a warranty or assumption of liability accompanies a
619 | copy of the Program in return for a fee.
620 |
621 | END OF TERMS AND CONDITIONS
622 |
623 | How to Apply These Terms to Your New Programs
624 |
625 | If you develop a new program, and you want it to be of the greatest
626 | possible use to the public, the best way to achieve this is to make it
627 | free software which everyone can redistribute and change under these terms.
628 |
629 | To do so, attach the following notices to the program. It is safest
630 | to attach them to the start of each source file to most effectively
631 | state the exclusion of warranty; and each file should have at least
632 | the "copyright" line and a pointer to where the full notice is found.
633 |
634 | {one line to give the program's name and a brief idea of what it does.}
635 | Copyright (C) {year} {name of author}
636 |
637 | This program is free software: you can redistribute it and/or modify
638 | it under the terms of the GNU General Public License as published by
639 | the Free Software Foundation, either version 3 of the License, or
640 | (at your option) any later version.
641 |
642 | This program is distributed in the hope that it will be useful,
643 | but WITHOUT ANY WARRANTY; without even the implied warranty of
644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
645 | GNU General Public License for more details.
646 |
647 | You should have received a copy of the GNU General Public License
648 | along with this program. If not, see .
649 |
650 | Also add information on how to contact you by electronic and paper mail.
651 |
652 | If the program does terminal interaction, make it output a short
653 | notice like this when it starts in an interactive mode:
654 |
655 | {project} Copyright (C) {year} {fullname}
656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
657 | This is free software, and you are welcome to redistribute it
658 | under certain conditions; type `show c' for details.
659 |
660 | The hypothetical commands `show w' and `show c' should show the appropriate
661 | parts of the General Public License. Of course, your program's commands
662 | might be different; for a GUI interface, you would use an "about box".
663 |
664 | You should also get your employer (if you work as a programmer) or school,
665 | if any, to sign a "copyright disclaimer" for the program, if necessary.
666 | For more information on this, and how to apply and follow the GNU GPL, see
667 | .
668 |
669 | The GNU General Public License does not permit incorporating your program
670 | into proprietary programs. If your program is a subroutine library, you
671 | may consider it more useful to permit linking proprietary applications with
672 | the library. If this is what you want to do, use the GNU Lesser General
673 | Public License instead of this License. But first, please read
674 | .
675 |
--------------------------------------------------------------------------------
/js/lib/maquette.js:
--------------------------------------------------------------------------------
1 | (function (root, factory) {
2 | if (typeof define === 'function' && define.amd) {
3 | // AMD. Register as an anonymous module.
4 | define(['exports'], factory);
5 | } else if (typeof exports === 'object' && typeof exports.nodeName !== 'string') {
6 | // CommonJS
7 | factory(exports);
8 | } else {
9 | // Browser globals
10 | factory(root.maquette = {});
11 | }
12 | }(this, function (exports) {
13 | 'use strict';
14 | // Comment that is displayed in the API documentation for the maquette module:
15 | /**
16 | * Welcome to the API documentation of the **maquette** library.
17 | *
18 | * [[http://maquettejs.org/|To the maquette homepage]]
19 | */
20 | Object.defineProperty(exports, '__esModule', { value: true });
21 | var NAMESPACE_W3 = 'http://www.w3.org/';
22 | var NAMESPACE_SVG = NAMESPACE_W3 + '2000/svg';
23 | var NAMESPACE_XLINK = NAMESPACE_W3 + '1999/xlink';
24 | // Utilities
25 | var emptyArray = [];
26 | var extend = function (base, overrides) {
27 | var result = {};
28 | Object.keys(base).forEach(function (key) {
29 | result[key] = base[key];
30 | });
31 | if (overrides) {
32 | Object.keys(overrides).forEach(function (key) {
33 | result[key] = overrides[key];
34 | });
35 | }
36 | return result;
37 | };
38 | // Hyperscript helper functions
39 | var same = function (vnode1, vnode2) {
40 | if (vnode1.vnodeSelector !== vnode2.vnodeSelector) {
41 | return false;
42 | }
43 | if (vnode1.properties && vnode2.properties) {
44 | if (vnode1.properties.key !== vnode2.properties.key) {
45 | return false;
46 | }
47 | return vnode1.properties.bind === vnode2.properties.bind;
48 | }
49 | return !vnode1.properties && !vnode2.properties;
50 | };
51 | var toTextVNode = function (data) {
52 | return {
53 | vnodeSelector: '',
54 | properties: undefined,
55 | children: undefined,
56 | text: data.toString(),
57 | domNode: null
58 | };
59 | };
60 | var appendChildren = function (parentSelector, insertions, main) {
61 | for (var i = 0, length_1 = insertions.length; i < length_1; i++) {
62 | var item = insertions[i];
63 | if (Array.isArray(item)) {
64 | appendChildren(parentSelector, item, main);
65 | } else {
66 | if (item !== null && item !== undefined) {
67 | if (!item.hasOwnProperty('vnodeSelector')) {
68 | item = toTextVNode(item);
69 | }
70 | main.push(item);
71 | }
72 | }
73 | }
74 | };
75 | // Render helper functions
76 | var missingTransition = function () {
77 | throw new Error('Provide a transitions object to the projectionOptions to do animations');
78 | };
79 | var DEFAULT_PROJECTION_OPTIONS = {
80 | namespace: undefined,
81 | eventHandlerInterceptor: undefined,
82 | styleApplyer: function (domNode, styleName, value) {
83 | // Provides a hook to add vendor prefixes for browsers that still need it.
84 | domNode.style[styleName] = value;
85 | },
86 | transitions: {
87 | enter: missingTransition,
88 | exit: missingTransition
89 | }
90 | };
91 | var applyDefaultProjectionOptions = function (projectorOptions) {
92 | return extend(DEFAULT_PROJECTION_OPTIONS, projectorOptions);
93 | };
94 | var checkStyleValue = function (styleValue) {
95 | if (typeof styleValue !== 'string') {
96 | throw new Error('Style values must be strings');
97 | }
98 | };
99 | var setProperties = function (domNode, properties, projectionOptions) {
100 | if (!properties) {
101 | return;
102 | }
103 | var eventHandlerInterceptor = projectionOptions.eventHandlerInterceptor;
104 | var propNames = Object.keys(properties);
105 | var propCount = propNames.length;
106 | var _loop_1 = function (i) {
107 | var propName = propNames[i];
108 | /* tslint:disable:no-var-keyword: edge case */
109 | var propValue = properties[propName];
110 | /* tslint:enable:no-var-keyword */
111 | if (propName === 'className') {
112 | throw new Error('Property "className" is not supported, use "class".');
113 | } else if (propName === 'class') {
114 | propValue.split(/\s+/).forEach(function (token) {
115 | return domNode.classList.add(token);
116 | });
117 | } else if (propName === 'classes') {
118 | // object with string keys and boolean values
119 | var classNames = Object.keys(propValue);
120 | var classNameCount = classNames.length;
121 | for (var j = 0; j < classNameCount; j++) {
122 | var className = classNames[j];
123 | if (propValue[className]) {
124 | domNode.classList.add(className);
125 | }
126 | }
127 | } else if (propName === 'styles') {
128 | // object with string keys and string (!) values
129 | var styleNames = Object.keys(propValue);
130 | var styleCount = styleNames.length;
131 | for (var j = 0; j < styleCount; j++) {
132 | var styleName = styleNames[j];
133 | var styleValue = propValue[styleName];
134 | if (styleValue) {
135 | checkStyleValue(styleValue);
136 | projectionOptions.styleApplyer(domNode, styleName, styleValue);
137 | }
138 | }
139 | } else if (propName !== 'key' && propValue !== null && propValue !== undefined) {
140 | var type = typeof propValue;
141 | if (type === 'function') {
142 | if (propName.lastIndexOf('on', 0) === 0) {
143 | if (eventHandlerInterceptor) {
144 | propValue = eventHandlerInterceptor(propName, propValue, domNode, properties); // intercept eventhandlers
145 | }
146 | if (propName === 'oninput') {
147 | (function () {
148 | // record the evt.target.value, because IE and Edge sometimes do a requestAnimationFrame between changing value and running oninput
149 | var oldPropValue = propValue;
150 | propValue = function (evt) {
151 | oldPropValue.apply(this, [evt]);
152 | evt.target['oninput-value'] = evt.target.value; // may be HTMLTextAreaElement as well
153 | };
154 | }());
155 | }
156 | domNode[propName] = propValue;
157 | }
158 | } else if (type === 'string' && propName !== 'value' && propName !== 'innerHTML') {
159 | if (projectionOptions.namespace === NAMESPACE_SVG && propName === 'href') {
160 | domNode.setAttributeNS(NAMESPACE_XLINK, propName, propValue);
161 | } else {
162 | domNode.setAttribute(propName, propValue);
163 | }
164 | } else {
165 | domNode[propName] = propValue;
166 | }
167 | }
168 | };
169 | for (var i = 0; i < propCount; i++) {
170 | _loop_1(i);
171 | }
172 | };
173 | var updateProperties = function (domNode, previousProperties, properties, projectionOptions) {
174 | if (!properties) {
175 | return;
176 | }
177 | var propertiesUpdated = false;
178 | var propNames = Object.keys(properties);
179 | var propCount = propNames.length;
180 | for (var i = 0; i < propCount; i++) {
181 | var propName = propNames[i];
182 | // assuming that properties will be nullified instead of missing is by design
183 | var propValue = properties[propName];
184 | var previousValue = previousProperties[propName];
185 | if (propName === 'class') {
186 | if (previousValue !== propValue) {
187 | throw new Error('"class" property may not be updated. Use the "classes" property for conditional css classes.');
188 | }
189 | } else if (propName === 'classes') {
190 | var classList = domNode.classList;
191 | var classNames = Object.keys(propValue);
192 | var classNameCount = classNames.length;
193 | for (var j = 0; j < classNameCount; j++) {
194 | var className = classNames[j];
195 | var on = !!propValue[className];
196 | var previousOn = !!previousValue[className];
197 | if (on === previousOn) {
198 | continue;
199 | }
200 | propertiesUpdated = true;
201 | if (on) {
202 | classList.add(className);
203 | } else {
204 | classList.remove(className);
205 | }
206 | }
207 | } else if (propName === 'styles') {
208 | var styleNames = Object.keys(propValue);
209 | var styleCount = styleNames.length;
210 | for (var j = 0; j < styleCount; j++) {
211 | var styleName = styleNames[j];
212 | var newStyleValue = propValue[styleName];
213 | var oldStyleValue = previousValue[styleName];
214 | if (newStyleValue === oldStyleValue) {
215 | continue;
216 | }
217 | propertiesUpdated = true;
218 | if (newStyleValue) {
219 | checkStyleValue(newStyleValue);
220 | projectionOptions.styleApplyer(domNode, styleName, newStyleValue);
221 | } else {
222 | projectionOptions.styleApplyer(domNode, styleName, '');
223 | }
224 | }
225 | } else {
226 | if (!propValue && typeof previousValue === 'string') {
227 | propValue = '';
228 | }
229 | if (propName === 'value') {
230 | var domValue = domNode[propName];
231 | if (domValue !== propValue // The 'value' in the DOM tree !== newValue
232 | && (domNode['oninput-value'] ? domValue === domNode['oninput-value'] // If the last reported value to 'oninput' does not match domValue, do nothing and wait for oninput
233 | : propValue !== previousValue // Only update the value if the vdom changed
234 | )) {
235 | domNode[propName] = propValue;
236 | // Reset the value, even if the virtual DOM did not change
237 | domNode['oninput-value'] = undefined;
238 | }
239 | // else do not update the domNode, otherwise the cursor position would be changed
240 | if (propValue !== previousValue) {
241 | propertiesUpdated = true;
242 | }
243 | } else if (propValue !== previousValue) {
244 | var type = typeof propValue;
245 | if (type === 'function') {
246 | throw new Error('Functions may not be updated on subsequent renders (property: ' + propName + '). Hint: declare event handler functions outside the render() function.');
247 | }
248 | if (type === 'string' && propName !== 'innerHTML') {
249 | if (projectionOptions.namespace === NAMESPACE_SVG && propName === 'href') {
250 | domNode.setAttributeNS(NAMESPACE_XLINK, propName, propValue);
251 | } else if (propName === 'role' && propValue === '') {
252 | domNode.removeAttribute(propName);
253 | } else {
254 | domNode.setAttribute(propName, propValue);
255 | }
256 | } else {
257 | if (domNode[propName] !== propValue) {
258 | domNode[propName] = propValue;
259 | }
260 | }
261 | propertiesUpdated = true;
262 | }
263 | }
264 | }
265 | return propertiesUpdated;
266 | };
267 | var findIndexOfChild = function (children, sameAs, start) {
268 | if (sameAs.vnodeSelector !== '') {
269 | // Never scan for text-nodes
270 | for (var i = start; i < children.length; i++) {
271 | if (same(children[i], sameAs)) {
272 | return i;
273 | }
274 | }
275 | }
276 | return -1;
277 | };
278 | var nodeAdded = function (vNode, transitions) {
279 | if (vNode.properties) {
280 | var enterAnimation = vNode.properties.enterAnimation;
281 | if (enterAnimation) {
282 | if (typeof enterAnimation === 'function') {
283 | enterAnimation(vNode.domNode, vNode.properties);
284 | } else {
285 | transitions.enter(vNode.domNode, vNode.properties, enterAnimation);
286 | }
287 | }
288 | }
289 | };
290 | var nodeToRemove = function (vNode, transitions) {
291 | var domNode = vNode.domNode;
292 | if (vNode.properties) {
293 | var exitAnimation = vNode.properties.exitAnimation;
294 | if (exitAnimation) {
295 | domNode.style.pointerEvents = 'none';
296 | var removeDomNode = function () {
297 | if (domNode.parentNode) {
298 | domNode.parentNode.removeChild(domNode);
299 | }
300 | };
301 | if (typeof exitAnimation === 'function') {
302 | exitAnimation(domNode, removeDomNode, vNode.properties);
303 | return;
304 | } else {
305 | transitions.exit(vNode.domNode, vNode.properties, exitAnimation, removeDomNode);
306 | return;
307 | }
308 | }
309 | }
310 | if (domNode.parentNode) {
311 | domNode.parentNode.removeChild(domNode);
312 | }
313 | };
314 | var checkDistinguishable = function (childNodes, indexToCheck, parentVNode, operation) {
315 | var childNode = childNodes[indexToCheck];
316 | if (childNode.vnodeSelector === '') {
317 | return; // Text nodes need not be distinguishable
318 | }
319 | var properties = childNode.properties;
320 | var key = properties ? properties.key === undefined ? properties.bind : properties.key : undefined;
321 | if (!key) {
322 | for (var i = 0; i < childNodes.length; i++) {
323 | if (i !== indexToCheck) {
324 | var node = childNodes[i];
325 | if (same(node, childNode)) {
326 | if (operation === 'added') {
327 | throw new Error(parentVNode.vnodeSelector + ' had a ' + childNode.vnodeSelector + ' child ' + 'added, but there is now more than one. You must add unique key properties to make them distinguishable.');
328 | } else {
329 | throw new Error(parentVNode.vnodeSelector + ' had a ' + childNode.vnodeSelector + ' child ' + 'removed, but there were more than one. You must add unique key properties to make them distinguishable.');
330 | }
331 | }
332 | }
333 | }
334 | }
335 | };
336 | var createDom;
337 | var updateDom;
338 | var updateChildren = function (vnode, domNode, oldChildren, newChildren, projectionOptions) {
339 | if (oldChildren === newChildren) {
340 | return false;
341 | }
342 | oldChildren = oldChildren || emptyArray;
343 | newChildren = newChildren || emptyArray;
344 | var oldChildrenLength = oldChildren.length;
345 | var newChildrenLength = newChildren.length;
346 | var transitions = projectionOptions.transitions;
347 | var oldIndex = 0;
348 | var newIndex = 0;
349 | var i;
350 | var textUpdated = false;
351 | while (newIndex < newChildrenLength) {
352 | var oldChild = oldIndex < oldChildrenLength ? oldChildren[oldIndex] : undefined;
353 | var newChild = newChildren[newIndex];
354 | if (oldChild !== undefined && same(oldChild, newChild)) {
355 | textUpdated = updateDom(oldChild, newChild, projectionOptions) || textUpdated;
356 | oldIndex++;
357 | } else {
358 | var findOldIndex = findIndexOfChild(oldChildren, newChild, oldIndex + 1);
359 | if (findOldIndex >= 0) {
360 | // Remove preceding missing children
361 | for (i = oldIndex; i < findOldIndex; i++) {
362 | nodeToRemove(oldChildren[i], transitions);
363 | checkDistinguishable(oldChildren, i, vnode, 'removed');
364 | }
365 | textUpdated = updateDom(oldChildren[findOldIndex], newChild, projectionOptions) || textUpdated;
366 | oldIndex = findOldIndex + 1;
367 | } else {
368 | // New child
369 | createDom(newChild, domNode, oldIndex < oldChildrenLength ? oldChildren[oldIndex].domNode : undefined, projectionOptions);
370 | nodeAdded(newChild, transitions);
371 | checkDistinguishable(newChildren, newIndex, vnode, 'added');
372 | }
373 | }
374 | newIndex++;
375 | }
376 | if (oldChildrenLength > oldIndex) {
377 | // Remove child fragments
378 | for (i = oldIndex; i < oldChildrenLength; i++) {
379 | nodeToRemove(oldChildren[i], transitions);
380 | checkDistinguishable(oldChildren, i, vnode, 'removed');
381 | }
382 | }
383 | return textUpdated;
384 | };
385 | var addChildren = function (domNode, children, projectionOptions) {
386 | if (!children) {
387 | return;
388 | }
389 | for (var i = 0; i < children.length; i++) {
390 | createDom(children[i], domNode, undefined, projectionOptions);
391 | }
392 | };
393 | var initPropertiesAndChildren = function (domNode, vnode, projectionOptions) {
394 | addChildren(domNode, vnode.children, projectionOptions);
395 | // children before properties, needed for value property of