I am a hobby programmer, and have made an HTA file, with some JavaScript and a single contentEditable <DIV>
. Using minimal JavaScript I can make keyboard shortcuts to format, save, and exit.
I am using mshta32.exe to run the file, in a fully updated Windows 10 system. My empty HTA is only 12k and for this I get word wrapping, formatting, linked images, print (including to PDF) and live spellchecking! It s not a word processor, but for distraction free composition I am loving the experience.
My next goal is to use these files for presentations; the minimum functionality I need is a "next slide" and "previous slide" shortcut. It seems reasonable to use <H1>
tags as slide titles and therefore my shortcuts only need to scroll so the next, or previous, <H1>
is at the top of the screen (yes HTA does save tags in upper case).
On the internet/ChatGPT there are solutions using document.querySelector
but this does not seem to be supported within HTA/MSHTA (I have tried a meta tag with content=ie=edge
and http-equiv=X-UA-Compatible
). Not keen on JQuery as this is a minimalist project and needs to work offline, so I tried translating a suggestion in jQuery:
making a skip button that scrolls to the next heading
$('html, body').scrollTop($(this).nextInDOM($('h1, h2, h3, h4, h5, h6')).position().top);
event.preventDefault();
To JavaScript:
document.querySelector('html, body').scrollTop(document.querySelector(this).nextInDOM(document.querySelector('h1, h2, h3, h4, h5, h6')).position().top);
event.preventDefault();
This did not work! Any guidance welcome.
Kind Regards Gavin Holt
Whole HTA
<!DOCTYPE html>
<HTML>
<HEAD unselectable="on">
<TITLE unselectable="on"> ContentEditor - O:\MyProfile\editor\templates\default.hta</TITLE><?XML:NAMESPACE PREFIX = "HTA" />
<HTA:Application id=document.currentScript VERSION="2" SYSMENU="yes" Singleinstance="no" ShowInTaskBar="yes" scroll="yes" NAVIGABLE="yes" MinimizeButton="yes" MaximizeButton="yes" Icon="O:\MyProfile\cmd\IcoFX\ContentEditor.ico" ContextMenu="No" Border="No" APPLICATIONNAME="MSI-BUILD"></HTA:Application>
<BASE target=_blank unselectable="on">
<META name=viewport content="width=device-width, initial-scale=1" unselectable="on">
<META content=ie=edge unselectable="on">
<META http-equiv=X-UA-Compatible unselectable="on">
<STYLE unselectable="on">
@media Unknown
{
.sidenav {
PADDING-TOP: 15px
}
}
HTML {
COLOR: #657b83; MARGIN: 1em; BACKGROUND-COLOR: #fdf6e3
}
BODY {
FONT-SIZE: 24px; FONT-FAMILY: Helvetica, arial, sans-serif; LETTER-SPACING: 0px; BACKGROUND-COLOR: #fdf6e3
}
.sidenav {
FONT-SIZE: 22px; OVERFLOW: hidden; HEIGHT: 100%; WIDTH: 0px; OVERFLOW-X: hidden; POSITION: fixed; PADDING-TOP: 4px; LEFT: 0px; Z-INDEX: 1; TOP: 0px; BACKGROUND-COLOR: #111; transition: 0.5s
}
.sidenav A {
TEXT-DECORATION: none; COLOR: #818181; PADDING-BOTTOM: 8px; PADDING-TOP: 8px; PADDING-LEFT: 32px; DISPLAY: block; PADDING-RIGHT: 8px; transition: 0.3s
}
.sidenav P {
COLOR: #818181; PADDING-BOTTOM: 4px; PADDING-TOP: 8px; PADDING-LEFT: 4px; PADDING-RIGHT: 4px
}
TD {
COLOR: #818181; PADDING-BOTTOM: 4px; PADDING-TOP: 8px; PADDING-LEFT: 4px; PADDING-RIGHT: 4px
}
TH {
COLOR: #818181; PADDING-BOTTOM: 4px; PADDING-TOP: 8px; PADDING-LEFT: 4px; PADDING-RIGHT: 4px
}
B {
COLOR: #818181; PADDING-BOTTOM: 4px; PADDING-TOP: 8px; PADDING-LEFT: 4px; PADDING-RIGHT: 4px
}
U {
COLOR: #818181; PADDING-BOTTOM: 4px; PADDING-TOP: 8px; PADDING-LEFT: 4px; PADDING-RIGHT: 4px
}
.sidenav A:hover {
COLOR: #f1f1f1
}
.sidenav TABLE {
BORDER-LEFT-WIDTH: 0px; BORDER-RIGHT-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; LINE-HEIGHT: 1; BORDER-TOP-WIDTH: 0px
}
TR {
BORDER-LEFT-WIDTH: 0px; BORDER-RIGHT-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; LINE-HEIGHT: 1; BORDER-TOP-WIDTH: 0px
}
TD {
BORDER-LEFT-WIDTH: 0px; BORDER-RIGHT-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; LINE-HEIGHT: 1; BORDER-TOP-WIDTH: 0px
}
TH {
BORDER-LEFT-WIDTH: 0px; BORDER-RIGHT-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; LINE-HEIGHT: 1; BORDER-TOP-WIDTH: 0px
}
#editor {
PADDING-BOTTOM: 16px; PADDING-TOP: 16px; PADDING-LEFT: 16px; PADDING-RIGHT: 16px; transition: margin-left .5s
}
H1 {
FONT-SIZE: 24px; TEXT-ALIGN: left; LINE-HEIGHT: 1.2
}
H2 {
FONT-SIZE: 24px; TEXT-ALIGN: left; LINE-HEIGHT: 1.2
}
H3 {
FONT-SIZE: 24px; TEXT-ALIGN: left; LINE-HEIGHT: 1.2
}
P {
FONT-SIZE: 24px; TEXT-ALIGN: left; LINE-HEIGHT: 1.2
}
A {
FONT-SIZE: 24px; TEXT-ALIGN: left; LINE-HEIGHT: 1.2
}
CODE {
FONT-SIZE: 24px; TEXT-ALIGN: left; LINE-HEIGHT: 1.2
}
H1 {
FONT-SIZE: 26px; FONT-VARIANT: small-caps
}
TABLE {
WIDTH: 95%; BORDER-COLLAPSE: collapse
}
TABLE {
BORDER-TOP-STYLE: none; BORDER-LEFT-STYLE: none; BORDER-BOTTOM-STYLE: none; BORDER-RIGHT-STYLE: none
}
TD {
BORDER-TOP-STYLE: none; BORDER-LEFT-STYLE: none; BORDER-BOTTOM-STYLE: none; BORDER-RIGHT-STYLE: none
}
TH {
BORDER-TOP-STYLE: none; BORDER-LEFT-STYLE: none; BORDER-BOTTOM-STYLE: none; BORDER-RIGHT-STYLE: none
}
TR {
VERTICAL-ALIGN: top; TEXT-ALIGN: left; LINE-HEIGHT: 1.2
}
TD {
VERTICAL-ALIGN: top; TEXT-ALIGN: left; LINE-HEIGHT: 1.2
}
TD {
}
TH {
}
TABLE {
PAGE-BREAK-INSIDE: auto
}
TR {
PAGE-BREAK-INSIDE: avoid; PAGE-BREAK-AFTER: auto
}
THEAD {
DISPLAY: table-header-group
}
TFOOT {
DISPLAY: table-footer-group
}
</STYLE>
<SCRIPT language=JScript type=text/jscript unselectable="on">
<!--
function preventDefault(e){
if (e.preventDefault) {
e.preventDefault();
} else {
e.returnValue = false;
}
}
function toggleNav() {
if (document.getElementById("mySidenav").style.width=="260px") {
closeNav();
} else {
openNav();
}
}
function openNav() {
document.getElementById("mySidenav").style.width = "260px";
document.getElementById("editor").style.marginLeft = "260px";
document.getElementById("mySidenav").style.overflow = "scroll";
}
function closeNav() {
document.getElementById("mySidenav").style.width = "0";
document.getElementById("editor").style.marginLeft= "0";
document.getElementById("mySidenav").style.overflow = "hidden";
}
function writeFile(){
// Deal with funny \ at work!
var filename = window.location.pathname
var fso, fileHandle;
fso = new ActiveXObject("Scripting.FileSystemObject");
fileHandle = fso.CreateTextFile(filename.replace(/\//,""), true);
fileHandle.write("<!DOCTYPE html>");
fileHandle.write("<HTML>");
fileHandle.write(document.documentElement.innerHTML);
fileHandle.write("</HTML>");
fileHandle.close();
}
function getSelected() {
if (window.getSelection) {
return window.getSelection();
} else if (document.getSelection) {
return document.getSelection();
} else {
var selection = document.selection && document.selection.createRange();
if (selection.text) {
return selection.text;
}
return false;
}
}
function insertText(text) {
if (document.selection){
var range = document.selection.createRange();
range.pasteHTML(text);
}
}
function followlink(){
alert('followlink called');
}
function popupmenu(event) {
alert('popupmenu called');
// Need to know the event.target - but don't want jquery!
// use window.screenLeft window.screenTop
}
function Shortcuts(e){
if (!e) var e = window.event;
var key = e.keyCode
if (e.ctrlKey) { key = "ctrl"+key; }
if (e.altKey) { key = "alt"+key; }
if (e.shiftKey) { key = "shift"+key; }
// alert(key)
// ESC
if ( key == 27) {
writeFile();
window.close();
}
// TAB alone
if ( key == 9) {
preventDefault(e);
document.execCommand("indent", true, null);
return false;
}
// Shift+TAB
if (key == "shift9") {
preventDefault(e);
document.execCommand("outdent", true, null);
return false;
}
// Ctrl+>
if ( key == "ctrl190") {
document.execCommand("indent", true);
}
// Ctrl+<
if ( key == "ctrl188") {
document.execCommand("outdent", true);
}
// Ctrl+Up TODO: Fails with this key
if ( key == "ctrl38") {
document.execCommand("superscript", true);
}
// Ctrl+Down TODO: Fails with this key
if ( key == "ctrl40") {
document.execCommand("subscript", true);
}
// Ctrl++ - Built in zoom+
// Ctrl+- - Built in zoom-
// Ctrl+/ Try to wrap any selection
if ( key == "ctrl191") {
var sText = getSelected();
var code = "<code>" + sText + "</code>"
insertText(code);
}
// Ctrl+\ Clear formatting
if ( key == "ctrl220") {
document.execCommand("removeFormat", true);
}
// Ctrl+A - Built in select all
// Ctrl+B - Built in bold
// Ctrl+C - Built in copy
// Ctrl+D
if ( key == "ctrl68") {
document.execCommand("strikethrough", true);
}
// Ctrl+E - Editor
if ( key == "ctrl69") {
WshShell = new ActiveXObject("WScript.Shell");
WshShell.Run("O:/MyProfile/editor/micro.bat " + window.location.pathname,1,true)
}
// Ctrl+F - Built in Find
// Ctrl+G - Google
if ( key == "ctrl71") {
WshShell = new ActiveXObject("WScript.Shell");
WshShell.run("http://www.google.com");
}
// Ctrl+H - Hyperlink
if ( key == "ctrl72") {
var sText = getSelected();
var linkURL = prompt('Enter a URL:', 'http://');
// This is to match the automated format when URL is pasted/typed
var link = '<A href="' + linkURL + '" target="_blank">' + sText + '</A>';
insertText(link);
}
// Ctrl+I - Built in italic
// Ctrl+J - Jump to next H1 - for slide presentations
if ( key == "ctrl74") {
}
// Ctrl+K - Built in hyperlink
// Ctrl+L - Ordered list
if ( key == "ctrl76") {
document.execCommand("insertUnorderedList", true);
}
// Ctrl+M
if ( key == "ctrl77") {
// Undo default action of CRLF
return false;
}
// Ctrl+N - Numbered List
if ( key == "ctrl78") {
document.execCommand("insertOrderedList", true);
}
// Ctrl+O - Empty
// Ctrl+P - Built in Print
// Ctrl+Q - Empty
// Ctrl+R - Revert
if ( key == "ctrl82") {
window.location.reload(false);
}
// Ctrl+S - Save
if ( key == "ctrl83") {
writeFile();
}
// Ctrl+T - Insert Table - TODO: need some css or in line styles
if ( key == "ctrl84") {
document.execCommand("indent", true, null);
var table = "<table>"
table = table +"<tr>"
table = table +" <th>Company</th>"
table = table +" <th>Contact</th>"
table = table +" <th>Country</th>"
table = table +"</tr>"
table = table +"<tr>"
table = table +" <td>Alfreds</td>"
table = table +" <td>Maria</td>"
table = table +" <td>Germany</td>"
table = table +"</tr>"
table = table +"<tr>"
table = table +" <td>Centro</td>"
table = table +" <td>Francisco</td>"
table = table +" <td>Mexico</td>"
table = table +"</tr>"
table = table +"</table>"
insertText(table);
}
// Ctrl+U - Built in Underline
// Ctrl+V - Built in Paste
// Ctrl+W - Write and Close
if ( key == "ctrl87") {
writeFile();
window.close();
}
// Ctrl+X - Built in Cut
// Ctrl+Y - Built in Redo
// Ctrl+Z - Built in Undo
// F1
if ( key == 112) {
toggleNav();
}
// Ctrl+0
if ( key == "ctrl48") {
document.execCommand("formatBlock", false, "<P>");
}
// Ctrl+1
if ( key == "ctrl49") {
document.execCommand("formatBlock", false, "<H1>");
}
// Ctrl+2
if ( key == "ctrl50") {
document.execCommand("formatBlock", false, "<H2>");
}
// Ctrl+3
if ( key == "ctrl51") {
document.execCommand("formatBlock", false, "<H3>");
}
}
// Try to stop focus outside my div
for (i=0; i<document.all.length; i++){
//ensure that all document elements except the content editable DIV are unselectable
document.all(i).unselectable = "on";
}
// Change title
var filename = window.location.pathname
document.title = " ContentEditor - " + filename.replace(/\//,"")
// Focus on the editable section
window.location.hash = '#editor';
// Settings
document.execCommand("LiveResize", null, true);
//-->
</SCRIPT>
</HEAD>
<BODY tabIndex=-1>
<DIV tabIndex=-1 id=mySidenav class=sidenav style="OVERFLOW: scroll; WIDTH: 260px" unselectable="on"><U><B>Help</B></U>
<TABLE>
<TBODY>
<TR>
<TD>ESC </TD>
<TD>Save + exit</TD></TR>
<TR>
<TD>TAB </TD>
<TD>Indent</TD></TR>
<TR>
<TD>Shift+TAB </TD>
<TD>Outdent</TD></TR>
<TR>
<TD>Ctrl+A </TD>
<TD>Select All</TD></TR>
<TR>
<TD>Ctrl+B </TD>
<TD>Bold</TD></TR>
<TR>
<TD>Ctrl+C </TD>
<TD>Copy</TD></TR>
<TR>
<TD>Ctrl+D </TD>
<TD>Strikeout</TD></TR>
<TR>
<TD>Ctrl+E </TD>
<TD>Edit page</TD></TR>
<TR>
<TD>Ctrl+F </TD>
<TD>Find</TD></TR>
<TR>
<TD>Ctrl+I </TD>
<TD>Italic</TD></TR>
<TR>
<TD>Ctrl+K </TD>
<TD>Hyperlink</TD></TR>
<TR>
<TD>Ctrl+L </TD>
<TD>Bullet list</TD></TR>
<TR>
<TD>Ctrl+N </TD>
<TD>Num list</TD></TR>
<TR>
<TD>Ctrl+P </TD>
<TD>Print</TD></TR>
<TR>
<TD>Ctrl+R </TD>
<TD>Revert</TD></TR>
<TR>
<TD>Ctrl+S </TD>
<TD>Save</TD></TR>
<TR>
<TD>Ctrl+T </TD>
<TD>Insert table</TD></TR>
<TR>
<TD>Ctrl+U </TD>
<TD>Underline</TD></TR>
<TR>
<TD>Ctrl+V </TD>
<TD>Paste</TD></TR>
<TR>
<TD>Ctrl+W </TD>
<TD>Save + exit</TD></TR>
<TR>
<TD>Ctrl+X </TD>
<TD>Cut</TD></TR>
<TR>
<TD>Ctrl+Y </TD>
<TD>Redo</TD></TR>
<TR>
<TD>Ctrl+Z </TD>
<TD>Undo</TD></TR>
<TR>
<TD>Ctrl+/ </TD>
<TD>Code</TD></TR>
<TR>
<TD>Ctrl+< </TD>
<TD>Superscript</TD></TR>
<TR>
<TD>Ctrl+> </TD>
<TD>Subscript</TD></TR>
<TR>
<TD>Ctrl+\\ </TD>
<TD>Unformat</TD></TR>
<TR>
<TD>Ctrl+Del </TD>
<TD>Del EOW</TD></TR>
<TR>
<TD>Ctrl+Bksp </TD>
<TD>Del BOW</TD></TR>
<TR>
<TD>Ctrl+n </TD>
<TD>Heading 1-3</TD></TR>
<TR>
<TD>F1 </TD>
<TD>Toggle Help</TD></TR></TBODY></TABLE>
<P></P></DIV>
<DIV spellcheck=true tabIndex=-1 onkeyup=Shortcuts() id=editor contentEditable=true onkeydown="return (event.keyCode!=9);" style="MARGIN-LEFT: 260px" oncontextmenu=popupmenu()></DIV>
<SCRIPT type=text/javascript>
<!--
//-->
</SCRIPT>
</BODY></HTML>
Hi,
Many thanks for all the advice so far, in summary:
My writeFile()
function will always add <?XML:NAMESPACE PREFIX = "HTA" />
, which puts me in IE7 mode.
function writeFile(){
// Deal with funny \ at work!
var filename = window.location.pathname
var fso, fileHandle;
fso = new ActiveXObject("Scripting.FileSystemObject");
fileHandle = fso.CreateTextFile(filename.replace(/\//,""), true);
fileHandle.write("<!DOCTYPE html>");
fileHandle.write("<HTML>");
fileHandle.write(document.documentElement.innerHTML);
fileHandle.write("</HTML>");
fileHandle.close();
}
I need the <hta:application>
section to for its control over the window and the icon.
<hta:application
id=document.currentScript
applicationname="MSI-BUILD"
border="No"
contextmenu="No"
icon="O:\MyProfile\cmd\ContentEditor.ico"
maximizebutton="yes"
minimizebutton="yes"
navigable="yes"
scroll="yes"
showintaskbar="yes"
singleinstance="no"
sysmenu="yes"
version="2"
>
Using a function to see documentMode
and ScriptEngine
, I get IE7 with JScript 11.0.16384.
ver = 'IE' + document.documentMode + ' , ';
ver += ScriptEngine() + ' ';
ver += ScriptEngineMajorVersion() + '.';
ver += ScriptEngineMinorVersion() + '.';
ver += ScriptEngineBuildVersion();
alert(ver);
So, I think I am stuck with IE7 era scripting.
Kind Regards Gavin Holt
Your advice on how to ask ChatGPT has been very helpful, this function does exactly what I need:
function scrollToNextH1() {
var h1Elements = document.getElementsByTagName('h1');
var currentH1Index = -1;
for (var i = 0; i < h1Elements.length; i++) {
if (h1Elements[i].getBoundingClientRect().top > 0) {
currentH1Index = i;
break;
}
}
if (currentH1Index !== -1 && currentH1Index < h1Elements.length - 1) {
var nextH1 = h1Elements[currentH1Index + 1];
window.scrollTo(0, nextH1.offsetTop);
}
}
I have learnt more in this short time, than days hunting with search engines. A combination of helpful humans and a good LLM is very effective.
Many thanks