settings page, clipboard component

This commit is contained in:
realaravinth 2021-07-20 18:14:23 +05:30
parent db941d51b7
commit 4b18992f6a
No known key found for this signature in database
GPG key ID: AD9F0F08E855ED88
23 changed files with 375 additions and 99 deletions

2
Cargo.lock generated
View file

@ -1457,7 +1457,7 @@ checksum = "320cfe77175da3a483efed4bc0adc1968ca050b098ce4f2f1c13a56626128790"
[[package]] [[package]]
name = "libmcaptcha" name = "libmcaptcha"
version = "0.1.4" version = "0.1.4"
source = "git+https://github.com/mCaptcha/libmcaptcha?branch=master#6eac51de1035c20954752642b4fbdbf6ae3bec6d" source = "git+https://github.com/mCaptcha/libmcaptcha?branch=master#1d4f97f511ad851a58359ecd1737b381b61cf22e"
dependencies = [ dependencies = [
"actix", "actix",
"derive_builder", "derive_builder",

View file

@ -14,6 +14,32 @@
"nullable": [] "nullable": []
} }
}, },
"06699fda6b1542bf4544c0bdece91531a3020c24c9c76bcf967980e71ee25b42": {
"query": "SELECT email, secret FROM mcaptcha_users WHERE name = ($1)",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "email",
"type_info": "Varchar"
},
{
"ordinal": 1,
"name": "secret",
"type_info": "Varchar"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
true,
false
]
}
},
"1be6274d5cc6d16f38285b8a62c9f66e8c3014cd403bc599598e911023bfeedb": { "1be6274d5cc6d16f38285b8a62c9f66e8c3014cd403bc599598e911023bfeedb": {
"query": "INSERT INTO mcaptcha_pow_fetched_stats \n (config_id) VALUES ((SELECT config_id FROM mcaptcha_config WHERE key = $1))", "query": "INSERT INTO mcaptcha_pow_fetched_stats \n (config_id) VALUES ((SELECT config_id FROM mcaptcha_config WHERE key = $1))",
"describe": { "describe": {

View file

@ -30,7 +30,6 @@ pub mod routes {
pub struct Levels { pub struct Levels {
pub add: &'static str, pub add: &'static str,
pub delete: &'static str,
pub get: &'static str, pub get: &'static str,
pub update: &'static str, pub update: &'static str,
} }
@ -39,14 +38,8 @@ pub mod routes {
pub const fn new() -> Levels { pub const fn new() -> Levels {
let add = "/api/v1/mcaptcha/add"; let add = "/api/v1/mcaptcha/add";
let update = "/api/v1/mcaptcha/update"; let update = "/api/v1/mcaptcha/update";
let delete = "/api/v1/mcaptcha/delete";
let get = "/api/v1/mcaptcha/get"; let get = "/api/v1/mcaptcha/get";
Levels { Levels { add, get, update }
add,
delete,
get,
update,
}
} }
} }
} }

View file

@ -11,8 +11,7 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details. * GNU Affero General Public License for more details.
* *
* You should have received a copy of the GNU Affero General Public License * 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/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
use actix_web::web::ServiceConfig; use actix_web::web::ServiceConfig;
@ -51,17 +50,22 @@ mod tests {
delete_user(NAME, &data).await; delete_user(NAME, &data).await;
} }
let (data, _, signin_resp) = register_and_signin(NAME, EMAIL, PASSWORD).await; register_and_signin(NAME, EMAIL, PASSWORD).await;
let (data, _, signin_resp, token_key) = add_levels_util(NAME, PASSWORD).await;
let cookies = get_cookie!(signin_resp); let cookies = get_cookie!(signin_resp);
let app = get_app!(data).await; let app = get_app!(data).await;
let edit_sitekey_url = format!("/sitekey/{}/edit", &token_key.key);
let delete_sitekey_url = format!("/sitekey/{}/delete", &token_key.key);
let urls = vec![ let urls = vec![
PAGES.home, PAGES.home,
PAGES.panel.sitekey.add, PAGES.panel.sitekey.add,
PAGES.panel.sitekey.list, PAGES.panel.sitekey.list,
PAGES.panel.notifications, PAGES.panel.notifications,
"/sitekey/test/delete", PAGES.panel.settings,
&delete_sitekey_url,
&edit_sitekey_url,
]; ];
for url in urls.iter() { for url in urls.iter() {

View file

@ -1,25 +1,26 @@
/* /*
* Copyright (C) 2021 Aravinth Manivannan <realaravinth@batsense.net> * Copyright (C) 2021 Aravinth Manivannan <realaravinth@batsense.net>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as * it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the * published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version. * License, or (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details. * GNU Affero General Public License for more details.
* *
* You should have received a copy of the GNU Affero General Public License * 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/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
use actix_identity::Identity; use actix_identity::Identity;
use actix_web::{HttpResponse, Responder}; use actix_web::{HttpResponse, Responder};
use sailfish::TemplateOnce; use sailfish::TemplateOnce;
mod notifications; mod notifications;
mod settings;
pub mod sitekey; pub mod sitekey;
use crate::errors::PageResult; use crate::errors::PageResult;
@ -51,6 +52,7 @@ async fn panel(data: AppData, id: Identity) -> PageResult<impl Responder> {
pub fn services(cfg: &mut actix_web::web::ServiceConfig) { pub fn services(cfg: &mut actix_web::web::ServiceConfig) {
cfg.service(panel); cfg.service(panel);
cfg.service(settings::settings);
sitekey::services(cfg); sitekey::services(cfg);
cfg.service(notifications::notifications); cfg.service(notifications::notifications);
} }
@ -61,6 +63,7 @@ pub mod routes {
pub home: &'static str, pub home: &'static str,
pub sitekey: Sitekey, pub sitekey: Sitekey,
pub notifications: &'static str, pub notifications: &'static str,
pub settings: &'static str,
} }
impl Panel { impl Panel {
@ -69,6 +72,7 @@ pub mod routes {
home: "/", home: "/",
sitekey: Sitekey::new(), sitekey: Sitekey::new(),
notifications: "/notifications", notifications: "/notifications",
settings: "/settings",
} }
} }
} }

View file

@ -0,0 +1,49 @@
/*
* 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/>.
*/
use actix_identity::Identity;
use actix_web::{HttpResponse, Responder};
use sailfish::TemplateOnce;
use crate::errors::PageResult;
use crate::AppData;
#[derive(TemplateOnce, Clone)]
#[template(path = "panel/settings/index.html")]
pub struct IndexPage {
email: Option<String>,
secret: String,
}
const PAGE: &str = "Settings";
#[my_codegen::get(path = "crate::PAGES.panel.settings", wrap = "crate::CheckLogin")]
pub async fn settings(data: AppData, id: Identity) -> PageResult<impl Responder> {
let username = id.identity().unwrap();
let details = sqlx::query_as!(
IndexPage,
r#"SELECT email, secret FROM mcaptcha_users WHERE name = ($1)"#,
&username,
)
.fetch_one(&data.db)
.await?;
let body = details.render_once().unwrap();
Ok(HttpResponse::Ok()
.content_type("text/html; charset=utf-8")
.body(body))
}

1
static/cache/img/svg/refresh.svg vendored Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-refresh-ccw"><polyline points="1 4 1 10 7 10"></polyline><polyline points="23 20 23 14 17 14"></polyline><path d="M20.49 9A9 9 0 0 0 5.64 5.64L1 10m22 4l-4.64 4.36A9 9 0 0 1 3.51 15"></path></svg>

After

Width:  |  Height:  |  Size: 400 B

View file

@ -34,7 +34,7 @@
id="password" id="password"
required required
/> />
<. include!("../../components/showPassword/index.html"); .> <. include!("../../components/showPassword/index.html"); .>
</label> </label>
<input type="submit" class="sitekey-form__submit" value="Sign in" /> <input type="submit" class="sitekey-form__submit" value="Sign in" />

View file

@ -0,0 +1,38 @@
/*
* 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/>.
*/
@mixin copy-icon-base {
margin: auto;
padding: 5px;
}
@mixin copy-icon {
@include copy-icon-base;
}
@mixin copy-icon-hover {
cursor: pointer;
filter: invert(17%) sepia(93%) saturate(5039%) hue-rotate(204deg)
brightness(100%) contrast(98%);
}
@mixin copy-done-icon {
@include copy-icon-base;
display: none;
filter: invert(58%) sepia(60%) saturate(331%) hue-rotate(76deg)
brightness(91%) contrast(92%);
}

View file

@ -0,0 +1,10 @@
<img class="<.= COPY_CLASS .>"
src="<.= crate::FILES.get("./static/cache/img/svg/clipboard.svg").unwrap() .>"
alt="<.= COPY_ALT .>"
data-<.= clipboard_data.0 .>="<.= clipboard_data.1 .>"
/>
<img
class="<.= DONE_CLASS .>"
src="<.= crate::FILES.get("./static/cache/img/svg/check.svg").unwrap() .>"
alt="<.= DONE_ALT .>"
/>

View file

@ -0,0 +1,65 @@
/*
* 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/>.
*/
class CopyIcon {
copyIconClass: string;
copyDoneIconClass: string;
writeText: string;
constructor(
writeText: string,
copyIconClass: string,
copyDoneIconClass: string,
) {
this.copyIconClass = copyIconClass;
this.copyDoneIconClass = copyDoneIconClass;
this.writeText = writeText;
this.__registerHandlers();
}
__registerHandlers() {
const icons = document.querySelectorAll(`.${this.copyIconClass}`);
icons.forEach(icon => {
icon.addEventListener('click', e => this.copySitekey(e));
});
}
/*
* Copy secret to clipboard
*/
async copySitekey(e: Event) {
const image = <HTMLElement>e.target;
if (!image.classList.contains(this.copyIconClass)) {
throw new Error(
'This method should only be called when sitekey copy button/icon is clicked',
);
}
const copyDoneIcon = <HTMLElement>(
image.parentElement.querySelector(`.${this.copyDoneIconClass}`)
);
await navigator.clipboard.writeText(this.writeText);
image.style.display = 'none';
copyDoneIcon.style.display = 'block';
setTimeout(() => {
copyDoneIcon.style.display = 'none';
image.style.display = 'block';
}, 1200);
}
}
export default CopyIcon;

View file

@ -10,7 +10,7 @@
<link <link
rel="stylesheet" rel="stylesheet"
media="screen and (max-width: 961px)" media="screen and (max-width: 1200px)"
type="text/css" type="text/css"
href="<.= &*crate::MOBILE_CSS .>" href="<.= &*crate::MOBILE_CSS .>"
/> />

View file

@ -50,8 +50,7 @@ const hidePasswordButtons = () => {
// e is click event from show password container // e is click event from show password container
export const showPassword = () => { export const showPassword = () => {
const form = document.querySelector('form'); const inputs = document.body.querySelectorAll('input');
const inputs = form.querySelectorAll('input');
if (display == 'hidden') { if (display == 'hidden') {
display = 'show'; display = 'show';
@ -93,13 +92,3 @@ export const registerShowPassword = () => {
}; };
export default registerShowPassword; export default registerShowPassword;
/*
* so here's what im going to do:
* the wrapper will be a check box and the check box will manipulate
* show password and hide password buttons using CSS.
*
* There will also be an event hadler attached that will change field types of
* parent fields only. Well, sibling maybe. Will have to see document structure
*
*/

View file

@ -20,6 +20,7 @@ import {Router} from './router';
import * as login from './auth/login/ts/'; import * as login from './auth/login/ts/';
import * as register from './auth/register/ts/'; import * as register from './auth/register/ts/';
import * as panel from './panel/ts/index'; import * as panel from './panel/ts/index';
import settings from './panel/settings/';
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 deleteSitekey from './panel/sitekey/delete/'; import * as deleteSitekey from './panel/sitekey/delete/';
@ -37,6 +38,7 @@ import './components/error/main.scss';
import './components/showPassword/main.scss'; import './components/showPassword/main.scss';
import './panel/css/main.scss'; import './panel/css/main.scss';
import './panel/navbar/main.scss'; import './panel/navbar/main.scss';
import './panel/settings/main.scss';
import './panel/notifications/main.scss'; import './panel/notifications/main.scss';
import './panel/header/taskbar/main.scss'; import './panel/header/taskbar/main.scss';
import './panel/help-banner/main.scss'; import './panel/help-banner/main.scss';
@ -50,6 +52,7 @@ log.setMode(MODE.production);
const router = new Router(); const router = new Router();
router.register(VIEWS.panelHome, panel.index); router.register(VIEWS.panelHome, panel.index);
router.register(VIEWS.settings, settings);
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.notifications, notidications.index);

View file

@ -1,3 +1,8 @@
<. const DONE_ALT: &str = "sitekey copied"; .>
<. const DONE_CLASS: &str = "sitekey__copy-done-icon"; .>
<. const COPY_ALT: &str = "copy sitekey"; .>
<. const COPY_CLASS: &str = "sitekey__copy-icon"; .>
<. include!("../components/headers/index.html"); .> <. <. include!("../components/headers/index.html"); .> <.
include!("./navbar/index.html"); .> include!("./navbar/index.html"); .>
<div class="tmp-layout"> <div class="tmp-layout">
@ -37,12 +42,8 @@ include!("./navbar/index.html"); .>
</td> </td>
<td class="sitekey-list__key"> <td class="sitekey-list__key">
<div class="sitekey__key-container"> <div class="sitekey__key-container">
<img class="sitekey__copy-icon" src="<.= crate::FILES <. let clipboard_data = ("sitekey", &sitekey.key); .>
.get("./static/cache/img/svg/clipboard.svg") .unwrap() .>" <. include!("../components/clipboard/index.html"); .>
alt="copy sitekey" data-sitekey="<.= sitekey.key .>" /> <img
class="sitekey__copy-done-icon" src="<.= crate::FILES
.get("./static/cache/img/svg/check.svg") .unwrap() .>"
alt="sitekey copied" data-sitekey="<.= sitekey.key .>" />
<a <a
class="sitekey__widget-link" class="sitekey__widget-link"
href="<.= crate::WIDGET_ROUTES.verification_widget .>/?sitekey=<.= sitekey.key .>" href="<.= crate::WIDGET_ROUTES.verification_widget .>/?sitekey=<.= sitekey.key .>"

View file

@ -35,7 +35,7 @@
</li> </li>
<li class="secondary-menu__item"> <li class="secondary-menu__item">
<a class="secondary-menu__item-link" href=""> <a class="secondary-menu__item-link" href="<.= crate::PAGES.panel.settings .>">
<img class="secondary-menu__icon" src="<.= crate::FILES.get("./static/cache/img/svg/settings.svg").unwrap() .>" alt="" /> <img class="secondary-menu__icon" src="<.= crate::FILES.get("./static/cache/img/svg/settings.svg").unwrap() .>" alt="" />
<div class="secondary-menu__item-name"> <div class="secondary-menu__item-name">
Settings Settings

View file

@ -0,0 +1,56 @@
<. const COPY_ALT: &str = "copy secret"; .>
<. const COPY_CLASS: &str = "settings__secret-copy"; .>
<. const DONE_ALT: &str = "secret copied"; .>
<. const DONE_CLASS: &str = "settings__secret-copy-done"; .>
<. let clipboard_data = ("secret", &secret); .>
<. include!("../../components/headers/index.html"); .>
<. include!("../navbar/index.html"); .>
<div class="tmp-layout">
<. include!("../header/index.html"); .>
<main class="panel-main">
<. include!("../help-banner/index.html"); .>
<!-- Main content container -->
<div class="inner-container">
<div class="sitekey-form" action="<.= crate::V1_API_ROUTES.levels.add .>" method="post">
<h1 class="form__title">
<.= PAGE .>
</h1>
<form action="<.= crate::V1_API_ROUTES.account.update_email .>" method="post">
<label class="sitekey-form__label" for="description">
Email
<input
class="sitekey-form__input"
type="email"
name="email"
id="email"
<. if let Some(email) = email { .>
<. if !email.trim().is_empty() { .>
value="<.= email .>"
<. } .>
<. } .>
/>
</label>
<button class="sitekey-form__submit" type="submit">Update</button>
</form>
<label class="sitekey-form__label" for="secret">
Cooldown Duratoin(in seconds)
<input
class="sitekey-form__input"
type="password"
name="secret-password"
id="secret"
value="<.= secret .>"
/>
<. include!("../../components/showPassword/index.html"); .>
<. include!("../../components/clipboard/index.html"); .>
</label>
<a class="settings__delete-account-link" href="<.= crate::PAGES.panel.sitekey.add .>">
<button class="settings__delete-account-btn" type="submit">Delete Account</button>
</a>
</div>
</div>
<!-- end of container -->
<. include!("../../components/footers.html"); .>

View file

@ -0,0 +1,34 @@
/*
* 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 registerShowPassword from '../../components/showPassword/';
import CopyIcon from '../../components/clipboard/';
const SECRET_COPY_ICON = 'settings__secret-copy';
const SECRET_COPY_DONE_ICON = 'settings__secret-copy-done';
const index = () => {
registerShowPassword();
const secretElement = <HTMLElement>(
document.querySelector(`.${SECRET_COPY_ICON}`)
);
const writeText = secretElement.dataset.secret;
new CopyIcon(writeText, SECRET_COPY_ICON, SECRET_COPY_DONE_ICON);
};
export default index;

View file

@ -0,0 +1,30 @@
/*
* 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 '../../components/clipboard/copy';
.settings__secret-copy {
@include copy-icon;
}
.settings__secret-copy:hover .settings__secret-copy-done:hover {
@include copy-icon-hover;
}
.settings__secret-copy-done {
@include copy-done-icon;
}

View file

@ -18,6 +18,7 @@
@import '../../../../reset'; @import '../../../../reset';
@import '../../../../vars'; @import '../../../../vars';
@import '../../../../components/box'; @import '../../../../components/box';
@import '../../../../components/clipboard/copy';
@import '../../../../components/table/main'; @import '../../../../components/table/main';
.sitekey__table { .sitekey__table {
@ -37,26 +38,17 @@
width: 10px; width: 10px;
} }
@mixin copy-icon-base {
margin: auto;
padding: 5px;
}
.sitekey__copy-icon { .sitekey__copy-icon {
@include copy-icon-base; @include copy-icon;
} }
.sitekey__copy-icon:hover { .sitekey__copy-icon:hover,
cursor: pointer; .sitekey__copy-done-icon:hover {
filter: invert(17%) sepia(93%) saturate(5039%) hue-rotate(204deg) @include copy-icon-hover;
brightness(100%) contrast(98%);
} }
.sitekey__copy-done-icon { .sitekey__copy-done-icon {
@include copy-icon-base; @include copy-done-icon;
display: none;
filter: invert(58%) sepia(60%) saturate(331%) hue-rotate(76deg)
brightness(91%) contrast(92%);
} }
.sitekey__key-container { .sitekey__key-container {

View file

@ -1,3 +1,8 @@
<. const DONE_ALT: &str = "sitekey copied"; .>
<. const DONE_CLASS: &str = "sitekey__copy-done-icon"; .>
<. const COPY_ALT: &str = "copy sitekey"; .>
<. const COPY_CLASS: &str = "sitekey__copy-icon"; .>
<. include!("../../../components/headers/index.html"); .> <. <. include!("../../../components/headers/index.html"); .> <.
include!("../../navbar/index.html"); .> include!("../../navbar/index.html"); .>
<div class="tmp-layout"> <div class="tmp-layout">
@ -28,12 +33,8 @@ include!("../../navbar/index.html"); .>
</td> </td>
<td class="sitekey-list__key"> <td class="sitekey-list__key">
<div class="sitekey__key-container"> <div class="sitekey__key-container">
<img class="sitekey__copy-icon" src="<.= crate::FILES <. let clipboard_data = ("sitekey", &sitekey.key); .>
.get("./static/cache/img/svg/clipboard.svg") .unwrap() .>" <. include!("../../../components/clipboard/index.html"); .>
alt="copy sitekey" data-sitekey="<.= sitekey.key .>" /> <img
class="sitekey__copy-done-icon" src="<.= crate::FILES
.get("./static/cache/img/svg/check.svg") .unwrap() .>"
alt="sitekey copied" data-sitekey="<.= sitekey.key .>" />
<a <a
class="sitekey__widget-link" class="sitekey__widget-link"
href="<.= crate::WIDGET_ROUTES.verification_widget .>/?sitekey=<.= sitekey.key .>" href="<.= crate::WIDGET_ROUTES.verification_widget .>/?sitekey=<.= sitekey.key .>"
@ -43,10 +44,10 @@ include!("../../navbar/index.html"); .>
</div> </div>
</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); .> <. let key = format!("/sitekey/{}", &sitekey.key); .>
<. include!("../view/__edit-sitekey-icon.html"); .> <. include!("../view/__edit-sitekey-icon.html"); .>
</div> </div>
</td> </td>
</tr> </tr>
<. } .> <. } .>

View file

@ -14,40 +14,19 @@
* You should have received a copy of the GNU Affero General Public License * 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/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import CopyIcon from '../../../../components/clipboard/';
export const index = () => {
registerCopySitekey();
};
const SITEKEY_COPY_ICON = `sitekey__copy-icon`; const SITEKEY_COPY_ICON = `sitekey__copy-icon`;
const SITEKEY_COPY_DONE_ICON = `sitekey__copy-done-icon`; const SITEKEY_COPY_DONE_ICON = `sitekey__copy-done-icon`;
const registerCopySitekey = () => { export const index = () => {
const icons = document.querySelectorAll(`.${SITEKEY_COPY_ICON}`); const image = <HTMLElement>document.querySelector(`.${SITEKEY_COPY_ICON}`);
icons.forEach(icon => {
icon.addEventListener('click', e => copySitekey(e));
});
};
/*
* Copy sitekey to clipboard
*/
const copySitekey = async (e: Event) => {
const image = <HTMLElement>e.target;
if (!image.classList.contains(SITEKEY_COPY_ICON)) { if (!image.classList.contains(SITEKEY_COPY_ICON)) {
throw new Error( throw new Error(
'This method should only be called when sitekey copy button/icon is clicked', 'This method should only be called when sitekey copy button/icon is clicked',
); );
} }
const copyDoneIcon = <HTMLElement>(
image.parentElement.querySelector(`.${SITEKEY_COPY_DONE_ICON}`)
);
const sitekey = image.dataset.sitekey; const sitekey = image.dataset.sitekey;
await navigator.clipboard.writeText(sitekey);
image.style.display = 'none'; new CopyIcon(sitekey, SITEKEY_COPY_ICON, SITEKEY_COPY_DONE_ICON);
copyDoneIcon.style.display = 'block';
setTimeout(() => {
copyDoneIcon.style.display = 'none';
image.style.display = 'block';
}, 1200);
}; };

View file

@ -20,6 +20,7 @@ const ROUTES = {
loginUser: '/login/', loginUser: '/login/',
signoutUser: '/api/v1/signout', signoutUser: '/api/v1/signout',
panelHome: '/', panelHome: '/',
settings: '/settings/',
docsHome: '/docs/', docsHome: '/docs/',
notifications: '/notifications', notifications: '/notifications',
listSitekey: '/sitekeys/', listSitekey: '/sitekeys/',