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 = $('')
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
}