@@ -11,8 +11,8 @@ import getUids from "roamjs-components/dom/getUids";
1111import getPageTitleByPageUid from "roamjs-components/queries/getPageTitleByPageUid" ;
1212import type { OnloadArgs } from "roamjs-components/types" ;
1313import { addCommand } from "./workBench" ;
14+ import createHTMLObserver from "roamjs-components/dom/createHTMLObserver" ;
1415
15- let observerHeadings : MutationObserver | undefined = undefined ;
1616let closeDailyNotesPopup : ( ( ) => void ) | undefined ;
1717
1818export 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 } ) ( s t | n d | r d | t h ) , $ / ) ;
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
546672export 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 } ) ( s t | n d | r d | t h ) , $ / ) ;
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