Replace link has button role with button tag

# Change HTML tag to button

Replace the link tag with an HTML button to prevent some screen readers from having confusing announcements. By using the HTML button, users can use the Enter and Space keys to activate actions by default, instead of implementing them in JavaScript.

# Differentiate links and buttons visually

When activating the link element, the user may expect the web page to navigate to the URL and the page will refresh; when activating the button element, the user may expect the web page to still be on the same page, so that their current state, such as: input value, won't disappear.

Links and buttons should have different styles visually, so that users can't expect what will happen when they activate a link or a button.

I added the underline to the links, because that is the common pattern. Buttons have border and background color in a common pattern. But I think that will change the current layout drastically. So I added the focus, hover and active classes to the buttons instead.
This commit is contained in:
Tân Î-sîn 2024-02-10 09:09:30 +08:00 committed by GitHub
parent 0f85c0511a
commit ea58bac548
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 186 additions and 124 deletions

View file

@ -59,27 +59,25 @@
aria-describedby="feed-title-{{ .ID }}">{{ icon "edit" }}<span class="icon-label">{{ t "menu.edit_feed" }}</span></a>
</li>
<li class="item-meta-icons-remove">
<a href="#"
role="button"
<button
aria-describedby="feed-title-{{ .ID }}"
data-confirm="true"
data-label-question="{{ t "confirm.question" }}"
data-label-yes="{{ t "confirm.yes" }}"
data-label-no="{{ t "confirm.no" }}"
data-label-loading="{{ t "confirm.loading" }}"
data-url="{{ route "removeFeed" "feedID" .ID }}">{{ icon "delete" }}<span class="icon-label">{{ t "action.remove" }}</span></a>
data-url="{{ route "removeFeed" "feedID" .ID }}">{{ icon "delete" }}<span class="icon-label">{{ t "action.remove" }}</span></button>
</li>
{{ if .UnreadCount }}
<li class="item-meta-icons-mark-as-read">
<a href="#"
role="button"
<button
aria-describedby="feed-title-{{ .ID }}"
data-confirm="true"
data-label-question="{{ t "confirm.question" }}"
data-label-yes="{{ t "confirm.yes" }}"
data-label-no="{{ t "confirm.no" }}"
data-label-loading="{{ t "confirm.loading" }}"
data-url="{{ route "markFeedAsRead" "feedID" .ID }}">{{ icon "read" }}<span class="icon-label">{{ t "menu.mark_all_as_read" }}</span></a>
data-url="{{ route "markFeedAsRead" "feedID" .ID }}">{{ icon "read" }}<span class="icon-label">{{ t "menu.mark_all_as_read" }}</span></button>
</li>
{{ end }}
</ul>

View file

@ -1,19 +1,23 @@
{{ define "feed_menu" }}
<nav aria-label="{{ t "page.feeds.title" }} {{ t "menu.title" }}"><ul>
<li>
<a href="{{ route "feeds" }}">{{ icon "feeds" }}{{ t "menu.feeds" }}</a>
<a class="page-link" href="{{ route "feeds" }}">{{ icon "feeds" }}{{ t "menu.feeds" }}</a>
</li>
<li>
<a href="{{ route "addSubscription" }}">{{ icon "add-feed" }}{{ t "menu.add_feed" }}</a>
<a class="page-link" href="{{ route "addSubscription" }}">{{ icon "add-feed" }}{{ t "menu.add_feed" }}</a>
</li>
<li>
<a href="{{ route "export" }}">{{ icon "feed-export" }}{{ t "menu.export" }}</a>
<a class="page-link" href="{{ route "export" }}">{{ icon "feed-export" }}{{ t "menu.export" }}</a>
</li>
<li>
<a href="{{ route "import" }}">{{ icon "feed-import" }}{{ t "menu.import" }}</a>
<a class="page-link" href="{{ route "import" }}">{{ icon "feed-import" }}{{ t "menu.import" }}</a>
</li>
<li>
<a role="button" href="{{ route "refreshAllFeeds" }}">{{ icon "refresh" }}{{ t "menu.refresh_all_feeds" }}</a>
<form action="{{ route "refreshAllFeeds" }}" class="page-header-action-form">
<button class="page-button" data-label-loading="{{ t "confirm.loading" }}">
{{ icon "refresh" }}{{ t "menu.refresh_all_feeds" }}
</button>
</form>
</li>
</ul></nav>
{{ end }}

View file

@ -17,8 +17,7 @@
</ul>
<ul class="item-meta-icons">
<li class="item-meta-icons-read">
<a href="#"
role="button"
<button
aria-describedby="entry-title-{{ .entry.ID }}"
title="{{ t "entry.status.title" }}"
data-toggle-status="true"
@ -26,11 +25,10 @@
data-label-read="{{ t "entry.status.read" }}"
data-label-unread="{{ t "entry.status.unread" }}"
data-value="{{ if eq .entry.Status "read" }}read{{ else }}unread{{ end }}"
>{{ if eq .entry.Status "read" }}{{ icon "unread" }}{{ else }}{{ icon "read" }}{{ end }}<span class="icon-label">{{ if eq .entry.Status "read" }}{{ t "entry.status.unread" }}{{ else }}{{ t "entry.status.read" }}{{ end }}</span></a>
>{{ if eq .entry.Status "read" }}{{ icon "unread" }}{{ else }}{{ icon "read" }}{{ end }}<span class="icon-label">{{ if eq .entry.Status "read" }}{{ t "entry.status.unread" }}{{ else }}{{ t "entry.status.read" }}{{ end }}</span></button>
</li>
<li class="item-meta-icons-star">
<a href="#"
role="button"
<button
aria-describedby="entry-title-{{ .entry.ID }}"
data-toggle-bookmark="true"
data-bookmark-url="{{ route "toggleBookmark" "entryID" .entry.ID }}"
@ -38,39 +36,36 @@
data-label-star="{{ t "entry.bookmark.toggle.on" }}"
data-label-unstar="{{ t "entry.bookmark.toggle.off" }}"
data-value="{{ if .entry.Starred }}star{{ else }}unstar{{ end }}"
>{{ if .entry.Starred }}{{ icon "unstar" }}{{ else }}{{ icon "star" }}{{ end }}<span class="icon-label">{{ if .entry.Starred }}{{ t "entry.bookmark.toggle.off" }}{{ else }}{{ t "entry.bookmark.toggle.on" }}{{ end }}</span></a>
>{{ if .entry.Starred }}{{ icon "unstar" }}{{ else }}{{ icon "star" }}{{ end }}<span class="icon-label">{{ if .entry.Starred }}{{ t "entry.bookmark.toggle.off" }}{{ else }}{{ t "entry.bookmark.toggle.on" }}{{ end }}</span></button>
</li>
{{ if .entry.ShareCode }}
<li class="item-meta-icons-share">
<a href="{{ route "sharedEntry" "shareCode" .entry.ShareCode }}"
role="button"
aria-describedby="entry-title-{{ .entry.ID }}"
title="{{ t "entry.shared_entry.title" }}"
target="_blank">{{ icon "share" }}<span class="icon-label">{{ t "entry.shared_entry.label" }}</span></a>
</li>
<li class="item-meta-icons-delete">
<a href="#"
role="button"
<button
aria-describedby="entry-title-{{ .entry.ID }}"
data-confirm="true"
data-url="{{ route "unshareEntry" "entryID" .entry.ID }}"
data-label-question="{{ t "confirm.question" }}"
data-label-yes="{{ t "confirm.yes" }}"
data-label-no="{{ t "confirm.no" }}"
data-label-loading="{{ t "confirm.loading" }}">{{ icon "delete" }}<span class="icon-label">{{ t "entry.unshare.label" }}</span></a>
data-label-loading="{{ t "confirm.loading" }}">{{ icon "delete" }}<span class="icon-label">{{ t "entry.unshare.label" }}</span></button>
</li>
{{ end }}
{{ if .hasSaveEntry }}
<li>
<a href="#"
role="button"
<button
aria-describedby="entry-title-{{ .entry.ID }}"
title="{{ t "entry.save.title" }}"
data-save-entry="true"
data-save-url="{{ route "saveEntry" "entryID" .entry.ID }}"
data-label-loading="{{ t "entry.state.saving" }}"
data-label-done="{{ t "entry.save.completed" }}"
>{{ icon "save" }}<span class="icon-label">{{ t "entry.save.label" }}</span></a>
>{{ icon "save" }}<span class="icon-label">{{ t "entry.save.label" }}</span></button>
</li>
{{ end }}
<li class="item-meta-icons-external-url">

View file

@ -55,28 +55,26 @@
</li>
{{ if eq (deRef .FeedCount) 0 }}
<li class="item-meta-icons-delete">
<a href="#"
role="button"
<button
aria-describedby="category-title-{{ .ID }}"
data-confirm="true"
data-label-question="{{ t "confirm.question" }}"
data-label-yes="{{ t "confirm.yes" }}"
data-label-no="{{ t "confirm.no" }}"
data-label-loading="{{ t "confirm.loading" }}"
data-url="{{ route "removeCategory" "categoryID" .ID }}">{{ icon "delete" }}<span class="icon-label">{{ t "action.remove" }}</span></a>
data-url="{{ route "removeCategory" "categoryID" .ID }}">{{ icon "delete" }}<span class="icon-label">{{ t "action.remove" }}</span></button>
</li>
{{ end }}
{{ if gt (deRef .TotalUnread) 0 }}
<li class="item-meta-icons-mark-as-read">
<a href="#"
role="button"
<button
aria-describedby="category-title-{{ .ID }}"
data-confirm="true"
data-label-question="{{ t "confirm.question" }}"
data-label-yes="{{ t "confirm.yes" }}"
data-label-no="{{ t "confirm.no" }}"
data-label-loading="{{ t "confirm.loading" }}"
data-url="{{ route "markCategoryAsRead" "categoryID" .ID }}">{{ icon "read" }}<span class="icon-label">{{ t "menu.mark_all_as_read" }}</span></a>
data-url="{{ route "markCategoryAsRead" "categoryID" .ID }}">{{ icon "read" }}<span class="icon-label">{{ t "menu.mark_all_as_read" }}</span></button>
</li>
{{ end }}
</ul>

View file

@ -17,40 +17,47 @@
<ul>
{{ if .entries }}
<li>
<a href="#"
role="button"
<button
class="page-button"
data-action="markPageAsRead"
data-label-question="{{ t "confirm.question" }}"
data-label-yes="{{ t "confirm.yes" }}"
data-label-no="{{ t "confirm.no" }}"
data-label-loading="{{ t "confirm.loading" }}"
data-show-only-unread="{{ if .showOnlyUnreadEntries }}1{{ end }}">{{ icon "mark-page-as-read" }}{{ t "menu.mark_page_as_read" }}</a>
data-show-only-unread="{{ if .showOnlyUnreadEntries }}1{{ end }}">{{ icon "mark-page-as-read" }}{{ t "menu.mark_page_as_read" }}</button>
</li>
<li>
<a href="#"
role="button"
<button
class="page-button"
data-confirm="true"
data-label-question="{{ t "confirm.question" }}"
data-label-yes="{{ t "confirm.yes" }}"
data-label-no="{{ t "confirm.no" }}"
data-label-loading="{{ t "confirm.loading" }}"
data-url="{{ route "markCategoryAsRead" "categoryID" .category.ID }}">{{ icon "mark-all-as-read" }}{{ t "menu.mark_all_as_read" }}</a>
data-url="{{ route "markCategoryAsRead" "categoryID" .category.ID }}">{{ icon "mark-all-as-read" }}{{ t "menu.mark_all_as_read" }}</button>
</li>
{{ end }}
{{ if .showOnlyUnreadEntries }}
<li>
<a href="{{ route "categoryEntriesAll" "categoryID" .category.ID }}">{{ icon "show-all-entries" }}{{ t "menu.show_all_entries" }}</a>
<a class="page-link" href="{{ route "categoryEntriesAll" "categoryID" .category.ID }}">{{ icon "show-all-entries" }}{{ t "menu.show_all_entries" }}</a>
</li>
{{ else }}
<li>
<a href="{{ route "categoryEntries" "categoryID" .category.ID }}">{{ icon "show-unread-entries" }}{{ t "menu.show_only_unread_entries" }}</a>
<a class="page-link" href="{{ route "categoryEntries" "categoryID" .category.ID }}">{{ icon "show-unread-entries" }}{{ t "menu.show_only_unread_entries" }}</a>
</li>
{{ end }}
<li>
<a href="{{ route "categoryFeeds" "categoryID" .category.ID }}">{{ icon "feeds" }}{{ t "menu.feeds" }}</a>
<a class="page-link" href="{{ route "categoryFeeds" "categoryID" .category.ID }}">{{ icon "feeds" }}{{ t "menu.feeds" }}</a>
</li>
<li>
<a role="button" href="{{ route "refreshCategoryEntriesPage" "categoryID" .category.ID }}">{{ icon "refresh" }}{{ t "menu.refresh_all_feeds" }}</a>
<form
action="{{ route "refreshCategoryEntriesPage" "categoryID" .category.ID }}"
class="page-header-action-form"
>
<button class="page-button" data-label-loading="{{ t "confirm.loading" }}">
{{ icon "refresh" }}{{ t "menu.refresh_all_feeds" }}
</button>
</form>
</li>
</ul>
</nav>
@ -101,13 +108,14 @@
{{ if .entries }}
<ul>
<li>
<a href="#"
<button
class="page-button"
data-action="markPageAsRead"
data-label-question="{{ t "confirm.question" }}"
data-label-yes="{{ t "confirm.yes" }}"
data-label-no="{{ t "confirm.no" }}"
data-label-loading="{{ t "confirm.loading" }}"
data-show-only-unread="{{ if .showOnlyUnreadEntries }}1{{ end }}">{{ icon "mark-page-as-read" }}{{ t "menu.mark_page_as_read" }}</a>
data-show-only-unread="{{ if .showOnlyUnreadEntries }}1{{ end }}">{{ icon "mark-page-as-read" }}{{ t "menu.mark_page_as_read" }}</button>
</li>
</ul>
{{ end }}

View file

@ -10,26 +10,39 @@
<nav aria-label="{{ .category.Title }} {{ t "page.feeds.title" }} {{ t "menu.title" }}">
<ul>
<li>
<a href="{{ route "categoryEntries" "categoryID" .category.ID }}">{{ icon "entries" }}{{ t "menu.feed_entries" }}</a>
<a class="page-link" href="{{ route "categoryEntries" "categoryID" .category.ID }}">{{ icon "entries" }}{{ t "menu.feed_entries" }}</a>
</li>
<li>
<a href="{{ route "editCategory" "categoryID" .category.ID }}">{{ icon "edit" }}{{ t "menu.edit_category" }}</a>
<a class="page-link" href="{{ route "editCategory" "categoryID" .category.ID }}">{{ icon "edit" }}{{ t "menu.edit_category" }}</a>
</li>
{{ if eq .total 0 }}
<li>
<a href="#"
role="button"
<button
class="page-button"
data-confirm="true"
data-label-question="{{ t "confirm.question" }}"
data-label-yes="{{ t "confirm.yes" }}"
data-label-no="{{ t "confirm.no" }}"
data-label-loading="{{ t "confirm.loading" }}"
data-redirect-url="{{ route "categories" }}"
data-url="{{ route "removeCategory" "categoryID" .category.ID }}">{{ icon "delete" }}{{ t "action.remove" }}</a>
data-url="{{ route "removeCategory" "categoryID" .category.ID }}"
>
{{ icon "delete" }}{{ t "action.remove" }}
</button>
</li>
{{ end }}
<li>
<a role="button" href="{{ route "refreshCategoryFeedsPage" "categoryID" .category.ID }}">{{ icon "refresh" }}{{ t "menu.refresh_all_feeds" }}</a>
<form
class="page-header-action-form"
action="{{ route "refreshCategoryFeedsPage" "categoryID" .category.ID }}"
>
<button
class="page-button"
data-label-loading="{{ t "confirm.loading" }}"
>
{{ icon "refresh" }}{{ t "menu.refresh_all_feeds" }}
</button>
</form>
</li>
</ul>
</nav>

View file

@ -10,8 +10,8 @@
<div class="entry-actions">
<ul>
<li>
<a href="#"
role="button"
<button
class="page-button"
title="{{ t "entry.status.title" }}"
data-toggle-status="true"
data-label-loading="{{ t "entry.state.saving" }}"
@ -20,11 +20,11 @@
data-toast-unread="{{ t "entry.status.toast.unread" }}"
data-toast-read="{{ t "entry.status.toast.read" }}"
data-value="{{ if eq .entry.Status "read" }}read{{ else }}unread{{ end }}"
>{{ if eq .entry.Status "unread" }}{{ icon "read" }}{{ else }}{{ icon "unread" }}{{ end }}<span class="icon-label">{{ if eq .entry.Status "unread" }}{{ t "entry.status.read" }}{{ else }}{{ t "entry.status.unread" }}{{ end }}</span></a>
>{{ if eq .entry.Status "unread" }}{{ icon "read" }}{{ else }}{{ icon "unread" }}{{ end }}<span class="icon-label">{{ if eq .entry.Status "unread" }}{{ t "entry.status.read" }}{{ else }}{{ t "entry.status.unread" }}{{ end }}</span></button>
</li>
<li>
<a href="#"
role="button"
<button
class="page-button"
data-toggle-bookmark="true"
data-bookmark-url="{{ route "toggleBookmark" "entryID" .entry.ID }}"
data-label-loading="{{ t "entry.state.saving" }}"
@ -33,19 +33,19 @@
data-toast-star="{{ t "entry.bookmark.toast.on" }}"
data-toast-unstar="{{ t "entry.bookmark.toast.off" }}"
data-value="{{ if .entry.Starred }}star{{ else }}unstar{{ end }}"
>{{ if .entry.Starred }}{{ icon "unstar" }}{{ else }}{{ icon "star" }}{{ end }}<span class="icon-label">{{ if .entry.Starred }}{{ t "entry.bookmark.toggle.off" }}{{ else }}{{ t "entry.bookmark.toggle.on" }}{{ end }}</span></a>
>{{ if .entry.Starred }}{{ icon "unstar" }}{{ else }}{{ icon "star" }}{{ end }}<span class="icon-label">{{ if .entry.Starred }}{{ t "entry.bookmark.toggle.off" }}{{ else }}{{ t "entry.bookmark.toggle.on" }}{{ end }}</span></button>
</li>
{{ if .hasSaveEntry }}
<li>
<a href="#"
role="button"
<button
class="page-button"
title="{{ t "entry.save.title" }}"
data-save-entry="true"
data-save-url="{{ route "saveEntry" "entryID" .entry.ID }}"
data-label-loading="{{ t "entry.state.saving" }}"
data-label-done="{{ t "entry.save.completed" }}"
data-toast-done="{{ t "entry.save.toast.completed" }}"
>{{ icon "save" }}<span class="icon-label">{{ t "entry.save.label" }}</span></a>
>{{ icon "save" }}<span class="icon-label">{{ t "entry.save.label" }}</span></button>
</li>
{{ end }}
{{ if .entry.ShareCode }}
@ -56,18 +56,19 @@
target="_blank">{{ icon "share" }}<span class="icon-label">{{ t "entry.shared_entry.label" }}</span></a>
</li>
<li>
<a href="#"
role="button"
<button
class="page-button"
data-confirm="true"
data-url="{{ route "unshareEntry" "entryID" .entry.ID }}"
data-label-question="{{ t "confirm.question" }}"
data-label-yes="{{ t "confirm.yes" }}"
data-label-no="{{ t "confirm.no" }}"
data-label-loading="{{ t "confirm.loading" }}">{{ icon "delete" }}<span class="icon-label">{{ t "entry.unshare.label" }}</span></a>
data-label-loading="{{ t "confirm.loading" }}">{{ icon "delete" }}<span class="icon-label">{{ t "entry.unshare.label" }}</span></button>
</li>
{{ else }}
<li>
<a href="{{ route "shareEntry" "entryID" .entry.ID }}"
class="page-link"
title="{{ t "entry.share.title" }}"
data-share-status="share"
target="_blank">{{ icon "share" }}<span class="icon-label">{{ t "entry.share.label" }}</span></a>
@ -75,23 +76,25 @@
{{ end }}
<li>
<a href="{{ .entry.URL | safeURL }}"
class="page-link"
target="_blank"
rel="noopener noreferrer"
referrerpolicy="no-referrer"
data-original-link="{{ .user.MarkReadOnView }}">{{ icon "external-link" }}<span class="icon-label">{{ t "entry.external_link.label" }}</span></a>
</li>
<li>
<a href="#"
role="button"
<button
class="page-button"
title="{{ t "entry.scraper.title" }}"
data-fetch-content-entry="true"
data-fetch-content-url="{{ route "fetchContent" "entryID" .entry.ID }}"
data-label-loading="{{ t "entry.state.loading" }}"
>{{ icon "scraper" }}<span class="icon-label">{{ t "entry.scraper.label" }}</span></a>
>{{ icon "scraper" }}<span class="icon-label">{{ t "entry.scraper.label" }}</span></button>
</li>
{{ if .entry.CommentsURL }}
<li>
<a href="{{ .entry.CommentsURL | safeURL }}"
class="page-link"
title="{{ t "entry.comments.title" }}"
target="_blank"
rel="noopener noreferrer"

View file

@ -17,48 +17,52 @@
<ul>
{{ if .entries }}
<li>
<a href="#"
<button
class="page-button"
data-action="markPageAsRead"
data-label-question="{{ t "confirm.question" }}"
data-label-yes="{{ t "confirm.yes" }}"
data-label-no="{{ t "confirm.no" }}"
data-label-loading="{{ t "confirm.loading" }}"
data-show-only-unread="{{ if .showOnlyUnreadEntries }}1{{ end }}">{{ icon "mark-page-as-read" }}{{ t "menu.mark_page_as_read" }}</a>
data-show-only-unread="{{ if .showOnlyUnreadEntries }}1{{ end }}">{{ icon "mark-page-as-read" }}{{ t "menu.mark_page_as_read" }}</button>
</li>
<li>
<a href="#"
<button
class="page-button"
data-confirm="true"
data-label-question="{{ t "confirm.question" }}"
data-label-yes="{{ t "confirm.yes" }}"
data-label-no="{{ t "confirm.no" }}"
data-label-loading="{{ t "confirm.loading" }}"
data-url="{{ route "markFeedAsRead" "feedID" .feed.ID }}">{{ icon "mark-all-as-read" }}{{ t "menu.mark_all_as_read" }}</a>
data-url="{{ route "markFeedAsRead" "feedID" .feed.ID }}">{{ icon "mark-all-as-read" }}{{ t "menu.mark_all_as_read" }}</button>
</li>
{{ end }}
{{ if .showOnlyUnreadEntries }}
<li>
<a href="{{ route "feedEntriesAll" "feedID" .feed.ID }}">{{ icon "show-all-entries" }}{{ t "menu.show_all_entries" }}</a>
<a class="page-link" href="{{ route "feedEntriesAll" "feedID" .feed.ID }}">{{ icon "show-all-entries" }}{{ t "menu.show_all_entries" }}</a>
</li>
{{ else }}
<li>
<a href="{{ route "feedEntries" "feedID" .feed.ID }}">{{ icon "show-unread-entries" }}{{ t "menu.show_only_unread_entries" }}</a>
<a class="page-link" href="{{ route "feedEntries" "feedID" .feed.ID }}">{{ icon "show-unread-entries" }}{{ t "menu.show_only_unread_entries" }}</a>
</li>
{{ end }}
<li>
<a href="#"
<button
class="page-button"
data-confirm="true"
data-label-question="{{ t "confirm.question.refresh" }}"
data-label-yes="{{ t "confirm.yes" }}"
data-label-no="{{ t "confirm.no" }}"
data-label-loading="{{ t "confirm.loading" }}"
data-url="{{ route "refreshFeed" "feedID" .feed.ID }}?forceRefresh=true"
data-no-action-url="{{ route "refreshFeed" "feedID" .feed.ID }}?forceRefresh=false">{{ icon "refresh" }}{{ t "menu.refresh_feed" }}</a>
data-no-action-url="{{ route "refreshFeed" "feedID" .feed.ID }}?forceRefresh=false">{{ icon "refresh" }}{{ t "menu.refresh_feed" }}</button>
</li>
<li>
<a href="{{ route "editFeed" "feedID" .feed.ID }}">{{ icon "edit" }}{{ t "menu.edit_feed" }}</a>
<a class="page-link" href="{{ route "editFeed" "feedID" .feed.ID }}">{{ icon "edit" }}{{ t "menu.edit_feed" }}</a>
</li>
<li>
<a href="#"
<button
class="page-button"
data-confirm="true"
data-action="remove-feed"
data-label-question="{{ t "confirm.question" }}"
@ -66,7 +70,7 @@
data-label-no="{{ t "confirm.no" }}"
data-label-loading="{{ t "confirm.loading" }}"
data-url="{{ route "removeFeed" "feedID" .feed.ID }}"
data-redirect-url="{{ route "feeds" }}">{{ icon "delete" }}{{ t "action.remove_feed" }}</a>
data-redirect-url="{{ route "feeds" }}">{{ icon "delete" }}{{ t "action.remove_feed" }}</button>
</li>
</ul>
</nav>
@ -131,13 +135,14 @@
{{ if .entries }}
<ul>
<li>
<a href="#"
<button
class="page-button"
data-action="markPageAsRead"
data-label-question="{{ t "confirm.question" }}"
data-label-yes="{{ t "confirm.yes" }}"
data-label-no="{{ t "confirm.no" }}"
data-label-loading="{{ t "confirm.loading" }}"
data-show-only-unread="{{ if .showOnlyUnreadEntries }}1{{ end }}">{{ icon "mark-page-as-read" }}{{ t "menu.mark_page_as_read" }}</a>
data-show-only-unread="{{ if .showOnlyUnreadEntries }}1{{ end }}">{{ icon "mark-page-as-read" }}{{ t "menu.mark_page_as_read" }}</button>
</li>
</ul>
{{ end }}

View file

@ -11,18 +11,18 @@
<ul>
{{ if .entries }}
<li>
<a href="#"
role="button"
<button
class="page-button"
data-confirm="true"
data-url="{{ route "flushHistory" }}"
data-label-question="{{ t "confirm.question" }}"
data-label-yes="{{ t "confirm.yes" }}"
data-label-no="{{ t "confirm.no" }}"
data-label-loading="{{ t "confirm.loading" }}">{{ icon "delete" }}{{ t "menu.flush_history" }}</a>
data-label-loading="{{ t "confirm.loading" }}">{{ icon "delete" }}{{ t "menu.flush_history" }}</button>
</li>
{{ end }}
<li>
<a href="{{ route "sharedEntries" }}">{{ icon "share" }}{{ t "menu.shared_entries" }}</a>
<a class="page-link" href="{{ route "sharedEntries" }}">{{ icon "share" }}{{ t "menu.shared_entries" }}</a>
</li>
</ul>
</nav>

View file

@ -43,6 +43,6 @@
{{ end }}
</section>
<footer id="prompt-home-screen">
<a href="#" id="btn-add-to-home-screen" role="button">{{ icon "home" }}<span class="icon-label">{{ t "action.home_screen" }}</span></a>
<button id="btn-add-to-home-screen">{{ icon "home" }}<span class="icon-label">{{ t "action.home_screen" }}</span></button>
</footer>
{{ end }}

View file

@ -11,17 +11,17 @@
<nav aria-label="{{ t "page.shared_entries.title" }} {{ t "menu.title" }}">
<ul>
<li>
<a href="#"
role="button"
<button
class="page-button"
data-confirm="true"
data-url="{{ route "flushHistory" }}"
data-label-question="{{ t "confirm.question" }}"
data-label-yes="{{ t "confirm.yes" }}"
data-label-no="{{ t "confirm.no" }}"
data-label-loading="{{ t "confirm.loading" }}">{{ icon "delete" }}{{ t "menu.flush_history" }}</a>
data-label-loading="{{ t "confirm.loading" }}">{{ icon "delete" }}{{ t "menu.flush_history" }}</button>
</li>
<li>
<a href="{{ route "sharedEntries" }}">{{ icon "share" }}{{ t "menu.shared_entries" }}</a>
<a class="page-link" href="{{ route "sharedEntries" }}">{{ icon "share" }}{{ t "menu.shared_entries" }}</a>
</li>
</ul>
</nav>

View file

@ -11,25 +11,25 @@
<nav aria-label="{{ t "page.unread.title" }} {{ t "menu.title" }}">
<ul>
<li>
<a href="#"
role="button"
<button
class="page-button"
data-action="markPageAsRead"
data-show-only-unread="1"
data-label-question="{{ t "confirm.question" }}"
data-label-yes="{{ t "confirm.yes" }}"
data-label-no="{{ t "confirm.no" }}"
data-label-loading="{{ t "confirm.loading" }}">{{ icon "mark-page-as-read" }}{{ t "menu.mark_page_as_read" }}</a>
data-label-loading="{{ t "confirm.loading" }}">{{ icon "mark-page-as-read" }}{{ t "menu.mark_page_as_read" }}</button>
</li>
<li>
<a href="#"
role="button"
<button
class="page-button"
data-confirm="true"
data-url="{{ route "markAllAsRead" }}"
data-redirect-url="{{ route "unread" }}"
data-label-question="{{ t "confirm.question" }}"
data-label-yes="{{ t "confirm.yes" }}"
data-label-no="{{ t "confirm.no" }}"
data-label-loading="{{ t "confirm.loading" }}">{{ icon "mark-all-as-read" }}{{ t "menu.mark_all_as_read" }}</a>
data-label-loading="{{ t "confirm.loading" }}">{{ icon "mark-all-as-read" }}{{ t "menu.mark_all_as_read" }}</button>
</li>
</ul>
</nav>
@ -75,8 +75,8 @@
{{ if .entries }}
<ul>
<li>
<a href="#"
role="button"
<button
class="page-button"
data-action="markPageAsRead"
data-label-question="{{ t "confirm.question" }}"
data-label-yes="{{ t "confirm.yes" }}"
@ -84,7 +84,7 @@
data-label-loading="{{ t "confirm.loading" }}"
>
{{ icon "mark-page-as-read" }}{{ t "menu.mark_page_as_read" }}
</a>
</button>
</li>
</ul>
{{ end }}

View file

@ -180,6 +180,26 @@ a:hover {
padding-bottom: 2px;
}
.page-header-action-form {
display: inline-flex;
}
:is(.page-button, .page-link) {
color: var(--link-color);
border: none;
background-color: transparent;
font-size: 1rem;
cursor: pointer;
&:is(hover, :focus) {
color: var(--link-hover-color);
}
}
.page-button:active {
translate: 1px 1px;
}
/* Logo */
.logo {
text-align: center;
@ -850,8 +870,7 @@ template {
text-decoration: none;
}
.item-meta a:hover,
.item-meta a:focus {
.item-meta :is(a:is(:focus, :hover), button:is(:focus, :hover)) {
color: #333;
}
@ -881,6 +900,23 @@ template {
margin-right: 0;
}
.item-meta-icons li > :is(a, button) {
color: #777;
text-decoration: none;
font-size: 0.8rem;
border: none;
background-color: transparent;
cursor: pointer;
}
.item-meta-icons a span {
text-decoration: underline;
}
.item-meta-icons button:active {
translate: 1px 1px;
}
.items {
overflow-x: hidden;
touch-action: pan-y;
@ -967,8 +1003,8 @@ article.category-has-unread {
margin-bottom: 20px;
}
.entry-actions a {
text-decoration: none;
.entry-actions a span {
text-decoration: underline;
}
.entry-actions li {
@ -1198,8 +1234,12 @@ details.entry-enclosures {
color: #ed2d04;
}
.confirm a {
.confirm button {
color: #ed2d04;
border: none;
background-color: transparent;
cursor: pointer;
font-size: inherit;
}
.loading {

View file

@ -135,7 +135,7 @@ function markPageAsRead() {
updateEntriesStatus(entryIDs, "read", () => {
// Make sure the Ajax request reach the server before we reload the page.
let element = document.querySelector("a[data-action=markPageAsRead]");
let element = document.querySelector(":is(a, button)[data-action=markPageAsRead]");
let showOnlyUnread = false;
if (element) {
showOnlyUnread = element.dataset.showOnlyUnread || false;
@ -161,7 +161,7 @@ function handleEntryStatus(item, element, setToRead) {
let toasting = !element;
let currentEntry = findEntry(element);
if (currentEntry) {
if (!setToRead || currentEntry.querySelector("a[data-toggle-status]").dataset.value == "unread") {
if (!setToRead || currentEntry.querySelector(":is(a, button)[data-toggle-status]").dataset.value == "unread") {
toggleEntryStatus(currentEntry, toasting);
}
if (isListView() && currentEntry.classList.contains('current-item')) {
@ -180,7 +180,7 @@ function handleEntryStatus(item, element, setToRead) {
// Change the entry status to the opposite value.
function toggleEntryStatus(element, toasting) {
let entryID = parseInt(element.dataset.id, 10);
let link = element.querySelector("a[data-toggle-status]");
let link = element.querySelector(":is(a, button)[data-toggle-status]");
let currentStatus = link.dataset.value;
let newStatus = currentStatus === "read" ? "unread" : "read";
@ -259,7 +259,7 @@ function handleSaveEntry(element) {
let toasting = !element;
let currentEntry = findEntry(element);
if (currentEntry) {
saveEntry(currentEntry.querySelector("a[data-save-entry]"), toasting);
saveEntry(currentEntry.querySelector(":is(a, button)[data-save-entry]"), toasting);
}
}
@ -299,7 +299,7 @@ function handleBookmark(element) {
// Send the Ajax request and change the icon when bookmarking an entry.
function toggleBookmark(parentElement, toasting) {
let element = parentElement.querySelector("a[data-toggle-bookmark]");
let element = parentElement.querySelector(":is(a, button)[data-toggle-bookmark]");
if (!element) {
return;
}
@ -340,7 +340,7 @@ function handleFetchOriginalContent() {
return;
}
let element = document.querySelector("a[data-fetch-content-entry]");
let element = document.querySelector(":is(a, button)[data-fetch-content-entry]");
if (!element) {
return;
}
@ -376,13 +376,13 @@ function openOriginalLink(openLinkInCurrentTab) {
return;
}
let currentItemOriginalLink = document.querySelector(".current-item a[data-original-link]");
let currentItemOriginalLink = document.querySelector(".current-item :is(a, button)[data-original-link]");
if (currentItemOriginalLink !== null) {
DomHelper.openNewTab(currentItemOriginalLink.getAttribute("href"));
let currentItem = document.querySelector(".current-item");
// If we are not on the list of starred items, move to the next item
if (document.location.href != document.querySelector('a[data-page=starred]').href) {
if (document.location.href != document.querySelector(':is(a, button)[data-page=starred]').href) {
goToListItem(1);
}
markEntryAsRead(currentItem);
@ -391,7 +391,7 @@ function openOriginalLink(openLinkInCurrentTab) {
function openCommentLink(openLinkInCurrentTab) {
if (!isListView()) {
let entryLink = document.querySelector("a[data-comments-link]");
let entryLink = document.querySelector(":is(a, button)[data-comments-link]");
if (entryLink !== null) {
if (openLinkInCurrentTab) {
window.location.href = entryLink.getAttribute("href");
@ -401,7 +401,7 @@ function openCommentLink(openLinkInCurrentTab) {
return;
}
} else {
let currentItemCommentsLink = document.querySelector(".current-item a[data-comments-link]");
let currentItemCommentsLink = document.querySelector(".current-item :is(a, button)[data-comments-link]");
if (currentItemCommentsLink !== null) {
DomHelper.openNewTab(currentItemCommentsLink.getAttribute("href"));
}
@ -437,7 +437,7 @@ function unsubscribeFromFeed() {
* @param {boolean} fallbackSelf Refresh actual page if the page is not found.
*/
function goToPage(page, fallbackSelf) {
let element = document.querySelector("a[data-page=" + page + "]");
let element = document.querySelector(":is(a, button)[data-page=" + page + "]");
if (element) {
document.location.href = element.href;
@ -477,7 +477,7 @@ function goToFeed() {
window.location.href = feedAnchor.href;
}
} else {
let currentItemFeed = document.querySelector(".current-item a[data-feed-link]");
let currentItemFeed = document.querySelector(".current-item :is(a, button)[data-feed-link]");
if (currentItemFeed !== null) {
window.location.href = currentItemFeed.getAttribute("href");
}
@ -574,7 +574,7 @@ function findEntry(element) {
}
function handleConfirmationMessage(linkElement, callback) {
if (linkElement.tagName != 'A') {
if (linkElement.tagName != 'A' && linkElement.tagName != "BUTTON") {
linkElement = linkElement.parentNode;
}
@ -592,8 +592,7 @@ function handleConfirmationMessage(linkElement, callback) {
containerElement.appendChild(loadingElement);
}
let yesElement = document.createElement("a");
yesElement.href = "#";
let yesElement = document.createElement("button");
yesElement.appendChild(document.createTextNode(linkElement.dataset.labelYes));
yesElement.onclick = (event) => {
event.preventDefault();
@ -603,8 +602,7 @@ function handleConfirmationMessage(linkElement, callback) {
callback(linkElement.dataset.url, linkElement.dataset.redirectUrl);
};
let noElement = document.createElement("a");
noElement.href = "#";
let noElement = document.createElement("button");
noElement.appendChild(document.createTextNode(linkElement.dataset.labelNo));
noElement.onclick = (event) => {
event.preventDefault();
@ -677,7 +675,7 @@ function handlePlayerProgressionSave(playerElement) {
* handle new share entires and already shared entries
*/
function handleShare() {
let link = document.querySelector('a[data-share-status]');
let link = document.querySelector(':is(a, button)[data-share-status]');
let title = document.querySelector("body > main > section > header > h1 > a");
if (link.dataset.shareStatus === "shared") {
checkShareAPI(title, link.href);

View file

@ -79,14 +79,14 @@ document.addEventListener("DOMContentLoaded", () => {
}
}
onClick("a[data-save-entry]", (event) => handleSaveEntry(event.target));
onClick("a[data-toggle-bookmark]", (event) => handleBookmark(event.target));
onClick("a[data-fetch-content-entry]", () => handleFetchOriginalContent());
onClick("a[data-share-status]", () => handleShare());
onClick("a[data-action=markPageAsRead]", (event) => handleConfirmationMessage(event.target, () => markPageAsRead()));
onClick("a[data-toggle-status]", (event) => handleEntryStatus("next", event.target));
onClick(":is(a, button)[data-save-entry]", (event) => handleSaveEntry(event.target));
onClick(":is(a, button)[data-toggle-bookmark]", (event) => handleBookmark(event.target));
onClick(":is(a, button)[data-fetch-content-entry]", () => handleFetchOriginalContent());
onClick(":is(a, button)[data-share-status]", () => handleShare());
onClick(":is(a, button)[data-action=markPageAsRead]", (event) => handleConfirmationMessage(event.target, () => markPageAsRead()));
onClick(":is(a, button)[data-toggle-status]", (event) => handleEntryStatus("next", event.target));
onClick("a[data-confirm]", (event) => handleConfirmationMessage(event.target, (url, redirectURL) => {
onClick(":is(a, button)[data-confirm]", (event) => handleConfirmationMessage(event.target, (url, redirectURL) => {
let request = new RequestBuilder(url);
request.withCallback((response) => {