While I wait for the day Apple adds a publish note link to Apple Notes, I have been trying to figure out how to do it with a workaround. This is the best I have come up with without having to involve another blogging service. It's not perfect, but here's how I did it: I found [a script by Bear](https://bear.app/faq/migrate-from-apple-notes/) (another notes app) that will export your Apple Notes as HTML. I used that (and some ChatGPT) to get it to only export notes in a Posts folder in my Notes app: ![Yesterday](https://hackmd.io/_uploads/rk7fcPKVA.png) Here's the script: ```oascript= set exportFolder to (choose folder) as string -- Function to delete all .html files in the chosen folder on deleteHTMLFilesInFolder(folderPath) tell application "Finder" set htmlFiles to every file of folder folderPath whose name ends with ".html" repeat with htmlFile in htmlFiles delete htmlFile end repeat end tell end deleteHTMLFilesInFolder -- Simple text replacing on replaceText(find, replace, subject) set prevTIDs to text item delimiters of AppleScript set text item delimiters of AppleScript to find set subject to text items of subject set text item delimiters of AppleScript to replace set subject to "" & subject set text item delimiters of AppleScript to prevTIDs return subject end replaceText -- Get an HTML file to save the note in. We have to escape -- the colons, or AppleScript gets upset. on noteNameToFilePath(noteName) global exportFolder set strLength to the length of noteName if strLength > 250 then set noteName to text 1 thru 250 of noteName end if set fileName to (exportFolder & replaceText(":", "_", noteName) & ".html") return fileName end noteNameToFilePath -- Delete all existing HTML files in the chosen folder deleteHTMLFilesInFolder(exportFolder) tell application "Notes" -- Limit the export to the "Posts" folder repeat with theNote in notes in folder "Posts" of default account set noteLocked to password protected of theNote as boolean set modDate to modification date of theNote as date set creDate to creation date of theNote as date if not noteLocked then -- File name composed only by note title set fileName to (name of theNote as string) set filepath to noteNameToFilePath(fileName) of me set noteFile to open for access filepath with write permission set theText to body of theNote as string set theContainer to container of theNote -- Export the folder containing the notes as tag in bear -- The try-catch overcomes a 10.15.7 bug with some folders try if theContainer is not missing value then set tag to name of theContainer set theText to ("" & theText & "#" & tag & "#") as string end if end try write theText to noteFile as «class utf8» close access noteFile tell application "Finder" set modification date of file (filepath) to modDate end tell end if end repeat end tell ``` I saved this as an automator application that I can run anytime I want. Here's a [ZIP](https://dropover.cloud/558de0e9a2fe582b367fc55cc6f2687d) of that application. When you run it, it will export any notes in the Posts folder in Notes.app into a folder you choose. ![CleanShot 2024-06-01 at 21.25.43@2x](https://hackmd.io/_uploads/BJjNqDFVR.png) This script will: - Export notes from a folder called Posts - Delete all *.html files in the folder you choose (in case you deleted anything) - Export all note into a .html file named with the title of the note I then used [shfs/MacFuse](https://sftptogo.com/blog/how-to-mount-sftp-as-a-drive-on-mac/) to mount an STFP folder on my mac: ![How I figured out how to publish an Apple Note online.html](https://hackmd.io/_uploads/B1kUqvFEA.png) ![CleanShot 2024-06-01 at 21.20.52@2x](https://hackmd.io/_uploads/BySvqPKE0.png) So, when I use the exporting app to select this folder, the new files will be uploaded via SFTP to a server automatically. I then wrote a simple PHP script to list out the HTML files (stored in `./html/`) and output the HTML when you select one, here's that script (index.php): ```php= <?php $post = $_GET['post'] ?? ''; if ( ! empty( $post ) ) { show_post( $post ); } else { show_posts(); } function get_base_post( $file ) { return basename( str_replace( '.html', '', $file ) ); } function get_post_files() { // Notice if your site is notes.aubreypwd.com, mount the ./html folder on your Mac to export to. $files = glob( __DIR__ . '/html/*.html' ); $sorted_files = array(); foreach ( $files as $file ) { $sorted_files[ filemtime( $file ) ] = $file; } ksort( $sorted_files ); return $sorted_files; } function show_posts() { ?> <?php the_header( 'Posts' ); ?> <div class="posts"> <ul class="post-list"> <?php foreach ( get_post_files() as $date => $file ) : ?> <li> <a href="?post=<?php echo get_base_post( $file ); ?>"><?php echo get_base_post( $file ); ?></a><br> <small><span class="date"><date><?php echo date( 'm/d/Y', $date ); ?></date></span></small> </li> <?php endforeach; ?> </ul> </div> <?php footer(); ?> <?php } function the_header( $title ) { ?> <!DOCTYPE html> <html lang="en" data-theme="light"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title><?php echo $title; ?></title> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.min.css"> <meta name="color-scheme" content="light dark" /> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.classless.blue.min.css" /> <style> body { /* zoom: 90%; */ } .site-title a { text-decoration: none; color: black;; } .site-title { font-size: 80%; border-bottom: 1px solid #dadada; padding-bottom: 30px; padding-top: 30px; } .post-date { margin-top: 30px; padding-top: 30px; border-top: 1px solid #dadada; } code { display:block; padding-left: 20px; background: none; } .post h1.post-title { margin-top: 20px; } .post > div { margin-bottom: var(--pico-typography-spacing-vertical); } img { border-radius: 3px; } .post-list { padding-left: 0; } .post-list li { list-style: none; padding-bottom: 5px; } </style> </head> <body> <main class="container"> <header><h1 class="site-title"><a href="../">👨🏻‍💻 Aubrey's Notes</a></h1></header> <?php } function footer() { ?> </main> </body> </html> <?php } function get_post_filename( $post ) { return __DIR__ . '/html/' . "{$post}.html"; } function show_post( $post ) { if ( ! file_exists( get_post_filename( $post ) ) ) { show_posts(); return; } ob_start(); ?> <?php the_header( $post ); ?> <div class="post"> <?php echo str_replace( array( '#Posts#', '<div><tt', '</tt></div', "\t", '<div><br></div>', ), array( '', '<code', '</code', '&nbsp;&nbsp;', '', ), file_get_contents( get_post_filename( $post ) ) ); ?> <p class="post-date"> <strong>Posted on: </strong> <date><?php echo date( 'm/d/Y', filemtime( get_post_filename( $post ) ) ); ?></date> </p> </div> <?php footer(); ?> <?php echo ob_get_clean(); } ``` It was dead-simple, but it worked! But now I can basically update any post in my Posts folder, run that export script, and just wait for a simple site to update! ![CleanShot 2024-06-01 at 21.44.44@2x](https://hackmd.io/_uploads/ryohcvYVC.png) As you can see, the images come out in base64, so there's no files to manage (I'm fine with that for now). ## But... I wasn't super happy with the solution, but wanted to share just in case it might be enough for someone else. I left it up on [notes.aubreypwd.com &rarr;](http://notes.aubreypwd.com)