Bear Blog markdown shortcuts
A dashboard plugin that adds keyboard shortcuts for bold, italic, code, strikethrough, and removing formatting in the post editor.
No more manually typing ** or ~~, just select your text and press a key. And it's just as easy to undo any formatting.1
Preview

How to use
Installation
- Copy the script below.
- Go to Customise dashboard.
- Paste it under "Dashboard footer content".
- Save. Enjoy.
Shortcuts
- Cmd/Ctrl+B — bold
- Cmd/Ctrl+I — italic
- Cmd/Ctrl+K — code
- Cmd/Ctrl+Shift+X — strikethrough
- Cmd/Ctrl+U — remove formatting
Select some text and hit the shortcut combo to format it, hit it again to undo. Remove formatting even works without selecting anything, it just finds the nearest formatted bit near your cursor.
Plugin
<script>
/*
Plugin name: Markdown shortcuts
Description: Keyboard shortcuts for bold, italic, code, strikethrough, and removing formatting in the post editor.
Author: Robert Birming
Author URI: https://robertbirming.com
*/
(() => {
const isMac = /Mac|iPhone|iPad|iPod/i.test(
navigator.userAgentData?.platform || navigator.platform || navigator.userAgent
);
const getTextarea = () =>
document.querySelector('textarea#body_content') ||
document.querySelector('textarea');
const isFocused = (el) => el && document.activeElement === el;
const setSel = (el, start, end) => {
el.selectionStart = start;
el.selectionEnd = end;
};
// Replaces a range via execCommand so the browser's native undo history
// (Cmd/Ctrl+Z) and any input-event listeners (autosave, char counters, etc.)
// keep working. Falls back to direct value mutation if execCommand fails.
const replaceText = (el, start, end, text, newStart, newEnd) => {
el.focus();
setSel(el, start, end);
if (!document.execCommand('insertText', false, text)) {
el.value = el.value.slice(0, start) + text + el.value.slice(end);
}
setSel(el, newStart, newEnd);
};
const wrap = (el, w) => {
const v = el.value;
const s = el.selectionStart ?? 0;
const e = el.selectionEnd ?? 0;
if (s === e) {
const placeholder = w === '`' ? 'code' : 'text';
const ins = w + placeholder + w;
replaceText(el, s, e, ins, s + w.length, s + w.length + placeholder.length);
return;
}
const left = v.slice(Math.max(0, s - w.length), s);
const right = v.slice(e, e + w.length);
if (left === w && right === w) {
const un = v.slice(s, e);
replaceText(el, s - w.length, e + w.length, un, s - w.length, e - w.length);
return;
}
const sel = v.slice(s, e);
if (sel.startsWith(w) && sel.endsWith(w) && sel.length >= w.length * 2) {
const un = sel.slice(w.length, sel.length - w.length);
replaceText(el, s, e, un, s, s + un.length);
return;
}
replaceText(el, s, e, w + sel + w, s + w.length, e + w.length);
};
// Scans left and right from a cursor position for the nearest matching pair
// of one of the given delimiters, e.g. for "**bold *and italic* text**" with
// the cursor inside "italic", this finds the *…* pair, not the **…** pair,
// matching how most editors toggle off the innermost formatting under the cursor.
const findNearestPair = (v, pos, delimiters) => {
let best = null;
for (const w of delimiters) {
// Nearest opening delimiter at or before the cursor. Skips a "*" match
// that's actually one half of a "**" run, so the shorter delimiter
// never falsely matches inside the longer one.
let openStart = -1;
for (let i = pos - w.length; i >= 0; i--) {
if (v.slice(i, i + w.length) !== w) continue;
if (w === '*' && (v[i - 1] === '*' || v[i + 1] === '*')) continue;
openStart = i;
break;
}
if (openStart === -1) continue;
// Nearest closing delimiter at or after the cursor, after the opener
const searchFrom = Math.max(pos, openStart + w.length);
let closeStart = -1;
for (let i = searchFrom; i <= v.length - w.length; i++) {
if (v.slice(i, i + w.length) !== w) continue;
if (w === '*' && (v[i - 1] === '*' || v[i + 1] === '*')) continue;
closeStart = i;
break;
}
if (closeStart === -1) continue;
if (closeStart < openStart + w.length) continue; // no content between them
const span = closeStart - (openStart + w.length);
// Prefer the pair with the smallest span, i.e. the innermost / tightest match
if (!best || span < best.span) {
best = { w, openStart, closeStart, span };
}
}
return best;
};
const unwrapAny = (el) => {
const ws = ['**', '*', '~~', '`'];
const v = el.value;
let s = el.selectionStart ?? 0;
let e = el.selectionEnd ?? 0;
if (s === e) {
const pair = findNearestPair(v, s, ws);
if (!pair) return;
const { w, openStart, closeStart } = pair;
const un = v.slice(openStart + w.length, closeStart);
const newCursor = s - w.length; // cursor shifts left by one opening delimiter
replaceText(
el,
openStart,
closeStart + w.length,
un,
Math.max(openStart, newCursor),
Math.max(openStart, newCursor)
);
return;
}
for (const w of ws) {
const left = v.slice(Math.max(0, s - w.length), s);
const right = v.slice(e, e + w.length);
if (left === w && right === w) {
const un = v.slice(s, e);
replaceText(el, s - w.length, e + w.length, un, s - w.length, e - w.length);
return;
}
}
const sel = v.slice(s, e);
for (const w of ws) {
if (sel.startsWith(w) && sel.endsWith(w) && sel.length >= w.length * 2) {
const un = sel.slice(w.length, sel.length - w.length);
replaceText(el, s, e, un, s, s + un.length);
return;
}
}
};
document.addEventListener('keydown', (ev) => {
const ta = getTextarea();
if (!isFocused(ta)) return;
const mod = isMac ? ev.metaKey : ev.ctrlKey;
if (!mod) return;
const k = (ev.key || '').toLowerCase();
if (k === 'b') { ev.preventDefault(); wrap(ta, '**'); }
else if (k === 'i') { ev.preventDefault(); wrap(ta, '*'); }
else if (k === 'k') { ev.preventDefault(); wrap(ta, '`'); }
else if (k === 'x' && ev.shiftKey) { ev.preventDefault(); wrap(ta, '~~'); }
else if (k === 'u') { ev.preventDefault(); unwrapAny(ta); }
});
})();
</script>
Want more? Check out the full Bear Blog library.
Requires JavaScript, available with Bear Blog subscription.↩