Merge pull request #99 from zeronet-conservancy/cleanupcoffee
[cleanup] get rid of old coffescript remnants nobody develops in coffescript anymore and some auto-generated js files are already modified . and we're eventually deprecating all the current js api so keeping pretense that the coffeescript source is still relevant refs #93
This commit is contained in:
commit
2d1ffc59ff
52 changed files with 3 additions and 5017 deletions
|
@ -1,23 +0,0 @@
|
||||||
class Class
|
|
||||||
trace: true
|
|
||||||
|
|
||||||
log: (args...) ->
|
|
||||||
return unless @trace
|
|
||||||
return if typeof console is 'undefined'
|
|
||||||
args.unshift("[#{@.constructor.name}]")
|
|
||||||
console.log(args...)
|
|
||||||
@
|
|
||||||
|
|
||||||
logStart: (name, args...) ->
|
|
||||||
return unless @trace
|
|
||||||
@logtimers or= {}
|
|
||||||
@logtimers[name] = +(new Date)
|
|
||||||
@log "#{name}", args..., "(started)" if args.length > 0
|
|
||||||
@
|
|
||||||
|
|
||||||
logEnd: (name, args...) ->
|
|
||||||
ms = +(new Date)-@logtimers[name]
|
|
||||||
@log "#{name}", args..., "(Done in #{ms}ms)"
|
|
||||||
@
|
|
||||||
|
|
||||||
window.Class = Class
|
|
|
@ -1,201 +0,0 @@
|
||||||
class Console extends Class
|
|
||||||
constructor: (@sidebar) ->
|
|
||||||
@tag = null
|
|
||||||
@opened = false
|
|
||||||
@filter = null
|
|
||||||
@tab_types = [
|
|
||||||
{title: "All", filter: ""},
|
|
||||||
{title: "Info", filter: "INFO"},
|
|
||||||
{title: "Warning", filter: "WARNING"},
|
|
||||||
{title: "Error", filter: "ERROR"}
|
|
||||||
]
|
|
||||||
@read_size = 32 * 1024
|
|
||||||
@tab_active = ""
|
|
||||||
#@filter = @sidebar.wrapper.site_info.address_short
|
|
||||||
handleMessageWebsocket_original = @sidebar.wrapper.handleMessageWebsocket
|
|
||||||
@sidebar.wrapper.handleMessageWebsocket = (message) =>
|
|
||||||
if message.cmd == "logLineAdd" and message.params.stream_id == @stream_id
|
|
||||||
@addLines(message.params.lines)
|
|
||||||
else
|
|
||||||
handleMessageWebsocket_original(message)
|
|
||||||
|
|
||||||
$(window).on "hashchange", =>
|
|
||||||
if window.top.location.hash.startsWith("#ZeroNet:Console")
|
|
||||||
@open()
|
|
||||||
|
|
||||||
if window.top.location.hash.startsWith("#ZeroNet:Console")
|
|
||||||
setTimeout (=> @open()), 10
|
|
||||||
|
|
||||||
createHtmltag: ->
|
|
||||||
if not @container
|
|
||||||
@container = $("""
|
|
||||||
<div class="console-container">
|
|
||||||
<div class="console">
|
|
||||||
<div class="console-top">
|
|
||||||
<div class="console-tabs"></div>
|
|
||||||
<div class="console-text">Loading...</div>
|
|
||||||
</div>
|
|
||||||
<div class="console-middle">
|
|
||||||
<div class="mynode"></div>
|
|
||||||
<div class="peers">
|
|
||||||
<div class="peer"><div class="line"></div><a href="#" class="icon">\u25BD</div></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
""")
|
|
||||||
@text = @container.find(".console-text")
|
|
||||||
@text_elem = @text[0]
|
|
||||||
@tabs = @container.find(".console-tabs")
|
|
||||||
|
|
||||||
@text.on "mousewheel", (e) => # Stop animation on manual scrolling
|
|
||||||
if e.originalEvent.deltaY < 0
|
|
||||||
@text.stop()
|
|
||||||
RateLimit 300, @checkTextIsBottom
|
|
||||||
|
|
||||||
@text.is_bottom = true
|
|
||||||
|
|
||||||
@container.appendTo(document.body)
|
|
||||||
@tag = @container.find(".console")
|
|
||||||
for tab_type in @tab_types
|
|
||||||
tab = $("<a></a>", {href: "#", "data-filter": tab_type.filter, "data-title": tab_type.title}).text(tab_type.title)
|
|
||||||
if tab_type.filter == @tab_active
|
|
||||||
tab.addClass("active")
|
|
||||||
tab.on("click", @handleTabClick)
|
|
||||||
if window.top.location.hash.endsWith(tab_type.title)
|
|
||||||
@log "Triggering click on", tab
|
|
||||||
tab.trigger("click")
|
|
||||||
@tabs.append(tab)
|
|
||||||
|
|
||||||
@container.on "mousedown touchend touchcancel", (e) =>
|
|
||||||
if e.target != e.currentTarget
|
|
||||||
return true
|
|
||||||
@log "closing"
|
|
||||||
if $(document.body).hasClass("body-console")
|
|
||||||
@close()
|
|
||||||
return true
|
|
||||||
|
|
||||||
@loadConsoleText()
|
|
||||||
|
|
||||||
checkTextIsBottom: =>
|
|
||||||
@text.is_bottom = Math.round(@text_elem.scrollTop + @text_elem.clientHeight) >= @text_elem.scrollHeight - 15
|
|
||||||
|
|
||||||
toColor: (text, saturation=60, lightness=70) ->
|
|
||||||
hash = 0
|
|
||||||
for i in [0..text.length-1]
|
|
||||||
hash += text.charCodeAt(i)*i
|
|
||||||
hash = hash % 1777
|
|
||||||
return "hsl(" + (hash % 360) + ",#{saturation}%,#{lightness}%)";
|
|
||||||
|
|
||||||
formatLine: (line) =>
|
|
||||||
match = line.match(/(\[.*?\])[ ]+(.*?)[ ]+(.*?)[ ]+(.*)/)
|
|
||||||
if not match
|
|
||||||
return line.replace(/\</g, "<").replace(/\>/g, ">")
|
|
||||||
|
|
||||||
[line, added, level, module, text] = line.match(/(\[.*?\])[ ]+(.*?)[ ]+(.*?)[ ]+(.*)/)
|
|
||||||
added = "<span style='color: #dfd0fa'>#{added}</span>"
|
|
||||||
level = "<span style='color: #{@toColor(level, 100)};'>#{level}</span>"
|
|
||||||
module = "<span style='color: #{@toColor(module, 60)}; font-weight: bold;'>#{module}</span>"
|
|
||||||
|
|
||||||
text = text.replace(/(Site:[A-Za-z0-9\.]+)/g, "<span style='color: #AAAAFF'>$1</span>")
|
|
||||||
text = text.replace(/\</g, "<").replace(/\>/g, ">")
|
|
||||||
#text = text.replace(/( [0-9\.]+(|s|ms))/g, "<span style='color: #FFF;'>$1</span>")
|
|
||||||
return "#{added} #{level} #{module} #{text}"
|
|
||||||
|
|
||||||
|
|
||||||
addLines: (lines, animate=true) =>
|
|
||||||
html_lines = []
|
|
||||||
@logStart "formatting"
|
|
||||||
for line in lines
|
|
||||||
html_lines.push @formatLine(line)
|
|
||||||
@logEnd "formatting"
|
|
||||||
@logStart "adding"
|
|
||||||
@text.append(html_lines.join("<br>") + "<br>")
|
|
||||||
@logEnd "adding"
|
|
||||||
if @text.is_bottom and animate
|
|
||||||
@text.stop().animate({scrollTop: @text_elem.scrollHeight - @text_elem.clientHeight + 1}, 600, 'easeInOutCubic')
|
|
||||||
|
|
||||||
|
|
||||||
loadConsoleText: =>
|
|
||||||
@sidebar.wrapper.ws.cmd "consoleLogRead", {filter: @filter, read_size: @read_size}, (res) =>
|
|
||||||
@text.html("")
|
|
||||||
pos_diff = res["pos_end"] - res["pos_start"]
|
|
||||||
size_read = Math.round(pos_diff/1024)
|
|
||||||
size_total = Math.round(res['pos_end']/1024)
|
|
||||||
@text.append("<br><br>")
|
|
||||||
@text.append("Displaying #{res.lines.length} of #{res.num_found} lines found in the last #{size_read}kB of the log file. (#{size_total}kB total)<br>")
|
|
||||||
@addLines res.lines, false
|
|
||||||
@text_elem.scrollTop = @text_elem.scrollHeight
|
|
||||||
if @stream_id
|
|
||||||
@sidebar.wrapper.ws.cmd "consoleLogStreamRemove", {stream_id: @stream_id}
|
|
||||||
@sidebar.wrapper.ws.cmd "consoleLogStream", {filter: @filter}, (res) =>
|
|
||||||
@stream_id = res.stream_id
|
|
||||||
|
|
||||||
close: =>
|
|
||||||
window.top.location.hash = ""
|
|
||||||
@sidebar.move_lock = "y"
|
|
||||||
@sidebar.startDrag()
|
|
||||||
@sidebar.stopDrag()
|
|
||||||
|
|
||||||
open: =>
|
|
||||||
@sidebar.startDrag()
|
|
||||||
@sidebar.moved("y")
|
|
||||||
@sidebar.fixbutton_targety = @sidebar.page_height - @sidebar.fixbutton_inity - 50
|
|
||||||
@sidebar.stopDrag()
|
|
||||||
|
|
||||||
onOpened: =>
|
|
||||||
@sidebar.onClosed()
|
|
||||||
@log "onOpened"
|
|
||||||
|
|
||||||
onClosed: =>
|
|
||||||
$(document.body).removeClass("body-console")
|
|
||||||
if @stream_id
|
|
||||||
@sidebar.wrapper.ws.cmd "consoleLogStreamRemove", {stream_id: @stream_id}
|
|
||||||
|
|
||||||
cleanup: =>
|
|
||||||
if @container
|
|
||||||
@container.remove()
|
|
||||||
@container = null
|
|
||||||
|
|
||||||
stopDragY: =>
|
|
||||||
# Animate sidebar and iframe
|
|
||||||
if @sidebar.fixbutton_targety == @sidebar.fixbutton_inity
|
|
||||||
# Closed
|
|
||||||
targety = 0
|
|
||||||
@opened = false
|
|
||||||
else
|
|
||||||
# Opened
|
|
||||||
targety = @sidebar.fixbutton_targety - @sidebar.fixbutton_inity
|
|
||||||
@onOpened()
|
|
||||||
@opened = true
|
|
||||||
|
|
||||||
# Revent sidebar transitions
|
|
||||||
if @tag
|
|
||||||
@tag.css("transition", "0.5s ease-out")
|
|
||||||
@tag.css("transform", "translateY(#{targety}px)").one transitionEnd, =>
|
|
||||||
@tag.css("transition", "")
|
|
||||||
if not @opened
|
|
||||||
@cleanup()
|
|
||||||
# Revert body transformations
|
|
||||||
@log "stopDragY", "opened:", @opened, targety
|
|
||||||
if not @opened
|
|
||||||
@onClosed()
|
|
||||||
|
|
||||||
changeFilter: (filter) =>
|
|
||||||
@filter = filter
|
|
||||||
if @filter == ""
|
|
||||||
@read_size = 32 * 1024
|
|
||||||
else
|
|
||||||
@read_size = 5 * 1024 * 1024
|
|
||||||
@loadConsoleText()
|
|
||||||
|
|
||||||
handleTabClick: (e) =>
|
|
||||||
elem = $(e.currentTarget)
|
|
||||||
@tab_active = elem.data("filter")
|
|
||||||
$("a", @tabs).removeClass("active")
|
|
||||||
elem.addClass("active")
|
|
||||||
@changeFilter(@tab_active)
|
|
||||||
window.top.location.hash = "#ZeroNet:Console:" + elem.data("title")
|
|
||||||
return false
|
|
||||||
|
|
||||||
window.Console = Console
|
|
|
@ -1,49 +0,0 @@
|
||||||
class Menu
|
|
||||||
constructor: (@button) ->
|
|
||||||
@elem = $(".menu.template").clone().removeClass("template")
|
|
||||||
@elem.appendTo("body")
|
|
||||||
@items = []
|
|
||||||
|
|
||||||
show: ->
|
|
||||||
if window.visible_menu and window.visible_menu.button[0] == @button[0] # Same menu visible then hide it
|
|
||||||
window.visible_menu.hide()
|
|
||||||
@hide()
|
|
||||||
else
|
|
||||||
button_pos = @button.offset()
|
|
||||||
left = button_pos.left
|
|
||||||
@elem.css({"top": button_pos.top+@button.outerHeight(), "left": left})
|
|
||||||
@button.addClass("menu-active")
|
|
||||||
@elem.addClass("visible")
|
|
||||||
if @elem.position().left + @elem.width() + 20 > window.innerWidth
|
|
||||||
@elem.css("left", window.innerWidth - @elem.width() - 20)
|
|
||||||
if window.visible_menu then window.visible_menu.hide()
|
|
||||||
window.visible_menu = @
|
|
||||||
|
|
||||||
|
|
||||||
hide: ->
|
|
||||||
@elem.removeClass("visible")
|
|
||||||
@button.removeClass("menu-active")
|
|
||||||
window.visible_menu = null
|
|
||||||
|
|
||||||
|
|
||||||
addItem: (title, cb) ->
|
|
||||||
item = $(".menu-item.template", @elem).clone().removeClass("template")
|
|
||||||
item.html(title)
|
|
||||||
item.on "click", =>
|
|
||||||
if not cb(item)
|
|
||||||
@hide()
|
|
||||||
return false
|
|
||||||
item.appendTo(@elem)
|
|
||||||
@items.push item
|
|
||||||
return item
|
|
||||||
|
|
||||||
|
|
||||||
log: (args...) ->
|
|
||||||
console.log "[Menu]", args...
|
|
||||||
|
|
||||||
window.Menu = Menu
|
|
||||||
|
|
||||||
# Hide menu on outside click
|
|
||||||
$("body").on "click", (e) ->
|
|
||||||
if window.visible_menu and e.target != window.visible_menu.button[0] and $(e.target).parent()[0] != window.visible_menu.elem[0]
|
|
||||||
window.visible_menu.hide()
|
|
|
@ -1,9 +0,0 @@
|
||||||
String::startsWith = (s) -> @[...s.length] is s
|
|
||||||
String::endsWith = (s) -> s is '' or @[-s.length..] is s
|
|
||||||
String::capitalize = -> if @.length then @[0].toUpperCase() + @.slice(1) else ""
|
|
||||||
String::repeat = (count) -> new Array( count + 1 ).join(@)
|
|
||||||
|
|
||||||
window.isEmpty = (obj) ->
|
|
||||||
for key of obj
|
|
||||||
return false
|
|
||||||
return true
|
|
|
@ -1,14 +0,0 @@
|
||||||
limits = {}
|
|
||||||
call_after_interval = {}
|
|
||||||
window.RateLimit = (interval, fn) ->
|
|
||||||
if not limits[fn]
|
|
||||||
call_after_interval[fn] = false
|
|
||||||
fn() # First call is not delayed
|
|
||||||
limits[fn] = setTimeout (->
|
|
||||||
if call_after_interval[fn]
|
|
||||||
fn()
|
|
||||||
delete limits[fn]
|
|
||||||
delete call_after_interval[fn]
|
|
||||||
), interval
|
|
||||||
else # Called within iterval, delay the call
|
|
||||||
call_after_interval[fn] = true
|
|
|
@ -1,644 +0,0 @@
|
||||||
class Sidebar extends Class
|
|
||||||
constructor: (@wrapper) ->
|
|
||||||
@tag = null
|
|
||||||
@container = null
|
|
||||||
@opened = false
|
|
||||||
@width = 410
|
|
||||||
@console = new Console(@)
|
|
||||||
@fixbutton = $(".fixbutton")
|
|
||||||
@fixbutton_addx = 0
|
|
||||||
@fixbutton_addy = 0
|
|
||||||
@fixbutton_initx = 0
|
|
||||||
@fixbutton_inity = 15
|
|
||||||
@fixbutton_targetx = 0
|
|
||||||
@move_lock = null
|
|
||||||
@page_width = $(window).width()
|
|
||||||
@page_height = $(window).height()
|
|
||||||
@frame = $("#inner-iframe")
|
|
||||||
@initFixbutton()
|
|
||||||
@dragStarted = 0
|
|
||||||
@globe = null
|
|
||||||
@preload_html = null
|
|
||||||
|
|
||||||
@original_set_site_info = @wrapper.setSiteInfo # We going to override this, save the original
|
|
||||||
|
|
||||||
# Start in opened state for debugging
|
|
||||||
if window.top.location.hash == "#ZeroNet:OpenSidebar"
|
|
||||||
@startDrag()
|
|
||||||
@moved("x")
|
|
||||||
@fixbutton_targetx = @fixbutton_initx - @width
|
|
||||||
@stopDrag()
|
|
||||||
|
|
||||||
|
|
||||||
initFixbutton: ->
|
|
||||||
|
|
||||||
# Detect dragging
|
|
||||||
@fixbutton.on "mousedown touchstart", (e) =>
|
|
||||||
if e.button > 0 # Right or middle click
|
|
||||||
return
|
|
||||||
e.preventDefault()
|
|
||||||
|
|
||||||
# Disable previous listeners
|
|
||||||
@fixbutton.off "click touchend touchcancel"
|
|
||||||
|
|
||||||
# Make sure its not a click
|
|
||||||
@dragStarted = (+ new Date)
|
|
||||||
|
|
||||||
# Fullscreen drag bg to capture mouse events over iframe
|
|
||||||
$(".drag-bg").remove()
|
|
||||||
$("<div class='drag-bg'></div>").appendTo(document.body)
|
|
||||||
|
|
||||||
$("body").one "mousemove touchmove", (e) =>
|
|
||||||
mousex = e.pageX
|
|
||||||
mousey = e.pageY
|
|
||||||
if not mousex
|
|
||||||
mousex = e.originalEvent.touches[0].pageX
|
|
||||||
mousey = e.originalEvent.touches[0].pageY
|
|
||||||
|
|
||||||
@fixbutton_addx = @fixbutton.offset().left - mousex
|
|
||||||
@fixbutton_addy = @fixbutton.offset().top - mousey
|
|
||||||
@startDrag()
|
|
||||||
@fixbutton.parent().on "click touchend touchcancel", (e) =>
|
|
||||||
if (+ new Date) - @dragStarted < 100
|
|
||||||
window.top.location = @fixbutton.find(".fixbutton-bg").attr("href")
|
|
||||||
@stopDrag()
|
|
||||||
@resized()
|
|
||||||
$(window).on "resize", @resized
|
|
||||||
|
|
||||||
resized: =>
|
|
||||||
@page_width = $(window).width()
|
|
||||||
@page_height = $(window).height()
|
|
||||||
@fixbutton_initx = @page_width - 75 # Initial x position
|
|
||||||
if @opened
|
|
||||||
@fixbutton.css
|
|
||||||
left: @fixbutton_initx - @width
|
|
||||||
else
|
|
||||||
@fixbutton.css
|
|
||||||
left: @fixbutton_initx
|
|
||||||
|
|
||||||
# Start dragging the fixbutton
|
|
||||||
startDrag: ->
|
|
||||||
#@move_lock = "x" # Temporary until internals not finished
|
|
||||||
@log "startDrag", @fixbutton_initx, @fixbutton_inity
|
|
||||||
@fixbutton_targetx = @fixbutton_initx # Fallback x position
|
|
||||||
@fixbutton_targety = @fixbutton_inity # Fallback y position
|
|
||||||
|
|
||||||
@fixbutton.addClass("dragging")
|
|
||||||
|
|
||||||
# IE position wrap fix
|
|
||||||
if navigator.userAgent.indexOf('MSIE') != -1 or navigator.appVersion.indexOf('Trident/') > 0
|
|
||||||
@fixbutton.css("pointer-events", "none")
|
|
||||||
|
|
||||||
# Don't go to homepage
|
|
||||||
@fixbutton.one "click", (e) =>
|
|
||||||
@stopDrag()
|
|
||||||
@fixbutton.removeClass("dragging")
|
|
||||||
moved_x = Math.abs(@fixbutton.offset().left - @fixbutton_initx)
|
|
||||||
moved_y = Math.abs(@fixbutton.offset().top - @fixbutton_inity)
|
|
||||||
if moved_x > 5 or moved_y > 10
|
|
||||||
# If moved more than some pixel the button then don't go to homepage
|
|
||||||
e.preventDefault()
|
|
||||||
|
|
||||||
# Animate drag
|
|
||||||
@fixbutton.parents().on "mousemove touchmove", @animDrag
|
|
||||||
@fixbutton.parents().on "mousemove touchmove" ,@waitMove
|
|
||||||
|
|
||||||
# Stop dragging listener
|
|
||||||
@fixbutton.parents().one "mouseup touchend touchcancel", (e) =>
|
|
||||||
e.preventDefault()
|
|
||||||
@stopDrag()
|
|
||||||
|
|
||||||
|
|
||||||
# Wait for moving the fixbutton
|
|
||||||
waitMove: (e) =>
|
|
||||||
document.body.style.perspective = "1000px"
|
|
||||||
document.body.style.height = "100%"
|
|
||||||
document.body.style.willChange = "perspective"
|
|
||||||
document.documentElement.style.height = "100%"
|
|
||||||
#$(document.body).css("backface-visibility", "hidden").css("perspective", "1000px").css("height", "900px")
|
|
||||||
# $("iframe").css("backface-visibility", "hidden")
|
|
||||||
|
|
||||||
moved_x = Math.abs(parseInt(@fixbutton[0].style.left) - @fixbutton_targetx)
|
|
||||||
moved_y = Math.abs(parseInt(@fixbutton[0].style.top) - @fixbutton_targety)
|
|
||||||
if moved_x > 5 and (+ new Date) - @dragStarted + moved_x > 50
|
|
||||||
@moved("x")
|
|
||||||
@fixbutton.stop().animate {"top": @fixbutton_inity}, 1000
|
|
||||||
@fixbutton.parents().off "mousemove touchmove" ,@waitMove
|
|
||||||
|
|
||||||
else if moved_y > 5 and (+ new Date) - @dragStarted + moved_y > 50
|
|
||||||
@moved("y")
|
|
||||||
@fixbutton.parents().off "mousemove touchmove" ,@waitMove
|
|
||||||
|
|
||||||
moved: (direction) ->
|
|
||||||
@log "Moved", direction
|
|
||||||
@move_lock = direction
|
|
||||||
if direction == "y"
|
|
||||||
$(document.body).addClass("body-console")
|
|
||||||
return @console.createHtmltag()
|
|
||||||
@createHtmltag()
|
|
||||||
$(document.body).addClass("body-sidebar")
|
|
||||||
@container.on "mousedown touchend touchcancel", (e) =>
|
|
||||||
if e.target != e.currentTarget
|
|
||||||
return true
|
|
||||||
@log "closing"
|
|
||||||
if $(document.body).hasClass("body-sidebar")
|
|
||||||
@close()
|
|
||||||
return true
|
|
||||||
|
|
||||||
$(window).off "resize"
|
|
||||||
$(window).on "resize", =>
|
|
||||||
$(document.body).css "height", $(window).height()
|
|
||||||
@scrollable()
|
|
||||||
@resized()
|
|
||||||
|
|
||||||
# Override setsiteinfo to catch changes
|
|
||||||
@wrapper.setSiteInfo = (site_info) =>
|
|
||||||
@setSiteInfo(site_info)
|
|
||||||
@original_set_site_info.apply(@wrapper, arguments)
|
|
||||||
|
|
||||||
# Preload world.jpg
|
|
||||||
img = new Image();
|
|
||||||
img.src = "/uimedia/globe/world.jpg";
|
|
||||||
|
|
||||||
setSiteInfo: (site_info) ->
|
|
||||||
RateLimit 1500, =>
|
|
||||||
@updateHtmlTag()
|
|
||||||
RateLimit 30000, =>
|
|
||||||
@displayGlobe()
|
|
||||||
|
|
||||||
# Create the sidebar html tag
|
|
||||||
createHtmltag: ->
|
|
||||||
@when_loaded = $.Deferred()
|
|
||||||
if not @container
|
|
||||||
@container = $("""
|
|
||||||
<div class="sidebar-container"><div class="sidebar scrollable"><div class="content-wrapper"><div class="content">
|
|
||||||
</div></div></div></div>
|
|
||||||
""")
|
|
||||||
@container.appendTo(document.body)
|
|
||||||
@tag = @container.find(".sidebar")
|
|
||||||
@updateHtmlTag()
|
|
||||||
@scrollable = window.initScrollable()
|
|
||||||
|
|
||||||
|
|
||||||
updateHtmlTag: ->
|
|
||||||
if @preload_html
|
|
||||||
@setHtmlTag(@preload_html)
|
|
||||||
@preload_html = null
|
|
||||||
else
|
|
||||||
@wrapper.ws.cmd "sidebarGetHtmlTag", {}, @setHtmlTag
|
|
||||||
|
|
||||||
setHtmlTag: (res) =>
|
|
||||||
if @tag.find(".content").children().length == 0 # First update
|
|
||||||
@log "Creating content"
|
|
||||||
@container.addClass("loaded")
|
|
||||||
morphdom(@tag.find(".content")[0], '<div class="content">'+res+'</div>')
|
|
||||||
# @scrollable()
|
|
||||||
@when_loaded.resolve()
|
|
||||||
|
|
||||||
else # Not first update, patch the html to keep unchanged dom elements
|
|
||||||
morphdom @tag.find(".content")[0], '<div class="content">'+res+'</div>', {
|
|
||||||
onBeforeMorphEl: (from_el, to_el) -> # Ignore globe loaded state
|
|
||||||
if from_el.className == "globe" or from_el.className.indexOf("noupdate") >= 0
|
|
||||||
return false
|
|
||||||
else
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
# Save and forget privatekey for site signing
|
|
||||||
@tag.find("#privatekey-add").off("click, touchend").on "click touchend", (e) =>
|
|
||||||
@wrapper.displayPrompt "Enter your private key:", "password", "Save", "", (privatekey) =>
|
|
||||||
@wrapper.ws.cmd "userSetSitePrivatekey", [privatekey], (res) =>
|
|
||||||
@wrapper.notifications.add "privatekey", "done", "Private key saved for site signing", 5000
|
|
||||||
return false
|
|
||||||
|
|
||||||
@tag.find("#privatekey-forget").off("click, touchend").on "click touchend", (e) =>
|
|
||||||
@wrapper.displayConfirm "Remove saved private key for this site?", "Forget", (res) =>
|
|
||||||
if not res
|
|
||||||
return false
|
|
||||||
@wrapper.ws.cmd "userSetSitePrivatekey", [""], (res) =>
|
|
||||||
@wrapper.notifications.add "privatekey", "done", "Saved private key removed", 5000
|
|
||||||
return false
|
|
||||||
|
|
||||||
# Use requested address for browse files urls
|
|
||||||
@tag.find("#browse-files").attr("href", document.location.pathname.replace(/(\/.*?(\/|$)).*$/, "/list$1"))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
animDrag: (e) =>
|
|
||||||
mousex = e.pageX
|
|
||||||
mousey = e.pageY
|
|
||||||
if not mousex and e.originalEvent.touches
|
|
||||||
mousex = e.originalEvent.touches[0].pageX
|
|
||||||
mousey = e.originalEvent.touches[0].pageY
|
|
||||||
|
|
||||||
overdrag = @fixbutton_initx - @width - mousex
|
|
||||||
if overdrag > 0 # Overdragged
|
|
||||||
overdrag_percent = 1 + overdrag/300
|
|
||||||
mousex = (mousex + (@fixbutton_initx-@width)*overdrag_percent)/(1+overdrag_percent)
|
|
||||||
targetx = @fixbutton_initx - mousex - @fixbutton_addx
|
|
||||||
targety = @fixbutton_inity - mousey - @fixbutton_addy
|
|
||||||
|
|
||||||
if @move_lock == "x"
|
|
||||||
targety = @fixbutton_inity
|
|
||||||
else if @move_lock == "y"
|
|
||||||
targetx = @fixbutton_initx
|
|
||||||
|
|
||||||
if not @move_lock or @move_lock == "x"
|
|
||||||
@fixbutton[0].style.left = (mousex + @fixbutton_addx) + "px"
|
|
||||||
if @tag
|
|
||||||
@tag[0].style.transform = "translateX(#{0 - targetx}px)"
|
|
||||||
|
|
||||||
if not @move_lock or @move_lock == "y"
|
|
||||||
@fixbutton[0].style.top = (mousey + @fixbutton_addy) + "px"
|
|
||||||
if @console.tag
|
|
||||||
@console.tag[0].style.transform = "translateY(#{0 - targety}px)"
|
|
||||||
|
|
||||||
#if @move_lock == "x"
|
|
||||||
# @fixbutton[0].style.left = "#{@fixbutton_targetx} px"
|
|
||||||
#@fixbutton[0].style.top = "#{@fixbutton_inity}px"
|
|
||||||
#if @move_lock == "y"
|
|
||||||
# @fixbutton[0].style.top = "#{@fixbutton_targety} px"
|
|
||||||
|
|
||||||
# Check if opened
|
|
||||||
if (not @opened and targetx > @width/3) or (@opened and targetx > @width*0.9)
|
|
||||||
@fixbutton_targetx = @fixbutton_initx - @width # Make it opened
|
|
||||||
else
|
|
||||||
@fixbutton_targetx = @fixbutton_initx
|
|
||||||
|
|
||||||
if (not @console.opened and 0 - targety > @page_height/10) or (@console.opened and 0 - targety > @page_height*0.8)
|
|
||||||
@fixbutton_targety = @page_height - @fixbutton_inity - 50
|
|
||||||
else
|
|
||||||
@fixbutton_targety = @fixbutton_inity
|
|
||||||
|
|
||||||
|
|
||||||
# Stop dragging the fixbutton
|
|
||||||
stopDrag: ->
|
|
||||||
@fixbutton.parents().off "mousemove touchmove"
|
|
||||||
@fixbutton.off "mousemove touchmove"
|
|
||||||
@fixbutton.css("pointer-events", "")
|
|
||||||
$(".drag-bg").remove()
|
|
||||||
if not @fixbutton.hasClass("dragging")
|
|
||||||
return
|
|
||||||
@fixbutton.removeClass("dragging")
|
|
||||||
|
|
||||||
# Move back to initial position
|
|
||||||
if @fixbutton_targetx != @fixbutton.offset().left or @fixbutton_targety != @fixbutton.offset().top
|
|
||||||
# Animate fixbutton
|
|
||||||
if @move_lock == "y"
|
|
||||||
top = @fixbutton_targety
|
|
||||||
left = @fixbutton_initx
|
|
||||||
if @move_lock == "x"
|
|
||||||
top = @fixbutton_inity
|
|
||||||
left = @fixbutton_targetx
|
|
||||||
@fixbutton.stop().animate {"left": left, "top": top}, 500, "easeOutBack", =>
|
|
||||||
# Switch back to auto align
|
|
||||||
if @fixbutton_targetx == @fixbutton_initx # Closed
|
|
||||||
@fixbutton.css("left", "auto")
|
|
||||||
else # Opened
|
|
||||||
@fixbutton.css("left", left)
|
|
||||||
|
|
||||||
$(".fixbutton-bg").trigger "mouseout" # Switch fixbutton back to normal status
|
|
||||||
|
|
||||||
@stopDragX()
|
|
||||||
@console.stopDragY()
|
|
||||||
@move_lock = null
|
|
||||||
|
|
||||||
stopDragX: ->
|
|
||||||
# Animate sidebar and iframe
|
|
||||||
if @fixbutton_targetx == @fixbutton_initx or @move_lock == "y"
|
|
||||||
# Closed
|
|
||||||
targetx = 0
|
|
||||||
@opened = false
|
|
||||||
else
|
|
||||||
# Opened
|
|
||||||
targetx = @width
|
|
||||||
if @opened
|
|
||||||
@onOpened()
|
|
||||||
else
|
|
||||||
@when_loaded.done =>
|
|
||||||
@onOpened()
|
|
||||||
@opened = true
|
|
||||||
|
|
||||||
# Revent sidebar transitions
|
|
||||||
if @tag
|
|
||||||
@tag.css("transition", "0.4s ease-out")
|
|
||||||
@tag.css("transform", "translateX(-#{targetx}px)").one transitionEnd, =>
|
|
||||||
@tag.css("transition", "")
|
|
||||||
if not @opened
|
|
||||||
@container.remove()
|
|
||||||
@container = null
|
|
||||||
if @tag
|
|
||||||
@tag.remove()
|
|
||||||
@tag = null
|
|
||||||
|
|
||||||
# Revert body transformations
|
|
||||||
@log "stopdrag", "opened:", @opened
|
|
||||||
if not @opened
|
|
||||||
@onClosed()
|
|
||||||
|
|
||||||
sign: (inner_path, privatekey) ->
|
|
||||||
@wrapper.displayProgress("sign", "Signing: #{inner_path}...", 0)
|
|
||||||
@wrapper.ws.cmd "siteSign", {privatekey: privatekey, inner_path: inner_path, update_changed_files: true}, (res) =>
|
|
||||||
if res == "ok"
|
|
||||||
@wrapper.displayProgress("sign", "#{inner_path} signed!", 100)
|
|
||||||
else
|
|
||||||
@wrapper.displayProgress("sign", "Error signing #{inner_path}", -1)
|
|
||||||
|
|
||||||
publish: (inner_path, privatekey) ->
|
|
||||||
@wrapper.ws.cmd "sitePublish", {privatekey: privatekey, inner_path: inner_path, sign: true, update_changed_files: true}, (res) =>
|
|
||||||
if res == "ok"
|
|
||||||
@wrapper.notifications.add "sign", "done", "#{inner_path} Signed and published!", 5000
|
|
||||||
|
|
||||||
handleSiteDeleteClick: ->
|
|
||||||
if @wrapper.site_info.privatekey
|
|
||||||
question = "Are you sure?<br>This site has a saved private key"
|
|
||||||
options = ["Forget private key and delete site"]
|
|
||||||
else
|
|
||||||
question = "Are you sure?"
|
|
||||||
options = ["Delete this site", "Blacklist"]
|
|
||||||
@wrapper.displayConfirm question, options, (confirmed) =>
|
|
||||||
if confirmed == 1
|
|
||||||
@tag.find("#button-delete").addClass("loading")
|
|
||||||
@wrapper.ws.cmd "siteDelete", @wrapper.site_info.address, ->
|
|
||||||
document.location = $(".fixbutton-bg").attr("href")
|
|
||||||
else if confirmed == 2
|
|
||||||
@wrapper.displayPrompt "Blacklist this site", "text", "Delete and Blacklist", "Reason", (reason) =>
|
|
||||||
@tag.find("#button-delete").addClass("loading")
|
|
||||||
@wrapper.ws.cmd "siteblockAdd", [@wrapper.site_info.address, reason]
|
|
||||||
@wrapper.ws.cmd "siteDelete", @wrapper.site_info.address, ->
|
|
||||||
document.location = $(".fixbutton-bg").attr("href")
|
|
||||||
|
|
||||||
onOpened: ->
|
|
||||||
@log "Opened"
|
|
||||||
@scrollable()
|
|
||||||
|
|
||||||
# Re-calculate height when site admin opened or closed
|
|
||||||
@tag.find("#checkbox-owned, #checkbox-autodownloadoptional").off("click touchend").on "click touchend", =>
|
|
||||||
setTimeout (=>
|
|
||||||
@scrollable()
|
|
||||||
), 300
|
|
||||||
|
|
||||||
# Site limit button
|
|
||||||
@tag.find("#button-sitelimit").off("click touchend").on "click touchend", =>
|
|
||||||
@wrapper.ws.cmd "siteSetLimit", $("#input-sitelimit").val(), (res) =>
|
|
||||||
if res == "ok"
|
|
||||||
@wrapper.notifications.add "done-sitelimit", "done", "Site storage limit modified!", 5000
|
|
||||||
@updateHtmlTag()
|
|
||||||
return false
|
|
||||||
|
|
||||||
# Site autodownload limit button
|
|
||||||
@tag.find("#button-autodownload_bigfile_size_limit").off("click touchend").on "click touchend", =>
|
|
||||||
@wrapper.ws.cmd "siteSetAutodownloadBigfileLimit", $("#input-autodownload_bigfile_size_limit").val(), (res) =>
|
|
||||||
if res == "ok"
|
|
||||||
@wrapper.notifications.add "done-bigfilelimit", "done", "Site bigfile auto download limit modified!", 5000
|
|
||||||
@updateHtmlTag()
|
|
||||||
return false
|
|
||||||
|
|
||||||
# Site start download optional files
|
|
||||||
@tag.find("#button-autodownload_previous").off("click touchend").on "click touchend", =>
|
|
||||||
@wrapper.ws.cmd "siteUpdate", {"address": @wrapper.site_info.address, "check_files": true}, =>
|
|
||||||
@wrapper.notifications.add "done-download_optional", "done", "Optional files downloaded", 5000
|
|
||||||
|
|
||||||
@wrapper.notifications.add "start-download_optional", "info", "Optional files download started", 5000
|
|
||||||
return false
|
|
||||||
|
|
||||||
# Database reload
|
|
||||||
@tag.find("#button-dbreload").off("click touchend").on "click touchend", =>
|
|
||||||
@wrapper.ws.cmd "dbReload", [], =>
|
|
||||||
@wrapper.notifications.add "done-dbreload", "done", "Database schema reloaded!", 5000
|
|
||||||
@updateHtmlTag()
|
|
||||||
return false
|
|
||||||
|
|
||||||
# Database rebuild
|
|
||||||
@tag.find("#button-dbrebuild").off("click touchend").on "click touchend", =>
|
|
||||||
@wrapper.notifications.add "done-dbrebuild", "info", "Database rebuilding...."
|
|
||||||
@wrapper.ws.cmd "dbRebuild", [], =>
|
|
||||||
@wrapper.notifications.add "done-dbrebuild", "done", "Database rebuilt!", 5000
|
|
||||||
@updateHtmlTag()
|
|
||||||
return false
|
|
||||||
|
|
||||||
# Update site
|
|
||||||
@tag.find("#button-update").off("click touchend").on "click touchend", =>
|
|
||||||
@tag.find("#button-update").addClass("loading")
|
|
||||||
@wrapper.ws.cmd "siteUpdate", @wrapper.site_info.address, =>
|
|
||||||
@wrapper.notifications.add "done-updated", "done", "Site updated!", 5000
|
|
||||||
@tag.find("#button-update").removeClass("loading")
|
|
||||||
return false
|
|
||||||
|
|
||||||
# Pause site
|
|
||||||
@tag.find("#button-pause").off("click touchend").on "click touchend", =>
|
|
||||||
@tag.find("#button-pause").addClass("hidden")
|
|
||||||
@wrapper.ws.cmd "sitePause", @wrapper.site_info.address
|
|
||||||
return false
|
|
||||||
|
|
||||||
# Resume site
|
|
||||||
@tag.find("#button-resume").off("click touchend").on "click touchend", =>
|
|
||||||
@tag.find("#button-resume").addClass("hidden")
|
|
||||||
@wrapper.ws.cmd "siteResume", @wrapper.site_info.address
|
|
||||||
return false
|
|
||||||
|
|
||||||
# Delete site
|
|
||||||
@tag.find("#button-delete").off("click touchend").on "click touchend", =>
|
|
||||||
@handleSiteDeleteClick()
|
|
||||||
return false
|
|
||||||
|
|
||||||
# Owned checkbox
|
|
||||||
@tag.find("#checkbox-owned").off("click touchend").on "click touchend", =>
|
|
||||||
owned = @tag.find("#checkbox-owned").is(":checked")
|
|
||||||
@wrapper.ws.cmd "siteSetOwned", [owned], (res_set_owned) =>
|
|
||||||
@log "Owned", owned
|
|
||||||
if owned
|
|
||||||
@wrapper.ws.cmd "siteRecoverPrivatekey", [], (res_recover) =>
|
|
||||||
if res_recover == "ok"
|
|
||||||
@wrapper.notifications.add("recover", "done", "Private key recovered from master seed", 5000)
|
|
||||||
else
|
|
||||||
@log "Unable to recover private key: #{res_recover.error}"
|
|
||||||
|
|
||||||
|
|
||||||
# Owned auto download checkbox
|
|
||||||
@tag.find("#checkbox-autodownloadoptional").off("click touchend").on "click touchend", =>
|
|
||||||
@wrapper.ws.cmd "siteSetAutodownloadoptional", [@tag.find("#checkbox-autodownloadoptional").is(":checked")]
|
|
||||||
|
|
||||||
# Change identity button
|
|
||||||
@tag.find("#button-identity").off("click touchend").on "click touchend", =>
|
|
||||||
@wrapper.ws.cmd "certSelect"
|
|
||||||
return false
|
|
||||||
|
|
||||||
# Save settings
|
|
||||||
@tag.find("#button-settings").off("click touchend").on "click touchend", =>
|
|
||||||
@wrapper.ws.cmd "fileGet", "content.json", (res) =>
|
|
||||||
data = JSON.parse(res)
|
|
||||||
data["title"] = $("#settings-title").val()
|
|
||||||
data["description"] = $("#settings-description").val()
|
|
||||||
json_raw = unescape(encodeURIComponent(JSON.stringify(data, undefined, '\t')))
|
|
||||||
@wrapper.ws.cmd "fileWrite", ["content.json", btoa(json_raw), true], (res) =>
|
|
||||||
if res != "ok" # fileWrite failed
|
|
||||||
@wrapper.notifications.add "file-write", "error", "File write error: #{res}"
|
|
||||||
else
|
|
||||||
@wrapper.notifications.add "file-write", "done", "Site settings saved!", 5000
|
|
||||||
if @wrapper.site_info.privatekey
|
|
||||||
@wrapper.ws.cmd "siteSign", {privatekey: "stored", inner_path: "content.json", update_changed_files: true}
|
|
||||||
@updateHtmlTag()
|
|
||||||
return false
|
|
||||||
|
|
||||||
|
|
||||||
# Open site directory
|
|
||||||
@tag.find("#link-directory").off("click touchend").on "click touchend", =>
|
|
||||||
@wrapper.ws.cmd "serverShowdirectory", ["site", @wrapper.site_info.address]
|
|
||||||
return false
|
|
||||||
|
|
||||||
# Copy site with peers
|
|
||||||
@tag.find("#link-copypeers").off("click touchend").on "click touchend", (e) =>
|
|
||||||
copy_text = e.currentTarget.href
|
|
||||||
handler = (e) =>
|
|
||||||
e.clipboardData.setData('text/plain', copy_text)
|
|
||||||
e.preventDefault()
|
|
||||||
@wrapper.notifications.add "copy", "done", "Site address with peers copied to your clipboard", 5000
|
|
||||||
document.removeEventListener('copy', handler, true)
|
|
||||||
|
|
||||||
document.addEventListener('copy', handler, true)
|
|
||||||
document.execCommand('copy')
|
|
||||||
return false
|
|
||||||
|
|
||||||
# Sign and publish content.json
|
|
||||||
$(document).on "click touchend", =>
|
|
||||||
@tag?.find("#button-sign-publish-menu").removeClass("visible")
|
|
||||||
@tag?.find(".contents + .flex").removeClass("sign-publish-flex")
|
|
||||||
|
|
||||||
@tag.find(".contents-content").off("click touchend").on "click touchend", (e) =>
|
|
||||||
$("#input-contents").val(e.currentTarget.innerText);
|
|
||||||
return false;
|
|
||||||
|
|
||||||
menu = new Menu(@tag.find("#menu-sign-publish"))
|
|
||||||
menu.elem.css("margin-top", "-130px") # Open upwards
|
|
||||||
menu.addItem "Sign", =>
|
|
||||||
inner_path = @tag.find("#input-contents").val()
|
|
||||||
|
|
||||||
@wrapper.ws.cmd "fileRules", {inner_path: inner_path}, (rules) =>
|
|
||||||
if @wrapper.site_info.auth_address in rules.signers
|
|
||||||
# ZeroID or other ID provider
|
|
||||||
@sign(inner_path)
|
|
||||||
else if @wrapper.site_info.privatekey
|
|
||||||
# Privatekey stored in users.json
|
|
||||||
@sign(inner_path, "stored")
|
|
||||||
else
|
|
||||||
# Ask the user for privatekey
|
|
||||||
@wrapper.displayPrompt "Enter your private key:", "password", "Sign", "", (privatekey) => # Prompt the private key
|
|
||||||
@sign(inner_path, privatekey)
|
|
||||||
|
|
||||||
@tag.find(".contents + .flex").removeClass "active"
|
|
||||||
menu.hide()
|
|
||||||
|
|
||||||
menu.addItem "Publish", =>
|
|
||||||
inner_path = @tag.find("#input-contents").val()
|
|
||||||
@wrapper.ws.cmd "sitePublish", {"inner_path": inner_path, "sign": false}
|
|
||||||
|
|
||||||
@tag.find(".contents + .flex").removeClass "active"
|
|
||||||
menu.hide()
|
|
||||||
|
|
||||||
@tag.find("#menu-sign-publish").off("click touchend").on "click touchend", =>
|
|
||||||
if window.visible_menu == menu
|
|
||||||
@tag.find(".contents + .flex").removeClass "active"
|
|
||||||
menu.hide()
|
|
||||||
else
|
|
||||||
@tag.find(".contents + .flex").addClass "active"
|
|
||||||
@tag.find(".content-wrapper").prop "scrollTop", 10000
|
|
||||||
menu.show()
|
|
||||||
return false
|
|
||||||
|
|
||||||
$("body").on "click", =>
|
|
||||||
if @tag
|
|
||||||
@tag.find(".contents + .flex").removeClass "active"
|
|
||||||
|
|
||||||
@tag.find("#button-sign-publish").off("click touchend").on "click touchend", =>
|
|
||||||
inner_path = @tag.find("#input-contents").val()
|
|
||||||
|
|
||||||
@wrapper.ws.cmd "fileRules", {inner_path: inner_path}, (rules) =>
|
|
||||||
if @wrapper.site_info.auth_address in rules.signers
|
|
||||||
# ZeroID or other ID provider
|
|
||||||
@publish(inner_path, null)
|
|
||||||
else if @wrapper.site_info.privatekey
|
|
||||||
# Privatekey stored in users.json
|
|
||||||
@publish(inner_path, "stored")
|
|
||||||
else
|
|
||||||
# Ask the user for privatekey
|
|
||||||
@wrapper.displayPrompt "Enter your private key:", "password", "Sign", "", (privatekey) => # Prompt the private key
|
|
||||||
@publish(inner_path, privatekey)
|
|
||||||
return false
|
|
||||||
|
|
||||||
# Close
|
|
||||||
@tag.find(".close").off("click touchend").on "click touchend", (e) =>
|
|
||||||
@close()
|
|
||||||
return false
|
|
||||||
|
|
||||||
@loadGlobe()
|
|
||||||
|
|
||||||
close: ->
|
|
||||||
@move_lock = "x"
|
|
||||||
@startDrag()
|
|
||||||
@stopDrag()
|
|
||||||
|
|
||||||
|
|
||||||
onClosed: ->
|
|
||||||
$(window).off "resize"
|
|
||||||
$(window).on "resize", @resized
|
|
||||||
$(document.body).css("transition", "0.6s ease-in-out").removeClass("body-sidebar").on transitionEnd, (e) =>
|
|
||||||
if e.target == document.body and not $(document.body).hasClass("body-sidebar") and not $(document.body).hasClass("body-console")
|
|
||||||
$(document.body).css("height", "auto").css("perspective", "").css("will-change", "").css("transition", "").off transitionEnd
|
|
||||||
@unloadGlobe()
|
|
||||||
|
|
||||||
# We dont need site info anymore
|
|
||||||
@wrapper.setSiteInfo = @original_set_site_info
|
|
||||||
|
|
||||||
|
|
||||||
loadGlobe: =>
|
|
||||||
if @tag.find(".globe").hasClass("loading")
|
|
||||||
setTimeout (=>
|
|
||||||
if typeof(DAT) == "undefined" # Globe script not loaded, do it first
|
|
||||||
script_tag = $("<script>")
|
|
||||||
script_tag.attr("nonce", @wrapper.script_nonce)
|
|
||||||
script_tag.attr("src", "/uimedia/globe/all.js")
|
|
||||||
script_tag.on("load", @displayGlobe)
|
|
||||||
document.head.appendChild(script_tag[0])
|
|
||||||
else
|
|
||||||
@displayGlobe()
|
|
||||||
), 600
|
|
||||||
|
|
||||||
|
|
||||||
displayGlobe: =>
|
|
||||||
img = new Image();
|
|
||||||
img.src = "/uimedia/globe/world.jpg";
|
|
||||||
img.onload = =>
|
|
||||||
@wrapper.ws.cmd "sidebarGetPeers", [], (globe_data) =>
|
|
||||||
if @globe
|
|
||||||
@globe.scene.remove(@globe.points)
|
|
||||||
@globe.addData( globe_data, {format: 'magnitude', name: "hello", animated: false} )
|
|
||||||
@globe.createPoints()
|
|
||||||
@tag?.find(".globe").removeClass("loading")
|
|
||||||
else if typeof(DAT) != "undefined"
|
|
||||||
try
|
|
||||||
@globe = new DAT.Globe( @tag.find(".globe")[0], {"imgDir": "/uimedia/globe/"} )
|
|
||||||
@globe.addData( globe_data, {format: 'magnitude', name: "hello"} )
|
|
||||||
@globe.createPoints()
|
|
||||||
@globe.animate()
|
|
||||||
catch e
|
|
||||||
console.log "WebGL error", e
|
|
||||||
@tag?.find(".globe").addClass("error").text("WebGL not supported")
|
|
||||||
@tag?.find(".globe").removeClass("loading")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
unloadGlobe: =>
|
|
||||||
if not @globe
|
|
||||||
return false
|
|
||||||
@globe.unload()
|
|
||||||
@globe = null
|
|
||||||
|
|
||||||
|
|
||||||
wrapper = window.wrapper
|
|
||||||
setTimeout ( ->
|
|
||||||
window.sidebar = new Sidebar(wrapper)
|
|
||||||
), 500
|
|
||||||
|
|
||||||
|
|
||||||
window.transitionEnd = 'transitionend webkitTransitionEnd oTransitionEnd otransitionend'
|
|
|
@ -1,222 +0,0 @@
|
||||||
class ConfigStorage extends Class
|
|
||||||
constructor: (@config) ->
|
|
||||||
@items = []
|
|
||||||
@createSections()
|
|
||||||
@setValues(@config)
|
|
||||||
|
|
||||||
setValues: (values) ->
|
|
||||||
for section in @items
|
|
||||||
for item in section.items
|
|
||||||
if not values[item.key]
|
|
||||||
continue
|
|
||||||
item.value = @formatValue(values[item.key].value)
|
|
||||||
item.default = @formatValue(values[item.key].default)
|
|
||||||
item.pending = values[item.key].pending
|
|
||||||
values[item.key].item = item
|
|
||||||
|
|
||||||
formatValue: (value) ->
|
|
||||||
if not value
|
|
||||||
return false
|
|
||||||
else if typeof(value) == "object"
|
|
||||||
return value.join("\n")
|
|
||||||
else if typeof(value) == "number"
|
|
||||||
return value.toString()
|
|
||||||
else
|
|
||||||
return value
|
|
||||||
|
|
||||||
deformatValue: (value, type) ->
|
|
||||||
if type == "object" and typeof(value) == "string"
|
|
||||||
if not value.length
|
|
||||||
return value = null
|
|
||||||
else
|
|
||||||
return value.split("\n")
|
|
||||||
if type == "boolean" and not value
|
|
||||||
return false
|
|
||||||
else if type == "number"
|
|
||||||
if typeof(value) == "number"
|
|
||||||
return value.toString()
|
|
||||||
else if not value
|
|
||||||
return "0"
|
|
||||||
else
|
|
||||||
return value
|
|
||||||
else
|
|
||||||
return value
|
|
||||||
|
|
||||||
createSections: ->
|
|
||||||
# Web Interface
|
|
||||||
section = @createSection("Web Interface")
|
|
||||||
|
|
||||||
section.items.push
|
|
||||||
key: "open_browser"
|
|
||||||
title: "Open web browser on ZeroNet startup"
|
|
||||||
type: "checkbox"
|
|
||||||
|
|
||||||
# Network
|
|
||||||
section = @createSection("Network")
|
|
||||||
section.items.push
|
|
||||||
key: "offline"
|
|
||||||
title: "Offline mode"
|
|
||||||
type: "checkbox"
|
|
||||||
description: "Disable network communication."
|
|
||||||
|
|
||||||
section.items.push
|
|
||||||
key: "fileserver_ip_type"
|
|
||||||
title: "File server network"
|
|
||||||
type: "select"
|
|
||||||
options: [
|
|
||||||
{title: "IPv4", value: "ipv4"}
|
|
||||||
{title: "IPv6", value: "ipv6"}
|
|
||||||
{title: "Dual (IPv4 & IPv6)", value: "dual"}
|
|
||||||
]
|
|
||||||
description: "Accept incoming peers using IPv4 or IPv6 address. (default: dual)"
|
|
||||||
|
|
||||||
section.items.push
|
|
||||||
key: "fileserver_port"
|
|
||||||
title: "File server port"
|
|
||||||
type: "text"
|
|
||||||
valid_pattern: /[0-9]*/
|
|
||||||
description: "Other peers will use this port to reach your served sites. (default: randomize)"
|
|
||||||
|
|
||||||
section.items.push
|
|
||||||
key: "ip_external"
|
|
||||||
title: "File server external ip"
|
|
||||||
type: "textarea"
|
|
||||||
placeholder: "Detect automatically"
|
|
||||||
description: "Your file server is accessible on these ips. (default: detect automatically)"
|
|
||||||
|
|
||||||
section.items.push
|
|
||||||
title: "Tor"
|
|
||||||
key: "tor"
|
|
||||||
type: "select"
|
|
||||||
options: [
|
|
||||||
{title: "Disable", value: "disable"}
|
|
||||||
{title: "Enable", value: "enable"}
|
|
||||||
{title: "Always", value: "always"}
|
|
||||||
]
|
|
||||||
description: [
|
|
||||||
"Disable: Don't connect to peers on Tor network", h("br"),
|
|
||||||
"Enable: Only use Tor for Tor network peers", h("br"),
|
|
||||||
"Always: Use Tor for every connections to hide your IP address (slower)"
|
|
||||||
]
|
|
||||||
|
|
||||||
section.items.push
|
|
||||||
title: "Use Tor bridges"
|
|
||||||
key: "tor_use_bridges"
|
|
||||||
type: "checkbox"
|
|
||||||
description: "Use obfuscated bridge relays to avoid network level Tor block (even slower)"
|
|
||||||
isHidden: ->
|
|
||||||
return not Page.server_info.tor_has_meek_bridges
|
|
||||||
|
|
||||||
section.items.push
|
|
||||||
title: "Trackers"
|
|
||||||
key: "trackers"
|
|
||||||
type: "textarea"
|
|
||||||
description: "Discover new peers using these adresses"
|
|
||||||
|
|
||||||
section.items.push
|
|
||||||
title: "Trackers files"
|
|
||||||
key: "trackers_file"
|
|
||||||
type: "textarea"
|
|
||||||
description: "Load additional list of torrent trackers dynamically, from a file"
|
|
||||||
placeholder: "Eg.: {data_dir}/trackers.json"
|
|
||||||
value_pos: "fullwidth"
|
|
||||||
|
|
||||||
section.items.push
|
|
||||||
title: "Proxy for tracker connections"
|
|
||||||
key: "trackers_proxy"
|
|
||||||
type: "select"
|
|
||||||
options: [
|
|
||||||
{title: "Custom", value: ""}
|
|
||||||
{title: "Tor", value: "tor"}
|
|
||||||
{title: "Disable", value: "disable"}
|
|
||||||
]
|
|
||||||
isHidden: ->
|
|
||||||
Page.values["tor"] == "always"
|
|
||||||
|
|
||||||
section.items.push
|
|
||||||
title: "Custom socks proxy address for trackers"
|
|
||||||
key: "trackers_proxy"
|
|
||||||
type: "text"
|
|
||||||
placeholder: "Eg.: 127.0.0.1:1080"
|
|
||||||
value_pos: "fullwidth"
|
|
||||||
valid_pattern: /.+:[0-9]+/
|
|
||||||
isHidden: =>
|
|
||||||
Page.values["trackers_proxy"] in ["tor", "disable"]
|
|
||||||
|
|
||||||
# Performance
|
|
||||||
section = @createSection("Performance")
|
|
||||||
|
|
||||||
section.items.push
|
|
||||||
key: "log_level"
|
|
||||||
title: "Level of logging to file"
|
|
||||||
type: "select"
|
|
||||||
options: [
|
|
||||||
{title: "Everything", value: "DEBUG"}
|
|
||||||
{title: "Only important messages", value: "INFO"}
|
|
||||||
{title: "Only errors", value: "ERROR"}
|
|
||||||
]
|
|
||||||
|
|
||||||
section.items.push
|
|
||||||
key: "threads_fs_read"
|
|
||||||
title: "Threads for async file system reads"
|
|
||||||
type: "select"
|
|
||||||
options: [
|
|
||||||
{title: "Sync read", value: 0}
|
|
||||||
{title: "1 thread", value: 1}
|
|
||||||
{title: "2 threads", value: 2}
|
|
||||||
{title: "3 threads", value: 3}
|
|
||||||
{title: "4 threads", value: 4}
|
|
||||||
{title: "5 threads", value: 5}
|
|
||||||
{title: "10 threads", value: 10}
|
|
||||||
]
|
|
||||||
|
|
||||||
section.items.push
|
|
||||||
key: "threads_fs_write"
|
|
||||||
title: "Threads for async file system writes"
|
|
||||||
type: "select"
|
|
||||||
options: [
|
|
||||||
{title: "Sync write", value: 0}
|
|
||||||
{title: "1 thread", value: 1}
|
|
||||||
{title: "2 threads", value: 2}
|
|
||||||
{title: "3 threads", value: 3}
|
|
||||||
{title: "4 threads", value: 4}
|
|
||||||
{title: "5 threads", value: 5}
|
|
||||||
{title: "10 threads", value: 10}
|
|
||||||
]
|
|
||||||
|
|
||||||
section.items.push
|
|
||||||
key: "threads_crypt"
|
|
||||||
title: "Threads for cryptographic functions"
|
|
||||||
type: "select"
|
|
||||||
options: [
|
|
||||||
{title: "Sync execution", value: 0}
|
|
||||||
{title: "1 thread", value: 1}
|
|
||||||
{title: "2 threads", value: 2}
|
|
||||||
{title: "3 threads", value: 3}
|
|
||||||
{title: "4 threads", value: 4}
|
|
||||||
{title: "5 threads", value: 5}
|
|
||||||
{title: "10 threads", value: 10}
|
|
||||||
]
|
|
||||||
|
|
||||||
section.items.push
|
|
||||||
key: "threads_db"
|
|
||||||
title: "Threads for database operations"
|
|
||||||
type: "select"
|
|
||||||
options: [
|
|
||||||
{title: "Sync execution", value: 0}
|
|
||||||
{title: "1 thread", value: 1}
|
|
||||||
{title: "2 threads", value: 2}
|
|
||||||
{title: "3 threads", value: 3}
|
|
||||||
{title: "4 threads", value: 4}
|
|
||||||
{title: "5 threads", value: 5}
|
|
||||||
{title: "10 threads", value: 10}
|
|
||||||
]
|
|
||||||
|
|
||||||
createSection: (title) =>
|
|
||||||
section = {}
|
|
||||||
section.title = title
|
|
||||||
section.items = []
|
|
||||||
@items.push(section)
|
|
||||||
return section
|
|
||||||
|
|
||||||
window.ConfigStorage = ConfigStorage
|
|
|
@ -1,124 +0,0 @@
|
||||||
class ConfigView extends Class
|
|
||||||
constructor: () ->
|
|
||||||
@
|
|
||||||
|
|
||||||
render: ->
|
|
||||||
@config_storage.items.map @renderSection
|
|
||||||
|
|
||||||
renderSection: (section) =>
|
|
||||||
h("div.section", {key: section.title}, [
|
|
||||||
h("h2", section.title),
|
|
||||||
h("div.config-items", section.items.map @renderSectionItem)
|
|
||||||
])
|
|
||||||
|
|
||||||
handleResetClick: (e) =>
|
|
||||||
node = e.currentTarget
|
|
||||||
config_key = node.attributes.config_key.value
|
|
||||||
default_value = node.attributes.default_value?.value
|
|
||||||
Page.cmd "wrapperConfirm", ["Reset #{config_key} value?", "Reset to default"], (res) =>
|
|
||||||
if (res)
|
|
||||||
@values[config_key] = default_value
|
|
||||||
Page.projector.scheduleRender()
|
|
||||||
|
|
||||||
renderSectionItem: (item) =>
|
|
||||||
value_pos = item.value_pos
|
|
||||||
|
|
||||||
if item.type == "textarea"
|
|
||||||
value_pos ?= "fullwidth"
|
|
||||||
else
|
|
||||||
value_pos ?= "right"
|
|
||||||
|
|
||||||
value_changed = @config_storage.formatValue(@values[item.key]) != item.value
|
|
||||||
value_default = @config_storage.formatValue(@values[item.key]) == item.default
|
|
||||||
|
|
||||||
if item.key in ["open_browser", "fileserver_port"] # Value default for some settings makes no sense
|
|
||||||
value_default = true
|
|
||||||
|
|
||||||
marker_title = "Changed from default value: #{item.default} -> #{@values[item.key]}"
|
|
||||||
if item.pending
|
|
||||||
marker_title += " (change pending until client restart)"
|
|
||||||
|
|
||||||
if item.isHidden?()
|
|
||||||
return null
|
|
||||||
|
|
||||||
h("div.config-item", {key: item.title, enterAnimation: Animation.slideDown, exitAnimation: Animation.slideUpInout}, [
|
|
||||||
h("div.title", [
|
|
||||||
h("h3", item.title),
|
|
||||||
h("div.description", item.description)
|
|
||||||
])
|
|
||||||
h("div.value.value-#{value_pos}",
|
|
||||||
if item.type == "select"
|
|
||||||
@renderValueSelect(item)
|
|
||||||
else if item.type == "checkbox"
|
|
||||||
@renderValueCheckbox(item)
|
|
||||||
else if item.type == "textarea"
|
|
||||||
@renderValueTextarea(item)
|
|
||||||
else
|
|
||||||
@renderValueText(item)
|
|
||||||
h("a.marker", {
|
|
||||||
href: "#Reset", title: marker_title,
|
|
||||||
onclick: @handleResetClick, config_key: item.key, default_value: item.default,
|
|
||||||
classes: {default: value_default, changed: value_changed, visible: not value_default or value_changed or item.pending, pending: item.pending}
|
|
||||||
}, "\u2022")
|
|
||||||
)
|
|
||||||
])
|
|
||||||
|
|
||||||
# Values
|
|
||||||
handleInputChange: (e) =>
|
|
||||||
node = e.target
|
|
||||||
config_key = node.attributes.config_key.value
|
|
||||||
@values[config_key] = node.value
|
|
||||||
Page.projector.scheduleRender()
|
|
||||||
|
|
||||||
handleCheckboxChange: (e) =>
|
|
||||||
node = e.currentTarget
|
|
||||||
config_key = node.attributes.config_key.value
|
|
||||||
value = not node.classList.contains("checked")
|
|
||||||
@values[config_key] = value
|
|
||||||
Page.projector.scheduleRender()
|
|
||||||
|
|
||||||
renderValueText: (item) =>
|
|
||||||
value = @values[item.key]
|
|
||||||
if not value
|
|
||||||
value = ""
|
|
||||||
h("input.input-#{item.type}", {type: item.type, config_key: item.key, value: value, placeholder: item.placeholder, oninput: @handleInputChange})
|
|
||||||
|
|
||||||
autosizeTextarea: (e) =>
|
|
||||||
if e.currentTarget
|
|
||||||
# @handleInputChange(e)
|
|
||||||
node = e.currentTarget
|
|
||||||
else
|
|
||||||
node = e
|
|
||||||
height_before = node.style.height
|
|
||||||
if height_before
|
|
||||||
node.style.height = "0px"
|
|
||||||
h = node.offsetHeight
|
|
||||||
scrollh = node.scrollHeight + 20
|
|
||||||
if scrollh > h
|
|
||||||
node.style.height = scrollh + "px"
|
|
||||||
else
|
|
||||||
node.style.height = height_before
|
|
||||||
|
|
||||||
renderValueTextarea: (item) =>
|
|
||||||
value = @values[item.key]
|
|
||||||
if not value
|
|
||||||
value = ""
|
|
||||||
h("textarea.input-#{item.type}.input-text",{
|
|
||||||
type: item.type, config_key: item.key, oninput: @handleInputChange, afterCreate: @autosizeTextarea,
|
|
||||||
updateAnimation: @autosizeTextarea, value: value, placeholder: item.placeholder
|
|
||||||
})
|
|
||||||
|
|
||||||
renderValueCheckbox: (item) =>
|
|
||||||
if @values[item.key] and @values[item.key] != "False"
|
|
||||||
checked = true
|
|
||||||
else
|
|
||||||
checked = false
|
|
||||||
h("div.checkbox", {onclick: @handleCheckboxChange, config_key: item.key, classes: {checked: checked}}, h("div.checkbox-skin"))
|
|
||||||
|
|
||||||
renderValueSelect: (item) =>
|
|
||||||
h("select.input-select", {config_key: item.key, oninput: @handleInputChange},
|
|
||||||
item.options.map (option) =>
|
|
||||||
h("option", {selected: option.value.toString() == @values[item.key], value: option.value}, option.title)
|
|
||||||
)
|
|
||||||
|
|
||||||
window.ConfigView = ConfigView
|
|
|
@ -1,129 +0,0 @@
|
||||||
window.h = maquette.h
|
|
||||||
|
|
||||||
class UiConfig extends ZeroFrame
|
|
||||||
init: ->
|
|
||||||
@save_visible = true
|
|
||||||
@config = null # Setting currently set on the server
|
|
||||||
@values = null # Entered values on the page
|
|
||||||
@config_view = new ConfigView()
|
|
||||||
window.onbeforeunload = =>
|
|
||||||
if @getValuesChanged().length > 0
|
|
||||||
return true
|
|
||||||
else
|
|
||||||
return null
|
|
||||||
|
|
||||||
onOpenWebsocket: =>
|
|
||||||
@cmd("wrapperSetTitle", "Config - ZeroNet")
|
|
||||||
@cmd "serverInfo", {}, (server_info) =>
|
|
||||||
@server_info = server_info
|
|
||||||
@restart_loading = false
|
|
||||||
@updateConfig()
|
|
||||||
|
|
||||||
updateConfig: (cb) =>
|
|
||||||
@cmd "configList", [], (res) =>
|
|
||||||
@config = res
|
|
||||||
@values = {}
|
|
||||||
@config_storage = new ConfigStorage(@config)
|
|
||||||
@config_view.values = @values
|
|
||||||
@config_view.config_storage = @config_storage
|
|
||||||
for key, item of res
|
|
||||||
value = item.value
|
|
||||||
@values[key] = @config_storage.formatValue(value)
|
|
||||||
@projector.scheduleRender()
|
|
||||||
cb?()
|
|
||||||
|
|
||||||
createProjector: =>
|
|
||||||
@projector = maquette.createProjector()
|
|
||||||
@projector.replace($("#content"), @render)
|
|
||||||
@projector.replace($("#bottom-save"), @renderBottomSave)
|
|
||||||
@projector.replace($("#bottom-restart"), @renderBottomRestart)
|
|
||||||
|
|
||||||
getValuesChanged: =>
|
|
||||||
values_changed = []
|
|
||||||
for key, value of @values
|
|
||||||
if @config_storage.formatValue(value) != @config_storage.formatValue(@config[key]?.value)
|
|
||||||
values_changed.push({key: key, value: value})
|
|
||||||
return values_changed
|
|
||||||
|
|
||||||
getValuesPending: =>
|
|
||||||
values_pending = []
|
|
||||||
for key, item of @config
|
|
||||||
if item.pending
|
|
||||||
values_pending.push(key)
|
|
||||||
return values_pending
|
|
||||||
|
|
||||||
saveValues: (cb) =>
|
|
||||||
changed_values = @getValuesChanged()
|
|
||||||
for item, i in changed_values
|
|
||||||
last = i == changed_values.length - 1
|
|
||||||
value = @config_storage.deformatValue(item.value, typeof(@config[item.key].default))
|
|
||||||
default_value = @config_storage.deformatValue(@config[item.key].default, typeof(@config[item.key].default))
|
|
||||||
value_same_as_default = JSON.stringify(default_value) == JSON.stringify(value)
|
|
||||||
|
|
||||||
if @config[item.key].item.valid_pattern and not @config[item.key].item.isHidden?()
|
|
||||||
match = value.match(@config[item.key].item.valid_pattern)
|
|
||||||
if not match or match[0] != value
|
|
||||||
message = "Invalid value of #{@config[item.key].item.title}: #{value} (does not matches #{@config[item.key].item.valid_pattern})"
|
|
||||||
Page.cmd("wrapperNotification", ["error", message])
|
|
||||||
cb(false)
|
|
||||||
break
|
|
||||||
|
|
||||||
if value_same_as_default
|
|
||||||
value = null
|
|
||||||
|
|
||||||
@saveValue(item.key, value, if last then cb else null)
|
|
||||||
|
|
||||||
saveValue: (key, value, cb) =>
|
|
||||||
if key == "open_browser"
|
|
||||||
if value
|
|
||||||
value = "default_browser"
|
|
||||||
else
|
|
||||||
value = "False"
|
|
||||||
|
|
||||||
Page.cmd "configSet", [key, value], (res) =>
|
|
||||||
if res != "ok"
|
|
||||||
Page.cmd "wrapperNotification", ["error", res.error]
|
|
||||||
cb?(true)
|
|
||||||
|
|
||||||
render: =>
|
|
||||||
if not @config
|
|
||||||
return h("div.content")
|
|
||||||
|
|
||||||
h("div.content", [
|
|
||||||
@config_view.render()
|
|
||||||
])
|
|
||||||
|
|
||||||
handleSaveClick: =>
|
|
||||||
@save_loading = true
|
|
||||||
@logStart "Save"
|
|
||||||
@saveValues (success) =>
|
|
||||||
@save_loading = false
|
|
||||||
@logEnd "Save"
|
|
||||||
if success
|
|
||||||
@updateConfig()
|
|
||||||
Page.projector.scheduleRender()
|
|
||||||
return false
|
|
||||||
|
|
||||||
renderBottomSave: =>
|
|
||||||
values_changed = @getValuesChanged()
|
|
||||||
h("div.bottom.bottom-save", {classes: {visible: values_changed.length}}, h("div.bottom-content", [
|
|
||||||
h("div.title", "#{values_changed.length} configuration item value changed"),
|
|
||||||
h("a.button.button-submit.button-save", {href: "#Save", classes: {loading: @save_loading}, onclick: @handleSaveClick}, "Save settings")
|
|
||||||
]))
|
|
||||||
|
|
||||||
handleRestartClick: =>
|
|
||||||
@restart_loading = true
|
|
||||||
Page.cmd("serverShutdown", {restart: true})
|
|
||||||
Page.projector.scheduleRender()
|
|
||||||
return false
|
|
||||||
|
|
||||||
renderBottomRestart: =>
|
|
||||||
values_pending = @getValuesPending()
|
|
||||||
values_changed = @getValuesChanged()
|
|
||||||
h("div.bottom.bottom-restart", {classes: {visible: values_pending.length and not values_changed.length}}, h("div.bottom-content", [
|
|
||||||
h("div.title", "Some changed settings requires restart"),
|
|
||||||
h("a.button.button-submit.button-restart", {href: "#Restart", classes: {loading: @restart_loading}, onclick: @handleRestartClick}, "Restart ZeroNet client")
|
|
||||||
]))
|
|
||||||
|
|
||||||
window.Page = new UiConfig()
|
|
||||||
window.Page.createProjector()
|
|
|
@ -1,23 +0,0 @@
|
||||||
class Class
|
|
||||||
trace: true
|
|
||||||
|
|
||||||
log: (args...) ->
|
|
||||||
return unless @trace
|
|
||||||
return if typeof console is 'undefined'
|
|
||||||
args.unshift("[#{@.constructor.name}]")
|
|
||||||
console.log(args...)
|
|
||||||
@
|
|
||||||
|
|
||||||
logStart: (name, args...) ->
|
|
||||||
return unless @trace
|
|
||||||
@logtimers or= {}
|
|
||||||
@logtimers[name] = +(new Date)
|
|
||||||
@log "#{name}", args..., "(started)" if args.length > 0
|
|
||||||
@
|
|
||||||
|
|
||||||
logEnd: (name, args...) ->
|
|
||||||
ms = +(new Date)-@logtimers[name]
|
|
||||||
@log "#{name}", args..., "(Done in #{ms}ms)"
|
|
||||||
@
|
|
||||||
|
|
||||||
window.Class = Class
|
|
|
@ -1,74 +0,0 @@
|
||||||
# From: http://dev.bizo.com/2011/12/promises-in-javascriptcoffeescript.html
|
|
||||||
|
|
||||||
class Promise
|
|
||||||
@when: (tasks...) ->
|
|
||||||
num_uncompleted = tasks.length
|
|
||||||
args = new Array(num_uncompleted)
|
|
||||||
promise = new Promise()
|
|
||||||
|
|
||||||
for task, task_id in tasks
|
|
||||||
((task_id) ->
|
|
||||||
task.then(() ->
|
|
||||||
args[task_id] = Array.prototype.slice.call(arguments)
|
|
||||||
num_uncompleted--
|
|
||||||
promise.complete.apply(promise, args) if num_uncompleted == 0
|
|
||||||
)
|
|
||||||
)(task_id)
|
|
||||||
|
|
||||||
return promise
|
|
||||||
|
|
||||||
constructor: ->
|
|
||||||
@resolved = false
|
|
||||||
@end_promise = null
|
|
||||||
@result = null
|
|
||||||
@callbacks = []
|
|
||||||
|
|
||||||
resolve: ->
|
|
||||||
if @resolved
|
|
||||||
return false
|
|
||||||
@resolved = true
|
|
||||||
@data = arguments
|
|
||||||
if not arguments.length
|
|
||||||
@data = [true]
|
|
||||||
@result = @data[0]
|
|
||||||
for callback in @callbacks
|
|
||||||
back = callback.apply callback, @data
|
|
||||||
if @end_promise
|
|
||||||
@end_promise.resolve(back)
|
|
||||||
|
|
||||||
fail: ->
|
|
||||||
@resolve(false)
|
|
||||||
|
|
||||||
then: (callback) ->
|
|
||||||
if @resolved == true
|
|
||||||
callback.apply callback, @data
|
|
||||||
return
|
|
||||||
|
|
||||||
@callbacks.push callback
|
|
||||||
|
|
||||||
@end_promise = new Promise()
|
|
||||||
|
|
||||||
window.Promise = Promise
|
|
||||||
|
|
||||||
###
|
|
||||||
s = Date.now()
|
|
||||||
log = (text) ->
|
|
||||||
console.log Date.now()-s, Array.prototype.slice.call(arguments).join(", ")
|
|
||||||
|
|
||||||
log "Started"
|
|
||||||
|
|
||||||
cmd = (query) ->
|
|
||||||
p = new Promise()
|
|
||||||
setTimeout ( ->
|
|
||||||
p.resolve query+" Result"
|
|
||||||
), 100
|
|
||||||
return p
|
|
||||||
|
|
||||||
back = cmd("SELECT * FROM message").then (res) ->
|
|
||||||
log res
|
|
||||||
return "Return from query"
|
|
||||||
.then (res) ->
|
|
||||||
log "Back then", res
|
|
||||||
|
|
||||||
log "Query started", back
|
|
||||||
###
|
|
|
@ -1,8 +0,0 @@
|
||||||
String::startsWith = (s) -> @[...s.length] is s
|
|
||||||
String::endsWith = (s) -> s is '' or @[-s.length..] is s
|
|
||||||
String::repeat = (count) -> new Array( count + 1 ).join(@)
|
|
||||||
|
|
||||||
window.isEmpty = (obj) ->
|
|
||||||
for key of obj
|
|
||||||
return false
|
|
||||||
return true
|
|
|
@ -1,138 +0,0 @@
|
||||||
class Animation
|
|
||||||
slideDown: (elem, props) ->
|
|
||||||
if elem.offsetTop > 2000
|
|
||||||
return
|
|
||||||
|
|
||||||
h = elem.offsetHeight
|
|
||||||
cstyle = window.getComputedStyle(elem)
|
|
||||||
margin_top = cstyle.marginTop
|
|
||||||
margin_bottom = cstyle.marginBottom
|
|
||||||
padding_top = cstyle.paddingTop
|
|
||||||
padding_bottom = cstyle.paddingBottom
|
|
||||||
transition = cstyle.transition
|
|
||||||
|
|
||||||
elem.style.boxSizing = "border-box"
|
|
||||||
elem.style.overflow = "hidden"
|
|
||||||
elem.style.transform = "scale(0.6)"
|
|
||||||
elem.style.opacity = "0"
|
|
||||||
elem.style.height = "0px"
|
|
||||||
elem.style.marginTop = "0px"
|
|
||||||
elem.style.marginBottom = "0px"
|
|
||||||
elem.style.paddingTop = "0px"
|
|
||||||
elem.style.paddingBottom = "0px"
|
|
||||||
elem.style.transition = "none"
|
|
||||||
|
|
||||||
setTimeout (->
|
|
||||||
elem.className += " animate-inout"
|
|
||||||
elem.style.height = h+"px"
|
|
||||||
elem.style.transform = "scale(1)"
|
|
||||||
elem.style.opacity = "1"
|
|
||||||
elem.style.marginTop = margin_top
|
|
||||||
elem.style.marginBottom = margin_bottom
|
|
||||||
elem.style.paddingTop = padding_top
|
|
||||||
elem.style.paddingBottom = padding_bottom
|
|
||||||
), 1
|
|
||||||
|
|
||||||
elem.addEventListener "transitionend", ->
|
|
||||||
elem.classList.remove("animate-inout")
|
|
||||||
elem.style.transition = elem.style.transform = elem.style.opacity = elem.style.height = null
|
|
||||||
elem.style.boxSizing = elem.style.marginTop = elem.style.marginBottom = null
|
|
||||||
elem.style.paddingTop = elem.style.paddingBottom = elem.style.overflow = null
|
|
||||||
elem.removeEventListener "transitionend", arguments.callee, false
|
|
||||||
|
|
||||||
|
|
||||||
slideUp: (elem, remove_func, props) ->
|
|
||||||
if elem.offsetTop > 1000
|
|
||||||
return remove_func()
|
|
||||||
|
|
||||||
elem.className += " animate-back"
|
|
||||||
elem.style.boxSizing = "border-box"
|
|
||||||
elem.style.height = elem.offsetHeight+"px"
|
|
||||||
elem.style.overflow = "hidden"
|
|
||||||
elem.style.transform = "scale(1)"
|
|
||||||
elem.style.opacity = "1"
|
|
||||||
elem.style.pointerEvents = "none"
|
|
||||||
setTimeout (->
|
|
||||||
elem.style.height = "0px"
|
|
||||||
elem.style.marginTop = "0px"
|
|
||||||
elem.style.marginBottom = "0px"
|
|
||||||
elem.style.paddingTop = "0px"
|
|
||||||
elem.style.paddingBottom = "0px"
|
|
||||||
elem.style.transform = "scale(0.8)"
|
|
||||||
elem.style.borderTopWidth = "0px"
|
|
||||||
elem.style.borderBottomWidth = "0px"
|
|
||||||
elem.style.opacity = "0"
|
|
||||||
), 1
|
|
||||||
elem.addEventListener "transitionend", (e) ->
|
|
||||||
if e.propertyName == "opacity" or e.elapsedTime >= 0.6
|
|
||||||
elem.removeEventListener "transitionend", arguments.callee, false
|
|
||||||
remove_func()
|
|
||||||
|
|
||||||
|
|
||||||
slideUpInout: (elem, remove_func, props) ->
|
|
||||||
elem.className += " animate-inout"
|
|
||||||
elem.style.boxSizing = "border-box"
|
|
||||||
elem.style.height = elem.offsetHeight+"px"
|
|
||||||
elem.style.overflow = "hidden"
|
|
||||||
elem.style.transform = "scale(1)"
|
|
||||||
elem.style.opacity = "1"
|
|
||||||
elem.style.pointerEvents = "none"
|
|
||||||
setTimeout (->
|
|
||||||
elem.style.height = "0px"
|
|
||||||
elem.style.marginTop = "0px"
|
|
||||||
elem.style.marginBottom = "0px"
|
|
||||||
elem.style.paddingTop = "0px"
|
|
||||||
elem.style.paddingBottom = "0px"
|
|
||||||
elem.style.transform = "scale(0.8)"
|
|
||||||
elem.style.borderTopWidth = "0px"
|
|
||||||
elem.style.borderBottomWidth = "0px"
|
|
||||||
elem.style.opacity = "0"
|
|
||||||
), 1
|
|
||||||
elem.addEventListener "transitionend", (e) ->
|
|
||||||
if e.propertyName == "opacity" or e.elapsedTime >= 0.6
|
|
||||||
elem.removeEventListener "transitionend", arguments.callee, false
|
|
||||||
remove_func()
|
|
||||||
|
|
||||||
|
|
||||||
showRight: (elem, props) ->
|
|
||||||
elem.className += " animate"
|
|
||||||
elem.style.opacity = 0
|
|
||||||
elem.style.transform = "TranslateX(-20px) Scale(1.01)"
|
|
||||||
setTimeout (->
|
|
||||||
elem.style.opacity = 1
|
|
||||||
elem.style.transform = "TranslateX(0px) Scale(1)"
|
|
||||||
), 1
|
|
||||||
elem.addEventListener "transitionend", ->
|
|
||||||
elem.classList.remove("animate")
|
|
||||||
elem.style.transform = elem.style.opacity = null
|
|
||||||
|
|
||||||
|
|
||||||
show: (elem, props) ->
|
|
||||||
delay = arguments[arguments.length-2]?.delay*1000 or 1
|
|
||||||
elem.style.opacity = 0
|
|
||||||
setTimeout (->
|
|
||||||
elem.className += " animate"
|
|
||||||
), 1
|
|
||||||
setTimeout (->
|
|
||||||
elem.style.opacity = 1
|
|
||||||
), delay
|
|
||||||
elem.addEventListener "transitionend", ->
|
|
||||||
elem.classList.remove("animate")
|
|
||||||
elem.style.opacity = null
|
|
||||||
elem.removeEventListener "transitionend", arguments.callee, false
|
|
||||||
|
|
||||||
hide: (elem, remove_func, props) ->
|
|
||||||
delay = arguments[arguments.length-2]?.delay*1000 or 1
|
|
||||||
elem.className += " animate"
|
|
||||||
setTimeout (->
|
|
||||||
elem.style.opacity = 0
|
|
||||||
), delay
|
|
||||||
elem.addEventListener "transitionend", (e) ->
|
|
||||||
if e.propertyName == "opacity"
|
|
||||||
remove_func()
|
|
||||||
|
|
||||||
addVisibleClass: (elem, props) ->
|
|
||||||
setTimeout ->
|
|
||||||
elem.classList.add("visible")
|
|
||||||
|
|
||||||
window.Animation = new Animation()
|
|
|
@ -1,3 +0,0 @@
|
||||||
window.$ = (selector) ->
|
|
||||||
if selector.startsWith("#")
|
|
||||||
return document.getElementById(selector.replace("#", ""))
|
|
|
@ -1,85 +0,0 @@
|
||||||
class ZeroFrame extends Class
|
|
||||||
constructor: (url) ->
|
|
||||||
@url = url
|
|
||||||
@waiting_cb = {}
|
|
||||||
@wrapper_nonce = document.location.href.replace(/.*wrapper_nonce=([A-Za-z0-9]+).*/, "$1")
|
|
||||||
@connect()
|
|
||||||
@next_message_id = 1
|
|
||||||
@history_state = {}
|
|
||||||
@init()
|
|
||||||
|
|
||||||
|
|
||||||
init: ->
|
|
||||||
@
|
|
||||||
|
|
||||||
|
|
||||||
connect: ->
|
|
||||||
@target = window.parent
|
|
||||||
window.addEventListener("message", @onMessage, false)
|
|
||||||
@cmd("innerReady")
|
|
||||||
|
|
||||||
# Save scrollTop
|
|
||||||
window.addEventListener "beforeunload", (e) =>
|
|
||||||
@log "save scrollTop", window.pageYOffset
|
|
||||||
@history_state["scrollTop"] = window.pageYOffset
|
|
||||||
@cmd "wrapperReplaceState", [@history_state, null]
|
|
||||||
|
|
||||||
# Restore scrollTop
|
|
||||||
@cmd "wrapperGetState", [], (state) =>
|
|
||||||
@history_state = state if state?
|
|
||||||
@log "restore scrollTop", state, window.pageYOffset
|
|
||||||
if window.pageYOffset == 0 and state
|
|
||||||
window.scroll(window.pageXOffset, state.scrollTop)
|
|
||||||
|
|
||||||
|
|
||||||
onMessage: (e) =>
|
|
||||||
message = e.data
|
|
||||||
cmd = message.cmd
|
|
||||||
if cmd == "response"
|
|
||||||
if @waiting_cb[message.to]?
|
|
||||||
@waiting_cb[message.to](message.result)
|
|
||||||
else
|
|
||||||
@log "Websocket callback not found:", message
|
|
||||||
else if cmd == "wrapperReady" # Wrapper inited later
|
|
||||||
@cmd("innerReady")
|
|
||||||
else if cmd == "ping"
|
|
||||||
@response message.id, "pong"
|
|
||||||
else if cmd == "wrapperOpenedWebsocket"
|
|
||||||
@onOpenWebsocket()
|
|
||||||
else if cmd == "wrapperClosedWebsocket"
|
|
||||||
@onCloseWebsocket()
|
|
||||||
else
|
|
||||||
@onRequest cmd, message.params
|
|
||||||
|
|
||||||
|
|
||||||
onRequest: (cmd, message) =>
|
|
||||||
@log "Unknown request", message
|
|
||||||
|
|
||||||
|
|
||||||
response: (to, result) ->
|
|
||||||
@send {"cmd": "response", "to": to, "result": result}
|
|
||||||
|
|
||||||
|
|
||||||
cmd: (cmd, params={}, cb=null) ->
|
|
||||||
@send {"cmd": cmd, "params": params}, cb
|
|
||||||
|
|
||||||
|
|
||||||
send: (message, cb=null) ->
|
|
||||||
message.wrapper_nonce = @wrapper_nonce
|
|
||||||
message.id = @next_message_id
|
|
||||||
@next_message_id += 1
|
|
||||||
@target.postMessage(message, "*")
|
|
||||||
if cb
|
|
||||||
@waiting_cb[message.id] = cb
|
|
||||||
|
|
||||||
|
|
||||||
onOpenWebsocket: =>
|
|
||||||
@log "Websocket open"
|
|
||||||
|
|
||||||
|
|
||||||
onCloseWebsocket: =>
|
|
||||||
@log "Websocket close"
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
window.ZeroFrame = ZeroFrame
|
|
|
@ -1,15 +0,0 @@
|
||||||
window.BINARY_EXTENSIONS = [
|
|
||||||
"3dm", "3ds", "3g2", "3gp", "7z", "a", "aac", "adp", "ai", "aif", "aiff", "alz", "ape", "apk", "appimage", "ar", "arj", "asc", "asf", "au", "avi", "bak",
|
|
||||||
"baml", "bh", "bin", "bk", "bmp", "btif", "bz2", "bzip2", "cab", "caf", "cgm", "class", "cmx", "cpio", "cr2", "cur", "dat", "dcm", "deb", "dex", "djvu",
|
|
||||||
"dll", "dmg", "dng", "doc", "docm", "docx", "dot", "dotm", "dra", "DS_Store", "dsk", "dts", "dtshd", "dvb", "dwg", "dxf", "ecelp4800", "ecelp7470",
|
|
||||||
"ecelp9600", "egg", "eol", "eot", "epub", "exe", "f4v", "fbs", "fh", "fla", "flac", "flatpak", "fli", "flv", "fpx", "fst", "fvt", "g3", "gh", "gif",
|
|
||||||
"gpg", "graffle", "gz", "gzip", "h261", "h263", "h264", "icns", "ico", "ief", "img", "ipa", "iso", "jar", "jpeg", "jpg", "jpgv", "jpm", "jxr", "key",
|
|
||||||
"ktx", "lha", "lib", "lvp", "lz", "lzh", "lzma", "lzo", "m3u", "m4a", "m4v", "mar", "mdi", "mht", "mid", "midi", "mj2", "mka", "mkv", "mmr", "mng",
|
|
||||||
"mobi", "mov", "movie", "mp3", "mp4", "mp4a", "mpeg", "mpg", "mpga", "msgpack", "mxu", "nef", "npx", "numbers", "nupkg", "o", "oga", "ogg", "ogv",
|
|
||||||
"otf", "pages", "pbm", "pcx", "pdb", "pdf", "pea", "pgm", "pic", "png", "pnm", "pot", "potm", "potx", "ppa", "ppam", "ppm", "pps", "ppsm", "ppsx",
|
|
||||||
"ppt", "pptm", "pptx", "psd", "pya", "pyc", "pyo", "pyv", "qt", "rar", "ras", "raw", "resources", "rgb", "rip", "rlc", "rmf", "rmvb", "rpm", "rtf",
|
|
||||||
"rz", "s3m", "s7z", "scpt", "sgi", "shar", "sig", "sil", "sketch", "slk", "smv", "snap", "snk", "so", "stl", "sub", "suo", "swf", "tar", "tbz2", "tbz",
|
|
||||||
"tga", "tgz", "thmx", "tif", "tiff", "tlz", "ttc", "ttf", "txz", "udf", "uvh", "uvi", "uvm", "uvp", "uvs", "uvu", "viv", "vob", "war", "wav", "wax",
|
|
||||||
"wbmp", "wdp", "weba", "webm", "webp", "whl", "wim", "wm", "wma", "wmv", "wmx", "woff2", "woff", "wrm", "wvx", "xbm", "xif", "xla", "xlam", "xls",
|
|
||||||
"xlsb", "xlsm", "xlsx", "xlt", "xltm", "xltx", "xm", "xmind", "xpi", "xpm", "xwd", "xz", "z", "zip", "zipx"
|
|
||||||
]
|
|
|
@ -1,179 +0,0 @@
|
||||||
class FileEditor extends Class
|
|
||||||
constructor: (@inner_path) ->
|
|
||||||
@need_update = true
|
|
||||||
@on_loaded = new Promise()
|
|
||||||
@is_loading = false
|
|
||||||
@content = ""
|
|
||||||
@node_cm = null
|
|
||||||
@cm = null
|
|
||||||
@error = null
|
|
||||||
@is_loaded = false
|
|
||||||
@is_modified = false
|
|
||||||
@is_saving = false
|
|
||||||
@mode = "Loading"
|
|
||||||
|
|
||||||
update: ->
|
|
||||||
is_required = Page.url_params.get("edit_mode") != "new"
|
|
||||||
|
|
||||||
Page.cmd "fileGet", {inner_path: @inner_path, required: is_required}, (res) =>
|
|
||||||
if res?.error
|
|
||||||
@error = res.error
|
|
||||||
@content = res.error
|
|
||||||
@log "Error loading: #{@error}"
|
|
||||||
else
|
|
||||||
if res
|
|
||||||
@content = res
|
|
||||||
else
|
|
||||||
@content = ""
|
|
||||||
@mode = "Create"
|
|
||||||
if not @content
|
|
||||||
@cm.getDoc().clearHistory()
|
|
||||||
@cm.setValue(@content)
|
|
||||||
if not @error
|
|
||||||
@is_loaded = true
|
|
||||||
Page.projector.scheduleRender()
|
|
||||||
|
|
||||||
isModified: =>
|
|
||||||
return @content != @cm.getValue()
|
|
||||||
|
|
||||||
storeCmNode: (node) =>
|
|
||||||
@node_cm = node
|
|
||||||
|
|
||||||
getMode: (inner_path) ->
|
|
||||||
ext = inner_path.split(".").pop()
|
|
||||||
types = {
|
|
||||||
"py": "python",
|
|
||||||
"json": "application/json",
|
|
||||||
"js": "javascript",
|
|
||||||
"coffee": "coffeescript",
|
|
||||||
"html": "htmlmixed",
|
|
||||||
"htm": "htmlmixed",
|
|
||||||
"php": "htmlmixed",
|
|
||||||
"rs": "rust",
|
|
||||||
"css": "css",
|
|
||||||
"md": "markdown",
|
|
||||||
"xml": "xml",
|
|
||||||
"svg": "xml"
|
|
||||||
}
|
|
||||||
return types[ext]
|
|
||||||
|
|
||||||
foldJson: (from, to) =>
|
|
||||||
@log "foldJson", from, to
|
|
||||||
# Get open / close token
|
|
||||||
startToken = '{'
|
|
||||||
endToken = '}'
|
|
||||||
prevLine = @cm.getLine(from.line)
|
|
||||||
if prevLine.lastIndexOf('[') > prevLine.lastIndexOf('{')
|
|
||||||
startToken = '['
|
|
||||||
endToken = ']'
|
|
||||||
|
|
||||||
# Get json content
|
|
||||||
internal = @cm.getRange(from, to)
|
|
||||||
toParse = startToken + internal + endToken
|
|
||||||
|
|
||||||
#Get key count
|
|
||||||
try
|
|
||||||
parsed = JSON.parse(toParse)
|
|
||||||
count = Object.keys(parsed).length
|
|
||||||
catch e
|
|
||||||
null
|
|
||||||
|
|
||||||
return if count then "\u21A4#{count}\u21A6" else "\u2194"
|
|
||||||
|
|
||||||
createCodeMirror: ->
|
|
||||||
mode = @getMode(@inner_path)
|
|
||||||
@log "Creating CodeMirror", @inner_path, mode
|
|
||||||
options = {
|
|
||||||
value: "Loading...",
|
|
||||||
mode: mode,
|
|
||||||
lineNumbers: true,
|
|
||||||
styleActiveLine: true,
|
|
||||||
matchBrackets: true,
|
|
||||||
keyMap: "sublime",
|
|
||||||
theme: "mdn-like",
|
|
||||||
extraKeys: {"Ctrl-Space": "autocomplete"},
|
|
||||||
foldGutter: true,
|
|
||||||
gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"]
|
|
||||||
|
|
||||||
}
|
|
||||||
if mode == "application/json"
|
|
||||||
options.gutters.unshift("CodeMirror-lint-markers")
|
|
||||||
options.lint = true
|
|
||||||
options.foldOptions = { widget: @foldJson }
|
|
||||||
|
|
||||||
@cm = CodeMirror(@node_cm, options)
|
|
||||||
@cm.on "changes", (changes) =>
|
|
||||||
if @is_loaded and not @is_modified
|
|
||||||
@is_modified = true
|
|
||||||
Page.projector.scheduleRender()
|
|
||||||
|
|
||||||
|
|
||||||
loadEditor: ->
|
|
||||||
if not @is_loading
|
|
||||||
document.getElementsByTagName("head")[0].insertAdjacentHTML(
|
|
||||||
"beforeend",
|
|
||||||
"""<link rel="stylesheet" href="codemirror/all.css" />"""
|
|
||||||
)
|
|
||||||
script = document.createElement('script')
|
|
||||||
script.src = "codemirror/all.js"
|
|
||||||
script.onload = =>
|
|
||||||
@createCodeMirror()
|
|
||||||
@on_loaded.resolve()
|
|
||||||
document.head.appendChild(script)
|
|
||||||
return @on_loaded
|
|
||||||
|
|
||||||
handleSidebarButtonClick: =>
|
|
||||||
Page.is_sidebar_closed = not Page.is_sidebar_closed
|
|
||||||
return false
|
|
||||||
|
|
||||||
handleSaveClick: =>
|
|
||||||
num_errors = (mark for mark in Page.file_editor.cm.getAllMarks() when mark.className == "CodeMirror-lint-mark-error").length
|
|
||||||
if num_errors > 0
|
|
||||||
Page.cmd "wrapperConfirm", ["<b>Warning:</b> The file looks invalid.", "Save anyway"], @save
|
|
||||||
else
|
|
||||||
@save()
|
|
||||||
return false
|
|
||||||
|
|
||||||
save: =>
|
|
||||||
Page.projector.scheduleRender()
|
|
||||||
@is_saving = true
|
|
||||||
Page.cmd "fileWrite", [@inner_path, Text.fileEncode(@cm.getValue())], (res) =>
|
|
||||||
@is_saving = false
|
|
||||||
if res.error
|
|
||||||
Page.cmd "wrapperNotification", ["error", "Error saving #{res.error}"]
|
|
||||||
else
|
|
||||||
@is_save_done = true
|
|
||||||
setTimeout (() =>
|
|
||||||
@is_save_done = false
|
|
||||||
Page.projector.scheduleRender()
|
|
||||||
), 2000
|
|
||||||
@content = @cm.getValue()
|
|
||||||
@is_modified = false
|
|
||||||
if @mode == "Create"
|
|
||||||
@mode = "Edit"
|
|
||||||
Page.file_list.need_update = true
|
|
||||||
Page.projector.scheduleRender()
|
|
||||||
|
|
||||||
render: ->
|
|
||||||
if @need_update
|
|
||||||
@loadEditor().then =>
|
|
||||||
@update()
|
|
||||||
@need_update = false
|
|
||||||
h("div.editor", {afterCreate: @storeCmNode, classes: {error: @error, loaded: @is_loaded}}, [
|
|
||||||
h("a.sidebar-button", {href: "#Sidebar", onclick: @handleSidebarButtonClick}, h("span", "\u2039")),
|
|
||||||
h("div.editor-head", [
|
|
||||||
if @mode in ["Edit", "Create"]
|
|
||||||
h("a.save.button",
|
|
||||||
{href: "#Save", classes: {loading: @is_saving, done: @is_save_done, disabled: not @is_modified}, onclick: @handleSaveClick},
|
|
||||||
if @is_save_done then "Save: done!" else "Save"
|
|
||||||
)
|
|
||||||
h("span.title", @mode, ": ", @inner_path)
|
|
||||||
]),
|
|
||||||
if @error
|
|
||||||
h("div.error-message",
|
|
||||||
h("h2", "Unable to load the file: #{@error}")
|
|
||||||
h("a", {href: Page.file_list.getHref(@inner_path)}, "View in browser")
|
|
||||||
)
|
|
||||||
])
|
|
||||||
|
|
||||||
window.FileEditor = FileEditor
|
|
|
@ -1,194 +0,0 @@
|
||||||
class FileItemList extends Class
|
|
||||||
constructor: (@inner_path) ->
|
|
||||||
@items = []
|
|
||||||
@updating = false
|
|
||||||
@files_modified = {}
|
|
||||||
@dirs_modified = {}
|
|
||||||
@files_added = {}
|
|
||||||
@dirs_added = {}
|
|
||||||
@files_optional = {}
|
|
||||||
@items_by_name = {}
|
|
||||||
|
|
||||||
# Update item list
|
|
||||||
update: (cb) ->
|
|
||||||
@updating = true
|
|
||||||
@logStart("Updating dirlist")
|
|
||||||
Page.cmd "dirList", {inner_path: @inner_path, stats: true}, (res) =>
|
|
||||||
if res.error
|
|
||||||
@error = res.error
|
|
||||||
else
|
|
||||||
@error = null
|
|
||||||
pattern_ignore = RegExp("^" + Page.site_info.content?.ignore)
|
|
||||||
|
|
||||||
@items.splice(0, @items.length) # Remove all items
|
|
||||||
|
|
||||||
@items_by_name = {}
|
|
||||||
for row in res
|
|
||||||
row.type = @getFileType(row)
|
|
||||||
row.inner_path = @inner_path + row.name
|
|
||||||
if Page.site_info.content?.ignore and row.inner_path.match(pattern_ignore)
|
|
||||||
row.ignored = true
|
|
||||||
@items.push(row)
|
|
||||||
@items_by_name[row.name] = row
|
|
||||||
|
|
||||||
@sort()
|
|
||||||
|
|
||||||
if Page.site_info?.settings?.own
|
|
||||||
@updateAddedFiles()
|
|
||||||
|
|
||||||
@updateOptionalFiles =>
|
|
||||||
@updating = false
|
|
||||||
cb?()
|
|
||||||
@logEnd("Updating dirlist", @inner_path)
|
|
||||||
Page.projector.scheduleRender()
|
|
||||||
|
|
||||||
@updateModifiedFiles =>
|
|
||||||
Page.projector.scheduleRender()
|
|
||||||
|
|
||||||
|
|
||||||
updateModifiedFiles: (cb) =>
|
|
||||||
# Add modified attribute to changed files
|
|
||||||
Page.cmd "siteListModifiedFiles", [], (res) =>
|
|
||||||
@files_modified = {}
|
|
||||||
@dirs_modified = {}
|
|
||||||
for inner_path in res.modified_files
|
|
||||||
@files_modified[inner_path] = true
|
|
||||||
dir_inner_path = ""
|
|
||||||
dir_parts = inner_path.split("/")
|
|
||||||
for dir_part in dir_parts[..-2]
|
|
||||||
if dir_inner_path
|
|
||||||
dir_inner_path += "/#{dir_part}"
|
|
||||||
else
|
|
||||||
dir_inner_path = dir_part
|
|
||||||
@dirs_modified[dir_inner_path] = true
|
|
||||||
|
|
||||||
cb?()
|
|
||||||
|
|
||||||
# Update newly added items list since last sign
|
|
||||||
updateAddedFiles: =>
|
|
||||||
Page.cmd "fileGet", "content.json", (res) =>
|
|
||||||
if not res
|
|
||||||
return false
|
|
||||||
|
|
||||||
content = JSON.parse(res)
|
|
||||||
|
|
||||||
# Check new files
|
|
||||||
if not content.files?
|
|
||||||
return false
|
|
||||||
|
|
||||||
@files_added = {}
|
|
||||||
|
|
||||||
for file in @items
|
|
||||||
if file.name == "content.json" or file.is_dir
|
|
||||||
continue
|
|
||||||
if not content.files[@inner_path + file.name]
|
|
||||||
@files_added[@inner_path + file.name] = true
|
|
||||||
|
|
||||||
# Check new dirs
|
|
||||||
@dirs_added = {}
|
|
||||||
|
|
||||||
dirs_content = {}
|
|
||||||
for file_name of Object.assign({}, content.files, content.files_optional)
|
|
||||||
if not file_name.startsWith(@inner_path)
|
|
||||||
continue
|
|
||||||
|
|
||||||
pattern = new RegExp("#{@inner_path}(.*?)/")
|
|
||||||
match = file_name.match(pattern)
|
|
||||||
|
|
||||||
if not match
|
|
||||||
continue
|
|
||||||
|
|
||||||
dirs_content[match[1]] = true
|
|
||||||
|
|
||||||
for file in @items
|
|
||||||
if not file.is_dir
|
|
||||||
continue
|
|
||||||
if not dirs_content[file.name]
|
|
||||||
@dirs_added[@inner_path + file.name] = true
|
|
||||||
|
|
||||||
# Update optional files list
|
|
||||||
updateOptionalFiles: (cb) =>
|
|
||||||
Page.cmd "optionalFileList", {filter: ""}, (res) =>
|
|
||||||
@files_optional = {}
|
|
||||||
for optional_file in res
|
|
||||||
@files_optional[optional_file.inner_path] = optional_file
|
|
||||||
|
|
||||||
@addOptionalFilesToItems()
|
|
||||||
|
|
||||||
cb?()
|
|
||||||
|
|
||||||
# Add optional files to item list
|
|
||||||
addOptionalFilesToItems: =>
|
|
||||||
is_added = false
|
|
||||||
for inner_path, optional_file of @files_optional
|
|
||||||
if optional_file.inner_path.startsWith(@inner_path)
|
|
||||||
if @getDirectory(optional_file.inner_path) == @inner_path
|
|
||||||
# Add optional file to list
|
|
||||||
file_name = @getFileName(optional_file.inner_path)
|
|
||||||
if not @items_by_name[file_name]
|
|
||||||
row = {
|
|
||||||
"name": file_name, "type": "file", "optional_empty": true,
|
|
||||||
"size": optional_file.size, "is_dir": false, "inner_path": optional_file.inner_path
|
|
||||||
}
|
|
||||||
@items.push(row)
|
|
||||||
@items_by_name[file_name] = row
|
|
||||||
is_added = true
|
|
||||||
else
|
|
||||||
# Add optional dir to list
|
|
||||||
dir_name = optional_file.inner_path.replace(@inner_path, "").match(/(.*?)\//, "")?[1]
|
|
||||||
if dir_name and not @items_by_name[dir_name]
|
|
||||||
row = {
|
|
||||||
"name": dir_name, "type": "dir", "optional_empty": true,
|
|
||||||
"size": 0, "is_dir": true, "inner_path": optional_file.inner_path
|
|
||||||
}
|
|
||||||
@items.push(row)
|
|
||||||
@items_by_name[dir_name] = row
|
|
||||||
is_added = true
|
|
||||||
|
|
||||||
if is_added
|
|
||||||
@sort()
|
|
||||||
|
|
||||||
getFileType: (file) =>
|
|
||||||
if file.is_dir
|
|
||||||
return "dir"
|
|
||||||
else
|
|
||||||
return "unknown"
|
|
||||||
|
|
||||||
getDirectory: (inner_path) ->
|
|
||||||
if inner_path.indexOf("/") != -1
|
|
||||||
return inner_path.replace(/^(.*\/)(.*?)$/, "$1")
|
|
||||||
else
|
|
||||||
return ""
|
|
||||||
|
|
||||||
getFileName: (inner_path) ->
|
|
||||||
return inner_path.replace(/^(.*\/)(.*?)$/, "$2")
|
|
||||||
|
|
||||||
|
|
||||||
isModified: (inner_path) =>
|
|
||||||
return @files_modified[inner_path] or @dirs_modified[inner_path]
|
|
||||||
|
|
||||||
isAdded: (inner_path) =>
|
|
||||||
return @files_added[inner_path] or @dirs_added[inner_path]
|
|
||||||
|
|
||||||
hasPermissionDelete: (file) =>
|
|
||||||
if file.type in ["dir", "parent"]
|
|
||||||
return false
|
|
||||||
|
|
||||||
if file.inner_path == "content.json"
|
|
||||||
return false
|
|
||||||
|
|
||||||
optional_info = @getOptionalInfo(file.inner_path)
|
|
||||||
if optional_info and optional_info.downloaded_percent > 0
|
|
||||||
return true
|
|
||||||
else
|
|
||||||
return Page.site_info?.settings?.own
|
|
||||||
|
|
||||||
getOptionalInfo: (inner_path) =>
|
|
||||||
return @files_optional[inner_path]
|
|
||||||
|
|
||||||
sort: =>
|
|
||||||
@items.sort (a, b) ->
|
|
||||||
return (b.is_dir - a.is_dir) || a.name.localeCompare(b.name)
|
|
||||||
|
|
||||||
|
|
||||||
window.FileItemList = FileItemList
|
|
|
@ -1,268 +0,0 @@
|
||||||
class FileList extends Class
|
|
||||||
constructor: (@site, @inner_path, @is_owner=false) ->
|
|
||||||
@need_update = true
|
|
||||||
@error = null
|
|
||||||
@url_root = "/list/" + @site + "/"
|
|
||||||
if @inner_path
|
|
||||||
@inner_path += "/"
|
|
||||||
@url_root += @inner_path
|
|
||||||
@log("inited", @url_root)
|
|
||||||
@item_list = new FileItemList(@inner_path)
|
|
||||||
@item_list.items = @item_list.items
|
|
||||||
@menu_create = new Menu()
|
|
||||||
|
|
||||||
@select_action = null
|
|
||||||
@selected = {}
|
|
||||||
@selected_items_num = 0
|
|
||||||
@selected_items_size = 0
|
|
||||||
@selected_optional_empty_num = 0
|
|
||||||
|
|
||||||
isSelectedAll: ->
|
|
||||||
false
|
|
||||||
|
|
||||||
update: =>
|
|
||||||
@item_list.update =>
|
|
||||||
document.body.classList.add("loaded")
|
|
||||||
|
|
||||||
getHref: (inner_path) =>
|
|
||||||
return "/" + @site + "/" + inner_path
|
|
||||||
|
|
||||||
getListHref: (inner_path) =>
|
|
||||||
return "/list/" + @site + "/" + inner_path
|
|
||||||
|
|
||||||
getEditHref: (inner_path, mode=null) =>
|
|
||||||
href = @url_root + "?file=" + inner_path
|
|
||||||
if mode
|
|
||||||
href += "&edit_mode=#{mode}"
|
|
||||||
return href
|
|
||||||
|
|
||||||
checkSelectedItems: =>
|
|
||||||
@selected_items_num = 0
|
|
||||||
@selected_items_size = 0
|
|
||||||
@selected_optional_empty_num = 0
|
|
||||||
for item in @item_list.items
|
|
||||||
if @selected[item.inner_path]
|
|
||||||
@selected_items_num += 1
|
|
||||||
@selected_items_size += item.size
|
|
||||||
optional_info = @item_list.getOptionalInfo(item.inner_path)
|
|
||||||
if optional_info and not optional_info.downloaded_percent > 0
|
|
||||||
@selected_optional_empty_num += 1
|
|
||||||
|
|
||||||
handleMenuCreateClick: =>
|
|
||||||
@menu_create.items = []
|
|
||||||
@menu_create.items.push ["File", @handleNewFileClick]
|
|
||||||
@menu_create.items.push ["Directory", @handleNewDirectoryClick]
|
|
||||||
@menu_create.toggle()
|
|
||||||
return false
|
|
||||||
|
|
||||||
handleNewFileClick: =>
|
|
||||||
Page.cmd "wrapperPrompt", "New file name:", (file_name) =>
|
|
||||||
window.top.location.href = @getEditHref(@inner_path + file_name, "new")
|
|
||||||
return false
|
|
||||||
|
|
||||||
handleNewDirectoryClick: =>
|
|
||||||
Page.cmd "wrapperPrompt", "New directory name:", (res) =>
|
|
||||||
alert("directory name #{res}")
|
|
||||||
return false
|
|
||||||
|
|
||||||
handleSelectClick: (e) =>
|
|
||||||
return false
|
|
||||||
|
|
||||||
handleSelectEnd: (e) =>
|
|
||||||
document.body.removeEventListener('mouseup', @handleSelectEnd)
|
|
||||||
@select_action = null
|
|
||||||
|
|
||||||
handleSelectMousedown: (e) =>
|
|
||||||
inner_path = e.currentTarget.attributes.inner_path.value
|
|
||||||
if @selected[inner_path]
|
|
||||||
delete @selected[inner_path]
|
|
||||||
@select_action = "deselect"
|
|
||||||
else
|
|
||||||
@selected[inner_path] = true
|
|
||||||
@select_action = "select"
|
|
||||||
@checkSelectedItems()
|
|
||||||
document.body.addEventListener('mouseup', @handleSelectEnd)
|
|
||||||
e.stopPropagation()
|
|
||||||
Page.projector.scheduleRender()
|
|
||||||
return false
|
|
||||||
|
|
||||||
handleRowMouseenter: (e) =>
|
|
||||||
if e.buttons and @select_action
|
|
||||||
inner_path = e.target.attributes.inner_path.value
|
|
||||||
if @select_action == "select"
|
|
||||||
@selected[inner_path] = true
|
|
||||||
else
|
|
||||||
delete @selected[inner_path]
|
|
||||||
@checkSelectedItems()
|
|
||||||
Page.projector.scheduleRender()
|
|
||||||
return false
|
|
||||||
|
|
||||||
handleSelectbarCancel: =>
|
|
||||||
@selected = {}
|
|
||||||
@checkSelectedItems()
|
|
||||||
Page.projector.scheduleRender()
|
|
||||||
return false
|
|
||||||
|
|
||||||
handleSelectbarDelete: (e, remove_optional=false) =>
|
|
||||||
for inner_path of @selected
|
|
||||||
optional_info = @item_list.getOptionalInfo(inner_path)
|
|
||||||
delete @selected[inner_path]
|
|
||||||
if optional_info and not remove_optional
|
|
||||||
Page.cmd "optionalFileDelete", inner_path
|
|
||||||
else
|
|
||||||
Page.cmd "fileDelete", inner_path
|
|
||||||
@need_update = true
|
|
||||||
Page.projector.scheduleRender()
|
|
||||||
@checkSelectedItems()
|
|
||||||
return false
|
|
||||||
|
|
||||||
handleSelectbarRemoveOptional: (e) =>
|
|
||||||
return @handleSelectbarDelete(e, true)
|
|
||||||
|
|
||||||
renderSelectbar: =>
|
|
||||||
h("div.selectbar", {classes: {visible: @selected_items_num > 0}}, [
|
|
||||||
"Selected:",
|
|
||||||
h("span.info", [
|
|
||||||
h("span.num", "#{@selected_items_num} files"),
|
|
||||||
h("span.size", "(#{Text.formatSize(@selected_items_size)})"),
|
|
||||||
])
|
|
||||||
h("div.actions", [
|
|
||||||
if @selected_optional_empty_num > 0
|
|
||||||
h("a.action.delete.remove_optional", {href: "#", onclick: @handleSelectbarRemoveOptional}, "Delete and remove optional")
|
|
||||||
else
|
|
||||||
h("a.action.delete", {href: "#", onclick: @handleSelectbarDelete}, "Delete")
|
|
||||||
])
|
|
||||||
h("a.cancel.link", {href: "#", onclick: @handleSelectbarCancel}, "Cancel")
|
|
||||||
])
|
|
||||||
|
|
||||||
renderHead: =>
|
|
||||||
parent_links = []
|
|
||||||
inner_path_parent = ""
|
|
||||||
for parent_dir in @inner_path.split("/")
|
|
||||||
if not parent_dir
|
|
||||||
continue
|
|
||||||
if inner_path_parent
|
|
||||||
inner_path_parent += "/"
|
|
||||||
inner_path_parent += "#{parent_dir}"
|
|
||||||
parent_links.push(
|
|
||||||
[" / ", h("a", {href: @getListHref(inner_path_parent)}, parent_dir)]
|
|
||||||
)
|
|
||||||
return h("div.tr.thead", h("div.td.full",
|
|
||||||
h("a", {href: @getListHref("")}, "root"),
|
|
||||||
parent_links
|
|
||||||
))
|
|
||||||
|
|
||||||
renderItemCheckbox: (item) =>
|
|
||||||
if not @item_list.hasPermissionDelete(item)
|
|
||||||
return [" "]
|
|
||||||
|
|
||||||
return h("a.checkbox-outer", {
|
|
||||||
href: "#Select",
|
|
||||||
onmousedown: @handleSelectMousedown,
|
|
||||||
onclick: @handleSelectClick,
|
|
||||||
inner_path: item.inner_path
|
|
||||||
}, h("span.checkbox"))
|
|
||||||
|
|
||||||
renderItem: (item) =>
|
|
||||||
if item.type == "parent"
|
|
||||||
href = @url_root.replace(/^(.*)\/.{2,255}?$/, "$1/")
|
|
||||||
else if item.type == "dir"
|
|
||||||
href = @url_root + item.name
|
|
||||||
else
|
|
||||||
href = @url_root.replace(/^\/list\//, "/") + item.name
|
|
||||||
|
|
||||||
inner_path = @inner_path + item.name
|
|
||||||
href_edit = @getEditHref(inner_path)
|
|
||||||
is_dir = item.type in ["dir", "parent"]
|
|
||||||
ext = item.name.split(".").pop()
|
|
||||||
|
|
||||||
is_editing = inner_path == Page.file_editor?.inner_path
|
|
||||||
is_editable = not is_dir and item.size < 1024 * 1024 and ext not in window.BINARY_EXTENSIONS
|
|
||||||
is_modified = @item_list.isModified(inner_path)
|
|
||||||
is_added = @item_list.isAdded(inner_path)
|
|
||||||
optional_info = @item_list.getOptionalInfo(inner_path)
|
|
||||||
|
|
||||||
style = ""
|
|
||||||
title = ""
|
|
||||||
|
|
||||||
if optional_info
|
|
||||||
downloaded_percent = optional_info.downloaded_percent
|
|
||||||
if not downloaded_percent
|
|
||||||
downloaded_percent = 0
|
|
||||||
style += "background: linear-gradient(90deg, #fff6dd, #{downloaded_percent}%, white, #{downloaded_percent}%, white);"
|
|
||||||
is_added = false
|
|
||||||
|
|
||||||
if item.ignored
|
|
||||||
is_added = false
|
|
||||||
|
|
||||||
if is_modified then title += " (modified)"
|
|
||||||
if is_added then title += " (new)"
|
|
||||||
if optional_info or item.optional_empty then title += " (optional)"
|
|
||||||
if item.ignored then title += " (ignored from content.json)"
|
|
||||||
|
|
||||||
classes = {
|
|
||||||
"type-#{item.type}": true, editing: is_editing, nobuttons: not is_editable, selected: @selected[inner_path],
|
|
||||||
modified: is_modified, added: is_added, ignored: item.ignored, optional: optional_info, optional_empty: item.optional_empty
|
|
||||||
}
|
|
||||||
|
|
||||||
h("div.tr", {key: item.name, classes: classes, style: style, onmouseenter: @handleRowMouseenter, inner_path: inner_path}, [
|
|
||||||
h("div.td.pre", {title: title},
|
|
||||||
@renderItemCheckbox(item)
|
|
||||||
),
|
|
||||||
h("div.td.name", h("a.link", {href: href}, item.name))
|
|
||||||
h("div.td.buttons", if is_editable then h("a.edit", {href: href_edit}, if Page.site_info.settings.own then "Edit" else "View"))
|
|
||||||
h("div.td.size", if is_dir then "[DIR]" else Text.formatSize(item.size))
|
|
||||||
])
|
|
||||||
|
|
||||||
|
|
||||||
renderItems: =>
|
|
||||||
return [
|
|
||||||
if @item_list.error and not @item_list.items.length and not @item_list.updating then [
|
|
||||||
h("div.tr", {key: "error"}, h("div.td.full.error", @item_list.error))
|
|
||||||
],
|
|
||||||
if @inner_path then @renderItem({"name": "..", type: "parent", size: 0})
|
|
||||||
@item_list.items.map @renderItem
|
|
||||||
]
|
|
||||||
|
|
||||||
renderFoot: =>
|
|
||||||
files = (item for item in @item_list.items when item.type not in ["parent", "dir"])
|
|
||||||
dirs = (item for item in @item_list.items when item.type == "dir")
|
|
||||||
if files.length
|
|
||||||
total_size = (item.size for file in files).reduce (a, b) -> a + b
|
|
||||||
else
|
|
||||||
total_size = 0
|
|
||||||
|
|
||||||
foot_text = "Total: "
|
|
||||||
foot_text += "#{dirs.length} dir, #{files.length} file in #{Text.formatSize(total_size)}"
|
|
||||||
|
|
||||||
return [
|
|
||||||
if dirs.length or files.length or Page.site_info?.settings?.own
|
|
||||||
h("div.tr.foot-info.foot", h("div.td.full", [
|
|
||||||
if @item_list.updating
|
|
||||||
"Updating file list..."
|
|
||||||
else
|
|
||||||
if dirs.length or files.length then foot_text
|
|
||||||
if Page.site_info?.settings?.own
|
|
||||||
h("div.create", [
|
|
||||||
h("a.link", {href: "#Create+new+file", onclick: @handleNewFileClick}, "+ New")
|
|
||||||
@menu_create.render()
|
|
||||||
])
|
|
||||||
]))
|
|
||||||
]
|
|
||||||
|
|
||||||
render: =>
|
|
||||||
if @need_update
|
|
||||||
@update()
|
|
||||||
@need_update = false
|
|
||||||
|
|
||||||
if not @item_list.items
|
|
||||||
return []
|
|
||||||
|
|
||||||
return h("div.files", [
|
|
||||||
@renderSelectbar(),
|
|
||||||
@renderHead(),
|
|
||||||
h("div.tbody", @renderItems()),
|
|
||||||
@renderFoot()
|
|
||||||
])
|
|
||||||
|
|
||||||
window.FileList = FileList
|
|
|
@ -1,79 +0,0 @@
|
||||||
window.h = maquette.h
|
|
||||||
|
|
||||||
class UiFileManager extends ZeroFrame
|
|
||||||
init: ->
|
|
||||||
@url_params = new URLSearchParams(window.location.search)
|
|
||||||
@list_site = @url_params.get("site")
|
|
||||||
@list_address = @url_params.get("address")
|
|
||||||
@list_inner_path = @url_params.get("inner_path")
|
|
||||||
@editor_inner_path = @url_params.get("file")
|
|
||||||
@file_list = new FileList(@list_site, @list_inner_path)
|
|
||||||
|
|
||||||
@site_info = null
|
|
||||||
@server_info = null
|
|
||||||
|
|
||||||
@is_sidebar_closed = false
|
|
||||||
|
|
||||||
if @editor_inner_path
|
|
||||||
@file_editor = new FileEditor(@editor_inner_path)
|
|
||||||
|
|
||||||
window.onbeforeunload = =>
|
|
||||||
if @file_editor?.isModified()
|
|
||||||
return true
|
|
||||||
else
|
|
||||||
return null
|
|
||||||
|
|
||||||
window.onresize = =>
|
|
||||||
@checkBodyWidth()
|
|
||||||
|
|
||||||
@checkBodyWidth()
|
|
||||||
|
|
||||||
@cmd("wrapperSetViewport", "width=device-width, initial-scale=0.8")
|
|
||||||
|
|
||||||
@cmd "serverInfo", {}, (server_info) =>
|
|
||||||
@server_info = server_info
|
|
||||||
@cmd "siteInfo", {}, (site_info) =>
|
|
||||||
@cmd("wrapperSetTitle", "List: /#{@list_inner_path} - #{site_info.content.title} - ZeroNet")
|
|
||||||
@site_info = site_info
|
|
||||||
if @file_editor then @file_editor.on_loaded.then =>
|
|
||||||
@file_editor.cm.setOption("readOnly", not site_info.settings.own)
|
|
||||||
@file_editor.mode = if site_info.settings.own then "Edit" else "View"
|
|
||||||
@projector.scheduleRender()
|
|
||||||
|
|
||||||
checkBodyWidth: =>
|
|
||||||
if not @file_editor
|
|
||||||
return false
|
|
||||||
|
|
||||||
if document.body.offsetWidth < 960 and not @is_sidebar_closed
|
|
||||||
@is_sidebar_closed = true
|
|
||||||
@projector?.scheduleRender()
|
|
||||||
else if document.body.offsetWidth > 960 and @is_sidebar_closed
|
|
||||||
@is_sidebar_closed = false
|
|
||||||
@projector?.scheduleRender()
|
|
||||||
|
|
||||||
onRequest: (cmd, message) =>
|
|
||||||
if cmd == "setSiteInfo"
|
|
||||||
@site_info = message
|
|
||||||
RateLimitCb 1000, (cb_done) =>
|
|
||||||
@file_list.update(cb_done)
|
|
||||||
@projector.scheduleRender()
|
|
||||||
else if cmd == "setServerInfo"
|
|
||||||
@server_info = message
|
|
||||||
@projector.scheduleRender()
|
|
||||||
else
|
|
||||||
@log "Unknown incoming message:", cmd
|
|
||||||
|
|
||||||
createProjector: =>
|
|
||||||
@projector = maquette.createProjector()
|
|
||||||
@projector.replace($("#content"), @render)
|
|
||||||
|
|
||||||
render: =>
|
|
||||||
return h("div.content#content", [
|
|
||||||
h("div.manager", {classes: {editing: @file_editor, sidebar_closed: @is_sidebar_closed}}, [
|
|
||||||
@file_list.render(),
|
|
||||||
if @file_editor then @file_editor.render()
|
|
||||||
])
|
|
||||||
])
|
|
||||||
|
|
||||||
window.Page = new UiFileManager()
|
|
||||||
window.Page.createProjector()
|
|
|
@ -1,138 +0,0 @@
|
||||||
class Animation
|
|
||||||
slideDown: (elem, props) ->
|
|
||||||
if elem.offsetTop > 2000
|
|
||||||
return
|
|
||||||
|
|
||||||
h = elem.offsetHeight
|
|
||||||
cstyle = window.getComputedStyle(elem)
|
|
||||||
margin_top = cstyle.marginTop
|
|
||||||
margin_bottom = cstyle.marginBottom
|
|
||||||
padding_top = cstyle.paddingTop
|
|
||||||
padding_bottom = cstyle.paddingBottom
|
|
||||||
transition = cstyle.transition
|
|
||||||
|
|
||||||
elem.style.boxSizing = "border-box"
|
|
||||||
elem.style.overflow = "hidden"
|
|
||||||
elem.style.transform = "scale(0.6)"
|
|
||||||
elem.style.opacity = "0"
|
|
||||||
elem.style.height = "0px"
|
|
||||||
elem.style.marginTop = "0px"
|
|
||||||
elem.style.marginBottom = "0px"
|
|
||||||
elem.style.paddingTop = "0px"
|
|
||||||
elem.style.paddingBottom = "0px"
|
|
||||||
elem.style.transition = "none"
|
|
||||||
|
|
||||||
setTimeout (->
|
|
||||||
elem.className += " animate-inout"
|
|
||||||
elem.style.height = h+"px"
|
|
||||||
elem.style.transform = "scale(1)"
|
|
||||||
elem.style.opacity = "1"
|
|
||||||
elem.style.marginTop = margin_top
|
|
||||||
elem.style.marginBottom = margin_bottom
|
|
||||||
elem.style.paddingTop = padding_top
|
|
||||||
elem.style.paddingBottom = padding_bottom
|
|
||||||
), 1
|
|
||||||
|
|
||||||
elem.addEventListener "transitionend", ->
|
|
||||||
elem.classList.remove("animate-inout")
|
|
||||||
elem.style.transition = elem.style.transform = elem.style.opacity = elem.style.height = null
|
|
||||||
elem.style.boxSizing = elem.style.marginTop = elem.style.marginBottom = null
|
|
||||||
elem.style.paddingTop = elem.style.paddingBottom = elem.style.overflow = null
|
|
||||||
elem.removeEventListener "transitionend", arguments.callee, false
|
|
||||||
|
|
||||||
|
|
||||||
slideUp: (elem, remove_func, props) ->
|
|
||||||
if elem.offsetTop > 1000
|
|
||||||
return remove_func()
|
|
||||||
|
|
||||||
elem.className += " animate-back"
|
|
||||||
elem.style.boxSizing = "border-box"
|
|
||||||
elem.style.height = elem.offsetHeight+"px"
|
|
||||||
elem.style.overflow = "hidden"
|
|
||||||
elem.style.transform = "scale(1)"
|
|
||||||
elem.style.opacity = "1"
|
|
||||||
elem.style.pointerEvents = "none"
|
|
||||||
setTimeout (->
|
|
||||||
elem.style.height = "0px"
|
|
||||||
elem.style.marginTop = "0px"
|
|
||||||
elem.style.marginBottom = "0px"
|
|
||||||
elem.style.paddingTop = "0px"
|
|
||||||
elem.style.paddingBottom = "0px"
|
|
||||||
elem.style.transform = "scale(0.8)"
|
|
||||||
elem.style.borderTopWidth = "0px"
|
|
||||||
elem.style.borderBottomWidth = "0px"
|
|
||||||
elem.style.opacity = "0"
|
|
||||||
), 1
|
|
||||||
elem.addEventListener "transitionend", (e) ->
|
|
||||||
if e.propertyName == "opacity" or e.elapsedTime >= 0.6
|
|
||||||
elem.removeEventListener "transitionend", arguments.callee, false
|
|
||||||
remove_func()
|
|
||||||
|
|
||||||
|
|
||||||
slideUpInout: (elem, remove_func, props) ->
|
|
||||||
elem.className += " animate-inout"
|
|
||||||
elem.style.boxSizing = "border-box"
|
|
||||||
elem.style.height = elem.offsetHeight+"px"
|
|
||||||
elem.style.overflow = "hidden"
|
|
||||||
elem.style.transform = "scale(1)"
|
|
||||||
elem.style.opacity = "1"
|
|
||||||
elem.style.pointerEvents = "none"
|
|
||||||
setTimeout (->
|
|
||||||
elem.style.height = "0px"
|
|
||||||
elem.style.marginTop = "0px"
|
|
||||||
elem.style.marginBottom = "0px"
|
|
||||||
elem.style.paddingTop = "0px"
|
|
||||||
elem.style.paddingBottom = "0px"
|
|
||||||
elem.style.transform = "scale(0.8)"
|
|
||||||
elem.style.borderTopWidth = "0px"
|
|
||||||
elem.style.borderBottomWidth = "0px"
|
|
||||||
elem.style.opacity = "0"
|
|
||||||
), 1
|
|
||||||
elem.addEventListener "transitionend", (e) ->
|
|
||||||
if e.propertyName == "opacity" or e.elapsedTime >= 0.6
|
|
||||||
elem.removeEventListener "transitionend", arguments.callee, false
|
|
||||||
remove_func()
|
|
||||||
|
|
||||||
|
|
||||||
showRight: (elem, props) ->
|
|
||||||
elem.className += " animate"
|
|
||||||
elem.style.opacity = 0
|
|
||||||
elem.style.transform = "TranslateX(-20px) Scale(1.01)"
|
|
||||||
setTimeout (->
|
|
||||||
elem.style.opacity = 1
|
|
||||||
elem.style.transform = "TranslateX(0px) Scale(1)"
|
|
||||||
), 1
|
|
||||||
elem.addEventListener "transitionend", ->
|
|
||||||
elem.classList.remove("animate")
|
|
||||||
elem.style.transform = elem.style.opacity = null
|
|
||||||
|
|
||||||
|
|
||||||
show: (elem, props) ->
|
|
||||||
delay = arguments[arguments.length-2]?.delay*1000 or 1
|
|
||||||
elem.style.opacity = 0
|
|
||||||
setTimeout (->
|
|
||||||
elem.className += " animate"
|
|
||||||
), 1
|
|
||||||
setTimeout (->
|
|
||||||
elem.style.opacity = 1
|
|
||||||
), delay
|
|
||||||
elem.addEventListener "transitionend", ->
|
|
||||||
elem.classList.remove("animate")
|
|
||||||
elem.style.opacity = null
|
|
||||||
elem.removeEventListener "transitionend", arguments.callee, false
|
|
||||||
|
|
||||||
hide: (elem, remove_func, props) ->
|
|
||||||
delay = arguments[arguments.length-2]?.delay*1000 or 1
|
|
||||||
elem.className += " animate"
|
|
||||||
setTimeout (->
|
|
||||||
elem.style.opacity = 0
|
|
||||||
), delay
|
|
||||||
elem.addEventListener "transitionend", (e) ->
|
|
||||||
if e.propertyName == "opacity"
|
|
||||||
remove_func()
|
|
||||||
|
|
||||||
addVisibleClass: (elem, props) ->
|
|
||||||
setTimeout ->
|
|
||||||
elem.classList.add("visible")
|
|
||||||
|
|
||||||
window.Animation = new Animation()
|
|
|
@ -1,23 +0,0 @@
|
||||||
class Class
|
|
||||||
trace: true
|
|
||||||
|
|
||||||
log: (args...) ->
|
|
||||||
return unless @trace
|
|
||||||
return if typeof console is 'undefined'
|
|
||||||
args.unshift("[#{@.constructor.name}]")
|
|
||||||
console.log(args...)
|
|
||||||
@
|
|
||||||
|
|
||||||
logStart: (name, args...) ->
|
|
||||||
return unless @trace
|
|
||||||
@logtimers or= {}
|
|
||||||
@logtimers[name] = +(new Date)
|
|
||||||
@log "#{name}", args..., "(started)" if args.length > 0
|
|
||||||
@
|
|
||||||
|
|
||||||
logEnd: (name, args...) ->
|
|
||||||
ms = +(new Date)-@logtimers[name]
|
|
||||||
@log "#{name}", args..., "(Done in #{ms}ms)"
|
|
||||||
@
|
|
||||||
|
|
||||||
window.Class = Class
|
|
|
@ -1,3 +0,0 @@
|
||||||
window.$ = (selector) ->
|
|
||||||
if selector.startsWith("#")
|
|
||||||
return document.getElementById(selector.replace("#", ""))
|
|
|
@ -1,26 +0,0 @@
|
||||||
class ItemList
|
|
||||||
constructor: (@item_class, @key) ->
|
|
||||||
@items = []
|
|
||||||
@items_bykey = {}
|
|
||||||
|
|
||||||
sync: (rows, item_class, key) ->
|
|
||||||
@items.splice(0, @items.length) # Empty items
|
|
||||||
for row in rows
|
|
||||||
current_obj = @items_bykey[row[@key]]
|
|
||||||
if current_obj
|
|
||||||
current_obj.row = row
|
|
||||||
@items.push current_obj
|
|
||||||
else
|
|
||||||
item = new @item_class(row, @)
|
|
||||||
@items_bykey[row[@key]] = item
|
|
||||||
@items.push item
|
|
||||||
|
|
||||||
deleteItem: (item) ->
|
|
||||||
index = @items.indexOf(item)
|
|
||||||
if index > -1
|
|
||||||
@items.splice(index, 1)
|
|
||||||
else
|
|
||||||
console.log "Can't delete item", item
|
|
||||||
delete @items_bykey[item.row[@key]]
|
|
||||||
|
|
||||||
window.ItemList = ItemList
|
|
|
@ -1,110 +0,0 @@
|
||||||
class Menu
|
|
||||||
constructor: ->
|
|
||||||
@visible = false
|
|
||||||
@items = []
|
|
||||||
@node = null
|
|
||||||
@height = 0
|
|
||||||
@direction = "bottom"
|
|
||||||
|
|
||||||
show: =>
|
|
||||||
window.visible_menu?.hide()
|
|
||||||
@visible = true
|
|
||||||
window.visible_menu = @
|
|
||||||
@direction = @getDirection()
|
|
||||||
|
|
||||||
hide: =>
|
|
||||||
@visible = false
|
|
||||||
|
|
||||||
toggle: =>
|
|
||||||
if @visible
|
|
||||||
@hide()
|
|
||||||
else
|
|
||||||
@show()
|
|
||||||
Page.projector.scheduleRender()
|
|
||||||
|
|
||||||
|
|
||||||
addItem: (title, cb, selected=false) ->
|
|
||||||
@items.push([title, cb, selected])
|
|
||||||
|
|
||||||
|
|
||||||
storeNode: (node) =>
|
|
||||||
@node = node
|
|
||||||
# Animate visible
|
|
||||||
if @visible
|
|
||||||
node.className = node.className.replace("visible", "")
|
|
||||||
setTimeout (=>
|
|
||||||
node.className += " visible"
|
|
||||||
node.attributes.style.value = @getStyle()
|
|
||||||
), 20
|
|
||||||
node.style.maxHeight = "none"
|
|
||||||
@height = node.offsetHeight
|
|
||||||
node.style.maxHeight = "0px"
|
|
||||||
@direction = @getDirection()
|
|
||||||
|
|
||||||
getDirection: =>
|
|
||||||
if @node and @node.parentNode.getBoundingClientRect().top + @height + 60 > document.body.clientHeight and @node.parentNode.getBoundingClientRect().top - @height > 0
|
|
||||||
return "top"
|
|
||||||
else
|
|
||||||
return "bottom"
|
|
||||||
|
|
||||||
handleClick: (e) =>
|
|
||||||
keep_menu = false
|
|
||||||
for item in @items
|
|
||||||
[title, cb, selected] = item
|
|
||||||
if title == e.currentTarget.textContent or e.currentTarget["data-title"] == title
|
|
||||||
keep_menu = cb?(item)
|
|
||||||
break
|
|
||||||
if keep_menu != true and cb != null
|
|
||||||
@hide()
|
|
||||||
return false
|
|
||||||
|
|
||||||
renderItem: (item) =>
|
|
||||||
[title, cb, selected] = item
|
|
||||||
if typeof(selected) == "function"
|
|
||||||
selected = selected()
|
|
||||||
|
|
||||||
if title == "---"
|
|
||||||
return h("div.menu-item-separator", {key: Time.timestamp()})
|
|
||||||
else
|
|
||||||
if cb == null
|
|
||||||
href = undefined
|
|
||||||
onclick = @handleClick
|
|
||||||
else if typeof(cb) == "string" # Url
|
|
||||||
href = cb
|
|
||||||
onclick = true
|
|
||||||
else # Callback
|
|
||||||
href = "#"+title
|
|
||||||
onclick = @handleClick
|
|
||||||
classes = {
|
|
||||||
"selected": selected,
|
|
||||||
"noaction": (cb == null)
|
|
||||||
}
|
|
||||||
return h("a.menu-item", {href: href, onclick: onclick, "data-title": title, key: title, classes: classes}, title)
|
|
||||||
|
|
||||||
getStyle: =>
|
|
||||||
if @visible
|
|
||||||
max_height = @height
|
|
||||||
else
|
|
||||||
max_height = 0
|
|
||||||
style = "max-height: #{max_height}px"
|
|
||||||
if @direction == "top"
|
|
||||||
style += ";margin-top: #{0 - @height - 50}px"
|
|
||||||
else
|
|
||||||
style += ";margin-top: 0px"
|
|
||||||
return style
|
|
||||||
|
|
||||||
render: (class_name="") =>
|
|
||||||
if @visible or @node
|
|
||||||
h("div.menu#{class_name}", {classes: {"visible": @visible}, style: @getStyle(), afterCreate: @storeNode}, @items.map(@renderItem))
|
|
||||||
|
|
||||||
window.Menu = Menu
|
|
||||||
|
|
||||||
# Hide menu on outside click
|
|
||||||
document.body.addEventListener "mouseup", (e) ->
|
|
||||||
if not window.visible_menu or not window.visible_menu.node
|
|
||||||
return false
|
|
||||||
menu_node = window.visible_menu.node
|
|
||||||
menu_parents = [menu_node, menu_node.parentNode]
|
|
||||||
if e.target.parentNode not in menu_parents and e.target.parentNode.parentNode not in menu_parents
|
|
||||||
window.visible_menu.hide()
|
|
||||||
Page.projector.scheduleRender()
|
|
|
@ -1,74 +0,0 @@
|
||||||
# From: http://dev.bizo.com/2011/12/promises-in-javascriptcoffeescript.html
|
|
||||||
|
|
||||||
class Promise
|
|
||||||
@when: (tasks...) ->
|
|
||||||
num_uncompleted = tasks.length
|
|
||||||
args = new Array(num_uncompleted)
|
|
||||||
promise = new Promise()
|
|
||||||
|
|
||||||
for task, task_id in tasks
|
|
||||||
((task_id) ->
|
|
||||||
task.then(() ->
|
|
||||||
args[task_id] = Array.prototype.slice.call(arguments)
|
|
||||||
num_uncompleted--
|
|
||||||
promise.complete.apply(promise, args) if num_uncompleted == 0
|
|
||||||
)
|
|
||||||
)(task_id)
|
|
||||||
|
|
||||||
return promise
|
|
||||||
|
|
||||||
constructor: ->
|
|
||||||
@resolved = false
|
|
||||||
@end_promise = null
|
|
||||||
@result = null
|
|
||||||
@callbacks = []
|
|
||||||
|
|
||||||
resolve: ->
|
|
||||||
if @resolved
|
|
||||||
return false
|
|
||||||
@resolved = true
|
|
||||||
@data = arguments
|
|
||||||
if not arguments.length
|
|
||||||
@data = [true]
|
|
||||||
@result = @data[0]
|
|
||||||
for callback in @callbacks
|
|
||||||
back = callback.apply callback, @data
|
|
||||||
if @end_promise
|
|
||||||
@end_promise.resolve(back)
|
|
||||||
|
|
||||||
fail: ->
|
|
||||||
@resolve(false)
|
|
||||||
|
|
||||||
then: (callback) ->
|
|
||||||
if @resolved == true
|
|
||||||
callback.apply callback, @data
|
|
||||||
return
|
|
||||||
|
|
||||||
@callbacks.push callback
|
|
||||||
|
|
||||||
@end_promise = new Promise()
|
|
||||||
|
|
||||||
window.Promise = Promise
|
|
||||||
|
|
||||||
###
|
|
||||||
s = Date.now()
|
|
||||||
log = (text) ->
|
|
||||||
console.log Date.now()-s, Array.prototype.slice.call(arguments).join(", ")
|
|
||||||
|
|
||||||
log "Started"
|
|
||||||
|
|
||||||
cmd = (query) ->
|
|
||||||
p = new Promise()
|
|
||||||
setTimeout ( ->
|
|
||||||
p.resolve query+" Result"
|
|
||||||
), 100
|
|
||||||
return p
|
|
||||||
|
|
||||||
back = cmd("SELECT * FROM message").then (res) ->
|
|
||||||
log res
|
|
||||||
return "Return from query"
|
|
||||||
.then (res) ->
|
|
||||||
log "Back then", res
|
|
||||||
|
|
||||||
log "Query started", back
|
|
||||||
###
|
|
|
@ -1,9 +0,0 @@
|
||||||
String::startsWith = (s) -> @[...s.length] is s
|
|
||||||
String::endsWith = (s) -> s is '' or @[-s.length..] is s
|
|
||||||
String::repeat = (count) -> new Array( count + 1 ).join(@)
|
|
||||||
|
|
||||||
window.isEmpty = (obj) ->
|
|
||||||
for key of obj
|
|
||||||
return false
|
|
||||||
return true
|
|
||||||
|
|
|
@ -1,62 +0,0 @@
|
||||||
last_time = {}
|
|
||||||
calling = {}
|
|
||||||
calling_iterval = {}
|
|
||||||
call_after_interval = {}
|
|
||||||
|
|
||||||
# Rate limit function call and don't allow to run in parallel (until callback is called)
|
|
||||||
window.RateLimitCb = (interval, fn, args=[]) ->
|
|
||||||
cb = -> # Callback when function finished
|
|
||||||
left = interval - (Date.now() - last_time[fn]) # Time life until next call
|
|
||||||
# console.log "CB, left", left, "Calling:", calling[fn]
|
|
||||||
if left <= 0 # No time left from rate limit interval
|
|
||||||
delete last_time[fn]
|
|
||||||
if calling[fn] # Function called within interval
|
|
||||||
RateLimitCb(interval, fn, calling[fn])
|
|
||||||
delete calling[fn]
|
|
||||||
else # Time left from rate limit interval
|
|
||||||
setTimeout (->
|
|
||||||
delete last_time[fn]
|
|
||||||
if calling[fn] # Function called within interval
|
|
||||||
RateLimitCb(interval, fn, calling[fn])
|
|
||||||
delete calling[fn]
|
|
||||||
), left
|
|
||||||
if last_time[fn] # Function called within interval
|
|
||||||
calling[fn] = args # Schedule call and update arguments
|
|
||||||
else # Not called within interval, call instantly
|
|
||||||
last_time[fn] = Date.now()
|
|
||||||
fn.apply(this, [cb, args...])
|
|
||||||
|
|
||||||
|
|
||||||
window.RateLimit = (interval, fn) ->
|
|
||||||
if calling_iterval[fn] > interval
|
|
||||||
clearInterval calling[fn]
|
|
||||||
delete calling[fn]
|
|
||||||
|
|
||||||
if not calling[fn]
|
|
||||||
call_after_interval[fn] = false
|
|
||||||
fn() # First call is not delayed
|
|
||||||
calling_iterval[fn] = interval
|
|
||||||
calling[fn] = setTimeout (->
|
|
||||||
if call_after_interval[fn]
|
|
||||||
fn()
|
|
||||||
delete calling[fn]
|
|
||||||
delete call_after_interval[fn]
|
|
||||||
), interval
|
|
||||||
else # Called within iterval, delay the call
|
|
||||||
call_after_interval[fn] = true
|
|
||||||
|
|
||||||
|
|
||||||
###
|
|
||||||
window.s = Date.now()
|
|
||||||
window.load = (done, num) ->
|
|
||||||
console.log "Loading #{num}...", Date.now()-window.s
|
|
||||||
setTimeout (-> done()), 1000
|
|
||||||
|
|
||||||
RateLimit 500, window.load, [0] # Called instantly
|
|
||||||
RateLimit 500, window.load, [1]
|
|
||||||
setTimeout (-> RateLimit 500, window.load, [300]), 300
|
|
||||||
setTimeout (-> RateLimit 500, window.load, [600]), 600 # Called after 1000ms
|
|
||||||
setTimeout (-> RateLimit 500, window.load, [1000]), 1000
|
|
||||||
setTimeout (-> RateLimit 500, window.load, [1200]), 1200 # Called after 2000ms
|
|
||||||
setTimeout (-> RateLimit 500, window.load, [3000]), 3000 # Called after 3000ms
|
|
||||||
###
|
|
|
@ -1,147 +0,0 @@
|
||||||
class Text
|
|
||||||
toColor: (text, saturation=30, lightness=50) ->
|
|
||||||
hash = 0
|
|
||||||
for i in [0..text.length-1]
|
|
||||||
hash += text.charCodeAt(i)*i
|
|
||||||
hash = hash % 1777
|
|
||||||
return "hsl(" + (hash % 360) + ",#{saturation}%,#{lightness}%)";
|
|
||||||
|
|
||||||
|
|
||||||
renderMarked: (text, options={}) ->
|
|
||||||
options["gfm"] = true
|
|
||||||
options["breaks"] = true
|
|
||||||
options["sanitize"] = true
|
|
||||||
options["renderer"] = marked_renderer
|
|
||||||
text = marked(text, options)
|
|
||||||
return @fixHtmlLinks text
|
|
||||||
|
|
||||||
emailLinks: (text) ->
|
|
||||||
return text.replace(/([a-zA-Z0-9]+)@zeroid.bit/g, "<a href='?to=$1' onclick='return Page.message_create.show(\"$1\")'>$1@zeroid.bit</a>")
|
|
||||||
|
|
||||||
# Convert zeronet html links to relaitve
|
|
||||||
fixHtmlLinks: (text) ->
|
|
||||||
if window.is_proxy
|
|
||||||
return text.replace(/href="http:\/\/(127.0.0.1|localhost):43110/g, 'href="http://zero')
|
|
||||||
else
|
|
||||||
return text.replace(/href="http:\/\/(127.0.0.1|localhost):43110/g, 'href="')
|
|
||||||
|
|
||||||
# Convert a single link to relative
|
|
||||||
fixLink: (link) ->
|
|
||||||
if window.is_proxy
|
|
||||||
back = link.replace(/http:\/\/(127.0.0.1|localhost):43110/, 'http://zero')
|
|
||||||
return back.replace(/http:\/\/zero\/([^\/]+\.bit)/, "http://$1") # Domain links
|
|
||||||
else
|
|
||||||
return link.replace(/http:\/\/(127.0.0.1|localhost):43110/, '')
|
|
||||||
|
|
||||||
toUrl: (text) ->
|
|
||||||
return text.replace(/[^A-Za-z0-9]/g, "+").replace(/[+]+/g, "+").replace(/[+]+$/, "")
|
|
||||||
|
|
||||||
getSiteUrl: (address) ->
|
|
||||||
if window.is_proxy
|
|
||||||
if "." in address # Domain
|
|
||||||
return "http://"+address+"/"
|
|
||||||
else
|
|
||||||
return "http://zero/"+address+"/"
|
|
||||||
else
|
|
||||||
return "/"+address+"/"
|
|
||||||
|
|
||||||
|
|
||||||
fixReply: (text) ->
|
|
||||||
return text.replace(/(>.*\n)([^\n>])/gm, "$1\n$2")
|
|
||||||
|
|
||||||
toBitcoinAddress: (text) ->
|
|
||||||
return text.replace(/[^A-Za-z0-9]/g, "")
|
|
||||||
|
|
||||||
|
|
||||||
jsonEncode: (obj) ->
|
|
||||||
return unescape(encodeURIComponent(JSON.stringify(obj)))
|
|
||||||
|
|
||||||
jsonDecode: (obj) ->
|
|
||||||
return JSON.parse(decodeURIComponent(escape(obj)))
|
|
||||||
|
|
||||||
fileEncode: (obj) ->
|
|
||||||
if typeof(obj) == "string"
|
|
||||||
return btoa(unescape(encodeURIComponent(obj)))
|
|
||||||
else
|
|
||||||
return btoa(unescape(encodeURIComponent(JSON.stringify(obj, undefined, '\t'))))
|
|
||||||
|
|
||||||
utf8Encode: (s) ->
|
|
||||||
return unescape(encodeURIComponent(s))
|
|
||||||
|
|
||||||
utf8Decode: (s) ->
|
|
||||||
return decodeURIComponent(escape(s))
|
|
||||||
|
|
||||||
|
|
||||||
distance: (s1, s2) ->
|
|
||||||
s1 = s1.toLocaleLowerCase()
|
|
||||||
s2 = s2.toLocaleLowerCase()
|
|
||||||
next_find_i = 0
|
|
||||||
next_find = s2[0]
|
|
||||||
match = true
|
|
||||||
extra_parts = {}
|
|
||||||
for char in s1
|
|
||||||
if char != next_find
|
|
||||||
if extra_parts[next_find_i]
|
|
||||||
extra_parts[next_find_i] += char
|
|
||||||
else
|
|
||||||
extra_parts[next_find_i] = char
|
|
||||||
else
|
|
||||||
next_find_i++
|
|
||||||
next_find = s2[next_find_i]
|
|
||||||
|
|
||||||
if extra_parts[next_find_i]
|
|
||||||
extra_parts[next_find_i] = "" # Extra chars on the end doesnt matter
|
|
||||||
extra_parts = (val for key, val of extra_parts)
|
|
||||||
if next_find_i >= s2.length
|
|
||||||
return extra_parts.length + extra_parts.join("").length
|
|
||||||
else
|
|
||||||
return false
|
|
||||||
|
|
||||||
|
|
||||||
parseQuery: (query) ->
|
|
||||||
params = {}
|
|
||||||
parts = query.split('&')
|
|
||||||
for part in parts
|
|
||||||
[key, val] = part.split("=")
|
|
||||||
if val
|
|
||||||
params[decodeURIComponent(key)] = decodeURIComponent(val)
|
|
||||||
else
|
|
||||||
params["url"] = decodeURIComponent(key)
|
|
||||||
return params
|
|
||||||
|
|
||||||
encodeQuery: (params) ->
|
|
||||||
back = []
|
|
||||||
if params.url
|
|
||||||
back.push(params.url)
|
|
||||||
for key, val of params
|
|
||||||
if not val or key == "url"
|
|
||||||
continue
|
|
||||||
back.push("#{encodeURIComponent(key)}=#{encodeURIComponent(val)}")
|
|
||||||
return back.join("&")
|
|
||||||
|
|
||||||
highlight: (text, search) ->
|
|
||||||
if not text
|
|
||||||
return [""]
|
|
||||||
parts = text.split(RegExp(search, "i"))
|
|
||||||
back = []
|
|
||||||
for part, i in parts
|
|
||||||
back.push(part)
|
|
||||||
if i < parts.length-1
|
|
||||||
back.push(h("span.highlight", {key: i}, search))
|
|
||||||
return back
|
|
||||||
|
|
||||||
formatSize: (size) ->
|
|
||||||
if isNaN(parseInt(size))
|
|
||||||
return ""
|
|
||||||
size_mb = size/1024/1024
|
|
||||||
if size_mb >= 1000
|
|
||||||
return (size_mb/1024).toFixed(1)+" GB"
|
|
||||||
else if size_mb >= 100
|
|
||||||
return size_mb.toFixed(0)+" MB"
|
|
||||||
else if size/1024 >= 1000
|
|
||||||
return size_mb.toFixed(2)+" MB"
|
|
||||||
else
|
|
||||||
return (parseInt(size)/1024).toFixed(2)+" KB"
|
|
||||||
|
|
||||||
window.is_proxy = (document.location.host == "zero" or window.location.pathname == "/")
|
|
||||||
window.Text = new Text()
|
|
|
@ -1,59 +0,0 @@
|
||||||
class Time
|
|
||||||
since: (timestamp) ->
|
|
||||||
now = +(new Date)/1000
|
|
||||||
if timestamp > 1000000000000 # In ms
|
|
||||||
timestamp = timestamp/1000
|
|
||||||
secs = now - timestamp
|
|
||||||
if secs < 60
|
|
||||||
back = "Just now"
|
|
||||||
else if secs < 60*60
|
|
||||||
minutes = Math.round(secs/60)
|
|
||||||
back = "" + minutes + " minutes ago"
|
|
||||||
else if secs < 60*60*24
|
|
||||||
back = "#{Math.round(secs/60/60)} hours ago"
|
|
||||||
else if secs < 60*60*24*3
|
|
||||||
back = "#{Math.round(secs/60/60/24)} days ago"
|
|
||||||
else
|
|
||||||
back = "on "+@date(timestamp)
|
|
||||||
back = back.replace(/^1 ([a-z]+)s/, "1 $1") # 1 days ago fix
|
|
||||||
return back
|
|
||||||
|
|
||||||
dateIso: (timestamp=null) ->
|
|
||||||
if not timestamp
|
|
||||||
timestamp = window.Time.timestamp()
|
|
||||||
|
|
||||||
if timestamp > 1000000000000 # In ms
|
|
||||||
timestamp = timestamp/1000
|
|
||||||
tzoffset = (new Date()).getTimezoneOffset() * 60
|
|
||||||
return (new Date((timestamp - tzoffset) * 1000)).toISOString().split("T")[0]
|
|
||||||
|
|
||||||
date: (timestamp=null, format="short") ->
|
|
||||||
if not timestamp
|
|
||||||
timestamp = window.Time.timestamp()
|
|
||||||
|
|
||||||
if timestamp > 1000000000000 # In ms
|
|
||||||
timestamp = timestamp/1000
|
|
||||||
parts = (new Date(timestamp * 1000)).toString().split(" ")
|
|
||||||
if format == "short"
|
|
||||||
display = parts.slice(1, 4)
|
|
||||||
else if format == "day"
|
|
||||||
display = parts.slice(1, 3)
|
|
||||||
else if format == "month"
|
|
||||||
display = [parts[1], parts[3]]
|
|
||||||
else if format == "long"
|
|
||||||
display = parts.slice(1, 5)
|
|
||||||
return display.join(" ").replace(/( [0-9]{4})/, ",$1")
|
|
||||||
|
|
||||||
weekDay: (timestamp) ->
|
|
||||||
if timestamp > 1000000000000 # In ms
|
|
||||||
timestamp = timestamp/1000
|
|
||||||
return ["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"][ (new Date(timestamp * 1000)).getDay() ]
|
|
||||||
|
|
||||||
timestamp: (date="") ->
|
|
||||||
if date == "now" or date == ""
|
|
||||||
return parseInt(+(new Date)/1000)
|
|
||||||
else
|
|
||||||
return parseInt(Date.parse(date)/1000)
|
|
||||||
|
|
||||||
|
|
||||||
window.Time = new Time
|
|
|
@ -1,85 +0,0 @@
|
||||||
class ZeroFrame extends Class
|
|
||||||
constructor: (url) ->
|
|
||||||
@url = url
|
|
||||||
@waiting_cb = {}
|
|
||||||
@wrapper_nonce = document.location.href.replace(/.*wrapper_nonce=([A-Za-z0-9]+).*/, "$1")
|
|
||||||
@connect()
|
|
||||||
@next_message_id = 1
|
|
||||||
@history_state = {}
|
|
||||||
@init()
|
|
||||||
|
|
||||||
|
|
||||||
init: ->
|
|
||||||
@
|
|
||||||
|
|
||||||
|
|
||||||
connect: ->
|
|
||||||
@target = window.parent
|
|
||||||
window.addEventListener("message", @onMessage, false)
|
|
||||||
@cmd("innerReady")
|
|
||||||
|
|
||||||
# Save scrollTop
|
|
||||||
window.addEventListener "beforeunload", (e) =>
|
|
||||||
@log "save scrollTop", window.pageYOffset
|
|
||||||
@history_state["scrollTop"] = window.pageYOffset
|
|
||||||
@cmd "wrapperReplaceState", [@history_state, null]
|
|
||||||
|
|
||||||
# Restore scrollTop
|
|
||||||
@cmd "wrapperGetState", [], (state) =>
|
|
||||||
@history_state = state if state?
|
|
||||||
@log "restore scrollTop", state, window.pageYOffset
|
|
||||||
if window.pageYOffset == 0 and state
|
|
||||||
window.scroll(window.pageXOffset, state.scrollTop)
|
|
||||||
|
|
||||||
|
|
||||||
onMessage: (e) =>
|
|
||||||
message = e.data
|
|
||||||
cmd = message.cmd
|
|
||||||
if cmd == "response"
|
|
||||||
if @waiting_cb[message.to]?
|
|
||||||
@waiting_cb[message.to](message.result)
|
|
||||||
else
|
|
||||||
@log "Websocket callback not found:", message
|
|
||||||
else if cmd == "wrapperReady" # Wrapper inited later
|
|
||||||
@cmd("innerReady")
|
|
||||||
else if cmd == "ping"
|
|
||||||
@response message.id, "pong"
|
|
||||||
else if cmd == "wrapperOpenedWebsocket"
|
|
||||||
@onOpenWebsocket()
|
|
||||||
else if cmd == "wrapperClosedWebsocket"
|
|
||||||
@onCloseWebsocket()
|
|
||||||
else
|
|
||||||
@onRequest cmd, message.params
|
|
||||||
|
|
||||||
|
|
||||||
onRequest: (cmd, message) =>
|
|
||||||
@log "Unknown request", message
|
|
||||||
|
|
||||||
|
|
||||||
response: (to, result) ->
|
|
||||||
@send {"cmd": "response", "to": to, "result": result}
|
|
||||||
|
|
||||||
|
|
||||||
cmd: (cmd, params={}, cb=null) ->
|
|
||||||
@send {"cmd": cmd, "params": params}, cb
|
|
||||||
|
|
||||||
|
|
||||||
send: (message, cb=null) ->
|
|
||||||
message.wrapper_nonce = @wrapper_nonce
|
|
||||||
message.id = @next_message_id
|
|
||||||
@next_message_id += 1
|
|
||||||
@target.postMessage(message, "*")
|
|
||||||
if cb
|
|
||||||
@waiting_cb[message.id] = cb
|
|
||||||
|
|
||||||
|
|
||||||
onOpenWebsocket: =>
|
|
||||||
@log "Websocket open"
|
|
||||||
|
|
||||||
|
|
||||||
onCloseWebsocket: =>
|
|
||||||
@log "Websocket close"
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
window.ZeroFrame = ZeroFrame
|
|
|
@ -1,132 +0,0 @@
|
||||||
class PluginList extends Class
|
|
||||||
constructor: (plugins) ->
|
|
||||||
@plugins = plugins
|
|
||||||
|
|
||||||
savePluginStatus: (plugin, is_enabled) =>
|
|
||||||
Page.cmd "pluginConfigSet", [plugin.source, plugin.inner_path, "enabled", is_enabled], (res) =>
|
|
||||||
if res == "ok"
|
|
||||||
Page.updatePlugins()
|
|
||||||
else
|
|
||||||
Page.cmd "wrapperNotification", ["error", res.error]
|
|
||||||
|
|
||||||
Page.projector.scheduleRender()
|
|
||||||
|
|
||||||
handleCheckboxChange: (e) =>
|
|
||||||
node = e.currentTarget
|
|
||||||
plugin = node["data-plugin"]
|
|
||||||
node.classList.toggle("checked")
|
|
||||||
value = node.classList.contains("checked")
|
|
||||||
|
|
||||||
@savePluginStatus(plugin, value)
|
|
||||||
|
|
||||||
handleResetClick: (e) =>
|
|
||||||
node = e.currentTarget
|
|
||||||
plugin = node["data-plugin"]
|
|
||||||
|
|
||||||
@savePluginStatus(plugin, null)
|
|
||||||
|
|
||||||
handleUpdateClick: (e) =>
|
|
||||||
node = e.currentTarget
|
|
||||||
plugin = node["data-plugin"]
|
|
||||||
node.classList.add("loading")
|
|
||||||
|
|
||||||
Page.cmd "pluginUpdate", [plugin.source, plugin.inner_path], (res) =>
|
|
||||||
if res == "ok"
|
|
||||||
Page.cmd "wrapperNotification", ["done", "Plugin #{plugin.name} updated to latest version"]
|
|
||||||
Page.updatePlugins()
|
|
||||||
else
|
|
||||||
Page.cmd "wrapperNotification", ["error", res.error]
|
|
||||||
node.classList.remove("loading")
|
|
||||||
|
|
||||||
return false
|
|
||||||
|
|
||||||
handleDeleteClick: (e) =>
|
|
||||||
node = e.currentTarget
|
|
||||||
plugin = node["data-plugin"]
|
|
||||||
if plugin.loaded
|
|
||||||
Page.cmd "wrapperNotification", ["info", "You can only delete plugin that are not currently active"]
|
|
||||||
return false
|
|
||||||
|
|
||||||
node.classList.add("loading")
|
|
||||||
|
|
||||||
Page.cmd "wrapperConfirm", ["Delete #{plugin.name} plugin?", "Delete"], (res) =>
|
|
||||||
if not res
|
|
||||||
node.classList.remove("loading")
|
|
||||||
return false
|
|
||||||
|
|
||||||
Page.cmd "pluginRemove", [plugin.source, plugin.inner_path], (res) =>
|
|
||||||
if res == "ok"
|
|
||||||
Page.cmd "wrapperNotification", ["done", "Plugin #{plugin.name} deleted"]
|
|
||||||
Page.updatePlugins()
|
|
||||||
else
|
|
||||||
Page.cmd "wrapperNotification", ["error", res.error]
|
|
||||||
node.classList.remove("loading")
|
|
||||||
|
|
||||||
return false
|
|
||||||
|
|
||||||
render: ->
|
|
||||||
h("div.plugins", @plugins.map (plugin) =>
|
|
||||||
if not plugin.info
|
|
||||||
return
|
|
||||||
descr = plugin.info.description
|
|
||||||
plugin.info.default ?= "enabled"
|
|
||||||
if plugin.info.default
|
|
||||||
descr += " (default: #{plugin.info.default})"
|
|
||||||
|
|
||||||
tag_version = ""
|
|
||||||
tag_source = ""
|
|
||||||
tag_delete = ""
|
|
||||||
if plugin.source != "builtin"
|
|
||||||
tag_update = ""
|
|
||||||
if plugin.site_info?.rev
|
|
||||||
if plugin.site_info.rev > plugin.info.rev
|
|
||||||
tag_update = h("a.version-update.button",
|
|
||||||
{href: "#Update+plugin", onclick: @handleUpdateClick, "data-plugin": plugin},
|
|
||||||
"Update to rev#{plugin.site_info.rev}"
|
|
||||||
)
|
|
||||||
|
|
||||||
else
|
|
||||||
tag_update = h("span.version-missing", "(unable to get latest vesion: update site missing)")
|
|
||||||
|
|
||||||
tag_version = h("span.version",[
|
|
||||||
"rev#{plugin.info.rev} ",
|
|
||||||
tag_update,
|
|
||||||
])
|
|
||||||
|
|
||||||
tag_source = h("div.source",[
|
|
||||||
"Source: ",
|
|
||||||
h("a", {"href": "/#{plugin.source}", "target": "_top"}, if plugin.site_title then plugin.site_title else plugin.source),
|
|
||||||
" /" + plugin.inner_path
|
|
||||||
])
|
|
||||||
|
|
||||||
tag_delete = h("a.delete", {"href": "#Delete+plugin", onclick: @handleDeleteClick, "data-plugin": plugin}, "Delete plugin")
|
|
||||||
|
|
||||||
|
|
||||||
enabled_default = plugin.info.default == "enabled"
|
|
||||||
if plugin.enabled != plugin.loaded or plugin.updated
|
|
||||||
marker_title = "Change pending"
|
|
||||||
is_pending = true
|
|
||||||
else
|
|
||||||
marker_title = "Changed from default status (click to reset to #{plugin.info.default})"
|
|
||||||
is_pending = false
|
|
||||||
|
|
||||||
is_changed = plugin.enabled != enabled_default and plugin.owner == "builtin"
|
|
||||||
|
|
||||||
h("div.plugin", {key: plugin.name}, [
|
|
||||||
h("div.title", [
|
|
||||||
h("h3", [plugin.name, tag_version]),
|
|
||||||
h("div.description", [descr, tag_source, tag_delete]),
|
|
||||||
])
|
|
||||||
h("div.value.value-right",
|
|
||||||
h("div.checkbox", {onclick: @handleCheckboxChange, "data-plugin": plugin, classes: {checked: plugin.enabled}}, h("div.checkbox-skin"))
|
|
||||||
h("a.marker", {
|
|
||||||
href: "#Reset", title: marker_title,
|
|
||||||
onclick: @handleResetClick, "data-plugin": plugin,
|
|
||||||
classes: {visible: is_pending or is_changed, pending: is_pending}
|
|
||||||
}, "\u2022")
|
|
||||||
)
|
|
||||||
])
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
window.PluginList = PluginList
|
|
|
@ -1,71 +0,0 @@
|
||||||
window.h = maquette.h
|
|
||||||
|
|
||||||
class UiPluginManager extends ZeroFrame
|
|
||||||
init: ->
|
|
||||||
@plugin_list_builtin = new PluginList()
|
|
||||||
@plugin_list_custom = new PluginList()
|
|
||||||
@plugins_changed = null
|
|
||||||
@need_restart = null
|
|
||||||
@
|
|
||||||
|
|
||||||
onOpenWebsocket: =>
|
|
||||||
@cmd("wrapperSetTitle", "Plugin manager - ZeroNet")
|
|
||||||
@cmd "serverInfo", {}, (server_info) =>
|
|
||||||
@server_info = server_info
|
|
||||||
@updatePlugins()
|
|
||||||
|
|
||||||
updatePlugins: (cb) =>
|
|
||||||
@cmd "pluginList", [], (res) =>
|
|
||||||
@plugins_changed = (item for item in res.plugins when item.enabled != item.loaded or item.updated)
|
|
||||||
|
|
||||||
plugins_builtin = (item for item in res.plugins when item.source == "builtin")
|
|
||||||
@plugin_list_builtin.plugins = plugins_builtin.sort (a, b) ->
|
|
||||||
return a.name.localeCompare(b.name)
|
|
||||||
|
|
||||||
plugins_custom = (item for item in res.plugins when item.source != "builtin")
|
|
||||||
@plugin_list_custom.plugins = plugins_custom.sort (a, b) ->
|
|
||||||
return a.name.localeCompare(b.name)
|
|
||||||
|
|
||||||
@projector.scheduleRender()
|
|
||||||
cb?()
|
|
||||||
|
|
||||||
createProjector: =>
|
|
||||||
@projector = maquette.createProjector()
|
|
||||||
@projector.replace($("#content"), @render)
|
|
||||||
@projector.replace($("#bottom-restart"), @renderBottomRestart)
|
|
||||||
|
|
||||||
render: =>
|
|
||||||
if not @plugin_list_builtin.plugins
|
|
||||||
return h("div.content")
|
|
||||||
|
|
||||||
h("div.content", [
|
|
||||||
h("div.section", [
|
|
||||||
if @plugin_list_custom.plugins?.length
|
|
||||||
[
|
|
||||||
h("h2", "Installed third-party plugins"),
|
|
||||||
@plugin_list_custom.render()
|
|
||||||
]
|
|
||||||
h("h2", "Built-in plugins")
|
|
||||||
@plugin_list_builtin.render()
|
|
||||||
])
|
|
||||||
])
|
|
||||||
|
|
||||||
handleRestartClick: =>
|
|
||||||
@restart_loading = true
|
|
||||||
setTimeout ( =>
|
|
||||||
Page.cmd("serverShutdown", {restart: true})
|
|
||||||
), 300
|
|
||||||
Page.projector.scheduleRender()
|
|
||||||
return false
|
|
||||||
|
|
||||||
renderBottomRestart: =>
|
|
||||||
h("div.bottom.bottom-restart", {classes: {visible: @plugins_changed?.length}}, h("div.bottom-content", [
|
|
||||||
h("div.title", "Some plugins status has been changed"),
|
|
||||||
h("a.button.button-submit.button-restart",
|
|
||||||
{href: "#Restart", classes: {loading: @restart_loading}, onclick: @handleRestartClick},
|
|
||||||
"Restart ZeroNet client"
|
|
||||||
)
|
|
||||||
]))
|
|
||||||
|
|
||||||
window.Page = new UiPluginManager()
|
|
||||||
window.Page.createProjector()
|
|
|
@ -1,23 +0,0 @@
|
||||||
class Class
|
|
||||||
trace: true
|
|
||||||
|
|
||||||
log: (args...) ->
|
|
||||||
return unless @trace
|
|
||||||
return if typeof console is 'undefined'
|
|
||||||
args.unshift("[#{@.constructor.name}]")
|
|
||||||
console.log(args...)
|
|
||||||
@
|
|
||||||
|
|
||||||
logStart: (name, args...) ->
|
|
||||||
return unless @trace
|
|
||||||
@logtimers or= {}
|
|
||||||
@logtimers[name] = +(new Date)
|
|
||||||
@log "#{name}", args..., "(started)" if args.length > 0
|
|
||||||
@
|
|
||||||
|
|
||||||
logEnd: (name, args...) ->
|
|
||||||
ms = +(new Date)-@logtimers[name]
|
|
||||||
@log "#{name}", args..., "(Done in #{ms}ms)"
|
|
||||||
@
|
|
||||||
|
|
||||||
window.Class = Class
|
|
|
@ -1,74 +0,0 @@
|
||||||
# From: http://dev.bizo.com/2011/12/promises-in-javascriptcoffeescript.html
|
|
||||||
|
|
||||||
class Promise
|
|
||||||
@when: (tasks...) ->
|
|
||||||
num_uncompleted = tasks.length
|
|
||||||
args = new Array(num_uncompleted)
|
|
||||||
promise = new Promise()
|
|
||||||
|
|
||||||
for task, task_id in tasks
|
|
||||||
((task_id) ->
|
|
||||||
task.then(() ->
|
|
||||||
args[task_id] = Array.prototype.slice.call(arguments)
|
|
||||||
num_uncompleted--
|
|
||||||
promise.complete.apply(promise, args) if num_uncompleted == 0
|
|
||||||
)
|
|
||||||
)(task_id)
|
|
||||||
|
|
||||||
return promise
|
|
||||||
|
|
||||||
constructor: ->
|
|
||||||
@resolved = false
|
|
||||||
@end_promise = null
|
|
||||||
@result = null
|
|
||||||
@callbacks = []
|
|
||||||
|
|
||||||
resolve: ->
|
|
||||||
if @resolved
|
|
||||||
return false
|
|
||||||
@resolved = true
|
|
||||||
@data = arguments
|
|
||||||
if not arguments.length
|
|
||||||
@data = [true]
|
|
||||||
@result = @data[0]
|
|
||||||
for callback in @callbacks
|
|
||||||
back = callback.apply callback, @data
|
|
||||||
if @end_promise
|
|
||||||
@end_promise.resolve(back)
|
|
||||||
|
|
||||||
fail: ->
|
|
||||||
@resolve(false)
|
|
||||||
|
|
||||||
then: (callback) ->
|
|
||||||
if @resolved == true
|
|
||||||
callback.apply callback, @data
|
|
||||||
return
|
|
||||||
|
|
||||||
@callbacks.push callback
|
|
||||||
|
|
||||||
@end_promise = new Promise()
|
|
||||||
|
|
||||||
window.Promise = Promise
|
|
||||||
|
|
||||||
###
|
|
||||||
s = Date.now()
|
|
||||||
log = (text) ->
|
|
||||||
console.log Date.now()-s, Array.prototype.slice.call(arguments).join(", ")
|
|
||||||
|
|
||||||
log "Started"
|
|
||||||
|
|
||||||
cmd = (query) ->
|
|
||||||
p = new Promise()
|
|
||||||
setTimeout ( ->
|
|
||||||
p.resolve query+" Result"
|
|
||||||
), 100
|
|
||||||
return p
|
|
||||||
|
|
||||||
back = cmd("SELECT * FROM message").then (res) ->
|
|
||||||
log res
|
|
||||||
return "Return from query"
|
|
||||||
.then (res) ->
|
|
||||||
log "Back then", res
|
|
||||||
|
|
||||||
log "Query started", back
|
|
||||||
###
|
|
|
@ -1,8 +0,0 @@
|
||||||
String::startsWith = (s) -> @[...s.length] is s
|
|
||||||
String::endsWith = (s) -> s is '' or @[-s.length..] is s
|
|
||||||
String::repeat = (count) -> new Array( count + 1 ).join(@)
|
|
||||||
|
|
||||||
window.isEmpty = (obj) ->
|
|
||||||
for key of obj
|
|
||||||
return false
|
|
||||||
return true
|
|
|
@ -1,138 +0,0 @@
|
||||||
class Animation
|
|
||||||
slideDown: (elem, props) ->
|
|
||||||
if elem.offsetTop > 2000
|
|
||||||
return
|
|
||||||
|
|
||||||
h = elem.offsetHeight
|
|
||||||
cstyle = window.getComputedStyle(elem)
|
|
||||||
margin_top = cstyle.marginTop
|
|
||||||
margin_bottom = cstyle.marginBottom
|
|
||||||
padding_top = cstyle.paddingTop
|
|
||||||
padding_bottom = cstyle.paddingBottom
|
|
||||||
transition = cstyle.transition
|
|
||||||
|
|
||||||
elem.style.boxSizing = "border-box"
|
|
||||||
elem.style.overflow = "hidden"
|
|
||||||
elem.style.transform = "scale(0.6)"
|
|
||||||
elem.style.opacity = "0"
|
|
||||||
elem.style.height = "0px"
|
|
||||||
elem.style.marginTop = "0px"
|
|
||||||
elem.style.marginBottom = "0px"
|
|
||||||
elem.style.paddingTop = "0px"
|
|
||||||
elem.style.paddingBottom = "0px"
|
|
||||||
elem.style.transition = "none"
|
|
||||||
|
|
||||||
setTimeout (->
|
|
||||||
elem.className += " animate-inout"
|
|
||||||
elem.style.height = h+"px"
|
|
||||||
elem.style.transform = "scale(1)"
|
|
||||||
elem.style.opacity = "1"
|
|
||||||
elem.style.marginTop = margin_top
|
|
||||||
elem.style.marginBottom = margin_bottom
|
|
||||||
elem.style.paddingTop = padding_top
|
|
||||||
elem.style.paddingBottom = padding_bottom
|
|
||||||
), 1
|
|
||||||
|
|
||||||
elem.addEventListener "transitionend", ->
|
|
||||||
elem.classList.remove("animate-inout")
|
|
||||||
elem.style.transition = elem.style.transform = elem.style.opacity = elem.style.height = null
|
|
||||||
elem.style.boxSizing = elem.style.marginTop = elem.style.marginBottom = null
|
|
||||||
elem.style.paddingTop = elem.style.paddingBottom = elem.style.overflow = null
|
|
||||||
elem.removeEventListener "transitionend", arguments.callee, false
|
|
||||||
|
|
||||||
|
|
||||||
slideUp: (elem, remove_func, props) ->
|
|
||||||
if elem.offsetTop > 1000
|
|
||||||
return remove_func()
|
|
||||||
|
|
||||||
elem.className += " animate-back"
|
|
||||||
elem.style.boxSizing = "border-box"
|
|
||||||
elem.style.height = elem.offsetHeight+"px"
|
|
||||||
elem.style.overflow = "hidden"
|
|
||||||
elem.style.transform = "scale(1)"
|
|
||||||
elem.style.opacity = "1"
|
|
||||||
elem.style.pointerEvents = "none"
|
|
||||||
setTimeout (->
|
|
||||||
elem.style.height = "0px"
|
|
||||||
elem.style.marginTop = "0px"
|
|
||||||
elem.style.marginBottom = "0px"
|
|
||||||
elem.style.paddingTop = "0px"
|
|
||||||
elem.style.paddingBottom = "0px"
|
|
||||||
elem.style.transform = "scale(0.8)"
|
|
||||||
elem.style.borderTopWidth = "0px"
|
|
||||||
elem.style.borderBottomWidth = "0px"
|
|
||||||
elem.style.opacity = "0"
|
|
||||||
), 1
|
|
||||||
elem.addEventListener "transitionend", (e) ->
|
|
||||||
if e.propertyName == "opacity" or e.elapsedTime >= 0.6
|
|
||||||
elem.removeEventListener "transitionend", arguments.callee, false
|
|
||||||
remove_func()
|
|
||||||
|
|
||||||
|
|
||||||
slideUpInout: (elem, remove_func, props) ->
|
|
||||||
elem.className += " animate-inout"
|
|
||||||
elem.style.boxSizing = "border-box"
|
|
||||||
elem.style.height = elem.offsetHeight+"px"
|
|
||||||
elem.style.overflow = "hidden"
|
|
||||||
elem.style.transform = "scale(1)"
|
|
||||||
elem.style.opacity = "1"
|
|
||||||
elem.style.pointerEvents = "none"
|
|
||||||
setTimeout (->
|
|
||||||
elem.style.height = "0px"
|
|
||||||
elem.style.marginTop = "0px"
|
|
||||||
elem.style.marginBottom = "0px"
|
|
||||||
elem.style.paddingTop = "0px"
|
|
||||||
elem.style.paddingBottom = "0px"
|
|
||||||
elem.style.transform = "scale(0.8)"
|
|
||||||
elem.style.borderTopWidth = "0px"
|
|
||||||
elem.style.borderBottomWidth = "0px"
|
|
||||||
elem.style.opacity = "0"
|
|
||||||
), 1
|
|
||||||
elem.addEventListener "transitionend", (e) ->
|
|
||||||
if e.propertyName == "opacity" or e.elapsedTime >= 0.6
|
|
||||||
elem.removeEventListener "transitionend", arguments.callee, false
|
|
||||||
remove_func()
|
|
||||||
|
|
||||||
|
|
||||||
showRight: (elem, props) ->
|
|
||||||
elem.className += " animate"
|
|
||||||
elem.style.opacity = 0
|
|
||||||
elem.style.transform = "TranslateX(-20px) Scale(1.01)"
|
|
||||||
setTimeout (->
|
|
||||||
elem.style.opacity = 1
|
|
||||||
elem.style.transform = "TranslateX(0px) Scale(1)"
|
|
||||||
), 1
|
|
||||||
elem.addEventListener "transitionend", ->
|
|
||||||
elem.classList.remove("animate")
|
|
||||||
elem.style.transform = elem.style.opacity = null
|
|
||||||
|
|
||||||
|
|
||||||
show: (elem, props) ->
|
|
||||||
delay = arguments[arguments.length-2]?.delay*1000 or 1
|
|
||||||
elem.style.opacity = 0
|
|
||||||
setTimeout (->
|
|
||||||
elem.className += " animate"
|
|
||||||
), 1
|
|
||||||
setTimeout (->
|
|
||||||
elem.style.opacity = 1
|
|
||||||
), delay
|
|
||||||
elem.addEventListener "transitionend", ->
|
|
||||||
elem.classList.remove("animate")
|
|
||||||
elem.style.opacity = null
|
|
||||||
elem.removeEventListener "transitionend", arguments.callee, false
|
|
||||||
|
|
||||||
hide: (elem, remove_func, props) ->
|
|
||||||
delay = arguments[arguments.length-2]?.delay*1000 or 1
|
|
||||||
elem.className += " animate"
|
|
||||||
setTimeout (->
|
|
||||||
elem.style.opacity = 0
|
|
||||||
), delay
|
|
||||||
elem.addEventListener "transitionend", (e) ->
|
|
||||||
if e.propertyName == "opacity"
|
|
||||||
remove_func()
|
|
||||||
|
|
||||||
addVisibleClass: (elem, props) ->
|
|
||||||
setTimeout ->
|
|
||||||
elem.classList.add("visible")
|
|
||||||
|
|
||||||
window.Animation = new Animation()
|
|
|
@ -1,3 +0,0 @@
|
||||||
window.$ = (selector) ->
|
|
||||||
if selector.startsWith("#")
|
|
||||||
return document.getElementById(selector.replace("#", ""))
|
|
|
@ -1,85 +0,0 @@
|
||||||
class ZeroFrame extends Class
|
|
||||||
constructor: (url) ->
|
|
||||||
@url = url
|
|
||||||
@waiting_cb = {}
|
|
||||||
@wrapper_nonce = document.location.href.replace(/.*wrapper_nonce=([A-Za-z0-9]+).*/, "$1")
|
|
||||||
@connect()
|
|
||||||
@next_message_id = 1
|
|
||||||
@history_state = {}
|
|
||||||
@init()
|
|
||||||
|
|
||||||
|
|
||||||
init: ->
|
|
||||||
@
|
|
||||||
|
|
||||||
|
|
||||||
connect: ->
|
|
||||||
@target = window.parent
|
|
||||||
window.addEventListener("message", @onMessage, false)
|
|
||||||
@cmd("innerReady")
|
|
||||||
|
|
||||||
# Save scrollTop
|
|
||||||
window.addEventListener "beforeunload", (e) =>
|
|
||||||
@log "save scrollTop", window.pageYOffset
|
|
||||||
@history_state["scrollTop"] = window.pageYOffset
|
|
||||||
@cmd "wrapperReplaceState", [@history_state, null]
|
|
||||||
|
|
||||||
# Restore scrollTop
|
|
||||||
@cmd "wrapperGetState", [], (state) =>
|
|
||||||
@history_state = state if state?
|
|
||||||
@log "restore scrollTop", state, window.pageYOffset
|
|
||||||
if window.pageYOffset == 0 and state
|
|
||||||
window.scroll(window.pageXOffset, state.scrollTop)
|
|
||||||
|
|
||||||
|
|
||||||
onMessage: (e) =>
|
|
||||||
message = e.data
|
|
||||||
cmd = message.cmd
|
|
||||||
if cmd == "response"
|
|
||||||
if @waiting_cb[message.to]?
|
|
||||||
@waiting_cb[message.to](message.result)
|
|
||||||
else
|
|
||||||
@log "Websocket callback not found:", message
|
|
||||||
else if cmd == "wrapperReady" # Wrapper inited later
|
|
||||||
@cmd("innerReady")
|
|
||||||
else if cmd == "ping"
|
|
||||||
@response message.id, "pong"
|
|
||||||
else if cmd == "wrapperOpenedWebsocket"
|
|
||||||
@onOpenWebsocket()
|
|
||||||
else if cmd == "wrapperClosedWebsocket"
|
|
||||||
@onCloseWebsocket()
|
|
||||||
else
|
|
||||||
@onRequest cmd, message.params
|
|
||||||
|
|
||||||
|
|
||||||
onRequest: (cmd, message) =>
|
|
||||||
@log "Unknown request", message
|
|
||||||
|
|
||||||
|
|
||||||
response: (to, result) ->
|
|
||||||
@send {"cmd": "response", "to": to, "result": result}
|
|
||||||
|
|
||||||
|
|
||||||
cmd: (cmd, params={}, cb=null) ->
|
|
||||||
@send {"cmd": cmd, "params": params}, cb
|
|
||||||
|
|
||||||
|
|
||||||
send: (message, cb=null) ->
|
|
||||||
message.wrapper_nonce = @wrapper_nonce
|
|
||||||
message.id = @next_message_id
|
|
||||||
@next_message_id += 1
|
|
||||||
@target.postMessage(message, "*")
|
|
||||||
if cb
|
|
||||||
@waiting_cb[message.id] = cb
|
|
||||||
|
|
||||||
|
|
||||||
onOpenWebsocket: =>
|
|
||||||
@log "Websocket open"
|
|
||||||
|
|
||||||
|
|
||||||
onCloseWebsocket: =>
|
|
||||||
@log "Websocket close"
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
window.ZeroFrame = ZeroFrame
|
|
|
@ -124,11 +124,6 @@ class Config(object):
|
||||||
"http://tracker.bt4g.com:2095/announce", # Cloudflare
|
"http://tracker.bt4g.com:2095/announce", # Cloudflare
|
||||||
"zero://2602:ffc5::c5b2:5360:26312" # US/ATL
|
"zero://2602:ffc5::c5b2:5360:26312" # US/ATL
|
||||||
]
|
]
|
||||||
# Platform specific
|
|
||||||
if sys.platform.startswith("win"):
|
|
||||||
coffeescript = "type %s | tools\\coffee\\coffee.cmd"
|
|
||||||
else:
|
|
||||||
coffeescript = None
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
language, enc = locale.getdefaultlocale()
|
language, enc = locale.getdefaultlocale()
|
||||||
|
@ -336,9 +331,6 @@ class Config(object):
|
||||||
|
|
||||||
self.parser.add_argument("--download_optional", choices=["manual", "auto"], default="manual")
|
self.parser.add_argument("--download_optional", choices=["manual", "auto"], default="manual")
|
||||||
|
|
||||||
self.parser.add_argument('--coffeescript_compiler', help='Coffeescript compiler for developing', default=coffeescript,
|
|
||||||
metavar='executable_path')
|
|
||||||
|
|
||||||
self.parser.add_argument('--tor', help='enable: Use only for Tor peers, always: Use Tor for every connection', choices=["disable", "enable", "always"], default='enable')
|
self.parser.add_argument('--tor', help='enable: Use only for Tor peers, always: Use Tor for every connection', choices=["disable", "enable", "always"], default='enable')
|
||||||
self.parser.add_argument('--tor_controller', help='Tor controller address', metavar='ip:port', default='127.0.0.1:9051')
|
self.parser.add_argument('--tor_controller', help='Tor controller address', metavar='ip:port', default='127.0.0.1:9051')
|
||||||
self.parser.add_argument('--tor_proxy', help='Tor proxy address', metavar='ip:port', default='127.0.0.1:9050')
|
self.parser.add_argument('--tor_proxy', help='Tor proxy address', metavar='ip:port', default='127.0.0.1:9050')
|
||||||
|
|
|
@ -29,29 +29,12 @@ def findfiles(path, find_ext):
|
||||||
yield file_path.replace("\\", "/")
|
yield file_path.replace("\\", "/")
|
||||||
|
|
||||||
|
|
||||||
# Try to find coffeescript compiler in path
|
# Generates: all.js: merge *.js, all.css: merge *.css, vendor prefix features
|
||||||
def findCoffeescriptCompiler():
|
|
||||||
coffeescript_compiler = None
|
|
||||||
try:
|
|
||||||
import distutils.spawn
|
|
||||||
coffeescript_compiler = helper.shellquote(distutils.spawn.find_executable("coffee")) + " --no-header -p"
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
if coffeescript_compiler:
|
|
||||||
return coffeescript_compiler
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
# Generates: all.js: merge *.js, compile coffeescript, all.css: merge *.css, vendor prefix features
|
|
||||||
def merge(merged_path):
|
def merge(merged_path):
|
||||||
merged_path = merged_path.replace("\\", "/")
|
merged_path = merged_path.replace("\\", "/")
|
||||||
merge_dir = os.path.dirname(merged_path)
|
merge_dir = os.path.dirname(merged_path)
|
||||||
s = time.time()
|
s = time.time()
|
||||||
ext = merged_path.split(".")[-1]
|
ext = merged_path.split(".")[-1]
|
||||||
if ext == "js": # If merging .js find .coffee too
|
|
||||||
find_ext = ["js", "coffee"]
|
|
||||||
else:
|
|
||||||
find_ext = [ext]
|
find_ext = [ext]
|
||||||
|
|
||||||
# If exist check the other files modification date
|
# If exist check the other files modification date
|
||||||
|
@ -80,43 +63,6 @@ def merge(merged_path):
|
||||||
for file_path in findfiles(merge_dir, find_ext):
|
for file_path in findfiles(merge_dir, find_ext):
|
||||||
file_relative_path = file_path.replace(merge_dir + "/", "")
|
file_relative_path = file_path.replace(merge_dir + "/", "")
|
||||||
parts.append(b"\n/* ---- %s ---- */\n\n" % file_relative_path.encode("utf8"))
|
parts.append(b"\n/* ---- %s ---- */\n\n" % file_relative_path.encode("utf8"))
|
||||||
if file_path.endswith(".coffee"): # Compile coffee script
|
|
||||||
if file_path in changed or file_relative_path not in old_parts: # Only recompile if changed or its not compiled before
|
|
||||||
if config.coffeescript_compiler is None:
|
|
||||||
config.coffeescript_compiler = findCoffeescriptCompiler()
|
|
||||||
if not config.coffeescript_compiler:
|
|
||||||
logging.error("No coffeescript compiler defined, skipping compiling %s" % merged_path)
|
|
||||||
return False # No coffeescript compiler, skip this file
|
|
||||||
|
|
||||||
# Replace / with os separators and escape it
|
|
||||||
file_path_escaped = helper.shellquote(file_path.replace("/", os.path.sep))
|
|
||||||
|
|
||||||
if "%s" in config.coffeescript_compiler: # Replace %s with coffeescript file
|
|
||||||
command = config.coffeescript_compiler.replace("%s", file_path_escaped)
|
|
||||||
else: # Put coffeescript file to end
|
|
||||||
command = config.coffeescript_compiler + " " + file_path_escaped
|
|
||||||
|
|
||||||
# Start compiling
|
|
||||||
s = time.time()
|
|
||||||
compiler = subprocess.Popen(command, shell=True, stderr=subprocess.STDOUT, stdout=subprocess.PIPE)
|
|
||||||
out = compiler.stdout.read()
|
|
||||||
compiler.wait()
|
|
||||||
logging.debug("Running: %s (Done in %.2fs)" % (command, time.time() - s))
|
|
||||||
|
|
||||||
# Check errors
|
|
||||||
if out and out.startswith(b"("): # No error found
|
|
||||||
parts.append(out)
|
|
||||||
else: # Put error message in place of source code
|
|
||||||
error = out
|
|
||||||
logging.error("%s Compile error: %s" % (file_relative_path, error))
|
|
||||||
error_escaped = re.escape(error).replace(b"\n", b"\\n").replace(br"\\n", br"\n")
|
|
||||||
parts.append(
|
|
||||||
b"alert('%s compile error: %s');" %
|
|
||||||
(file_relative_path.encode(), error_escaped)
|
|
||||||
)
|
|
||||||
else: # Not changed use the old_part
|
|
||||||
parts.append(old_parts[file_relative_path])
|
|
||||||
else: # Add to parts
|
|
||||||
parts.append(open(file_path, "rb").read())
|
parts.append(open(file_path, "rb").read())
|
||||||
|
|
||||||
merged = b"\n".join(parts)
|
merged = b"\n".join(parts)
|
||||||
|
@ -131,5 +77,4 @@ def merge(merged_path):
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
logging.getLogger().setLevel(logging.DEBUG)
|
logging.getLogger().setLevel(logging.DEBUG)
|
||||||
os.chdir("..")
|
os.chdir("..")
|
||||||
config.coffeescript_compiler = r'type "%s" | tools\coffee-node\bin\node.exe tools\coffee-node\bin\coffee --no-header -s -p'
|
|
||||||
merge("data/12Hw8rTgzrNo4DSh2AkqwPRqDyTticwJyH/js/all.js")
|
merge("data/12Hw8rTgzrNo4DSh2AkqwPRqDyTticwJyH/js/all.js")
|
||||||
|
|
|
@ -1,32 +0,0 @@
|
||||||
class Fixbutton
|
|
||||||
constructor: ->
|
|
||||||
@dragging = false
|
|
||||||
$(".fixbutton-bg").on "mouseover", ->
|
|
||||||
$(".fixbutton-bg").stop().animate({"scale": 0.7}, 800, "easeOutElastic")
|
|
||||||
$(".fixbutton-burger").stop().animate({"opacity": 1.5, "left": 0}, 800, "easeOutElastic")
|
|
||||||
$(".fixbutton-text").stop().animate({"opacity": 0, "left": 20}, 300, "easeOutCubic")
|
|
||||||
|
|
||||||
$(".fixbutton-bg").on "mouseout", ->
|
|
||||||
if $(".fixbutton").hasClass("dragging")
|
|
||||||
return true
|
|
||||||
$(".fixbutton-bg").stop().animate({"scale": 0.6}, 300, "easeOutCubic")
|
|
||||||
$(".fixbutton-burger").stop().animate({"opacity": 0, "left": -20}, 300, "easeOutCubic")
|
|
||||||
$(".fixbutton-text").stop().animate({"opacity": 0.9, "left": 0}, 300, "easeOutBack")
|
|
||||||
|
|
||||||
|
|
||||||
###$(".fixbutton-bg").on "click", ->
|
|
||||||
return false
|
|
||||||
###
|
|
||||||
|
|
||||||
$(".fixbutton-bg").on "mousedown", ->
|
|
||||||
# $(".fixbutton-burger").stop().animate({"scale": 0.7, "left": 0}, 300, "easeOutCubic")
|
|
||||||
#$("#inner-iframe").toggleClass("back")
|
|
||||||
#$(".wrapper-iframe").stop().animate({"scale": 0.9}, 600, "easeOutCubic")
|
|
||||||
#$("body").addClass("back")
|
|
||||||
|
|
||||||
$(".fixbutton-bg").on "mouseup", ->
|
|
||||||
# $(".fixbutton-burger").stop().animate({"scale": 1, "left": 0}, 600, "easeOutElastic")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
window.Fixbutton = Fixbutton
|
|
|
@ -1,57 +0,0 @@
|
||||||
class Infopanel
|
|
||||||
constructor: (@elem) ->
|
|
||||||
@visible = false
|
|
||||||
|
|
||||||
show: (closed=false) =>
|
|
||||||
@elem.parent().addClass("visible")
|
|
||||||
if closed
|
|
||||||
@close()
|
|
||||||
else
|
|
||||||
@open()
|
|
||||||
|
|
||||||
unfold: =>
|
|
||||||
@elem.toggleClass("unfolded")
|
|
||||||
return false
|
|
||||||
|
|
||||||
updateEvents: =>
|
|
||||||
@elem.off("click")
|
|
||||||
@elem.find(".close").off("click")
|
|
||||||
@elem.find(".line").off("click")
|
|
||||||
|
|
||||||
@elem.find(".line").on("click", @unfold)
|
|
||||||
|
|
||||||
if @elem.hasClass("closed")
|
|
||||||
@elem.on "click", =>
|
|
||||||
@onOpened()
|
|
||||||
@open()
|
|
||||||
else
|
|
||||||
@elem.find(".close").on "click", =>
|
|
||||||
@onClosed()
|
|
||||||
@close()
|
|
||||||
|
|
||||||
hide: =>
|
|
||||||
@elem.parent().removeClass("visible")
|
|
||||||
|
|
||||||
close: =>
|
|
||||||
@elem.addClass("closed")
|
|
||||||
@updateEvents()
|
|
||||||
return false
|
|
||||||
|
|
||||||
open: =>
|
|
||||||
@elem.removeClass("closed")
|
|
||||||
@updateEvents()
|
|
||||||
return false
|
|
||||||
|
|
||||||
setTitle: (line1, line2) =>
|
|
||||||
@elem.find(".line-1").text(line1)
|
|
||||||
@elem.find(".line-2").text(line2)
|
|
||||||
|
|
||||||
setClosedNum: (num) =>
|
|
||||||
@elem.find(".closed-num").text(num)
|
|
||||||
|
|
||||||
setAction: (title, func) =>
|
|
||||||
@elem.find(".button").text(title).off("click").on("click", func)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
window.Infopanel = Infopanel
|
|
|
@ -1,91 +0,0 @@
|
||||||
class Loading
|
|
||||||
constructor: (@wrapper) ->
|
|
||||||
if window.show_loadingscreen then @showScreen()
|
|
||||||
@timer_hide = null
|
|
||||||
@timer_set = null
|
|
||||||
|
|
||||||
setProgress: (percent) ->
|
|
||||||
if @timer_hide
|
|
||||||
clearInterval @timer_hide
|
|
||||||
@timer_set = RateLimit 500, ->
|
|
||||||
$(".progressbar").css("transform": "scaleX(#{parseInt(percent*100)/100})").css("opacity", "1").css("display", "block")
|
|
||||||
|
|
||||||
hideProgress: ->
|
|
||||||
@log "hideProgress"
|
|
||||||
if @timer_set
|
|
||||||
clearInterval @timer_set
|
|
||||||
@timer_hide = setTimeout ( =>
|
|
||||||
$(".progressbar").css("transform": "scaleX(1)").css("opacity", "0").hideLater(1000)
|
|
||||||
), 300
|
|
||||||
|
|
||||||
|
|
||||||
showScreen: ->
|
|
||||||
$(".loadingscreen").css("display", "block").addClassLater("ready")
|
|
||||||
@screen_visible = true
|
|
||||||
@printLine " Connecting..."
|
|
||||||
|
|
||||||
|
|
||||||
showTooLarge: (site_info) ->
|
|
||||||
@log "Displaying large site confirmation"
|
|
||||||
if $(".console .button-setlimit").length == 0 # Not displaying it yet
|
|
||||||
line = @printLine("Site size: <b>#{parseInt(site_info.settings.size/1024/1024)}MB</b> is larger than default allowed #{parseInt(site_info.size_limit)}MB", "warning")
|
|
||||||
button = $("<a href='#Set+limit' class='button button-setlimit'>" + "Open site and set size limit to #{site_info.next_size_limit}MB" + "</a>")
|
|
||||||
button.on "click", =>
|
|
||||||
button.addClass("loading")
|
|
||||||
return @wrapper.setSizeLimit(site_info.next_size_limit)
|
|
||||||
line.after(button)
|
|
||||||
setTimeout (=>
|
|
||||||
@printLine('Ready.')
|
|
||||||
), 100
|
|
||||||
|
|
||||||
showTrackerTorBridge: (server_info) ->
|
|
||||||
if $(".console .button-settrackerbridge").length == 0 and not server_info.tor_use_meek_bridges
|
|
||||||
line = @printLine("Tracker connection error detected.", "error")
|
|
||||||
button = $("<a href='#Enable+Tor+bridges' class='button button-settrackerbridge'>" + "Use Tor meek bridges for tracker connections" + "</a>")
|
|
||||||
button.on "click", =>
|
|
||||||
button.addClass("loading")
|
|
||||||
@wrapper.ws.cmd "configSet", ["tor_use_bridges", ""]
|
|
||||||
@wrapper.ws.cmd "configSet", ["trackers_proxy", "tor"]
|
|
||||||
@wrapper.ws.cmd "siteUpdate", {address: @wrapper.site_info.address, announce: true}
|
|
||||||
@wrapper.reloadIframe()
|
|
||||||
return false
|
|
||||||
line.after(button)
|
|
||||||
if not server_info.tor_has_meek_bridges
|
|
||||||
button.addClass("disabled")
|
|
||||||
@printLine("No meek bridge support in your client, please <a href='https://github.com/HelloZeroNet/ZeroNet#how-to-join'>download the latest bundle</a>.", "warning")
|
|
||||||
|
|
||||||
# We dont need loadingscreen anymore
|
|
||||||
hideScreen: ->
|
|
||||||
@log "hideScreen"
|
|
||||||
if not $(".loadingscreen").hasClass("done") # Only if its not animating already
|
|
||||||
if @screen_visible # Hide with animate
|
|
||||||
$(".loadingscreen").addClass("done").removeLater(2000)
|
|
||||||
else # Not visible, just remove
|
|
||||||
$(".loadingscreen").remove()
|
|
||||||
@screen_visible = false
|
|
||||||
|
|
||||||
|
|
||||||
# Append text to last line of loadingscreen
|
|
||||||
print: (text, type="normal") ->
|
|
||||||
if not @screen_visible then return false
|
|
||||||
$(".loadingscreen .console .cursor").remove() # Remove previous cursor
|
|
||||||
last_line = $(".loadingscreen .console .console-line:last-child")
|
|
||||||
if type == "error" then text = "<span class='console-error'>#{text}</span>"
|
|
||||||
last_line.html(last_line.html()+text)
|
|
||||||
|
|
||||||
|
|
||||||
# Add line to loading screen
|
|
||||||
printLine: (text, type="normal") ->
|
|
||||||
if not @screen_visible then return false
|
|
||||||
$(".loadingscreen .console .cursor").remove() # Remove previous cursor
|
|
||||||
if type == "error" then text = "<span class='console-error'>#{text}</span>" else text = text+"<span class='cursor'> </span>"
|
|
||||||
|
|
||||||
line = $("<div class='console-line'>#{text}</div>").appendTo(".loadingscreen .console")
|
|
||||||
if type == "warning" then line.addClass("console-warning")
|
|
||||||
return line
|
|
||||||
|
|
||||||
log: (args...) ->
|
|
||||||
console.log "[Loading]", args...
|
|
||||||
|
|
||||||
|
|
||||||
window.Loading = Loading
|
|
|
@ -1,89 +0,0 @@
|
||||||
class Notifications
|
|
||||||
constructor: (@elem) ->
|
|
||||||
@
|
|
||||||
|
|
||||||
test: ->
|
|
||||||
setTimeout (=>
|
|
||||||
@add("connection", "error", "Connection lost to <b>UiServer</b> on <b>localhost</b>!")
|
|
||||||
@add("message-Anyone", "info", "New from <b>Anyone</b>.")
|
|
||||||
), 1000
|
|
||||||
setTimeout (=>
|
|
||||||
@add("connection", "done", "<b>UiServer</b> connection recovered.", 5000)
|
|
||||||
), 3000
|
|
||||||
|
|
||||||
|
|
||||||
add: (id, type, body, timeout=0) ->
|
|
||||||
id = id.replace /[^A-Za-z0-9-]/g, ""
|
|
||||||
# Close notifications with same id
|
|
||||||
for elem in $(".notification-#{id}")
|
|
||||||
@close $(elem)
|
|
||||||
|
|
||||||
# Create element
|
|
||||||
elem = $(".notification.template", @elem).clone().removeClass("template")
|
|
||||||
elem.addClass("notification-#{type}").addClass("notification-#{id}")
|
|
||||||
if type == "progress"
|
|
||||||
elem.addClass("notification-done")
|
|
||||||
|
|
||||||
# Update text
|
|
||||||
if type == "error"
|
|
||||||
$(".notification-icon", elem).html("!")
|
|
||||||
else if type == "done"
|
|
||||||
$(".notification-icon", elem).html("<div class='icon-success'></div>")
|
|
||||||
else if type == "progress"
|
|
||||||
$(".notification-icon", elem).html("<div class='icon-success'></div>")
|
|
||||||
else if type == "ask"
|
|
||||||
$(".notification-icon", elem).html("?")
|
|
||||||
else
|
|
||||||
$(".notification-icon", elem).html("i")
|
|
||||||
|
|
||||||
if typeof(body) == "string"
|
|
||||||
$(".body", elem).html("<div class='message'><span class='multiline'>"+body+"</span></div>")
|
|
||||||
else
|
|
||||||
$(".body", elem).html("").append(body)
|
|
||||||
|
|
||||||
elem.appendTo(@elem)
|
|
||||||
|
|
||||||
# Timeout
|
|
||||||
if timeout
|
|
||||||
$(".close", elem).remove() # No need of close button
|
|
||||||
setTimeout (=>
|
|
||||||
@close elem
|
|
||||||
), timeout
|
|
||||||
|
|
||||||
# Animate
|
|
||||||
width = Math.min(elem.outerWidth() + 50, 580)
|
|
||||||
if not timeout then width += 20 # Add space for close button
|
|
||||||
if elem.outerHeight() > 55 then elem.addClass("long")
|
|
||||||
elem.css({"width": "50px", "transform": "scale(0.01)"})
|
|
||||||
elem.animate({"scale": 1}, 800, "easeOutElastic")
|
|
||||||
elem.animate({"width": width}, 700, "easeInOutCubic")
|
|
||||||
$(".body", elem).css("width": (width - 50))
|
|
||||||
$(".body", elem).cssLater("box-shadow", "0px 0px 5px rgba(0,0,0,0.1)", 1000)
|
|
||||||
|
|
||||||
# Close button or Confirm button
|
|
||||||
$(".close, .button", elem).on "click", =>
|
|
||||||
@close elem
|
|
||||||
return false
|
|
||||||
|
|
||||||
# Select list
|
|
||||||
$(".select", elem).on "click", =>
|
|
||||||
@close elem
|
|
||||||
|
|
||||||
# Input enter
|
|
||||||
$("input", elem).on "keyup", (e) =>
|
|
||||||
if e.keyCode == 13
|
|
||||||
@close elem
|
|
||||||
|
|
||||||
return elem
|
|
||||||
|
|
||||||
|
|
||||||
close: (elem) ->
|
|
||||||
elem.stop().animate {"width": 0, "opacity": 0}, 700, "easeInOutCubic"
|
|
||||||
elem.slideUp 300, (-> elem.remove())
|
|
||||||
|
|
||||||
|
|
||||||
log: (args...) ->
|
|
||||||
console.log "[Notifications]", args...
|
|
||||||
|
|
||||||
|
|
||||||
window.Notifications = Notifications
|
|
|
@ -1,714 +0,0 @@
|
||||||
class Wrapper
|
|
||||||
constructor: (ws_url) ->
|
|
||||||
@log "Created!"
|
|
||||||
|
|
||||||
@loading = new Loading(@)
|
|
||||||
@notifications = new Notifications($(".notifications"))
|
|
||||||
@infopanel = new Infopanel($(".infopanel"))
|
|
||||||
@infopanel.onClosed = =>
|
|
||||||
@ws.cmd("siteSetSettingsValue", ["modified_files_notification", false])
|
|
||||||
@infopanel.onOpened = =>
|
|
||||||
@ws.cmd("siteSetSettingsValue", ["modified_files_notification", true])
|
|
||||||
@fixbutton = new Fixbutton()
|
|
||||||
|
|
||||||
window.addEventListener("message", @onMessageInner, false)
|
|
||||||
@inner = document.getElementById("inner-iframe").contentWindow
|
|
||||||
@ws = new ZeroWebsocket(ws_url)
|
|
||||||
@ws.next_message_id = 1000000 # Avoid messageid collision :)
|
|
||||||
@ws.onOpen = @onOpenWebsocket
|
|
||||||
@ws.onClose = @onCloseWebsocket
|
|
||||||
@ws.onMessage = @onMessageWebsocket
|
|
||||||
@ws.connect()
|
|
||||||
@ws_error = null # Ws error message
|
|
||||||
|
|
||||||
@next_cmd_message_id = -1
|
|
||||||
|
|
||||||
@site_info = null # Hold latest site info
|
|
||||||
@server_info = null # Hold latest server info
|
|
||||||
@event_site_info = $.Deferred() # Event when site_info received
|
|
||||||
@inner_loaded = false # If iframe loaded or not
|
|
||||||
@inner_ready = false # Inner frame ready to receive messages
|
|
||||||
@wrapperWsInited = false # Wrapper notified on websocket open
|
|
||||||
@site_error = null # Latest failed file download
|
|
||||||
@address = null
|
|
||||||
@opener_tested = false
|
|
||||||
@announcer_line = null
|
|
||||||
@web_notifications = {}
|
|
||||||
@is_title_changed = false
|
|
||||||
|
|
||||||
@allowed_event_constructors = [window.MouseEvent, window.KeyboardEvent, window.PointerEvent] # Allowed event constructors
|
|
||||||
|
|
||||||
window.onload = @onPageLoad # On iframe loaded
|
|
||||||
window.onhashchange = (e) => # On hash change
|
|
||||||
@log "Hashchange", window.location.hash
|
|
||||||
if window.location.hash
|
|
||||||
src = $("#inner-iframe").attr("src").replace(/#.*/, "")+window.location.hash
|
|
||||||
$("#inner-iframe").attr("src", src)
|
|
||||||
|
|
||||||
window.onpopstate = (e) =>
|
|
||||||
@sendInner {"cmd": "wrapperPopState", "params": {"href": document.location.href, "state": e.state}}
|
|
||||||
|
|
||||||
$("#inner-iframe").focus()
|
|
||||||
|
|
||||||
|
|
||||||
verifyEvent: (allowed_target, e) =>
|
|
||||||
if not e.originalEvent.isTrusted
|
|
||||||
throw "Event not trusted"
|
|
||||||
|
|
||||||
if e.originalEvent.constructor not in @allowed_event_constructors
|
|
||||||
throw "Invalid event constructor: #{e.constructor} not in #{JSON.stringify(@allowed_event_constructors)}"
|
|
||||||
|
|
||||||
if e.originalEvent.currentTarget != allowed_target[0]
|
|
||||||
throw "Invalid event target: #{e.originalEvent.currentTarget} != #{allowed_target[0]}"
|
|
||||||
|
|
||||||
# Incoming message from UiServer websocket
|
|
||||||
onMessageWebsocket: (e) =>
|
|
||||||
message = JSON.parse(e.data)
|
|
||||||
@handleMessageWebsocket(message)
|
|
||||||
|
|
||||||
handleMessageWebsocket: (message) =>
|
|
||||||
cmd = message.cmd
|
|
||||||
if cmd == "response"
|
|
||||||
if @ws.waiting_cb[message.to]? # We are waiting for response
|
|
||||||
@ws.waiting_cb[message.to](message.result)
|
|
||||||
else
|
|
||||||
@sendInner message # Pass message to inner frame
|
|
||||||
else if cmd == "notification" # Display notification
|
|
||||||
type = message.params[0]
|
|
||||||
id = "notification-ws-#{message.id}"
|
|
||||||
if "-" in message.params[0] # - in first param: message id defined
|
|
||||||
[id, type] = message.params[0].split("-")
|
|
||||||
@notifications.add(id, type, message.params[1], message.params[2])
|
|
||||||
else if cmd == "progress" # Display notification
|
|
||||||
@actionProgress(message)
|
|
||||||
else if cmd == "prompt" # Prompt input
|
|
||||||
@displayPrompt message.params[0], message.params[1], message.params[2], message.params[3], (res) =>
|
|
||||||
@ws.response message.id, res
|
|
||||||
else if cmd == "confirm" # Confirm action
|
|
||||||
@displayConfirm message.params[0], message.params[1], (res) =>
|
|
||||||
@ws.response message.id, res
|
|
||||||
else if cmd == "setSiteInfo"
|
|
||||||
@sendInner message # Pass to inner frame
|
|
||||||
if message.params.address == @address # Current page
|
|
||||||
@setSiteInfo message.params
|
|
||||||
@updateProgress message.params
|
|
||||||
else if cmd == "setAnnouncerInfo"
|
|
||||||
@sendInner message # Pass to inner frame
|
|
||||||
if message.params.address == @address # Current page
|
|
||||||
@setAnnouncerInfo message.params
|
|
||||||
@updateProgress message.params
|
|
||||||
else if cmd == "error"
|
|
||||||
@notifications.add("notification-#{message.id}", "error", message.params, 0)
|
|
||||||
else if cmd == "updating" # Close connection
|
|
||||||
@log "Updating: Closing websocket"
|
|
||||||
@ws.ws.close()
|
|
||||||
@ws.onCloseWebsocket(null, 4000)
|
|
||||||
else if cmd == "redirect"
|
|
||||||
window.top.location = message.params
|
|
||||||
else if cmd == "injectHtml"
|
|
||||||
$("body").append(message.params)
|
|
||||||
else if cmd == "injectScript"
|
|
||||||
script_tag = $("<script>")
|
|
||||||
script_tag.attr("nonce", @script_nonce)
|
|
||||||
script_tag.html(message.params)
|
|
||||||
document.head.appendChild(script_tag[0])
|
|
||||||
else
|
|
||||||
@sendInner message # Pass message to inner frame
|
|
||||||
|
|
||||||
# Incoming message from inner frame
|
|
||||||
onMessageInner: (e) =>
|
|
||||||
# No nonce security enabled, test if window opener present
|
|
||||||
if not window.postmessage_nonce_security and @opener_tested == false
|
|
||||||
if window.opener and window.opener != window
|
|
||||||
@log "Opener present", window.opener
|
|
||||||
@displayOpenerDialog()
|
|
||||||
return false
|
|
||||||
else
|
|
||||||
@opener_tested = true
|
|
||||||
|
|
||||||
message = e.data
|
|
||||||
# Invalid message (probably not for us)
|
|
||||||
if not message.cmd
|
|
||||||
@log "Invalid message:", message
|
|
||||||
return false
|
|
||||||
|
|
||||||
# Test nonce security to avoid third-party messages
|
|
||||||
if window.postmessage_nonce_security and message.wrapper_nonce != window.wrapper_nonce
|
|
||||||
@log "Message nonce error:", message.wrapper_nonce, '!=', window.wrapper_nonce
|
|
||||||
return
|
|
||||||
|
|
||||||
@handleMessage message
|
|
||||||
|
|
||||||
cmd: (cmd, params={}, cb=null) =>
|
|
||||||
message = {}
|
|
||||||
message.cmd = cmd
|
|
||||||
message.params = params
|
|
||||||
message.id = @next_cmd_message_id
|
|
||||||
if cb
|
|
||||||
@ws.waiting_cb[message.id] = cb
|
|
||||||
@next_cmd_message_id -= 1
|
|
||||||
|
|
||||||
@handleMessage(message)
|
|
||||||
|
|
||||||
handleMessage: (message) =>
|
|
||||||
cmd = message.cmd
|
|
||||||
if cmd == "innerReady"
|
|
||||||
@inner_ready = true
|
|
||||||
if @ws.ws.readyState == 1 and not @wrapperWsInited # If ws already opened
|
|
||||||
@sendInner {"cmd": "wrapperOpenedWebsocket"}
|
|
||||||
@wrapperWsInited = true
|
|
||||||
else if cmd == "innerLoaded" or cmd == "wrapperInnerLoaded"
|
|
||||||
if window.location.hash
|
|
||||||
$("#inner-iframe")[0].src += window.location.hash # Hash tag
|
|
||||||
@log "Added hash to location", $("#inner-iframe")[0].src
|
|
||||||
else if cmd == "wrapperNotification" # Display notification
|
|
||||||
@actionNotification(message)
|
|
||||||
else if cmd == "wrapperConfirm" # Display confirm message
|
|
||||||
@actionConfirm(message)
|
|
||||||
else if cmd == "wrapperPrompt" # Prompt input
|
|
||||||
@actionPrompt(message)
|
|
||||||
else if cmd == "wrapperProgress" # Progress bar
|
|
||||||
@actionProgress(message)
|
|
||||||
else if cmd == "wrapperSetViewport" # Set the viewport
|
|
||||||
@actionSetViewport(message)
|
|
||||||
else if cmd == "wrapperSetTitle"
|
|
||||||
@log "wrapperSetTitle", message.params
|
|
||||||
$("head title").text(message.params)
|
|
||||||
@is_title_changed = true
|
|
||||||
else if cmd == "wrapperReload" # Reload current page
|
|
||||||
@actionReload(message)
|
|
||||||
else if cmd == "wrapperGetLocalStorage"
|
|
||||||
@actionGetLocalStorage(message)
|
|
||||||
else if cmd == "wrapperSetLocalStorage"
|
|
||||||
@actionSetLocalStorage(message)
|
|
||||||
else if cmd == "wrapperPushState"
|
|
||||||
query = @toRelativeQuery(message.params[2])
|
|
||||||
window.history.pushState(message.params[0], message.params[1], query)
|
|
||||||
else if cmd == "wrapperReplaceState"
|
|
||||||
query = @toRelativeQuery(message.params[2])
|
|
||||||
window.history.replaceState(message.params[0], message.params[1], query)
|
|
||||||
else if cmd == "wrapperGetState"
|
|
||||||
@sendInner {"cmd": "response", "to": message.id, "result": window.history.state}
|
|
||||||
else if cmd == "wrapperGetAjaxKey"
|
|
||||||
@sendInner {"cmd": "response", "to": message.id, "result": window.ajax_key}
|
|
||||||
else if cmd == "wrapperOpenWindow"
|
|
||||||
@actionOpenWindow(message.params)
|
|
||||||
else if cmd == "wrapperPermissionAdd"
|
|
||||||
@actionPermissionAdd(message)
|
|
||||||
else if cmd == "wrapperRequestFullscreen"
|
|
||||||
@actionRequestFullscreen()
|
|
||||||
else if cmd == "wrapperWebNotification"
|
|
||||||
@actionWebNotification(message)
|
|
||||||
else if cmd == "wrapperCloseWebNotification"
|
|
||||||
@actionCloseWebNotification(message)
|
|
||||||
else # Send to websocket
|
|
||||||
if message.id < 1000000
|
|
||||||
if message.cmd == "fileWrite" and not @modified_panel_updater_timer and site_info?.settings?.own
|
|
||||||
@modified_panel_updater_timer = setTimeout ( => @updateModifiedPanel(); @modified_panel_updater_timer = null ), 1000
|
|
||||||
@ws.send(message) # Pass message to websocket
|
|
||||||
else
|
|
||||||
@log "Invalid inner message id"
|
|
||||||
|
|
||||||
toRelativeQuery: (query=null) ->
|
|
||||||
if query == null
|
|
||||||
query = window.location.search
|
|
||||||
back = window.location.pathname
|
|
||||||
if back.match /^\/[^\/]+$/ # Add / after site address if called without it
|
|
||||||
back += "/"
|
|
||||||
if query.startsWith("#")
|
|
||||||
back = query
|
|
||||||
else if query.replace("?", "")
|
|
||||||
back += "?"+query.replace("?", "")
|
|
||||||
return back
|
|
||||||
|
|
||||||
|
|
||||||
displayOpenerDialog: ->
|
|
||||||
elem = $("<div class='opener-overlay'><div class='dialog'>You have opened this page by clicking on a link. Please, confirm if you want to load this site.<a href='?' target='_blank' class='button'>Open site</a></div></div>")
|
|
||||||
elem.find('a').on "click", ->
|
|
||||||
window.open("?", "_blank")
|
|
||||||
window.close()
|
|
||||||
return false
|
|
||||||
$("body").prepend(elem)
|
|
||||||
|
|
||||||
# - Actions -
|
|
||||||
|
|
||||||
actionOpenWindow: (params) ->
|
|
||||||
if typeof(params) == "string"
|
|
||||||
w = window.open()
|
|
||||||
w.opener = null
|
|
||||||
w.location = params
|
|
||||||
else
|
|
||||||
w = window.open(null, params[1], params[2])
|
|
||||||
w.opener = null
|
|
||||||
w.location = params[0]
|
|
||||||
|
|
||||||
actionRequestFullscreen: ->
|
|
||||||
elem = document.getElementById("inner-iframe")
|
|
||||||
request_fullscreen = elem.requestFullScreen || elem.webkitRequestFullscreen || elem.mozRequestFullScreen || elem.msRequestFullScreen
|
|
||||||
request_fullscreen.call(elem)
|
|
||||||
|
|
||||||
actionWebNotification: (message) ->
|
|
||||||
$.when(@event_site_info).done =>
|
|
||||||
# Check that the wrapper may send notifications
|
|
||||||
if Notification.permission == "granted"
|
|
||||||
@displayWebNotification message
|
|
||||||
else if Notification.permission == "denied"
|
|
||||||
res = {"error": "Web notifications are disabled by the user"}
|
|
||||||
@sendInner {"cmd": "response", "to": message.id, "result": res}
|
|
||||||
else
|
|
||||||
Notification.requestPermission().then (permission) =>
|
|
||||||
if permission == "granted"
|
|
||||||
@displayWebNotification message
|
|
||||||
|
|
||||||
actionCloseWebNotification: (message) ->
|
|
||||||
$.when(@event_site_info).done =>
|
|
||||||
id = message.params[0]
|
|
||||||
@web_notifications[id].close()
|
|
||||||
|
|
||||||
displayWebNotification: (message) ->
|
|
||||||
title = message.params[0]
|
|
||||||
id = message.params[1]
|
|
||||||
options = message.params[2]
|
|
||||||
notification = new Notification(title, options)
|
|
||||||
@web_notifications[id] = notification
|
|
||||||
notification.onshow = () =>
|
|
||||||
@sendInner {"cmd": "response", "to": message.id, "result": "ok"}
|
|
||||||
notification.onclick = (e) =>
|
|
||||||
if not options.focus_tab
|
|
||||||
e.preventDefault()
|
|
||||||
@sendInner {"cmd": "webNotificationClick", "params": {"id": id}}
|
|
||||||
notification.onclose = () =>
|
|
||||||
@sendInner {"cmd": "webNotificationClose", "params": {"id": id}}
|
|
||||||
delete @web_notifications[id]
|
|
||||||
|
|
||||||
actionPermissionAdd: (message) ->
|
|
||||||
permission = message.params
|
|
||||||
$.when(@event_site_info).done =>
|
|
||||||
if permission in @site_info.settings.permissions
|
|
||||||
return false
|
|
||||||
@ws.cmd "permissionDetails", permission, (permission_details) =>
|
|
||||||
@displayConfirm "This site requests permission:" + " <b>#{@toHtmlSafe(permission)}</b>" + "<br><small style='color: #4F4F4F'>#{permission_details}</small>", "Grant", =>
|
|
||||||
@ws.cmd "permissionAdd", permission, (res) =>
|
|
||||||
@sendInner {"cmd": "response", "to": message.id, "result": res}
|
|
||||||
|
|
||||||
actionNotification: (message) ->
|
|
||||||
message.params = @toHtmlSafe(message.params) # Escape html
|
|
||||||
body = $("<span class='message'>"+message.params[1]+"</span>")
|
|
||||||
@notifications.add("notification-#{message.id}", message.params[0], body, message.params[2])
|
|
||||||
|
|
||||||
displayConfirm: (body, captions, cb) ->
|
|
||||||
body = $("<span class='message-outer'><span class='message'>"+body+"</span></span>")
|
|
||||||
buttons = $("<span class='buttons'></span>")
|
|
||||||
if captions not instanceof Array then captions = [captions] # Convert to list if necessary
|
|
||||||
for caption, i in captions
|
|
||||||
button = $("<a></a>", {href: "#" + caption, class: "button button-confirm button-#{caption} button-#{i+1}", "data-value": i + 1}) # Add confirm button
|
|
||||||
button.text(caption)
|
|
||||||
((button) =>
|
|
||||||
button.on "click", (e) =>
|
|
||||||
@verifyEvent button, e
|
|
||||||
cb(parseInt(e.currentTarget.dataset.value))
|
|
||||||
return false
|
|
||||||
)(button)
|
|
||||||
buttons.append(button)
|
|
||||||
body.append(buttons)
|
|
||||||
@notifications.add("notification-#{caption}", "ask", body)
|
|
||||||
|
|
||||||
buttons.first().focus()
|
|
||||||
$(".notification").scrollLeft(0)
|
|
||||||
|
|
||||||
|
|
||||||
actionConfirm: (message, cb=false) ->
|
|
||||||
message.params = @toHtmlSafe(message.params) # Escape html
|
|
||||||
if message.params[1] then caption = message.params[1] else caption = "ok"
|
|
||||||
@displayConfirm message.params[0], caption, (res) =>
|
|
||||||
@sendInner {"cmd": "response", "to": message.id, "result": res} # Response to confirm
|
|
||||||
return false
|
|
||||||
|
|
||||||
|
|
||||||
displayPrompt: (message, type, caption, placeholder, cb) ->
|
|
||||||
body = $("<span class='message'></span>").html(message)
|
|
||||||
placeholder ?= ""
|
|
||||||
|
|
||||||
input = $("<input/>", {type: type, class: "input button-#{type}", placeholder: placeholder}) # Add input
|
|
||||||
input.on "keyup", (e) => # Send on enter
|
|
||||||
@verifyEvent input, e
|
|
||||||
if e.keyCode == 13
|
|
||||||
cb input.val() # Response to confirm
|
|
||||||
body.append(input)
|
|
||||||
|
|
||||||
button = $("<a></a>", {href: "#" + caption, class: "button button-#{caption}"}).text(caption) # Add confirm button
|
|
||||||
button.on "click", (e) => # Response on button click
|
|
||||||
@verifyEvent button, e
|
|
||||||
cb input.val()
|
|
||||||
return false
|
|
||||||
body.append(button)
|
|
||||||
|
|
||||||
@notifications.add("notification-#{message.id}", "ask", body)
|
|
||||||
|
|
||||||
input.focus()
|
|
||||||
$(".notification").scrollLeft(0)
|
|
||||||
|
|
||||||
|
|
||||||
actionPrompt: (message) ->
|
|
||||||
message.params = @toHtmlSafe(message.params) # Escape html
|
|
||||||
if message.params[1] then type = message.params[1] else type = "text"
|
|
||||||
caption = if message.params[2] then message.params[2] else "OK"
|
|
||||||
if message.params[3]?
|
|
||||||
placeholder = message.params[3]
|
|
||||||
else
|
|
||||||
placeholder = ""
|
|
||||||
|
|
||||||
@displayPrompt message.params[0], type, caption, placeholder, (res) =>
|
|
||||||
@sendInner {"cmd": "response", "to": message.id, "result": res} # Response to confirm
|
|
||||||
|
|
||||||
displayProgress: (type, body, percent) ->
|
|
||||||
percent = Math.min(100, percent)/100
|
|
||||||
offset = 75-(percent*75)
|
|
||||||
circle = """
|
|
||||||
<div class="circle"><svg class="circle-svg" width="30" height="30" viewport="0 0 30 30" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<circle r="12" cx="15" cy="15" fill="transparent" class="circle-bg"></circle>
|
|
||||||
<circle r="12" cx="15" cy="15" fill="transparent" class="circle-fg" style="stroke-dashoffset: #{offset}"></circle>
|
|
||||||
</svg></div>
|
|
||||||
"""
|
|
||||||
body = "<span class='message'>"+body+"</span>" + circle
|
|
||||||
elem = $(".notification-#{type}")
|
|
||||||
if elem.length
|
|
||||||
width = $(".body .message", elem).outerWidth()
|
|
||||||
$(".body .message", elem).html(body)
|
|
||||||
if $(".body .message", elem).css("width") == ""
|
|
||||||
$(".body .message", elem).css("width", width)
|
|
||||||
$(".body .circle-fg", elem).css("stroke-dashoffset", offset)
|
|
||||||
else
|
|
||||||
elem = @notifications.add(type, "progress", $(body))
|
|
||||||
if percent > 0
|
|
||||||
$(".body .circle-bg", elem).css {"animation-play-state": "paused", "stroke-dasharray": "180px"}
|
|
||||||
|
|
||||||
if $(".notification-icon", elem).data("done")
|
|
||||||
return false
|
|
||||||
else if percent >= 1 # Done
|
|
||||||
$(".circle-fg", elem).css("transition", "all 0.3s ease-in-out")
|
|
||||||
setTimeout (->
|
|
||||||
$(".notification-icon", elem).css {transform: "scale(1)", opacity: 1}
|
|
||||||
$(".notification-icon .icon-success", elem).css {transform: "rotate(45deg) scale(1)"}
|
|
||||||
), 300
|
|
||||||
setTimeout (=>
|
|
||||||
@notifications.close elem
|
|
||||||
), 3000
|
|
||||||
$(".notification-icon", elem).data("done", true)
|
|
||||||
else if percent < 0 # Error
|
|
||||||
$(".body .circle-fg", elem).css("stroke", "#ec6f47").css("transition", "transition: all 0.3s ease-in-out")
|
|
||||||
setTimeout (=>
|
|
||||||
$(".notification-icon", elem).css {transform: "scale(1)", opacity: 1}
|
|
||||||
elem.removeClass("notification-done").addClass("notification-error")
|
|
||||||
$(".notification-icon .icon-success", elem).removeClass("icon-success").html("!")
|
|
||||||
), 300
|
|
||||||
$(".notification-icon", elem).data("done", true)
|
|
||||||
|
|
||||||
|
|
||||||
actionProgress: (message) ->
|
|
||||||
message.params = @toHtmlSafe(message.params) # Escape html
|
|
||||||
@displayProgress(message.params[0], message.params[1], message.params[2])
|
|
||||||
|
|
||||||
actionSetViewport: (message) ->
|
|
||||||
@log "actionSetViewport", message
|
|
||||||
if $("#viewport").length > 0
|
|
||||||
$("#viewport").attr("content", @toHtmlSafe message.params)
|
|
||||||
else
|
|
||||||
$('<meta name="viewport" id="viewport">').attr("content", @toHtmlSafe message.params).appendTo("head")
|
|
||||||
|
|
||||||
actionReload: (message) ->
|
|
||||||
@reload(message.params[0])
|
|
||||||
|
|
||||||
reload: (url_post="") ->
|
|
||||||
@log "Reload"
|
|
||||||
current_url = window.location.toString().replace(/#.*/g, "")
|
|
||||||
if url_post
|
|
||||||
if current_url.indexOf("?") > 0
|
|
||||||
window.location = current_url + "&" + url_post
|
|
||||||
else
|
|
||||||
window.location = current_url + "?" + url_post
|
|
||||||
else
|
|
||||||
window.location.reload()
|
|
||||||
|
|
||||||
|
|
||||||
actionGetLocalStorage: (message) ->
|
|
||||||
$.when(@event_site_info).done =>
|
|
||||||
data = localStorage.getItem "site.#{@site_info.address}.#{@site_info.auth_address}"
|
|
||||||
if not data # Migrate from non auth_address based local storage
|
|
||||||
data = localStorage.getItem "site.#{@site_info.address}"
|
|
||||||
if data
|
|
||||||
localStorage.setItem "site.#{@site_info.address}.#{@site_info.auth_address}", data
|
|
||||||
localStorage.removeItem "site.#{@site_info.address}"
|
|
||||||
@log "Migrated LocalStorage from global to auth_address based"
|
|
||||||
if data then data = JSON.parse(data)
|
|
||||||
@sendInner {"cmd": "response", "to": message.id, "result": data}
|
|
||||||
|
|
||||||
|
|
||||||
actionSetLocalStorage: (message) ->
|
|
||||||
$.when(@event_site_info).done =>
|
|
||||||
back = localStorage.setItem "site.#{@site_info.address}.#{@site_info.auth_address}", JSON.stringify(message.params)
|
|
||||||
@sendInner {"cmd": "response", "to": message.id, "result": back}
|
|
||||||
|
|
||||||
|
|
||||||
# EOF actions
|
|
||||||
|
|
||||||
|
|
||||||
onOpenWebsocket: (e) =>
|
|
||||||
if window.show_loadingscreen # Get info on modifications
|
|
||||||
@ws.cmd "channelJoin", {"channels": ["siteChanged", "serverChanged", "announcerChanged"]}
|
|
||||||
else
|
|
||||||
@ws.cmd "channelJoin", {"channels": ["siteChanged", "serverChanged"]}
|
|
||||||
if not @wrapperWsInited and @inner_ready
|
|
||||||
@sendInner {"cmd": "wrapperOpenedWebsocket"} # Send to inner frame
|
|
||||||
@wrapperWsInited = true
|
|
||||||
if window.show_loadingscreen
|
|
||||||
@ws.cmd "serverInfo", [], (server_info) =>
|
|
||||||
@server_info = server_info
|
|
||||||
|
|
||||||
@ws.cmd "announcerInfo", [], (announcer_info) =>
|
|
||||||
@setAnnouncerInfo(announcer_info)
|
|
||||||
|
|
||||||
if @inner_loaded # Update site info
|
|
||||||
@reloadSiteInfo()
|
|
||||||
|
|
||||||
# If inner frame not loaded for 2 sec show peer informations on loading screen by loading site info
|
|
||||||
setTimeout (=>
|
|
||||||
if not @site_info then @reloadSiteInfo()
|
|
||||||
), 2000
|
|
||||||
|
|
||||||
if @ws_error
|
|
||||||
@notifications.add("connection", "done", "Connection with <b>UiServer Websocket</b> recovered.", 6000)
|
|
||||||
@ws_error = null
|
|
||||||
|
|
||||||
|
|
||||||
onCloseWebsocket: (e) =>
|
|
||||||
@wrapperWsInited = false
|
|
||||||
setTimeout (=> # Wait a bit, maybe its page closing
|
|
||||||
@sendInner {"cmd": "wrapperClosedWebsocket"} # Send to inner frame
|
|
||||||
if e and e.code == 1000 and e.wasClean == false # Server error please reload page
|
|
||||||
@ws_error = @notifications.add("connection", "error", "UiServer Websocket error, please reload the page.")
|
|
||||||
else if e and e.code == 1001 and e.wasClean == true # Navigating to other page
|
|
||||||
return
|
|
||||||
else if not @ws_error
|
|
||||||
@ws_error = @notifications.add("connection", "error", "Connection with <b>UiServer Websocket</b> was lost. Reconnecting...")
|
|
||||||
), 1000
|
|
||||||
|
|
||||||
|
|
||||||
# Iframe loaded
|
|
||||||
onPageLoad: (e) =>
|
|
||||||
@log "onPageLoad"
|
|
||||||
@inner_loaded = true
|
|
||||||
if not @inner_ready then @sendInner {"cmd": "wrapperReady"} # Inner frame loaded before wrapper
|
|
||||||
#if not @site_error then @loading.hideScreen() # Hide loading screen
|
|
||||||
if @ws.ws.readyState == 1 and not @site_info # Ws opened
|
|
||||||
@reloadSiteInfo()
|
|
||||||
else if @site_info and @site_info.content?.title? and not @is_title_changed
|
|
||||||
window.document.title = @site_info.content.title + " - ZeroNet"
|
|
||||||
@log "Setting title to", window.document.title
|
|
||||||
|
|
||||||
onWrapperLoad: =>
|
|
||||||
@script_nonce = window.script_nonce
|
|
||||||
@wrapper_key = window.wrapper_key
|
|
||||||
# Cleanup secret variables
|
|
||||||
delete window.wrapper
|
|
||||||
delete window.wrapper_key
|
|
||||||
delete window.script_nonce
|
|
||||||
$("#script_init").remove()
|
|
||||||
|
|
||||||
# Send message to innerframe
|
|
||||||
sendInner: (message) ->
|
|
||||||
@inner.postMessage(message, '*')
|
|
||||||
|
|
||||||
|
|
||||||
# Get site info from UiServer
|
|
||||||
reloadSiteInfo: ->
|
|
||||||
if @loading.screen_visible # Loading screen visible
|
|
||||||
params = {"file_status": window.file_inner_path} # Query the current required file status
|
|
||||||
else
|
|
||||||
params = {}
|
|
||||||
|
|
||||||
@ws.cmd "siteInfo", params, (site_info) =>
|
|
||||||
@address = site_info.address
|
|
||||||
@setSiteInfo site_info
|
|
||||||
|
|
||||||
if site_info.settings.size > site_info.size_limit * 1024 * 1024 and not @loading.screen_visible # Site size too large and not displaying it yet
|
|
||||||
@displayConfirm "Site is larger than allowed: #{(site_info.settings.size/1024/1024).toFixed(1)}MB/#{site_info.size_limit}MB", "Set limit to #{site_info.next_size_limit}MB", =>
|
|
||||||
@ws.cmd "siteSetLimit", [site_info.next_size_limit], (res) =>
|
|
||||||
if res == "ok"
|
|
||||||
@notifications.add("size_limit", "done", "Site storage limit modified!", 5000)
|
|
||||||
|
|
||||||
if site_info.content?.title? and not @is_title_changed
|
|
||||||
window.document.title = site_info.content.title + " - ZeroNet"
|
|
||||||
@log "Setting title to", window.document.title
|
|
||||||
|
|
||||||
|
|
||||||
# Got setSiteInfo from websocket UiServer
|
|
||||||
setSiteInfo: (site_info) ->
|
|
||||||
if site_info.event? # If loading screen visible add event to it
|
|
||||||
# File started downloading
|
|
||||||
if site_info.event[0] == "file_added" and site_info.bad_files
|
|
||||||
@loading.printLine("#{site_info.bad_files} files needs to be downloaded")
|
|
||||||
# File finished downloading
|
|
||||||
else if site_info.event[0] == "file_done"
|
|
||||||
@loading.printLine("#{site_info.event[1]} downloaded")
|
|
||||||
if site_info.event[1] == window.file_inner_path # File downloaded we currently on
|
|
||||||
@loading.hideScreen()
|
|
||||||
if not @site_info then @reloadSiteInfo()
|
|
||||||
if site_info.content and not @is_title_changed
|
|
||||||
window.document.title = site_info.content.title + " - ZeroNet"
|
|
||||||
@log "Required file #{window.file_inner_path} done, setting title to", window.document.title
|
|
||||||
if not window.show_loadingscreen
|
|
||||||
@notifications.add("modified", "info", "New version of this page has just released.<br>Reload to see the modified content.")
|
|
||||||
# File failed downloading
|
|
||||||
else if site_info.event[0] == "file_failed"
|
|
||||||
@site_error = site_info.event[1]
|
|
||||||
if site_info.settings.size > site_info.size_limit*1024*1024 # Site size too large and not displaying it yet
|
|
||||||
@loading.showTooLarge(site_info)
|
|
||||||
|
|
||||||
else
|
|
||||||
@loading.printLine("#{site_info.event[1]} download failed", "error")
|
|
||||||
# New peers found
|
|
||||||
else if site_info.event[0] == "peers_added"
|
|
||||||
@loading.printLine("Peers found: #{site_info.peers}")
|
|
||||||
|
|
||||||
if @loading.screen_visible and not @site_info # First site info display current peers
|
|
||||||
if site_info.peers > 1
|
|
||||||
@loading.printLine "Peers found: #{site_info.peers}"
|
|
||||||
else
|
|
||||||
@site_error = "No peers found"
|
|
||||||
@loading.printLine "No peers found"
|
|
||||||
|
|
||||||
if not @site_info and not @loading.screen_visible and $("#inner-iframe").attr("src").replace("?wrapper=False", "").replace(/\?wrapper_nonce=[A-Za-z0-9]+/, "").indexOf("?") == -1 # First site info and we are on mainpage (does not have other parameter thatn wrapper)
|
|
||||||
if site_info.size_limit*1.1 < site_info.next_size_limit # Need upgrade soon
|
|
||||||
@displayConfirm "Running out of size limit (#{(site_info.settings.size/1024/1024).toFixed(1)}MB/#{site_info.size_limit}MB)", "Set limit to #{site_info.next_size_limit}MB", =>
|
|
||||||
@ws.cmd "siteSetLimit", [site_info.next_size_limit], (res) =>
|
|
||||||
if res == "ok"
|
|
||||||
@notifications.add("size_limit", "done", "Site storage limit modified!", 5000)
|
|
||||||
return false
|
|
||||||
|
|
||||||
if @loading.screen_visible and @inner_loaded and site_info.settings.size < site_info.size_limit * 1024 * 1024 and site_info.settings.size > 0 # Loading screen still visible, but inner loaded
|
|
||||||
@log "Loading screen visible, but inner loaded"
|
|
||||||
@loading.hideScreen()
|
|
||||||
|
|
||||||
if site_info?.settings?.own and site_info?.settings?.modified != @site_info?.settings?.modified
|
|
||||||
@updateModifiedPanel()
|
|
||||||
|
|
||||||
if @loading.screen_visible and site_info.settings.size > site_info.size_limit * 1024 * 1024
|
|
||||||
@log "Site too large"
|
|
||||||
@loading.showTooLarge(site_info)
|
|
||||||
|
|
||||||
@site_info = site_info
|
|
||||||
@event_site_info.resolve()
|
|
||||||
|
|
||||||
siteSign: (inner_path, cb) =>
|
|
||||||
if @site_info.privatekey
|
|
||||||
# Privatekey stored in users.json
|
|
||||||
@infopanel.elem.find(".button").addClass("loading")
|
|
||||||
@ws.cmd "siteSign", {privatekey: "stored", inner_path: inner_path, update_changed_files: true}, (res) =>
|
|
||||||
if res == "ok"
|
|
||||||
cb?(true)
|
|
||||||
else
|
|
||||||
cb?(false)
|
|
||||||
@infopanel.elem.find(".button").removeClass("loading")
|
|
||||||
else
|
|
||||||
# Ask the user for privatekey
|
|
||||||
@displayPrompt "Enter your private key:", "password", "Sign", "", (privatekey) => # Prompt the private key
|
|
||||||
@infopanel.elem.find(".button").addClass("loading")
|
|
||||||
@ws.cmd "siteSign", {privatekey: privatekey, inner_path: inner_path, update_changed_files: true}, (res) =>
|
|
||||||
if res == "ok"
|
|
||||||
cb?(true)
|
|
||||||
else
|
|
||||||
cb?(false)
|
|
||||||
@infopanel.elem.find(".button").removeClass("loading")
|
|
||||||
|
|
||||||
sitePublish: (inner_path) =>
|
|
||||||
@ws.cmd "sitePublish", {"inner_path": inner_path, "sign": false}
|
|
||||||
|
|
||||||
updateModifiedPanel: =>
|
|
||||||
@ws.cmd "siteListModifiedFiles", [], (res) =>
|
|
||||||
num = res.modified_files?.length
|
|
||||||
if num > 0
|
|
||||||
closed = @site_info.settings.modified_files_notification == false
|
|
||||||
@infopanel.show(closed)
|
|
||||||
else
|
|
||||||
@infopanel.hide()
|
|
||||||
|
|
||||||
if num > 0
|
|
||||||
@infopanel.setTitle(
|
|
||||||
"#{res.modified_files.length} modified file#{if num > 1 then 's' else ''}",
|
|
||||||
res.modified_files.join(", ")
|
|
||||||
)
|
|
||||||
@infopanel.setClosedNum(num)
|
|
||||||
@infopanel.setAction "Sign & Publish", =>
|
|
||||||
@siteSign "content.json", (res) =>
|
|
||||||
if (res)
|
|
||||||
@notifications.add "sign", "done", "content.json Signed!", 5000
|
|
||||||
@sitePublish("content.json")
|
|
||||||
return false
|
|
||||||
@log "siteListModifiedFiles", num, res
|
|
||||||
|
|
||||||
setAnnouncerInfo: (announcer_info) ->
|
|
||||||
status_db = {announcing: [], error: [], announced: []}
|
|
||||||
for key, val of announcer_info.stats
|
|
||||||
if val.status
|
|
||||||
status_db[val.status].push(val)
|
|
||||||
status_line = "Trackers announcing: #{status_db.announcing.length}, error: #{status_db.error.length}, done: #{status_db.announced.length}"
|
|
||||||
if @announcer_line
|
|
||||||
@announcer_line.text(status_line)
|
|
||||||
else
|
|
||||||
@announcer_line = @loading.printLine(status_line)
|
|
||||||
|
|
||||||
if status_db.error.length > (status_db.announced.length + status_db.announcing.length) and status_db.announced.length < 3
|
|
||||||
@loading.showTrackerTorBridge(@server_info)
|
|
||||||
|
|
||||||
updateProgress: (site_info) ->
|
|
||||||
if site_info.tasks > 0 and site_info.started_task_num > 0
|
|
||||||
@loading.setProgress 1-(Math.max(site_info.tasks, site_info.bad_files) / site_info.started_task_num)
|
|
||||||
else
|
|
||||||
@loading.hideProgress()
|
|
||||||
|
|
||||||
|
|
||||||
toHtmlSafe: (values) ->
|
|
||||||
if values not instanceof Array then values = [values] # Convert to array if its not
|
|
||||||
for value, i in values
|
|
||||||
if value instanceof Array
|
|
||||||
value = @toHtmlSafe(value)
|
|
||||||
else
|
|
||||||
value = String(value).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''') # Escape dangerous characters
|
|
||||||
value = value.replace(/<([\/]{0,1}(br|b|u|i|small))>/g, "<$1>") # Unescape b, i, u, br tags
|
|
||||||
values[i] = value
|
|
||||||
return values
|
|
||||||
|
|
||||||
|
|
||||||
setSizeLimit: (size_limit, reload=true) =>
|
|
||||||
@log "setSizeLimit: #{size_limit}, reload: #{reload}"
|
|
||||||
@inner_loaded = false # Inner frame not loaded, just a 404 page displayed
|
|
||||||
@ws.cmd "siteSetLimit", [size_limit], (res) =>
|
|
||||||
if res != "ok"
|
|
||||||
return false
|
|
||||||
@loading.printLine res
|
|
||||||
@inner_loaded = false
|
|
||||||
if reload then @reloadIframe()
|
|
||||||
return false
|
|
||||||
|
|
||||||
reloadIframe: =>
|
|
||||||
src = $("iframe").attr("src")
|
|
||||||
@ws.cmd "serverGetWrapperNonce", [], (wrapper_nonce) =>
|
|
||||||
src = src.replace(/wrapper_nonce=[A-Za-z0-9]+/, "wrapper_nonce=" + wrapper_nonce)
|
|
||||||
@log "Reloading iframe using url", src
|
|
||||||
$("iframe").attr "src", src
|
|
||||||
|
|
||||||
log: (args...) ->
|
|
||||||
console.log "[Wrapper]", args...
|
|
||||||
|
|
||||||
origin = window.server_url or window.location.href.replace(/(\:\/\/.*?)\/.*/, "$1")
|
|
||||||
|
|
||||||
if origin.indexOf("https:") == 0
|
|
||||||
proto = { ws: 'wss', http: 'https' }
|
|
||||||
else
|
|
||||||
proto = { ws: 'ws', http: 'http' }
|
|
||||||
|
|
||||||
ws_url = proto.ws + ":" + origin.replace(proto.http+":", "") + "/ZeroNet-Internal/Websocket?wrapper_key=" + window.wrapper_key
|
|
||||||
|
|
||||||
window.wrapper = new Wrapper(ws_url)
|
|
||||||
|
|
|
@ -1,22 +0,0 @@
|
||||||
class WrapperZeroFrame
|
|
||||||
constructor: (wrapper) ->
|
|
||||||
@wrapperCmd = wrapper.cmd
|
|
||||||
@wrapperResponse = wrapper.ws.response
|
|
||||||
console.log "WrapperZeroFrame", wrapper
|
|
||||||
|
|
||||||
cmd: (cmd, params={}, cb=null) =>
|
|
||||||
@wrapperCmd(cmd, params, cb)
|
|
||||||
|
|
||||||
response: (to, result) =>
|
|
||||||
@wrapperResponse(to, result)
|
|
||||||
|
|
||||||
isProxyRequest: ->
|
|
||||||
return window.location.pathname == "/"
|
|
||||||
|
|
||||||
certSelectGotoSite: (elem) =>
|
|
||||||
href = $(elem).attr("href")
|
|
||||||
if @isProxyRequest() # Fix for proxy request
|
|
||||||
$(elem).attr("href", "http://zero#{href}")
|
|
||||||
|
|
||||||
|
|
||||||
window.zeroframe = new WrapperZeroFrame(window.wrapper)
|
|
|
@ -1,49 +0,0 @@
|
||||||
DARK = "(prefers-color-scheme: dark)"
|
|
||||||
LIGHT = "(prefers-color-scheme: light)"
|
|
||||||
|
|
||||||
mqDark = window.matchMedia(DARK)
|
|
||||||
mqLight = window.matchMedia(LIGHT)
|
|
||||||
|
|
||||||
|
|
||||||
changeColorScheme = (theme) ->
|
|
||||||
zeroframe.cmd "userGetGlobalSettings", [], (user_settings) ->
|
|
||||||
if user_settings.theme != theme
|
|
||||||
user_settings.theme = theme
|
|
||||||
zeroframe.cmd "userSetGlobalSettings", [user_settings], (status) ->
|
|
||||||
if status == "ok"
|
|
||||||
location.reload()
|
|
||||||
return
|
|
||||||
return
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
displayNotification = ({matches, media}) ->
|
|
||||||
if !matches
|
|
||||||
return
|
|
||||||
|
|
||||||
zeroframe.cmd "siteInfo", [], (site_info) ->
|
|
||||||
if "ADMIN" in site_info.settings.permissions
|
|
||||||
zeroframe.cmd "wrapperNotification", ["info", "Your system's theme has been changed.<br>Please reload site to use it."]
|
|
||||||
else
|
|
||||||
zeroframe.cmd "wrapperNotification", ["info", "Your system's theme has been changed.<br>Please open ZeroHello to use it."]
|
|
||||||
return
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
detectColorScheme = ->
|
|
||||||
if mqDark.matches
|
|
||||||
changeColorScheme("dark")
|
|
||||||
else if mqLight.matches
|
|
||||||
changeColorScheme("light")
|
|
||||||
|
|
||||||
mqDark.addListener(displayNotification)
|
|
||||||
mqLight.addListener(displayNotification)
|
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
zeroframe.cmd "userGetGlobalSettings", [], (user_settings) ->
|
|
||||||
if user_settings.use_system_theme == true
|
|
||||||
detectColorScheme()
|
|
||||||
|
|
||||||
return
|
|
|
@ -1,14 +0,0 @@
|
||||||
limits = {}
|
|
||||||
call_after_interval = {}
|
|
||||||
window.RateLimit = (interval, fn) ->
|
|
||||||
if not limits[fn]
|
|
||||||
call_after_interval[fn] = false
|
|
||||||
fn() # First call is not delayed
|
|
||||||
limits[fn] = setTimeout (->
|
|
||||||
if call_after_interval[fn]
|
|
||||||
fn()
|
|
||||||
delete limits[fn]
|
|
||||||
delete call_after_interval[fn]
|
|
||||||
), interval
|
|
||||||
else # Called within iterval, delay the call
|
|
||||||
call_after_interval[fn] = true
|
|
|
@ -1 +0,0 @@
|
||||||
window._ = (s) -> return s
|
|
|
@ -1,95 +0,0 @@
|
||||||
class ZeroWebsocket
|
|
||||||
constructor: (url) ->
|
|
||||||
@url = url
|
|
||||||
@next_message_id = 1
|
|
||||||
@waiting_cb = {}
|
|
||||||
@init()
|
|
||||||
|
|
||||||
|
|
||||||
init: ->
|
|
||||||
@
|
|
||||||
|
|
||||||
|
|
||||||
connect: ->
|
|
||||||
@ws = new WebSocket(@url)
|
|
||||||
@ws.onmessage = @onMessage
|
|
||||||
@ws.onopen = @onOpenWebsocket
|
|
||||||
@ws.onerror = @onErrorWebsocket
|
|
||||||
@ws.onclose = @onCloseWebsocket
|
|
||||||
@connected = false
|
|
||||||
@message_queue = []
|
|
||||||
|
|
||||||
|
|
||||||
onMessage: (e) =>
|
|
||||||
message = JSON.parse(e.data)
|
|
||||||
cmd = message.cmd
|
|
||||||
if cmd == "response"
|
|
||||||
if @waiting_cb[message.to]?
|
|
||||||
@waiting_cb[message.to](message.result)
|
|
||||||
else
|
|
||||||
@log "Websocket callback not found:", message
|
|
||||||
else if cmd == "ping"
|
|
||||||
@response message.id, "pong"
|
|
||||||
else
|
|
||||||
@route cmd, message
|
|
||||||
|
|
||||||
route: (cmd, message) =>
|
|
||||||
@log "Unknown command", message
|
|
||||||
|
|
||||||
|
|
||||||
response: (to, result) =>
|
|
||||||
@send {"cmd": "response", "to": to, "result": result}
|
|
||||||
|
|
||||||
|
|
||||||
cmd: (cmd, params={}, cb=null) ->
|
|
||||||
@send {"cmd": cmd, "params": params}, cb
|
|
||||||
|
|
||||||
|
|
||||||
send: (message, cb=null) ->
|
|
||||||
if not message.id?
|
|
||||||
message.id = @next_message_id
|
|
||||||
@next_message_id += 1
|
|
||||||
if @connected
|
|
||||||
@ws.send(JSON.stringify(message))
|
|
||||||
else
|
|
||||||
@log "Not connected, adding message to queue"
|
|
||||||
@message_queue.push(message)
|
|
||||||
if cb
|
|
||||||
@waiting_cb[message.id] = cb
|
|
||||||
|
|
||||||
|
|
||||||
log: (args...) =>
|
|
||||||
console.log "[ZeroWebsocket]", args...
|
|
||||||
|
|
||||||
|
|
||||||
onOpenWebsocket: (e) =>
|
|
||||||
@log "Open"
|
|
||||||
@connected = true
|
|
||||||
|
|
||||||
# Process messages sent before websocket opened
|
|
||||||
for message in @message_queue
|
|
||||||
@ws.send(JSON.stringify(message))
|
|
||||||
@message_queue = []
|
|
||||||
|
|
||||||
if @onOpen? then @onOpen(e)
|
|
||||||
|
|
||||||
|
|
||||||
onErrorWebsocket: (e) =>
|
|
||||||
@log "Error", e
|
|
||||||
if @onError? then @onError(e)
|
|
||||||
|
|
||||||
|
|
||||||
onCloseWebsocket: (e, reconnect=10000) =>
|
|
||||||
@log "Closed", e
|
|
||||||
@connected = false
|
|
||||||
if e and e.code == 1000 and e.wasClean == false
|
|
||||||
@log "Server error, please reload the page", e.wasClean
|
|
||||||
else # Connection error
|
|
||||||
setTimeout (=>
|
|
||||||
@log "Reconnecting..."
|
|
||||||
@connect()
|
|
||||||
), reconnect
|
|
||||||
if @onClose? then @onClose(e)
|
|
||||||
|
|
||||||
|
|
||||||
window.ZeroWebsocket = ZeroWebsocket
|
|
|
@ -1,36 +0,0 @@
|
||||||
jQuery.fn.readdClass = (class_name) ->
|
|
||||||
elem = @
|
|
||||||
elem.removeClass class_name
|
|
||||||
setTimeout ( ->
|
|
||||||
elem.addClass class_name
|
|
||||||
), 1
|
|
||||||
return @
|
|
||||||
|
|
||||||
jQuery.fn.removeLater = (time = 500) ->
|
|
||||||
elem = @
|
|
||||||
setTimeout ( ->
|
|
||||||
elem.remove()
|
|
||||||
), time
|
|
||||||
return @
|
|
||||||
|
|
||||||
jQuery.fn.hideLater = (time = 500) ->
|
|
||||||
elem = @
|
|
||||||
setTimeout ( ->
|
|
||||||
if elem.css("opacity") == 0
|
|
||||||
elem.css("display", "none")
|
|
||||||
), time
|
|
||||||
return @
|
|
||||||
|
|
||||||
jQuery.fn.addClassLater = (class_name, time = 5) ->
|
|
||||||
elem = @
|
|
||||||
setTimeout ( ->
|
|
||||||
elem.addClass(class_name)
|
|
||||||
), time
|
|
||||||
return @
|
|
||||||
|
|
||||||
jQuery.fn.cssLater = (name, val, time = 500) ->
|
|
||||||
elem = @
|
|
||||||
setTimeout ( ->
|
|
||||||
elem.css name, val
|
|
||||||
), time
|
|
||||||
return @
|
|
Loading…
Reference in a new issue