Mastodonのリンクの所有者認証に対応 (#5161)

* Profile metadata を設定できるように

* API desc
This commit is contained in:
MeiMei 2019-07-18 00:11:39 +09:00 committed by syuilo
parent f1a7ab639b
commit ef44eda69e
8 changed files with 92 additions and 4 deletions

View file

@ -804,6 +804,9 @@ common/views/components/profile-editor.vue:
danger-zone: "危険な設定" danger-zone: "危険な設定"
delete-account: "アカウントを削除" delete-account: "アカウントを削除"
account-deleted: "アカウントが削除されました。データが消えるまで時間がかかる場合があります。" account-deleted: "アカウントが削除されました。データが消えるまで時間がかかる場合があります。"
profile-metadata: "プロフィール補足情報"
metadata-label: "ラベル"
metadata-content: "内容"
common/views/components/user-list-editor.vue: common/views/components/user-list-editor.vue:
users: "ユーザー" users: "ユーザー"

View file

@ -51,6 +51,26 @@
<template #desc v-if="bannerUploading">{{ $t('uploading') }}<mk-ellipsis/></template> <template #desc v-if="bannerUploading">{{ $t('uploading') }}<mk-ellipsis/></template>
</ui-input> </ui-input>
<div class="fields">
<header>{{ $t('profile-metadata') }}</header>
<ui-horizon-group>
<ui-input v-model="fieldName0">{{ $t('metadata-label') }}</ui-input>
<ui-input v-model="fieldValue0">{{ $t('metadata-content') }}</ui-input>
</ui-horizon-group>
<ui-horizon-group>
<ui-input v-model="fieldName1">{{ $t('metadata-label') }}</ui-input>
<ui-input v-model="fieldValue1">{{ $t('metadata-content') }}</ui-input>
</ui-horizon-group>
<ui-horizon-group>
<ui-input v-model="fieldName2">{{ $t('metadata-label') }}</ui-input>
<ui-input v-model="fieldValue2">{{ $t('metadata-content') }}</ui-input>
</ui-horizon-group>
<ui-horizon-group>
<ui-input v-model="fieldName3">{{ $t('metadata-label') }}</ui-input>
<ui-input v-model="fieldValue3">{{ $t('metadata-content') }}</ui-input>
</ui-horizon-group>
</div>
<ui-button @click="save(true)"><fa :icon="faSave"/> {{ $t('save') }}</ui-button> <ui-button @click="save(true)"><fa :icon="faSave"/> {{ $t('save') }}</ui-button>
</ui-form> </ui-form>
</section> </section>
@ -189,6 +209,17 @@ export default Vue.extend({
this.isLocked = this.$store.state.i.isLocked; this.isLocked = this.$store.state.i.isLocked;
this.carefulBot = this.$store.state.i.carefulBot; this.carefulBot = this.$store.state.i.carefulBot;
this.autoAcceptFollowed = this.$store.state.i.autoAcceptFollowed; this.autoAcceptFollowed = this.$store.state.i.autoAcceptFollowed;
if (this.$store.state.i.fields) {
this.fieldName0 = this.$store.state.i.fields[0].name;
this.fieldValue0 = this.$store.state.i.fields[0].value;
this.fieldName1 = this.$store.state.i.fields[1].name;
this.fieldValue1 = this.$store.state.i.fields[1].value;
this.fieldName2 = this.$store.state.i.fields[2].name;
this.fieldValue2 = this.$store.state.i.fields[2].value;
this.fieldName3 = this.$store.state.i.fields[3].name;
this.fieldValue3 = this.$store.state.i.fields[3].value;
}
}, },
methods: { methods: {
@ -237,6 +268,13 @@ export default Vue.extend({
}, },
save(notify) { save(notify) {
const fields = [
{ name: this.fieldName0, value: this.fieldValue0 },
{ name: this.fieldName1, value: this.fieldValue1 },
{ name: this.fieldName2, value: this.fieldValue2 },
{ name: this.fieldName3, value: this.fieldValue3 },
];
this.saving = true; this.saving = true;
this.$root.api('i/update', { this.$root.api('i/update', {
@ -247,6 +285,7 @@ export default Vue.extend({
birthday: this.birthday || null, birthday: this.birthday || null,
avatarId: this.avatarId || undefined, avatarId: this.avatarId || undefined,
bannerId: this.bannerId || undefined, bannerId: this.bannerId || undefined,
fields,
isCat: !!this.isCat, isCat: !!this.isCat,
isBot: !!this.isBot, isBot: !!this.isBot,
isLocked: !!this.isLocked, isLocked: !!this.isLocked,
@ -389,4 +428,11 @@ export default Vue.extend({
height 72px height 72px
margin auto margin auto
.fields
> header
padding 8px 0px
font-weight bold
> div
padding-left 16px
</style> </style>

View file

@ -148,6 +148,7 @@ export class UserRepository extends Repository<User> {
description: profile!.description, description: profile!.description,
location: profile!.location, location: profile!.location,
birthday: profile!.birthday, birthday: profile!.birthday,
fields: profile!.fields,
followersCount: user.followersCount, followersCount: user.followersCount,
followingCount: user.followingCount, followingCount: user.followingCount,
notesCount: user.notesCount, notesCount: user.notesCount,

View file

@ -21,13 +21,24 @@ export async function renderPerson(user: ILocalUser) {
]); ]);
const attachment: { const attachment: {
type: string, type: 'PropertyValue',
name: string, name: string,
value: string, value: string,
verified_at?: string,
identifier?: IIdentifier identifier?: IIdentifier
}[] = []; }[] = [];
if (profile.fields) {
for (const field of profile.fields) {
attachment.push({
type: 'PropertyValue',
name: field.name,
value: (field.value != null && field.value.match(/^https?:/))
? `<a href="${new URL(field.value).href}" rel="me nofollow noopener" target="_blank">${new URL(field.value).href}</a>`
: field.value
});
}
}
if (profile.twitter) { if (profile.twitter) {
attachment.push({ attachment.push({
type: 'PropertyValue', type: 'PropertyValue',

View file

@ -77,6 +77,13 @@ export const meta = {
} }
}, },
fields: {
validator: $.optional.arr($.object()).range(1, 4),
desc: {
'ja-JP': 'プロフィール補足情報'
}
},
isLocked: { isLocked: {
validator: $.optional.bool, validator: $.optional.bool,
desc: { desc: {
@ -226,6 +233,14 @@ export default define(meta, async (ps, user, app) => {
profileUpdates.pinnedPageId = null; profileUpdates.pinnedPageId = null;
} }
if (ps.fields) {
profileUpdates.fields = ps.fields
.filter(x => typeof x.name === 'string' && x.name !== '' && typeof x.value === 'string' && x.value !== '')
.map(x => {
return { name: x.name, value: x.value };
});
}
//#region emojis/tags //#region emojis/tags
let emojis = [] as string[]; let emojis = [] as string[];

View file

@ -156,11 +156,17 @@ router.get('/@:user', async (ctx, next) => {
if (user != null) { if (user != null) {
const profile = await UserProfiles.findOne(user.id).then(ensure); const profile = await UserProfiles.findOne(user.id).then(ensure);
const meta = await fetchMeta(); const meta = await fetchMeta();
const me = profile.fields
? profile.fields
.filter(filed => filed.value != null && filed.value.match(/^https?:/))
.map(field => field.value)
: [];
await ctx.render('user', { await ctx.render('user', {
user, profile, user, profile, me,
instanceName: meta.name || 'Misskey' instanceName: meta.name || 'Misskey'
}); });
ctx.set('Cache-Control', 'public, max-age=180'); ctx.set('Cache-Control', 'public, max-age=30');
} else { } else {
// リモートユーザーなので // リモートユーザーなので
await next(); await next();

View file

@ -44,3 +44,4 @@ html
<svg viewBox="0 0 50 50"> <svg viewBox="0 0 50 50">
<path fill=#fb4e4e d="M25.251,6.461c-10.318,0-18.683,8.365-18.683,18.683h4.068c0-8.071,6.543-14.615,14.615-14.615V6.461z" /> <path fill=#fb4e4e d="M25.251,6.461c-10.318,0-18.683,8.365-18.683,18.683h4.068c0-8.071,6.543-14.615,14.615-14.615V6.461z" />
</svg> </svg>
block content

View file

@ -36,3 +36,8 @@ block meta
link(rel='alternate' href=user.uri type='application/activity+json') link(rel='alternate' href=user.uri type='application/activity+json')
if profile.url if profile.url
link(rel='alternate' href=profile.url type='text/html') link(rel='alternate' href=profile.url type='text/html')
block content
div#me
each m in me
a(rel='me' href=`${m}`) #{m}