@@ -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,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 } ) ( 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 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
546666export 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 } ) ( 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- } ;
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