Add Stimulus controller for navigation shortcuts

This commit is contained in:
Karina Kwiatek 2023-03-05 17:17:02 +01:00
parent dbd6f96f53
commit a64a4699b0
7 changed files with 62 additions and 4 deletions

View File

@ -107,6 +107,7 @@ $unicodeRangeValues in Lexend.$unicodeMap {
"components/comments",
"components/container",
"components/entry",
"components/hotkey",
"components/icons",
"components/inbox-actions",
"components/inbox-entry",

View File

@ -0,0 +1,5 @@
.js-hotkey-navigating {
[data-navigation-target="current"] {
outline: var(--primary) solid 4px;
}
}

View File

@ -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);
}
}
}

View File

@ -11,6 +11,7 @@ import CropperController from "retrospring/controllers/cropper_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
@ -31,6 +32,7 @@ export default function (): void {
window['Stimulus'].register('format-popup', FormatPopupController);
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);
}

View File

@ -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
%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

View File

@ -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

View File

@ -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