/** Version 1.0 **/ renderTimeline(); async function renderTimeline() { // Initialize libraries and context var vis = require('vis.min.js'); //var moment = require('moment.min.js'); var container = document.getElementById("timeline"); var menu = document.getElementById("menu"); // Get widget attribute values and fetch notes from defined tags const events = await api.runOnServer(() => { const parentNote = api.startNote.getParentNotes()[0]; // fetch what labels to look for from widget attributes // event notes (start/end) const event_note_label_start = parentNote.getLabelValue('event_label_start').toString(); const event_note_label_end = parentNote.getLabelValue('event_label_end').toString(); // agent notes (birth/death) const person_note_label_start = parentNote.getLabelValue('person_label_start').toString(); const person_note_label_end = parentNote.getLabelValue('person_label_end').toString(); // date types (categories) const event_label_type = parentNote.getLabelValue('event_label_type').toString(); // fetch notes with those labels const event_notes = api.getNotesWithLabel(event_note_label_start); const person_notes_birth = api.getNotesWithLabel(person_note_label_start); const person_notes_death = api.getNotesWithLabel(person_note_label_end); const events = []; for (const note of event_notes) { const id = note.noteId; const content = note.title; const start = note.getLabelValue(event_note_label_start); const end = note.getLabelValue(event_note_label_end); const group = note.getLabelValue(event_label_type) ? note.getLabelValue(event_label_type) : ( parentNote.getLabelValue('event_type_default') ? parentNote.getLabelValue('event_type_default') : 'default'); if (content && start) { events.push({ id, content, start, end, group }); } } // make a 'birth' event for each person note with start date for (const note of person_notes_birth) { const id = note.noteId; const start = note.getLabelValue(person_note_label_start); const group = note.getLabelValue(event_label_type) ? note.getLabelValue(event_label_type) : ( parentNote.getLabelValue('event_type_default') ? parentNote.getLabelValue('event_type_default') : 'default'); if (start) { const content = note.title + ' (Birth)'; events.push({ id, content, start, group }); } } // make a 'death' event for each person note with end date for (const note of person_notes_death) { const id = note.noteId; const start = note.getLabelValue(person_note_label_end); const group = note.getLabelValue(event_label_type) ? note.getLabelValue(event_label_type) : ( parentNote.getLabelValue('event_type_default') ? parentNote.getLabelValue('event_type_default') : 'default'); if (start) { const content = note.title + ' (Death)'; events.push({ id, content, start, group }); } } return events; }); // Get event types, make groups const types = await api.runOnServer(() => { const parentNote = api.startNote.getParentNotes()[0]; const event_type_list = parentNote.getLabelValue('event_type_list').toString().split(';'); const types = []; var i = 1; for (const type of event_type_list) { // type format each type seperated by ; // id(string), order(int), label(string), color(string), visible(bool), type(string), forceGroup(int) types.push({ id: type.split(',')[0].toString(), order: type.split(',')[1] ? (parseInt(type.split(',')[1],10)) : i, content: type.split(',')[2] ? (type.split(',')[2].toString()) : type.split(',')[0].toString(), color: type.split(',')[3] ? (type.split(',')[3].toString()) : null, visible: type.split(',')[4] ? (type.split(',')[4] === 'true') : true, type: type.split(',')[5] ? type.split(',')[5].toString() : null, forceGroup: type.split(',')[6] ? (type.split(',')[6].toString()) : null }); i++; } return types }); // merge the default group with attribute-added groups var groups = new vis.DataSet([ {id:'default', order: 100, content:'', color:'white', visible:true, type:null, forceGroup:null}, ...types ]); // Create a DataSet for all event items var items = new vis.DataSet(); for (var i = 0; i < events.length; i++) { // get date values and note link var note_id = events[i].id; var note_link = await api.createNoteLink(note_id); var event_content = events[i].content; note_link[0].firstChild.innerText = event_content; var event_start = vis.moment(new Date(events[i].start), 'YYYY-MM-DD-hh:mm:ss'); var event_end = events[i].end ? vis.moment(new Date(events[i].end), 'YYYY-MM-DD-hh:mm:ss') : null; // set event group var event_group = events[i].group ? (groups.get({filter: function (item) { return (item.id === events[i].group) }})[0] ? groups.get({filter: function (item) { return (item.id === events[i].group) }})[0] : groups.get({filter: function (item) { return (item.id === 'default') }})[0]) : groups.get({filter: function (item) { return (item.id === 'default') }})[0]; // fill event data items.add({ id: i, content: note_link[0].firstChild, start: event_start, end: event_end, group: event_group.forceGroup ? event_group.forceGroup : event_group.id, type: (event_group.type=='box' || event_group.type=='point' || event_group.type=='range' || event_group.type=='background') ? event_group.type : null, className: event_group.id, style: `background-color:${event_group.color};border-color:${event_group.color};` }); } // Timeline options const timeline_options = await api.runOnServer(() => { const parentNote = api.startNote.getParentNotes()[0]; const timeline_start = parentNote.getLabelValue('timeline_start'); const timeline_end = parentNote.getLabelValue('timeline_end'); const timeline_present = parentNote.getLabelValue('timeline_present'); return {timeline_start, timeline_end, timeline_present}; }); var options = { clickToUse: false, showCurrentTime: true, height: '95%', start: timeline_options.timeline_start, end: timeline_options.timeline_end }; // Set timeline wrapper height container.parentNode.style.height = '100%'; // Fix timeline display container.parentNode.parentNode.parentNode.parentNode.style.display = 'initial'; // Create Timeline var timeline = new vis.Timeline(container, items, groups, options); // Add present time marker based on "timeline_present" label timeline.setCurrentTime(timeline_options.timeline_present); // Add event listener to activate a day note on click timeline.on("click", async (e) => { const timelineHeight = timeline.dom.centerContainer.getBoundingClientRect().height; if (e.y <= timelineHeight) { // ignore clicks on the timeline return; } const effectiveY = e.y - timelineHeight; if (effectiveY > timeline.dom.bottom.children[0].children[0].getBoundingClientRect().height) { // ignore clicks on the month axis return; } const day = e.time.toISOString().substr(0, 10); const todayNote = await api.getDayNote(day); api.waitUntilSynced(); await api.openTabWithNote(todayNote.noteId, true); }); // Create UI menu buttons from added groups (that are not forced into another group) menu.innerHTML = ''; const toggle_groups = groups.get({filter:function(item){return(item.forceGroup===null && item.id!=='default')}}); for (const group of toggle_groups) { var button = document.createElement('input'); button.type = 'button'; button.id = `toggle-${group.id}`; button.value = group.content; button.className = (group.visible===true) ? 'toggled' : ''; menu.appendChild(button); } // Bind toggle buttons for (const group of toggle_groups) { document.getElementById(`toggle-${group.id}`).onclick = function(){ toggleGroupVisibility(group.id); } } // Function to toggle group visibility function toggleGroupVisibility(group_id){ var visibility = groups.get({filter:function(item){return(item.id==group_id)}})[0].visible; groups.update({id: group_id, visible: !visibility}); console.log(document.getElementById(`toggle-${group_id}`)); document.getElementById(`toggle-${group_id}`).className = (!visibility===true) ? 'toggled' : ''; timeline.setGroups(groups); timeline.redraw(); } }