notification mark read

This commit is contained in:
realaravinth 2021-07-16 21:16:49 +05:30
parent 102ef5b4a1
commit 6f690734c5
No known key found for this signature in database
GPG key ID: AD9F0F08E855ED88
14 changed files with 89 additions and 41 deletions

View file

@ -177,6 +177,7 @@ impl Data {
mailer: Self::get_mailer(), mailer: Self::get_mailer(),
}; };
#[cfg(not(debug_assertions))]
init.join().unwrap(); init.join().unwrap();
Arc::new(data) Arc::new(data)

View file

@ -17,48 +17,28 @@
const ROUTES = { const ROUTES = {
registerUser: '/api/v1/signup', registerUser: '/api/v1/signup',
loginUser: '/api/v1/signin', loginUser: '/api/v1/signin',
signoutUser: '/api/v1/signout', signoutUser: '/api/v1/signout',
deleteAccount: '/api/v1/account/delete', deleteAccount: '/api/v1/account/delete',
usernameExists: '/api/v1/account/username/exists', usernameExists: '/api/v1/account/username/exists',
emailExists: '/api/v1/account/email/exists', emailExists: '/api/v1/account/email/exists',
healthCheck: '/api/v1/meta/health', healthCheck: '/api/v1/meta/health',
buildDetails: '/api/v1/meta/build', buildDetails: '/api/v1/meta/build',
addDomain: '/api/v1/mcaptcha/domain/add', addDomain: '/api/v1/mcaptcha/domain/add',
challengeDomain: '/api/v1/mcaptcha/domain/domain/verify/challenge/get', challengeDomain: '/api/v1/mcaptcha/domain/domain/verify/challenge/get',
proveDomain: '/api/v1/mcaptcha/domain/domain/verify/challenge/prove', proveDomain: '/api/v1/mcaptcha/domain/domain/verify/challenge/prove',
deleteDomain: '/api/v1/mcaptcha/domain/delete', deleteDomain: '/api/v1/mcaptcha/domain/delete',
addToken: '/api/v1/mcaptcha/domain/token/add', addToken: '/api/v1/mcaptcha/domain/token/add',
updateTokenKey: '/api/v1/mcaptcha/domain/token/update', updateTokenKey: '/api/v1/mcaptcha/domain/token/update',
getTokenKey: '/api/v1/mcaptcha/domain/token/get', getTokenKey: '/api/v1/mcaptcha/domain/token/get',
deleteToken: '/api/v1/mcaptcha/domain/token/delete', deleteToken: '/api/v1/mcaptcha/domain/token/delete',
addTokenLevels: '/api/v1/mcaptcha/domain/token/levels/add', addTokenLevels: '/api/v1/mcaptcha/domain/token/levels/add',
updateTokenLevels: '/api/v1/mcaptcha/domain/token/levels/update', updateTokenLevels: '/api/v1/mcaptcha/domain/token/levels/update',
deleteTokenLevels: '/api/v1/mcaptcha/domain/token/levels/delete', deleteTokenLevels: '/api/v1/mcaptcha/domain/token/levels/delete',
getTokenLevels: '/api/v1/mcaptcha/domain/token/levels/get', getTokenLevels: '/api/v1/mcaptcha/domain/token/levels/get',
getTokenDuration: '/api/v1/mcaptcha/domain/token/token/get', getTokenDuration: '/api/v1/mcaptcha/domain/token/token/get',
updateTokenDuration: '/api/v1/mcaptcha/domain/token/token/update', updateTokenDuration: '/api/v1/mcaptcha/domain/token/token/update',
markNotificationRead: '/api/v1/notifications/read',
}; };
export default ROUTES; export default ROUTES;

View file

@ -23,6 +23,7 @@ import * as panel from './panel/ts/index';
import * as addSiteKey from './panel/sitekey/add/ts'; import * as addSiteKey from './panel/sitekey/add/ts';
import * as editSitekey from './panel/sitekey/edit/'; import * as editSitekey from './panel/sitekey/edit/';
import * as listSitekeys from './panel/sitekey/list/ts'; import * as listSitekeys from './panel/sitekey/list/ts';
import * as notidications from './panel/notifications/ts';
import {MODE} from './logger'; import {MODE} from './logger';
import log from './logger'; import log from './logger';
@ -50,9 +51,10 @@ const router = new Router();
router.register(VIEWS.panelHome, panel.index); router.register(VIEWS.panelHome, panel.index);
router.register(VIEWS.registerUser, register.index); router.register(VIEWS.registerUser, register.index);
router.register(VIEWS.loginUser, login.index); router.register(VIEWS.loginUser, login.index);
router.register(VIEWS.notifications, notidications.index);
router.register(VIEWS.listSitekey, listSitekeys.index); router.register(VIEWS.listSitekey, listSitekeys.index);
router.register(VIEWS.addSiteKey, addSiteKey.index); router.register(VIEWS.addSiteKey, addSiteKey.index);
router.register(VIEWS.editSitekey("[A-Z,a-z,0-9]+"), editSitekey.index); router.register(VIEWS.editSitekey('[A-Z),a-z,0-9]+'), editSitekey.index);
try { try {
router.route(); router.route();

View file

@ -53,6 +53,7 @@ include!("./navbar/index.html"); .>
</td> </td>
<td class="sitekey-list__key"> <td class="sitekey-list__key">
<div class="sitekey-list__edit"> <div class="sitekey-list__edit">
<. let key = format!("/sitekey/{}", &sitekey.key); .>
<. include!("./sitekey/view/__edit-sitekey-icon.html"); .> <. include!("./sitekey/view/__edit-sitekey-icon.html"); .>
</div> </div>
</td> </td>

View file

@ -14,7 +14,7 @@ include!("../navbar/index.html"); .>
</thead> </thead>
<tbody class="notification__body"> <tbody class="notification__body">
<. for notification in n.iter() { .> <. for notification in n.iter() { .>
<tr class="notification__item"> <tr class="notification__item" id="notification__item-<.= notification.id .>">
<td> <td>
<h3 class="notification__item-heading"> <h3 class="notification__item-heading">
<.= notification.heading .> <.= notification.heading .>
@ -34,6 +34,7 @@ include!("../navbar/index.html"); .>
src="<.= crate::FILES src="<.= crate::FILES
.get("./static/cache/img/svg/check.svg") .get("./static/cache/img/svg/check.svg")
.unwrap() .>" .unwrap() .>"
data-id="<.= notification.id .>"
alt="Mark Read" alt="Mark Read"
/> />
</button> </button>

View file

@ -35,7 +35,7 @@
} }
.notification__mark-read-btn:hover { .notification__mark-read-btn:hover {
cursor: grab; cursor: pointer;
background-color: $light-grey; background-color: $light-grey;
} }

View file

@ -0,0 +1,55 @@
/*
* Copyright (C) 2021 Aravinth Manivannan <realaravinth@batsense.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import genJsonPayload from '../../../utils/genJsonPayload';
import createError from '../../../components/error';
import ROUTES from '../../../api/v1/routes';
const BTN = document.querySelectorAll('.notification__mark-read-btn');
const TABLE_BODY = document.querySelector('.notification__body');
const notification_record = (id: number) =>
<HTMLElement>TABLE_BODY.querySelector(`#notification__item-${id}`);
const markRead = async (e: Event) => {
const element = <HTMLElement>e.target;
const id = Number.parseInt(element.dataset.id);
const payload = {
id,
};
const res = await fetch(ROUTES.markNotificationRead, genJsonPayload(payload));
if (res.ok) {
notification_record(id).remove();
} else {
const err = await res.json();
createError(err.error);
}
};
const addMarkReadEventListenet = () => {
BTN.forEach(btn => {
btn.addEventListener('click', markRead, true);
});
};
export const index = () => {
addMarkReadEventListenet();
};

View file

@ -30,8 +30,7 @@ import createError from '../../../components/error';
import VIEWS from '../../../views/v1/routes'; import VIEWS from '../../../views/v1/routes';
const BTN = <HTMLElement>document.querySelector('.sitekey-form__submit'); const BTN_CLASS = document.querySelector('sitekey-form__submit');
const key = BTN.dataset.sitekey;
const submit = async (e: Event) => { const submit = async (e: Event) => {
e.preventDefault(); e.preventDefault();
@ -44,6 +43,9 @@ const submit = async (e: Event) => {
const levels = LEVELS.getLevels(); const levels = LEVELS.getLevels();
console.debug(`[form submition]: levels: ${levels}`); console.debug(`[form submition]: levels: ${levels}`);
const btn = <HTMLElement>document.querySelector(`${BTN_CLASS}`);
const key = btn.dataset.sitekey;
const payload = { const payload = {
levels, levels,
duration, duration,

View file

@ -44,6 +44,7 @@ include!("../../navbar/index.html"); .>
</td> </td>
<td class="sitekey-list__key"> <td class="sitekey-list__key">
<div class="sitekey-list__edit"> <div class="sitekey-list__edit">
<. let key = format!("/sitekey/{}", &sitekey.key); .>
<. include!("../view/__edit-sitekey-icon.html"); .> <. include!("../view/__edit-sitekey-icon.html"); .>
</div> </div>
</td> </td>

View file

@ -1,4 +1,4 @@
<a href="./edit/"> <a href="<.= key .>/edit/">
<img class="sitekey-form__edit" src="<.= <img class="sitekey-form__edit" src="<.=
crate::FILES.get("./static/cache/img/svg/edit.svg").unwrap() .>" alt="Edit crate::FILES.get("./static/cache/img/svg/edit.svg").unwrap() .>" alt="Edit
sitekey" /> sitekey" />

View file

@ -20,6 +20,7 @@
/> />
</a> </a>
<. if READONLY { .> <. if READONLY { .>
<. let key = "."; .>
<. include!("./__edit-sitekey-icon.html"); .> <. include!("./__edit-sitekey-icon.html"); .>
<. } .> <. } .>
</h1> </h1>

View file

@ -27,7 +27,7 @@ const panelResult = 'hello from panel';
const panelRoute = '/panel'; const panelRoute = '/panel';
const panel = () => (result.result = panelResult); const panel = () => (result.result = panelResult);
const settingsRoute = '/settings/'; const settingsRoute = '/sitekey/';
const settingsResult = 'hello from settings'; const settingsResult = 'hello from settings';
const settings = () => (result.result = settingsResult); const settings = () => (result.result = settingsResult);
@ -41,9 +41,9 @@ const emptyUriErr = 'uri is empty';
const unregisteredRouteErr = "Route isn't registered"; const unregisteredRouteErr = "Route isn't registered";
const router = new Router(); const router = new Router();
router.register(patternRoute, pattern);
router.register(panelRoute, panel); router.register(panelRoute, panel);
router.register(settingsRoute, settings); router.register(settingsRoute, settings);
router.register(patternRoute, pattern);
it('checks if Router works', () => { it('checks if Router works', () => {
window.history.pushState({}, '', examplePatternRoute); window.history.pushState({}, '', examplePatternRoute);

View file

@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
/** Removes trailing slashed from URI */ /** Removes trailing slash from URI */
const normalizeUri = (uri: string) => { const normalizeUri = (uri: string) => {
uri = uri.trim(); uri = uri.trim();
if (uri.length == 0) { if (uri.length == 0) {
@ -54,7 +54,7 @@ export class Router {
register(uri: string, fn: () => void) { register(uri: string, fn: () => void) {
uri = normalizeUri(uri); uri = normalizeUri(uri);
let pattern = new RegExp(`^${uri}(.*)`); let pattern = new RegExp(`^${uri}$`);
let patterString = pattern.toString(); let patterString = pattern.toString();
if ( if (
@ -83,18 +83,21 @@ export class Router {
route() { route() {
const path = normalizeUri(window.location.pathname); const path = normalizeUri(window.location.pathname);
let fn: () => void | undefined; let fn: undefined | (() => void);
this.routes.forEach(route => { if (
if (path.match(route.pattern)) { this.routes.find(route => {
fn = route.fn; if (path.match(route.pattern)) {
fn = route.fn;
return true;
}
})
) {
if (fn === undefined) {
throw new Error("Route isn't registered");
} else {
return fn();
} }
});
if (fn === undefined) {
throw new Error("Route isn't registered");
} }
return fn();
} }
} }

View file

@ -21,6 +21,7 @@ const ROUTES = {
signoutUser: '/api/v1/signout', signoutUser: '/api/v1/signout',
panelHome: '/', panelHome: '/',
docsHome: '/docs/', docsHome: '/docs/',
notifications: '/notifications',
listSitekey: '/sitekeys/', listSitekey: '/sitekeys/',
viewSitekey: (key: string) => `/sitekey/${key}/`, viewSitekey: (key: string) => `/sitekey/${key}/`,
editSitekey: (key: string) => `/sitekey/${key}/edit/`, editSitekey: (key: string) => `/sitekey/${key}/edit/`,