1
0
mirror of https://github.com/hrfee/jfa-go.git synced 2024-12-28 20:10:11 +00:00

Compare commits

..

No commits in common. "47ce8a9ec4fd0a7408761d44978a93480d90bfcf" and "a0db685af2337a1bdfb24008c3a7817b40afb1fc" have entirely different histories.

9 changed files with 52 additions and 162 deletions

View File

@ -104,7 +104,7 @@ func (app *appContext) GetActivities(gc *gin.Context) {
query = badgerhold.Where("Type").In(activityTypes...)
}
if !req.Ascending {
if req.Ascending {
query = query.Reverse()
}
@ -125,7 +125,6 @@ func (app *appContext) GetActivities(gc *gin.Context) {
resp := GetActivitiesRespDTO{
Activities: make([]ActivityDTO, len(results)),
LastPage: len(results) != req.Limit,
}
for i, act := range results {

View File

@ -3,10 +3,6 @@
color: rgba(0, 0, 0, 0) !important;
}
.loader.rel {
position: relative;
}
.loader .dot {
--diameter: 0.5rem;
--radius: calc(var(--diameter) / 2);
@ -19,12 +15,6 @@
left: calc(50% - var(--radius));
animation: osc 1s cubic-bezier(.72,.16,.31,.97) infinite;
}
.loader.rel .dot {
position: absolute;
top: 50%;
}
.loader.loader-sm .dot {
--deviation: 10%;
}

View File

@ -732,29 +732,34 @@
</div>
</div>
</div>
<button class="button ~neutral @low ml-2" id="activity-sort-direction">{{ .strings.sortDirection }}</button>
<input type="search" class="field ~neutral @low input search ml-2 mr-2" id="activity-search" placeholder="{{ .strings.search }}">
<span class="button ~neutral @low center ml-[-2.64rem] rounded-s-none activity-search-clear" aria-label="{{ .strings.clearSearch }}" text="{{ .strings.clearSearch }}"><i class="ri-close-line"></i></span>
<button class="button ~info @low ml-2" id="activity-refresh" aria-label="{{ .strings.refresh }}" disabled><i class="ri-refresh-line"></i></button>
</div>
<div class="supra py-1 sm hidden" id="activity-search-options-header">{{ .strings.searchOptions }}</div>
<div class="row -mx-2 mb-2">
<button type="button" class="button ~neutral @low center mx-2 hidden"><span id="activity-sort-by-field"></span> <i class="ri-close-line ml-2 text-2xl"></i></button>
<span id="activity-filter-area"></span>
</div>
<div class="my-2">
<div id="activity-card-list"></div>
<div id="activity-loader"></div>
<div class="unfocused h-[100%] my-3" id="activity-not-found">
<div class="flex flex-col h-[100%] justify-center items-center">
<span class="text-2xl font-medium italic mb-3">{{ .strings.noResultsFound }}</span>
<button class="button ~neutral @low activity-search-clear">
<span class="mr-2">{{ .strings.clearSearch }}</span><i class="ri-close-line"></i>
</button>
<div class="flex flex-col md:flex-row gap-3">
<div class="card @low dark:~d_neutral col max-w-[20%]">
<div class="flex-expand">
<input type="search" class="field ~neutral @low input settings-section-button justify-between mb-2" id="settings-search" placeholder="{{ .strings.search }}">
<button class="button ~neutral @low center -ml-10 rounded-s-none mb-2 settings-search-clear" aria-label="{{ .strings.clearSearch }}" text="{{ .strings.clearSearch }}"><i class="ri-close-line"></i></button>
</div>
<aside class="aside sm ~urge dark:~d_info mb-2 @low" id="settings-message">Note: <span class="badge ~critical">*</span> indicates a required field, <span class="badge ~info dark:~d_warning">R</span> indicates changes require a restart.</aside>
<span class="button ~neutral @low settings-section-button justify-between mb-2" id="setting-about"><span class="flex">{{ .strings.aboutProgram }} <i class="ri-information-line ml-2"></i></span></span>
<span class="button ~neutral @low settings-section-button justify-between mb-2" id="setting-profiles"><span class="flex">{{ .strings.userProfiles }} <i class="ri-user-line ml-2"></i></span></span>
</div>
<div class="flex justify-center">
<button class="button my-2 ~neutral @low" id="activity-load-more">{{ .strings.loadMore }}</button>
<div class="card ~neutral @low col">
<div id="activity-card-list"></div>
<div class="unfocused h-[100%] my-3" id="activity-not-found">
<div class="flex flex-col h-[100%] justify-center items-center">
<span class="text-2xl font-medium italic mb-3">{{ .strings.noResultsFound }}</span>
<button class="button ~neutral @low activity-search-clear">
<span class="mr-2">{{ .strings.clearSearch }}</span><i class="ri-close-line"></i>
</button>
</div>
</div>
</div>
</div>
</div>

View File

@ -121,7 +121,6 @@
"accessJFA": "Access jfa-go",
"accessJFASettings": "Cannot be changed as either \"Admin Only\" or \"Allow All\" has been set in Settings > General.",
"sortingBy": "Sorting By",
"sortDirection": "Sort Direction",
"filters": "Filters",
"clickToRemoveFilter": "Click to remove this filter.",
"clearSearch": "Clear search",
@ -157,7 +156,7 @@
"title": "Title",
"usersMentioned": "User mentioned",
"actor": "Actor",
"actorDescription": "The thing that caused this action. \"user\"/\"admin\"/\"daemon\" or a username.",
"actorDescription": "The thing that caused this action. <hr class=\"sep\"> \"user\"/\"admin\"/\"daemon\" or a username.",
"accountCreationFilter": "Account Creation",
"accountDeletionFilter": "Account Deletion",
"accountDisabledFilter": "Account Disabled",
@ -167,9 +166,7 @@
"passwordChangeFilter": "Password Changed",
"passwordResetFilter": "Password Reset",
"inviteCreatedFilter": "Invite Created",
"inviteDeletedFilter": "Invite Deleted/Expired",
"loadMore": "Load More",
"noMoreResults": "No more results."
"inviteDeletedFilter": "Invite Deleted/Expired"
},
"notifications": {
"changedEmailAddress": "Changed email address of {n}.",

View File

@ -453,5 +453,4 @@ type GetActivitiesDTO struct {
type GetActivitiesRespDTO struct {
Activities []ActivityDTO `json:"activities"`
LastPage bool `json:"last_page"`
}

View File

@ -1808,7 +1808,7 @@ export class accountsList {
queries: this._queries,
setVisibility: this.setVisibility,
clearSearchButtonSelector: ".accounts-search-clear",
onSearchCallback: (_0: number, _1: boolean) => {
onSearchCallback: () => {
this._checkCheckCount();
}
};

View File

@ -1,4 +1,4 @@
import { _post, _delete, toDateString, addLoader, removeLoader } from "../modules/common.js";
import { _post, _delete, toDateString } from "../modules/common.js";
import { Search, SearchConfiguration, QueryType, SearchableItem } from "../modules/search.js";
export interface activity {
@ -31,7 +31,7 @@ var activityTypeMoods = {
export var activityReload = new CustomEvent("activity-reload");
export class Activity implements activity, SearchableItem {
export class Activity implements activity, SearchableItem { // FIXME: Add "implements"
private _card: HTMLElement;
private _title: HTMLElement;
private _time: HTMLElement;
@ -173,6 +173,14 @@ export class Activity implements activity, SearchableItem {
this._title.innerHTML = innerHTML.replace("{invite}", this._renderInvText());
}
/*} else if (this.source_type == "admin") {
// FIXME: Handle contactLinked/Unlinked, creation/deletion, enable/disable, createInvite/deleteInvite
} else if (this.source_type == "anon") {
this._referrer.innerHTML = ``;
} else if (this.source_type == "daemon") {
// FIXME: Handle deleteInvite, disabled, deletion
}*/
}
get time(): number { return this._timeUnix; }
@ -281,6 +289,7 @@ export class Activity implements activity, SearchableItem {
}
update = (act: activity) => {
// FIXME
this._act = act;
this.source_type = act.source_type;
this.invite_code = act.invite_code;
@ -303,7 +312,6 @@ export class Activity implements activity, SearchableItem {
interface ActivitiesDTO {
activities: activity[];
last_page: boolean;
}
export class activityList {
@ -315,16 +323,7 @@ export class activityList {
private _sortingByButton = document.getElementById("activity-sort-by-field") as HTMLButtonElement;
private _notFoundPanel = document.getElementById("activity-not-found");
private _searchBox = document.getElementById("activity-search") as HTMLInputElement;
private _sortDirection = document.getElementById("activity-sort-direction") as HTMLButtonElement;
private _loader = document.getElementById("activity-loader");
private _loadMoreButton = document.getElementById("activity-load-more") as HTMLButtonElement;
private _refreshButton = document.getElementById("activity-refresh") as HTMLButtonElement;
private _search: Search;
private _ascending: boolean;
private _hasLoaded: boolean;
private _lastLoad: number;
private _page: number = 0;
private _lastPage: boolean;
setVisibility = (activities: string[], visible: boolean) => {
@ -339,21 +338,12 @@ export class activityList {
}
reload = () => {
this._lastLoad = Date.now();
this._lastPage = false;
// this._page = 0;
let limit = 10;
if (this._page != 0) {
limit *= this._page+1;
};
let send = {
"type": [],
"limit": limit,
"limit": 60,
"page": 0,
"ascending": this.ascending
"ascending": false
}
_post("/activity", send, (req: XMLHttpRequest) => {
if (req.readyState != 4) return;
if (req.status != 200) {
@ -361,82 +351,33 @@ export class activityList {
return;
}
this._hasLoaded = true;
// Allow refreshes every 15s
this._refreshButton.disabled = true;
setTimeout(() => this._refreshButton.disabled = false, 15000);
let resp = req.response as ActivitiesDTO;
// FIXME: Don't destroy everything each reload!
this._activities = {};
this._ordering = [];
for (let act of resp.activities) {
this._activities[act.id] = new Activity(act);
this._ordering.push(act.id);
this._activityList.appendChild(this._activities[act.id].asElement());
}
this._search.items = this._activities;
// FIXME: Actually implement sorting
this._ordering = Object.keys(this._activities);
this._search.ordering = this._ordering;
if (this._search.inSearch) {
this._search.onSearchBoxChange(true);
const results = this._search.search(this._searchBox.value);
this.setVisibility(results, true);
if (results.length == 0) {
this._notFoundPanel.classList.remove("unfocused");
} else {
this._notFoundPanel.classList.add("unfocused");
}
} else {
this.setVisibility(this._ordering, true);
this._notFoundPanel.classList.add("unfocused");
}
}, true);
}
loadMore = () => {
this._lastLoad = Date.now();
this._loadMoreButton.disabled = true;
const timeout = setTimeout(() => this._loadMoreButton.disabled = false, 1000);
this._page += 1;
let send = {
"type": [],
"limit": 10,
"page": this._page,
"ascending": this._ascending
};
// this._activityList.classList.add("unfocused");
// addLoader(this._loader, false, true);
_post("/activity", send, (req: XMLHttpRequest) => {
if (req.readyState != 4) return;
if (req.status != 200) {
window.notifications.customError("loadActivitiesError", window.lang.notif("errorLoadActivities"));
return;
}
let resp = req.response as ActivitiesDTO;
this._lastPage = resp.last_page;
if (this._lastPage) {
clearTimeout(timeout);
this._loadMoreButton.disabled = true;
this._loadMoreButton.textContent = window.lang.strings("noMoreResults");
}
for (let act of resp.activities) {
this._activities[act.id] = new Activity(act);
this._ordering.push(act.id);
}
// this._search.items = this._activities;
// this._search.ordering = this._ordering;
if (this._search.inSearch) {
this._search.onSearchBoxChange(true);
} else {
this.setVisibility(this._ordering, true);
this._notFoundPanel.classList.add("unfocused");
}
// removeLoader(this._loader);
// this._activityList.classList.remove("unfocused");
}, true);
}
private _queries: { [field: string]: QueryType } = {
"id": {
name: window.lang.strings("activityID"),
@ -553,27 +494,6 @@ export class activityList {
}
};
get ascending(): boolean { return this._ascending; }
set ascending(v: boolean) {
this._ascending = v;
this._sortDirection.innerHTML = `${window.lang.strings("sortDirection")} <i class="ri-arrow-${v ? "up" : "down"}-s-line ml-2"></i>`;
if (this._hasLoaded) {
this.reload();
}
}
detectScroll = () => {
// console.log(window.innerHeight + document.documentElement.scrollTop, document.scrollingElement.scrollHeight);
if (Math.abs(window.innerHeight + document.documentElement.scrollTop - document.scrollingElement.scrollHeight) < 50) {
// window.notifications.customSuccess("scroll", "Reached bottom.");
// Wait 1s between loads
if (this._lastLoad + 1000 > Date.now()) return;
this.loadMore();
}
}
private _prevResultCount = 0;
constructor() {
this._activityList = document.getElementById("activity-card-list");
document.addEventListener("activity-reload", this.reload);
@ -588,24 +508,9 @@ export class activityList {
queries: this._queries,
setVisibility: this.setVisibility,
filterList: document.getElementById("activity-filter-list"),
onSearchCallback: (visibleCount: number, newItems: boolean) => {
if (visibleCount < 10) {
if (!newItems || this._prevResultCount != visibleCount || (visibleCount == 0 && !this._lastPage)) this.loadMore();
}
this._prevResultCount = visibleCount;
}
onSearchCallback: () => {}
}
this._search = new Search(conf);
this._search.generateFilterList();
this._hasLoaded = false;
this.ascending = false;
this._sortDirection.addEventListener("click", () => this.ascending = !this.ascending);
this._loadMoreButton.onclick = this.loadMore;
this._refreshButton.onclick = this.reload;
window.onscroll = this.detectScroll;
}
}

View File

@ -199,10 +199,9 @@ export function toggleLoader(el: HTMLElement, small: boolean = true) {
}
}
export function addLoader(el: HTMLElement, small: boolean = true, relative: boolean = false) {
export function addLoader(el: HTMLElement, small: boolean = true) {
if (!el.classList.contains("loader")) {
el.classList.add("loader");
if (relative) el.classList.add("rel");
if (small) { el.classList.add("loader-sm"); }
const dot = document.createElement("span") as HTMLSpanElement;
dot.classList.add("dot")
@ -214,7 +213,6 @@ export function removeLoader(el: HTMLElement, small: boolean = true) {
if (el.classList.contains("loader")) {
el.classList.remove("loader");
el.classList.remove("loader-sm");
el.classList.remove("rel");
const dot = el.querySelector("span.dot");
if (dot) { dot.remove(); }
}

View File

@ -21,8 +21,7 @@ export interface SearchConfiguration {
search: HTMLInputElement;
queries: { [field: string]: QueryType };
setVisibility: (items: string[], visible: boolean) => void;
onSearchCallback: (visibleCount: number, newItems: boolean) => void;
loadMore?: () => void;
onSearchCallback: () => void;
}
export interface SearchableItem {
@ -268,7 +267,7 @@ export class Search {
get ordering(): string[] { return this._ordering; }
set ordering(v: string[]) { this._ordering = v; }
onSearchBoxChange = (newItems: boolean = false) => {
onSearchBoxChange = () => {
const query = this._c.search.value;
if (!query) {
this.inSearch = false;
@ -277,7 +276,7 @@ export class Search {
}
const results = this.search(query);
this._c.setVisibility(results, true);
this._c.onSearchCallback(results.length, newItems);
this._c.onSearchCallback();
this.showHideSearchOptionsHeader();
if (results.length == 0) {
this._c.notFoundPanel.classList.remove("unfocused");
@ -295,8 +294,6 @@ export class Search {
this._c.search.setSelectionRange(newPos, newPos);
this._c.search.oninput(null as any);
};
generateFilterList = () => {
// Generate filter buttons
@ -375,7 +372,7 @@ export class Search {
constructor(c: SearchConfiguration) {
this._c = c;
this._c.search.oninput = () => this.onSearchBoxChange();
this._c.search.oninput = this.onSearchBoxChange;
const clearSearchButtons = Array.from(document.querySelectorAll(this._c.clearSearchButtonSelector)) as Array<HTMLSpanElement>;
for (let b of clearSearchButtons) {