mirror of
https://github.com/Retrospring/retrospring.git
synced 2024-11-20 16:29:52 +01:00
Merge pull request #1061 from Retrospring/feature/hotkey
Keyboard shortcuts
This commit is contained in:
commit
b801af9465
40 changed files with 280 additions and 41 deletions
|
@ -100,26 +100,27 @@ $unicodeRangeValues in Lexend.$unicodeMap {
|
|||
*/
|
||||
|
||||
@import "components/announcements",
|
||||
"components/answerbox",
|
||||
"components/avatars",
|
||||
"components/buttons",
|
||||
"components/collapse",
|
||||
"components/comments",
|
||||
"components/container",
|
||||
"components/entry",
|
||||
"components/icons",
|
||||
"components/inbox-actions",
|
||||
"components/inbox-entry",
|
||||
"components/mobile-nav",
|
||||
"components/navbar",
|
||||
"components/notifications",
|
||||
"components/profile",
|
||||
"components/push-settings",
|
||||
"components/question",
|
||||
"components/smiles",
|
||||
"components/themes",
|
||||
"components/totp-setup",
|
||||
"components/userbox";
|
||||
"components/answerbox",
|
||||
"components/avatars",
|
||||
"components/buttons",
|
||||
"components/collapse",
|
||||
"components/comments",
|
||||
"components/container",
|
||||
"components/entry",
|
||||
"components/hotkey",
|
||||
"components/icons",
|
||||
"components/inbox-actions",
|
||||
"components/inbox-entry",
|
||||
"components/mobile-nav",
|
||||
"components/navbar",
|
||||
"components/notifications",
|
||||
"components/profile",
|
||||
"components/push-settings",
|
||||
"components/question",
|
||||
"components/smiles",
|
||||
"components/themes",
|
||||
"components/totp-setup",
|
||||
"components/userbox";
|
||||
|
||||
/**
|
||||
UTILITIES
|
||||
|
|
6
app/assets/stylesheets/components/_hotkey.scss
Normal file
6
app/assets/stylesheets/components/_hotkey.scss
Normal file
|
@ -0,0 +1,6 @@
|
|||
.js-hotkey-navigating {
|
||||
|
||||
.js-hotkey-current-selection {
|
||||
outline: var(--primary) solid 4px;
|
||||
}
|
||||
}
|
|
@ -7,7 +7,8 @@ module BootstrapHelper
|
|||
badge_color: nil,
|
||||
badge_attr: {},
|
||||
icon: nil,
|
||||
class: ""
|
||||
class: "",
|
||||
hotkey: nil,
|
||||
}.merge(options)
|
||||
|
||||
classes = [
|
||||
|
@ -33,7 +34,7 @@ module BootstrapHelper
|
|||
body += " #{content_tag(:span, options[:badge], class: badge_class, **options[:badge_attr])}".html_safe
|
||||
end
|
||||
|
||||
content_tag(:li, link_to(body.html_safe, path, class: "nav-link"), class: classes)
|
||||
content_tag(:li, link_to(body.html_safe, path, class: "nav-link", data: { hotkey: options[:hotkey] }), class: classes)
|
||||
end
|
||||
|
||||
def list_group_item(body, path, options = {})
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
import '@hotwired/turbo-rails';
|
||||
import initializeBootstrap from './initializers/bootstrap';
|
||||
import initializeHotkey from './initializers/hotkey';
|
||||
import initializeServiceWorker from './initializers/serviceWorker';
|
||||
import initializeStimulus from './initializers/stimulus';
|
||||
|
||||
export default function start(): void {
|
||||
try {
|
||||
initializeBootstrap();
|
||||
initializeHotkey();
|
||||
initializeServiceWorker();
|
||||
initializeStimulus();
|
||||
} catch (e) {
|
||||
|
|
12
app/javascript/retrospring/controllers/hotkey_controller.ts
Normal file
12
app/javascript/retrospring/controllers/hotkey_controller.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
import { Controller } from "@hotwired/stimulus";
|
||||
import { install, uninstall } from "@github/hotkey";
|
||||
|
||||
export default class extends Controller<HTMLElement> {
|
||||
connect(): void {
|
||||
install(this.element);
|
||||
}
|
||||
|
||||
disconnect(): void {
|
||||
uninstall(this.element);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
import { Controller } from "@hotwired/stimulus";
|
||||
import { install, uninstall } from "@github/hotkey";
|
||||
|
||||
export default class extends Controller {
|
||||
static classes = ["current"];
|
||||
static targets = ["current", "traversable"];
|
||||
|
||||
declare readonly hasCurrentTarget: boolean;
|
||||
declare readonly currentTarget: HTMLElement;
|
||||
declare readonly traversableTargets: HTMLElement[];
|
||||
|
||||
traversableTargetConnected(target: HTMLElement): void {
|
||||
if (!("navigationIndex" in target.dataset)) {
|
||||
target.dataset.navigationIndex = this.traversableTargets.indexOf(target).toString();
|
||||
}
|
||||
|
||||
if (!this.hasCurrentTarget) {
|
||||
const first = this.traversableTargets[0];
|
||||
first.dataset.navigationTarget += " current";
|
||||
}
|
||||
}
|
||||
|
||||
currentTargetConnected(target: HTMLElement): void {
|
||||
target.classList.add("js-hotkey-current-selection");
|
||||
|
||||
target.querySelectorAll<HTMLElement>("[data-selection-hotkey]")
|
||||
.forEach(el => install(el, el.dataset.selectionHotkey));
|
||||
}
|
||||
|
||||
currentTargetDisconnected(target: HTMLElement): void {
|
||||
target.classList.remove("js-hotkey-current-selection");
|
||||
|
||||
target.querySelectorAll<HTMLElement>("[data-selection-hotkey]")
|
||||
.forEach(el => uninstall(el));
|
||||
}
|
||||
|
||||
up(): void {
|
||||
const prevIndex = this.traversableTargets.indexOf(this.currentTarget) - 1;
|
||||
if (prevIndex == -1) return;
|
||||
|
||||
this.navigate(this.traversableTargets[prevIndex]);
|
||||
}
|
||||
|
||||
down(): void {
|
||||
const nextIndex = this.traversableTargets.indexOf(this.currentTarget) + 1;
|
||||
if (nextIndex == this.traversableTargets.length) return;
|
||||
|
||||
this.navigate(this.traversableTargets[nextIndex]);
|
||||
}
|
||||
|
||||
navigate(target: HTMLElement): void {
|
||||
if (!document.body.classList.contains("js-hotkey-navigating")) {
|
||||
document.body.classList.add("js-hotkey-navigating");
|
||||
}
|
||||
|
||||
if (target.dataset.navigationTarget == "traversable") {
|
||||
this.currentTarget.dataset.navigationTarget = "traversable";
|
||||
target.dataset.navigationTarget = "traversable current";
|
||||
target.scrollIntoView({ block: "center", inline: "center" });
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
export function commentHotkeyHandler(event: Event): void {
|
||||
const button = event.target as HTMLButtonElement;
|
||||
const id = button.dataset.aId;
|
||||
|
||||
document.querySelector(`#ab-comments-section-${id}`).classList.remove('d-none');
|
||||
document.querySelector<HTMLElement>(`[name="ab-comment-new"][data-a-id="${id}"]`).focus();
|
||||
}
|
|
@ -4,10 +4,12 @@ import { commentComposeEnd, commentComposeStart, commentCreateClickHandler, comm
|
|||
import { commentReportHandler } from "./report";
|
||||
import { commentSmileHandler } from "./smile";
|
||||
import { commentToggleHandler } from "./toggle";
|
||||
import { commentHotkeyHandler } from "retrospring/features/answerbox/comment/hotkey";
|
||||
|
||||
export default (): void => {
|
||||
registerEvents([
|
||||
{ type: 'click', target: '[name=ab-comments]', handler: commentToggleHandler, global: true },
|
||||
{ type: 'click', target: '[name=ab-open-and-comment]', handler: commentHotkeyHandler, global: true },
|
||||
{ type: 'click', target: '[name=ab-smile-comment]', handler: commentSmileHandler, global: true },
|
||||
{ type: 'click', target: '[data-action=ab-comment-report]', handler: commentReportHandler, global: true },
|
||||
{ type: 'click', target: '[data-action=ab-comment-destroy]', handler: commentDestroyHandler, global: true },
|
||||
|
|
7
app/javascript/retrospring/initializers/hotkey.ts
Normal file
7
app/javascript/retrospring/initializers/hotkey.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
import { install } from '@github/hotkey'
|
||||
|
||||
export default function (): void {
|
||||
document.addEventListener('turbo:load', () => {
|
||||
document.querySelectorAll('[data-hotkey]').forEach(el => install(el as HTMLElement));
|
||||
});
|
||||
}
|
|
@ -8,9 +8,11 @@ import CollapseController from "retrospring/controllers/collapse_controller";
|
|||
import ThemeController from "retrospring/controllers/theme_controller";
|
||||
import CapabilitiesController from "retrospring/controllers/capabilities_controller";
|
||||
import CropperController from "retrospring/controllers/cropper_controller";
|
||||
import HotkeyController from "retrospring/controllers/hotkey_controller";
|
||||
import InboxSharingController from "retrospring/controllers/inbox_sharing_controller";
|
||||
import ToastController from "retrospring/controllers/toast_controller";
|
||||
import PwaBadgeController from "retrospring/controllers/pwa_badge_controller";
|
||||
import NavigationController from "retrospring/controllers/navigation_controller";
|
||||
|
||||
/**
|
||||
* This module sets up Stimulus and our controllers
|
||||
|
@ -29,8 +31,10 @@ export default function (): void {
|
|||
window['Stimulus'].register('collapse', CollapseController);
|
||||
window['Stimulus'].register('cropper', CropperController);
|
||||
window['Stimulus'].register('format-popup', FormatPopupController);
|
||||
window['Stimulus'].register('hotkey', HotkeyController);
|
||||
window['Stimulus'].register('inbox-sharing', InboxSharingController);
|
||||
window['Stimulus'].register('pwa-badge', PwaBadgeController);
|
||||
window['Stimulus'].register('navigation', NavigationController);
|
||||
window['Stimulus'].register('theme', ThemeController);
|
||||
window['Stimulus'].register('toast', ToastController);
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
%button.btn.btn-link.answerbox__action{ type: :button, name: "ab-smile", data: { a_id: a.id, action: current_user&.smiled?(a) ? :unsmile : :smile }, disabled: !user_signed_in? }
|
||||
%button.btn.btn-link.answerbox__action{ type: :button, name: "ab-smile", data: { a_id: a.id, action: current_user&.smiled?(a) ? :unsmile : :smile, selection_hotkey: "s" }, disabled: !user_signed_in? }
|
||||
%i.fa.fa-fw.fa-smile-o
|
||||
%span{ id: "ab-smile-count-#{a.id}" }= a.smiles.count
|
||||
- unless display_all
|
||||
%button.btn.btn-link.answerbox__action{ type: :button, name: "ab-comments", data: { a_id: a.id, state: :hidden } }
|
||||
%button.btn.btn-link.answerbox__action{ type: :button, name: "ab-comments", data: { a_id: a.id, state: :hidden, selection_hotkey: "x" } }
|
||||
%i.fa.fa-fw.fa-comments
|
||||
%span{ id: "ab-comment-count-#{a.id}" }= a.comment_count
|
||||
.btn-group
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
%span.caret
|
||||
= render "actions/comment", comment: comment, answer: a
|
||||
- if user_signed_in?
|
||||
%button.d-none{ name: "ab-open-and-comment", data: { a_id: a.id, selection_hotkey: "c" } }
|
||||
.comment__compose-wrapper{
|
||||
name: "ab-comment-new-group",
|
||||
data: { a_id: a.id, controller: "character-count", character_count_max_value: 512 }
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
= t(".asked_html", user: user_screen_name(a.question.user, context_user: a.user, author_identifier: a.question.author_is_anonymous ? a.question.author_identifier: nil), time: time_tooltip(a.question))
|
||||
- if !a.question.author_is_anonymous && !a.question.direct
|
||||
·
|
||||
%a{ href: question_path(a.question.user.screen_name, a.question.id) }
|
||||
%a{ href: question_path(a.question.user.screen_name, a.question.id), data: { selection_hotkey: "a" } }
|
||||
= t(".answers", count: a.question.answer_count)
|
||||
.answerbox__question-body{ data: { controller: a.question.long? ? "collapse" : nil } }
|
||||
.answerbox__question-text{ class: a.question.long? && !display_all ? "collapsed" : "", data: { collapse_target: "content" } }
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
- display_all ||= nil
|
||||
.card.answerbox{ data: { id: a.id, q_id: a.question.id } }
|
||||
.card.answerbox{ data: { id: a.id, q_id: a.question.id, navigation_target: "traversable" } }
|
||||
- if @question.nil?
|
||||
= render "answerbox/header", a: a, display_all: display_all
|
||||
.card-body
|
||||
|
@ -19,7 +19,7 @@
|
|||
%h6.answerbox__answer-user
|
||||
= raw t(".answered", hide: hidespan(t(".hide"), "d-none d-sm-inline"), user: user_screen_name(a.user))
|
||||
.answerbox__answer-date
|
||||
= link_to(raw(t("time.distance_ago", time: time_tooltip(a))), answer_path(a.user.screen_name, a.id))
|
||||
= link_to(raw(t("time.distance_ago", time: time_tooltip(a))), answer_path(a.user.screen_name, a.id), data: { selection_hotkey: "l" })
|
||||
.col-md-6.d-flex.d-md-block.answerbox__actions
|
||||
= render "answerbox/actions", a: a, display_all: display_all
|
||||
- else
|
||||
|
|
|
@ -11,4 +11,5 @@
|
|||
class: "btn btn-light",
|
||||
method: :get,
|
||||
params: { last_id: @inbox_last_id, author: @author }.compact,
|
||||
data: { controller: :hotkey, hotkey: "." },
|
||||
form: { data: { turbo_stream: true } }
|
||||
|
|
|
@ -8,4 +8,5 @@
|
|||
class: "btn btn-light",
|
||||
method: :get,
|
||||
params: { last_id: @inbox_last_id, author: @author }.compact,
|
||||
data: { controller: :hotkey, hotkey: "." },
|
||||
form: { data: { turbo_stream: true } }
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
= render 'shared/announcements'
|
||||
= yield
|
||||
= render "shared/formatting"
|
||||
= render "shared/hotkeys"
|
||||
.d-none#toasts
|
||||
- if Rails.env.development?
|
||||
#debug
|
||||
|
|
|
@ -14,4 +14,5 @@
|
|||
class: "btn btn-light",
|
||||
method: :get,
|
||||
params: { last_id: @inbox_last_id },
|
||||
data: { controller: :hotkey, hotkey: "." },
|
||||
form: { data: { turbo_stream: true } }
|
||||
|
|
|
@ -8,4 +8,5 @@
|
|||
class: "btn btn-light",
|
||||
method: :get,
|
||||
params: { last_id: @inbox_last_id },
|
||||
data: { controller: :hotkey, hotkey: "." },
|
||||
form: { data: { turbo_stream: true } }
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
class: "btn btn-light",
|
||||
method: :get,
|
||||
params: { last_id: @reports_last_id },
|
||||
data: { controller: :hotkey, hotkey: "." },
|
||||
form: { data: { turbo_stream: true } }
|
||||
|
||||
- parent_layout "moderation"
|
||||
|
|
|
@ -8,4 +8,5 @@
|
|||
class: "btn btn-light",
|
||||
method: :get,
|
||||
params: { last_id: @reports_last_id },
|
||||
data: { controller: :hotkey, hotkey: "." },
|
||||
form: { data: { turbo_stream: true } }
|
||||
|
|
|
@ -9,17 +9,17 @@
|
|||
%span.badge.rounded-pill.bg-warning.text-bg-warning.fs-7
|
||||
DEV
|
||||
%ul.nav.navbar-nav.me-auto
|
||||
= nav_entry t("navigation.timeline"), root_path, icon: 'home'
|
||||
= nav_entry t("navigation.inbox"), "/inbox", icon: "inbox", badge: inbox_count, badge_attr: { data: { controller: "pwa-badge" } }
|
||||
= nav_entry t("navigation.timeline"), root_path, icon: "home", hotkey: "g t"
|
||||
= nav_entry t("navigation.inbox"), "/inbox", icon: "inbox", badge: inbox_count, badge_attr: { data: { controller: "pwa-badge" } }, hotkey: "g i"
|
||||
- if APP_CONFIG.dig(:features, :discover, :enabled) || current_user.mod?
|
||||
= nav_entry t("navigation.discover"), discover_path, icon: 'compass'
|
||||
= nav_entry t("navigation.discover"), discover_path, icon: "compass", hotkey: "g d"
|
||||
%ul.nav.navbar-nav
|
||||
- if @user.present? && @user != current_user
|
||||
%li.nav-item.d-none.d-sm-block{ data: { bs_toggle: 'tooltip', bs_placement: 'bottom' }, title: t(".list") }
|
||||
%a.nav-link{ href: '#', data: { bs_target: '#modal-list-memberships', bs_toggle: :modal } }
|
||||
%i.fa.fa-list.hidden-xs
|
||||
%span.d-none.d-sm-inline.d-md-none= t(".list")
|
||||
= nav_entry t("navigation.notifications"), notifications_path, badge: notification_count, class: 'd-block d-sm-none'
|
||||
= nav_entry t("navigation.notifications"), notifications_path, badge: notification_count, class: "d-block d-sm-none", hotkey: "g n"
|
||||
%li.nav-item.dropdown.d-none.d-sm-block
|
||||
%a.nav-link.dropdown-toggle{ href: '#', data: { bs_toggle: :dropdown } }
|
||||
- if notification_count.nil?
|
||||
|
@ -30,7 +30,7 @@
|
|||
%span.badge= notification_count
|
||||
= render 'navigation/dropdown/notifications', notifications: notifications, size: "desktop"
|
||||
%li.nav-item.d-none.d-sm-block{ data: { bs_toggle: 'tooltip', bs_placement: 'bottom' }, title: t('.ask_question') }
|
||||
%a.nav-link{ href: '#', name: 'toggle-all-ask', data: { bs_target: '#modal-ask-followers', bs_toggle: :modal } }
|
||||
%a.nav-link{ href: "#", name: "toggle-all-ask", data: { bs_target: "#modal-ask-followers", bs_toggle: :modal, hotkey: "n" } }
|
||||
%i.fa.fa-pencil-square-o
|
||||
%li.nav-item.dropdown.profile--image-dropdown
|
||||
%a.nav-link.dropdown-toggle.p-sm-0{ href: "#", data: { bs_toggle: :dropdown } }
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
.dropdown-menu.dropdown-menu-end.profile-dropdown{ id: "rs-#{size}-nav-profile" }
|
||||
%h6.dropdown-header.d-none.d-sm-block= current_user.screen_name
|
||||
%a.dropdown-item{ href: user_path(current_user) }
|
||||
%a.dropdown-item{ href: user_path(current_user), data: { hotkey: "g p" } }
|
||||
%i.fa.fa-fw.fa-user
|
||||
= t(".profile")
|
||||
%a.dropdown-item{ href: edit_user_registration_path }
|
||||
|
@ -34,6 +34,10 @@
|
|||
%i.fa.fa-fw.fa-flask
|
||||
= t(".feedback.features")
|
||||
.dropdown-divider
|
||||
%a.dropdown-item{ href: "#", data: { bs_target: "#modal-hotkeys", bs_toggle: "modal", hotkey: "Shift+?,?,Shift+ß" } }
|
||||
%i.fa.fa-keyboard
|
||||
= t(".hotkeys")
|
||||
.dropdown-divider
|
||||
= link_to destroy_user_session_path, data: { turbo_method: :delete }, class: "dropdown-item" do
|
||||
%i.fa.fa-fw.fa-sign-out
|
||||
= t("voc.logout")
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
class: "btn btn-light",
|
||||
method: :get,
|
||||
params: { last_id: @notifications_last_id },
|
||||
data: { controller: :hotkey, hotkey: "." },
|
||||
form: { data: { turbo_stream: true } }
|
||||
|
||||
- provide(:title, generate_title(t(".title")))
|
||||
|
|
|
@ -11,4 +11,5 @@
|
|||
class: "btn btn-light",
|
||||
method: :get,
|
||||
params: { last_id: @notifications_last_id },
|
||||
data: { controller: :hotkey, hotkey: "." },
|
||||
form: { data: { turbo_stream: true } }
|
||||
|
|
|
@ -2,7 +2,9 @@
|
|||
= render "question", question: @question, hidden: false
|
||||
= render "question", question: @question, hidden: true
|
||||
.container.question-page
|
||||
#answers
|
||||
#answers{ data: { controller: "navigation" } }
|
||||
%button.d-none{ data: { hotkey: "j", action: "navigation#down" } }
|
||||
%button.d-none{ data: { hotkey: "k", action: "navigation#up" } }
|
||||
- @answers.each do |a|
|
||||
= render "answerbox", a: a, show_question: false
|
||||
|
||||
|
@ -12,6 +14,7 @@
|
|||
class: "btn btn-light",
|
||||
method: :get,
|
||||
params: { last_id: @answers_last_id },
|
||||
data: { controller: :hotkey, hotkey: "." },
|
||||
form: { data: { turbo_stream: true } }
|
||||
|
||||
- if user_signed_in? && !current_user.answered?(@question) && current_user != @question.user && @question.user&.privacy_allow_stranger_answers
|
||||
|
|
|
@ -8,4 +8,5 @@
|
|||
class: "btn btn-light",
|
||||
method: :get,
|
||||
params: { last_id: @answers_last_id },
|
||||
data: { controller: :hotkey, hotkey: "." },
|
||||
form: { data: { turbo_stream: true } }
|
||||
|
|
80
app/views/shared/_hotkeys.html.haml
Normal file
80
app/views/shared/_hotkeys.html.haml
Normal file
|
@ -0,0 +1,80 @@
|
|||
.modal.fade#modal-hotkeys{ aria: { hidden: true, labelledby: "modal-hotkeys-label" }, role: :dialog, tabindex: -1 }
|
||||
.modal-dialog
|
||||
.modal-content
|
||||
.modal-header
|
||||
%h5.modal-title#modal-hotkeys-label= t(".title")
|
||||
%button.btn-close{ data: { bs_dismiss: :modal }, type: :button }
|
||||
%span.visually-hidden= t("voc.close")
|
||||
.modal-body
|
||||
.row
|
||||
.col-6
|
||||
.card
|
||||
.card-header
|
||||
%h5.card-title= t(".navigation.title")
|
||||
%ul.list-group.list-group-flush
|
||||
%li.list-group-item
|
||||
= t("navigation.timeline")
|
||||
%kbd
|
||||
%kbd g
|
||||
%kbd t
|
||||
%li.list-group-item
|
||||
= t("navigation.inbox")
|
||||
%kbd
|
||||
%kbd g
|
||||
%kbd i
|
||||
- if APP_CONFIG.dig(:features, :discover, :enabled) || current_user.mod?
|
||||
%li.list-group-item
|
||||
= t("navigation.discover")
|
||||
%kbd
|
||||
%kbd g
|
||||
%kbd d
|
||||
%li.list-group-item
|
||||
= t("navigation.notifications")
|
||||
%kbd
|
||||
%kbd g
|
||||
%kbd n
|
||||
%li.list-group-item
|
||||
= t("navigation.dropdown.profile.profile")
|
||||
%kbd
|
||||
%kbd g
|
||||
%kbd p
|
||||
.col-6
|
||||
.card
|
||||
.card-header
|
||||
%h5.card-title= t(".global.title")
|
||||
%ul.list-group.list-group-flush
|
||||
%li.list-group-item
|
||||
= t(".global.navigation.up")
|
||||
%kbd k
|
||||
%li.list-group-item
|
||||
= t(".global.navigation.down")
|
||||
%kbd j
|
||||
%li.list-group-item
|
||||
= t("navigation.desktop.ask_question")
|
||||
%kbd n
|
||||
%li.list-group-item
|
||||
= t("voc.load")
|
||||
%kbd .
|
||||
%li.list-group-item
|
||||
= t(".show_dialog")
|
||||
%kbd ?
|
||||
.card
|
||||
.card-header
|
||||
%h5.card-title= t(".answer.title")
|
||||
%ul.list-group.list-group-flush
|
||||
%li.list-group-item
|
||||
= t("voc.smile")
|
||||
%kbd s
|
||||
%li.list-group-item
|
||||
= t(".answer.view_comments")
|
||||
%kbd x
|
||||
%li.list-group-item
|
||||
= t(".answer.comment")
|
||||
%kbd c
|
||||
%li.list-group-item
|
||||
= t(".answer.all_answers")
|
||||
%kbd a
|
||||
%li.list-group-item
|
||||
= t(".answer.view_answer")
|
||||
%kbd l
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
#timeline
|
||||
#timeline{ data: { controller: "navigation" } }
|
||||
%button.d-none{ data: { hotkey: "j", action: "navigation#down" } }
|
||||
%button.d-none{ data: { hotkey: "k", action: "navigation#up" } }
|
||||
- @timeline.each do |answer|
|
||||
= render "answerbox", a: answer
|
||||
|
||||
|
@ -8,6 +10,7 @@
|
|||
class: "btn btn-light",
|
||||
method: :get,
|
||||
params: { last_id: @timeline_last_id },
|
||||
data: { controller: :hotkey, hotkey: "." },
|
||||
form: { data: { turbo_stream: true } }
|
||||
|
||||
- provide(:title, @title || APP_CONFIG["site_name"])
|
||||
|
|
|
@ -8,4 +8,5 @@
|
|||
class: "btn btn-light",
|
||||
method: :get,
|
||||
params: { last_id: @timeline_last_id },
|
||||
data: { controller: :hotkey, hotkey: "." },
|
||||
form: { data: { turbo_stream: true } }
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
class: "btn btn-light",
|
||||
method: :get,
|
||||
params: { last_id: @questions_last_id },
|
||||
data: { controller: :hotkey, hotkey: "." },
|
||||
form: { data: { turbo_stream: true } }
|
||||
|
||||
- provide(:title, questions_title(@user))
|
||||
|
|
|
@ -8,4 +8,5 @@
|
|||
class: "btn btn-light",
|
||||
method: :get,
|
||||
params: { last_id: @questions_last_id },
|
||||
data: { controller: :hotkey, hotkey: "." },
|
||||
form: { data: { turbo_stream: true } }
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
- unless @user.banned?
|
||||
#pinned-answers
|
||||
- @pinned_answers.each do |a|
|
||||
= render "answerbox", a:
|
||||
%div{ data: { controller: "navigation" } }
|
||||
%button.d-none{ data: { hotkey: "j", action: "navigation#down" } }
|
||||
%button.d-none{ data: { hotkey: "k", action: "navigation#up" } }
|
||||
#pinned-answers
|
||||
- @pinned_answers.each do |a|
|
||||
= render "answerbox", a:
|
||||
|
||||
#answers
|
||||
- @answers.each do |a|
|
||||
= render "answerbox", a:
|
||||
#answers
|
||||
- @answers.each do |a|
|
||||
= render "answerbox", a:
|
||||
|
||||
- if @more_data_available
|
||||
.d-flex.justify-content-center.justify-content-sm-start#paginator
|
||||
|
@ -13,6 +16,7 @@
|
|||
class: "btn btn-light",
|
||||
method: :get,
|
||||
params: { last_id: @answers_last_id },
|
||||
data: { controller: :hotkey, hotkey: "." },
|
||||
form: { data: { turbo_stream: true } }
|
||||
|
||||
:ruby
|
||||
|
|
|
@ -8,4 +8,5 @@
|
|||
class: "btn btn-light",
|
||||
method: :get,
|
||||
params: { last_id: @answers_last_id },
|
||||
data: { controller: :hotkey, hotkey: "." },
|
||||
form: { data: { turbo_stream: true } }
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
class: "btn btn-light",
|
||||
method: :get,
|
||||
params: { last_id: @relationships_last_id },
|
||||
data: { controller: :hotkey, hotkey: "." },
|
||||
form: { data: { turbo_stream: true } }
|
||||
|
||||
- provide(:title, t(".title.#{type}", user: @user.profile.safe_name))
|
||||
|
|
|
@ -9,4 +9,5 @@
|
|||
class: "btn btn-light",
|
||||
method: :get,
|
||||
params: { last_id: @relationships_last_id },
|
||||
data: { controller: :hotkey, hotkey: "." },
|
||||
form: { data: { turbo_stream: true } }
|
||||
|
|
|
@ -330,6 +330,7 @@ en:
|
|||
heading: "Feedback"
|
||||
bugs: "Bugs"
|
||||
features: "Feature Requests"
|
||||
hotkeys: "Keyboard Shortcuts"
|
||||
desktop:
|
||||
ask_question: "Ask a question"
|
||||
list: :user.actions.list
|
||||
|
@ -583,6 +584,22 @@ en:
|
|||
question:
|
||||
visible_to_you: "Only visible to you as it was asked directly"
|
||||
visible_mod_mode: "You can see this because you are in moderation view"
|
||||
hotkeys:
|
||||
navigation:
|
||||
title: "Navigation"
|
||||
title: "Keyboard Shortcuts"
|
||||
show_dialog: "Show this dialog"
|
||||
answer:
|
||||
title: "Answer actions"
|
||||
view_comments: "View comments"
|
||||
comment: "Write a comment"
|
||||
all_answers: "View all answers"
|
||||
view_answer: "View answer page"
|
||||
global:
|
||||
navigation:
|
||||
up: "Move selection up"
|
||||
down: "Move selection down"
|
||||
title: "Site-wide"
|
||||
tabs:
|
||||
admin:
|
||||
announcements: "Announcements"
|
||||
|
|
|
@ -17,6 +17,7 @@ en:
|
|||
mute: "Mute"
|
||||
save: "Save changes"
|
||||
show_anonymous_questions: "Show all questions from this user"
|
||||
smile: "Smile"
|
||||
subscribe: "Subscribe"
|
||||
unsubscribe: "Unsubscribe"
|
||||
register: "Sign up"
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
"dependencies": {
|
||||
"@fontsource/lexend": "^4.5.15",
|
||||
"@fortawesome/fontawesome-free": "^6.4.0",
|
||||
"@github/hotkey": "^2.0.1",
|
||||
"@hotwired/stimulus": "^3.2.1",
|
||||
"@hotwired/turbo-rails": "^7.3.0",
|
||||
"@melloware/coloris": "^0.19.1",
|
||||
|
|
|
@ -184,6 +184,11 @@
|
|||
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-free/-/fontawesome-free-6.4.0.tgz#1ee0c174e472c84b23cb46c995154dc383e3b4fe"
|
||||
integrity sha512-0NyytTlPJwB/BF5LtRV8rrABDbe3TdTXqNB3PdZ+UUUZAEIrdOJdmABqKjt4AXwIoJNaRVVZEXxpNrqvE1GAYQ==
|
||||
|
||||
"@github/hotkey@^2.0.1":
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@github/hotkey/-/hotkey-2.0.1.tgz#24ad6b49313cee5b98368174eab16a4b53a08ec7"
|
||||
integrity sha512-qKXjAJjtheJbf4ie3hi8IwrHWJZHB5qdojR6JGo6jvQNPpsdUbk/NIdU8sxu4PW41CjW80vfciDMu3MAP3j2Fg==
|
||||
|
||||
"@hotwired/stimulus@^3.2.1":
|
||||
version "3.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@hotwired/stimulus/-/stimulus-3.2.1.tgz#e3de23623b0c52c247aba4cd5d530d257008676b"
|
||||
|
|
Loading…
Reference in a new issue