let {pages, inbox, select, taskOrder, taskFiles, globalTaskFilter, dailyNoteFolder, dailyNoteFormat, done, sort, css, forward, dateFormat, options, section} = input; // Error Handling if (!pages && pages!="") { dv.span('> [!ERROR] Missing pages parameter\n> \n> Please set the pages parameter like\n> \n> `pages: ""`'); return false }; if (dailyNoteFormat) { if (dailyNoteFormat.match(/[|\\YMDWwd.,-: \[\]]/g).length != dailyNoteFormat.length) { dv.span('> [!ERROR] The `dailyNoteFormat` contains invalid characters'); return false }}; // Get, Set, Eval Pages if (pages=="") { var tasks = dv.pages().file.tasks } else { if (pages.startsWith("dv.pages")) { var tasks = eval(pages) } else { var tasks = dv.pages(pages).file.tasks } }; if (!taskFiles) { taskFiles = [...new Set(dv.pages().file.map(f=>f.tasks.filter(t=>!t.completed)).path)].sort(); } else { taskFiles = [...new Set(dv.pagePaths(taskFiles))].sort() }; if (!options) {options = ""}; if (!dailyNoteFolder) {dailyNoteFolder = ""} else {dailyNoteFolder = dailyNoteFolder+"/"}; if (!dailyNoteFormat) {dailyNoteFormat = "YYYY-MM-DD"}; if (!taskOrder) {taskOrder = ["overdue", "due", "scheduled", "start", "process", "unplanned","done","cancelled"]}; if (!sort) {sort = "t=>t.order"}; if (!dateFormat) {dateFormat = "ddd, MMM D"}; if (!select) {select = "dailyNote"}; // Variables var timelineDates = []; var tid = (new Date()).getTime(); var today = moment().format("YYYY-MM-DD"); var dailyNoteRegEx = momentToRegex(dailyNoteFormat); // Set Root const rootNode = dv.el("div", "", {cls: "taskido "+options, attr: {id: "taskido"+tid}}); if (css) { var style = document.createElement("style"); style.innerHTML = css; rootNode.querySelector("span").append(style) }; // Icons var doneIcon = ''; var dueIcon = ''; var scheduledIcon = ''; var startIcon = ''; var overdueIcon = ''; var processIcon = ''; var dailynoteIcon = ''; var unplannedIcon = ''; var taskIcon = ''; var addIcon = ''; var tagIcon = ''; var repeatIcon = ''; var priorityIcon = ''; var fileIcon = ''; var forwardIcon = ''; var alertIcon = ''; var cancelledIcon = ''; // Initialze getMeta(tasks); getTimeline(tasks); getSelectOptions(); setEvents(); function getMeta(tasks) { for (i=0;i= today) { timelineDates.push(moment(dailyNoteMatch[1], dailyNoteFormat).format("YYYY-MM-DD")); happens["cancelled"] = moment(dailyNoteMatch[1], dailyNoteFormat).format("YYYY-MM-DD"); tasks[i].order = taskOrder.indexOf("cancelled"); } else if (dailyNoteMatch) { tasks[i].dailyNote = true; } else if (!dailyNoteMatch) { tasks[i].dailyNote = false; }; // Dataview Tasks while (inlineFields = /\[([^\]]+)\:\:([^\]]+)\]/g.exec(tasks[i].text)) { var inlineField = inlineFields[0]; var fieldKey = inlineFields[1].toLowerCase(); var fieldValue = inlineFields[2]; if ( fieldKey == "due" || fieldKey == "scheduled" || fieldKey == "start" || fieldKey == "completion") { var fieldDate = moment(fieldValue).format("YYYY-MM-DD"); if (tasks[i].completed == false && tasks[i].checked == false) { if ( fieldKey == "due" && fieldDate < today ) { if (forward == true) { happens["overdue"] = fieldDate; happens["overdueForward"] = today; tasks[i].order = taskOrder.indexOf("overdue"); } else { happens["overdue"] = fieldDate; tasks[i].order = taskOrder.indexOf("overdue"); timelineDates.push(fieldDate); }; } else if ( fieldKey == "due" && fieldDate == today ) { happens["due"] = fieldDate; tasks[i].order = taskOrder.indexOf("due"); timelineDates.push(fieldDate); } else if ( fieldKey == "due" && fieldDate > today ) { happens["due"] = fieldDate; tasks[i].order = taskOrder.indexOf("due"); timelineDates.push(fieldDate); }; if ( fieldKey == "scheduled" && fieldDate < today ) { happens["scheduled"] = fieldDate; happens["scheduledForward"] = today; tasks[i].order = taskOrder.indexOf("scheduled"); } else if (fieldKey == "scheduled") { happens["scheduled"] = fieldDate; tasks[i].order = taskOrder.indexOf("scheduled"); timelineDates.push(fieldDate); }; if ( fieldKey == "start" && fieldDate < today ) { happens["start"] = fieldDate; happens["startForward"] = today; tasks[i].order = taskOrder.indexOf("start"); } else if (fieldKey == "start") { happens["start"] = fieldDate; tasks[i].order = taskOrder.indexOf("start"); timelineDates.push(fieldDate); }; } else if (tasks[i].completed == true && tasks[i].checked == true) { if (fieldKey == "completion") { happens["done"] = fieldDate; tasks[i].order = taskOrder.indexOf("done"); }; } else if (tasks[i].completed == false && tasks[i].checked == true && fieldDate >= today) { happens["cancelled"] = fieldDate; tasks[i].order = taskOrder.indexOf("cancelled"); }; }; tasks[i].text = tasks[i].text.replace(inlineField, ""); }; // Tasks Plugin Tasks var dueMatch = taskText.match(/📅 *(\d{4}-\d{2}-\d{2})/); if (dueMatch && tasks[i].completed == false && tasks[i].checked == false) { tasks[i].text = tasks[i].text.replace(dueMatch[0], ""); if ( dueMatch[1] < today ) { if (forward == true) { happens["overdue"] = dueMatch[1]; happens["overdueForward"] = today; tasks[i].order = taskOrder.indexOf("overdue"); } else { happens["overdue"] = dueMatch[1]; tasks[i].order = taskOrder.indexOf("overdue"); timelineDates.push(dueMatch[1]); }; } else if ( dueMatch[1] == today ) { happens["due"] = dueMatch[1]; tasks[i].order = taskOrder.indexOf("due"); timelineDates.push(dueMatch[1]); } else if ( dueMatch[1] > moment().format("YYYY-MM-DD") ) { happens["due"] = dueMatch[1]; tasks[i].order = taskOrder.indexOf("due"); timelineDates.push(dueMatch[1]); }; } else if (dueMatch && tasks[i].completed == true && tasks[i].checked == true) { tasks[i].text = tasks[i].text.replace(dueMatch[0], ""); } else if (dueMatch && tasks[i].completed == false && tasks[i].checked == true && dueMatch[1] >= today) { tasks[i].text = tasks[i].text.replace(dueMatch[0], ""); happens["cancelled"] = dueMatch[1]; tasks[i].order = taskOrder.indexOf("cancelled"); timelineDates.push(dueMatch[1]); }; var scheduledMatch = taskText.match(/⏳ *(\d{4}-\d{2}-\d{2})/); if (scheduledMatch && tasks[i].completed == false && tasks[i].checked == false) { tasks[i].text = tasks[i].text.replace(scheduledMatch[0], ""); if ( scheduledMatch[1] < today ) { happens["scheduled"] = scheduledMatch[1]; happens["scheduledForward"] = today; tasks[i].order = taskOrder.indexOf("scheduled"); } else { happens["scheduled"] = scheduledMatch[1]; tasks[i].order = taskOrder.indexOf("scheduled"); timelineDates.push(scheduledMatch[1]); }; } else if (scheduledMatch && tasks[i].completed == true) { tasks[i].text = tasks[i].text.replace(scheduledMatch[0], ""); }; var startMatch = taskText.match(/🛫 *(\d{4}-\d{2}-\d{2})/); if (startMatch && tasks[i].completed == false && tasks[i].checked == false) { tasks[i].text = tasks[i].text.replace(startMatch[0], ""); if ( startMatch[1] < today ) { happens["start"] = startMatch[1]; happens["startForward"] = today; tasks[i].order = taskOrder.indexOf("start"); } else { happens["start"] = startMatch[1]; tasks[i].order = taskOrder.indexOf("start"); timelineDates.push(startMatch[1]); }; } else if (startMatch && tasks[i].completed == true) { tasks[i].text = tasks[i].text.replace(startMatch[0], ""); }; var doneMatch = taskText.match(/✅ *(\d{4}-\d{2}-\d{2})/); if (doneMatch && tasks[i].completed == true && tasks[i].checked == true) { tasks[i].text = tasks[i].text.replace(doneMatch[0], ""); if (done == true || doneMatch[1] == today) { timelineDates.push(doneMatch[1]); happens["done"] = doneMatch[1]; tasks[i].order = taskOrder.indexOf("done"); }; }; var repeatMatch = taskText.match(/🔁 ?([a-zA-Z0-9, !]+)/) if (repeatMatch) { tasks[i].repeat = repeatMatch[1]; tasks[i].text = tasks[i].text.replace(repeatMatch[0], ""); }; var lowMatch = taskText.includes("🔽"); if (lowMatch) { tasks[i].text = tasks[i].text.replace("🔽",""); tasks[i].priority = "D"; tasks[i].priorityLabel = "low priority"; }; var mediumMatch = taskText.includes("🔼"); if (mediumMatch) { tasks[i].text = tasks[i].text.replace("🔼",""); tasks[i].priority = "B"; tasks[i].priorityLabel = "medium priority"; }; var highMatch = taskText.includes("⏫"); if (highMatch) { tasks[i].text = tasks[i].text.replace("⏫",""); tasks[i].priority = "A"; tasks[i].priorityLabel = "high priority"; }; if (!lowMatch && !mediumMatch && !highMatch) { tasks[i].priority = "C"; } if (globalTaskFilter) { tasks[i].text = tasks[i].text.replaceAll(globalTaskFilter,""); } else { tasks[i].text = tasks[i].text.replaceAll("#task",""); }; // Link Detection while (outerLink = /\[([^\]]+)\]\(([^)]+)\)/g.exec(tasks[i].text)) { tasks[i].text = tasks[i].text.replace(outerLink[0], "" + outerLink[1] + ""); }; while (innerLink = /\[\[([^\]]+)\]\]/g.exec(tasks[i].text)) { tasks[i].text = tasks[i].text.replace(innerLink[0], "" + innerLink[1] + ""); }; // Markdown Highlights while (mark = /\=\=([^\]]+)\=\=/g.exec(tasks[i].text)) { tasks[i].text = tasks[i].text.replace(mark[0], "" + mark[1] + ""); }; // Reminder Syntax var reminderMatch = taskText.match(/⏰ *(\d{4}-\d{2}-\d{2}) *(\d{2}\:\d{2})|⏰ *(\d{4}-\d{2}-\d{2})|(\(\@(\d{4}-\d{2}-\d{2}) *(\d{2}\:\d{2})\))|(\(\@(\d{4}-\d{2}-\d{2})\))/); if (reminderMatch) { tasks[i].text = tasks[i].text.replace(reminderMatch[0], ""); }; tasks[i].happens = happens; }; timelineDates.push(today); timelineDates = [...new Set(timelineDates)].sort(); }; function getRelative(someDate) { let date = moment(someDate); if (moment().diff(date, 'days') >= 1 || moment().diff(date, 'days') <= -1) { return date.fromNow(); } else { return date.calendar().split(' ')[0]; }; }; function getSelectOptions() { // Push daily note and Inbox files const currentDailyNote = dailyNoteFolder + moment().format(dailyNoteFormat) + ".md"; taskFiles.push(currentDailyNote); if (inbox) {taskFiles.push(inbox)}; taskFiles = [...new Set(taskFiles)].sort(); // Loop files const fileSelect = rootNode.querySelector('.fileSelect'); taskFiles.forEach(function(file) { var opt = document.createElement('option'); opt.value = file; var secondParentFolder = file.split("/")[file.split("/").length - 3] == null ? "" : "… / "; var parentFolder = file.split("/")[file.split("/").length - 2] == null ? "" : secondParentFolder + "📂 " + file.split("/")[file.split("/").length - 2] + " / "; var filePath = parentFolder + "📄 " + getFilename(file); opt.innerHTML = filePath; opt.title = file; if (select && file == select) { opt.setAttribute('selected', true); } else if (select && select == "dailyNote" && file == currentDailyNote) { opt.setAttribute('selected', true); }; fileSelect.appendChild(opt); }); }; function setEvents() { rootNode.querySelectorAll('.counter').forEach(cnt => cnt.addEventListener('click', (() => { var activeFocus = Array.from(rootNode.classList).filter(c=>c.endsWith("Filter") && !c.startsWith("today")); if (activeFocus == cnt.id+"Filter") { rootNode.classList.remove(activeFocus); return false; }; rootNode.classList.remove.apply(rootNode.classList, Array.from(rootNode.classList).filter(c=>c.endsWith("Filter") && !c.startsWith("today"))); rootNode.classList.add(cnt.id+"Filter"); }))); rootNode.querySelector('.todayHeader').addEventListener('click', (() => { rootNode.classList.toggle("todayFocus"); })); rootNode.querySelectorAll('.task:not(.star, .add)').forEach(t => t.addEventListener('click', ((e) => { var link = t.getAttribute("data-link"); var line = t.getAttribute("data-line"); var col = t.getAttribute("data-col"); if (e.target.closest(".task .tag")) { // Tag } else if (e.target.closest(".timeline .icon")) { // Check var task = e.target.closest(".task"); var icon = e.target.closest(".timeline .icon"); task.className = "task done"; icon.innerHTML = doneIcon; completeTask(link, line, col); } else { // File openFile(link, line, col); }; }))); rootNode.querySelector('.ok').addEventListener('click', (() => { var filePath = rootNode.querySelector('.fileSelect').value; var newTask = rootNode.querySelector('.newTask').value; if (newTask.length > 1) { try { var abstractFilePath = app.vault.getAbstractFileByPath(filePath); if (abstractFilePath) { app.vault.read(abstractFilePath).then(function(fileText) { app.vault.modify(abstractFilePath, addNewTask(fileText, newTask)); }); } else { app.vault.create(filePath, "- [ ] " + newTask); }; new Notice("New task saved!"); rootNode.querySelector('.newTask').value = ""; rootNode.querySelector('.newTask').focus(); } catch(err) { new Notice("Something went wrong!"); }; } else { rootNode.querySelector('.newTask').focus(); }; })); rootNode.querySelector('.fileSelect').addEventListener('change', (() => { rootNode.querySelector('.newTask').focus(); })); rootNode.querySelector('.newTask').addEventListener('input', (() => { var input = rootNode.querySelector('.newTask'); var newTask = input.value; // Icons if (newTask.includes("due ")) { input.value = newTask.replace("due", "📅") }; if (newTask.includes("start ")) { input.value = newTask.replace("start", "🛫") }; if (newTask.includes("scheduled ")) { input.value = newTask.replace("scheduled", "⏳") }; if (newTask.includes("done ")) { input.value = newTask.replace("done", "✅") }; if (newTask.includes("high ")) { input.value = newTask.replace("high", "⏫") }; if (newTask.includes("medium ")) { input.value = newTask.replace("medium", "🔼") }; if (newTask.includes("low ")) { input.value = newTask.replace("low", "🔽") }; if (newTask.includes("repeat ")) { input.value = newTask.replace("repeat", "🔁") }; if (newTask.includes("recurring ")) { input.value = newTask.replace("recurring", "🔁") }; // Dates if (newTask.includes("today ")) { input.value = newTask.replace("today", moment().format("YYYY-MM-DD")) }; if (newTask.includes("tomorrow ")) { input.value = newTask.replace("tomorrow", moment().add(1, "days").format("YYYY-MM-DD")) }; if (newTask.includes("yesterday ")) { input.value = newTask.replace("yesterday", moment().subtract(1, "days").format("YYYY-MM-DD")) }; // In X days/weeks/month/years var futureDate = newTask.match(/(in)\W(\d{1,3})\W(days|day|weeks|week|month|years|year) /); if (futureDate) { var x = parseInt(futureDate[2]); var unit = futureDate[3]; var date = moment().add(x, unit).format("YYYY-MM-DD[ ]") input.value = newTask.replace(futureDate[0], date); }; // Next Weekday var weekday = newTask.match(/(monday|tuesday|wednesday|thursday|friday|saturday|sunday) /); if (weekday) { var weekdays = ["","monday","tuesday","wednesday","thursday","friday","saturday","sunday"]; const dayINeed = weekdays.indexOf(weekday[1]); if (moment().isoWeekday() < dayINeed) { input.value = newTask.replace(weekday[1], moment().isoWeekday(dayINeed).format("YYYY-MM-DD")); } else { input.value = newTask.replace(weekday[1], moment().add(1, 'weeks').isoWeekday(dayINeed).format("YYYY-MM-DD")); }; }; rootNode.querySelector('.newTask').focus(); })); rootNode.querySelector('.newTask').addEventListener('keyup', ((e) => { if (e.which === 13) { // Enter key rootNode.querySelector('.ok').click(); }; })); rootNode.querySelector('.newTask').addEventListener('focus', (() => { rootNode.querySelector('.quickEntryPanel').classList.add("focus"); })); rootNode.querySelector('.newTask').addEventListener('blur', (() => { rootNode.querySelector('.quickEntryPanel').classList.remove("focus"); })); }; function addNewTask(fileText, newTask) { let newFileText; const newTaskText = "- [ ] " + newTask; if (section != undefined) { const lines = fileText.split("\n"); const index = lines.indexOf(section); if (index != -1) { lines.splice(index + 1, 0, newTaskText); newFileText = lines.join("\n"); return newFileText; } else { var createSection = confirm("Section marker '" + section + "' not found. Would you like to create it?"); if (createSection == true) { newFileText = fileText.replace(/\n+$/, "") + "\n\n" + section + "\n\n" + newTaskText; return newFileText; }; }; }; newFileText = fileText.replace(/\n+$/, "") + "\n\n" + newTaskText; return newFileText; }; function openFile(link, line, col) { app.workspace.openLinkText('', link).then(() => { if (line && col) { try { const view = app.workspace.activeLeaf.getViewState(); view.state.mode = 'source'; // mode = source || preview app.workspace.activeLeaf.setViewState(view); var cmEditor = app.workspace.activeLeaf.view.editor; cmEditor.setSelection({line: parseInt(line), ch: 6},{line: parseInt(line), ch: parseInt(col)}); cmEditor.focus(); } catch(err) { new Notice("Something went wrong!") }; }; }); }; function completeTask(link, line, col) { app.workspace.openLinkText('', link).then(() => { if (line && col) { try { const view = app.workspace.activeLeaf.getViewState(); view.state.mode = 'source'; // mode = source || preview app.workspace.activeLeaf.setViewState(view); var cmEditor = app.workspace.activeLeaf.view.editor; var cmLine = cmEditor.getLine(parseInt(line)); if (cmLine.includes("🔁")) {var addRange = 1} else {var addRange = 0}; cmEditor.setCursor(parseInt(line), parseInt(col)); app.commands.executeCommandById('obsidian-tasks-plugin:toggle-done'); cmEditor.setSelection({line: parseInt(line) + addRange, ch: 6},{line: parseInt(line) + addRange, ch: parseInt(col) + 13}); cmEditor.focus(); } catch(err) { new Notice("Something went wrong!") }; }; }); }; function getFilename(path) { var filename = path.match(/^(?:.*\/)?([^\/]+?|)(?=(?:\.[^\/.]*)?$)/)[1]; return filename; }; function getMetaFromNote(task, metaName) { var meta = dv.pages('"'+task.link.path+'"')[metaName][0]; if (meta) { return meta } else { return "" }; }; function momentToRegex(momentFormat) { momentFormat = momentFormat.replaceAll(".", "\\."); momentFormat = momentFormat.replaceAll(",", "\\,"); momentFormat = momentFormat.replaceAll("-", "\\-"); momentFormat = momentFormat.replaceAll(":", "\\:"); momentFormat = momentFormat.replaceAll(" ", "\\s"); momentFormat = momentFormat.replace("dddd", "\\w{1,}"); momentFormat = momentFormat.replace("ddd", "\\w{1,3}"); momentFormat = momentFormat.replace("dd", "\\w{2}"); momentFormat = momentFormat.replace("d", "\\d{1}"); momentFormat = momentFormat.replace("YYYY", "\\d{4}"); momentFormat = momentFormat.replace("YY", "\\d{2}"); momentFormat = momentFormat.replace("MMMM", "\\w{1,}"); momentFormat = momentFormat.replace("MMM", "\\w{3}"); momentFormat = momentFormat.replace("MM", "\\d{2}"); momentFormat = momentFormat.replace("DDDD", "\\d{3}"); momentFormat = momentFormat.replace("DDD", "\\d{1,3}"); momentFormat = momentFormat.replace("DD", "\\d{2}"); momentFormat = momentFormat.replace("D", "\\d{1,2}"); momentFormat = momentFormat.replace("ww", "\\d{1,2}"); regEx = "/^(" + momentFormat + ")$/"; return regEx; }; function getTimeline(tasks) { var yearNode; var lastYear = null; var containedTypesPerYear = null; for (i=0; iObject.values(t.happens).includes(timelineDates[i].toString())).sort(eval(sort)); var date = moment(timelineDates[i].toString()).format(dateFormat); var weekday = moment(timelineDates[i].toString()).format("dddd"); var year = moment(timelineDates[i].toString()).format("YYYY"); var detailsCls = ""; var content = ""; var containedTypesPerDay = []; // Add Year Section if (year != lastYear) { containedTypesPerYear = []; lastYear = year; yearNode = dv.el("div", "", {cls: "year", attr: {"data-types": ""}}) if (moment().format("YYYY") == year) { yearNode.classList.add("current") }; yearNode.innerHTML = year; rootNode.querySelector("span").appendChild(yearNode); }; // Add Today Information if (timelineDates[i] == today) { detailsCls += "today"; var overdueCount = tasks.filter(t=>t.happens["overdue"]).length; var dueCount = tasksFiltered.filter(t=>t.happens["due"]).length; var startCount = tasksFiltered.filter(t=>t.happens["start"]).length; var scheduledCount = tasksFiltered.filter(t=>t.happens["scheduled"]).length; var doneCount = tasksFiltered.filter(t=>t.happens["done"]).length; var dailynoteCount = tasksFiltered.filter(t=>t.happens["dailynote"]).length; var processCount = tasksFiltered.filter(t=>t.happens["process"]).length; var todoCount = tasksFiltered.filter(t=>!t.completed && !t.happens["overdue"] && !t.happens["unplanned"]).length; var unplannedCount = tasks.filter(t=>t.happens["unplanned"]).length; var allCount = doneCount + todoCount + overdueCount; // Counter var todayContent = "
Today
" todayContent += "
" todayContent += "
" + todoCount + "
To Do
" todayContent += "
" + overdueCount + "
Overdue
" todayContent += "
" + unplannedCount + "
Unplanned
" todayContent += "
" // Quick Entry panel todayContent += "
" todayContent += "
" todayContent += "
" todayContent += "
" content += todayContent; }; tasksFiltered.forEach(function(item) { var file = getFilename(item.path); var header = item.header.subpath; var link = item.link.path.replace("'", "'"); var text = item.text; var posEndLine = item.position.start.line; var posEndCol = item.position.end.col; var info = ""; var color = getMetaFromNote(item, "color"); if (!color) {color = "var(--text-muted)"}; var cls = Object.keys(item.happens).find(key => item.happens[key] === timelineDates[i].toString()).replace("Forward",""); var dailyNote = item.dailyNote; containedTypesPerDay.push(cls); containedTypesPerYear.push(cls); // Handle forwarded tasks to get relative by cls for (h=0;h
" + eval(key+"Icon") + "
" + relative + "
"; }; }; if (item.repeat) { info += "
" + repeatIcon + "
" + item.repeat.replace("🔁", "") + "
"; }; if (item.priorityLabel) { info += "
" + priorityIcon + "
" + item.priorityLabel + "
"; }; info += "
" + fileIcon + "
" + file + " > " + header + "
"; item.tags.forEach(function(tag) { var tagText = tag.replace("#",""); var hexColorMatch = tag.match(/([a-fA-F0-9]{6}|[a-fA-F0-9]{3})\/(.*)/); if (hexColorMatch) { var style = "style='--tag-color:#" + hexColorMatch[1] + ";--tag-background:#" + hexColorMatch[1] + "1a'"; tagText = hexColorMatch[2]; } else { var style = "style='--tag-color:var(--text-muted)'"; }; info += "
" + tagIcon + "
" + tagText + "
"; text = text.replace(tag, ""); }); if (item.completed) { var icon = doneIcon } else { var icon = taskIcon }; if (cls == "overdue") { var icon = alertIcon } else if (cls == "cancelled") { var icon = cancelledIcon }; var task = "
" + icon + "
" + text + "
" + info + "
"; content += task; }); // Set Date Template var date = "
" + content + "
" // Append To Root Node containedTypesPerDay = [...new Set(containedTypesPerDay)].sort(); rootNode.querySelector("span").appendChild(dv.el("div", date, {cls: "details " + detailsCls, attr: {"data-year": year, "data-types": containedTypesPerDay.join(" ")}})); // Set containedTypesPerYear containedTypesPerYear = [...new Set(containedTypesPerYear)].sort() yearNode.setAttribute("data-types", containedTypesPerYear); }; };