Compare commits

...

5 commits

Author SHA1 Message Date
ThatOneCalculator
e90d8e3b05 refactor: ♻️ Use vcard:Nickname instead of vcard:Gender 2022-05-23 19:07:39 -07:00
ThatOneCalculator
82eb927dd4 refactor: ♻️ Use vcard:Gender for vCard
https://datatracker.ietf.org/doc/html/rfc6350#section-6.2.7
2022-05-23 19:03:29 -07:00
ThatOneCalculator
446ab176ac docs: 📝 Add to CHANGELOG 2022-05-23 18:34:43 -07:00
ThatOneCalculator
7177354219 Undo IDE formatting 2022-05-23 18:18:22 -07:00
ThatOneCalculator
4f652dab02 feat: Start work on pronouns 2022-05-23 18:13:49 -07:00
12 changed files with 39 additions and 1 deletions

View file

@ -26,6 +26,7 @@ You should also include the user name that made the change.
Your own theme color may be unset if it was in an invalid format. Your own theme color may be unset if it was in an invalid format.
Admins should check their instance settings if in doubt. Admins should check their instance settings if in doubt.
- Perform port diagnosis at startup only when Listen fails @mei23 - Perform port diagnosis at startup only when Listen fails @mei23
- Add pronouns to vCard and display when replying @ThatOneCalculator
### Bugfixes ### Bugfixes
- Client: fix settings page @tamaina - Client: fix settings page @tamaina

View file

@ -258,6 +258,7 @@ remoteUserCaution: "As this user is from a remote instance, the shown informatio
activity: "Activity" activity: "Activity"
images: "Images" images: "Images"
birthday: "Birthday" birthday: "Birthday"
pronouns: "Pronouns"
yearsOld: "{age} years old" yearsOld: "{age} years old"
registeredDate: "Joined on" registeredDate: "Joined on"
location: "Location" location: "Location"

View file

@ -258,6 +258,7 @@ remoteUserCaution: "リモートユーザーのため、情報が不完全です
activity: "アクティビティ" activity: "アクティビティ"
images: "画像" images: "画像"
birthday: "誕生日" birthday: "誕生日"
pronouns: "代名詞"
yearsOld: "{age}歳" yearsOld: "{age}歳"
registeredDate: "登録日" registeredDate: "登録日"
location: "場所" location: "場所"

View file

@ -17,6 +17,12 @@ export class UserProfile {
@JoinColumn() @JoinColumn()
public user: User | null; public user: User | null;
@Column('varchar', {
length: 32, nullable: true,
comment: 'The pronouns of the User.',
})
public pronouns: string | null;
@Column('varchar', { @Column('varchar', {
length: 128, nullable: true, length: 128, nullable: true,
comment: 'The location of the User.', comment: 'The location of the User.',

View file

@ -16,7 +16,7 @@ const userInstanceCache = new Cache<Instance | null>(1000 * 60 * 60 * 3);
type IsUserDetailed<Detailed extends boolean> = Detailed extends true ? Packed<'UserDetailed'> : Packed<'UserLite'>; type IsUserDetailed<Detailed extends boolean> = Detailed extends true ? Packed<'UserDetailed'> : Packed<'UserLite'>;
type IsMeAndIsUserDetailed<ExpectsMe extends boolean | null, Detailed extends boolean> = type IsMeAndIsUserDetailed<ExpectsMe extends boolean | null, Detailed extends boolean> =
Detailed extends true ? Detailed extends true ?
ExpectsMe extends true ? Packed<'MeDetailed'> : ExpectsMe extends true ? Packed<'MeDetailed'> :
ExpectsMe extends false ? Packed<'UserDetailedNotMe'> : ExpectsMe extends false ? Packed<'UserDetailedNotMe'> :
Packed<'UserDetailed'> : Packed<'UserDetailed'> :
@ -30,6 +30,7 @@ const nameSchema = { type: 'string', minLength: 1, maxLength: 50 } as const;
const descriptionSchema = { type: 'string', minLength: 1, maxLength: 500 } as const; const descriptionSchema = { type: 'string', minLength: 1, maxLength: 500 } as const;
const locationSchema = { type: 'string', minLength: 1, maxLength: 50 } as const; const locationSchema = { type: 'string', minLength: 1, maxLength: 50 } as const;
const birthdaySchema = { type: 'string', pattern: /^([0-9]{4})-([0-9]{2})-([0-9]{2})$/.toString().slice(1, -1) } as const; const birthdaySchema = { type: 'string', pattern: /^([0-9]{4})-([0-9]{2})-([0-9]{2})$/.toString().slice(1, -1) } as const;
const pronounsSchema = { type: 'string', minLength: 1, maxLength: 20 } as const;
function isLocalUser(user: User): user is ILocalUser; function isLocalUser(user: User): user is ILocalUser;
function isLocalUser<T extends { host: User['host'] }>(user: T): user is T & { host: null; }; function isLocalUser<T extends { host: User['host'] }>(user: T): user is T & { host: null; };
@ -50,6 +51,7 @@ export const UserRepository = db.getRepository(User).extend({
descriptionSchema, descriptionSchema,
locationSchema, locationSchema,
birthdaySchema, birthdaySchema,
pronounsSchema,
//#region Validators //#region Validators
validateLocalUsername: ajv.compile(localUsernameSchema), validateLocalUsername: ajv.compile(localUsernameSchema),
@ -58,6 +60,7 @@ export const UserRepository = db.getRepository(User).extend({
validateDescription: ajv.compile(descriptionSchema), validateDescription: ajv.compile(descriptionSchema),
validateLocation: ajv.compile(locationSchema), validateLocation: ajv.compile(locationSchema),
validateBirthday: ajv.compile(birthdaySchema), validateBirthday: ajv.compile(birthdaySchema),
validatePronouns: ajv.compile(pronounsSchema),
//#endregion //#endregion
async getRelation(me: User['id'], target: User['id']) { async getRelation(me: User['id'], target: User['id']) {
@ -318,6 +321,7 @@ export const UserRepository = db.getRepository(User).extend({
isSilenced: user.isSilenced || falsy, isSilenced: user.isSilenced || falsy,
isSuspended: user.isSuspended || falsy, isSuspended: user.isSuspended || falsy,
description: profile!.description, description: profile!.description,
pronouns: profile!.pronouns,
location: profile!.location, location: profile!.location,
birthday: profile!.birthday, birthday: profile!.birthday,
lang: profile!.lang, lang: profile!.lang,

View file

@ -143,6 +143,11 @@ export const packedUserDetailedNotMeOnlySchema = {
nullable: true, optional: false, nullable: true, optional: false,
example: 'Hi masters, I am Ai!', example: 'Hi masters, I am Ai!',
}, },
pronouns: {
type: 'string',
nullable: true, optional: false,
example: 'They/Them',
},
location: { location: {
type: 'string', type: 'string',
nullable: true, optional: false, nullable: true, optional: false,

View file

@ -192,6 +192,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<Us
description: person.summary ? htmlToMfm(truncate(person.summary, summaryLength), person.tag) : null, description: person.summary ? htmlToMfm(truncate(person.summary, summaryLength), person.tag) : null,
url: getOneApHrefNullable(person.url), url: getOneApHrefNullable(person.url),
fields, fields,
pronouns: person['vcard:Nickname'] || null,
birthday: bday ? bday[0] : null, birthday: bday ? bday[0] : null,
location: person['vcard:Address'] || null, location: person['vcard:Address'] || null,
userHost: host, userHost: host,
@ -368,6 +369,7 @@ export async function updatePerson(uri: string, resolver?: Resolver | null, hint
url: getOneApHrefNullable(person.url), url: getOneApHrefNullable(person.url),
fields, fields,
description: person.summary ? htmlToMfm(truncate(person.summary, summaryLength), person.tag) : null, description: person.summary ? htmlToMfm(truncate(person.summary, summaryLength), person.tag) : null,
pronouns: person['vcard:Nickname'] || null,
birthday: bday ? bday[0] : null, birthday: bday ? bday[0] : null,
location: person['vcard:Address'] || null, location: person['vcard:Address'] || null,
}); });

View file

@ -77,6 +77,10 @@ export async function renderPerson(user: ILocalUser) {
attachment: attachment.length ? attachment : undefined, attachment: attachment.length ? attachment : undefined,
} as any; } as any;
if (profile?.pronouns) {
person['vcard:Nickname'] = profile.pronouns;
}
if (profile?.birthday) { if (profile?.birthday) {
person['vcard:bday'] = profile.birthday; person['vcard:bday'] = profile.birthday;
} }

View file

@ -164,6 +164,7 @@ export interface IActor extends IObject {
endpoints?: { endpoints?: {
sharedInbox?: string; sharedInbox?: string;
}; };
'vcard:Nickname'?: string;
'vcard:bday'?: string; 'vcard:bday'?: string;
'vcard:Address'?: string; 'vcard:Address'?: string;
} }

View file

@ -72,6 +72,7 @@ export const paramDef = {
properties: { properties: {
name: { ...Users.nameSchema, nullable: true }, name: { ...Users.nameSchema, nullable: true },
description: { ...Users.descriptionSchema, nullable: true }, description: { ...Users.descriptionSchema, nullable: true },
pronouns: { ...Users.pronounsSchema, nullable: true },
location: { ...Users.locationSchema, nullable: true }, location: { ...Users.locationSchema, nullable: true },
birthday: { ...Users.birthdaySchema, nullable: true }, birthday: { ...Users.birthdaySchema, nullable: true },
lang: { type: 'string', enum: [null, ...Object.keys(langmap)], nullable: true }, lang: { type: 'string', enum: [null, ...Object.keys(langmap)], nullable: true },
@ -132,6 +133,7 @@ export default define(meta, paramDef, async (ps, _user, token) => {
if (ps.name !== undefined) updates.name = ps.name; if (ps.name !== undefined) updates.name = ps.name;
if (ps.description !== undefined) profileUpdates.description = ps.description; if (ps.description !== undefined) profileUpdates.description = ps.description;
if (ps.lang !== undefined) profileUpdates.lang = ps.lang; if (ps.lang !== undefined) profileUpdates.lang = ps.lang;
if (ps.pronouns !== undefined) profileUpdates.pronouns = ps.pronouns;
if (ps.location !== undefined) profileUpdates.location = ps.location; if (ps.location !== undefined) profileUpdates.location = ps.location;
if (ps.birthday !== undefined) profileUpdates.birthday = ps.birthday; if (ps.birthday !== undefined) profileUpdates.birthday = ps.birthday;
if (ps.ffVisibility !== undefined) profileUpdates.ffVisibility = ps.ffVisibility; if (ps.ffVisibility !== undefined) profileUpdates.ffVisibility = ps.ffVisibility;

View file

@ -17,6 +17,11 @@
<template #caption>{{ i18n.ts._profile.youCanIncludeHashtags }}</template> <template #caption>{{ i18n.ts._profile.youCanIncludeHashtags }}</template>
</FormTextarea> </FormTextarea>
<FormInput v-model="profile.pronouns" manual-save class="_formBlock">
<template #label>{{ i18n.ts.pronouns }}</template>
<template #prefix><i class="fas fa-heart"></i></template>
</FormInput>
<FormInput v-model="profile.location" manual-save class="_formBlock"> <FormInput v-model="profile.location" manual-save class="_formBlock">
<template #label>{{ i18n.ts.location }}</template> <template #label>{{ i18n.ts.location }}</template>
<template #prefix><i class="fas fa-map-marker-alt"></i></template> <template #prefix><i class="fas fa-map-marker-alt"></i></template>
@ -82,6 +87,7 @@ import { langmap } from '@/scripts/langmap';
const profile = reactive({ const profile = reactive({
name: $i.name, name: $i.name,
description: $i.description, description: $i.description,
pronouns: $i.pronouns,
location: $i.location, location: $i.location,
birthday: $i.birthday, birthday: $i.birthday,
lang: $i.lang, lang: $i.lang,
@ -120,6 +126,7 @@ function save() {
os.apiWithDialog('i/update', { os.apiWithDialog('i/update', {
name: profile.name || null, name: profile.name || null,
description: profile.description || null, description: profile.description || null,
pronouns: profile.pronouns || null,
location: profile.location || null, location: profile.location || null,
birthday: profile.birthday || null, birthday: profile.birthday || null,
lang: profile.lang || null, lang: profile.lang || null,

View file

@ -47,6 +47,10 @@
<p v-else class="empty">{{ $ts.noAccountDescription }}</p> <p v-else class="empty">{{ $ts.noAccountDescription }}</p>
</div> </div>
<div class="fields system"> <div class="fields system">
<dl v-if="user.pronouns" class="field">
<dt class="name"><i class="fas fa-heart fa-fw"></i> {{ $ts.pronouns }}</dt>
<dd class="value">{{ user.pronouns }}</dd>
</dl>
<dl v-if="user.location" class="field"> <dl v-if="user.location" class="field">
<dt class="name"><i class="fas fa-map-marker fa-fw"></i> {{ $ts.location }}</dt> <dt class="name"><i class="fas fa-map-marker fa-fw"></i> {{ $ts.location }}</dt>
<dd class="value">{{ user.location }}</dd> <dd class="value">{{ user.location }}</dd>