Change default user handle. Use async template renderer

This commit is contained in:
Cohee
2024-04-12 00:35:51 +03:00
parent d8092ec3eb
commit 396eeca73a
7 changed files with 76 additions and 35 deletions

10
package-lock.json generated
View File

@ -45,8 +45,6 @@
"sanitize-filename": "^1.6.3",
"sillytavern-transformers": "^2.14.6",
"simple-git": "^3.19.1",
"slugify": "^1.6.6",
"uuid": "^9.0.1",
"vectra": "^0.2.2",
"wavefile": "^11.0.0",
"write-file-atomic": "^5.0.1",
@ -4264,14 +4262,6 @@
"version": "1.0.1",
"license": "MIT"
},
"node_modules/slugify": {
"version": "1.6.6",
"resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.6.tgz",
"integrity": "sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==",
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/statuses": {
"version": "2.0.1",
"license": "MIT",

View File

@ -35,8 +35,6 @@
"sanitize-filename": "^1.6.3",
"sillytavern-transformers": "^2.14.6",
"simple-git": "^3.19.1",
"slugify": "^1.6.6",
"uuid": "^9.0.1",
"vectra": "^0.2.2",
"wavefile": "^11.0.0",
"write-file-atomic": "^5.0.1",

View File

@ -72,23 +72,23 @@
</div>
<div class="navTab registerNewUserBlock" style="display: none;">
<form class="flex-container flexFlowColumn flexGap10 userCreateForm" action="javascript:void(0);">
<div class="flex-container flexNoGap">
<span data-i18n="User Handle:">User Handle:</span>
<span class="warning">*</span>
<input name="handle" class="text_pole" placeholder="Lowercase letters, numbers, and dashes only." type="text" pattern="[a-z0-9-]+">
</div>
<div class="flex-container flexNoGap">
<span data-i18n="Display Name:">Display Name:</span>
<span class="warning">*</span>
<input name="_name" class="text_pole" type="text" placeholder="e.g. John" autocomplete="username">
<input name="_name" class="createUserDisplayName text_pole" type="text" placeholder="e.g. John Snow" autocomplete="username">
</div>
<div class="flex-container flexNoGap">
<span data-i18n="User Handle:">User Handle:</span>
<span class="warning">*</span>
<input name="handle" class="createUserHandle text_pole" placeholder="e.g. john-snow (lowercase letters, numbers, and dashes only)" type="text" pattern="[a-z0-9-]+">
</div>
<div class="flex-container flexNoGap">
<span data-i18n="Password:">Password:</span>
<input name="password" class="text_pole" type="password" placeholder="[ No password ]" autocomplete="new-password">
<input name="password" class="createUserPassword text_pole" type="password" placeholder="[ No password ]" autocomplete="new-password">
</div>
<div class="flex-container flexNoGap">
<span data-i18n="Confirm Password:">Confirm Password:</span>
<input name="confirm" class="text_pole" type="password" placeholder="[ No password ]" autocomplete="new-password">
<input name="confirm" class="createUserConfirmPassword text_pole" type="password" placeholder="[ No password ]" autocomplete="new-password">
</div>
<span data-i18n="This will create a new subfolder...">
This will create a new subfolder in the /data/ directory with the user's handle as the folder name.

View File

@ -1,5 +1,6 @@
import { getRequestHeaders, renderTemplate } from '../script.js';
import { getRequestHeaders } from '../script.js';
import { POPUP_RESULT, POPUP_TYPE, callGenericPopup } from './popup.js';
import { renderTemplateAsync } from './templates.js';
import { humanFileSize } from './utils.js';
/**
@ -269,7 +270,7 @@ async function backupUserData(handle, callback) {
*/
async function changePassword(handle, callback) {
try {
const template = $(renderTemplate('changePassword'));
const template = $(await renderTemplateAsync('changePassword'));
template.find('.currentPasswordBlock').toggle(!isAdmin());
let newPassword = '';
let confirmPassword = '';
@ -313,6 +314,11 @@ async function changePassword(handle, callback) {
}
}
/**
* Delete a user.
* @param {string} handle User handle
* @param {function} callback Success callback
*/
async function deleteUser(handle, callback) {
try {
if (handle === currentUser.handle) {
@ -323,7 +329,7 @@ async function deleteUser(handle, callback) {
let purge = false;
let confirmHandle = '';
const template = $(renderTemplate('deleteUser'));
const template = $(await renderTemplateAsync('deleteUser'));
template.find('#deleteUserName').text(handle);
template.find('input[name="deleteUserData"]').on('input', function () {
purge = $(this).is(':checked');
@ -370,7 +376,7 @@ async function deleteUser(handle, callback) {
async function resetSettings(handle, callback) {
try {
let password = '';
const template = $(renderTemplate('resetSettings'));
const template = $(await renderTemplateAsync('resetSettings'));
template.find('input[name="password"]').on('input', function () {
password = String($(this).val());
});
@ -407,7 +413,7 @@ async function resetSettings(handle, callback) {
*/
async function changeName(handle, name, callback) {
try {
const template = $(renderTemplate('changeName'));
const template = $(await renderTemplateAsync('changeName'));
const result = await callGenericPopup(template, POPUP_TYPE.INPUT, name, { okButton: 'Change', cancelButton: 'Cancel', wide: false, large: false });
if (!result) {
@ -556,7 +562,7 @@ async function makeSnapshot(callback) {
* Open the settings snapshots view.
*/
async function viewSettingsSnapshots() {
const template = $(renderTemplate('snapshotsView'));
const template = $(await renderTemplateAsync('snapshotsView'));
async function renderSnapshots() {
const snapshots = await getSnapshots();
template.find('.snapshotList').empty();
@ -589,7 +595,7 @@ async function viewSettingsSnapshots() {
async function openUserProfile() {
await getCurrentUser();
const template = $(renderTemplate('userProfile'));
const template = $(await renderTemplateAsync('userProfile'));
template.find('.userName').text(currentUser.name);
template.find('.userHandle').text(currentUser.handle);
template.find('.avatar img').attr('src', currentUser.avatar);
@ -654,7 +660,7 @@ async function openAdminPanel() {
}
}
const template = $(renderTemplate('admin'));
const template = $(await renderTemplateAsync('admin'));
template.find('.adminNav > button').on('click', function () {
const target = String($(this).data('target-tab'));
@ -663,6 +669,11 @@ async function openAdminPanel() {
});
});
template.find('.createUserDisplayName').on('input', async function () {
const slug = await slugify(String($(this).val()));
template.find('.createUserHandle').val(slug);
});
template.find('.userCreateForm').on('submit', function (event) {
if (!(event.target instanceof HTMLFormElement)) {
return;
@ -692,6 +703,30 @@ async function logout() {
window.location.reload();
}
/**
* Runs a text through the slugify API endpoint.
* @param {string} text Text to slugify
* @returns {Promise<string>} Slugified text
*/
async function slugify(text) {
try {
const response = await fetch('/api/users/slugify', {
method: 'POST',
headers: getRequestHeaders(),
body: JSON.stringify({ text }),
});
if (!response.ok) {
throw new Error('Failed to slugify text');
}
return response.text();
} catch (error) {
console.error('Error slugifying text:', error);
return text;
}
}
jQuery(() => {
$('#logout_button').on('click', () => {
logout();

View File

@ -48,8 +48,7 @@ const USER_DIRECTORY_TEMPLATE = Object.freeze({
* @readonly
*/
const DEFAULT_USER = Object.freeze({
uuid: '00000000-0000-0000-0000-000000000000',
handle: 'user0',
handle: 'default-user',
name: 'User',
created: Date.now(),
password: '',

View File

@ -1,8 +1,7 @@
const fsPromises = require('fs').promises;
const storage = require('node-persist');
const express = require('express');
const slugify = require('slugify').default;
const uuid = require('uuid');
const lodash = require('lodash');
const { jsonParser } = require('../express-common');
const { checkForNewContent } = require('./content-manager');
const {
@ -16,6 +15,7 @@ const {
getUserDirectories,
ensurePublicDirectoriesExist,
} = require('../users');
const { DEFAULT_USER } = require('../constants');
const router = express.Router();
@ -157,7 +157,7 @@ router.post('/create', requireAdminMiddleware, jsonParser, async (request, respo
}
const handles = await getAllUserHandles();
const handle = slugify(String(request.body.handle).toLowerCase(), { lower: true, trim: true, remove: /[^a-z0-9-]/g });
const handle = lodash.kebabCase(String(request.body.handle).toLowerCase().trim());
if (!handle) {
console.log('Create user failed: Invalid handle');
@ -173,7 +173,6 @@ router.post('/create', requireAdminMiddleware, jsonParser, async (request, respo
const password = request.body.password ? getPasswordHash(request.body.password, salt) : '';
const newUser = {
uuid: uuid.v4(),
handle: handle,
name: request.body.name || 'Anonymous',
created: Date.now(),
@ -209,6 +208,11 @@ router.post('/delete', requireAdminMiddleware, jsonParser, async (request, respo
return response.status(400).json({ error: 'Cannot delete yourself' });
}
if (request.body.handle === DEFAULT_USER.handle) {
console.log('Delete user failed: Cannot delete default user');
return response.status(400).json({ error: 'Sorry, but the default user cannot be deleted. It is required as a fallback.' });
}
await storage.removeItem(toKey(request.body.handle));
if (request.body.purge) {
@ -224,6 +228,22 @@ router.post('/delete', requireAdminMiddleware, jsonParser, async (request, respo
}
});
router.post('/slugify', requireAdminMiddleware, jsonParser, async (request, response) => {
try {
if (!request.body.text) {
console.log('Slugify failed: Missing required fields');
return response.status(400).json({ error: 'Missing required fields' });
}
const text = lodash.kebabCase(String(request.body.text).toLowerCase().trim());
return response.send(text);
} catch (error) {
console.error('Slugify failed:', error);
return response.sendStatus(500);
}
});
module.exports = {
router,
};

View File

@ -32,7 +32,6 @@ const STORAGE_KEYS = {
/**
* @typedef {Object} User
* @property {string} uuid - The user's id
* @property {string} handle - The user's short handle. Used for directories and other references
* @property {string} name - The user's name. Displayed in the UI
* @property {number} created - The timestamp when the user was created