""" Created By: Jap-Slappa Date: 15th/Aug/2026 * Mod 01 Joplin Template Funcs v1.0 Purpose: - Convert windows folder + File paths into URL paths for use in Joplin """ # Tell Python to look in your C-Drive helper folder (Or the folder on your Laptop) # Better to have my Helper Modules on the PC using them. (But using them from my NAS might work?) # Use raw strings (r"...") to handle Windows backslashes properly import sys sys.path.append(r"C:\My_Python_Helper_Modules") import Get_Date_and_Time__v1 as Get_Date_and_Time import Error_Handling import re import PySimpleGUI as sg import pyperclip import time import Mod_01_Joplin_Template_Funcs_v1 as Joplin_Funcs_Mod from Mod_02_Joplin_Template_Class_v1 import Share_Text from pathlib import Path, PureWindowsPath from urllib.parse import quote from yt_dlp import YoutubeDL # This works perfectly now (13th/Aug/2026) # Written by ChatGPT, then I customized for my usage. def windows_path_to_markdown(path: str, link_text: str | None = None) -> str: """ Convert a Windows file/folder path into a Joplin-compatible Markdown file:// link. - Joplin can then open the converted Path (File or Folder) Example: G:\\My Folder\\My File.pdf Returns: [My File.pdf](file:///G:/My%20Folder/My%20File.pdf) """ # Convert the Windows path to a Path object windows_path = Path(path) pure_windows_path = PureWindowsPath(path) # Convert backslashes to forward slashes converted_WinPath = windows_path.as_posix() # URL-encode Chars (spaces=%20), + (+=2B%), brackets (=%28, )=%29, etc. converted_WinPath = quote(converted_WinPath, safe="/:") Share_Text.cls_converted_WinPath = converted_WinPath try: # Use the filename/folder name as the link text if link_text is None: link_text = windows_path.name # Debug_Print_Paths(windows_path, link_text, converted_WinPath) # Change the format of the final Joplin Link, # depending on whether it's a File or Folder # Nas Paths need x4 forward slashes in Joplin if pure_windows_path.anchor.startswith("\\\\"): # if '//192.168.4.212' in str(windows_path): print('======= Function: windows_path_to_markdown() ======= ') print('======= The Pasted URL is for my NAS!... ============ \n') Joplin_Folder_Path = NAS_URL_Formatter(windows_path, link_text, converted_WinPath) # Save the final Joplin Link into my Class Share_Text.cls_complete_Joplin_Path = Joplin_Folder_Path return Share_Text.cls_complete_Joplin_Path # Win Paths need x3 forward slashes in Joplin elif windows_path.is_file(): Joplin_Folder_Path = f"[Win_File: {link_text}](file:///{converted_WinPath})" # Save the final Joplin Link into my Class Share_Text.cls_complete_Joplin_Path = Joplin_Folder_Path return Share_Text.cls_complete_Joplin_Path elif windows_path.is_dir(): Joplin_Folder_Path = f"[Win_Folder: {link_text}](file:///{converted_WinPath})" # Save the final Joplin Link into my Class Share_Text.cls_complete_Joplin_Path = Joplin_Folder_Path return Share_Text.cls_complete_Joplin_Path else: Error_In_Path_or_URL(windows_path) # Save the final Joplin Link into my Class Share_Text.cls_complete_Joplin_Path = f'User Input Was NOT A Path = {windows_path}' return Share_Text.cls_complete_Joplin_Path except Exception as Error: Error_Handling.basic_custom_error() # ? ==== End Of Function ==== def NAS_URL_Formatter(nas_url: Path, link_text, converted_WinPath): # Debug_Print_Paths(nas_url, link_text, converted_WinPath) try: # Change the format of the final Joplin Link, # depending on whether it's a File or Folder # Nas Paths only need x4 forward slashes in Joplin if nas_url.is_file(): Joplin_NAS_Folder_Path = f"[NAS_File: {link_text}](file://{converted_WinPath})" return Joplin_NAS_Folder_Path elif nas_url.is_dir(): Joplin_NAS_Folder_Path = f"[NAS_Folder: {link_text}](file://{converted_WinPath})" return Joplin_NAS_Folder_Path except Exception as Error: Error_Handling.basic_custom_error() # Final Check that all is correct Debug_Print_Paths(nas_url, link_text, converted_WinPath) #? ==== End Of Function ==== def Error_In_Path_or_URL(Error_Text:str) ->None: # Handle Non WinPath string error popup_title = 'ERROR!!' popup_msg = (f'\nYour Input Was NOT A Path or URL! \n' f' >> {Error_Text} << \n\n' f'Your Input MUST Be!... \n' f'1. A File Path \n' f' or... \n' f'2. A Folder Path \n' f' or... \n' f'2. A Valid Web URL \n\n' f'Please Press Reset & Try Again \n' ) sg.popup_error(popup_msg, title=popup_title, font=('Arial', 12, 'bold'), ) #? ==== End Of Function ==== def check_if_url_is_valid(url:str) ->bool: # Using re.findall() # re.findall() function in Python is used to find all occurrences # of a pattern in a given string and return them as a list. # # https?://\S+ matches URLs starting with http:// or https://. # www\.\S+ matches URLs starting with www. # findall(): returns all matches in a list. pattern = r'https?://\S+|www\.\S+' re_result = re.match(pattern, url) print("URLs:", ) # if url.find("https:") == 0 or url.find("http:") == 0: # Other option if re_result != None: return True else: return False #? ==== End Of Function ==== def YouTube_URL_Formatter(url: str) -> str: """ Get the YouTube video title and create a Joplin Markdown link. Example result: [YouTube: Actual Video Title](https://www.youtube.com/watch?v=...) """ try: if check_if_url_is_valid(url) == True: options = { "quiet": True, "no_warnings": True, "skip_download": True, } with YoutubeDL(options) as ydl: info = ydl.extract_info(url, download=False) title = info["title"] return f"[YouTube: {title}]({url})" else: Error_In_Path_or_URL(url) except Exception: Error_Handling.basic_custom_error() return f"[YouTube: Video]({url})" #? ==== End Of Function ==== def YT_Search_URL_Formatter(url: str) -> str: """ Get the YouTube video title and create a Joplin Markdown link. Example result: [YouTube: Actual Video Title](https://www.youtube.com/watch?v=...) """ try: if check_if_url_is_valid(url) == True: return f"[YouTube Search For.. Docker App Tutorials]({url})" else: Error_In_Path_or_URL(url) except Exception: Error_Handling.basic_custom_error() return f"[YouTube: Search]({url})" #? ==== End Of Function ==== def Web_Search_URL_Formatter(url: str, search_engine_name:str) -> str: """ Get the YouTube video title and create a Joplin Markdown link. Example result: [YouTube: Actual Video Title](https://www.youtube.com/watch?v=...) """ try: if check_if_url_is_valid(url) == True: if search_engine_name == 'cls_SearXNG_Search': return f"[SearXNG Search For.. Docker App]({url})" elif search_engine_name == 'cls_Google_Search': return f"[Google Search For.. Docker App]({url})" else: Error_In_Path_or_URL(url) except Exception: Error_Handling.basic_custom_error() return f"[Web Search]({url})" #? ==== End Of Function ==== def Debug_Print_Paths(windows_path, link_text, converted_WinPath): print() print('======= Function: Debug_Print_Paths() ======= ') print('=======The Pasted URL is for my NAS!... ======= ') print('..........windows_path: \n', windows_path) print('---') print('........link_text: \n', link_text) print('---') print('converted_WinPath: \n', converted_WinPath) print('---') print() #? ==== End Of Function ==== def debug_print(): print('The OG WinPath...') print(Share_Text.cls_converted_WinPath) print('---') print('The Converted WinPath...') print(Share_Text.cls_converted_WinPath) print('---') print('The Completed Joplin_Folder_Path...') print(Share_Text.cls_complete_Joplin_Path) print() #? ==== End Of Function ==== def reset_class_attr(wanted_class_attr: str): try: print('=== Function: reset_class_attr() === ') if wanted_class_attr == 'cls_docker_app_name': Share_Text.cls_docker_app_name = '' #? === DEBUG Print === # print('Share_Text.cls_docker_app_name =',Share_Text.cls_docker_app_name) # print() return Share_Text.cls_docker_app_name # === OG Windows Paths === elif wanted_class_attr == 'cls_OG_WinPath': Share_Text.cls_OG_WinPath = '' elif wanted_class_attr == 'cls_converted_WinPath': Share_Text.cls_converted_WinPath = '' elif wanted_class_attr == 'cls_complete_Joplin_Path': Share_Text.cls_complete_Joplin_Path = '' # === Web URLs === elif wanted_class_attr == 'cls_GitHub_URL': Share_Text.cls_GitHub_URL = '' elif wanted_class_attr == 'cls_Home_Page_URL': Share_Text.cls_Home_Page_URL = '' elif wanted_class_attr == 'cls_Docs_URL': Share_Text.cls_Docs_URL = '' # === NEW Windows Paths === elif wanted_class_attr == 'cls_folder_path': Share_Text.cls_folder_path = '' elif wanted_class_attr == 'cls_MD_File_path': Share_Text.cls_MD_File_path = '' elif wanted_class_attr == 'cls_Compose_File_path': Share_Text.cls_Compose_File_path = '' # === TouTube Video Tutorial URLs === elif wanted_class_attr == 'cls_YT_Tut_Vid_01': Share_Text.cls_YT_Tut_Vid_01 = '' elif wanted_class_attr == 'cls_YT_Tut_Vid_02': Share_Text.cls_YT_Tut_Vid_02 = '' elif wanted_class_attr == 'cls_YT_Tut_Vid_Search': Share_Text.cls_YT_Tut_Vid_Search = '' # === Template === # elif wanted_class_attr == 'cls_YT_Tut_Vid_Search': # Share_Text.cls_YT_Tut_Vid_Search = '' except Exception: Error_Handling.basic_custom_error() #? ==== End Of Function ==== def reset_psg_window(psg_window: sg.Window, button_key: str, wanted_class_attr: str): try: # Update/Clear the Window Input widget psg_window[f'-{button_key}-'].update('') #? === DEBUG Print === # print('=== Function: reset_button() === ') # print(f'(BEFORE Attr Reset) Share_Text.{wanted_class_attr} ==', # Share_Text.cls_docker_app_name) # print('---') # Reset/Clear the Class Attr test_cls_is_reset = reset_class_attr(wanted_class_attr) #? === DEBUG Print === (Leave this as confirmation for the user) # Confirm the Class Attr data HAS been deleted print(f'(AFTER Attr Reset) Share_Text.{wanted_class_attr} ==', test_cls_is_reset) print('---') print() except Exception: Error_Handling.basic_custom_error() #? ==== End Of Function ====