diff --git a/functionsMonth.ts b/functionsMonth.ts index faad04d..f96c5a2 100644 --- a/functionsMonth.ts +++ b/functionsMonth.ts @@ -2,7 +2,7 @@ import ReleaseTimeline from "main"; import { getAPI, isPluginEnabled, DataviewAPI } from "obsidian-dataview"; import { moment } from "obsidian"; import { create } from "domain"; -import { createErrorMsg, createRowSeparator, createRowSeparatorYearMonth, createRowYear, createRowItem, createNewRow, parseQuerySortOrder } from "helperFunctions"; +import { createErrorMsg, createRowSeparator, createRowSeparatorYearMonth, createRowSeparatorWeek, createRowYear, createRowItem, createNewRow, parseQuerySortOrder } from "helperFunctions"; export default class MonthTimeline { @@ -12,277 +12,365 @@ export default class MonthTimeline { this.plugin = plugin; } - async renderTimelineMonth(content: string) { - - const dv = getAPI(); + async renderTimelineMonth(content) { + //get data from dataview + const dv = getAPI(); if ( typeof dv == 'undefined' ) { return createErrorMsg('Dataview is not installed. The Release Timeline plugin requires Dataview to properly function.'); } + + //filter data to remove non-dates + let dvResults; + let dvResultsFiltered; + try { + dvResults = await dv.query(content); + let dvResultsValues = dvResults.value.values; - var sortOrder = parseQuerySortOrder(content, this.plugin); - - //get results from dataview - try { - - var results; - var results0 = await dv.query(content); - let a = results0.value.values; - //filter out null years - let b = a.filter(x => typeof x[1] !== 'undefined' && x[1] !== null); + let a = dvResultsValues.filter( x => typeof x[1] !== 'undefined' && x[1] !== null ); + //filter out years without a month - b = b.filter(x => !(typeof(x[1]) == 'number') ); + let b = a.filter( x => !(typeof(x[1]) == 'number') ); - b.forEach(x => x[1] = moment( x[1].toString() ).format('YYYY-MM')) - b = b.filter(x => x[1] != "Invalid date") + //filter out incorrect dates + dvResultsFiltered = b.filter( x => moment( x[1].toString() ).format('YYYY-MM') != "Invalid date" ); + + //convert all to moment + dvResultsFiltered.forEach( x => x[1] = moment(x[1].toString()) ); - //parse file name from path, replace path, insert to notes with empty alias - b.forEach(x => x[0] = x[0].path.match(/([^\/]+(?=\.)).md/)[1]); - b.forEach(x => x[2]==null ? x[2]=x[0] : 1); + } + catch(error) { + return createErrorMsg("Error from dataview: " + error.message); + } + + //transform data to the new structure + const dvResultsTransformed = this.transformDvResults(dvResultsFiltered); + if (dvResultsTransformed.length == 0) { return createErrorMsg("No results"); } - //group by month - let monthGroup = []; + //fill in empty months + const fullMonthTimelineData = this.fillInMissingMonths(dvResultsTransformed); + + //collapse empty years + const collapsedEmptyYearsTimelineData = this.collapseEmptyYears(fullMonthTimelineData); - for(let i=0; i e.yearMonth === item); - if (ind > -1) { - monthGroup[ind].values.push([ b[i][0], b[i][2] ]) - } - else { - monthGroup.push( {'yearMonth': item, 'values': [ [b[i][0], b[i][2]] ] } ) + //mark rows with multiple items which will need separators + const markedSeparatorsTimelineData = this.markSeparators(sortedTimelineData); + + //render + const renderedTimeline = this.renderTimeline(markedSeparatorsTimelineData); + + return renderedTimeline; + + } + + transformDvResults(dvResults) { + + let transformedResults = []; + + dvResults.forEach(item => { + + //const datePart = item[1].c; + //const yearPart = datePart.year; + //const monthPart = datePart.month - 1; + //const dayPart = datePart.day; + //const momentDate = moment( { year: yearPart, month: monthPart, day: dayPart } ); + const momentDate = item[1]; + + const newYear = moment(momentDate).format('Y'); + const newMonth = moment(momentDate).format('Y-MM'); + const newMonthDisplay = moment(momentDate).format('MMM'); + + const fileName = item[0].path.match(/([^\/]+(?=\.)).md/)[1]; + const aliasName = item[2] === null || item[2] === undefined ? fileName : item[2]; + + const pageObject = { + fileName: fileName, + aliasName: aliasName, + date: momentDate.format('YYYY-MM-DD') + }; + + let element = transformedResults.find(e => e.month === newMonth); + if (element) { + element.contents.push(pageObject); + } + else { + let newMonthObject = { + year: newYear, + month: newMonth, + monthDisplay: newMonthDisplay, + contents: [ pageObject ], + collapsed: false, + separator: false } + + //newMonthObject.monthDisplay = this.setMonthFormatting(newMonthObject); + + transformedResults.push(newMonthObject); } + + }) + + return transformedResults; + } + + fillInMissingMonths(contentData) { + + //insert empty weeks + let filledInData = this.insertEmptyMonthsCollapsedNo(contentData); + + return filledInData; + + } + + insertEmptyMonthsCollapsedNo(contentData) { + + let existingMonths = contentData.map(item => item.month).sort(); + const minMonth = moment( existingMonths[0] ); + const maxMonth = moment( existingMonths[ existingMonths.length - 1 ] ); + + for (let month = minMonth; month.isSameOrBefore(maxMonth); month.add(1, 'months')) { - //group by year - let yearMonthGroup = []; + const monthFormatted = month.format('Y-MM'); - for(let j=0; j e.year === item); - if (ind > -1) { - yearMonthGroup[ind].months.push( monthGroup[j] ) - } - else { - yearMonthGroup.push( {'year': item, 'months': [monthGroup[j]] } ); + const newMonthObject = { + year: newYear, + month: newMonth, + monthDisplay: newMonthDisplay, + contents: [], + collapsed: false, + separator: false } + contentData.push(newMonthObject); } + } + + return contentData; + } + + collapseEmptyYears(fullMonthTimelineData) { + + let minYear = fullMonthTimelineData.reduce((min, item) => item.year < min ? item.year : min, fullMonthTimelineData[0].year); + let maxYear = fullMonthTimelineData.reduce((max, item) => item.year > max ? item.year : max, fullMonthTimelineData[0].year); + + for (let year = moment(minYear); year.isSameOrBefore(moment(maxYear)); year.add(1, 'years')) { + + const yearFormatted = year.format('Y'); + const yearData = fullMonthTimelineData.filter(elem => elem.year == yearFormatted); + const itemsInYear = yearData.reduce((acc, item) => acc + item.contents.length, 0); - results = sortOrder == 'asc' ? yearMonthGroup.sort((a, b) => Number(a.year) - Number(b.year)) : yearMonthGroup.sort((a, b) => Number(b.year) - Number(a.year)); + if (itemsInYear == 0) { + fullMonthTimelineData = fullMonthTimelineData.filter(elem => elem.year != yearFormatted); + + const newYear = year.format('Y'); + const newMonth = year.format('Y-MM') + + const newObject = { + year: newYear, + month: newMonth, + monthDisplay: '', + contents: [], + collapsed: true, + separator: false + } + + fullMonthTimelineData.push(newObject); + } } - catch(error) { - return createErrorMsg("Error from dataview: " + error.message) + + return fullMonthTimelineData; + + } + + sortTimelineData(fullMonthTimelineData, sortOrder) { + + if (sortOrder == 'asc') { + //sort months + fullMonthTimelineData.sort( (a,b) => a.month.localeCompare(b.month) ); + + //sort data within the months + fullMonthTimelineData.forEach(item => { + item.contents.sort( (a,b) => a.date.localeCompare(b.date) ); + }) } - if (results.length == 0) { - return createErrorMsg("No results"); + if (sortOrder == 'desc') { + //sort weeks + fullMonthTimelineData.sort( (a,b) => b.month.localeCompare(a.month) ); + + //sort data within the months + fullMonthTimelineData.forEach(item => { + item.contents.sort( (a,b) => b.date.localeCompare(a.date) ); + }) } - else { - return this.createTimelineTableMonth(results, sortOrder); + + return fullMonthTimelineData; + + } + + markSeparators(timelineData) { + //get scope of year + //go through the month items in a year + //if prev item or next item has data - mark as separator + + for (let i = 0; i 1 && (currMonthNbItems > 0 || minusTwoMonthNbItems > 0) ) + || ( currMonthNbItems > 1 && (minusOneMonthNbItems > 0 || plusOneMonthNbItems > 0) ); + + if ( condition ) { + timelineData[i].separator = true; + } + } - }; + return timelineData; - createTimelineTableMonth(timeline, sortOrder) { + } - const newTbl = document.createElement("table"); - newTbl.classList.add("release-timeline") + renderTimeline(sortedTimelineData) { - //create table body - let newTbody = document.createElement("tbody"); - - //check to create an empty row separator - let isLongRow = 0; + let rlsTbody = document.createElement("tbody"); + + let prevYearCollapsed = undefined; + + //loop to render years + while(sortedTimelineData.length != 0) { + const currYear = sortedTimelineData[0].year; + + let currYearCollapsed = sortedTimelineData[0].collapsed; + + //add separator + if (! ((currYearCollapsed == true && prevYearCollapsed == true) || prevYearCollapsed == undefined) ) { + const yearBorder = createRowSeparatorYearMonth('border'); + const yearBorder2 = createRowSeparatorYearMonth('no-border'); + rlsTbody.appendChild(yearBorder); + rlsTbody.appendChild(yearBorder2); + } + + prevYearCollapsed = currYearCollapsed; - let prevYearExists = false; - let nextYearExists = timeline[1] !== undefined ? true : false; - let nextYear = timeline[1] == undefined ? undefined : Number(timeline[1].year); + const timelineDataFilteredByYear = sortedTimelineData.filter(elem => elem.year == currYear); + sortedTimelineData = sortedTimelineData.filter(elem => elem.year != currYear); - timeline.forEach( (item, index) => { + if (currYearCollapsed == false) { - nextYearExists = timeline[index+1] !== undefined ? true : false; - nextYear = timeline[index+1] == undefined ? undefined : Number(timeline[index+1].year); + const htmlYearData = this.renderMonthsInYear(timelineDataFilteredByYear); + const yearRowSpanNb = this.calculateRowSpanYear(htmlYearData); - newTbody = this.renderYearMonth(item, prevYearExists, nextYearExists, nextYear, newTbody, sortOrder); + let htmlYearTr = createEl("tr"); + let htmlYearTh = createEl("th", {cls: "year-header", text: currYear}); + htmlYearTh.setAttribute("scope", "row"); + htmlYearTh.setAttribute("rowspan", yearRowSpanNb); + htmlYearTr.appendChild(htmlYearTh); - prevYearExists = true; + rlsTbody.appendChild(htmlYearTr); + rlsTbody.appendChild(htmlYearData); + } + else { + let htmlYearTr = createEl("tr"); + let htmlYearTd = createEl("td"); + let htmlYearTh = createEl("th", {cls: "year-nonexisting", text: currYear}); + + htmlYearTr.appendChild(htmlYearTd); + htmlYearTr.appendChild(htmlYearTh); + + rlsTbody.appendChild(htmlYearTr); + } - }); + + } + + const rlsTbl = document.createElement("table"); + rlsTbl.classList.add("release-timeline"); + rlsTbl.appendChild(rlsTbody); - newTbl.appendChild(newTbody); + return(rlsTbl); - return newTbl; - /* - [ - { year: 2020, months: [ - { yearMonth: "Aug", values: ["Brothers", "Bastion"] }, - { yearMonth: "Sep", values: ["Pyre"] }, - { yearMonth: "Nov", values: ["Edith Finch"] } ] - }, - ]; - - */ } - renderYearMonth(item, prevYearExists, nextYearExists, nextYear, newTbody, sortOrder) { - - newTbody.appendChild(createRowSeparatorYearMonth('no-border')); - - let currentYear = item.year; - - if(sortOrder == 'desc') { - var firstMonth = moment.max(...item.months.map(o => moment(o.yearMonth))); - var lastMonth = moment.min(...item.months.map(o => moment(o.yearMonth))); - } - else if (sortOrder == 'asc') { - var firstMonth = moment.min(...item.months.map(o => moment(o.yearMonth))); - var lastMonth = moment.max(...item.months.map(o => moment(o.yearMonth))); - } - - - if(prevYearExists) { - if(sortOrder == 'asc' && firstMonth.format('MM') != '01') { firstMonth = moment([currentYear, 0]); } - if(sortOrder == 'desc' && firstMonth.format('MM') != '12') { firstMonth = moment([currentYear, 11]); } - }; - - if(nextYearExists) { - if(sortOrder == 'asc' && lastMonth.format('MM') != '12') { lastMonth = moment([currentYear, 11]); } - if(sortOrder == 'desc' && lastMonth.format('MM') != '01') { lastMonth = moment([currentYear, 0]); } - }; - - let nbRows = Math.abs(firstMonth.diff(lastMonth, 'months'))+1; - - - for(let q=0; q1){ - nbRows += item.months[q].values.length - 1; - } - } - nbRows += 1; - - let monthDiff = Math.abs(firstMonth.diff(lastMonth, 'months')); - let iterator = sortOrder == 'asc' ? 1 : -1; - - let isLongRow0 = false; - let ii = moment(firstMonth); - for (let qq = 0; qq<=monthDiff; qq++) { - - let ind2 = item.months.findIndex(e => e.yearMonth === ii.format('YYYY-MM')); - if(isLongRow0) {nbRows+=1}; - - if(ind2 > -1) { - isLongRow0 = false; - if(item.months[ind2].values.length == 1) {} - else { - if(qq!=0){nbRows+=1}; - isLongRow0 = true; - } - } - else {isLongRow0 = false;} - - ii.add(iterator , 'months'); - }; - - - let yearRow = createRowYear( { val: currentYear, cls: 'year-header', rowspanNb: nbRows } ); - const newYearRow = createNewRow(yearRow); - newTbody.appendChild(newYearRow); - - let i = moment(firstMonth); - - let isLongRow = false; - - for (let q = 0; q<=monthDiff; q++) { - - let ind = item.months.findIndex(e => e.yearMonth === i.format('YYYY-MM')); - - if(isLongRow) {newTbody.appendChild(createRowSeparator())}; - - //if month exists, create real records - if(ind > -1) { - isLongRow = false; - - //create single rows - if(item.months[ind].values.length == 1) { - const rowYear = createRowYear( { val: i.format('MMM'), cls: 'year-existing', rowspanNb: 1 } ); - const rowItem = createRowItem( { fileName: item.months[ind].values[0][0], fileAlias: item.months[ind].values[0][1] } ); - const newRow = createNewRow(rowYear, rowItem); - - newTbody.appendChild(newRow); - } - //create multiple value rows - else { - if(q!=0) {newTbody.appendChild(createRowSeparator())}; - isLongRow = true; - - const rowYear = createRowYear( { val: i.format('MMM'), cls: 'year-existing', rowspanNb: item.months[ind].values.length } ); - const rowItem = createRowItem( { fileName: item.months[ind].values[0][0], fileAlias: item.months[ind].values[0][1], cls: "td-first" } ); - const newRow = createNewRow(rowYear, rowItem); - newTbody.appendChild(newRow); - - for (let j = 1; j1) { - newTbody = this.createEmptyYears(newTbody, yearDiff2, sortOrder, currentYear); - } - - return newTbody; - - } - - createEmptyYears(newTbody, yearDiff, sortOrder, currentYear) { - newTbody.appendChild(createRowSeparatorYearMonth('no-border')); - - for(let j = 1; j { + + const currMonthText = monthData.monthDisplay; + const currMonthHasData = monthData.contents.length; + + const renderSeparator = monthData.separator; + + let htmlMonthTr = createEl("tr"); + + //render separator for months with multiple items + if (renderSeparator) { + let newSeparator = createRowSeparatorYearMonth('no-border'); + yearContainer.appendChild(newSeparator); + } + + let htmlMonthTh; + if (currMonthHasData == 0) { + htmlMonthTh = createEl("th", {cls: "year-nonexisting", text: currMonthText}); + } + else { + htmlMonthTh = createEl("th", {cls: "year-existing", text: currMonthText}); + } + + const monthRowSpanNb = this.calculateRowSpanMonth(monthData); + htmlMonthTh.setAttribute("scope", "row"); + htmlMonthTh.setAttribute("rowspan", monthRowSpanNb); + + htmlMonthTr.appendChild(htmlMonthTh); + yearContainer.appendChild(htmlMonthTr); + + const createBulletPoints = monthData.contents.length; + + monthData.contents.forEach(monthEvent => { + //create event row + const rowItem = createRowItem( { fileName: monthEvent.fileName, fileAlias: monthEvent.aliasName } ); + if (createBulletPoints > 1) { + rowItem.addClass('td-next'); + } + const newRow = createNewRow(rowItem); + + //insert event row + yearContainer.appendChild(newRow); + }) + + }) + + return yearContainer; + + } + + /****************/ + + calculateRowSpanYear(htmlYear) { + var trCount = htmlYear.querySelectorAll('tr').length + 1; + return trCount; + } + + calculateRowSpanMonth(dataMonth) { + let distinctItems = dataMonth.contents.length; + + return distinctItems + 1; + } + +} \ No newline at end of file diff --git a/functionsWeek.ts b/functionsWeek.ts index 0c3d599..ade4ba9 100644 --- a/functionsWeek.ts +++ b/functionsWeek.ts @@ -25,7 +25,18 @@ export default class WeekTimeline { dvResults = await dv.query(content); let dvResultsValues = dvResults.value.values; - dvResultsFiltered = dvResultsValues.filter( item => item[1].constructor.name == 'DateTime' ) + //filter out null years + let a = dvResultsValues.filter( x => typeof x[1] !== 'undefined' && x[1] !== null ); + + //filter out years without a date + let b = a.filter( x => !(typeof(x[1]) == 'number') ); + + //filter out incorrect dates + dvResultsFiltered = b.filter( x => moment( x[1].toString() ).format('YYYY-MM') != "Invalid date" ); + + //convert all to moment + dvResultsFiltered.forEach( x => x[1] = moment(x[1].toString()) ); + } catch(error) { return createErrorMsg("Error from dataview: " + error.message); @@ -54,11 +65,12 @@ export default class WeekTimeline { dvResults.forEach(item => { - const datePart = item[1].c; - const yearPart = datePart.year; - const monthPart = datePart.month - 1; - const dayPart = datePart.day; - const momentDate = moment( { year: yearPart, month: monthPart, day: dayPart } ); + //const datePart = item[1].c; + // const yearPart = datePart.year; + // const monthPart = datePart.month - 1; + // const dayPart = datePart.day; + // const momentDate = moment( { year: yearPart, month: monthPart, day: dayPart } ); + const momentDate = item[1]; const momentDateThursday = moment(momentDate).isoWeekday(4); const newYear = momentDateThursday.format('Y'); diff --git a/functionsYear.test.ts b/functionsYear.test.ts deleted file mode 100644 index 166c370..0000000 --- a/functionsYear.test.ts +++ /dev/null @@ -1,5 +0,0 @@ -const sumf = require("./functionsYear"); - -test("scenario 1", () => { - expect (sumf(1,2)).toBe(3); -}) diff --git a/functionsYear.ts b/functionsYear.ts index f52063c..c496fb2 100644 --- a/functionsYear.ts +++ b/functionsYear.ts @@ -2,184 +2,14 @@ import ReleaseTimeline from "main"; import { getAPI, isPluginEnabled, DataviewAPI } from "obsidian-dataview"; import { moment } from "obsidian"; import { create } from "domain"; -import { createErrorMsg, createRowSeparator, createRowSeparatorYearMonth, createRowYear, createRowItem, createNewRow, parseQuerySortOrder } from "helperFunctions"; +import { createErrorMsg, createRowSeparator, createRowSeparatorYearMonth, createRowSeparatorWeek, createRowYear, createRowItem, createNewRow, parseQuerySortOrder } from "helperFunctions"; -export function sumf(num1, num2) { - return num1+num2; -} +export default class MonthTimeline { -export default class YearTimeline { - plugin: ReleaseTimeline; constructor(plugin: ReleaseTimeline) { this.plugin = plugin; } - async renderTimeline(content: string) { - - const dv = getAPI(); - - if ( typeof dv == 'undefined' ) { return createErrorMsg('Dataview is not installed. The Release Timeline plugin requires Dataview to properly function.'); } - - var sortOrder = parseQuerySortOrder(content, this.plugin); - - //get results from dataview - try { - - var results; - var results0 = await dv.query(content); - let a = results0.value.values; - - //filter out null years - let b = a.filter(x => typeof x[1] !== 'undefined' && x[1] !== null); - - //convert date values to years, get rid of non-date values like character strings - b.forEach(x => x[1] = moment( x[1].toString(), 'Y' ).format('Y')) - b = b.filter(x => x[1] != "Invalid date") - - //parse file name from path, replace path, insert to notes with empty alias - b.forEach(x => x[0] = x[0].path.match(/([^\/]+(?=\.)).md/)[1]); - b.forEach(x => x[2]==null ? x[2]=x[0] : 1); - - //group by year - results = dv.array(b); - results = results.groupBy(x => x[1]); - - //sort by sort order - results = results.sort(k => k.key, sortOrder); - - } - catch(error) { - return createErrorMsg("Error from dataview: " + error.message) - } - - if (results.length == 0) { - return createErrorMsg("No results"); - } - else { - return this.createTimelineTable(results); - } - - }; - - createTimelineTable(timeline) { - - const newTbl = document.createElement("table"); - newTbl.classList.add("release-timeline") - - //create table body - const newTbody = document.createElement("tbody"); - - //check to create an empty row separator - let isLongRow = 0; - - // check if too many years are selected - - let minYear = Math.min(...timeline.key.values); - let maxYear = Math.max(...timeline.key.values); - - if (maxYear - minYear > 5000 && this.plugin.settings.collapseEmptyYears == false) { - let errorTbl = createErrorMsg("Error: More than 5000 years in selection and \"Collapse years\" option is not enabled. Enable this option in plugin settings to build the timeline."); - return errorTbl; - }; - - - //create rows for table - let prevYear = Number(timeline[0].key); - - timeline.forEach(item => { - - //year - let key = Number(item.key); - //array of titles, sorted by name - //let value = item.rows.values.map(k => k.file.name).sort(); - //[[filename1, alias1], [filename2, alias2], ..] - //let value = item.rows.values.map(k => [k.file.name, typeof k[aliasName] !== 'undefined' ? k[aliasName] : k.file.name]);//.sort((a, b) => b[0] - a[0]); - - let value = item.rows.values.map(k => [k[0], k[2]]); - - //create separator if previous row was long - if (isLongRow == 1) { newTbody.appendChild(createRowSeparator()) }; - - //create empty rows - let yearDiff = Math.abs(key - prevYear); - let collapseRows = this.plugin.settings.collapseEmptyYears; - let collapseLimit = Number(this.plugin.settings.collapseLimit) || 2; - - if ( yearDiff > 1) { - - //if collapse rows is on - create 1 row - if ( collapseRows && yearDiff > 2 && yearDiff > collapseLimit ) { - - let yearRange = key > prevYear ? `${prevYear + 1} - ${key - 1}` : `${key + 1} - ${prevYear - 1}`; - - const rowYear = createRowYear( { val: yearRange, cls: 'year-nonexisting' } ); - const rowItem = createRowItem( { fileName: "", fileAlias: "" } ); - const newRow = createNewRow(rowYear, rowItem); - newTbody.appendChild(newRow); - - //if collapse rows is off - create all rows - } else { - - for (let j = 1; j < yearDiff; j++) { - let i = (key > prevYear) ? prevYear + j : prevYear - j; - - const rowYear = createRowYear( { val: i, cls: 'year-nonexisting' } ); - const rowItem = createRowItem( { fileName: "", fileAlias: "" } ); - const newRow = createNewRow(rowYear, rowItem); - newTbody.appendChild(newRow); - }; - - }; - - isLongRow = 0; - - }; - - //create real rows - //create row with 1 element - if ( value.length == 1 ) { - - isLongRow = 0; - - const rowYear = createRowYear( { val: key, cls: 'year-existing' } ); - const rowItem = createRowItem( { fileName: value[0][0], fileAlias: value[0][1] } ); - const newRow = createNewRow(rowYear, rowItem); - newTbody.appendChild(newRow); - - //create rows with multiple elements - } else { - - //create separator if prev row was short, but this one is long - if (isLongRow == 0) { newTbody.appendChild(createRowSeparator()); }; - isLongRow = 1; - - //create 1st row - const rowYear = createRowYear( { val: key, cls: 'year-existing', rowspanNb: value.length } ); - const rowItem = createRowItem( { fileName: value[0][0], fileAlias: value[0][1], cls: "td-first" } ); - const newRow = createNewRow(rowYear, rowItem); - newTbody.appendChild(newRow); - - //create 2nd+ rows - for (let i = 1; i < value.length; i++) { - - const rowItem = createRowItem( { fileName: value[i][0], fileAlias: value[i][1], cls: "td-next" } ); - const newRow = createNewRow(rowItem); - newTbody.appendChild(newRow); - - }; - - }; - - prevYear = key; - - }); - - //append table body to table - newTbl.appendChild(newTbody); - - return newTbl; - }; - -} +} \ No newline at end of file diff --git a/functionsYear2.ts b/functionsYear2.ts new file mode 100644 index 0000000..da4c611 --- /dev/null +++ b/functionsYear2.ts @@ -0,0 +1,181 @@ +import ReleaseTimeline from "main"; +import { getAPI, isPluginEnabled, DataviewAPI } from "obsidian-dataview"; +import { moment } from "obsidian"; +import { create } from "domain"; +import { createErrorMsg, createRowSeparator, createRowSeparatorYearMonth, createRowYear, createRowItem, createNewRow, parseQuerySortOrder } from "helperFunctions"; + +export default class YearTimeline { + + plugin: ReleaseTimeline; + + constructor(plugin: ReleaseTimeline) { + this.plugin = plugin; + } + + async renderTimeline(content: string) { + + const dv = getAPI(); + + if ( typeof dv == 'undefined' ) { return createErrorMsg('Dataview is not installed. The Release Timeline plugin requires Dataview to properly function.'); } + + var sortOrder = parseQuerySortOrder(content, this.plugin); + + //get results from dataview + try { + + var results; + var results0 = await dv.query(content); + let a = results0.value.values; + + //filter out null years + let b = a.filter(x => typeof x[1] !== 'undefined' && x[1] !== null); + + //convert date values to years, get rid of non-date values like character strings + b.forEach(x => x[1] = moment( x[1].toString(), 'Y' ).format('Y')) + b = b.filter(x => x[1] != "Invalid date") + + //parse file name from path, replace path, insert to notes with empty alias + b.forEach(x => x[0] = x[0].path.match(/([^\/]+(?=\.)).md/)[1]); + b.forEach(x => x[2]==null ? x[2]=x[0] : 1); + + //group by year + results = dv.array(b); + results = results.groupBy(x => x[1]); + + //sort by sort order + results = results.sort(k => k.key, sortOrder); + + } + catch(error) { + return createErrorMsg("Error from dataview: " + error.message) + } + + if (results.length == 0) { + return createErrorMsg("No results"); + } + else { + return this.createTimelineTable(results); + } + + }; + + createTimelineTable(timeline) { + + const newTbl = document.createElement("table"); + newTbl.classList.add("release-timeline") + + //create table body + const newTbody = document.createElement("tbody"); + + //check to create an empty row separator + let isLongRow = 0; + + // check if too many years are selected + + let minYear = Math.min(...timeline.key.values); + let maxYear = Math.max(...timeline.key.values); + + if (maxYear - minYear > 5000 && this.plugin.settings.collapseEmptyYears == false) { + let errorTbl = createErrorMsg("Error: More than 5000 years in selection and \"Collapse years\" option is not enabled. Enable this option in plugin settings to build the timeline."); + return errorTbl; + }; + + + //create rows for table + let prevYear = Number(timeline[0].key); + + timeline.forEach(item => { + + //year + let key = Number(item.key); + //array of titles, sorted by name + //let value = item.rows.values.map(k => k.file.name).sort(); + //[[filename1, alias1], [filename2, alias2], ..] + //let value = item.rows.values.map(k => [k.file.name, typeof k[aliasName] !== 'undefined' ? k[aliasName] : k.file.name]);//.sort((a, b) => b[0] - a[0]); + + let value = item.rows.values.map(k => [k[0], k[2]]); + + //create separator if previous row was long + if (isLongRow == 1) { newTbody.appendChild(createRowSeparator()) }; + + //create empty rows + let yearDiff = Math.abs(key - prevYear); + let collapseRows = this.plugin.settings.collapseEmptyYears; + let collapseLimit = Number(this.plugin.settings.collapseLimit) || 2; + + if ( yearDiff > 1) { + + //if collapse rows is on - create 1 row + if ( collapseRows && yearDiff > 2 && yearDiff > collapseLimit ) { + + let yearRange = key > prevYear ? `${prevYear + 1} - ${key - 1}` : `${key + 1} - ${prevYear - 1}`; + + const rowYear = createRowYear( { val: yearRange, cls: 'year-nonexisting' } ); + const rowItem = createRowItem( { fileName: "", fileAlias: "" } ); + const newRow = createNewRow(rowYear, rowItem); + newTbody.appendChild(newRow); + + //if collapse rows is off - create all rows + } else { + + for (let j = 1; j < yearDiff; j++) { + let i = (key > prevYear) ? prevYear + j : prevYear - j; + + const rowYear = createRowYear( { val: i, cls: 'year-nonexisting' } ); + const rowItem = createRowItem( { fileName: "", fileAlias: "" } ); + const newRow = createNewRow(rowYear, rowItem); + newTbody.appendChild(newRow); + }; + + }; + + isLongRow = 0; + + }; + + //create real rows + //create row with 1 element + if ( value.length == 1 ) { + + isLongRow = 0; + + const rowYear = createRowYear( { val: key, cls: 'year-existing' } ); + const rowItem = createRowItem( { fileName: value[0][0], fileAlias: value[0][1] } ); + const newRow = createNewRow(rowYear, rowItem); + newTbody.appendChild(newRow); + + //create rows with multiple elements + } else { + + //create separator if prev row was short, but this one is long + if (isLongRow == 0) { newTbody.appendChild(createRowSeparator()); }; + isLongRow = 1; + + //create 1st row + const rowYear = createRowYear( { val: key, cls: 'year-existing', rowspanNb: value.length } ); + const rowItem = createRowItem( { fileName: value[0][0], fileAlias: value[0][1], cls: "td-first" } ); + const newRow = createNewRow(rowYear, rowItem); + newTbody.appendChild(newRow); + + //create 2nd+ rows + for (let i = 1; i < value.length; i++) { + + const rowItem = createRowItem( { fileName: value[i][0], fileAlias: value[i][1], cls: "td-next" } ); + const newRow = createNewRow(rowItem); + newTbody.appendChild(newRow); + + }; + + }; + + prevYear = key; + + }); + + //append table body to table + newTbl.appendChild(newTbody); + + return newTbl; + }; + +} diff --git a/main.ts b/main.ts index e07e9d3..8fa8757 100644 --- a/main.ts +++ b/main.ts @@ -1,6 +1,6 @@ import { App, Editor, MarkdownPostProcessorContext, MarkdownView, Modal, Notice, Plugin, PluginSettingTab, Setting } from 'obsidian'; import { getAPI, isPluginEnabled, DataviewAPI } from "obsidian-dataview"; -import YearTimeline from "functionsYear"; +import YearTimeline from "functionsYear2"; import MonthTimeline from "functionsMonth"; import WeekTimeline from "functionsWeek"; import { ReleaseTimelineSettings, DEFAULT_SETTINGS, SampleSettingTab } from "settings"; diff --git a/manifest.json b/manifest.json index 1f020bf..631b7e0 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "obsidian-release-timeline", "name": "Release Timeline", - "version": "1.1", + "version": "1.2.0", "minAppVersion": "0.12.0", "description": "Release timeline rendered based on notes metadata with a dataview-like syntax.", "author": "cakechaser", diff --git a/package-lock.json b/package-lock.json index cebe9fa..65ef77f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "obsidian-release-timeline", - "version": "1.0.7", + "version": "1.2.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "obsidian-release-timeline", - "version": "1.0.7", + "version": "1.2.0", "license": "ISC", "dependencies": { "builtin-modules": "^3.3.0", diff --git a/settings.ts b/settings.ts index fbcfbff..e3b4bd3 100644 --- a/settings.ts +++ b/settings.ts @@ -16,7 +16,7 @@ export const DEFAULT_SETTINGS: ReleaseTimelineSettings = { bulletPoints: true, collapseLimit: '2', collapseEmptyMonthsWeeklyTimeline: true, - weekDisplayFormat: 'weekNames' + weekDisplayFormat: 'dateNames' } export class SampleSettingTab extends PluginSettingTab { diff --git a/styles.css b/styles.css index 475a6db..9209112 100644 --- a/styles.css +++ b/styles.css @@ -4,10 +4,10 @@ table.release-timeline { padding: 0.75em 1em; font-size: 80%; line-height: 0.9; - border-collapse: separate; - border-spacing: 0 1px; + border-collapse: separate !important; + border-spacing: 0 1px !important; background-color: transparent; - border: 1px solid #a2a9b1; + border: 1px solid #a2a9b1 !important; border-radius: 6px 6px 6px 6px; display: table; } @@ -73,7 +73,7 @@ table.release-timeline { .release-timeline th { text-align: right; border: none; - padding: 0.4em; + padding: 0.4em !important; background: none; font: inherit; color: inherit; @@ -85,7 +85,7 @@ table.release-timeline { vertical-align: inherit; font-weight: bold; border: none; - padding: 0.4em; + padding: 0.4em !important; background: none; font: inherit; color: inherit; @@ -105,7 +105,7 @@ table.release-timeline { .release-timeline .td-separator { border: none; - padding-top: 0px; + padding-top: 0px !important; } .release-timeline .line-separator { diff --git a/versions.json b/versions.json index 3a50021..62217aa 100644 --- a/versions.json +++ b/versions.json @@ -1,4 +1,5 @@ { + "1.2.0": "0.12.0", "1.0.0": "0.12.0", "1.0.1": "0.12.0", "1.0.7": "0.12.0",