By the way, here's the current library that I'm using to send rich text. In some ways, it works wonderfully, but its still a work in progress.
To send a markdown demo, just type mddemo. After sending the demo, press ^!h to see the actual HTML that was sent.
Perhaps most helpfully, to see the Source code of the HTML on your clipboard, just press ^!c. You can use this to see how various programs export their formatted rich text into the clipboard's HTML Format.
gets credit for all the Markdown⇒HTML conversion. At this point, the actual code, omitting the demo text, is only about 650 lines long!
Code: Select all
;##################################################################################################################
;##################################################################################################################
;############
;############ MDSend
;############
;##################################################################################################################
;##################################################################################################################
; The following is just a demo of combining code by TheArkive and SKAN/teadrinker/FanaticGuru.
; Very little of the following is my own work.
; It just combines two scripts:
; 1.) For putting HTML in the clipboard, SetClipboardHTMLtext, by teadrinker and FanaticGuru:
; https://www.autohotkey.com/boards/viewtopic.php?f=82&t=115178
; 2.) For transforming Markdown into HTML, M-ArkDown (Ark_make_html) by TheArkive:
; https://github.com/TheArkive/M-ArkDown_ahk2/blob/master/_MD_Gen.ahk
LastSent:=""
^!h::ShowText(LastSent)
^!c::ShowText(ExtractHtmlData())
;^!v::PasteClipAsHTML()
::mddemo::{
MDSend(SampleText())
}
PasteClipAsHTML() {
}
MDSendLine(MarkDownText, RestoreClipboard:=True) {
options := {css: "p {margin: 0;}", linemode:true}
XSLog("MDSendLine() is about to make_HTML")
HTML := Ark_make_html(MarkDownText, options)
XSLog("MDSendLine() finished make_HTML")
HTMLSend(HTML, RestoreClipboard, MarkDownText)
}
MDSend(MarkDownText, RestoreClipboard:=True, PlainText:=MarkDownText) {
options := {css: "p {margin: 0;}"}
XSLog("MDSend() is about to make_HTML")
HTML := Ark_make_html(MarkDownText, options)
XSLog("MDSend() is about to call HTMLSend()")
HTMLSend(HTML, RestoreClipboard, PlainText)
}
HTMLSend(HTML, RestoreClipboard:=True, PlainText:=HTML) {
Global LastSent := HTML
If RestoreClipboard
SavedClip := ClipboardAll()
A_Clipboard := ""
XSLog("HTMLSend() is about to set clipboard")
TD_Condensed_SetClipboardHTMLtext(HTML, PlainText)
; wc.SetHTML(HTML)
XSLog("HTMLSend() just set clipboard")
If ClipWait(1, True) {
XSLog("HTMLSend() finished ClipWait - about to Send '^v'")
Send '^v'
} Else MsgBox 'An error occurred while waiting for the clipboard.', 'Error', 48
If RestoreClipboard {
Sleep 150
XSLog("HTMLSend() Sent '^v' and slept 150. Now restoring clipboard.")
A_Clipboard := SavedClip
SavedClip := ""
}
}
TextSend(Text, RestoreClipboard:=True) {
If RestoreClipboard
SavedClip := ClipboardAll()
XSLog("TextSend() is about to blank clipboard")
A_Clipboard := ""
XSLog("TextSend() has blanked clipboard. About to reset it.")
A_Clipboard := Text
XSLog("TextSend() just set clipboard")
If ClipWait(1, True) {
XSLog("TextSend() finished ClipWait - about to Send '^v'")
Send '^v'
} Else MsgBox 'An error occurred while waiting for the clipboard.', 'Error', 48
If RestoreClipboard {
Sleep 150
XSLog("HTMLSend() Sent '^v' and slept 150. Now restoring clipboard.")
A_Clipboard := SavedClip
SavedClip := ""
}
}
; Credit: teadrinker and FanaticGuru - https://www.autohotkey.com/boards/viewtopic.php?p=513124#p513671
; Helpful: https://learn.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format
TD_Condensed_SetClipboardHTMLtext(Html, Text := Html)
{
Html := 'Version:0.9`nStartHTML:-1`nEndHTML:-1`nStartFragment:00074`nEndFragment:' Format('{:05u}', StrLen(Html) + 74) '`n' Html
DllCall('OpenClipboard', 'Ptr', A_ScriptHwnd)
DllCall('EmptyClipboard')
hMem := DllCall('GlobalAlloc', 'UInt', 0x42, 'Ptr', StrPut(Html, 'cp0'), 'Ptr')
StrPut(Html, DllCall('GlobalLock', 'Ptr', hMem, 'Ptr'), 'cp0')
DllCall('GlobalUnlock', 'Ptr', hMem)
DllCall('SetClipboardData', 'UInt', DllCall('RegisterClipboardFormat', 'Str', 'HTML Format'), 'Ptr', hMem)
hMem := DllCall('GlobalAlloc', 'UInt', 0x42, 'Ptr', StrPut(Text, 'UTF-16') * 2, 'Ptr')
StrPut(Text, DllCall('GlobalLock', 'Ptr', hMem, 'Ptr'), 'UTF-16')
DllCall('GlobalUnlock', 'Ptr', hMem)
DllCall('SetClipboardData', 'UInt', 13, 'Ptr', hMem)
DllCall('CloseClipboard')
}
; Credit: teadrinker - https://www.autohotkey.com/boards/viewtopic.php?p=513124#p513257
ExtractHtmlData() {
static CF_HTML := DllCall('RegisterClipboardFormat', 'Str', 'HTML Format')
DllCall('OpenClipboard', 'Ptr', A_ScriptHwnd)
format := 0
Loop {
format := DllCall('EnumClipboardFormats', 'UInt', format)
} until format = CF_HTML || format = 0
if format != CF_HTML {
DllCall('CloseClipboard')
return
}
hData := DllCall('GetClipboardData', 'UInt', CF_HTML, 'Ptr')
pData := DllCall('GlobalLock', 'Ptr', hData, 'Ptr')
html := StrGet(pData, 'UTF-8')
DllCall('GlobalUnlock', 'Ptr', hData)
DllCall('CloseClipboard')
return html
}
ShowText(text,x:=900,rows:=40,eta:=false) {
static MyGui, TextCtl
MyGui := Gui('+Resize', 'Show Text')
MyGui.SetFont("s16")
TextCtl := MyGui.Add("Edit",'vText w' x ' r' rows , text)
MyGui.Show() ; NoActivate avoids deactivating the currently active window.
MyGui.OnEvent("Size", ShowTextGuiResize)
}
ShowTextGuiResize(GuiObj, MinMax, Width, Height){
GuiObj["Text"].Move(10,10,Width-20, Height-35)
}
XSLog(string) { ; Used for benchmarking and debugging
;karvis.Ramlog(string)
; With the above line uncommented, the following code runs in 30 MICROseconds:
;Loop 10000
; XSLog(A_Index)
}
;########################################################
;#####
;##### Cutdown M-ArkDown - Ark_make_html()
;#####
;##### Based on https://github.com/TheArkive/M-ArkDown_ahk2 by TheArkive
;##### Adapted to make clipboard snippets instead of real webpages
;#####
;########################################################
; Ignore the last 2 params. Those are used internally for recursion.
Ark_make_html(_in_text, options:={}, final:=true, md_type:="") {
Global LastSend
If !RegExMatch(_in_text,"[`r`n]+$") && (final) && md_type!="header" { ; add trailing CRLF if doesn't exist
_in_text .= "`r`n"
}
If (options.HasOwnProp("linemode") && options.linemode) {
Return "<html><head></head>`r`n<body><!--StartFragment -->" inline_code(_in_text) "<!--EndFragment --></body></html>"
}
body := ""
table_done := false
a := StrSplit(_in_text,"`n","`r")
i := 0
While (i < a.Length) { ; ( ) \x28 \x29
i++, line := a[i] ; [ ] \x5B \x5D
blockquote := "", ul := "", ul2 := "", code_block := "" ; { } \x7B \x7D
ol := "", ol2 := "", ol_type := ""
table := ""
; header h1 - h6
If RegExMatch(line, "^(#+) (.+)", &match) {
; dbg("HEADER H1-H6")
depth := match[1], _class := "", title := ltgt(match[2])
If RegExMatch(line, "\x5B *([\w ]+) *\x5D$", &_match)
_class := _match[1], title := SubStr(title, 1, StrLen(match[2]) - _match.Len(0))
opener := "<h" match.Len(1) (_class?' class="' _class '"':'') '>'
body .= (body?"`r`n":"") opener Trim(Ark_make_html(title,,false,"header"),"`r`n")
; body .= (body?"`r`n":"") opener title
;. '<a href="#' id '"><span class="link">•</span></a>'
. "</h" match.Len(1) ">"
Continue
}
; spoiler
If RegExMatch(line, "^<spoiler=([^>]+)>$", &match) {
disp_text := ltgt(match[1])
spoiler_text := ""
i++, line := a[i]
While !RegExMatch(line, "^</spoiler>$") {
spoiler_text .= (spoiler_text?"`r`n":"") line
i++, line := a[i]
}
body .= (body?"`r`n":"") '<p><details><summary class="spoiler">'
. disp_text "</summary>" Ark_make_html(spoiler_text,,false,"spoiler") "</details></p>"
Continue
}
; hr
If RegExMatch(line, "^([=\-\*_ ]{3,}[=\-\*_ ]*)(?:\x5B *[^\x5D]* *\x5D)?$", &match) {
; dbg("HR: " line)
hr_style := ""
If Trim(line)=""
Continue
If RegExMatch(line, "\x5B *([^\x5D]*) *\x5D", &match) {
hr_str := match[1]
arr := StrSplit(hr_str," ")
For i, style in arr {
If (SubStr(style, -2) = "px")
hr_style .= (hr_style?" ":"") "border-top-width: " style ";"
Else If RegExMatch(style, "(dotted|dashed|solid|double|groove|ridge|inset|outset|none|hidden)")
hr_style .= (hr_style?" ":"") "border-top-style: " style ";"
Else
hr_style .= (hr_style?" ":"") "border-top-color: " style ";"
}
}
body .= (body?"`r`n":"") '<hr style="' hr_style '">'
Continue
}
; blockquote - must come earlier because of nested elements
While RegExMatch(line, "^\> ?(.*)", &match) {
; dbg("BLOCKQUOTE 1")
blockquote .= (blockquote?"`r`n":"") match[1]
If a.Has(i+1)
i++, line := Trim(a[i]," `t")
Else
Break
}
If (blockquote) {
; dbg("BLOCKQUOTE 2")
body .= (body?"`r`n":"") "<blockquote>" Ark_make_html(blockquote,, false, "blockquote") "</blockquote>"
Continue
}
; code block
If (line = "``````") {
; dbg("CODEBLOCK")
If (i < a.Length)
i++, line := a[i]
Else
Break
While (line != "``````") {
code_block .= (code_block?"`r`n":"") line
If (i < a.Length)
i++, line := a[i]
Else
Break
}
body .= (body?"`r`n":"") "<pre><code>" StrReplace(StrReplace(code_block,"<","<"),">",">") "</code></pre>"
Continue
}
; table
While RegExMatch(line, "^\|.*?\|$") {
; dbg("TABLE 1")
table .= (table?"`r`n":"") line
If a.Has(i+1)
i++, line := a[i]
Else
Break
}
If (table) {
; dbg("TABLE 2")
table_done := true
body .= (body?"`r`n":"") '<table class="normal">'
b := [], h := [], t := " `t"
Loop Parse table, "`n", "`r"
{
body .= "<tr>"
c := StrSplit(A_LoopField,"|"), c.RemoveAt(1), c.RemoveAt(c.Length)
If (A_Index = 1) {
align := ""
Loop c.Length {
If RegExMatch(Trim(c[A_Index],t), "^:(.+?)(?<!\\):$", &match) {
m := StrReplace(inline_code(match[1]),"\:",":")
h.Push(["center",m])
} Else If RegExMatch(Trim(c[A_Index],t), "^([^:].+?)(?<!\\):$", &match) {
m := StrReplace(inline_code(match[1]),"\:",":")
h.Push(["right",m])
} Else If RegExMatch(Trim(c[A_Index],t), "^:(.+)", &match) {
m := StrReplace(inline_code(match[1]),"\:",":")
h.Push(["left",m])
} Else {
m := StrReplace(inline_code(Trim(c[A_Index],t)),"\:",":")
h.Push(["",m])
}
}
} Else If (A_Index = 2) {
Loop c.Length {
If RegExMatch(c[A_Index], "^:\-+:$", &match)
b.Push(align:="center")
Else If RegExMatch(c[A_Index], "^\-+:$", &match)
b.Push(align:="right")
Else
b.Push(align:="left")
If (!h[A_Index][1])
h[A_Index][1] := align
body .= '<th align="' h[A_Index][1] '">' h[A_Index][2] '</th>'
}
} Else {
Loop c.Length {
m := inline_code(c[A_Index]) ; Ark_make_html(c[A_Index],, false, "table data")
body .= '<td align="' b[A_Index] '">' Trim(m," `t") '</td>'
}
}
body .= "</tr>"
}
body .= "</table>"
Continue
}
; unordered lists
If RegExMatch(line, "^( *)[\*\+\-] (.+?)(\\?)$", &match) {
; dbg("UNORDERED LISTS")
While RegExMatch(line, "^( *)([\*\+\-] )?(.+?)(\\?)$", &match) { ; previous IF ensures first iteration is a list item
ul2 := ""
If !match[1] && match[2] && match[3] {
ul .= (ul?"</li>`r`n":"") "<li>" Ark_make_html(match[3],,false,"ul item")
If match[4]
ul .= "<br>"
If (i < a.Length)
i++, line := a[i]
Else
Break
Continue
} Else If !match[2] && match[3] {
ul .= Ark_make_html(match[3],,false,"ul item append")
If match[4]
ul .= "<br>"
If (i < a.Length)
i++, line := a[i]
Else
Break
Continue
} Else If match[1] && match[3] {
While RegExMatch(line, "^( *)([\*\+\-] )?(.+?)(\\?)$", &match) {
If (Mod(StrLen(match[1]),2) || !match[1] || !match[3])
Break
ul2 .= (ul2?"`r`n":"") SubStr(line, 3)
If (i < a.Length)
i++, line := a[i]
Else {
line := ""
Break
}
}
ul .= "`r`n" Ark_make_html(ul2,,false,"ul")
Continue
}
If (i < a.Length)
i++, line := a[i]
Else
Break
}
}
If (ul) {
body .= (body?"`r`n":"") "<ul>`r`n" ul "</li></ul>`r`n"
Continue
}
; ordered lists
If RegExMatch(line, "^( *)[\dA-Za-z]+(?:\.|\x29) +(.+?)(\\?)$", &match) {
; dbg("ORDERED LISTS")
While RegExMatch(line, "^( *)([\dA-Za-z]+(?:\.|\x29) )?(.+?)(\\?)$", &match) { ; previous IF ensures first iteration is a list item
ol2 := ""
If !match[1] && match[2] && match[3] {
ol .= (ol?"</li>`r`n":"") "<li>" Ark_make_html(match[3],,false,"ol item")
If (A_Index = 1)
ol_type := 'type="' RegExReplace(match[2], "[\.\) ]","") '"'
If match[4]
ol .= "<br>"
If (i < a.Length)
i++, line := a[i]
Else
Break
Continue
} Else If !match[2] && match[3] {
ol .= Ark_make_html(match[3],,false,"ol item append")
If match[4]
ol .= "<br>"
If (i < a.Length)
i++, line := a[i]
Else
Break
Continue
} Else If match[1] && match[3] {
While RegExMatch(line, "^( *)([\dA-Za-z]+(?:\.|\x29) )?(.+?)(\\?)$", &match) {
If (Mod(StrLen(match[1]),2) || !match[1] || !match[3])
Break
ol2 .= (ol2?"`r`n":"") SubStr(line, 3)
If (i < a.Length)
i++, line := a[i]
Else {
line := ""
Break
}
}
ol .= "`r`n" Ark_make_html(ol2,,false,"ol")
Continue
}
If (i < a.Length)
i++, line := a[i]
Else
Break
}
}
If (ol) {
body .= (body?"`r`n":"") "<ol " ol_type ">`r`n" ol "</li></ol>`r`n"
Continue
}
; =======================================================================
; ...
; =======================================================================
If RegExMatch(md_type,"^(ol|ul)") { ; ordered/unordered lists
body .= (body?"`r`n":"") inline_code(line)
Continue
} Else If RegExMatch(line, "\\$") { ; manual line break at end \
body .= (body?"`r`n":"") "<p>"
reps := 0
While RegExMatch(line, "(.+)\\$", &match) {
reps++
body .= ((A_Index>1)?"<br>":"") inline_code(match[1])
If (i < a.Length)
i++, line := a[i]
Else
Break
}
If line
body .= (reps?"<br>":"") inline_code(line) "</p>"
Else
body .= "</p>"
} Else If line {
If md_type != "header"
body .= (body?"`r`n":"") "<p>" inline_code(line) "</p>"
Else
body .= (body?"`r`n":"") inline_code(line)
}
}
body := RegExReplace(body, "</p>(\s*)<p","</p><p> </p>$1<p")
If final {
result := "<html><head><style>`r`n" options.css "</style></head>`r`n`r`n<body><!--StartFragment -->" body "<!--EndFragment --></body></html>"
; html1 . options.css . html2 . html3 . body . html4
} Else
result := body
return Result
; =======================================================================
; Local Functions
; =======================================================================
inline_code(_in) {
output := _in
; inline code
While RegExMatch(output, "``(.+?)``", &match) {
output := StrReplace(output, match[0], "<code>" ltgt(match[1]) "</code>",,,1)
}
; image
r := 1
While (s := RegExMatch(output, "!\x5B *([^\x5D]*) *\x5D\x28 *([^\x29]+) *\x29(\x28 *[^\x29]* *\x29)?", &match, r)) {
If IsInCode(match[0], output) || IsInTag(match[0], output) {
r := s + match.Len(0)
Continue
}
dims := Trim(match[3],"()")
output := StrReplace(output, match[0], '<img src="' match[2] '"' (dims?" " dims:"")
. ' alt="' ltgt(match[1]) '" title="' ltgt(match[1]) '">',,,1)
}
; link / url
r := 1
While (s := RegExMatch(output, "\x5B *([^\x5D]+) *\x5D\x28 *([^\x29]+) *\x29", &match, r)) {
If IsInCode(match[0], output) || IsInTag(match[0], output) {
r := s + match.Len(0)
Continue
}
output := StrReplace(output, match[0], '<a href="' match[2] '" target="_blank" rel="noopener noreferrer">'
. match[1] "</a>",,,1)
}
; strong + emphesis (bold + italics)
While (s := RegExMatch(output, "(?<!\w)[\*]{3,3}([^\*]+)[\*]{3,3}", &match, r))
|| (s := RegExMatch(output, "(?<!\w)[\_]{3,3}([^\_]+)[\_]{3,3}", &match, r)) {
If IsInCode(match[0], output) || IsInTag(match[0], output) {
r := s + match.Len(0)
Continue
}
output := StrReplace(output, match[0], "<em><strong>" ltgt(match[1]) "</strong></em>",,,1)
}
; strong (bold)
While (s := RegExMatch(output, "(?<!\w)[\*]{2,2}([^\*]+)[\*]{2,2}", &match, r))
|| (s := RegExMatch(output, "(?<!\w)[\_]{2,2}([^\_]+)[\_]{2,2}", &match, r)) {
If IsInCode(match[0], output) || IsInTag(match[0], output) {
r := s + match.Len(0)
Continue
}
output := StrReplace(output, match[0], "<strong>" ltgt(match[1]) "</strong>",,,1)
}
; emphesis (italics)
While (s := RegExMatch(output, "(?<!\w)[\*]{1,1}([^\*]+)[\*]{1,1}", &match, r))
|| (s := RegExMatch(output, "(?<!\w)[\_]{1,1}([^\_]+)[\_]{1,1}", &match, r)) {
If IsInCode(match[0], output) || IsInTag(match[0], output) {
r := s + match.Len(0)
Continue
}
output := StrReplace(output, match[0], "<em>" ltgt(match[1]) "</em>",,,1)
}
; strikethrough
While (s := RegExMatch(output, "(?<!\w)~{2,2}([^~]+)~{2,2}", &match, r)) {
If IsInCode(match[0], output) || IsInTag(match[0], output) {
r := s + match.Len(0)
Continue
}
output := StrReplace(output, match[0], "<del>" ltgt(match[1]) "</del>",,,1)
}
return output
}
ltgt(_in) {
return StrReplace(StrReplace(_in,"<","<"),">",">")
}
rpt(_in, reps) {
final_str := "" ; Had to change "final" var to "final_str".
Loop reps ; This may still be a bug in a133...
final_str .= _in
return final_str
}
IsInTag(needle, haystack) {
start := InStr(haystack, needle) + StrLen(needle)
sub_str := SubStr(haystack, start)
tag_start := InStr(sub_str,"<")
tag_end := InStr(sub_str,">")
If (!tag_start && tag_end) Or (tag_end < tag_start)
return true
Else
return false
}
IsInCode(needle, haystack) {
start := InStr(haystack, needle) + StrLen(needle)
sub_str := SubStr(haystack, start)
code_start := InStr(sub_str,"<code>")
code_end := InStr(sub_str,"</code>")
If (!code_start && code_end) Or (code_end < code_start)
return true
Else
return false
}
}
/*
; font-family: Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji;
html, body {
font-family: [_font_name_];
font-size: [_font_size_]px;
font-weight: [_font_weight_];
}
body {
line-height: 1.6em;
}
h1, h2, h3, h4, h5, h6
{ font-weight:bold; margin-top: 24px; margin-bottom: [_font_size_]px; }
.underline { border-bottom: 1px solid #00ACAF; }
h1.underline { padding-bottom: 10px; }
h2.underline { padding-bottom: 8px; }
h3.underline { padding-bottom: 6px; }
h4.underline { padding-bottom: 4px; }
h5.underline { padding-bottom: 2px; }
h1 { font-size:1.75em; }
h2 { font-size:1.5em; }
h3 { font-size:1.30em; }
h4 { font-size:1.15em; }
h5 { font-size:1em; }
h6 { font-size:0.75em; }
blockquote {
border-left: 4px solid ;
padding: 0 15px;
margin-top: 0px;
margin-bottom: [_font_size_]px;
}
p { margin-bottom: [_font_size_]px; margin-top: 0px; }
li { margin: 4px 0; }
*/
/* ul, ol { margin: 0; padding: 0; }
ul { margin: 4px 0; }
*/
SampleText(){
SyntaxGuide := "
(
# Markdown syntax guide
## Headers
# This is a Heading h1
## This is a Heading h2
### This is a Heading h3
#### This is a Heading h4
##### This is a Heading h5
###### This is a Heading h6
## Emphasis
*This text will be italic*
_This will also be italic_
**This text will be bold**
__This will also be bold__
_You **can** combine them_
## Lists
### Unordered
* Item 1
* Item 2
* Item 2a
* Item 2b
### Ordered
1. Item 1
1. Item 2
1. Item 3
1. Item 3a
1. Item 3b
## Images
![AutoHotkey Logo](https://www.autohotkey.com/static/ahk_logo_no_text.svg "AutoHotkey Logo.")
## Links
Help docs can be found [here](https://www.autohotkey.com/docs/).
## Blockquotes
> Markdown is a lightweight markup language with plain-text-formatting syntax, created in 2004 by John Gruber with Aaron Swartz.
>
>> Markdown is often used to format readme files, for writing messages in online discussion forums, and to create rich text using a plain text editor.
## Tables
| Left columns | Right columns |
| ------------- |:-------------:|
| left foo | right foo |
| left bar | right bar |
| left baz | right baz |
## Blocks of code
```
let message = 'Hello world';
alert(message);
```
## Inline code
This is created with `Ark_make_html()` and `PutHTMLonClipboard()`.
)"
;SyntaxGuide := "_You **can** combine them_"
Return SyntaxGuide
}