mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Change default user handle. Use async template renderer
This commit is contained in:
10
package-lock.json
generated
10
package-lock.json
generated
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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.
|
||||
|
@ -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();
|
||||
|
@ -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: '',
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -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
|
||||
|
Reference in New Issue
Block a user