Add Stimulus controller for navigation shortcuts
This commit is contained in:
parent
dbd6f96f53
commit
a64a4699b0
|
@ -107,6 +107,7 @@ $unicodeRangeValues in Lexend.$unicodeMap {
|
||||||
"components/comments",
|
"components/comments",
|
||||||
"components/container",
|
"components/container",
|
||||||
"components/entry",
|
"components/entry",
|
||||||
|
"components/hotkey",
|
||||||
"components/icons",
|
"components/icons",
|
||||||
"components/inbox-actions",
|
"components/inbox-actions",
|
||||||
"components/inbox-entry",
|
"components/inbox-entry",
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
.js-hotkey-navigating {
|
||||||
|
[data-navigation-target="current"] {
|
||||||
|
outline: var(--primary) solid 4px;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
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) {
|
||||||
|
if (!this.hasCurrentTarget) {
|
||||||
|
const first = this.traversableTargets[0];
|
||||||
|
first.dataset.navigationTarget = "current";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
currentTargetConnected(target: HTMLElement) {
|
||||||
|
target.querySelectorAll<HTMLElement>("[data-selection-hotkey]")
|
||||||
|
.forEach(el => install(el, el.dataset.selectionHotkey))
|
||||||
|
}
|
||||||
|
|
||||||
|
currentTargetDisconnected(target: HTMLElement) {
|
||||||
|
target.querySelectorAll<HTMLElement>("[data-selection-hotkey]")
|
||||||
|
.forEach(el => uninstall(el))
|
||||||
|
}
|
||||||
|
|
||||||
|
up(): void {
|
||||||
|
this.navigate(this.currentTarget.previousElementSibling as HTMLElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
down(): void {
|
||||||
|
this.navigate(this.currentTarget.nextElementSibling as HTMLElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = "current";
|
||||||
|
target.scrollIntoView(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,6 +11,7 @@ import CropperController from "retrospring/controllers/cropper_controller";
|
||||||
import InboxSharingController from "retrospring/controllers/inbox_sharing_controller";
|
import InboxSharingController from "retrospring/controllers/inbox_sharing_controller";
|
||||||
import ToastController from "retrospring/controllers/toast_controller";
|
import ToastController from "retrospring/controllers/toast_controller";
|
||||||
import PwaBadgeController from "retrospring/controllers/pwa_badge_controller";
|
import PwaBadgeController from "retrospring/controllers/pwa_badge_controller";
|
||||||
|
import NavigationController from "retrospring/controllers/navigation_controller";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This module sets up Stimulus and our controllers
|
* This module sets up Stimulus and our controllers
|
||||||
|
@ -31,6 +32,7 @@ export default function (): void {
|
||||||
window['Stimulus'].register('format-popup', FormatPopupController);
|
window['Stimulus'].register('format-popup', FormatPopupController);
|
||||||
window['Stimulus'].register('inbox-sharing', InboxSharingController);
|
window['Stimulus'].register('inbox-sharing', InboxSharingController);
|
||||||
window['Stimulus'].register('pwa-badge', PwaBadgeController);
|
window['Stimulus'].register('pwa-badge', PwaBadgeController);
|
||||||
|
window['Stimulus'].register('navigation', NavigationController);
|
||||||
window['Stimulus'].register('theme', ThemeController);
|
window['Stimulus'].register('theme', ThemeController);
|
||||||
window['Stimulus'].register('toast', ToastController);
|
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: "l" }, disabled: !user_signed_in? }
|
||||||
%i.fa.fa-fw.fa-smile-o
|
%i.fa.fa-fw.fa-smile-o
|
||||||
%span{ id: "ab-smile-count-#{a.id}" }= a.smiles.count
|
%span{ id: "ab-smile-count-#{a.id}" }= a.smiles.count
|
||||||
- unless display_all
|
- 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
|
%i.fa.fa-fw.fa-comments
|
||||||
%span{ id: "ab-comment-count-#{a.id}" }= a.comment_count
|
%span{ id: "ab-comment-count-#{a.id}" }= a.comment_count
|
||||||
.btn-group
|
.btn-group
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
- display_all ||= nil
|
- 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?
|
- if @question.nil?
|
||||||
= render "answerbox/header", a: a, display_all: display_all
|
= render "answerbox/header", a: a, display_all: display_all
|
||||||
.card-body
|
.card-body
|
||||||
|
|
|
@ -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|
|
- @timeline.each do |answer|
|
||||||
= render "answerbox", a: answer
|
= render "answerbox", a: answer
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue