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:
caryoscelus 2022-05-31 15:37:20 +04:00 committed by GitHub
commit 2d1ffc59ff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
52 changed files with 3 additions and 5017 deletions

View File

@ -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

View File

@ -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, "&lt;").replace(/\>/g, "&gt;")
[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, "&lt;").replace(/\>/g, "&gt;")
#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

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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'

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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
###

View File

@ -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

View File

@ -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()

View File

@ -1,3 +0,0 @@
window.$ = (selector) ->
if selector.startsWith("#")
return document.getElementById(selector.replace("#", ""))

View File

@ -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

View File

@ -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"
]

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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()

View File

@ -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

View File

@ -1,3 +0,0 @@
window.$ = (selector) ->
if selector.startsWith("#")
return document.getElementById(selector.replace("#", ""))

View File

@ -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

View File

@ -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()

View File

@ -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
###

View File

@ -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

View File

@ -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
###

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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
###

View File

@ -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

View File

@ -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()

View File

@ -1,3 +0,0 @@
window.$ = (selector) ->
if selector.startsWith("#")
return document.getElementById(selector.replace("#", ""))

View File

@ -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

View File

@ -124,11 +124,6 @@ class Config(object):
"http://tracker.bt4g.com:2095/announce", # Cloudflare
"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:
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('--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_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')

View File

@ -29,30 +29,13 @@ def findfiles(path, find_ext):
yield file_path.replace("\\", "/")
# Try to find coffeescript compiler in path
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
# Generates: all.js: merge *.js, all.css: merge *.css, vendor prefix features
def merge(merged_path):
merged_path = merged_path.replace("\\", "/")
merge_dir = os.path.dirname(merged_path)
s = time.time()
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 os.path.isfile(merged_path):
@ -80,44 +63,7 @@ def merge(merged_path):
for file_path in findfiles(merge_dir, find_ext):
file_relative_path = file_path.replace(merge_dir + "/", "")
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)
if ext == "css": # Vendor prefix css
@ -131,5 +77,4 @@ def merge(merged_path):
if __name__ == "__main__":
logging.getLogger().setLevel(logging.DEBUG)
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")

View File

@ -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

View File

@ -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

View File

@ -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 "&nbsp;&nbsp;&nbsp;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

View File

@ -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

View File

@ -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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&apos;') # Escape dangerous characters
value = value.replace(/&lt;([\/]{0,1}(br|b|u|i|small))&gt;/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)

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -1 +0,0 @@
window._ = (s) -> return s

View File

@ -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

View File

@ -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 @