function toggleFullScreen() { if (!document.fullscreenElement) { document.documentElement.requestFullscreen() } else { if (document.exitFullscreen) { document.exitFullscreen() } } } var cards = {} var totalcolumns = 0 var columns = [] var currentTheme = "bigcards" var boardInitialized = false var keyTrap = null var baseurl = location.pathname.substring(0, location.pathname.lastIndexOf("/")) var socket = io.connect({ path: baseurl + "/socket.io" }) moment.locale(navigator.language || navigator.languages[0]) marked.setOptions({ sanitize: true }) //an action has happened, send it to the //server function sendAction(a, d) { //console.log('--> ' + a); var message = { action: a, data: d, } socket.json.send(message) } socket.on("connect", function () { //console.log('successful socket.io connect'); //let the final part of the path be the room name var room = location.pathname.substring(location.pathname.lastIndexOf("/")) //imediately join the room which will trigger the initializations sendAction("joinRoom", room) }) socket.on("disconnect", function () { blockUI("Serveur déconnecté. Veuillez rafraîchir la page pour essayer de vous reconnecter…") //$('.blockOverlay').on('click', $.unblockUI); }) socket.on("message", function (data) { getMessage(data) }) function unblockUI() { $.unblockUI({ fadeOut: 50 }) } function blockUI(message) { message = message || "En attente…" $.blockUI({ message: message, css: { border: "none", padding: "15px", backgroundColor: "#000", "-webkit-border-radius": "10px", "-moz-border-radius": "10px", opacity: 0.5, color: "#fff", fontSize: "20px", }, fadeOut: 0, fadeIn: 10, }) } //respond to an action event function getMessage(m) { var message = m //JSON.parse(m); var action = message.action var data = message.data //console.log('<-- ' + action); switch (action) { case "roomAccept": //okay we're accepted, then request initialization //(this is a bit of unnessary back and forth but that's okay for now) sendAction("initializeMe", null) break case "roomDeny": //this doesn't happen yet break case "moveCard": moveCard($("#" + data.id), data.position) break case "initCards": initCards(data) break case "createCard": //console.log(data); drawNewCard(data.id, data.text, data.x, data.y, data.rot, data.colour, null) break case "deleteCard": $("#" + data.id).fadeOut(500, function () { $(this).remove() }) break case "editCard": $("#" + data.id) .children(".content:first") .attr("data-text", data.value) $("#" + data.id) .children(".content:first") .html(marked(data.value)) break case "initColumns": initColumns(data) break case "updateColumns": initColumns(data) break case "changeTheme": changeThemeTo(data) break case "join-announce": displayUserJoined(data.sid, data.user_name) break case "leave-announce": displayUserLeft(data.sid) break case "initialUsers": displayInitialUsers(data) break case "nameChangeAnnounce": updateName(message.data.sid, message.data.user_name) break case "addSticker": addSticker(message.data.cardId, message.data.stickerId) break case "setBoardSize": resizeBoard(message.data) break case "export": download(message.data.filename, message.data.text) break case "addRevision": addRevision(message.data) break case "deleteRevision": $("#revision-" + message.data).remove() break case "initRevisions": $("#revisions-list").empty() for (var i = 0; i < message.data.length; i++) { addRevision(message.data[i]) } break default: //unknown message alert("action inconnue : " + JSON.stringify(message)) break } } $(document).on("keyup", function (event) { keyTrap = event.which }) function drawNewCard(id, text, x, y, rot, colour, sticker, animationspeed, mx = 0 , my = 0) { //cards[id] = {id: id, text: text, x: x, y: y, rot: rot, colour: colour}; var h = '
\ \
' + marked(text) + '
\
' var card = $(h) card.appendTo("#board") $("#" + id) .children(".content:first") .attr("data-text", text) //@TODO //Draggable has a bug which prevents blur event //http://bugs.jqueryui.com/ticket/4261 //So we have to blur all the cards and editable areas when //we click on a card //The following doesn't work so we will do the bug //fix recommended in the above bug report // card.on('click', function() { // $(this).focus(); // } ); card.draggable({ snap: false, snapTolerance: 5, containment: [0, 0, 2000, 2000], stack: ".card", start: function (event, ui) { keyTrap = null }, drag: function (event, ui) { if (keyTrap == 27) { ui.helper.css(ui.originalPosition) return false } }, handle: "div.content", }) //After a drag: card.on("dragstop", function (event, ui) { if (keyTrap == 27) { keyTrap = null return } var data = { id: this.id, position: ui.position, oldposition: ui.originalPosition, } sendAction("moveCard", data) }) card.children(".droppable").droppable({ accept: ".sticker", drop: function (event, ui) { var stickerId = ui.draggable.attr("id") var cardId = $(this).parent().attr("id") addSticker(cardId, stickerId) var data = { cardId: cardId, stickerId: stickerId, } sendAction("addSticker", data) //remove hover state to everything on the board to prevent //a jquery bug where it gets left around $(".card-hover-draggable").removeClass("card-hover-draggable") }, hoverClass: "card-hover-draggable", }) var speed = Math.floor(Math.random() * 1000) if (typeof animationspeed != "undefined") speed = animationspeed if (mx == 0 && my == 0) { var startPosition = $("#create-card").position() mx = startPosition.left; my = startPosition.top; } card.css("top", my) card.css("left", mx) card.animate( { left: x + "px", top: y + "px", }, speed ) card.children(".delete-card-icon").on("click", function () { $("#" + id).remove() //notify server of delete sendAction("deleteCard", { id: id, }) }) card.children(".content").editable( function (value, settings) { $("#" + id) .children(".content:first") .attr("data-text", value) onCardChange(id, value) return marked(value) }, { type: "textarea", data: function () { return $("#" + id) .children(".content:first") .attr("data-text") }, submit: "OK", style: "inherit", cssclass: "card-edit-form", placeholder: "Double cliquez pour m’éditer", onblur: "submit", event: "dblclick", //event: 'mouseover' } ) //add applicable sticker if (sticker !== null) addSticker(id, sticker) } function onCardChange(id, text) { sendAction("editCard", { id: id, value: text, }) } function moveCard(card, position) { card.animate( { left: position.left + "px", top: position.top + "px", }, 500 ) } function addSticker(cardId, stickerId) { stickerContainer = $("#" + cardId + " .filler") if (stickerId === "nosticker") { stickerContainer.html("") return } if (Array.isArray(stickerId)) { for (var i in stickerId) { stickerContainer.prepend('') } } else { if (stickerContainer.html().indexOf(stickerId) < 0) stickerContainer.prepend('') } } //---------------------------------- // cards //---------------------------------- function createCard(id, text, x, y, rot, colour, mx = 0,my = 0) { drawNewCard(id, text, x, y, rot, colour, null, null, mx, my) var action = "createCard" var data = { id: id, text: text, x: x, y: y, rot: rot, colour: colour, } sendAction(action, data) } function randomCardColour() { var colours = ["yellow", "green", "blue", "white"] var i = Math.floor(Math.random() * colours.length) return colours[i] } function initCards(cardArray) { //first delete any cards that exist $(".card").remove() cards = cardArray for (var i in cardArray) { card = cardArray[i] drawNewCard(card.id, card.text, card.x, card.y, card.rot, card.colour, card.sticker, 0) } boardInitialized = true unblockUI() } //---------------------------------- // cols //---------------------------------- function drawNewColumn(columnName) { var cls = "col" if (totalcolumns === 0) { cls = "col first" } $("#icon-col").before( '

' + columnName + "

" ) $(".editable").editable( function (value, settings) { onColumnChange(this.id, value) return value }, { style: "inherit", cssclass: "card-edit-form", type: "textarea", placeholder: "Nouveau", onblur: "submit", width: "", height: "", xindicator: '', event: "dblclick", //event: 'mouseover' } ) $(".col:last").fadeIn(500) totalcolumns++ } function onColumnChange(id, text) { var names = Array() //console.log(id + " " + text ); //Get the names of all the columns right from the DOM $(".col").each(function () { //get ID of current column we are traversing over var thisID = $(this).children("h2").attr("id") if (id == thisID) { names.push(text) } else { names.push($(this).text()) } }) updateColumns(names) } function displayRemoveColumn() { if (totalcolumns <= 0) return false $(".col:last").fadeOut(150, function () { $(this).remove() }) totalcolumns-- } function createColumn(name) { if (totalcolumns >= 8) return false drawNewColumn(name) columns.push(name) var action = "updateColumns" var data = columns sendAction(action, data) } function deleteColumn() { if (totalcolumns <= 0) return false displayRemoveColumn() columns.pop() var action = "updateColumns" var data = columns sendAction(action, data) } function updateColumns(c) { columns = c var action = "updateColumns" var data = columns sendAction(action, data) } function deleteColumns(next) { //delete all existing columns: $(".col").fadeOut("slow", next()) } function initColumns(columnArray) { totalcolumns = 0 columns = columnArray $(".col").remove() for (var i in columnArray) { column = columnArray[i] drawNewColumn(column) } } function changeThemeTo(theme) { currentTheme = theme if (theme == 'bigcards') { $("#board").removeClass('smallcards') } else { $("#board").removeClass('bigcards') } $("#board").addClass(theme) } ////////////////////////////////////////////////////////// ////////// NAMES STUFF /////////////////////////////////// ////////////////////////////////////////////////////////// function setCookie(c_name, value, exdays) { var exdate = new Date() exdate.setDate(exdate.getDate() + exdays) var c_value = escape(value) + (exdays === null ? "" : "; expires=" + exdate.toUTCString()) + ';SameSite=Strict' document.cookie = c_name + "=" + c_value } function getCookie(c_name) { var i, x, y, ARRcookies = document.cookie.split(";") for (i = 0; i < ARRcookies.length; i++) { x = ARRcookies[i].substr(0, ARRcookies[i].indexOf("=")) y = ARRcookies[i].substr(ARRcookies[i].indexOf("=") + 1) x = x.replace(/^\s+|\s+$/g, "") if (x == c_name) { return unescape(y) } } } function setName(name) { sendAction("setUserName", name) setCookie("scrumscrum-username", name, 365) } function displayInitialUsers(users) { for (var i in users) { //console.log(users); displayUserJoined(users[i].sid, users[i].user_name) } } function displayUserJoined(sid, user_name) { name = "" if (user_name) name = user_name else name = sid.substring(0, 5) $("#names-ul").append('
  • ' + name + "
  • ") } function displayUserLeft(sid) { name = "" if (name) name = user_name else name = sid var id = "#user-" + sid.toString() $("#names-ul") .children(id) .fadeOut(1000, function () { $(this).remove() }) } function updateName(sid, name) { var id = "#user-" + sid.toString() $("#names-ul").children(id).text(name) } ////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////// function boardResizeHappened(event, ui) { var newsize = ui.size sendAction("setBoardSize", newsize) } function resizeBoard(size) { $(".board-outline").animate({ height: size.height, width: size.width, }) } ////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////// function calcCardOffset() { var offsets = {} $(".card").each(function () { var card = $(this) $(".col").each(function (i) { var col = $(this) if (col.offset().left + col.outerWidth() > card.offset().left + card.outerWidth() || i === $(".col").length - 1) { offsets[card.attr("id")] = { col: col, x: (card.offset().left - col.offset().left) / col.outerWidth(), } return false } }) }) return offsets } //moves cards with a resize of the Board //doSync is false if you don't want to synchronize //with all the other users who are in this room function adjustCard(offsets, doSync) { $(".card").each(function () { var card = $(this) var offset = offsets[this.id] if (offset) { var data = { id: this.id, position: { left: offset.col.position().left + offset.x * offset.col.outerWidth(), top: parseInt(card.css("top").slice(0, -2)), }, oldposition: { left: parseInt(card.css("left").slice(0, -2)), top: parseInt(card.css("top").slice(0, -2)), }, } //use .css() instead of .position() because css' rotate //console.log(data); if (!doSync) { card.css("left", data.position.left) card.css("top", data.position.top) } else { //note that in this case, data.oldposition isn't accurate since //many moves have happened since the last sync //but that's okay becuase oldPosition isn't used right now moveCard(card, data.position) sendAction("moveCard", data) } } }) } ////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////// function download(filename, text) { var element = document.createElement("a") var mime = "text/plain" if (filename.match(/.csv$/)) { mime = "text/csv" } element.setAttribute("href", "data:" + mime + ";charset=utf-8," + encodeURIComponent(text)) element.setAttribute("download", filename) element.style.display = "none" document.body.appendChild(element) element.click() document.body.removeChild(element) } function addRevision(timestamp) { var li = $('
  • ') var s1 = $("") var s2 = $('delete revision') if (typeof timestamp === "string") { timestamp = parseInt(timestamp) } s1.text(moment(timestamp).format("LLLL")) li.append(s1) li.append(s2) $("#revisions-list").append(li) // $('body').on("click", s1, function () { // socket.json.send({ // action: "exportRevision", // data: timestamp, // }) // }) // $('body').on("click", s2, function () { // socket.json.send({ // action: "deleteRevision", // data: timestamp, // }) // }) } ////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////// $(function () { //disable image dragging //window.ondragstart = function() { return false; }; if (boardInitialized === false) blockUI('') //setTimeout($.unblockUI, 2000); $(".add-post-it").on("click", function (e) { var rotation = Math.random() * 10 - 5 //add a bit of random rotation (+/- 10deg) var cardLeft = 150 + Math.random() * 400 var cardTop = 20 + Math.random() * 50 var uniqueID = Math.round(Math.random() * 99999999) //is this big enough to assure uniqueness? console.log(e.clientX, e.clientY); createCard("card" + uniqueID, "", cardLeft, cardTop, rotation, $(this).data("color"), e.clientX, e.clientY) }) // Style changer $("#smallify").on("click", function () { if (currentTheme == "bigcards") { changeThemeTo("smallcards") } else if (currentTheme == "smallcards") { changeThemeTo("bigcards") } sendAction("changeTheme", currentTheme) return false }) $("#icon-col").on( "hover", function () { $(".col-icon").fadeIn(10) }, function () { $(".col-icon").fadeOut(150) } ) $("#add-col").on("click", function () { createColumn("Nouvelle colonne") return false }) $("#delete-col").on("click", function () { deleteColumn() return false }) var user_name = getCookie("scrumscrum-username") $("#yourname-input").on("focus", function () { if ($(this).val() == "anonyme") { $(this).val("") } $(this).addClass("focused") }) $("#yourname-input").on("blur", function () { if ($(this).val() === "") { $(this).val("anonyme") } $(this).removeClass("focused") setName($(this).val()) }) $("#yourname-input").val(user_name) $("#yourname-input").trigger("blur") $("#yourname-li").hide() $("#yourname-input").on("keypress", function (e) { code = e.keyCode ? e.keyCode : e.which if (code == 10 || code == 13) { $(this).trigger("blur") } }) $(".sticker").draggable({ revert: true, zIndex: 1000, }) $(".board-outline").resizable({ ghost: false, minWidth: 640, minHeight: 480, maxWidth: 1140, maxHeight: 855, }) //A new scope for precalculating ;(function () { var offsets $(".board-outline").on("resizestart", function () { offsets = calcCardOffset() }) $(".board-outline").on("resize", function (event, ui) { adjustCard(offsets, false) }) $(".board-outline").on("resizestop", function (event, ui) { boardResizeHappened(event, ui) adjustCard(offsets, true) }) })() $("#marker").draggable({ axis: "x", containment: "parent", }) $("#eraser").draggable({ axis: "x", containment: "parent", }) $("#export-txt").on("click", function () { socket.json.send({ action: "exportTxt", data: $(".col").length !== 0 ? $(".col").css("width").replace("px", "") : null, }) }) $("#export-csv").on("click", function () { socket.json.send({ action: "exportCsv", data: $(".col").length !== 0 ? $(".col").css("width").replace("px", "") : null, }) }) $("#export-json").on("click", function () { socket.json.send({ action: "exportJson", data: { width: $(".board-outline").css("width").replace("px", ""), height: $(".board-outline").css("height").replace("px", ""), }, }) }) $("#import-file").on("click", function (evt) { evt.stopPropagation() evt.preventDefault() var f = $("#import-input").get(0).files[0] var fr = new FileReader() fr.onloadend = function () { var text = fr.result socket.json.send({ action: "importJson", data: JSON.parse(text), }) } fr.readAsText(f) }) $("#create-revision").on("click", function () { socket.json.send({ action: "createRevision", data: { width: $(".board-outline").css("width").replace("px", ""), height: $(".board-outline").css("height").replace("px", ""), }, }) }) }) /** Doubleclick on mobile + Layout Framemo with tabs **/ $(document).ready(function () { if (window.location.href != window.location.protocol + "//" + window.location.host + "/") { // Not on homepage /** Double click on mobile interface **/ var clickTimer = null var clickTarget = null var editTarget = null function doubletapCards(selector) { $(selector + " .stickertarget").addClass("doubletap") // Escape multi bound $(selector + " .doubletap").on("click", function () { clickTarget = selector.replace("#", "") if (clickTimer == null) { clickTimer = setTimeout(function () { clickTimer = null }, 1000) } else { //console.log('doubleclick : '+clickTimer+':'+editTarget); clearTimeout(clickTimer) clickTimer = null if (editTarget == clickTarget && clickTarget !== undefined && clickTarget !== null) { $("#" + clickTarget.replace("content:", "") + " .doubletap").trigger("dblclick") } } editTarget = clickTarget }) } function doubletapTitle(selector) { $(selector).addClass("doubletap") // Escape multi bound $(selector + ".doubletap").on("click", function () { clickTarget = selector.replace("#", "") if (clickTimer == null) { clickTimer = setTimeout(function () { clickTimer = null }, 1000) } else { //console.log('doubleclick : '+clickTimer+':'+editTarget); clearTimeout(clickTimer) clickTimer = null if (editTarget == clickTarget && clickTarget !== undefined && clickTarget !== null) { $("#" + clickTarget + ".doubletap").trigger("dblclick") } } editTarget = clickTarget }) } setInterval(function () { // Add periodically the doubletap event on new cards $(".stickertarget:not(.doubletap)").each(function () { doubletapCards("#" + $(this).attr("id").replace("content:", "")) }) $("#board-table .col h2:not(.doubletap)").each(function () { doubletapTitle("#" + $(this).attr("id")) }) }, 500) /** Layout Framemo - Tabs **/ // Defaut board real size (not 'auto' or 'inherit') saved in database // in order to be able to center it var boardReady = setInterval(function () { if (boardInitialized) { // when board is ready if ($(".board-outline").attr("style") === undefined) { // check if size is imported from db $(".board-outline").css({ width: $(".board-outline.ui-resizable").width() + 16 + "px", height: "466px", }) var data = {} data.size = { height: 466, width: $(".board-outline.ui-resizable").width() + 16, } boardResizeHappened("resizestop", data) // using scrumblr function that keep size in db after a resize } clearInterval(boardReady) } }, 500) // $("#scrumblr") // .append($(".names, .stickers, .buttons")) // .after( // '
    ' + // '
    ' + // '
    ' + // '
    ' // ) // $("#export-import").append($(".export, .import")) // $("#share").append($(".share")) // $("#revisions").append($(".revisions")) // $("#about").append($("#tuto-faq, #le-logiciel, #jardin")) // Style $("#smallify").on("click", function () { if (currentTheme == "bigcards") { $(this).children("i").removeClass("fa-search-plus").addClass("fa-search-minus") } else { $(this).children("i").removeClass("fa-search-minus").addClass("fa-search-plus") } }) $("#full-page").on("click", function () { if ($(this).children("i").hasClass("fa-expand")) { $(this).children("i").removeClass("fa-expand").addClass("fa-compress") $("#header-bar").hide() } else { $(this).children("i").removeClass("fa-compress").addClass("fa-expand") $("#header-bar").show() } toggleFullScreen() }) /** Mode iframe **/ if (top.location != self.document.location) { $("#header-bar").hide() } // put URL in share input var mainurl = location.toString().split('#')[0] $(".replace-url").val(mainurl) $(".share-iframe").text($(".share-iframe").text().replace('{{replace-url}}', mainurl)) // copy URL to clipboard $("#copyurl").on("click", function (e) { e.preventDefault() var node = document.getElementById("taburl") node.disabled = null node.select() var success = document.execCommand("copy") if (success) { getSelection().removeAllRanges() node.disabled = "disabled" alert("URL du tableau copiée dans votre presse-papier !") } else { alert( "Impossible de copier l'URL du tableau dans votre presse-papier. Veuillez copier son adresse manuellement (Ctrl+C)." ) } }) } }) function go() { var value = document.forms[0].elements["name"].value value = value.replace(/[\/\?&#]/g, "") window.location.href = value return false } $(function () { var headerBarUrl = $("#header-bar").data("url") if (headerBarUrl) { var getJSON = function (url, callback) { var xhr = new XMLHttpRequest() xhr.open("GET", url, true) xhr.responseType = "json" xhr.onload = function () { var status = xhr.status if (status === 200) { callback(null, xhr.response) } else { callback(status, xhr.response) } } xhr.send() } getJSON(headerBarUrl, function (err, data) { if (err !== null) { console.log("Something went wrong: " + err) } else { document.getElementById("header-bar").innerHTML = data.markup var styleElement = document.createElement("style") styleElement.innerHTML = data.style document.getElementById("header-bar").appendChild(styleElement) } }) } }) $(function () { // check if hash used to show informations if (window.location.hash == '#settings' || window.location.hash == '#share') { toggleNav(window.location.hash) } // Toggle Nav on Click $(".toggle-nav").on("click", function () { var target = $(this).attr("href") if (target === '#' || ($('#site-wrapper').hasClass('show-nav') && target == window.location.hash)) { target = false history.replaceState('', '', '#'); } else { history.replaceState('', '', target); } toggleNav(target) return false }) // When nav opened, a click on the canvas hides the menu $("body").on("click", ".show-nav #site-canvas main, .show-nav .main-header", function (e) { history.replaceState('', '', '#'); toggleNav(false) return false }) $(".backgrounds .bg").on("click", function () { if ($(this).hasClass("selected")) { $("body").css("background-image", "none") $(this).removeClass("selected") } else { $(".selected").removeClass("selected") $('.bgurl').val('') $("body").css("background-image", 'url("/' + $(this).attr("src") + '")') $(this).addClass("selected") } }) $('.bgurl').on('change', function() { var url = $(this).val() if (url) { $(".selected").removeClass("selected") $("body").css("background-image", 'url("' + url + '")') } }) }) function toggleNav(target) { if ($("#site-wrapper").hasClass("show-nav") && target === false) { $("#site-wrapper").removeClass("show-nav") } else { $("#share, #settings").hide() if (target !== false) { $(target).show() } $("#site-wrapper").addClass("show-nav") } return false }