')
+ # @scrollable()
+ @when_loaded.resolve()
+
+ else # Not first update, patch the html to keep unchanged dom elements
+ morphdom @tag.find(".content")[0], '
'+res+'
', {
+ 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? 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 = $("
+