From 8fca4a8d20c8fb87f11953c505a48dc20baefec9 Mon Sep 17 00:00:00 2001 From: Eli Cornell Date: Mon, 1 Jul 2024 21:00:29 -0500 Subject: [PATCH] + Display response as HTML/Markdown in popup viewer --- .gitignore | 3 +- AI-Tools.ahk | 44 +++- LICENSE | 2 +- README.md | 3 +- _MD2HTML.ahk | 632 +++++++++++++++++++++++++++++++++++++++++++++++++++ style.css | 262 +++++++++++++++++++++ 6 files changed, 937 insertions(+), 9 deletions(-) create mode 100644 _MD2HTML.ahk create mode 100644 style.css diff --git a/.gitignore b/.gitignore index 48f91d1..ef42342 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ settings.ini settings.ini.x AI-Tools.exe -*.log \ No newline at end of file +*.log +.vscode/settings.json diff --git a/AI-Tools.ahk b/AI-Tools.ahk index fd8212d..224cddd 100644 --- a/AI-Tools.ahk +++ b/AI-Tools.ahk @@ -6,6 +6,7 @@ #singleInstance force #Include "_jxon.ahk" #include "_Cursor.ahk" +#Include "_MD2HTML.ahk" Persistent SendMode "Input" @@ -118,7 +119,7 @@ GetTextFromClip() { if StrLen(text) < 1 { throw ValueError("No text selected", -1) - } else if StrLen(text) > 2048 { + } else if StrLen(text) > 16000 { throw ValueError("Text is too long", -1) } @@ -217,6 +218,16 @@ CallAPI(mode, promptName, prompt, input, promptEnd) { HandleResponse(data, mode, promptName, input) { + Gui_Size(thisGui, MinMax, Width, Height) + { + if MinMax = -1 ; The window has been minimized. No action needed. + return + ; Otherwise, the window has been resized or maximized. Resize the Edit control to match. + ;xEdit.Move(,, Width-30, Height-55) + ogcActiveXWBC.Move(,, Width-30, Height-55) + xClose.Move(Width/2 - 40,Height-40,,) + } + try { LogDebug "data ->`n" data @@ -249,13 +260,34 @@ HandleResponse(data, mode, promptName, input) { } } - if _displayResponse { + response_type := GetSetting(promptName, "response_type", "") + if _displayResponse or response_type == "popup" { MyGui := Gui(, "Response") - MyGui.SetFont("s14") + MyGui.SetFont("s13") MyGui.Opt("+AlwaysOnTop +Owner +Resize") ; +Owner avoids a taskbar button. - MyGui.Add("Edit", "r20 vMyEdit w600 Wrap", text) - MyGui.Add("Button", , "Close").OnEvent("Click", (*) => WinClose()) - MyGui.Show("NoActivate") + + ogcActiveXWBC := MyGui.Add("ActiveX", "xm w800 h480 vIE", "Shell.Explorer") + WB := ogcActiveXWBC.Value + WB.Navigate("about:blank") + css := FileRead("style.css") + options := {css:css + , font_name:"Segoe UI" + , font_size:16 + , font_weight:400 + , line_height:"1.6"} ; 1.6em - put decimals in "" for easier accuracy/handling. + html := make_html(text, options, false) + WB.document.write(html) + + ;xEdit := MyGui.Add("Edit", "r10 vMyEdit w800 Wrap", text) + ;xEdit.Value .= "`n`n----`n`n" html + + xClose := MyGui.Add("Button", "Default w80", "Close") + xClose.OnEvent("Click", (*) => WinClose()) + + MyGui.Show("NoActivate AutoSize Center") + MyGui.GetPos(&x,&y,&w,&h) + xClose.Move(w/2 - 40,,,) + MyGui.OnEvent("Size", Gui_Size) } else { WinActivate _activeWin A_Clipboard := text diff --git a/LICENSE b/LICENSE index 614dcb0..708e111 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2022 Elijah Cornell +Copyright (c) 2024 Elijah Cornell Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 186c9e8..0e359e7 100644 --- a/README.md +++ b/README.md @@ -72,8 +72,9 @@ Tested on Windows 10 Pro 22H2 64-bit. ## Credits -TheArkive (JXON_ahk2), iseahound (SetSystemCursor), and the AHK community. +TheArkive (JXON_ahk2, M-ArkDown_ahk2), iseahound (SetSystemCursor), and the AHK community. - https://github.com/iseahound/SetSystemCursor - https://github.com/TheArkive/JXON_ahk2 +- https://github.com/TheArkive/M-ArkDown_ahk2 diff --git a/_MD2HTML.ahk b/_MD2HTML.ahk new file mode 100644 index 0000000..0c221c8 --- /dev/null +++ b/_MD2HTML.ahk @@ -0,0 +1,632 @@ +#Requires AutoHotkey v2.0 +; ================================================ +; https://github.com/TheArkive/M-ArkDown_ahk2 +; +; Example Script - this just asks for a file and +; uses make_html() to convert the M-ArkDown into html. +; +; MIT License +; Copyright (c) 2021 TheArkive +; +; Permission is hereby granted, free of charge, to any person obtaining a +; copy of this software and associated documentation files (the "Software"), +; to deal in the Software without restriction, including without limitation +; the rights to use, copy, modify, merge, publish, distribute, sublicense, +; and/or sell copies of the Software, and to permit persons to whom the +; Software is furnished to do so, subject to the following conditions: +; +; The above copyright notice and this permission notice shall be included +; in all copies or substantial portions of the Software. +; +; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +; OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +; THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +; IN THE SOFTWARE. +; ================================================ + +; file_path := FileSelect("1",A_ScriptDir '\index.md',"Select markdown file:","Markdown (*.md)") +; If !file_path + ; ExitApp + +; SplitPath file_path,,&dir,,&file_title + +; If FileExist(dir "\" file_title ".html") + ; FileDelete dir "\" file_title ".html" + +; md_txt := FileRead(file_path) + +; css := FileRead("style.css") + +; options := {css:css + ; , font_name:"Segoe UI" + ; , font_size:16 + ; , font_weight:400 + ; , line_height:"1.6"} ; 1.6em - put decimals in "" for easier accuracy/handling. + +; html := make_html(md_txt, options, true) ; true/false = use some github elements + +; FileAppend html, dir "\" file_title ".html", "UTF-8" + +; Run dir "\" file_title ".html" ; open and test + +; dbg(_in) { ; AHK v2 + ; Loop Parse _in, "`n", "`r" + ; OutputDebug "AHK: " A_LoopField +; } + +; ================================================ +; make_html(_in_html, options_obj:="", github:=false) +; +; Ignore the last 2 params. Those are used internally. +; +; See above for constructing the options_obj. +; +; The "github" param is a work in progress, and tries to enforce some of the expected basics +; that are circumvented with my "flavor" of markdown. +; +; Current effects when [ github := true ]: +; * H1 and H2 always have underline (the [underline] tag still takes effect when specified) +; * the '=' is not usable for making an
, but --- *** and ___ still make
+; +; ================================================ + +make_html(_in_text, options:="", github:=false, final:=true, md_type:="") { + + If !RegExMatch(_in_text,"[`r`n]+$") && (final) && md_type!="header" { ; add trailing CRLF if doesn't exist + _in_text .= "`r`n" + } + + html1 := "`r`n`r`n" + toc_html1 := '
' + . '
' ; hamburger (3 horizontal lines) icon + . '
' + toc_html2 := "
" ; end toc-container and toc-contents + html3 := '
`r`n' ;
+ html4 := "
" ;
+ + body := "" + toc := [], do_toc := false + do_nav := false, nav_arr := [] + ref := Map(), ref.CaseSense := false + foot := Map(), ref.CaseSense := false + + link_ico := "•" ; 🔗 • + + Static chk_id := 0 ; increments throughout entire document + + If (final) + css := options.css + + a := StrSplit(_in_text,"`n","`r") + i := 0 + + ref_link_rgx := '^\x5B(\^)?([\w ]+)\x5D:([^"]+)(?:"([^"]+)")?' + in_code := false, code_tag := "" + + While (i < a.Length) { ; parse for ref-style links and footnotes first + i++, line := strip_comment(a[i]) + + If !in_code && RegExMatch(line,"^(``{3,4})$",&c) + in_code := true, code_tag := c[1] ; , msgbox("IN CODE`n`n" line "`n" a[i+1] "`n" a[i+2] "`n" a[i+3]) + + Else If in_code && RegExMatch(line,"^(``{3,4})",&c) && c[1]=code_tag + in_code := false, code_tag := "" + + If !in_code && RegExMatch(line,ref_link_rgx,&m) { + If m[1] + foot[m[2]] := {link:m[3],title:m[4]} ; foot notes + Else + ref[m[2]] := {link:m[3],title:m[4]} ; reference-style links / images + } + } + + i := 0 + While (i < a.Length) { ; ( ) \x28 \x29 ; [ ] \x5B \x5D ; { } \x7B \x7D + + i++, line := strip_comment(a[i]) + ul := "", ul2 := "" + ol := "", ol2 := "", ol_type := "" + + If RegExMatch(line,ref_link_rgx) ; skip ref-style links and footnotes + Continue + + If RegExMatch(line,"^') + Break + } + + While (i < a.Length && RegExMatch(line,"\\$")) ; concatenate lines ending in `\` with next line + line := SubStr(line,1,-1) '
' strip_comment(a[++i]) + + If final && RegExMatch(line, "^"),"|") + nav_arr.RemoveAt(1) + Continue + } + + code_block := "" ; code block + If (line = "``````") || (line = "````````") { + match := line + If !line_inc() + Break + + While (line != match) { + code_block .= (code_block?"`r`n":"") line + If !line_inc() + Break + } + + body .= (body?"`r`n":"") "
" convert(code_block) "
" + Continue + } + + ; header h1 - h6 + If RegExMatch(line, "^(#+) (.+?)(?:\x5B[ \t]*(\w+)[ \t]*\x5D)?$", &n) { + depth := StrLen(n[1]), title := inline_code(Trim(n[2]," `t")) + _class := (depth <= 2 || n[3]="underline") ? "underline" : "" + + id := RegExReplace(RegExReplace(StrLower(title),"[\[\]\{\}\(\)\@\!]",""),"[ \.]","-") + opener := "' + + body .= (body?"`r`n":"") opener title + . '' link_ico '' + . '' + + toc.Push([depth, title, id]) + Continue + } + + ; alt header h1 and h2 + ; ------ or ======= as underline in next_line + next_line := a.Has(i+1) ? strip_comment(a[i+1]) : "" + If next_line && line && RegExMatch(next_line,"^(\-+|=+)$") { + depth := (SubStr(next_line,1,1) = "=") ? 1 : 2 + + id := RegExReplace(RegExReplace(StrLower(line),"[\[\]\{\}\(\)\@\!<>\|]",""),"[ \.]","-") + opener := "' + + body .= (body?"`r`n":"") opener inline_code(line) + . '' link_ico '' + . '' + + toc.Push([depth, line, id]), i++ ; increase line count to skip the ---- or ==== form next_line + Continue + } + + ; check list + If RegExMatch(line,"^\- \x5B([ xX])\x5D (.+)",&n) { + body .= (body?"`r`n":"") '
    ' + While RegExMatch(line,"^\- \x5B([ xX])\x5D (.+)",&n) { + chk := (n[1]="x") ? 'checked=""' : '' + body .= '`r`n
  • ' + . '
  • ' + chk_id++ + If !line_inc() + Break + } + body .= '
' + Continue + } + + ; spoiler + spoiler_text := "" + If RegExMatch(line, "^]+)>$", &match) { + disp_text := match[1] + If !line_inc() + Break + + While !RegExMatch(line, "^$") { + spoiler_text .= (spoiler_text?"`r`n":"") line + If !line_inc() + throw Error("No closing tag found.",-1) + } + + body .= (body?"`r`n":"") '

' + . disp_text "" make_html(spoiler_text,,github,false,"spoiler") "

" + Continue + } + + ; hr + if RegExMatch(line, "^(\-{3,}|_{3,}|\*{3,}|={3,})(?:\x5B[ \t]*([^\x5D]+)*[ \t]*\x5D)?$", &match) { + hr_style := "" + + If match[2] { + For i, style in StrSplit(match[2]," ","`t") { + 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 If InStr(style,"opacity")=1 + hr_style .= (hr_style?" ":"") style ";" + Else + hr_style .= (hr_style?" ":"") "border-top-color: " style ";" + } + + } Else { + hr_style := "opacity: 0.25;" + } + + body .= (body?"`r`n":"") '
' + Continue + } + + ; blockquote + If RegExMatch(line, "^\> *(.*)") { + blockquote := "" + While RegExMatch(line, "^\> *(.*)", &match) { + blockquote .= (blockquote?"`r`n":"") match[1] + If !line_inc() + Break + } + + body .= (body?"`r`n":"") "
" make_html(blockquote,,github, false, "blockquote") "
" + Continue + } + + ; table + If RegExMatch(line, "^\|.*?\|$") { + table := "", lines := 0 + While RegExMatch(line, "^\|.*?\|$") { + table .= (table?"`r`n":"") line, lines++ + If !line_inc() + Break + } + + If lines < 3 + Continue + + If (table) { + b := [], h := [], body .= (body?"`r`n":"") '' + + Loop Parse table, "`n", "`r" + { + body .= "" + c := StrSplit(A_LoopField,"|"), c.RemoveAt(1), c.RemoveAt(c.Length) + + If (A_Index = 1) { + For i, t in c { ; table headers + txt := inline_code(Trim(t," `t")) ; , align := "center" + If RegExMatch(txt,"^(:)?(.+?)(:)?$",&n) { + align := (n[1]&&n[3]) ? "center" : (n[3]) ? "right" : "left" + txt := n[2], h.Push([align,txt]) + } Else + h.Push(["",txt]) + } + + } Else If (A_Index = 2) { + For i, t in c { + align := "left" ; column alignment + If RegExMatch(t,"^(:)?\-+(:)?$",&n) + align := (n[1]&&n[2]) ? "center" : (n[2]) ? "right" : "left" + b.Push(align) + body .= '' + } + + } Else { + For i, t in c + body .= '' + + } + body .= "" + } + body .= "
' h[i][2] '' Trim(inline_code(t)," `t") '
" + Continue + } + } + + ; ordered and unordered lists + list_rgx := '^( *)' ; leading spaces (no tabs) + . '([\*\+\-]|\d+(?:\.|\x29))' ; */+/- or 1. or 1) + . '( +)' ; at least one more space + . '(.+)' ; list content + + list := [] + While RegExMatch(line,list_rgx,&n) { + itm := LT_spec(n[2]) ; bullet item ... [ -, *, +, 1., or 1) ] + tag := LT_tag(n[2]) ; ol or ul + pre := n.Len[1] ; spaces before bullet item + lead := pre + n.Len[2] + n.Len[3] ; # chars before actual list text + + txt := n[4] + While RegExMatch(txt,"\\$") && (i < a.Length) ; append lines ending in '\' + txt := SubStr(txt,1,-1) '
' strip_comment(a[++i]) + + list.Push({itm:t, tag:tag, pre:pre, lead:lead, txt:txt, line:n[2] ' ' txt}) + + If !line_inc() + Break + } + + d := 1 ; depth - for make_list() + err := false ; checking for poorly formatted ordered lists + While list.Length { ; add all lists, normally is one, but can be multiple + body .= '`r`n' make_list(list) + If err + Break + Continue + } + + If list.Length { ; if no errs in list, then list array should be blank at this point + body .= '`r`n' AtoT(list) ; dump remaining plain text + Continue + } + + ; ======================================================================= + ; ... + ; ======================================================================= + + If RegExMatch(md_type,"^(ol|ul)") { ; ordered/unordered lists + body .= (body?"`r`n":"") inline_code(line) + Continue + } + + body .= (body?"`r`n":"") "

" inline_code(line) "

" + } + + ; processing toc ; try to process exact height + final_toc := "", toc_width := 0, toc_height := 0 + If (Final && do_toc) { + temp := Gui() + temp.SetFont("s" options.font_size, options.font_name) + + depth := toc[1][1] + diff := (depth > 1) ? depth - 1 : 0 + indent := "     " + + For i, item in toc { ; 1=depth, 2=title, 3=id + depth := item[1] - diff - 1 + + ctl := temp.Add("Text",, rpt(" ",depth) "• " item[2]) + ctl.GetPos(,,&w, &h) + toc_width := (w > toc_width) ? w : toc_width + toc_height += options.font_size * 2 + + final_toc .= (final_toc?"`r`n":"") '' + . '
' (depth?rpt(indent,depth):"") + . "• " item[2] "
" + } + + temp.Destroy() + } + + ; processing navigation menu + nav_str := "" + If (final && do_nav) { + temp := Gui() + temp.SetFont("s" options.font_size, options.font_name) + + Loop nav_arr.Length { + title := SubStr((txt := nav_arr[A_Index]), 1, (sep := InStr(txt, "=")) - 1) + + ctl := temp.Add("Text",,title) + ctl.GetPos(,,&w) + toc_width := (w > toc_width) ? w : toc_width + toc_height += options.font_size * 2 + + nav_str .= (final_toc?"`r`n":"") '' + . '
' title '
' + } + + (do_toc) ? nav_str .= "
" : "" + temp.Destroy() + } + + ; processing TOC + user_menu := "" + If Final && (do_nav || do_toc) + user_menu := toc_html1 nav_str final_toc toc_html2 + + If final { + If (do_nav && do_toc) + toc_height += Round(options.font_size * Float(options.line_height)) ; multiply by body line-height + + css := StrReplace(css, "[_toc_width_]",toc_width + 25) ; account for scrollbar width + css := StrReplace(css, "[_toc_height_]",Round(toc_height)) + css := StrReplace(css, "[_font_name_]", options.font_name) + css := StrReplace(css, "[_font_size_]", options.font_size) + css := StrReplace(css, "[_font_weight_]", options.font_weight) + css := StrReplace(css, "[_line_height_]", Round(options.line_height,1)) + + If (do_toc || do_nav) + result := html1 . css . html2 . user_menu . html3 . body . html4 + Else + result := html1 . css . html2 . html3 . body . html4 + } Else + result := body + + return result + + ; ======================================================================= + ; Local Functions + ; ======================================================================= + + ; GitHub spec for lists: + ; https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#lists + make_list(_L_) { ; o props = {itm, tag, pre, lead, txt} + _tag := _L_[1].tag, _itm := _L_[1].itm + _pre := _L_[1].pre, _lead := _L_[1].lead, t := "" + _txt := inline_code(_L_[1].txt) ; process escapes first line of list to determine optional list style + + rgx := '\x5B *(?:type=([1AaIi]|disc|circle|square|none)) *\x5D$' + If (_typ := RegExMatch(_txt,rgx,&y) ? y[1] : '') ; if list style found ... + _L_[1].txt := RegExReplace(_L_[1].txt,rgx) ; ... remove it after recording it + + If (_tag = 'ol') + t := (_typ) ? (' type="' _typ '"') : (' type="' ((d=1) ? '1' : (d=2) ? 'i' : 'a') '"') + Else + t := (_typ) ? (' style="list-style-type:' _typ ';"') : '' + + list_html := '<' _L_[1].tag t '>' ; GitHub list levels ... 1. > i. > a. + end_tag := '' + + _i := 0 + While (_L_.Length) { + o := _L_[1] + + If (o.pre >= _lead) { ; step in on proper indent (check GitHub spec) o.pre != _pre + d++ ; increase depth + If (r:=make_list(_L_)) && err ; if ordered list isn't properly numbered return only the 'good' part of the list + return list_html '`r`n' end_tag ; ... and quit parsing + list_html .= r + + Continue + } Else If (o.pre < _pre) { ; stepping back + d-- ; decrease depth + return '`r`n' list_html '`r`n' end_tag + } Else If (o.tag != _tag) ; if changing unordered list types ... + || (o.tag="ul" && o.itm != _itm) { ; ... or if next bullet type (*, +, -) doesn't match previous + d := 1 ; reset depth for new list + return '`r`n' list_html '`r`n' end_tag ; ... this starts a new list, according to GitHub spec + } + + _i++ + + If ( o.tag = 'ol' && _i != Integer(o.itm) ) { ; if ordered list numbers are not in sequence ... + err := true ; ... flag err and return the 'good' part of the list + return '`r`n' list_html '`r`n' end_tag + } + + list_html .= '`r`n
  • ' inline_code(o.txt) '
  • ' + + _tag := o.tag, _itm := o.itm + _pre := o.pre, _lead := o.lead + _L_.RemoveAt(1) + } + + list_html .= '`r`n' end_tag + + return list_html + } + + AtoT(_a_) { ; for dumping the remaining text of a poorly formated list + _txt_ := '

    ' + For i, o in _a_ + _txt_ .= ((i>1)?'
    ':'') o.line + return _txt_ '

    ' + } + + LT_tag(_in_) => IsInteger(t:=Trim(_in_,".)")) ? "ol" : IsAlpha(t) ? "" : "ul" ; list type + + LT_spec(_in_) => IsInteger(t:=Trim(_in_,".)")) ? Integer(t) : t + + inline_code(_in) { + output := _in, check := "" + + While (check != output) { ; repeat until no changes are made + check := output + + ; inline code + While RegExMatch(output, "``(.+?)``", &x) { + + If RegExMatch(x[1],"^\#[\da-fA-F]{6,6}$") + || RegExMatch(x[1],"^rgb\(\d{1,3}, *\d{1,3}, *\d{1,3}\)$") + || RegExMatch(x[1],"^hsl\(\d{1,3}, *\d{1,3}%, *\d{1,3}%\)$") { + output := '' x[1] ' ' + } Else If !IsInCode() + output := StrReplace(output, x[0], "" convert(x[1]) "",,,1) + } + + ; escape characters + While RegExMatch(output,"(\\)(.)",&x) + output := StrReplace(output,x[0],"&#" Ord(x[2]) ";") + + ; image + While RegExMatch(output, "!\x5B *([^\x5D]*) *\x5D\x28 *([^\x29]+) *\x29(?:\x28 *([^\x29]+) *\x29)?", &x) && !IsInCode() { + dims := (dm:=Trim(x[3],"()")) ? " " dm : "" + output := StrReplace(output, x[0], '' x[1] '',,,1) + } + + ; image reference-style + While RegExMatch(output, "!\x5B *([^\x5D]*) *\x5D\x5B *([^\x5D]+) *\x5D(?:\x28 *([^\x29]+) *\x29)?", &x) + && ref.Has(x[2]) ; ref link stored + && !IsInCode() { + dims := x[3] ? " " x[3] : "" + output := StrReplace(output, x[0], '' x[1] '',,,1) + } + + ; link / url + While RegExMatch(output, "\x5B *([^\x5D]+) *\x5D\x28 *([^\x29]+) *\x29", &x) && !IsInCode() { + rel := RegExMatch(x[2],"^#[\w\-]+") ? "" : 'noopener noreferrer' + tgt := RegExMatch(x[2],"^#[\w\-]+") ? "" : '_blank' + output := StrReplace(output, x[0], '' x[1] "",,,1) + } + + ; link / url reference-style 1 + While RegExMatch(output, "\x5B *([^\x5D]+) *\x5D\x5B *([^\x5D]+) *\x5D", &x) + && ref.Has(x[2]) + && !IsInCode() { + ; rel := + output := StrReplace(output, x[0] + , '' x[1] "",,,1) + } + + ; link / url reference-style 2 + While RegExMatch(output, "\x5B *([^\x5D]+) *\x5D", &x) + && ref.Has(x[1]) + && !IsInCode() + output := StrReplace(output, x[0] + , '' + . (ref[x[1]].title?ref[x[1]].title:x[1]) "",,,1) + + ; strong + emphasis (bold + italics) + While (RegExMatch(output, "(?" x[1] "",,,1) + } + + ; strong (bold) + While (RegExMatch(output, "(?" x[1] "",,,1) + } + + ; emphasis (italics) + While (RegExMatch(output, "(?" x[1] "",,,1) + } + + ; strikethrough + While RegExMatch(output, "(?" x[1] "",,,1) + } + + return output + + IsInCode() => ((st := x.Pos[0]-6) < 1) ? false : RegExMatch(output," *\Q" x[0] "\E",,st) ? true : false + } + + line_inc(concat:="") { + (i < a.Length) ? (line := (concat?concat:"") strip_comment(a[++i]), result:=true) : (line := "", result:=false) + return result + } + + strip_comment(_in_) => RTrim(RegExReplace(_in_,"^(.+)<\!\-\-[^>]+\-\->","$1")," `t") + + convert(_in_) { ; convert markup chars so they don't get recognized, a forced kind of escaping in certain contexts + output := _in_ + For i, v in ["&","<",">","\","*","_","-","=","~","``","[","]","(",")","!","{","}"] + output := StrReplace(output,v,"&#" Ord(v) ";") + return output + } + + rpt(x,y) => StrReplace(Format("{:-" y "}","")," ",x) ; string repeat ... x=str, y=iterations +} \ No newline at end of file diff --git a/style.css b/style.css new file mode 100644 index 0000000..7cdc64c --- /dev/null +++ b/style.css @@ -0,0 +1,262 @@ +:root{ + --link: #00ACAF; + --link-hover: cyan; + --link-special: orange; + --link-visited: #006bcf; + --text: #999; + --header: #00ACAF; + --border: #00ACAF; + --blockquote: #0000FF; + --blockquote-text: #DDD; + --row-HL: #000C67; + --bg: #000C17; + --bg1-5: #000C27; + --bg2: #000C37; + --bg3: #000C57; + --so1: yellow; + --so2: orange; + --so3: red; + --so4: magenta; + --emphasis: #EEE; /* was #0077CC */ +} + +html, body { + background-color: var(--bg); + /* font-family: Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji; */ + font-family: [_font_name_]; + font-size: [_font_size_]px; + font-weight: [_font_weight_]; +} + +body { + color: var(--text); + line-height: [_line_height_]em; +} + +#body-container { + width: 100%; + height: 100%; +} + +#main { + /* max-width: 1280px; */ + margin: auto; + /* height: 90%; */ + padding: 5px 5px; + /* overflow-y: scroll; */ + /* border-left: 1px solid var(--border); + border-right: 1px solid var(--border); */ +} + +#toc-container { + background-color: var(--bg); + overflow: hidden; + position: fixed; + display: block; + float: right; + right: 30px; + top: 0px; + width: 30px; + height: 30px; + border: 1px solid var(--border); + border-radius: 3px; + padding: 10px 10px; + transition-duration: 0.25s; + z-index: 10; +} + +@media screen and (min-width: 1383px) { + #toc-container { right: calc(50% - 640px - 25px); } +} + +#toc-icon { + font-size: 24px; + padding-right: 5px; + color: var(--link); +} + +#toc-contents { + overflow: hidden; + display: none; +} + +.toc-item { + padding: 2px 15px; +} + +#toc-container:hover { + overflow-y: auto; + width: [_toc_width_]px; + /* height: initial; */ + height: [_toc_height_]px; + max-height: calc(100vh - 40px); + transition-duration: 0.25s; +} + +#toc-container:hover #toc-contents { + display: block; +} + +#toc-container:hover a { + text-decoration: none; +} + +#toc-container:hover .toc-item:hover { + background-color: var(--row-HL); + color: cyan; +} + +:target a:visited { color: var(--so4); } +:target a:visited:hover, :target a:visited:hover .link { color: var(--link-hover); } +:target.underline { border-color: var(--so4); } + +img { opacity: 0.5; } +img:hover { opacity: 1; } + +table.normal tr th { background: var(--bg3); color: var(--header); } +table.normal tr td { background: var(--bg2); } +table.normal tr th, table tr td { padding: 5px; } +table.normal tr:hover td { background: var(--row-HL); } + +a, a:link { color: var(--link); } +a:hover { color: var(--link-hover); text-decoration: underline; } +a:visited { color: var(--link); } + +h1, h2, h3, h4, h5, h6 +{ font-weight:bold; color:var(--header); margin-top: 24px; margin-bottom: 0.5em; } + +h1 a, h2 a, h3 a, h4 a, h5 a, h6 a, +h1:hover a, h2:hover a, h3:hover a, h4:hover a, h5:hover a, h6:hover a +{ text-decoration: none; } + +h1 a .link, h2 a .link, h3 a .link, h4 a .link, h5 a .link, h6 a .link +{ position: relative; float: left; margin-left: -20px; display: none; } + +h1:hover, h2:hover, h3:hover, h4:hover, h5:hover, h6:hover +{ border-color: var(--link-hover); color: var(--link-hover); } + +h1:hover a .link, h2:hover a .link, h3:hover a .link, h4:hover a .link, h5:hover a .link, h6:hover a .link +{ display: inline; } + +.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; padding-bottom: 10px;} +h2 { font-size:1.5em; padding-bottom: 8px;} +h3 { font-size:1.30em; padding-bottom: 6px;} +h4 { font-size:1.15em; padding-bottom: 4px;} +h5 { font-size:1em; padding-bottom: 2px;} +h6 { font-size:0.75em; } + +em, strong { color: var(--emphasis); } +del { font-weight: 400; } +hr { + color: transparent; + border-top-style: solid; + border-top-width: 4px; + border-top-color: var(--border); +} + +blockquote { + border-left: 4px solid var(--blockquote); + padding: 0 15px; + color: var(--blockquote-text); + margin-top: 0px; + margin-bottom: [_font_size_]px; +} + +p { margin-bottom: [_font_size_]px; margin-top: 0px; } +/* ul, ol { margin: 0; padding: 0; } */ + +pre { + margin-top: 1.5em; + margin-bottom: 1.5em; + background-color: var(--b1-5); + padding: 2px 4px; + border: 1px solid var(--so1); + border-radius: 5px; + font-family: Consolas; + font-size: [_font_size_]px; +} + +code { + margin: 0px 3px 0px 0px; + padding: 2px 4px; + background-color: var(--b1-5); + border: 1px solid var(--border); + border-radius: 5px; + font-family: Consolas; + font-size: [_font_size_]px; + font-weight: 100; +} + +pre code { border:none; padding:0px; } + +ul.checklist { + margin: 1em 0 1em 0; + padding: 0; + list-style-type: none; +} + +/* span.circle { */ + /* font-size: 1em; */ + /* margin: 0; */ + /* width: [_font_size_]px; */ + /* height: [_font_size_]px; */ + /* border: 1px solid; */ + /* border-radius: 50% 50%; */ + /* content:' '; */ +/* } */ + +/* ul { margin: 4px 0; } */ +li { margin: 0px 0; } + +input[type="checkbox"] { + visibility: hidden; +} +input[type="checkbox"] + label:before { + border: 1px solid var(--border); + border-radius: calc([_font_size_]px / 5); + content: "\00a0"; + display: inline-block; + font: [_font_size_]px/1em sans-serif; + height: [_font_size_]px; + position: relative; + top: -1px; + margin-right: calc([_font_size_]px / 3); + width: [_font_size_]px; +} +input[type="checkbox"]:checked + label:before { + background: var(--bg); + color: var(--border); + content: "\2713"; + text-align: center; +} + +.header-menu { + position: fixed; + background-color: var(--bg2); + top: 0px; + max-width: 1280px; + display: inline-block; +} +.header-menu-cell { border: none; } +.header-menu-cell { + border-right: 1px solid var(--bg); + padding: 10px 20px; +} +.header-menu-cell:hover { + background-color: var(--row-HL); +} +.header-menu-cell:hover a, +.header-menu-cell a +{ text-decoration: none; } + +.spoiler { + color: var(--so2) +} + +summary { margin-bottom: [_font_size_]px; } \ No newline at end of file