My new template idea for Calendar

I love this “multiple minds know more” attitude of the Obsidian Community! Well done peepz! :heart: @ninjineer , @scholarInTraining and @derekvan !!

1 Like

I like idea of using WK. If I was serious about leaving it up to the user to define what works for them, I’d make the label a variable that gets passed into the function. Then in the Templater template, I could prompt for the value.

Sure you could apply bolding on the week numbers. Just replace the [ and ] characters with **. (Or put the ** before and after those characters if you want to keep the square brackets.) I live the edit mode with a monospaced font, so my preference tends towards formatting that looks at home in that environment.

1 Like

To add on to what @ninjaneer just said, to get the weekdays in bold, change

 + `| [WK] | ${wnames.join(" | ")} |\n`

to:

 + `| **WK** | **${wnames.join("** | **")}** |\n`

I think? There might need to be a space before the $ and after the }, or maybe it is fine this way.

1 Like

@derekvan @FiekeB
This is (hopefully) a simplified version of @derekvan’s working code above that goes all-in on using moment instead of Date to make localization easier, and adds FiekeB’s formatting requests. Unfortunately my limited (human) language skills make it hard for me to test for the inevitable errors in my (computer) language knowledge, so please do tell me if this has mistakes and I will attempt to learn! :grin:

function mdCalendar(year, month, localeString) {
	const prevLocale = moment.locale(); // save current locale to reset moment later
	moment.locale(localeString ?? prevLocale);
	const monthStart = moment({ years: year, month: month - 1, date: 1 });

	//Establish the calendar header with month, year, and abbreviated weekday names
	let cal =
		`**${moment.months(month - 1)} ${year}**\n\n` +
		`| **WK** | **${moment.weekdaysShort(true).join("** | **")}** |\n` +
		"|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|\n";

	const BLANK = "  .. ";
	const BLANKWEEK = [BLANK, BLANK, BLANK, BLANK, BLANK, BLANK, BLANK];

	let week = [...BLANKWEEK]; // copy of blankweek so can use it again later

	// Moments are mutable - must clone!
	for (let day = monthStart.clone(); day.date() <= monthStart.daysInMonth(); day.add(1, "day")) {
		let wday = day.weekday(); //day of the week localized
		if (day.date() === 1 || wday === 0) {
			cal += `| **[[${day.format("gggg-[W]WW")}\\|${day.format("WW")}]]**`;
		}
		week[wday] = `[[${day.format("YYYY-MM-DD")}\\|${day.format("DD")}]]`;
		if (wday === 6) {
			cal += `| ${week.join("|")} |\n`;
			week = [...BLANKWEEK];
		}
	}
	if (week[0] !== BLANK) {
		cal += `| ${week.join("|")} |\n`;
	}
	moment.locale(prevLocale); // reset moment's locale
	return cal;
}
module.exports = mdCalendar;

Thoughts, questions, comments? I’ll add one comment of my own: Moment has a documentation page saying basically “don’t use me anymore”, so if it wasn’t already a part of Obsidian I’d definitely be opting for @ninjineer’s strategy with the built-in Javascript Date objects.

1 Like

I figured this one out partially haha, because usually in the app Sublime Text green stuff is what gets parsed so I figured I would have to add the ** around that [wk] part. I backed up the original working script first and started fiddling with another instance of it. What I did worked, but only for the literal [wk] part and not for all the weeknumbers under it. I was just about to try something, when I remembered that this morning I asked about getting it bold here, and thought “maybe someone answered that” and yup!! Thanks :slight_smile: (again!)

I would prefer to have the names of the days of the week in bold too, because that would make them stand out a bit more, too. Can you please tell me how to do that? I know I can with CSS, butI will always want them bold so why not code it right in?

I am first going to see if the script works with the bold and all, and then I will try this version of yours!

I’m literally JUST back from bringing my daughter to Schiphol Airport/AMS and these last few days have been hellish for me. It is such a weird idea that I won’t see her until december when she will come visit for Christmas. Whole lot of mixed feelings, so I am happy that I can hang out with your guys here and create nice and goodworking stuff. I have an idea… I think there might more people interested in this and we could make it into a plugin or leave as is, with the javascript and template well documented and perhaps I can figure out how to make CSS so people can change colors and bold and italic etc. (which could be plugin options if we’d go that way).We could make plugin options to let people change the way they format their dates. I even figured out how to link the weeknumbers to the most common way most people name their weekly notes (gggg-[W]ww). If they can just type it in,in options instead of having to change the script themselves, that would be awesome.

Imagine being able to generate a fully linked year calendar for daily or periodical notes. Even the year and the months names could (optionally) be linked to their respective
counterparts.

This could be an AMAZING addition to the Periodic Notes plugin.

Another idea that i think is not too hard to code (for coders, for me it is impossible haha!), is making a script that parses a full year of months but with each day on a separate line, and 2 or 3 blank lines under each day where people can add their tasks. I am not quite finished yet building this by hand. But I will finish it soon, so that I can show you what that looks like and how that works.

This may sound like way more work than I think is has to be. But I am sure many people would love this alongside their periodic notes. I can see me providing templates the way I use this, with documentation. :slight_smile: but I am literally only good for the ideas and some basic stuff…I would need help with almost all the coding. But to me this seems pretty basic, given what you guys have come up with already!!

1 Like

That is what **${wnames.join("** | **")}** from my short add on post to ninjaneer should be doing, but I was not 100% sure it would work. What are you seeing instead?

The week numbers: if you’re looking at derekvan 's code you should be able to spot where those are being written to the template because of the week-related format string - then you can add bold there. If you’re looking at ninjaneer’s code you’ll want to put bold marks around [' + wnum.padStart(2,'0') + '], but that version does not yet link to your weekly note.

EDIT: I love your ideas here, and it would certainly be possible to extract the formatting strings as settings rather than hard-code them into the function! I am happy to see anything I wrote in this thread added to a plugin or template or whatever but I definitely cannot commit to keeping a plugin up to date over time. So someone else would need to be in charge of that!

1 Like

Hey, that’sokay, I can understand that. It is just an idea for now, and if I take the time to properly introduce that idea to the Obsidian community, with examples and all, surely there will be people having more ideas and someone will probably step up who has more spare time. If it ever gets this far, I will most definitely mention your name and @ninjineer and @derekvan too, as being the peepz who took the time to write up in the beginning!

1 Like

Exactly that :slight_smile: I hadn’t tested it yet!

1 Like

I had a similar epiphany yesterday afternoon and figured out how to code it so that a person could custom format all aspects of the calendar output in Templater. It’s a tad more complicated to put together than the functions I’ve done thus far but doable. I started working on it last night, but then life interrupted. I’ll get back to it again tonight and hopefully have something ready to post in a day or two.

2 Likes

Life has its moments :wink: my day wasn’t too super today, but that happens and we have to deal with it…Obsidian is a very good distraction for me, I just wish I had more of a beta brain so I could also help to code. You’ll have to put up with me as “the lady with the ideas” :stuck_out_tongue:

By the way… this piece of your code:

          //this here below is about calculating the weeknumbers and formatting them (brackets or not, bold or not)
          //cal += '| [' + wnum.padStart(2,'0') + '] |' + week.join('|') + '|\n'; I want the brackets gone
          //cal += '| ' + wnum.padStart(2,'0') + ' |' + week.join('|') + '|\n'; I want the numbers bold
          cal += '| **' + wnum.padStart(2,'0') + '** |' + week.join('|') + '|\n';
          week = ['  .. ','  .. ','  .. ','  .. ','  .. ','  .. ','  .. '];
       }
       day = new Date(day.getFullYear(), day.getMonth(), day.getDate() + 1);
    }

    //this here below is also about calculating the weeknumbers and formatting them (brackets or not, bold or not)
    //if (week[0] != '  .. ') { cal += '| [' + wnum.padStart(2,'0') + '] |' + week.join('|') + '|\n'; }I want the brackets gone and the numbers bold
    if (week[0] != '  .. ') { cal += '| **' + wnum.padStart(2,'0') + '** |' + week.join('|') + '|\n'; }
    return cal;

is at the bottom of your code and I commented some stuff with what I THINK it is and removed brackets and made it bold etc and that works. I never delete a line of code either unless I have tested thoroughly that it works. Until then, I just duplicate the code I want to change, comment out the original line(s), change it and test it. I often just leave the original code in even after the new code works, -commented out-, so that I can change it back again at a later point, if need be.

I know how to link the weeknumbers now, but not in the code you made…my weeknumbers are formatted as they are for most people: gggg-[W]ww

How would I turn the weeknumbers into a gggg-[W]ww formatted link? Can you show me, please?

Earlier in this discussion you worked out how to do a link to a daily note like so:

      momentDay = moment(day);
      week[wday] = `[[${momentDay.format("YYYY-MM-DD")}\\|${momentDay.format("DD")}]]`;

You’d do something similar for linking to the weekly notes. Assuming you have momentDay = moment(day); defined, you should replace the two instances of wnum.padStart(2,'0') with

`[[${momentDay.format("GGGG-[W]WW")}\\|${momentDay.format("WW")}]]`

(Note: I’m writing this on a computer that does not have Obsidian installed, so I cannot verify that the above is the exact syntax.)

2 Likes

Hi @ninjineer, thank you! I thought it would be something like that but it kept giving me an error when I tried it. But now I’ve got it working, I forgot I needed to comment out
week[wday] = ’ ’ + d.padStart(2,‘0’) + ’ '; as well … :woozy_face:

1 Like

As promised, here is a new and improved version of mdCalendar.js that offers users the ability to format the calendar output to meet their needs without having to directly modify the code that generates it. I’ll start with all the different options that can be modified and then present a few templates showing how these options are actually used. Then I’ll conclude with the source content of mdCalendar.js.

Options

  • Option: First Day of the Week

    • Default: Sunday is the first day of the week
    • Set: mondayFirst() sets Monday to be the first day of the week
    • Set: sundayFirst() sets Sunday to be the first day of the week
  • Option: Display a month title before the calendar

    • Default: Display the month title before the calendar
    • Set: hideMonthTitle() removes the month title from the output
    • Set: showMonthTitle() displays the month title in the output
  • Option: Set the format of the month title

    • Default: The title will show the localized month name and the year, followed by a blank line
    • Set: formatMonthTitle(format) where format is a callback function that takes in a Javascript date object and returns a string
    • Examples:
      • formatMonthTitle(d => { const m = moment(d); return m.format("YYYY-MMM") + '\n';}) displays the year, a dash, and then the abbreviated month.
      • formatMonthTitle(d => {const m = moment(d); return `[[m.format("YYYY-MM")\\|m.format("MMMM YYYY")]]\n\n` }) displays the month and year as a link to a monthly note that uses YYYY-MM name formatting.
    • Notes:
      • It is important that this header string end with at least one \n. Otherwise, this text will run into the first line of the calendar.
  • Option: Display a week-of-the-year column

    • Default: A column containing the week-of-the-year number will be displayed
    • Set: hideWeekNum() removes the week-of-the-year column from the output
    • Set: showWeekNum() displays the week-of-the-year column in the output
  • Option: Define the week-of-the-year column title

    • Default: [WK]
    • Set: weekNumTitle(string) where string is text to be displayed
    • Examples:
      • weekNumTitle('WK') removes the square brackets
      • weekNumTitle('Week') displays the title as a full word
  • Option: Embolden the week-of-the-year title

    • Default: not bold
    • Set: boldWeekNumTitle() emphasizes the week-of-the-year column title
    • Set: normalWeekNumTitle() shows the week-of-the-year column title without emphasis.
  • Option: Justify the week-of-the-year column

    • Default: centered
    • Set: justifyWeekNumRight() aligns the column to the right when seen in preview mode
    • Set: justifyWeekNumLeft() aligns the column to the left when seen in preview mode
    • Set: justifyWeekNumCenter() centers the column in preview mode
  • Option: Define brackets around the week-of-the-year numbers

    • Default: [ and `]
    • Set: weekNumBrackets(left,right) where left and right are the strings that go on either side of the week number
    • Examples:
      • weekNumBrackets('{','}') encloses the week numbers in curly braces
      • weekNumBrackets('**','**') makes the week numbers bold
      • weekNumBrackets('','') removes the braces altogether
  • Option: Set the format of the week number

    • Default: Displays the week number as a two-digit string with a leading zero.
    • Set: formatWeekNum(format) where format is a callback function that takes in a Javascript date object, a week number, and the left and right bracket strings in that order. (Note, the way JavaScript works, the function does not need to define all these parameters. You can leave off the ones you don’t need, but only by dropping them from the right and working backwards.) The function needs to return a string.
    • Examples:
      • formatWeekNum(d => { const m = moment(d); return `[[${m.format('GGGG-[W]WW')}\\|${m.format('WW')}]]`; }) formats the week number as a link to a weekly note
  • Option: Set the format of the day-of-the-week titles

    • Default: The days of the week will be shown in their shortened form, per the localization. For English, this means their three-letter abbreviations.
    • Set: formatDayTitle(format) where format is a callback function that takes in a Javascript date object and returns a string
    • Examples:
      • formatDayTitle(d => { const m = moment(d); return m.format('dddd'); }) shows the days of the week as their full name
  • Option: Embolden the day-of-the-week titles

    • Default: not bold
    • Set: boldDayTitles() emphasizes the day-of-week titles
    • Set: normalDayTitles() shows the day-of-week titles without emphasis
  • Option: Justify day-of-the-week columns

    • Default: centered
    • Set: justifyDayRight() aligns the columns to the right when seen in preview mode
    • Set: justifyDayLeft() aligns the columns to the left when seen in preview mode
    • Set: justifyDayCenter() centers the column in preview mode
  • Option: Define the text used for the empty days

    • Default: ..
    • Set: dayEmpty(string)
  • Option: Set the format of days

    • Default: Two digit numbers with the single digit numbers being zero filled
    • Set: formatDay(format) where format is a callback function that takes in a Javascript date object and returns a string
    • Examples:
      • formatDay(d => { const m = moment(d); return `[[${m.format('YYYY-MM-DD')}\\|${m.format('DD')}]]`; }) turns each day into a link to a corresponding daily note

Templates

insert-year-calendar.md:

This is essentially the same template I first presented that generates a monthly calendar for each of a year’s months. It uses all the default options such as Sunday’s being the first day of the week.

<%*
   let year = await tp.system.prompt("Year of Calendar",tp.date.now("YYYY"));
      
   if (tp.file.title.startsWith("Untitled"))
   {
      await tp.file.rename(`${year} calendar`);
   }
   
   let cal = tp.user.mdCalendar();
   
   tR += `# ${year} Calendar\n\n`
   for (let i = 1; i <= 12; i++)
   {
      tR += `## ${cal.monthCalendar(year,i)}\n\n`;
   }
%>

insert-month-calendar.md:

This does the same job as the first template, but only for a single month.

<%*
   let year = await tp.system.prompt("Year of Calendar",tp.date.now("YYYY"));
   let month = await tp.system.suggester(['1','2','3','4','5','6','7','8','9','10','11','12'],['1','2','3','4','5','6','7','8','9','10','11','12'],false,"Month");
      
   if (tp.file.title.startsWith("Untitled"))
   {
      await tp.file.rename(`${year}-${month} calendar`);
   }
   
   let cal = tp.user.mdCalendar();
   tR += `# ${cal.monthCalendar(year,month)}\n\n`;
%>

Outputs:

# July 2022

| [WK] | Sun | Mon | Tue | Wed | Thu | Fri | Sat |
|:----:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
| [26] |  .. |  .. |  .. |  .. |  .. |  01 |  02 |
| [27] |  03 |  04 |  05 |  06 |  07 |  08 |  09 |
| [28] |  10 |  11 |  12 |  13 |  14 |  15 |  16 |
| [29] |  17 |  18 |  19 |  20 |  21 |  22 |  23 |
| [30] |  24 |  25 |  26 |  27 |  28 |  29 |  30 |
| [31] |  31 |  .. |  .. |  .. |  .. |  .. |  .. |

insert-month-calendar-bold:

This shows how to use the aforementioned options to generate a calendar where the titles and week numbers are bold.

<%*
   let year = await tp.system.prompt("Year of Calendar",tp.date.now("YYYY"));
   let month = await tp.system.suggester(['1','2','3','4','5','6','7','8','9','10','11','12'],['1','2','3','4','5','6','7','8','9','10','11','12'],false,"Month");
      
   if (tp.file.title.startsWith("Untitled"))
   {
      await tp.file.rename(`${year}-${month} calendar`);
   }
   
   let cal = tp.user.mdCalendar()
           .mondayFirst()
           .boldWeekNumTitle()
           .boldDayTitles()
           .weekNumTitle('Week')
           .weekNumBrackets('**','**')
           ;
   
   tR += `# ${cal.monthCalendar(year,month)}\n\n`;
%>

Outputs:

# July 2022

| **Week** | **Mon** | **Tue** | **Wed** | **Thu** | **Fri** | **Sat** | **Sun** |
|:--------:|:-------:|:-------:|:-------:|:-------:|:-------:|:-------:|:-------:|
|   **26** |      .. |      .. |      .. |      .. |      01 |      02 |      03 |
|   **27** |      04 |      05 |      06 |      07 |      08 |      09 |      10 |
|   **28** |      11 |      12 |      13 |      14 |      15 |      16 |      17 |
|   **29** |      18 |      19 |      20 |      21 |      22 |      23 |      24 |
|   **30** |      25 |      26 |      27 |      28 |      29 |      30 |      31 |

insert-month-calendar-linked

This demonstrates how you can have all the pieces of a calendar link back to daily, weekly, monthly, and yearly notes.

<%*
   let year = await tp.system.prompt("Year of Calendar",tp.date.now("YYYY"));
   let month = await tp.system.suggester(['1','2','3','4','5','6','7','8','9','10','11','12'],['1','2','3','4','5','6','7','8','9','10','11','12'],false,"Month");
      
   if (tp.file.title.startsWith("Untitled"))
   {
      await tp.file.rename(`${year}-${month} calendar`);
   }
   
   const monthf = d => { const m = moment(d); return `[[${m.format('YYYY-MM')}|${m.format('MMMM')}]] [[${m.format('YYYY')}]]\n\n`; };
   const dayf = d => { const m = moment(d); return `[[${m.format('YYYY-MM-DD')}\\|${m.format('DD')}]]`; };
   const weekf = d => { const m = moment(d); return `[[${m.format('GGGG-[W]WW')}\\|${m.format('WW')}]]`; };
   const titlef = d => { const m = moment(d); return m.format('dddd'); };

   let cal = tp.user.mdCalendar()
           .formatMonthTitle(monthf)
           .formatDay(dayf)
           .formatWeekNum(weekf)
           .formatDayTitle(titlef)
           ;
   
   tR += `# ${cal.monthCalendar(year,month)}\n\n`;
%>

Outputs:

# [[2022-07|July]] [[2022]]

|             [WK] |             Sunday |             Monday |            Tuesday |          Wednesday |           Thursday |             Friday |           Saturday |
|            :----:|            :------:|            :------:|           :-------:|         :---------:|          :--------:|            :------:|          :--------:|
| [[2022-W26\|26]] |                 .. |                 .. |                 .. |                 .. |                 .. | [[2022-07-01\|01]] | [[2022-07-02\|02]] |
| [[2022-W27\|27]] | [[2022-07-03\|03]] | [[2022-07-04\|04]] | [[2022-07-05\|05]] | [[2022-07-06\|06]] | [[2022-07-07\|07]] | [[2022-07-08\|08]] | [[2022-07-09\|09]] |
| [[2022-W28\|28]] | [[2022-07-10\|10]] | [[2022-07-11\|11]] | [[2022-07-12\|12]] | [[2022-07-13\|13]] | [[2022-07-14\|14]] | [[2022-07-15\|15]] | [[2022-07-16\|16]] |
| [[2022-W29\|29]] | [[2022-07-17\|17]] | [[2022-07-18\|18]] | [[2022-07-19\|19]] | [[2022-07-20\|20]] | [[2022-07-21\|21]] | [[2022-07-22\|22]] | [[2022-07-23\|23]] |
| [[2022-W30\|30]] | [[2022-07-24\|24]] | [[2022-07-25\|25]] | [[2022-07-26\|26]] | [[2022-07-27\|27]] | [[2022-07-28\|28]] | [[2022-07-29\|29]] | [[2022-07-30\|30]] |
| [[2022-W30\|30]] | [[2022-07-31\|31]] |                 .. |                 .. |                 .. |                 .. |                 .. |                 .. |

mdCalendar.js

class MDCalendar
{
   #mondayFirst = false;

   #showMonthTitle = true;
   #formatMonthTitle = (adate) => { return `${adate.toLocaleString('default', { month: 'long' })} ${adate.getFullYear()}\n\n`; };

   #showWeekNum = true;
   #weekNumTitle = '[WK]';
   #boldWeekNumTitle = false;
   #weekNumLeft = ':';
   #weekNumRight = ':';
   #weekNumBracketLeft = '[';
   #weekNumBracketRight = ']';
   #formatWeekNum = (adate,weekNum,left,right) => { const n = `${weekNum}`; return `${left}${n.padStart(2,'0')}${right}`; }

   #formatDayTitle = (adate) => { return adate.toLocaleString('default', { weekday: 'short' }); };
   #boldDayTitles = false;
   #dayLeft = ':';
   #dayRight = ':';
   #dayEmpty = '..';
   #formatDay = (adate) => { const d = `${adate.getDate()}`; return d.padStart(2,'0'); };

   constructor() {}

   mondayFirst() { this.#mondayFirst = true; return this; }
   sundayFirst() { this.#mondayFirst = false; return this; }

   showMonthTitle() { this.#showMonthTitle = true; return this; }
   hideMonthTitle() { this.#showMonthTitle = false; return this; }
   formatMonthTitle(format) { this.#formatMonthTitle = format; return this; }

   showWeekNum() { this.#showWeekNum = true; return this; }
   hideWeekNum() { this.#showWeekNum = false; return this; }
   weekNumTitle(title) { this.#weekNumTitle = title; return this; }
   boldWeekNumTitle() { this.#boldWeekNumTitle = true; return this; }
   normalWeekNumTitle() { this.#boldWeekNumTitle = false; return this; }
   justifyWeekNumCenter() { this.#weekNumLeft = ':'; this.#weekNumRight = ':'; return this; }
   justifyWeekNumRight() { this.#weekNumLeft = '-'; this.#weekNumRight = ':'; return this; }
   justifyWeekNumLeft() { this.#weekNumLeft = '-'; this.#weekNumRight = '-'; return this; }
   weekNumBrackets(left,right) {this.#weekNumBracketLeft = left; this.#weekNumBracketRight = right; return this;}
   formatWeekNum(format) { this.#formatWeekNum = format; return this;}

   formatDayTitle(format) { this.#formatDayTitle = format; return this; }
   boldDayTitles() { this.#boldDayTitles = true; return this; }
   normalDayTitles() { this.#boldDayTitles = false; return this; }
   justifyDayCenter() { this.#dayLeft = ':'; this.#dayRight = ':'; return this; }
   justifyDayRight() { this.#dayLeft = '-'; this.#dayRight = ':'; return this; }
   justifyDayLeft() { this.#dayLeft = '-'; this.#dayRight = '-'; return this; }
   dayEmpty(txt) { this.#dayEmpty = txt; return this; }
   formatDay(format) { this.#formatDay = format; return this; }

   monthCalendar(year,month)
   {
      const day = new Date(year,month - 1,1);
      let cal = this.#getMonthTitle(day)
      let table = this.#getWeekNumTitle()
                + this.#getDayTitles(day)
                + this.#getHeadingRules()
                + this.#getCalendar(day)
                ;

      //start process of making column widths of table consistent
      let cells = table.split('\n');
      cells.pop(); //remove element caused by final return
      const rows = cells.length;
      const cols = cells[0].split('|').length;
      let i,j = 0;
      let widths = [];
      for(j=0;j<cols;j++) {widths.push(0);}

      //determine maximum width for each column
      for (i=0; i<rows; i++)
      {
         cells[i] = cells[i].replace(/\\\|/g,'??').split('|');
         for (j=0; j<cols; j++)
         {
            if (cells[i][j].length > widths[j]) { widths[j] = cells[i][j].length; }
         }
      }

      //apply column width to each cell and reconstitute markdown table
      for (i=0; i<rows; i++)
      {
         for(j=1; j<cols - 1; j++)
         {
            cells[i][j] = cells[i][j].padStart(widths[j],' ');
         }
         cal += cells[i].join('|').replace(/\?\?/g,'\\|') + '\n';
      }
      return cal;
   }

   weekOfYear(adate)
   {
      let firstWeekStart = new Date(adate.getFullYear(),0,1);
      let offset = 0;
      if (this.#mondayFirst) { offset = 1};
      let day = firstWeekStart.getDay() - offset;
      if (day < 0) day = 6;
      let daysUntilWeekStart = day !== 0 ? 7 - day : 0;
      firstWeekStart.setDate(firstWeekStart.getDate() + daysUntilWeekStart);
      let weekNum = Math.floor((adate - firstWeekStart)/(24 * 60 * 60 * 1000) / 7) + 1;
      if (weekNum < 1) { weekNum = 52; }
      return weekNum;
   }

   #getMonthTitle(adate)
   {
      let out = '';
      if (this.#showMonthTitle) { out = this.#formatMonthTitle(adate); }
      return out;
   }

   #getWeekNumTitle()
   {
      let out = '';
      if (this.#showWeekNum) { out = `| ${this.#doBold(this.#weekNumTitle,this.#boldWeekNumTitle)} `; }
      return out;
   }

   #getWeekNum(adate)
   {
      let out = '';
      if (this.#showWeekNum)
      {
         const weekNum = this.weekOfYear(adate);
         out = `| ${this.#formatWeekNum(adate,weekNum,this.#weekNumBracketLeft,this.#weekNumBracketRight)} `;
      }
      return out;
   }

   #getDayTitles()
   {
      let dd = new Date(2022,1,27); //a Sunday
      let titles = [];
      for (let i = 0; i < 8; i++)
      {
         titles.push(this.#doBold(this.#formatDayTitle(dd),this.#boldDayTitles));
         dd.setDate(dd.getDate() + 1);
      }
      if (this.#mondayFirst)
      {
         titles = titles.slice(1,8); //gives [Mon,Tue,Wed,Thu,Fri,Sat,Sun]
      }
      else
      {
         titles = titles.slice(0,7); //gives [Sun,Mon,Tue,Wed,Thu,Fri,Sat]
      }
      return `| ${titles.join(" | ")} |\n`
   }

   #doBold(str,tf)
   {
      if (tf) { return `**${str}**`; }
      else { return str; }
   }

   #getHeadingRules()
   {
      let out = '';
      if (this.#showWeekNum)
      {
         let len = this.#getWeekNumTitle().length - 3
         out += `|${this.#weekNumLeft}${'-'.repeat(len)}${this.#weekNumRight}`
      }

      let days = this.#getDayTitles().split('|');
      for (let i = 1; i <= 7; i++)
      {
         let len = days[i].length - 2;
         out += `|${this.#dayLeft}${'-'.repeat(len)}${this.#dayRight}`;
      }
      out += '|\n';
      return out;
   }


   #blankWeek()
   {
      let week = [];
      for(let i = 0; i < 7; i++)
      {
         week.push(this.#dayEmpty);
      }
      return week;
   }

   #getCalendar(day)
   {
   let cal = '';
   let month = day.getMonth();
   let week = this.#blankWeek();

   while (day.getMonth() == month)
   {
      let wday = day.getDay(); //day of the week (0[Sun] - 6[Sat])
      if (this.#mondayFirst) { wday = (wday - 1 < 0) ? 6 : wday - 1; }
      week[wday] = this.#formatDay(day);
      if (wday == 6)
      {
         cal += this.#getWeekNum(day) + '| ' + week.join(' | ') + ' |\n';
         week = this.#blankWeek();
      }
      day = new Date(day.getFullYear(), day.getMonth(), day.getDate() + 1);
   }

   if (week[0] != this.#dayEmpty) { cal += this.#getWeekNum(new Date(day.getFullYear(),day.getMonth(),0)) + '| ' + week.join(' | ') + ' |\n'; }

   return cal;
   }
}

function mdCalendar()
{
   return new MDCalendar();
}

module.exports = mdCalendar;
1 Like

I just saw this, I will test it out asap!! Looks like a whole lot of work you have done!

Feels a whole lot less like work when it’s something I enjoy doing.

1 Like

Hi @ninjineer, this is super awesome and I am learning a lot from reading your code!

Could you explain how the justify left/center/right works in Markdown? I did not know that there was actual meaning to the second row of a Markdown table with all the dashes and that you could use : on the appropriate sides to justify. Do the number of dashes matter? Are spaces allowed in that row? Do spaces in the following rows affect the justification of those cells? Which “views” in Obsidian does the justification show up in - Reading?

PS: I was adding JSDoc comments to your mdCalendar.js file to make sure I understood as I was reading and noticed a couple things. I don’t know enough Javascript to know if any of these are actual problems or just ones where Typescript+ESlint+my JSDoc comments were confused, but in case any are useful:

  • In monthCalendar you call #getDayTitles(day) but #getDayTitles() doesn’t take any args.
  • In weekOfYear I was getting a complaint about the Date subtraction being invalid, which disappeared when I changed it to adate.getMilliseconds() - firstWeekStart.getMilliseconds()
  • Does it make sense to give a default option for dayEmpty(txt) txt arg? I know JS thinks all arguments are optional, but I don’t know what that actually means for using or documenting them.

Cheers and thanks for sharing your time and expertise!

1 Like

Could you explain how the justify left/center/right works in Markdown? I did not know that there was actual meaning to the second row of a Markdown table with all the dashes and that you could use : on the appropriate sides to justify. Do the number of dashes matter? Are spaces allowed in that row? Do spaces in the following rows affect the justification of those cells? Which “views” in Obsidian does the justification show up in - Reading?

If memory serves, what I know about the header lines I learned from reading (Working with Tables in GitHub Markdown)[https://www.pluralsight.com/guides/working-tables-github-markdown]. Other Markdown flavors may, however, support different table syntax. I believe you need a minimum of three characters for it to recognize it as a header indicator. Visually it looks better if the header line of dashes spans the entire width of the column, but it is not a requirement that it do so. Spaces are allowed on either side and do not impact how the justification is handled. The alignment only really happens when the Markdown table is rendered as HTML, which means Obsidian’s preview mode. However, I’ve noticed that the Advanced Tables plugin also applies the justifications when it’s re-laying out the text of the Markdown table, so it can happen in the edit mode too.

In monthCalendar you call #getDayTitles(day) but #getDayTitles() doesn’t take any args.

You’re right. This is an error, abet a harmless one in this case. The function call should not be passing day.

In weekOfYear I was getting a complaint about the Date subtraction being invalid, which disappeared when I changed it to adate.getMilliseconds() - firstWeekStart.getMilliseconds()

The result when I run adate - firstWeekStart is a value in milliseconds. However, this is apparently not universal. Or maybe it’s a difference between Javascript and TypeScript. Your modification makes this explicit, so I think it’s a smart change.

Does it make sense to give a default option for dayEmpty(txt) txt arg? I know JS thinks all arguments are optional, but I don’t know what that actually means for using or documenting them.

I’m not sure if I understand your question. Are you asking why I didn’t do something like dayEmpty(txt='..')? Well, in this case, the property that is being set by this function already had default value assigned to it when the class was instantiated. And the only time someone is likely to call this function is when they want to set a value other than that default. (If you were asking something else please let me know.)

I hope this helps.

1 Like

I’m a visual person myself and I LOVE this idea! It just better helps put everything into perspective!