mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-10-27 17:16:14 +01:00
feat: move website/location fields into custom user fields
This commit is contained in:
@@ -107,10 +107,10 @@
|
||||
"nodebb-plugin-ntfy": "1.7.7",
|
||||
"nodebb-plugin-spam-be-gone": "2.2.2",
|
||||
"nodebb-rewards-essentials": "1.0.0",
|
||||
"nodebb-theme-harmony": "1.2.87",
|
||||
"nodebb-theme-harmony": "1.2.88",
|
||||
"nodebb-theme-lavender": "7.1.16",
|
||||
"nodebb-theme-peace": "2.2.21",
|
||||
"nodebb-theme-persona": "13.3.53",
|
||||
"nodebb-theme-peace": "2.2.22",
|
||||
"nodebb-theme-persona": "13.3.54",
|
||||
"nodebb-widget-essentials": "7.0.31",
|
||||
"nodemailer": "6.9.16",
|
||||
"nprogress": "0.2.0",
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
"min-rep-chat": "Minimum reputation to send chat messages",
|
||||
"min-rep-post-links": "Minimum reputation to post links",
|
||||
"min-rep-flag": "Minimum reputation to flag posts",
|
||||
"min-rep-website": "Minimum reputation to add \"Website\" to user profile",
|
||||
"min-rep-aboutme": "Minimum reputation to add \"About me\" to user profile",
|
||||
"min-rep-signature": "Minimum reputation to add \"Signature\" to user profile",
|
||||
"min-rep-profile-picture": "Minimum reputation to add \"Profile Picture\" to user profile",
|
||||
|
||||
@@ -211,6 +211,7 @@
|
||||
"not-enough-reputation-custom-field": "You need %1 reputation for %2",
|
||||
"custom-user-field-value-too-long": "Custom field value too long, %1",
|
||||
"custom-user-field-select-value-invalid": "Custom field selected option is invalid, %1",
|
||||
"custom-user-field-invalid-text": "Custom field text is invalid, %1",
|
||||
"custom-user-field-invalid-link": "Custom field link is invalid, %1",
|
||||
"custom-user-field-invalid-number": "Custom field number is invalid, %1",
|
||||
"custom-user-field-invalid-date": "Custom field date is invalid, %1",
|
||||
|
||||
@@ -41,19 +41,11 @@ UserObject:
|
||||
type: string
|
||||
description: This is either username or fullname depending on forum and user settings
|
||||
example: Dragon Fruit
|
||||
location:
|
||||
type: string
|
||||
example: 'Toronto, Canada'
|
||||
nullable: true
|
||||
birthday:
|
||||
type: string
|
||||
description: A birthdate given in an ISO format parseable by the Date object
|
||||
example: 03/27/2020
|
||||
nullable: true
|
||||
website:
|
||||
type: string
|
||||
example: 'https://example.org'
|
||||
nullable: true
|
||||
aboutme:
|
||||
type: string
|
||||
example: |
|
||||
@@ -172,9 +164,7 @@ UserObject:
|
||||
- joindate
|
||||
- lastonline
|
||||
- picture
|
||||
- location
|
||||
- birthday
|
||||
- website
|
||||
- aboutme
|
||||
- signature
|
||||
- uploadedpicture
|
||||
@@ -245,16 +235,10 @@ UserObjectFull:
|
||||
type: string
|
||||
description: This is either username or fullname depending on forum and user settings
|
||||
example: Dragon Fruit
|
||||
location:
|
||||
type: string
|
||||
example: 'Toronto, Canada'
|
||||
birthday:
|
||||
type: string
|
||||
description: A birthdate given in an ISO format parseable by the Date object
|
||||
example: 03/27/2020
|
||||
website:
|
||||
type: string
|
||||
example: 'https://example.org'
|
||||
aboutme:
|
||||
type: string
|
||||
example: |
|
||||
@@ -508,10 +492,6 @@ UserObjectFull:
|
||||
- name
|
||||
- visibility
|
||||
- public
|
||||
websiteLink:
|
||||
type: string
|
||||
websiteName:
|
||||
type: string
|
||||
username:disableEdit:
|
||||
type: number
|
||||
email:disableEdit:
|
||||
|
||||
@@ -15,6 +15,9 @@ get:
|
||||
type: array
|
||||
items:
|
||||
$ref: ../../../components/schemas/UserObject.yaml#/UserObjectACP
|
||||
customUserFields:
|
||||
type: array
|
||||
description: array of custom user fields
|
||||
page:
|
||||
type: number
|
||||
pageCount:
|
||||
|
||||
@@ -37,8 +37,6 @@ get:
|
||||
type: boolean
|
||||
allowAccountDelete:
|
||||
type: boolean
|
||||
allowWebsite:
|
||||
type: boolean
|
||||
allowAboutMe:
|
||||
type: boolean
|
||||
allowSignature:
|
||||
|
||||
@@ -43,12 +43,15 @@ define('admin/manage/users', [
|
||||
{ label: '[[admin/manage/users:export-field-followercount]]', field: 'followerCount', selected: false },
|
||||
{ label: '[[admin/manage/users:export-field-followingcount]]', field: 'followingCount', selected: false },
|
||||
{ label: '[[admin/manage/users:export-field-fullname]]', field: 'fullname', selected: false },
|
||||
{ label: '[[admin/manage/users:export-field-website]]', field: 'website', selected: false },
|
||||
{ label: '[[admin/manage/users:export-field-location]]', field: 'location', selected: false },
|
||||
{ label: '[[admin/manage/users:export-field-birthday]]', field: 'birthday', selected: false },
|
||||
{ label: '[[admin/manage/users:export-field-signature]]', field: 'signature', selected: false },
|
||||
{ label: '[[admin/manage/users:export-field-aboutme]]', field: 'aboutme', selected: false },
|
||||
];
|
||||
].concat(ajaxify.data.customUserFields.map(field => ({
|
||||
label: field.name,
|
||||
field: field.key,
|
||||
selected: false,
|
||||
})));
|
||||
|
||||
const options = defaultFields.map((field, i) => (`
|
||||
<div class="form-check mb-2">
|
||||
<input data-field="${field.field}" class="form-check-input" type="checkbox" id="option-${i}" ${field.selected ? 'checked' : ''}>
|
||||
|
||||
@@ -38,7 +38,6 @@ editController.get = async function (req, res, next) {
|
||||
userData.maximumProfileImageSize = meta.config.maximumProfileImageSize;
|
||||
userData.allowMultipleBadges = meta.config.allowMultipleBadges === 1;
|
||||
userData.allowAccountDelete = meta.config.allowAccountDelete === 1;
|
||||
userData.allowWebsite = !isSelf || !!meta.config['reputation:disabled'] || reputation >= meta.config['min:rep:website'];
|
||||
userData.allowAboutMe = !isSelf || !!meta.config['reputation:disabled'] || reputation >= meta.config['min:rep:aboutme'];
|
||||
userData.allowSignature = canUseSignature && (!isSelf || !!meta.config['reputation:disabled'] || reputation >= meta.config['min:rep:signature']);
|
||||
userData.profileImageDimension = meta.config.profileImageDimension;
|
||||
|
||||
@@ -104,12 +104,7 @@ helpers.getUserDataByUserSlug = async function (userslug, callerUID, query = {})
|
||||
|
||||
userData.banned = Boolean(userData.banned);
|
||||
userData.muted = parseInt(userData.mutedUntil, 10) > Date.now();
|
||||
userData.website = escape(userData.website);
|
||||
userData.websiteLink = !userData.website.startsWith('http') ? `http://${userData.website}` : userData.website;
|
||||
userData.websiteName = userData.website.replace(validator.escape('http://'), '').replace(validator.escape('https://'), '');
|
||||
|
||||
userData.fullname = escape(userData.fullname);
|
||||
userData.location = escape(userData.location);
|
||||
userData.signature = escape(userData.signature);
|
||||
userData.birthday = validator.escape(String(userData.birthday || ''));
|
||||
userData.moderationNote = validator.escape(String(userData.moderationNote || ''));
|
||||
@@ -148,6 +143,9 @@ helpers.getCustomUserFields = async function (userData) {
|
||||
if (f.type === 'select-multi' && userValue) {
|
||||
userValue = JSON.parse(userValue || '[]');
|
||||
}
|
||||
if (f.type === 'input-link' && userValue) {
|
||||
f.linkValue = validator.escape(String(userValue.replace('http://', '').replace('https://', '')));
|
||||
}
|
||||
f['select-options'] = f['select-options'].split('\n').filter(Boolean).map(
|
||||
opt => ({
|
||||
value: opt,
|
||||
|
||||
@@ -94,9 +94,10 @@ async function getUsers(req, res) {
|
||||
|
||||
const set = buildSet();
|
||||
const uids = await getUids(set);
|
||||
const [count, users] = await Promise.all([
|
||||
const [count, users, customUserFields] = await Promise.all([
|
||||
getCount(set),
|
||||
loadUserInfo(req.uid, uids),
|
||||
getCustomUserFields(),
|
||||
]);
|
||||
|
||||
await render(req, res, {
|
||||
@@ -106,9 +107,15 @@ async function getUsers(req, res) {
|
||||
resultsPerPage: resultsPerPage,
|
||||
reverse: reverse,
|
||||
sortBy: sortBy,
|
||||
customUserFields,
|
||||
});
|
||||
}
|
||||
|
||||
async function getCustomUserFields() {
|
||||
const keys = await db.getSortedSetRange('user-custom-fields', 0, -1);
|
||||
return (await db.getObjects(keys.map(k => `user-custom-field:${k}`))).filter(Boolean);
|
||||
}
|
||||
|
||||
usersController.search = async function (req, res) {
|
||||
const sortDirection = req.query.sortDirection || 'desc';
|
||||
const reverse = sortDirection === 'desc';
|
||||
|
||||
44
src/upgrades/3.11.0/default-custom-profile-fields.js
Normal file
44
src/upgrades/3.11.0/default-custom-profile-fields.js
Normal file
@@ -0,0 +1,44 @@
|
||||
/* eslint-disable no-await-in-loop */
|
||||
|
||||
'use strict';
|
||||
|
||||
const db = require('../../database');
|
||||
|
||||
module.exports = {
|
||||
name: 'Add website and location as custom profile fields',
|
||||
timestamp: Date.UTC(2024, 10, 25),
|
||||
method: async function () {
|
||||
const minRepWebsite = parseInt(await db.getObjectField('config', 'min:rep:website'), 10) || 0;
|
||||
|
||||
const website = {
|
||||
icon: 'fa-solid fa-globe',
|
||||
key: 'website',
|
||||
'min:rep': minRepWebsite,
|
||||
name: '[[user:website]]',
|
||||
'select-options': '',
|
||||
type: 'input-link',
|
||||
};
|
||||
|
||||
const location = {
|
||||
icon: 'fa-solid fa-map-pin',
|
||||
key: 'location',
|
||||
'min:rep': 0,
|
||||
name: '[[user:location]]',
|
||||
'select-options': '',
|
||||
type: 'input-text',
|
||||
};
|
||||
|
||||
await db.sortedSetAdd(
|
||||
`user-custom-fields`,
|
||||
[0, 1],
|
||||
['website', 'location']
|
||||
);
|
||||
|
||||
await db.setObjectBulk([
|
||||
[`user-custom-field:website`, website],
|
||||
[`user-custom-field:location`, location],
|
||||
]);
|
||||
|
||||
await db.deleteObjectField('config', 'min:rep:website');
|
||||
},
|
||||
};
|
||||
@@ -21,7 +21,7 @@ const intFields = [
|
||||
module.exports = function (User) {
|
||||
const fieldWhitelist = [
|
||||
'uid', 'username', 'userslug', 'email', 'email:confirmed', 'joindate',
|
||||
'lastonline', 'picture', 'icon:bgColor', 'fullname', 'location', 'birthday', 'website',
|
||||
'lastonline', 'picture', 'icon:bgColor', 'fullname', 'birthday',
|
||||
'aboutme', 'signature', 'uploadedpicture', 'profileviews', 'reputation',
|
||||
'postcount', 'topiccount', 'lastposttime', 'banned', 'banned:expire',
|
||||
'status', 'flags', 'followerCount', 'followingCount', 'cover:url',
|
||||
|
||||
@@ -77,11 +77,9 @@ module.exports = function (User) {
|
||||
async function validateData(callerUid, data) {
|
||||
await isEmailValid(data);
|
||||
await isUsernameAvailable(data, data.uid);
|
||||
await isWebsiteValid(callerUid, data);
|
||||
await isAboutMeValid(callerUid, data);
|
||||
await isSignatureValid(callerUid, data);
|
||||
isFullnameValid(data);
|
||||
isLocationValid(data);
|
||||
isBirthdayValid(data);
|
||||
isGroupTitleValid(data);
|
||||
await validateCustomFields(data);
|
||||
@@ -113,6 +111,10 @@ module.exports = function (User) {
|
||||
throw new Error(tx.compile(
|
||||
'error:custom-user-field-invalid-number', field.name
|
||||
));
|
||||
} else if (value && type === 'input-text' && validator.isURL(value)) {
|
||||
throw new Error(tx.compile(
|
||||
'error:custom-user-field-invalid-text', field.name
|
||||
));
|
||||
} else if (value && type === 'input-date' && !validator.isDate(value)) {
|
||||
throw new Error(tx.compile(
|
||||
'error:custom-user-field-invalid-date', field.name
|
||||
@@ -197,16 +199,6 @@ module.exports = function (User) {
|
||||
}
|
||||
User.checkUsername = async username => isUsernameAvailable({ username });
|
||||
|
||||
async function isWebsiteValid(callerUid, data) {
|
||||
if (!data.website) {
|
||||
return;
|
||||
}
|
||||
if (data.website.length > 255) {
|
||||
throw new Error('[[error:invalid-website]]');
|
||||
}
|
||||
await User.checkMinReputation(callerUid, data.uid, 'min:rep:website');
|
||||
}
|
||||
|
||||
async function isAboutMeValid(callerUid, data) {
|
||||
if (!data.aboutme) {
|
||||
return;
|
||||
@@ -235,12 +227,6 @@ module.exports = function (User) {
|
||||
}
|
||||
}
|
||||
|
||||
function isLocationValid(data) {
|
||||
if (data.location && (validator.isURL(data.location) || data.location.length > 255)) {
|
||||
throw new Error('[[error:invalid-location]]');
|
||||
}
|
||||
}
|
||||
|
||||
function isBirthdayValid(data) {
|
||||
if (!data.birthday) {
|
||||
return;
|
||||
|
||||
@@ -73,10 +73,6 @@
|
||||
<label class="form-label" for="min:rep:flag">[[admin/settings/reputation:min-rep-flag]]</label>
|
||||
<input type="number" class="form-control" placeholder="0" data-field="min:rep:flag" id="min:rep:flag">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="min:rep:website">[[admin/settings/reputation:min-rep-website]]</label>
|
||||
<input type="number" class="form-control" placeholder="0" data-field="min:rep:website" id="min:rep:website">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="min:rep:aboutme">[[admin/settings/reputation:min-rep-aboutme]]</label>
|
||||
<input type="number" class="form-control" placeholder="0" data-field="min:rep:aboutme" id="min:rep:aboutme">
|
||||
|
||||
Reference in New Issue
Block a user