Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 55 additions & 79 deletions app/assets/javascripts/keyboard_tools.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,20 @@ document.addEventListener('DOMContentLoaded', async () => {
});
return return_obj;
},
dialog: function (msg) {
dialog: function (...elements) {
this.dialogClose();
const d = document.createElement('div');
d.classList.add('__keyboard_help');
d.innerText = msg;
d.append(...elements.map(e => {
if (typeof e == 'string') {
const p = document.createElement('p');
p.innerText = e;
return p;
}
else {
return e
}
}));
document.body.appendChild(d);
},
dialogClose: function () {
Expand Down Expand Up @@ -98,34 +107,34 @@ document.addEventListener('DOMContentLoaded', async () => {
}
});

/**
* @param {number} len
* @returns {string}
*/
const delimitShortcutsGroup = (len) => {
return `${'='.repeat(len)}\n`;
};

/**
* @param {KeyboardShortcut} shortcut
* @param {number} [gap]
* @returns {string}
*/
const formatShortcut = ({ key, text }, gap = 4) => {
return `${key}${' '.repeat(gap - (key.length - 1))}${text}`;
};

/**
* @param {KeyboardShortcut[]} shortcuts
* @param {number} [gap]
* @returns {string}
* @param {string} caption
* @returns {HTMLTableElement}
*/
const formatShortcuts = (shortcuts, gap = 4) => {
return shortcuts.filter((s) => s.if === void 0 || s.if)
.map((s) => formatShortcut(s, gap))
.join('\n');
const formatShortcuts = (caption, shortcuts) => {
const table = document.createElement('table');
table.classList.add('table', 'is-full-width');
const captionEl = document.createElement('caption');
captionEl.innerText = caption;
table.append(captionEl);
for (const { key, text } of shortcuts.filter(s => s.if ?? true)) {
const tr = document.createElement('tr');
const shortcode = document.createElement('td');
const kbd = document.createElement('kbd');
kbd.innerText = key;
shortcode.append(kbd);
const description = document.createElement('td');
description.innerText = text;
tr.append(shortcode, description);
table.append(tr);
}
return table;
};

const categoryShortcuts = () => Object.keys(QPixel.Keyboard.categories())
.map((name, i) => ({ key: (i + 1).toString(), text: name }));

const renderHelpMenu = () => {
/** @type {KeyboardShortcut[]} */
const generalShortcuts = [
Expand All @@ -148,15 +157,9 @@ document.addEventListener('DOMContentLoaded', async () => {
];

QPixel.Keyboard.dialog(
'Keyboard Shortcuts\n' +
delimitShortcutsGroup(33) +
formatShortcuts(generalShortcuts, 4) +
'\n\n' +
'Selection shortcuts:\n\n' +
formatShortcuts(selectionShortcuts, 4) +
'\n\n' +
'Selection shortcuts will select\n' +
'first post, if none selected'
formatShortcuts('Keyboard Shortcuts', generalShortcuts),
formatShortcuts('Selection shortcuts', selectionShortcuts),
'Selection shortcuts will select first post, if none selected'
);
};

Expand All @@ -175,9 +178,8 @@ document.addEventListener('DOMContentLoaded', async () => {
];

QPixel.Keyboard.dialog(
'Go to ...\n' +
delimitShortcutsGroup(26) +
formatShortcuts(shortcuts, 3)
'Go to ...',
formatShortcuts('Pages', shortcuts)
);
};

Expand All @@ -194,9 +196,8 @@ document.addEventListener('DOMContentLoaded', async () => {
];

QPixel.Keyboard.dialog(
'Use tool ...\n' +
delimitShortcutsGroup(17) +
formatShortcuts(shortcuts, 3)
'Use tool ...' +
formatShortcuts('Tools', shortcuts)
);
};

Expand All @@ -207,11 +208,10 @@ document.addEventListener('DOMContentLoaded', async () => {
{ key: 'd', text: 'Down' },
{ key: 'c', text: 'Close' }
];

QPixel.Keyboard.dialog(
'Vote ...\n' +
delimitShortcutsGroup(9) +
formatShortcuts(shortcuts, 3)
'Vote ...',
formatShortcuts('Vote', shortcuts)
);
};

Expand Down Expand Up @@ -262,9 +262,11 @@ document.addEventListener('DOMContentLoaded', async () => {
renderToolsMenu();
QPixel.Keyboard.state = 'tools';
}
} else if (e.key === 's') {
window.location.href = '/posts/search';
} else if (e.key === 'a') {
const cl = $('#answer_body_markdown');
cl[0].scrollIntoView({ behavior: 'smooth' });
const cl = /** @type {HTMLTextAreaElement} */(document.getElementById('post_body_markdown'));
cl.scrollIntoView({ behavior: 'smooth' });
cl.focus();
QPixel.Keyboard.dialogClose();
} else if (e.key === 'Enter') {
Expand Down Expand Up @@ -296,48 +298,22 @@ document.addEventListener('DOMContentLoaded', async () => {
} else if (e.key === 'f') {
window.location.href = '/mod/flags';
} else if (e.key === 't') {
const data = Object.entries(QPixel.Keyboard.categories());
let string_response = '';
for (let i = 0; i < data.length; i++) {
string_response += formatShortcut({
key: (i + 1).toString(),
text: data[i][0]
}, 3) + '\n';
}
QPixel.Keyboard.dialog(
'Go to tags of ...\n' +
delimitShortcutsGroup(18) +
string_response.trim()
'Go to tags of ...',
formatShortcuts("Categories", categoryShortcuts())
);
QPixel.Keyboard.state = 'goto/category-tags';
} else if (e.key === 'e') {
const data = Object.entries(QPixel.Keyboard.categories());
let string_response = '';
for (let i = 0; i < data.length; i++) {
string_response += formatShortcut({
key: (i + 1).toString(),
text: data[i][0]
}, 3) + '\n';
}
QPixel.Keyboard.dialog(
'Go to suggested edits of ...\n' +
delimitShortcutsGroup(28) +
string_response.trim()
'Go to suggested edits of ...\n',
formatShortcuts("Categories", categoryShortcuts())
);
QPixel.Keyboard.state = 'goto/category-edits';
} else if (e.key === 'c') {
const data = Object.entries(QPixel.Keyboard.categories());
let string_response = '';
for (let i = 0; i < data.length; i++) {
string_response += formatShortcut({
key: (i + 1).toString(),
text: data[i][0]
}, 3) + '\n';
}
const data = Object.keys(QPixel.Keyboard.categories());
QPixel.Keyboard.dialog(
'Go to category ...\n' +
delimitShortcutsGroup(18) +
string_response.trim()
'Go to category ...\n',
formatShortcuts("Categories", categoryShortcuts())
);
QPixel.Keyboard.state = 'goto/category';
}
Expand Down
7 changes: 6 additions & 1 deletion app/assets/stylesheets/keyboard_tools.scss
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
.__keyboard_help {
padding: 1rem;
font-family: monospace;
white-space: pre-wrap;
max-width: 350px;
background-color: rgba(0,0,0,0.8);
Expand All @@ -12,6 +11,12 @@
z-index: 2305843009213693951; /* This is the largest Mersenne-prime shown in full on Wikipedia and should be
large enough for most practical use cases to ensure that the keyboard help
is always above everything else. */
table {
margin-top: 1em;
caption {
text-align: start;
}
}
}
.__keyboard_selected {
outline: 0.25rem solid red;
Expand Down
30 changes: 15 additions & 15 deletions global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ interface PostValidatorMessage {

type PostValidator = (postText: string) => [boolean, PostValidatorMessage[]];

interface ProcessedTag {
interface ProcessedTag {
id: number | string
text: string
desc: string
synonyms?: string | QPixelTagSynonym[]
}
}

interface UserPreferences {
community: Record<string, string | null>;
Expand Down Expand Up @@ -113,12 +113,12 @@ interface QPixelMD {
* @param text the text with which to replace the selection
*/
replaceSelection?: ($field: JQuery<HTMLInputElement | HTMLTextAreaElement>, text: string) => void;
/**
* Inserts text at a given {@link idx} in a given {@link str}
* @param str text to insert into
* @param idx position to insert at
* @param insert text to insert
*/
/**
* Inserts text at a given {@link idx} in a given {@link str}
* @param str text to insert into
* @param idx position to insert at
* @param insert text to insert
*/
stringInsert?: (str: string, idx: number, insert: string) => string;
/**
* See [strip_markdown](app/helpers/application_helper.rb) application helper
Expand Down Expand Up @@ -182,7 +182,7 @@ interface QPixelKeyboard {
user_id: number | null;

categories: () => Record<string, string>;
dialog: (message: string) => void;
dialog: (...elements: (HTMLElement | string)[]) => void;
dialogClose: () => void;
updateSelected: () => void;
}
Expand All @@ -203,7 +203,7 @@ type NotificationType = "warning" | "success" | "danger";

type QPixelPopupCallback = (ev: JQuery.ClickEvent, popup: QPixelPopup) => void

type QPixelPingablePopupCallback = (ev: JQuery.KeyUpEvent)=> Promise<void>
type QPixelPingablePopupCallback = (ev: JQuery.KeyUpEvent) => Promise<void>

declare class QPixelPopup {
static destroyAll: () => void;
Expand All @@ -215,8 +215,8 @@ declare class QPixelPopup {
static isSpecialKey: (keyCode: number) => boolean;

constructor(
items: JQuery[],
field: HTMLInputElement | HTMLTextAreaElement,
items: JQuery[],
field: HTMLInputElement | HTMLTextAreaElement,
callback: QPixelPopupCallback
);

Expand Down Expand Up @@ -377,7 +377,7 @@ type QPixelPostType = {
has_reactions: boolean
answer_type_id: number | null
has_only_specific_reactions: boolean
}
}

interface QPixel {
// constants
Expand Down Expand Up @@ -598,8 +598,8 @@ interface QPixel {
* @param onFinally callback to call for all requests
*/
handleJSONResponse?: <T extends QPixelResponseJSON>(data: T,
onSuccess: (data: Extract<T, QPixelSuccessResponseJSON>) => void,
onFinally?: (data: T) => void) => boolean
onSuccess: (data: Extract<T, QPixelSuccessResponseJSON>) => void,
onFinally?: (data: T) => void) => boolean

/**
* Attempts to archive a comment thread
Expand Down
Loading