Copy selected link(s) from browser as markdown?

Before I experiment, has anyone already done or found something like this ?

( An example context might be copying one or more links and pasting them as [label](url) markdown strings into one of these Discourse forum editing boxes )

UPDATE

This is similar,

but creates a markdown link to the current page rather than markdown versions of the links (to elsewhere) selected in the browser

UPDATE II

This is closer:

Thanks, Rob. All good tools.

Here’s my ideal solution:

Case:

  1. user has made a selection of a named link:
    • Copy selection as RTF named link
  2. selection is text only (no link):
    • Use text and page URL to create RTF named link
  3. No selection is made
    • Use page URL and Title to create RTF named link

Put on the clipboard:
• RTF named link
• Markdown text [name](url)

This way, if you paste into a RTF document, the receiving app would take the RTF version. If you paste into a text document/forum, then the app (like the KM Discourse forum) would take the text version.

Best of both worlds. :smile:

you could distinguish between 3. and the others ( any selection ?) with:

Boolean(window.getSelection().toString())

but perhaps 1. vs 2. might just fall on the wrong side of a keep it simple filter ?

( I might be inclined to break out the specific issue of copy link(s) as markdown to an item in a contextual menu )

but branching on [[ selection or not ? ]] seems a useful pattern.

I see your point, and would agree, except for the fact that there are too many web sites that have poor, non-descriptive (of the subject) page title. So while in many/most cases you could just have no selection and use page title/URL, in those cases it preferable to select a header or some text on the page that is more descriptive. In which case you need to get the URL from the page and the name from the selection.

Sorry to complicate things. If you want, you can start out with only cases #1 and #3. If I were more knowledgeable about this sort of stuff, I'd be glad to help with the coding.

If you use an Execute Javascript action in Chrome or Safari, you can gather document title and url, but also use XPath expressions to look for the text of the most recent heading (before the selection) and any #anchor id or name that may be attached to that heading.

// ver 0.1 Rob Trew @complexpoint 2015
(function () {
	'use strict';

	var nodeSeln = window.getSelection().anchorNode,
		lstH = [1, 2, 3, 4, 5, 6],
		strInHeading = lstH.map(function (x) {
			return 'ancestor-or-self::h' + x;
		}).join(' or '),

		lstAnchor = [
			'*[' + lstH.map(function (x) {
				return 'self::h' + x;
			}).join(' or ') + ']//*',
			'a[string(@name) and (' + strInHeading + ')]',
			'*[string(@id) and (' + strInHeading + ')]'
		].map(function (strType) {
			var strPath = './preceding::' + strType + '[1]',
				oNode = document.evaluate(
					strPath,
					nodeSeln,
					null, 0, 0
				).iterateNext(),
				strTag;

			return oNode ? {
				text: oNode.hasChildNodes() ?
					oNode.firstChild.textContent : oNode.textContent,
				tag: (strTag = oNode.getAttribute('name')) ?
					strTag : oNode.getAttribute('id')
			} : {};
		}),

		strHead = lstAnchor[0].text || lstAnchor[1].text || lstAnchor[2].text,
		dctName = lstAnchor[1],
		dctID = lstAnchor[2];

	// Page name, url, heading text, heading #id or #name
	return [
		document.title,
		document.URL.split('#')[0],  // drop any page=load anchor
		strHead,
		strHead ? (  // get any local anchor for the selection
			(dctName.text === strHead) ? dctName.tag :
			(dctID.text === strHead) ? dctID.tag : ''
		) : ''
	].join('	');
})();

Here's a first rough (Chrome-only) sketch.

Copy from Chrome as linked RTF.kmmacros (25.8 KB)

I'll see if can get a moment tomorrow evening to do various things:

  • make it work with Safari
  • add optional 'under the heading:' and 'source:' prefixes
  • sort out the css

etc

( I'd like to wrap it as something like this:

Rob, I'm a bit confused. Is this macro supposed to create the markdown code? If so, it's not working for me.

When I try to paste here, I get nothing.
When I paste into a RTF document (Evernote), I get the link I selected and a named link for the page.

Forgive me – I should have explained and labelled

That draft just creates the RTF version, with links to source and to any header#anchor

Life was a bit full this evening, but tomorrow night I’ll aim to package it as a plugin with a markdown option.

No problem. Take your time. I just wanted to make sure I wasn't missing something. :smile:

FWIW here is a simple interim macro:

Copy slngle selected link as Markdown (Chrome or Safari)

Copy single link as Markdown.kmmacros (24.0 KB)

It executes this in the foreground browser (Chrome or Safari):

(function (oNode) {

	var oLink = document.evaluate(
		"./ancestor-or-self::a",
		oNode,
		null, 0, 0
	).iterateNext();

	return oLink ? 
		"[" + oLink.text + "](" + oLink.href + ")" :
		"";

})(window.getSelection().anchorNode);

Works great after I made one change:
In the "Execute JavaScript..." Action, set the output to "paste results"

Thanks for sharing all the great work with us.

If you can tell me (or point me to an example) of how to put BOTH plain text (like on this macro) AND RTF on the same clipboard, I will combine your macros to give us the best of both worlds. :smile:

From Applescript you would essentially create a record with two fields:

  • Unicode Text :
  • «class RTF » : «data RTF »

and then set the clipboard contents to that record.

Skipping the details of obtaining hex-encoded RTF:

set strText to "hello world!"
set strRTF to "{\\rtf1\\ansi\\ansicpg1252\\cocoartf1348\\cocoasubrtf170
{\\fonttbl\\f0\\fswiss\\fcharset0 Helvetica;}
{\\colortbl;\\red255\\green255\\blue255;\\red255\\green39\\blue18;}
\\pard\\tx566\\tx1133\\tx1700\\tx2267\\tx2834\\tx3401\\tx3968\\tx4535\\tx5102\\tx5669\\tx6236\\tx6803\\pardirnatural

\\f0\\fs24 \\cf2 hello\\cf0
\\b world
\\b0  !\\
\\
\\
"
-- Hex encoding process not shown
set dataPackedRTF to «data RTF 7B5C727466315C616E73695C616E7369637067313235325C636F636F61727466313334385C636F636F617375627274663137300A7B5C666F6E7474626C5C66305C6673776973735C6663686172736574302048656C7665746963613B7D0A7B5C636F6C6F7274626C3B5C7265643235355C677265656E3235355C626C75653235353B5C7265643235355C677265656E33395C626C756531383B7D0A5C706172645C74783536365C7478313133335C7478313730305C7478323236375C7478323833345C7478333430315C7478333936385C7478343533355C7478353130325C7478353636395C7478363233365C7478363830335C7061726469726E61747572616C0A0A5C66305C66733234205C6366322068656C6C6F5C63663020200A5C6220776F726C640A5C62302020217D»

set recClip to {Unicode text:strText, «class RTF »:dataPackedRTF}
set the clipboard to recClip

UPDATE

PS you shouldn’t have to hex-encode raw RTF strings because textutil can place RTF in the clipboard for you, but if you did need to, you should be able to use the bash xxd command:

set dataPackedRTF to (run script "«data RTF " & (do shell script "echo " & quoted form of strRTF & " | xxd -p -u ") & "»")

OK, I'm almost there, but I am so lost with the shell script commands.

From your previous macro, you used the following to put RTF on the clipboard:

    set lstrCMD to "echo " & quoted form of pstrHTML & " | textutil -format html -convert rtf -stdin -stdout | pbcopy -Prefer rtf"

do shell script lstrCMD

How do I combine the two to set the dataPackedRTF using HTML code (the pstrHTML variable) ?

BTW, how do you get the color code in your above post?

Combining the two

  1. Assemble the content you want in HTML, and place an RTF version of it in the clipboard with textutil
  2. Adjust the text content of the clipboard, leaving the RTF unchanged:

Syntax highlighting

just use 3 backticks before and after the code – see under Github Markdown fenced code

-- Read the text and RTF components of the clipboard
set recClip to the clipboard as record
set strText to Unicode text of recClip
set dataRTF to «class RTF » of recClip

-- Derive an MD link version of the text
set {dlm, my text item delimiters} to {my text item delimiters, space}
set lstTokens to (text items of strText)
set my text item delimiters to "+"
set strTokens to lstTokens as string
set my text item delimiters to dlm

set strMDLink to "[" & strText & "](http://www.google.com/?=" & strTokens & ")"


-- New text contents, with existing RTF contents
set the clipboard to {Unicode text:strMDLink, «class RTF »:dataRTF}

-- inspect the result
the clipboard as record

OK, great! I got it! Many thanks.
Now let's see what I can do . . .

1 Like

OK, I patched together a very simple example/test, based mostly on your above code.
However, I got one error on your code.
The line:

set strText to Unicode text of recClip

gave this error:
Can’t get Unicode text of {«class RTF »:«data RTF

So, to keep things real simple for the example, I replaced your code to create the MD link with simple assignments.

Download AppleScript from here:
CB - How to Put Plain Text and Rich Text on Clipboard.scpt.zip (6.6 KB)

(*
How to Put BOTH Plain Text AND Rich Text on the same Clipboard

Assemble the content you want in HTML, and place an RTF version of it in the clipboard with textutil
Adjust the text content of the clipboard, leaving the RTF unchanged

When you paste into a Rich Text document, the rich text will be pasted.
When you paste into a plain text document (like the KM forum), the markdown text will be pasted.

AUTHOR:
	• ComplexPoint 
		• provided all of the hard code to create RTF and combine on clipboard
	• JMichaelTX 
		• simply added some simple prep code to this
	
VER:  0.2		DATE: Mon, Jul 20, 2015
*)

--- VERY SIMPLE EXAMPLE OF HTML CODE TO BE CONVERTED TO RICH TEXT ---

property gStyleLink : "color:blue"
set strFont to "font-family:verdana,geneva,sans-serif;"
set strLinkFontSize to "font-size:14px;"


set strHTMLLink to my createHTMLLink("Evernote Site", "http:/www.evernote.com")
set strHTMLLink to "<span style=\"" & strFont & strLinkFontSize & "\">" & strHTMLLink & "</span>"
set pstrHTML to strHTMLLink


-- PUT THE RTF Text on the Clipboard
set lstrCMD to "echo " & quoted form of pstrHTML & " | textutil -format html -convert rtf -stdin -stdout | pbcopy -Prefer rtf"
do shell script lstrCMD

-- Read the text and RTF components of the clipboard
set recClip to the clipboard as record
### set strText to Unicode text of recClip  -- gives error
set dataRTF to «class RTF » of recClip

--- My Simple Code to set Markdown Link ---

set strText to "Evernote Web Site"
set strURL to "http://www.evernote.com"
set strMDLink to "[" & strText & "](" & strURL & ")"


-- New text contents, with existing RTF contents
set the clipboard to {Unicode text:strMDLink, «class RTF »:dataRTF}

-- inspect the result
the clipboard as record

--—————————————————————————————————————————————————

on createHTMLLink(pstrLinkText, pstrURL)
	
	return "<a href=\"" & pstrURL & "\" style=\"" & gStyleLink & "\">" & pstrLinkText & "<a>"
	
end createHTMLLink

Comments/suggestions anyone?

Good !

The line:

set strText to Unicode text of recClip

gave this error: Can’t get Unicode text of {«class RTF »:«data RTF

The clipboard won't always have a Unicode text field.

( Applescript at its least impressive, alas – you can't ask a record for a list of the available fields, and error-handling is the only way to learn whether a particular field exists – in short no introspection )

Another approach – does all the Markdown conversion of the currently selected link or sentence in the browser.

( based on David Bengoa's https://gist.github.com/YouWoTMA/1762527, and using the following XPath to define the scope of the copy, in relation to the selection:

``
./ancestor-or-self::*[self::a or self::stuck_out_tongue: or self::ul or self::ol or self::tr or self::blockquote or " +
"self::h1 or self::h2 or self::h3 or self::h4 or self::h5 or self::h6][1]`

)

<a class="attachment" href="/uploads/default/original/2X/7/75552bd4a807fcf8205288205fbac8506cc73250.kmmacros">Copy link or sentence from Browser as Markdown.kmmacros</a> (31.3 KB) 

<img src="/uploads/default/original/2X/8/854c939198930d2f40733bbef24bbc5ff78ed101.png" width="452" height="642">

Hey Rob,

This is not entirely true now with advent of ASObjC:

use framework "Foundation"

set myRecord to {var1:"User", var2:"Record"}
set recordKeyList to (current application's NSDictionary's dictionaryWithDictionary:myRecord)'s allKeys() as list

Unfortunately this method will only work with user-records and not system-records.

In this particular instance with the clipboard we can do something like this:

clipboardContainsUnicodeText()

on clipboardContainsUnicodeText()
  repeat with i in (get clipboard info)
    if i contains Unicode text then
      return true
    end if
  end repeat
  return false
end clipboardContainsUnicodeText

Although I will admit it is seriously kludgey.

Or to be a bit more flexible:

recordLabelsContain(Unicode text)

on recordLabelsContain(labelName)
  set labelList to {}
  set clipboardInfoRec to clipboard info
  repeat with i in clipboardInfoRec
    set end of labelList to item 1 of i
  end repeat
  return labelName is in labelList
end recordLabelsContain

-Chris

1 Like