Skip to content

Commit ab22977

Browse files
authored
Enhance Daily Notes Popup with date banners for headings. (#507)
* Enhance Daily Notes Popup with date banners for headings. - Fix bug introduced by Roam DOM changes - Introduced utility functions for parsing dates and creating banners, and implemented an observer for dynamic heading updates. - Refactored initialization logic to improve readability and maintainability. * 1.7.1 * rm DNP check
1 parent dd09fad commit ab22977

File tree

3 files changed

+128
-91
lines changed

3 files changed

+128
-91
lines changed

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
"@types/mozilla-readability": "^0.2.0",
2424
"@types/turndown": "^5.0.1"
2525
},
26-
"version": "1.7.0",
26+
"version": "1.7.1",
2727
"samepage": {
2828
"extends": "node_modules/roamjs-components/package.json"
2929
}

src/features/dailyNotesPopup.tsx

Lines changed: 125 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ import getUids from "roamjs-components/dom/getUids";
1111
import getPageTitleByPageUid from "roamjs-components/queries/getPageTitleByPageUid";
1212
import type { OnloadArgs } from "roamjs-components/types";
1313
import { addCommand } from "./workBench";
14+
import createHTMLObserver from "roamjs-components/dom/createHTMLObserver";
1415

15-
let observerHeadings: MutationObserver | undefined = undefined;
1616
let closeDailyNotesPopup: (() => void) | undefined;
1717

1818
export const moveForwardToDate = (bForward: boolean) => {
@@ -543,96 +543,133 @@ const jumpDateIcon = () => {
543543
return true;
544544
};
545545

546+
// Daily Note Subtitles / Banner
547+
const ROAM_TITLE_CLASS = "rm-title-display";
548+
const ROAM_TITLE_CONTAINER_CLASS = "rm-title-display-container";
549+
const DAY_BANNER_CLASS = "roam-title-day-banner";
550+
const MONTHS = [
551+
"January",
552+
"February",
553+
"March",
554+
"April",
555+
"May",
556+
"June",
557+
"July",
558+
"August",
559+
"September",
560+
"October",
561+
"November",
562+
"December",
563+
];
564+
565+
const WEEKDAYS = [
566+
"Sunday",
567+
"Monday",
568+
"Tuesday",
569+
"Wednesday",
570+
"Thursday",
571+
"Friday",
572+
"Saturday",
573+
];
574+
575+
// Daily Note Subtitles / Banner - Utils
576+
const parseDateFromHeading = (headingText: string): Date | null => {
577+
const [month, date = "", year = ""] = headingText.split(" ");
578+
const dateMatch = date.match(/^(\d{1,2})(st|nd|rd|th),$/);
579+
580+
if (!year || !dateMatch || !MONTHS.includes(month)) {
581+
return null;
582+
}
583+
584+
const pageDate = new Date(
585+
Number(year),
586+
MONTHS.indexOf(month),
587+
Number(dateMatch[1])
588+
);
589+
return !isNaN(pageDate.valueOf()) ? pageDate : null;
590+
};
591+
const createDayBanner = (
592+
dayOfWeek: number,
593+
heading: HTMLHeadingElement
594+
): HTMLDivElement => {
595+
const banner = document.createElement("div");
596+
banner.className = DAY_BANNER_CLASS;
597+
banner.innerText = WEEKDAYS[dayOfWeek];
598+
banner.style.fontSize = "10pt";
599+
banner.style.position = "relative";
600+
601+
// Calculate positioning based on heading's margin
602+
const headingMargin = getComputedStyle(heading).marginBottom;
603+
const marginValue = Number(headingMargin.replace("px", "")) || 0;
604+
banner.style.top = `-${marginValue + 6}px`;
605+
606+
return banner;
607+
};
608+
const insertBanner = (
609+
banner: HTMLDivElement,
610+
heading: HTMLHeadingElement
611+
): void => {
612+
const container = heading.closest(`.${ROAM_TITLE_CONTAINER_CLASS}`);
613+
const insertionPoint = container || heading;
614+
insertionPoint.insertAdjacentElement("afterend", banner);
615+
};
616+
const hasExistingBanner = (heading: HTMLHeadingElement): boolean => {
617+
// Check if banner is next sibling of container (works for all instances now)
618+
const container = heading.closest(`.${ROAM_TITLE_CONTAINER_CLASS}`);
619+
if (container) {
620+
const containerNextSibling = container.nextElementSibling;
621+
if (
622+
containerNextSibling &&
623+
containerNextSibling.classList.contains(DAY_BANNER_CLASS)
624+
) {
625+
return true;
626+
}
627+
}
628+
629+
return false;
630+
};
631+
const addDateToRoamTitleBanner = (heading: HTMLHeadingElement): void => {
632+
if (hasExistingBanner(heading)) return;
633+
634+
const pageDate = parseDateFromHeading(heading.innerText);
635+
if (!pageDate) return;
636+
637+
const dayOfWeek = pageDate.getDay();
638+
const banner = createDayBanner(dayOfWeek, heading);
639+
insertBanner(banner, heading);
640+
};
641+
const processExistingHeadings = (): void => {
642+
const existingHeadings = document.querySelectorAll(`.${ROAM_TITLE_CLASS}`);
643+
existingHeadings.forEach((heading) => {
644+
addDateToRoamTitleBanner(heading as HTMLHeadingElement);
645+
});
646+
};
647+
648+
// Daily Note Subtitles / Banner - Observer
649+
let observerHeadings: { disconnect: () => void } | undefined = undefined;
650+
const setupHeadingObserver = (): void => {
651+
observerHeadings = createHTMLObserver({
652+
tag: "H1",
653+
className: ROAM_TITLE_CLASS,
654+
callback: (element) =>
655+
addDateToRoamTitleBanner(element as HTMLHeadingElement),
656+
});
657+
};
658+
const cleanupHeadingObserver = (): void => {
659+
if (observerHeadings) {
660+
observerHeadings.disconnect();
661+
observerHeadings = undefined;
662+
}
663+
};
664+
665+
// Main Daily Notes Popup Component
546666
export const component = {
547667
async initialize() {
548668
const setting = get("dailySubtitles");
549-
if (setting != "off") {
550-
// TODO - Move This
551-
const MONTHS = [
552-
"January",
553-
"February",
554-
"March",
555-
"April",
556-
"May",
557-
"June",
558-
"July",
559-
"August",
560-
"September",
561-
"October",
562-
"November",
563-
"December",
564-
];
565-
const addDateToRoamTitleBanners = (titles: HTMLHeadingElement[]) => {
566-
titles.forEach((title) => {
567-
if (
568-
title.nextElementSibling &&
569-
title.nextElementSibling.classList.contains("roam-title-day-banner")
570-
) {
571-
return;
572-
}
573-
const [month, date = "", year = ""] = title.innerText.split(" ");
574-
const dateMatch = date.match(/^(\d{1,2})(st|nd|rd|th),$/);
575-
const pageDate =
576-
year &&
577-
dateMatch &&
578-
MONTHS.includes(month) &&
579-
new Date(Number(year), MONTHS.indexOf(month), Number(dateMatch[1]));
580-
if (pageDate && !isNaN(pageDate.valueOf())) {
581-
var weekdays = new Array(
582-
"Sunday",
583-
"Monday",
584-
"Tuesday",
585-
"Wednesday",
586-
"Thursday",
587-
"Friday",
588-
"Saturday"
589-
);
590-
var day = pageDate.getDay();
591-
var div = document.createElement("DIV");
592-
div.className = "roam-title-day-banner";
593-
div.innerText = weekdays[day];
594-
div.style.fontSize = "10pt";
595-
div.style.top =
596-
-(
597-
Number(getComputedStyle(title).marginBottom.replace("px", "")) +
598-
6
599-
) + "px";
600-
div.style.position = "relative";
601-
title.insertAdjacentElement("afterend", div);
602-
}
603-
});
604-
};
669+
if (setting === "off") return;
605670

606-
const className = "rm-title-display";
607-
addDateToRoamTitleBanners(
608-
Array.from(document.querySelectorAll(`.${className}`))
609-
);
610-
observerHeadings = new MutationObserver((ms) => {
611-
const titles = ms
612-
.flatMap((m) =>
613-
Array.from(m.addedNodes).filter(
614-
(d) =>
615-
/^H\d$/.test(d.nodeName) &&
616-
(d as Element).classList.contains(className)
617-
)
618-
)
619-
.concat(
620-
ms.flatMap((m) =>
621-
Array.from(m.addedNodes)
622-
.filter((n) => n.hasChildNodes())
623-
.flatMap((d) =>
624-
Array.from((d as Element).getElementsByClassName(className))
625-
)
626-
)
627-
)
628-
.map((n) => n as HTMLHeadingElement);
629-
addDateToRoamTitleBanners(titles);
630-
});
631-
observerHeadings.observe(document.body, {
632-
childList: true,
633-
subtree: true,
634-
});
635-
}
671+
processExistingHeadings();
672+
setupHeadingObserver();
636673
},
637674

638675
saveUIChanges(UIValues: {
@@ -719,7 +756,7 @@ export const toggleFeature = (
719756
);
720757
component.initialize();
721758
} else {
722-
observerHeadings?.disconnect();
759+
cleanupHeadingObserver();
723760
unloads.forEach((u) => u());
724761
unloads.clear();
725762
}

0 commit comments

Comments
 (0)