A lot of things
- Added Database page. - Added Xenforo API compatibility - Added Hovercard - Added Notifications
This commit is contained in:
@@ -3,6 +3,8 @@ import EasyMDE from "easymde";
|
||||
import "easymde/dist/easymde.min.css";
|
||||
|
||||
import { calculate as calculateHashes } from "./hashes.js";
|
||||
import hovercard from "./hovercard.js";
|
||||
import notifications from "./notifications.js";
|
||||
|
||||
|
||||
// Lucide icons.
|
||||
@@ -19,3 +21,9 @@ window.EasyMDE = EasyMDE;
|
||||
|
||||
// Hashes.
|
||||
window.calculateHashes = calculateHashes;
|
||||
|
||||
// Hover card.
|
||||
Alpine.store('hovercard', hovercard() );
|
||||
|
||||
// Notifications
|
||||
Alpine.store('notifications', notifications() );
|
||||
|
||||
115
resources/js/hovercard.js
Normal file
115
resources/js/hovercard.js
Normal file
@@ -0,0 +1,115 @@
|
||||
/** @typedef { import('types/HovercardResponse.js').HovercardResponse} HovercardResponse */
|
||||
|
||||
export default function hovercard(){
|
||||
return {
|
||||
|
||||
/**
|
||||
* @type {boolean}
|
||||
*/
|
||||
start: false,
|
||||
|
||||
/**
|
||||
* @type {HovercardResponse}
|
||||
*/
|
||||
data: null,
|
||||
|
||||
/**
|
||||
* @type {boolean}
|
||||
*/
|
||||
loading: false,
|
||||
|
||||
/**
|
||||
* @type {any}
|
||||
*/
|
||||
error: false,
|
||||
|
||||
/**
|
||||
* @type {HTMLElement|null}
|
||||
*/
|
||||
anchorEl: null,
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
*/
|
||||
x: 0,
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
*/
|
||||
y: 0,
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {HTMLElement} anchorEl
|
||||
* @param {string} fetchUrl
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
async open(anchorEl, fetchUrl){
|
||||
|
||||
if( this.start && this.anchorEl === anchorEl ){
|
||||
// this.close();
|
||||
return;
|
||||
}
|
||||
|
||||
this.start = true;
|
||||
this.anchorEl = anchorEl;
|
||||
this.data = null;
|
||||
this.loading = true;
|
||||
this.error = false;
|
||||
this.updatePosition(anchorEl);
|
||||
|
||||
try {
|
||||
const RESPONSE = await fetch(fetchUrl);
|
||||
if( !RESPONSE.ok )
|
||||
throw new Error(RESPONSE.status);
|
||||
|
||||
let json = await RESPONSE.json();
|
||||
if( !json.user )
|
||||
throw new Error(RESPONSE.status);
|
||||
|
||||
this.data = json.user;
|
||||
|
||||
Alpine.nextTick(() => {
|
||||
const card = document.querySelector('.hovercard');
|
||||
if (card) window.refreshIcons(card);
|
||||
});
|
||||
|
||||
} catch( error ){
|
||||
this.error = true;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Update Hovercard position
|
||||
* @param {HTMLElement} anchorEl
|
||||
*/
|
||||
updatePosition(anchorEl){
|
||||
const RECT = anchorEl.getBoundingClientRect();
|
||||
const SCROLL_X = window.scrollX;
|
||||
const SCROLL_Y = window.scrollY;
|
||||
|
||||
let x = RECT.left + SCROLL_X;
|
||||
let y = RECT.bottom + SCROLL_Y + 8;
|
||||
|
||||
const WIDTH = 280;
|
||||
if( x + WIDTH > window.innerWidth ){
|
||||
x = window.innerWidth - WIDTH - 16;
|
||||
}
|
||||
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
},
|
||||
|
||||
/**
|
||||
* Close the hovercard.
|
||||
*/
|
||||
close(){
|
||||
this.start = false;
|
||||
this.data = null;
|
||||
this.anchorEl = null;
|
||||
this.error = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
126
resources/js/notifications.js
Normal file
126
resources/js/notifications.js
Normal file
@@ -0,0 +1,126 @@
|
||||
/** @typedef { import('types/AlertsResponseItem.js').AlertsResponseItem} AlertsResponseItem */
|
||||
|
||||
export default function notifications() {
|
||||
return {
|
||||
|
||||
/**
|
||||
* @type {boolean}
|
||||
*/
|
||||
start: false,
|
||||
|
||||
/**
|
||||
* @type {AlertsResponseItem[]}
|
||||
*/
|
||||
data: null,
|
||||
|
||||
/**
|
||||
* @type {boolean}
|
||||
*/
|
||||
loading: false,
|
||||
|
||||
/**
|
||||
* @type {boolean}
|
||||
*/
|
||||
error: false,
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
*/
|
||||
unviewed: 0,
|
||||
|
||||
/**
|
||||
* Request for getting notifications.
|
||||
*
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
async getNotifications() {
|
||||
if( this.loading )
|
||||
return;
|
||||
|
||||
this.loading = true;
|
||||
this.error = false;
|
||||
|
||||
try {
|
||||
const RESPONSE = await fetch('/api/dynamic/notifications', { credentials: "include", headers: { 'X-Requested-With': 'XMLHttpRequest' } });
|
||||
|
||||
if( !RESPONSE.ok )
|
||||
throw new Error(RESPONSE.status)
|
||||
|
||||
let json = await RESPONSE.json()
|
||||
if( !json.alerts )
|
||||
throw new Error(RESPONSE.status);
|
||||
|
||||
this.data = json.alerts;
|
||||
|
||||
|
||||
} catch (error) {
|
||||
this.error = true;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {HTMLElement} anchorEl
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
async open( anchorEl ){
|
||||
|
||||
if( this.start ){
|
||||
this.close();
|
||||
return;
|
||||
}
|
||||
|
||||
this.start = !this.start;
|
||||
if( this.start && !this.data ){
|
||||
await this.getNotifications();
|
||||
}
|
||||
|
||||
if( this.start ){
|
||||
Alpine.nextTick(() => window.refreshIcons(document.querySelector('.notifications')))
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
async markAllRead(){
|
||||
await fetch('/api/dynamic/notifications/mark-all-read', {
|
||||
method: 'POST',
|
||||
credentials: "include",
|
||||
headers: { 'X-Requested-With': 'XMLHttpRequest', 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.content ?? '' }
|
||||
});
|
||||
|
||||
if(this.data && this.data.length > 0){
|
||||
this.data = this.data.map(a => ({
|
||||
...a,
|
||||
view_date: Math.floor(Date.now() / 1000)
|
||||
}));
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @return {number}
|
||||
*/
|
||||
get unread(){
|
||||
if( !this.data ){
|
||||
return this.unviewed;
|
||||
}
|
||||
return this.data.filter(a => a.view_date === 0 ).length;
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
close(){
|
||||
|
||||
if( this.start && this.unread > 0)
|
||||
this.markAllRead();
|
||||
|
||||
this.start = false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
21
resources/js/types/AlertsResponseItem.js
Normal file
21
resources/js/types/AlertsResponseItem.js
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* @typedef {Object} AlertsResponseItem
|
||||
*
|
||||
* @see app/Http/DynamicLoadController.php
|
||||
*
|
||||
* @property {number} alert_id
|
||||
* @property {number} alerted_user_id
|
||||
* @property {number} user_id
|
||||
* @property {string} username
|
||||
* @property {string} content_type
|
||||
* @property {number} content_id
|
||||
* @property {string} action
|
||||
* @property {number} event_date
|
||||
* @property {number} view_date
|
||||
* @property {number} read_date
|
||||
* @property {boolean} auto_read
|
||||
* @property {string} alert_text
|
||||
* @property {string} alert_url
|
||||
* @property {Object} user See XenForo API Documentation for more details.
|
||||
*/
|
||||
export {}
|
||||
18
resources/js/types/HovercardResponse.js
Normal file
18
resources/js/types/HovercardResponse.js
Normal file
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* @typedef {Object} HovercardResponse
|
||||
*
|
||||
* @see app/Http/DynamicLoadController.php
|
||||
*
|
||||
* @property {string} username
|
||||
* @property {string|null} avatar_url
|
||||
* @property {string} avatar_color
|
||||
* @property {string} avatar_letter
|
||||
* @property {string} group_name
|
||||
* @property {string} joined
|
||||
* @property {string} last_seen
|
||||
* @property {number} message_count
|
||||
* @property {number} reaction_score
|
||||
* @property {number} trophy_points
|
||||
* @property {number} entries_count
|
||||
*/
|
||||
export {}
|
||||
Reference in New Issue
Block a user