var boardsJSON, currentBoard, tags, lists, activeCard, listMax, defaultTextColor = '#f0f0f0'; var tagPalette = ['#900', '#F80', '#DD0', '#090', '#0DD', '#00B', '#80F', '#F08', '#E0E', '#F8F', '#000', '#FFF', '#888']; const getCookie = (cookie) => (document.cookie.match('(^|;)\\s*'+cookie+'\\s*=\\s*([^;]+)')?.pop()||''); function setCookie(cookie, value, del=false) { var date = new Date(); if(del) date.setTime(1); else date.setTime(date.getTime() + (120*24*60*60*1000)); //120 days document.cookie = cookie + '=' + value + '; expires=' + date.toUTCString() + '; SameSite=Lax'; } //BOARD HOT LINK var urlParams = new URLSearchParams(window.location.search); var boardID = urlParams.get('b') || getCookie('bid') || null; var menuState = getCookie('menuState') || 'open'; getBoards(); if(boardID) changeBoard(boardID, true); if(menuState === 'closed' && window.innerWidth > 1000) toggleMenu('close'); populateTagColors(); //POP STATE window.onkeyup = (e) => {if(e.key === 'Escape') {closeCard();}}; window.onpopstate = function(event) { if(event.state && !activeCard) { route('home'); if(event.state !== boardID) changeBoard(event.state, true); } else closeCard(); }; //ROUTE function route(_route) { if(_route === 'deleteBoard') { saveBoardBtn.style.display = delChk.checked ? 'none' : 'block'; delBoardBtn.style.display = delChk.checked ? 'block' : 'none'; return; } activeCard = 0; popupBG.style.display = 'none'; newBoardBox.style.display = 'none'; editBoardBox.style.display = 'none'; importExportBox.style.display = 'none'; viewCardBox.style.display = 'none'; saveBoardBtn.style.display = 'block'; delBoardBtn.style.display = 'none'; delChk.checked = false; if(_route === 'home') return; if(_route === 'newBoard') { popupBG.style.display = 'flex'; newBoardBox.style.display = 'block'; newBoardName.value = null; newbgimgurl.value = null; newBoardName.focus(); } if(_route === 'editBoard') { if(!boardID) return; popupBG.style.display = 'flex'; editBoardBox.style.display = 'block'; boardName.value = currentBoard.name; bgimgurl.value = currentBoard.bgimg; boardName.focus(); } if(_route === 'importExport') { popupBG.style.display = 'flex'; importExportBox.style.display = 'block'; importFile.disabled = false; loader.style.display = 'none'; } if(_route === 'viewCard') { popupBG.style.display = 'flex'; viewCardBox.style.display = 'flex'; tagsBox.innerHTML = ''; cardDescTA.style.display = 'none'; cardDescDiv.style.display = 'block'; addTagBox.style.height = 0; } if(history.state) history.pushState(null, '', ''); } //GET BOARDS function getBoards() { var xhttp = new XMLHttpRequest(); xhttp.onloadend = function() { if(this.status === 200) { boardsJSON = JSON.parse(this.responseText); for(b of boardsJSON) { let option = document.createElement("option"); //select menu option option.text = b.name; option.value = b.id; boardSelect.appendChild(option); let menuBtn = document.createElement("a"); //side menu button menuBtn.href = "?b=" + b.id; menuBtn.setAttribute('onclick', "event.preventDefault();changeBoard('"+b.id+"',false)"); menuBtn.textContent = b.name; boards.appendChild(menuBtn); } if(boardID) { //default board set, changeBoard will/has run currentBoard = boardsJSON.find(e => e.id === boardID); if(currentBoard && lists !== undefined) { //board exists and changeBoard resolved first (race cond) setActiveBoardBtn(); history.replaceState(boardID, '', '?b=' + boardID); document.title = currentBoard.name + ' | Tellor'; main.style.backgroundImage = currentBoard.bgimg ? 'url(' + currentBoard.bgimg + ')' : null; } } } else alert('Error: ' + this.status); } xhttp.open('GET', 'api.php?api=getBoards', true); xhttp.send(); } //NEW BOARD function newBoard() { var bname = newBoardName.value.trim(); if(!bname) return; var imgurl = newbgimgurl.value.trim(); if(imgurl && !URL.canParse(imgurl)) {alert('Invalid URL'); return;} var xhttp = new XMLHttpRequest(); xhttp.onloadend = function() { if(this.status === 200) { let option = document.createElement("option"); //select menu option option.text = bname; option.value = this.responseText; boardSelect.appendChild(option); let menuBtn = document.createElement("a"); //side menu button menuBtn.href = "?b=" + this.responseText; menuBtn.setAttribute('onclick', "event.preventDefault();changeBoard('"+this.responseText+"',false)"); menuBtn.textContent = bname; boards.appendChild(menuBtn); boardsJSON.push({id:this.responseText, name:bname, bgimg:imgurl}); route('home'); changeBoard(this.responseText, false); } else alert('Error: ' + this.status); } xhttp.open('GET', 'api.php?api=newBoard&name=' + encodeURIComponent(bname) + '&imgurl=' + encodeURIComponent(imgurl), true); xhttp.send(); } //EDIT BOARD function editBoard() { var bname = boardName.value; if(!bname) return; var imgurl = bgimgurl.value; if(imgurl && !URL.canParse(imgurl)) {alert('Invalid URL'); return;} var xhttp = new XMLHttpRequest(); xhttp.onloadend = function() { if(this.status === 200) { currentBoard.name = bname; currentBoard.bgimg = imgurl; route('home'); var btn = boards.querySelector('a[href="?b='+boardID+'"'); //side menu button if(btn) btn.textContent = bname; btn = boardSelect.querySelector('option[value="'+boardID+'"'); //select menu option if(btn) btn.textContent = bname; document.title = bname + ' | Tellor'; main.style.backgroundImage = imgurl ? 'url(' + imgurl + ')' : null; } else alert('Error: ' + this.status); } xhttp.open('GET', 'api.php?api=editBoard&bid=' + boardID + '&name=' + encodeURIComponent(bname) + '&imgurl=' + encodeURIComponent(imgurl), true); xhttp.send(); } //CHANGE BOARD function changeBoard(bid, popState) { if(!bid) return; var xhttp = new XMLHttpRequest(); xhttp.onloadend = function() { if(this.status === 200) { var board = JSON.parse(this.responseText); boardID = bid; if(boardsJSON) //skip only if getBoards has not loaded yet (race cond) currentBoard = boardsJSON.find(e => e.id === bid); scrollOrientation('set'); lists = board.lists; render(board.cards); setCookie('bid', bid, false); if(!history.state) //init load. set history.state history.replaceState(bid, '', '?b=' + bid); if(!popState) //nav via menu, not history history.pushState(bid, '', '?b=' + bid); if(window.innerWidth < 1000) //close menu on mobile toggleMenu('open'); //mobile menu state is reversed. so to be closed by default delete board.cards; } else { boardID = currentBoard = main.style.backgroundImage = null; main.innerHTML = 'Error getting board: ' + this.status; } if(boardsJSON) { //skip only if getBoards has not loaded yet (race cond) setActiveBoardBtn(); document.title = currentBoard ? currentBoard.name + ' | Tellor' : 'Tellor'; } } xhttp.open('GET', 'api.php?api=getBoard&bid=' + bid, true); xhttp.send(); } //Set Active Board Btn function setActiveBoardBtn() { boardSelect.value = boardID || ''; var menuBtns = boards.querySelectorAll('a'); for(e of menuBtns) { if(e.search === '?b=' + boardID) e.classList.add('activeBoard'); else e.classList.remove('activeBoard'); } } //Find Card function findCard(cards, pid, start, end) { for(let i = start; i < end; i++) { if(cards[i].parent === pid) return cards[i]; } return false; } //RENDER function render(cards) { main.innerHTML = ''; lists.sort((a,b) => +a.ordr - b.ordr); listMax = 0; if(currentBoard) main.style.backgroundImage = currentBoard.bgimg ? 'url(' + currentBoard.bgimg + ')' : null; //find list boundaries in cards list. this scales better than .find var currentList, clID, clStart = 0; for(let i = 0; i < cards.length; i++) { if(cards[i].list !== clID) { clStart = i; clID = cards[i].list; if(currentList) currentList.end = i; currentList = lists.find(e => e.id === clID); //pointer currentList.start = clStart; } } if(currentList) currentList.end = cards.length; for(l of lists) { //iterate lists listMax = +l.ordr; let newList = document.createElement('div'); newList.classList.add('list'); newList.setAttribute('ondrop', "dragDrop(event)"); newList.setAttribute('ondragleave', "dragLeaveList(event)"); newList.innerHTML = `
${l.name}
+ Add Card
`; main.appendChild(newList); let cardsContainer = document.getElementById('cc' + l.id); let card = findCard(cards, '0', l.start, l.end); //find first card while(card) { let newCard = document.createElement('div'); //card newCard.classList.add('card'); newCard.id = card.id; if(card.description) newCard.classList.add('hasDescription'); if(card.color) newCard.style.color = card.color; newCard.setAttribute('onclick', "viewCard('"+card.id+"')"); newCard.setAttribute('draggable', true); newCard.setAttribute('ondragstart', "dragStart(event)"); newCard.setAttribute('ondragend', "dragEnd(event)"); newCard.setAttribute('ondragenter', "dragEnter(event)"); let tagsDiv = document.createElement('div'); //tags tagsDiv.id = 'tags' + card.id; tagsDiv.classList.add('tags'); let _tags = card.tags?.split(' '); //color tags if(_tags && _tags != 0 && _tags[0] != '') { for(tagColor of _tags) { let colorTag = document.createElement('div'); colorTag.style.background = tagColor; colorTag.setAttribute('color', tagColor); tagsDiv.appendChild(colorTag); } } newCard.appendChild(tagsDiv); newCard.append(card.title); cardsContainer.appendChild(newCard); card = findCard(cards, card.id, l.start, l.end); //find next card } } main.insertAdjacentHTML('beforeend', `
Add List

`); } //ADD LIST function addList() { var lname = newListName.value.trim(); if(!lname) return; lname = lname.replaceAll('<', '<'); var xhttp = new XMLHttpRequest(); xhttp.onloadend = function() { if(this.status === 200) { listMax += 1; lists.push({id:this.responseText, name:lname, color:null, ordr:listMax}); let newList = document.createElement('div'); newList.classList.add('list'); newList.setAttribute('ondrop', "dragDrop(event)"); newList.setAttribute('ondragleave', "dragLeaveList(event)"); newList.innerHTML = `
${lname}
+ Add Card
`; newListColumn.insertAdjacentElement('beforeBegin', newList); newListName.value = null; main.scrollLeft = main.scrollWidth; } if(this.status === 409) outOfSyncWarn(); } xhttp.open('GET', 'api.php?api=addList&bid=' + boardID + '&name=' + encodeURIComponent(lname) + '&pos=' + (listMax + 1), true); xhttp.send(); } //ADD CARD function newCard(listID) { if(!listID) return; var cardsContainer = document.getElementById('cc' + listID); var lastCard = cardsContainer.lastElementChild; var pid = lastCard ? lastCard.id : '0'; var _cardTitle = 'New Card'; var newCard = document.createElement('div'); newCard.id = 'new'; //tmp id newCard.classList.add('card'); newCard.setAttribute('draggable', true); newCard.setAttribute('ondragstart', "dragStart(event)"); newCard.setAttribute('ondragend', "dragEnd(event)"); newCard.setAttribute('ondragenter', "dragEnter(event)"); var newCardTags = document.createElement('div'); newCardTags.id = 'tagsnew'; //tmp id newCardTags.classList.add('tags'); newCard.appendChild(newCardTags); newCard.appendChild(document.createTextNode(_cardTitle)); cardsContainer.appendChild(newCard); cardsContainer.scrollTop = cardsContainer.scrollHeight; var xhttp = new XMLHttpRequest(); xhttp.onloadend = function() { if(this.status === 200) { newCard.id = activeCard.id = this.responseText; //real id newCardTags.id = 'tags' + this.responseText; //real id newCard.setAttribute('onclick', "viewCard('"+this.responseText+"')"); } else { newCard.remove(); if(this.status === 409) outOfSyncWarn(); } } xhttp.open('GET', 'api.php?api=newCard&bid=' + boardID + '&listid=' + listID + '&pid=' + pid + '&title=' + encodeURIComponent(_cardTitle), true); xhttp.send(); viewCard('new'); window.getSelection().selectAllChildren(cardTitle); } //CLOSE POPUPS function closeCard() { if(activeCard) { //save before route to preserve card details if(activeCard.id === 'new') { //retry save until new card id has resolved setTimeout(closeCard, 100); return; } saveCard(); } route('home'); } //VIEW CARD DETAILS function viewCard(cardID) { route('viewCard'); var card = document.getElementById(cardID); cardTitle.textContent = card.textContent; //get title from card tile cardDescTA.value = cardDescDiv.innerHTML = cdate.textContent = mdate.textContent = ''; //clear description var listID = card.parentElement.id.substring(2); var color = card.style.color ? rgb2hex(card.style.color) : defaultTextColor; //card text color cardTextColorPicker.value = color; var cardTags = document.getElementById('tags' + cardID); //get tags from card tile var _tags = [...cardTags.children].map(e => e.attributes.color.value); if(_tags && _tags != 0) { for(tagColor of _tags) { var newTag = document.createElement('div'); newTag.style.background = tagColor; newTag.setAttribute('onclick', "delTag(this,'"+tagColor+"')"); addTagBtn.insertAdjacentElement('beforebegin', newTag); } } var xhttp = new XMLHttpRequest(); xhttp.onloadend = function() { if(this.status === 200) { const descJSON = JSON.parse(this.responseText); cdate.textContent = descJSON.cdate; mdate.textContent = descJSON.mdate; activeCard.description = cardDescTA.value = descJSON.description; parseDescription(); } } if(cardID !== 'new') { xhttp.open('GET', 'api.php?api=getCard&bid=' + boardID + '&listid=' + listID + '&cardid=' + cardID, true); xhttp.send(); } activeCard = {id: cardID, title: card.textContent, color: color, list: listID, description: ''}; } //SAVE CARD DETAILS function saveCard() { if(!activeCard) return; var card = document.getElementById(activeCard.id); var title = cardTitle.innerText.trim(); if(!title) { //revert empty title cardTitle.textContent = activeCard.title; return; } var xhttp = new XMLHttpRequest(); xhttp.onloadend = function() { if(this.status === 200) { card.lastChild.textContent = title; if(activeCard) { activeCard.title = title; activeCard.description = cardDescTA.value; mdate.textContent = new Date().toLocaleDateString("en-gb", {day: 'numeric', month: 'short', year: 'numeric'}); } } } if(activeCard.title != title || (activeCard.description || '') != cardDescTA.value) { //title or descrption changed if(activeCard.id === 'new') { //retry save until new card id has resolved. skip if no change made setTimeout(saveCard, 100); return; } xhttp.open('POST', 'api.php?api=saveCard&bid=' + boardID + '&listid=' + activeCard.list + '&cardid=' + activeCard.id + '&title=' + encodeURIComponent(title), true); xhttp.send(cardDescTA.value); if(cardDescTA.value) card.classList.add('hasDescription'); else card.classList.remove('hasDescription'); } } //EDIT DESCRIPTION function editDescription(event) { //onClick if(event.target.nodeName == 'A') //enable link click w/o starting edit return; if(window.getSelection().anchorOffset - window.getSelection().focusOffset) //allow highlight description w/o starting edit return; cardDescDiv.style.display = 'none'; cardDescTA.style.display = 'block'; cardDescTA.style.height = 0; cardDescTA.style.height = cardDescTA.scrollHeight + 2 + 'px'; //+2 eliminates scrollbar cardDescTA.focus(); } //EDIT DESCRIPTION FINISHED function doneEditDescription() { //onBlur cardDescDiv.style.display = 'block'; cardDescTA.style.display = 'none'; if(!activeCard) return; //saved and closed already parseDescription(); } //CANCEL DESCRIPTION function cancelCard() { cardDescTA.value = activeCard.description; parseDescription(); } //PARSE DESCRIPTION function parseDescription() { var parsedDesc = cardDescTA.value.replaceAll('<', '<'); //html -> text var linkRegex = /(?(?[^\]]+)\]\((?https?:\/\/[^\s)]+)\))|(?https?:\/\/[^\s<>"'()[\]]+)/g; parsedDesc = parsedDesc.replace(linkRegex, (...args) => { var groups = args.at(-1); if(groups.markdown) return `${groups.label}`; return `${groups.rawUrl}`; }); cardDescDiv.innerHTML = parsedDesc; } //DELETE BOARD function deleteBoard() { if(!boardID) return; var xhttp = new XMLHttpRequest(); xhttp.onloadend = function() { if(this.status === 200) { setCookie('bid', 0, true); window.location = '?b='; } else alert('Error: ' + this.status); } if(confirm('DELETE Entire Board?\n' + currentBoard.name)) { xhttp.open('PUT', 'api.php?api=deleteBoard&bid=' + boardID, true); xhttp.send(); } } //ADD TAG function addTag(color) { if(!color) return; var card = document.getElementById(activeCard.id); var preExisting = card.querySelector('div[color="'+color+'"]'); if(preExisting) return; var xhttp = new XMLHttpRequest(); xhttp.onloadend = function() { if(this.status === 200) { var newTag = document.createElement('div'); //view card details tag newTag.style.background = color; newTag.setAttribute('onclick', "delTag(this,'"+color+"')"); addTagBtn.insertAdjacentElement('beforebegin', newTag); newTag = document.createElement('div'); //card tag newTag.style.background = color; newTag.setAttribute('color', color); document.getElementById('tags' + activeCard.id).appendChild(newTag); addTagBox.style.height = 0; } } xhttp.open('GET', 'api.php?api=addTag&bid=' + boardID + '&listid=' + activeCard.list + '&cardid=' + activeCard.id + '&color=' + encodeURIComponent(color), true); xhttp.send(); } //DELETE TAG function delTag(elem, color) { if(!color) return; var card = document.getElementById(activeCard.id); var colorTag = card.querySelector('div[color="'+color+'"]'); if(!colorTag) return; var xhttp = new XMLHttpRequest(); xhttp.onloadend = function() { if(this.status === 200) { elem.remove(); colorTag.remove(); addTagBox.style.height = 0; } else alert('Error: ' + this.status); } xhttp.open('GET', 'api.php?api=delTag&bid=' + boardID + '&listid=' + activeCard.list + '&cardid=' + activeCard.id + '&color=' + encodeURIComponent(color), true); xhttp.send(); } //RENAME LIST function renameList(titleElem) { if(!titleElem) return; var listTitle = titleElem.innerText.trim(); listTitle = listTitle.replaceAll('<', '<'); var listID = titleElem.parentElement.id; var list = lists.find(e => e.id === listID); if(!listTitle) { titleElem.innerText = list.name; return; } if(listTitle.length > 1023) { alert('Error:\nTitle length: ' + listTitle.length + '\nRequired: 1 - 1023'); return; } var xhttp = new XMLHttpRequest(); xhttp.onloadend = function() { if(this.status === 200) { list.name = listTitle; } else { titleElem.innerText = list.name; alert('Error: ' + this.status); } } if(listTitle !== list.name) { //only save if name changed xhttp.open('GET', 'api.php?api=renameList&bid=' + boardID + '&listid=' + listID + '&title=' + encodeURIComponent(listTitle), true); xhttp.send(); } } //MOVE LIST function moveList(listTitle, direction) { if(!listTitle || !direction) return; var listContainer = listTitle.parentElement; var swap = (direction === 'right') ? listContainer.nextElementSibling : listContainer.previousElementSibling; if(!swap || !swap.classList.contains('list') || !listContainer.classList.contains('list')) return; var swapTitle = swap.firstChild; var swapOrdr = swapTitle.getAttribute('ordr'); var listOrdr = listTitle.getAttribute('ordr'); //move list listTitle.setAttribute('ordr', swapOrdr); swapTitle.setAttribute('ordr', listOrdr); var swapDir = (direction === 'right') ? 'beforebegin' : 'afterend'; listContainer.insertAdjacentElement(swapDir, swap); var xhttp = new XMLHttpRequest(); xhttp.onloadend = function() { if(this.status !== 200) { //revert move listTitle.setAttribute('ordr', listOrdr); swapTitle.setAttribute('ordr', swapOrdr); swapDir = (direction !== 'right') ? 'beforebegin' : 'afterend'; listContainer.insertAdjacentElement(swapDir, swap); } if(this.status === 409) outOfSyncWarn(); } xhttp.open('GET', 'api.php?api=moveList&bid=' + boardID + '&lid1=' + listTitle.id + '&lid2=' + swapTitle.id + '&ordr1=' + listOrdr + '&ordr2=' + swapOrdr, true); xhttp.send(); } //DELETE LIST function deleteList(titleElem) { if(!titleElem) return; var listID = titleElem.id; var xhttp = new XMLHttpRequest(); xhttp.onloadend = function() { if(this.status === 200) { titleElem.parentElement.remove(); } else alert('Error: ' + this.status); } if(confirm('Delete List and all cards in it?\n' + titleElem.firstChild.textContent)) { xhttp.open('PUT', 'api.php?api=deleteList&bid=' + boardID + '&listid=' + listID, true); xhttp.send(); } } //CARD COLOR function cardColor(color=false) { color = color ? defaultTextColor : cardTextColorPicker.value; if(color === activeCard.color) return; var card = document.getElementById(activeCard.id); card.style.color = cardTextColorPicker.value = color; var xhttp = new XMLHttpRequest(); xhttp.onloadend = function() { if(this.status === 200) activeCard.color = color; else card.style.color = cardTextColorPicker.value = activeCard.color; } xhttp.open('GET', 'api.php?api=cardColor&bid=' + boardID + '&listid=' + activeCard.list + '&cardid=' + activeCard.id + '&color=' + encodeURIComponent(color), true); xhttp.send(); } //ARCHIVE CARD function archiveCard() { if(!activeCard || activeCard.id === 'new') return; var card = document.getElementById(activeCard.id); if(!card) return; var pid = card.previousElementSibling ? card.previousElementSibling.id : '0'; var xhttp = new XMLHttpRequest(); xhttp.onloadend = function() { if(this.status === 200) { card.remove(); } else { card.style.display = null; viewCard(card.id); } } xhttp.open('GET', 'api.php?api=archiveCard&bid=' + boardID + '&listid=' + activeCard.list + '&cardid=' + activeCard.id + '&pid=' + pid, true); xhttp.send(); card.style.display = 'none'; route('home'); } const rgb2hex = (rgb) => '#' + rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/).slice(1).map((n) => parseFloat(n).toString(16).padStart(2,'0')).join(''); //Populate Tag Colors function populateTagColors() { for(color of tagPalette) { let colorTag = document.createElement('div'); colorTag.style.background = color; colorTag.setAttribute('onclick', "addTag('"+color+"')"); addTagBox.appendChild(colorTag); } } //Show Tags function showTags() { addTagBox.style.height = (addTagBox.style.height != '13px') ? '13px' : 0; } //Out of Sync Warn function outOfSyncWarn() { if(confirm("Page is Out of Sync.\nClick OK to Refresh Page")) { location.reload(); } } //Toggle Menu function toggleMenu(forced=false) { if(forced === 'close') document.body.classList.add('menu-closed'); else if(forced === 'open') document.body.classList.remove('menu-closed'); else document.body.classList.toggle('menu-closed'); if(document.body.classList.contains('menu-closed') && window.innerWidth > 1000) //don't set cookie on mobile. Disallows mobile menu being open by default setCookie('menuState', 'closed', false); else setCookie('menuState', '', true); } //Scroll Orientation function scrollOrientation(rotate) { var orientation = +localStorage.getItem(boardID); orientation = (orientation + (rotate == 'change')) % 3; main.classList.remove('multiRow'); main.classList.remove('columns'); orientationSvg.style.transform = 'none'; if(orientation === 1) { main.classList.add('multiRow'); orientationSvg.style.transform = 'rotate(90deg)'; } if(orientation === 2) { main.classList.add('columns'); orientationSvg.style.transform = 'rotate(135deg)'; } if(orientation) localStorage.setItem(boardID, orientation); else localStorage.removeItem(boardID); } //Export Board function exportBoard() { if(boardID) window.location.href = 'api.php?api=export&bid=' + boardID; } //Import Board function importBoard() { if(importFile.files.length !== 1) return; importFile.disabled = true; loader.style.display = 'inline-block'; var file = importFile.files[0]; var xhttp = new XMLHttpRequest(); xhttp.onloadend = function() { importFile.disabled = false; loader.style.display = 'none'; importFile.value = null; if(this.status === 200) { route('home'); window.location = '?b=' + this.responseText; } else alert('Import Failed'); } xhttp.open('POST', 'api.php?api=import', true); xhttp.send(importFile.files[0]); } //DRAG DROP var offsetX, offsetY, draggedCard, dragster, spacer = document.createElement('div'); spacer.style.margin = '6px 0px 0px 0px'; function dragStart(e) { draggedCard = e.target; dragster = draggedCard.cloneNode(true); dragster.id = ''; dragster.classList.add('dragster'); document.body.appendChild(dragster); dragster.style.width = e.target.getBoundingClientRect().width + 'px'; spacer.style.height = e.target.getBoundingClientRect().height + 'px'; offsetX = e.pageX - e.target.getBoundingClientRect().x; offsetY = e.pageY - e.target.getBoundingClientRect().y; dragster.style.top = (e.pageY - offsetY - 6) + 'px'; dragster.style.left = (e.pageX - offsetX) + 'px'; setTimeout(()=>{ //chrome "bug" wontFix draggedCard.insertAdjacentElement('beforebegin', spacer); draggedCard.style.display = 'none'; },0); } function dragEnd(e) { spacer.remove(); if(dragster) { dragster.remove(); dragster = null; } draggedCard.style.display = null; } function dragEnter(e) { e.preventDefault(); e.target.insertAdjacentElement('beforebegin', spacer); } function dragOverNewCardBtn(e) { e.preventDefault(); e.target.previousElementSibling.insertAdjacentElement('beforeend', spacer); } function dragOver(e) { e.preventDefault(); dragster.style.top = (e.pageY - offsetY - 6) + 'px'; dragster.style.left = (e.pageX - offsetX) + 'px'; } function dragLeaveList(e) { if(e.relatedTarget.id === 'main') spacer.remove(); } //MOVE CARD function dragDrop(e) { e.preventDefault(); e.stopPropagation(); var lastInList = !spacer.nextElementSibling; var dropTarget = lastInList ? spacer.previousElementSibling : spacer.nextElementSibling; var dlid = spacer.parentElement.id.substr(2); var stid = draggedCard.id; var dtid = dropTarget ? dropTarget.id : '0'; var slist = draggedCard.parentElement; var slid = slist.id.substr(2); var voidTile = draggedCard.nextElementSibling; var vtid = voidTile ? voidTile.id : '0'; dragEnd(e); if(draggedCard === dropTarget) return; var spid = draggedCard.previousElementSibling ? draggedCard.previousElementSibling.id : '0'; var dpid = (dropTarget && dropTarget.previousElementSibling) ? dropTarget.previousElementSibling.id : '0'; if(!lastInList && dpid == stid) return; if(lastInList) { dpid = dtid; dtid = '0'; } //move card if(lastInList) { //moved to last in list if(dropTarget) //at least 1 card in list dropTarget.insertAdjacentElement('afterend', draggedCard); else //dest list is empty document.getElementById('cc' + dlid).appendChild(draggedCard); } else //move to anywhere but last/only dropTarget.insertAdjacentElement('beforebegin', draggedCard); var xhttp = new XMLHttpRequest(); xhttp.onloadend = function() { if(this.status !== 200) { //revert move if(voidTile) voidTile.insertAdjacentElement('beforebegin', draggedCard); else if(spid !== '0') document.getElementById(spid).insertAdjacentElement('afterend', draggedCard); else slist.appendChild(draggedCard); } if(this.status === 409) outOfSyncWarn(); } xhttp.open('GET', 'api.php?api=moveCard&bid=' + boardID + '&stid=' + stid + '&dtid=' + dtid + '&spid=' + spid + '&dpid=' + dpid + '&slid=' + slid + '&dlid=' + dlid + '&vtid=' + vtid, true); xhttp.send(); }