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",
|
"sanitize-filename": "^1.6.3",
|
||||||
"sillytavern-transformers": "^2.14.6",
|
"sillytavern-transformers": "^2.14.6",
|
||||||
"simple-git": "^3.19.1",
|
"simple-git": "^3.19.1",
|
||||||
"slugify": "^1.6.6",
|
|
||||||
"uuid": "^9.0.1",
|
|
||||||
"vectra": "^0.2.2",
|
"vectra": "^0.2.2",
|
||||||
"wavefile": "^11.0.0",
|
"wavefile": "^11.0.0",
|
||||||
"write-file-atomic": "^5.0.1",
|
"write-file-atomic": "^5.0.1",
|
||||||
@ -4264,14 +4262,6 @@
|
|||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/statuses": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
@ -35,8 +35,6 @@
|
|||||||
"sanitize-filename": "^1.6.3",
|
"sanitize-filename": "^1.6.3",
|
||||||
"sillytavern-transformers": "^2.14.6",
|
"sillytavern-transformers": "^2.14.6",
|
||||||
"simple-git": "^3.19.1",
|
"simple-git": "^3.19.1",
|
||||||
"slugify": "^1.6.6",
|
|
||||||
"uuid": "^9.0.1",
|
|
||||||
"vectra": "^0.2.2",
|
"vectra": "^0.2.2",
|
||||||
"wavefile": "^11.0.0",
|
"wavefile": "^11.0.0",
|
||||||
"write-file-atomic": "^5.0.1",
|
"write-file-atomic": "^5.0.1",
|
||||||
|
@ -72,23 +72,23 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="navTab registerNewUserBlock" style="display: none;">
|
<div class="navTab registerNewUserBlock" style="display: none;">
|
||||||
<form class="flex-container flexFlowColumn flexGap10 userCreateForm" action="javascript:void(0);">
|
<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">
|
<div class="flex-container flexNoGap">
|
||||||
<span data-i18n="Display Name:">Display Name:</span>
|
<span data-i18n="Display Name:">Display Name:</span>
|
||||||
<span class="warning">*</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>
|
||||||
<div class="flex-container flexNoGap">
|
<div class="flex-container flexNoGap">
|
||||||
<span data-i18n="Password:">Password:</span>
|
<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>
|
||||||
<div class="flex-container flexNoGap">
|
<div class="flex-container flexNoGap">
|
||||||
<span data-i18n="Confirm Password:">Confirm Password:</span>
|
<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>
|
</div>
|
||||||
<span data-i18n="This will create a new subfolder...">
|
<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.
|
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 { POPUP_RESULT, POPUP_TYPE, callGenericPopup } from './popup.js';
|
||||||
|
import { renderTemplateAsync } from './templates.js';
|
||||||
import { humanFileSize } from './utils.js';
|
import { humanFileSize } from './utils.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -269,7 +270,7 @@ async function backupUserData(handle, callback) {
|
|||||||
*/
|
*/
|
||||||
async function changePassword(handle, callback) {
|
async function changePassword(handle, callback) {
|
||||||
try {
|
try {
|
||||||
const template = $(renderTemplate('changePassword'));
|
const template = $(await renderTemplateAsync('changePassword'));
|
||||||
template.find('.currentPasswordBlock').toggle(!isAdmin());
|
template.find('.currentPasswordBlock').toggle(!isAdmin());
|
||||||
let newPassword = '';
|
let newPassword = '';
|
||||||
let confirmPassword = '';
|
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) {
|
async function deleteUser(handle, callback) {
|
||||||
try {
|
try {
|
||||||
if (handle === currentUser.handle) {
|
if (handle === currentUser.handle) {
|
||||||
@ -323,7 +329,7 @@ async function deleteUser(handle, callback) {
|
|||||||
let purge = false;
|
let purge = false;
|
||||||
let confirmHandle = '';
|
let confirmHandle = '';
|
||||||
|
|
||||||
const template = $(renderTemplate('deleteUser'));
|
const template = $(await renderTemplateAsync('deleteUser'));
|
||||||
template.find('#deleteUserName').text(handle);
|
template.find('#deleteUserName').text(handle);
|
||||||
template.find('input[name="deleteUserData"]').on('input', function () {
|
template.find('input[name="deleteUserData"]').on('input', function () {
|
||||||
purge = $(this).is(':checked');
|
purge = $(this).is(':checked');
|
||||||
@ -370,7 +376,7 @@ async function deleteUser(handle, callback) {
|
|||||||
async function resetSettings(handle, callback) {
|
async function resetSettings(handle, callback) {
|
||||||
try {
|
try {
|
||||||
let password = '';
|
let password = '';
|
||||||
const template = $(renderTemplate('resetSettings'));
|
const template = $(await renderTemplateAsync('resetSettings'));
|
||||||
template.find('input[name="password"]').on('input', function () {
|
template.find('input[name="password"]').on('input', function () {
|
||||||
password = String($(this).val());
|
password = String($(this).val());
|
||||||
});
|
});
|
||||||
@ -407,7 +413,7 @@ async function resetSettings(handle, callback) {
|
|||||||
*/
|
*/
|
||||||
async function changeName(handle, name, callback) {
|
async function changeName(handle, name, callback) {
|
||||||
try {
|
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 });
|
const result = await callGenericPopup(template, POPUP_TYPE.INPUT, name, { okButton: 'Change', cancelButton: 'Cancel', wide: false, large: false });
|
||||||
|
|
||||||
if (!result) {
|
if (!result) {
|
||||||
@ -556,7 +562,7 @@ async function makeSnapshot(callback) {
|
|||||||
* Open the settings snapshots view.
|
* Open the settings snapshots view.
|
||||||
*/
|
*/
|
||||||
async function viewSettingsSnapshots() {
|
async function viewSettingsSnapshots() {
|
||||||
const template = $(renderTemplate('snapshotsView'));
|
const template = $(await renderTemplateAsync('snapshotsView'));
|
||||||
async function renderSnapshots() {
|
async function renderSnapshots() {
|
||||||
const snapshots = await getSnapshots();
|
const snapshots = await getSnapshots();
|
||||||
template.find('.snapshotList').empty();
|
template.find('.snapshotList').empty();
|
||||||
@ -589,7 +595,7 @@ async function viewSettingsSnapshots() {
|
|||||||
|
|
||||||
async function openUserProfile() {
|
async function openUserProfile() {
|
||||||
await getCurrentUser();
|
await getCurrentUser();
|
||||||
const template = $(renderTemplate('userProfile'));
|
const template = $(await renderTemplateAsync('userProfile'));
|
||||||
template.find('.userName').text(currentUser.name);
|
template.find('.userName').text(currentUser.name);
|
||||||
template.find('.userHandle').text(currentUser.handle);
|
template.find('.userHandle').text(currentUser.handle);
|
||||||
template.find('.avatar img').attr('src', currentUser.avatar);
|
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 () {
|
template.find('.adminNav > button').on('click', function () {
|
||||||
const target = String($(this).data('target-tab'));
|
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) {
|
template.find('.userCreateForm').on('submit', function (event) {
|
||||||
if (!(event.target instanceof HTMLFormElement)) {
|
if (!(event.target instanceof HTMLFormElement)) {
|
||||||
return;
|
return;
|
||||||
@ -692,6 +703,30 @@ async function logout() {
|
|||||||
window.location.reload();
|
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(() => {
|
jQuery(() => {
|
||||||
$('#logout_button').on('click', () => {
|
$('#logout_button').on('click', () => {
|
||||||
logout();
|
logout();
|
||||||
|
@ -48,8 +48,7 @@ const USER_DIRECTORY_TEMPLATE = Object.freeze({
|
|||||||
* @readonly
|
* @readonly
|
||||||
*/
|
*/
|
||||||
const DEFAULT_USER = Object.freeze({
|
const DEFAULT_USER = Object.freeze({
|
||||||
uuid: '00000000-0000-0000-0000-000000000000',
|
handle: 'default-user',
|
||||||
handle: 'user0',
|
|
||||||
name: 'User',
|
name: 'User',
|
||||||
created: Date.now(),
|
created: Date.now(),
|
||||||
password: '',
|
password: '',
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
const fsPromises = require('fs').promises;
|
const fsPromises = require('fs').promises;
|
||||||
const storage = require('node-persist');
|
const storage = require('node-persist');
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const slugify = require('slugify').default;
|
const lodash = require('lodash');
|
||||||
const uuid = require('uuid');
|
|
||||||
const { jsonParser } = require('../express-common');
|
const { jsonParser } = require('../express-common');
|
||||||
const { checkForNewContent } = require('./content-manager');
|
const { checkForNewContent } = require('./content-manager');
|
||||||
const {
|
const {
|
||||||
@ -16,6 +15,7 @@ const {
|
|||||||
getUserDirectories,
|
getUserDirectories,
|
||||||
ensurePublicDirectoriesExist,
|
ensurePublicDirectoriesExist,
|
||||||
} = require('../users');
|
} = require('../users');
|
||||||
|
const { DEFAULT_USER } = require('../constants');
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
@ -157,7 +157,7 @@ router.post('/create', requireAdminMiddleware, jsonParser, async (request, respo
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handles = await getAllUserHandles();
|
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) {
|
if (!handle) {
|
||||||
console.log('Create user failed: Invalid 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 password = request.body.password ? getPasswordHash(request.body.password, salt) : '';
|
||||||
|
|
||||||
const newUser = {
|
const newUser = {
|
||||||
uuid: uuid.v4(),
|
|
||||||
handle: handle,
|
handle: handle,
|
||||||
name: request.body.name || 'Anonymous',
|
name: request.body.name || 'Anonymous',
|
||||||
created: Date.now(),
|
created: Date.now(),
|
||||||
@ -209,6 +208,11 @@ router.post('/delete', requireAdminMiddleware, jsonParser, async (request, respo
|
|||||||
return response.status(400).json({ error: 'Cannot delete yourself' });
|
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));
|
await storage.removeItem(toKey(request.body.handle));
|
||||||
|
|
||||||
if (request.body.purge) {
|
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 = {
|
module.exports = {
|
||||||
router,
|
router,
|
||||||
};
|
};
|
||||||
|
@ -32,7 +32,6 @@ const STORAGE_KEYS = {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {Object} User
|
* @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} handle - The user's short handle. Used for directories and other references
|
||||||
* @property {string} name - The user's name. Displayed in the UI
|
* @property {string} name - The user's name. Displayed in the UI
|
||||||
* @property {number} created - The timestamp when the user was created
|
* @property {number} created - The timestamp when the user was created
|
||||||
|
Reference in New Issue
Block a user