Skip to content

v0.9.3

Latest

Choose a tag to compare

@github-actions github-actions released this 25 Sep 12:29

Two one-time steps

This build is not signed with an Apple Developer ID and not notarized, so macOS blocks
the first launch. Allow it once:

  • macOS 15 Sequoia and later β€” double-click, dismiss the warning, then open
    System Settings β–Έ Privacy & Security and click Open Anyway. Apple removed the
    Control-click shortcut for unsigned apps in macOS 15, so right-clicking does not help.
  • macOS 13–14 β€” right-click the app β–Έ Open, then confirm.

Then answer the folder prompts once β€” macOS asks separately for Desktop, Documents and
Downloads the first time the app looks at them. Allow them and it stays quiet from then on.

Markdown and HTML as PDFs, from the renderer that already draws them β€” and the five ways an SFTP
site could be left unable to log in at all.

Added

  • Markdown and HTML documents export as PDFs. With the cursor on a .md or .html file,
    Commands β–Έ Export to PDF… β€” and the same entry in the panel's context menu β€” writes the
    document as a PDF beside it, under its own name; a marked selection is exported one file after
    another. The PDF is the page F3 shows, because it is produced by the same renderer: the same
    stylesheet, the same coloured fences, the same Mermaid diagrams and KaTeX formulae, and the same
    two web-view policies (the generated Markdown page may run the engines, a foreign .html file may
    run nothing). A4 or US Letter, a setting; a second export is numbered rather than overwriting the
    first, unless that is asked for; the panel moves its cursor to the new file, and when that is
    switched off a short message takes its place so an export is never silent. Offered on ordinary
    folders only β€” inside an archive or on a mounted drive the cursor's path is a VFS path with no
    file behind it on disk and nowhere beside it to write, so the entry is greyed out there.

    Pagination is done in the plugin rather than by WebKit, and that is measured rather than a
    preference: WKWebView.printOperation does not return on this platform β€” op.run() wrote a
    gigabyte of repeated pages in ninety seconds, first in a standalone harness and then in the
    application, with the print view's own frame reported as 0Γ—0. So the document is measured,
    the page breaks are put at the block boundaries the page itself reports β€” and never directly below
    a heading, which is the one break a reader notices β€” each page is captured with createPDF, and
    the slices are composed onto real sheets with Core Graphics. Anything that will not fit the text
    column shrinks the sheet to fit rather than running off its right edge: an eight-column table was
    measured at 604 points inside a 487-point column and lost its last two columns, silently. The whole thing is a contributed
    command: the host builds the menu entry from the plugin's manifest without loading it, so a
    removed or disabled plugin takes the entry with it. There is deliberately no switch for the export
    itself: nothing in the plugin's settings can be read at the moment the entry is built, so such a
    switch could only make the command refuse β€” a menu entry that is present and says no. PDFExport=
    in an older markdown.ini is ignored.

Fixed

  • A diagram that hung took its own source with it. The Markdown plugin's rule is that a figure
    which cannot be drawn says so where it was, with the block's source below it β€” "a silently
    missing figure is the failure that gets reported as 'the viewer lost my text'". The parse-error
    path obeyed it; the path where Mermaid's promise never settles did not, because the .catch that
    restores the source never ran. The <pre> had already been replaced by an empty holder, so the
    diagram and its text were simply gone β€” in the viewer and in an exported PDF, with nothing said.
    Found by deliberately arming a render() that never settles, which is what a broken engine file in
    the reader's own folder amounts to. The page now carries its own ten-second deadline per diagram,
    so the rule holds on the same path as a parse error.

  • Choosing SFTP locked a connection to the anonymous login (#4). A new site starts with
    "Anonymous" ticked, and SSH has no anonymous login β€” so picking SFTP greyed the box out while it
    was still ticked, and .anonymous is what disables the user name and the password as well. All
    three controls went dead together and the only way back was greyed out with them: the site could
    not be given a user name at all, short of switching to FTP, unticking, and switching back. It was
    saved that way too, so everyone who met this has auth=anonymous under protocol=sftp in their
    ftp-sites.ini β€” such a site is read as an ordinary password (or key) login now, without the file
    being rewritten behind anyone's back, and the dialog opens it unlocked. The rule lives with the
    others in FtpConnectionRules, so the greyed-out controls, the warning label and the connection
    all still say the same thing. Picking SFTP now clears the box and the name "anonymous" it had put
    in the field, which is what makes the dialog ask for a real one.

  • A bare sftp:// URL logged in as "anonymous" (#4). The same forcing sat on the quick-connect
    route (Ctrl+N): sftp://host without a user name became the user anonymous instead of the
    local account, although the URL parser had deliberately left it empty for exactly that fallback.
    It logs in as the local account now; ftp:// is unchanged, since there the anonymous login is
    real and is what a bare URL means.

  • The passphrase of an encrypted SSH key was asked for and then thrown away. The secret field is
    deliberately enabled for a key file β€” libssh2 wants the passphrase instead of a password, and
    that field is where it is typed β€” but the site's secret was written to the Keychain only when the
    login was a plain password. So the passphrase was read back on every selection and never once
    stored, and had to be retyped every session. It is kept whenever the field is offered at all,
    which is the same question the dialog already asks in order to enable it.

  • An ssh-agent login was silently demoted to a password login. auth=agent is a value only a
    hand-written ftp-sites.ini carries β€” no control in the dialog sets it β€” and the next keystroke
    in any field rewrote it to auth=password. It is kept now while nothing in the form contradicts
    it. The secret field is also no longer disabled for it: nothing in the dialog could set .agent
    and nothing could leave it either, so such a site could not be given a password at all. An empty
    field still sends no secret; typing in one is how a site stops being an agent site.

  • A tick that no longer applied was carried over behind a greyed-out box. "Transfer via SCP" and
    "Accept self-signed certificate" stayed set when the protocol moved to one that has no such
    setting, and were written to ftp-sites.ini that way β€” so a site that had been FTPS once accepted
    any certificate again the moment it became FTPS a second time, without anybody choosing it twice.
    Changing the protocol now clears the boxes the new protocol has no such thing for, visibly and by
    the user's own action.