Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 54 additions & 30 deletions assets/js/boorujs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* Apply event-based actions through data-* attributes. The attributes are structured like so: [data-event-action]
*/

import { assertType } from './utils/assert';
import { assertNotUndefined, assertType } from './utils/assert';
import { $, $$ } from './utils/dom';
import { fetchHtml, handleError } from './utils/requests';
import { showBlock } from './utils/image';
Expand All @@ -23,7 +23,6 @@ declare global {
type EventType = 'click' | 'change' | 'fetchcomplete';

interface ActionData {
attr: string;
el: HTMLElement;
value: string;
base?: ParentNode;
Expand Down Expand Up @@ -142,35 +141,44 @@ const actions: Record<string, Action> = {
}
},

tab(data) {
const block = data.el.parentNode?.parentNode;
if (!(block instanceof HTMLElement)) return;
tab: switchTab,
};

const newTab = $<HTMLElement>(`.block__tab[data-tab="${data.value}"]`, block);
const loadTab = data.el.dataset.loadTab;
function switchTab(data: ActionData & { noPushState?: boolean }) {
const block = data.el.parentNode?.parentNode;
if (!(block instanceof HTMLElement)) return;

// Switch tab
const selectedTab = $<HTMLElement>('.selected', block);
if (selectedTab) {
selectedTab.classList.remove('selected');
}
data.el.classList.add('selected');

// Switch contents
actions.tabHide({ ...data, base: block, value: '.block__tab' });
actions.show({ ...data, base: block, value: `.block__tab[data-tab="${data.value}"]` });

// If the tab has a 'data-load-tab' attribute, load and insert the content
if (loadTab && newTab && !newTab.dataset.loaded) {
fetchHtml(loadTab)
.then(handleError)
.then(response => response.text())
.then(response => (newTab.innerHTML = response))
.then(() => (newTab.dataset.loaded = 'true'))
.catch(() => (newTab.textContent = 'Error!'));
}
},
};
const newTab = $<HTMLElement>(`.block__tab[data-tab="${data.value}"]`, block);
const loadTab = data.el.dataset.loadTab;

// Switch tab
const selectedTab = $<HTMLElement>('.selected', block);
if (selectedTab) {
selectedTab.classList.remove('selected');
}
data.el.classList.add('selected');

// Switch contents
actions.tabHide({ ...data, base: block, value: '.block__tab' });
actions.show({ ...data, base: block, value: `.block__tab[data-tab="${data.value}"]` });

if (!data.noPushState && block.dataset.tabPersistInQueryParams === 'true') {
// Save navigation state in the URL query params
const url = new URL(window.location.href);
url.searchParams.set('tab', data.value);
window.history.pushState({}, '', url);
}

// If the tab has a 'data-load-tab' attribute, load and insert the content
if (loadTab && newTab && !newTab.dataset.loaded) {
fetchHtml(loadTab)
.then(handleError)
.then(response => response.text())
.then(response => (newTab.innerHTML = response))
.then(() => (newTab.dataset.loaded = 'true'))
.catch(() => (newTab.textContent = 'Error!'));
}
}

// Use this function to apply a callback to elements matching the selectors
function selectorCb(base: ParentNode = document, selector: string, cb: (el: Element) => void) {
Expand All @@ -197,7 +205,7 @@ function matchAttributes(event: Event) {
const value = el?.getAttribute(attr) || '';

if (el && value) {
actions[action]({ attr, el, value });
actions[action]({ el, value });
event.preventDefault();
}
}
Expand All @@ -208,4 +216,20 @@ export function registerEvents() {
for (const type in types) {
document.addEventListener(type, matchAttributes);
}

window.addEventListener('popstate', () => {
const url = new URL(window.location.href);
const tab = url.searchParams.get('tab');
const tabs = $$<HTMLElement>(`[data-click-tab]`);
const explicitTab = tab && tabs.find(btn => btn.dataset.clickTab === tab);
const tabBtn = explicitTab || tabs.find(btn => btn.dataset.hasOwnProperty('tabDefault'));
if (tabBtn) {
switchTab({
el: tabBtn,
value: assertNotUndefined(tabBtn.dataset.clickTab),
// We don't need to push a new state on back/forward navigation, only switch the tab
noPushState: true,
});
}
});
}
32 changes: 16 additions & 16 deletions lib/philomena_web/templates/setting/edit.html.slime
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,21 @@ h1 Content Settings
.alert.alert-danger
p Oops, something went wrong! Please check the errors below.

#js-setting-table.block
#js-setting-table.block data-tab-persist-in-query-params="true"
.block__header.block__header--js-tabbed
= if @conn.assigns.current_user do
= link "Watch List", to: "#", class: "selected", data: [click_tab: "watched"]
= link "Display", to: "#", data: [click_tab: "display"]
= link "Comments", to: "#", data: [click_tab: "comments"]
= link "Notifications", to: "#", data: [click_tab: "notifications"]
= link "Metadata", to: "#", data: [click_tab: "metadata"]
= link "Local", to: "#", data: [click_tab: "local"]
= tab_link(@conn, "Watch List", "watched", default: true)
= tab_link(@conn, "Display", "display")
= tab_link(@conn, "Comments", "comments")
= tab_link(@conn, "Notifications", "notifications")
= tab_link(@conn, "Metadata", "metadata")
= tab_link(@conn, "Local", "local")
- else
= link "Local", to: "#", class: "selected", data: [click_tab: "local"]
= link "More settings", to: "#", data: [click_tab: "join-the-herd"]
= tab_link(@conn, "Local", "local", default: true)
= tab_link(@conn, "More settings", "join-the-herd")

= if @conn.assigns.current_user do
.block__tab data-tab="watched"
.block__tab class=tab_class(@conn, "watched", default: true) data-tab="watched"
h4 Tags
.field
= label f, :watched_tag_list, "Tags to watch"
Expand Down Expand Up @@ -55,7 +55,7 @@ h1 Content Settings
br
' Do not share this URL with anyone, it may allow an attacker to compromise your account.

.block__tab.hidden data-tab="display"
.block__tab class=tab_class(@conn, "display") data-tab="display"
= field_with_help( \
"Align content to the center of the page - try this option out if you " <> \
"browse the site on a tablet or a fairly wide screen.",
Expand Down Expand Up @@ -134,7 +134,7 @@ h1 Content Settings
] \
)

.block__tab.hidden data-tab="comments"
.block__tab class=tab_class(@conn, "comments") data-tab="comments"
= field_with_help( \
"Display the newest comments at the top of the page.", \
[ \
Expand Down Expand Up @@ -174,7 +174,7 @@ h1 Content Settings
] \
)

.block__tab.hidden data-tab="notifications"
.block__tab class=tab_class(@conn, "notifications") data-tab="notifications"
= field_with_help( \
"If enabled, you'll be subscribed to things (images or topics) " <> \
"automatically as soon as you post a comment or reply, keeping " <> \
Expand Down Expand Up @@ -203,7 +203,7 @@ h1 Content Settings
] \
)

.block__tab.hidden data-tab="metadata"
.block__tab class=tab_class(@conn, "metadata") data-tab="metadata"
.field
=> checkbox f, :fancy_tag_field_on_upload, class: "checkbox"
=> label f, :fancy_tag_field_on_upload, "Fancy tags - uploads"
Expand All @@ -229,7 +229,7 @@ h1 Content Settings
] \
)

.block__tab class=local_tab_class(@conn) data-tab="local"
.block__tab class=tab_class(@conn, "local", default: is_nil(@conn.assigns.current_user)) data-tab="local"
.block.block--fixed.block--warning Settings on this tab are saved in the current browser. They are independent of your login.
= field_with_help( \
"Use high quality thumbnails on displays with a high pixel density. " <> \
Expand Down Expand Up @@ -347,7 +347,7 @@ h1 Content Settings
)

= if !@conn.assigns.current_user do
.block__tab.hidden data-tab="join-the-herd"
.block__tab class=tab_class(@conn, "join-the-herd") data-tab="join-the-herd"
p
' Consider
=> link "creating an account!", to: ~p"/registrations/new"
Expand Down
33 changes: 26 additions & 7 deletions lib/philomena_web/views/setting_view.ex
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,35 @@ defmodule PhilomenaWeb.SettingView do
]
end

def local_tab_class(conn) do
case conn.assigns.current_user do
nil -> ""
_user -> "hidden"
end
end

def staff?(%{role: role}), do: role != "user"
def staff?(_), do: false

def tab_class(conn, tab_id, opts \\ []) do
if is_active_tab(conn, tab_id, opts), do: "", else: "hidden"
end

def tab_link(conn, display_name, tab_id, opts \\ []) do
default = Keyword.get(opts, :default, false)
class = if is_active_tab(conn, tab_id, opts), do: "selected", else: ""

link(display_name,
to: "?tab=#{tab_id}",
data: [click_tab: tab_id, tab_default: default],
class: class
)
end

defp is_active_tab(conn, tab_id, opts) do
default = Keyword.get(opts, :default, false)
tab = conn.params["tab"]

if is_nil(tab) do
default
else
tab == tab_id
end
end

def field_with_help(title, children) do
content =
children
Expand Down
Loading