Skip to content

Commit 32ddc16

Browse files
committed
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 parent dd09fad commit 32ddc16

1 file changed

Lines changed: 131 additions & 88 deletions

File tree

src/features/dailyNotesPopup.tsx

Lines changed: 131 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,139 @@ 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 DNP case: banner is next sibling of heading
618+
const nextSibling = heading.nextElementSibling;
619+
if (nextSibling && nextSibling.classList.contains(DAY_BANNER_CLASS)) {
620+
return true;
621+
}
622+
623+
// Check page/sidebar case: banner is next sibling of container
624+
const container = heading.closest(`.${ROAM_TITLE_CONTAINER_CLASS}`);
625+
if (container) {
626+
const containerNextSibling = container.nextElementSibling;
627+
if (
628+
containerNextSibling &&
629+
containerNextSibling.classList.contains(DAY_BANNER_CLASS)
630+
) {
631+
return true;
632+
}
633+
}
634+
635+
return false;
636+
};
637+
const addDateToRoamTitleBanner = (heading: HTMLHeadingElement): void => {
638+
if (hasExistingBanner(heading)) return;
639+
640+
const pageDate = parseDateFromHeading(heading.innerText);
641+
if (!pageDate) return;
642+
643+
const dayOfWeek = pageDate.getDay();
644+
const banner = createDayBanner(dayOfWeek, heading);
645+
insertBanner(banner, heading);
646+
};
647+
const processExistingHeadings = (): void => {
648+
const existingHeadings = document.querySelectorAll(`.${ROAM_TITLE_CLASS}`);
649+
existingHeadings.forEach((heading) => {
650+
addDateToRoamTitleBanner(heading as HTMLHeadingElement);
651+
});
652+
};
653+
654+
// Daily Note Subtitles / Banner - Observer
655+
let observerHeadings: { disconnect: () => void } | undefined = undefined;
656+
const setupHeadingObserver = (): void => {
657+
observerHeadings = createHTMLObserver({
658+
tag: "H1",
659+
className: ROAM_TITLE_CLASS,
660+
callback: (element) =>
661+
addDateToRoamTitleBanner(element as HTMLHeadingElement),
662+
});
663+
};
664+
const cleanupHeadingObserver = (): void => {
665+
if (observerHeadings) {
666+
observerHeadings.disconnect();
667+
observerHeadings = undefined;
668+
}
669+
};
670+
671+
// Main Daily Notes Popup Component
546672
export const component = {
547673
async initialize() {
548674
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-
};
675+
if (setting === "off") return;
605676

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-
}
677+
processExistingHeadings();
678+
setupHeadingObserver();
636679
},
637680

638681
saveUIChanges(UIValues: {
@@ -719,7 +762,7 @@ export const toggleFeature = (
719762
);
720763
component.initialize();
721764
} else {
722-
observerHeadings?.disconnect();
765+
cleanupHeadingObserver();
723766
unloads.forEach((u) => u());
724767
unloads.clear();
725768
}

0 commit comments

Comments
 (0)