16
16
from bs4 import BeautifulSoup
17
17
from loguru import logger
18
18
from natsort import natsort
19
+ from tqdm import tqdm
19
20
20
21
from .utils .file_util import replace_extension
22
+ from .utils .html_util import add_internal_links
21
23
from .utils .markdown_math import reconstructMath , sanitizeInput
22
- from .utils .note_util import beautify_content , sort_note_by_headings
24
+ from .utils .note_util import beautify_content , sort_note_by_headings , preprocess_note_title_list
23
25
from .utils .param_util import clean_param , format_query_string
24
- from .utils .time_util import get_today , get_yesterday , synchronize_dates , format_date_to_etapi , get_local_timezone
26
+ from .utils .time_util import (
27
+ get_today ,
28
+ get_yesterday ,
29
+ synchronize_dates ,
30
+ format_date_to_etapi ,
31
+ get_local_timezone ,
32
+ )
25
33
from .utils .image_util import compress_image_bytes , get_extension_from_image_mime
26
34
27
35
@@ -373,9 +381,10 @@ def patch_note(
373
381
}
374
382
res = requests .patch (url , json = clean_param (params ), headers = self .get_header ())
375
383
return res .json ()
376
- def handle_dates (self ,
377
- dateCreated : Optional [datetime ] = None ,
378
- utcDateCreated : Optional [datetime ] = None ):
384
+
385
+ def handle_dates (
386
+ self , dateCreated : Optional [datetime ] = None , utcDateCreated : Optional [datetime ] = None
387
+ ):
379
388
'''Ensure that both local and UTC times are defined, and have same time
380
389
(adjusted for timezone)'''
381
390
if not dateCreated and not utcDateCreated :
@@ -384,21 +393,21 @@ def handle_dates(self,
384
393
print (f"dateCreated is not datetime object, is { type (dateCreated )} " )
385
394
raise TypeError ("dateCreated must be a datetime object" )
386
395
if utcDateCreated and not isinstance (utcDateCreated , datetime ):
387
- print (f"utcdateCreated is not datetime object, is { type (utcDateCreated )} " )
396
+ print (f"utcdateCreated is not datetime object, is { type (utcDateCreated )} " )
388
397
raise TypeError ("utcDateCreated must be a datetime object" )
389
398
390
399
if dateCreated and dateCreated .tzinfo is None :
391
400
tzinfo = get_local_timezone ()
392
401
dateCreated = dateCreated .replace (tzinfo = tzinfo )
393
402
print (f"dateCreated.tzinfo was None. Changed to: { dateCreated .tzinfo } ." )
394
-
403
+
395
404
if utcDateCreated and utcDateCreated .tzinfo is None :
396
405
utcDateCreated = utcDateCreated .replace (tzinfo = dateutil .tz .tzutc ())
397
406
print (f'utc date tzinfo was None, forced to UTC ({ utcDateCreated } )' )
398
407
399
408
# After ensuring TZ is set for one of the date types, synchronize them
400
409
synchronized_dates = synchronize_dates (local_date = dateCreated , utc_date = utcDateCreated )
401
-
410
+
402
411
return synchronized_dates
403
412
404
413
# def synchronize_dates(self, local_date: Optional[datetime], utc_date: Optional[datetime]) -> tuple[Optional[datetime], Optional[datetime]]:
@@ -423,10 +432,10 @@ def handle_dates(self,
423
432
# print(f"\tlocal_date: {local_date}")
424
433
# print(f"\tutc_date : {utc_date}")
425
434
# return local_date, utc_date
426
-
435
+
427
436
# def get_local_timezone(self=None):
428
437
# print("Getting local_timezone...")
429
-
438
+
430
439
# # this is short and sweet
431
440
# local_timezone = datetime.now().astimezone().tzinfo
432
441
@@ -446,7 +455,7 @@ def handle_dates(self,
446
455
# UTC : '2023-08-22 01:38:51.110Z'
447
456
# and exactly 3 decimal places for seconds.'''
448
457
# if kind == 'local':
449
- # date = date.strftime('%Y-%m-%d %H:%M:%S.%d3%z')
458
+ # date = date.strftime('%Y-%m-%d %H:%M:%S.%d3%z')
450
459
# if kind == 'utc':
451
460
# date = date.astimezone(dateutil.tz.tzstr('Z')) # use Zulu time
452
461
# date = date.strftime('%Y-%m-%d %H:%M:%S.%d3%Z')
@@ -1499,6 +1508,96 @@ def delete_empty_note(self, note_title=None, verbose=False):
1499
1508
if verbose :
1500
1509
logger .info (content )
1501
1510
1511
+ def auto_create_internal_link (
1512
+ self ,
1513
+ target_note_id = None ,
1514
+ target_notes = None ,
1515
+ process_all_notes = False ,
1516
+ skip_clipped_notes = True ,
1517
+ skip_day_notes = True ,
1518
+ verbose = True ,
1519
+ ):
1520
+ """
1521
+ Create internal link for notes
1522
+ """
1523
+
1524
+ # Prepare note title and note id list
1525
+ # Get all note titles and note ids
1526
+ all_notes = self .search_note (search = "note.title %= '.*'" )
1527
+ all_note_title_list = []
1528
+ for x in all_notes ['results' ]:
1529
+ if x ['isProtected' ]:
1530
+ # Remove protected notes, they are not editable via ETAPI
1531
+ continue
1532
+ title = x ['title' ]
1533
+ note_id = x ['noteId' ]
1534
+ all_note_title_list .append ([title , note_id ])
1535
+
1536
+ # Process the note titles, handling duplicates and sorting
1537
+ processed_note_title_list = preprocess_note_title_list (all_note_title_list )
1538
+
1539
+ # prepare target note id
1540
+ if target_note_id :
1541
+ target_notes = [
1542
+ target_note_id ,
1543
+ ]
1544
+ elif target_notes :
1545
+ pass
1546
+ elif process_all_notes :
1547
+ # process all notes if not provided a note id list
1548
+ target_notes = [x [1 ] for x in all_note_title_list ]
1549
+
1550
+ # Add internal link
1551
+
1552
+ def get_child_note_title_note_id_list (note_id ):
1553
+ res = self .get_note (note_id )
1554
+ result = []
1555
+ for child_note_id in res ['childNoteIds' ]:
1556
+ x = self .get_note (child_note_id )
1557
+ result .append ([x ['title' ], x ['noteId' ]])
1558
+ return preprocess_note_title_list (result )
1559
+
1560
+ for note_id in tqdm (target_notes ):
1561
+
1562
+ # only process text note here
1563
+ current_note = self .get_note (note_id )
1564
+
1565
+ if verbose :
1566
+ logger .info (f'current note id: { note_id } title: { current_note ["title" ]} ' )
1567
+
1568
+ if not current_note ['type' ] == 'text' :
1569
+ if verbose :
1570
+ logger .info ('skip: not text note' )
1571
+ continue
1572
+
1573
+ if skip_clipped_notes and any (
1574
+ [x ['name' ] == 'pageUrl' for x in current_note ['attributes' ]]
1575
+ ):
1576
+ if verbose :
1577
+ logger .info ('skip: clipped note' )
1578
+ continue
1579
+
1580
+ if skip_day_notes and any (
1581
+ [x ['name' ] == 'dateNote' for x in current_note ['attributes' ]]
1582
+ ):
1583
+ if verbose :
1584
+ logger .info ('skip: day note' )
1585
+ continue
1586
+
1587
+ # add child note, we can handle sub notes with same name from different parent notes
1588
+ processed_child_note_title_list = get_child_note_title_note_id_list (note_id )
1589
+ tmp_list_for_current_note = processed_child_note_title_list + processed_note_title_list
1590
+
1591
+ content = self .get_note_content (note_id )
1592
+ updated_content , replaced = add_internal_links (
1593
+ content , tmp_list_for_current_note , current_note_id = note_id
1594
+ )
1595
+ # If content has changed, update the note
1596
+ if replaced :
1597
+ self .update_note_content (note_id , updated_content )
1598
+ if verbose :
1599
+ logger .info (f"Added internal link to note { note_id } ." )
1600
+
1502
1601
1503
1602
class ListTemplate (string .Template ):
1504
1603
"""Encapsulate To Do List HTML details
0 commit comments