Skip to content

Commit

Permalink
Add hotkey to new folder form #134 (#140)
Browse files Browse the repository at this point in the history
* Refactored ShowSpace view: rendering name field dynamically

* Extract NewFolder view from ShowSpace view to separate templ file

* Added autofocus to new folder form (used when invalid) #134

* Refactored EditFolder view: rendering name field dynamically

* Added hotkey to "Make new folder" button #134

* Fix for hotkeys in Firefox/Safari #134
  • Loading branch information
verheyenkoen authored May 16, 2024
1 parent 276f034 commit 0cb01ec
Show file tree
Hide file tree
Showing 17 changed files with 476 additions and 265 deletions.
19 changes: 10 additions & 9 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Ignore all .env files
- Use Docker volumes in devcontainer
- Added hotkey "n" to the "+ Make new folder" button in the folder view

### Fixed

Expand Down Expand Up @@ -106,12 +107,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Initial release

[unreleased]: https://github.com/ugent-library/deliver/compare/v1.1.4...HEAD
[v1.1.4]: https://github.com/ugent-library/deliver/compare/v1.1.3...v1.1.4
[v1.1.3]: https://github.com/ugent-library/deliver/compare/v1.1.2...v1.1.3
[v1.1.2]: https://github.com/ugent-library/deliver/compare/v1.1.1...v1.1.2
[v1.1.1]: https://github.com/ugent-library/deliver/compare/v1.1.0...v1.1.1
[v1.1.0]: https://github.com/ugent-library/deliver/compare/v1.0.3...v1.1.0
[v1.0.3]: https://github.com/ugent-library/deliver/compare/v1.0.2...v1.0.3
[v1.0.2]: https://github.com/ugent-library/deliver/compare/v1.0.1...v1.0.2
[v1.0.1]: https://github.com/ugent-library/deliver/compare/v1.0.0...v1.0.1
[unreleased]: https://github.com/ugent-library/deliver/compare/v1.1.4...HEAD
[v1.1.4]: https://github.com/ugent-library/deliver/compare/v1.1.3...v1.1.4
[v1.1.3]: https://github.com/ugent-library/deliver/compare/v1.1.2...v1.1.3
[v1.1.2]: https://github.com/ugent-library/deliver/compare/v1.1.1...v1.1.2
[v1.1.1]: https://github.com/ugent-library/deliver/compare/v1.1.0...v1.1.1
[v1.1.0]: https://github.com/ugent-library/deliver/compare/v1.0.3...v1.1.0
[v1.0.3]: https://github.com/ugent-library/deliver/compare/v1.0.2...v1.0.3
[v1.0.2]: https://github.com/ugent-library/deliver/compare/v1.0.1...v1.0.2
[v1.0.1]: https://github.com/ugent-library/deliver/compare/v1.0.0...v1.0.1
8 changes: 5 additions & 3 deletions assets/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import formSubmit from "./form_submit.js";
import formUploadProgress from "./form_upload_progress.js";
import clipboard from "./clipboard.js";
import selectValue from "./select_value.js";
import hotkeys from "./hotkeys.js";

window.htmx = htmx;

Expand All @@ -19,11 +20,12 @@ htmx.onLoad(function (el) {
formUploadProgress(el);
clipboard(el);
selectValue(el);
hotkeys(el);
});

htmx.on("htmx:config-request", (evt) => {
evt.detail.headers["X-CSRF-Token"] = document.querySelector(
'meta[name="csrf-token"]'
'meta[name="csrf-token"]',
).content;
});

Expand Down Expand Up @@ -61,15 +63,15 @@ htmx.on("htmx:confirm", (evt) => {
() => {
evt.detail.issueRequest();
},
false
false,
);

modalEl.addEventListener(
"hidden.bs.modal",
() => {
modalEl.remove();
},
false
false,
);

new bs.Modal(modalEl).show();
Expand Down
24 changes: 24 additions & 0 deletions assets/js/hotkeys.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import htmx from "htmx.org";

export default function (rootEl) {
const hotkeyElements = Array.from(rootEl.querySelectorAll("[data-hotkey]"));
if (hotkeyElements.length) {
htmx.on("keyup", (evt) => {
const el = hotkeyElements.find((el) =>
el.matches(`[data-hotkey="${evt.key}"]`),
);

// Make sure the "hotkey-element" is still in the DOM
// and the triggering element is not a form field
if (el && el.isConnected && !isFormField(evt.target)) {
el.dispatchEvent(new MouseEvent("click"));
}
});
}
}

function isFormField(el) {
return el.matches(
"input:not([type=button]):not([type=reset]):not([type=submit]):not([type=image]):not([type=file]), textarea, select",
);
}
27 changes: 27 additions & 0 deletions cypress/e2e/managing-files.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,33 @@ describe("Managing files", () => {
assertFolderFileCount(0);
});

it("should be possible to move back to the make folder form using a hotkey", () => {
cy.intercept("POST", "/folders/*/files").as("uploadFile");

cy.get("input[type=file]").selectFile("cypress/fixtures/test.txt");
cy.wait("@uploadFile");

// Typing capital N should not trigger the hotkey
cy.get("body").type("N");
cy.url().should("eq", "@adminUrl");

// Typing in a form field should not trigger the hotkey
cy.get('input[value*="/share/"]')
.as("shareUrl")
.invoke("removeAttr", "readonly");
cy.get("@shareUrl").focus().type("{leftArrow}n").blur();
cy.url().should("eq", "@adminUrl");

// Test the actual hotkey
cy.get("body").type("n");
cy.location("pathname").should(
"eq",
`/spaces/${Cypress.env("DEFAULT_SPACE")}`,
);

cy.get("#folder-name").should("have.attr", "autofocus");
});

function assertFileUpload(
fileName: string,
{
Expand Down
16 changes: 12 additions & 4 deletions cypress/e2e/managing-folders.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,13 +91,17 @@ describe("Managing folders", () => {

cy.getFolderCount("total").as("totalNumberOfFolders");

cy.get("#folder-name").should("not.have.class", "is-invalid");
cy.get("#folder-name")
.should("not.have.class", "is-invalid")
.should("not.have.attr", "autofocus");
cy.get("#folder-name-invalid").should("not.exist");

cy.setFieldByLabel("Folder name", " ");
cy.contains(".btn", "Make folder").click();

cy.get("#folder-name").should("have.class", "is-invalid");
cy.get("#folder-name")
.should("have.class", "is-invalid")
.should("have.attr", "autofocus");
cy.get("#folder-name-invalid")
.should("be.visible")
.and("have.text", "name cannot be empty");
Expand All @@ -119,13 +123,17 @@ describe("Managing folders", () => {

cy.visitSpace();

cy.get("#folder-name").should("not.have.class", "is-invalid");
cy.get("#folder-name")
.should("not.have.class", "is-invalid")
.should("not.have.attr", "autofocus");
cy.get("#folder-name-invalid").should("not.exist");

cy.setFieldByLabel("Folder name", FOLDER_NAME);
cy.contains(".btn", "Make folder").click();

cy.get("#folder-name").should("have.class", "is-invalid");
cy.get("#folder-name")
.should("have.class", "is-invalid")
.should("have.attr", "autofocus");
cy.get("#folder-name-invalid")
.should("be.visible")
.and("have.text", "name must be unique");
Expand Down
13 changes: 10 additions & 3 deletions handlers/spaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,12 +156,19 @@ func showSpace(w http.ResponseWriter, r *http.Request, folder *models.Folder, er
c := ctx.Get(r)
space := ctx.GetSpace(r)

validationErrors := okay.NewErrors()
if err != nil && !errors.As(err, &validationErrors) {
newFolderArgs := views.NewFolderArgs{
Folder: folder,
Errors: okay.NewErrors(),
}
if err != nil && !errors.As(err, &newFolderArgs.Errors) {
c.HandleError(w, r, err)
return
}

if len(newFolderArgs.Errors.Errors) > 0 || r.URL.Query().Get("focus") == "new-folder" {
newFolderArgs.Autofocus = true
}

var userSpaces []*models.Space
if c.Permissions.IsAdmin(c.User) {
userSpaces, err = c.Repo.Spaces.GetAll(r.Context())
Expand All @@ -180,7 +187,7 @@ func showSpace(w http.ResponseWriter, r *http.Request, folder *models.Folder, er
return
}

views.ShowSpace(c, space, folders, pagination, userSpaces, folder, validationErrors).Render(r.Context(), w)
views.ShowSpace(c, space, folders, pagination, userSpaces, newFolderArgs).Render(r.Context(), w)
}

func getPagination(r *http.Request) *models.Pagination {
Expand Down
2 changes: 1 addition & 1 deletion static/js/app.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion static/js/app.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion static/mix-manifest.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"/js/app.js": "/js/app.js?id=9275508361e2b0ab13160aab9f78edff",
"/js/app.js": "/js/app.js?id=528e3e99b04cecd259db3e4d8d9fb445",
"/css/app.css": "/css/app.css?id=36d98b9870456847d5142cee3bbfcbdf",
"/images/ghent-university-library-logo.svg": "/images/ghent-university-library-logo.svg?id=729f32c469a1aaa6f22f913591cd6fa1",
"/images/ghent-university-library-mark.svg": "/images/ghent-university-library-mark.svg?id=ddfeba39843a5fa6ab3c7adae01c858c",
Expand Down
Loading

0 comments on commit 0cb01ec

Please sign in to comment.