mirror of
https://git.joinsharkey.org/Sharkey/Sharkey.git
synced 2024-12-23 15:53:09 +02:00
Some bug fixes
This commit is contained in:
parent
a1b490afa7
commit
a02ee3a08b
81 changed files with 337 additions and 1318 deletions
|
@ -56,14 +56,14 @@ export default function<T extends object>(data: {
|
|||
id: this.id,
|
||||
data: newProps
|
||||
}).then(() => {
|
||||
(this as any).os.i.account.clientSettings.mobileHome.find(w => w.id == this.id).data = newProps;
|
||||
(this as any).os.i.clientSettings.mobileHome.find(w => w.id == this.id).data = newProps;
|
||||
});
|
||||
} else {
|
||||
(this as any).api('i/update_home', {
|
||||
id: this.id,
|
||||
data: newProps
|
||||
}).then(() => {
|
||||
(this as any).os.i.account.clientSettings.home.find(w => w.id == this.id).data = newProps;
|
||||
(this as any).os.i.clientSettings.home.find(w => w.id == this.id).data = newProps;
|
||||
});
|
||||
}
|
||||
}, {
|
||||
|
|
|
@ -270,7 +270,7 @@ export default class MiOS extends EventEmitter {
|
|||
// Parse response
|
||||
res.json().then(i => {
|
||||
me = i;
|
||||
me.account.token = token;
|
||||
me.token = token;
|
||||
done();
|
||||
});
|
||||
})
|
||||
|
@ -294,12 +294,12 @@ export default class MiOS extends EventEmitter {
|
|||
const fetched = me => {
|
||||
if (me) {
|
||||
// デフォルトの設定をマージ
|
||||
me.account.clientSettings = Object.assign({
|
||||
me.clientSettings = Object.assign({
|
||||
fetchOnScroll: true,
|
||||
showMaps: true,
|
||||
showPostFormOnTopOfTl: false,
|
||||
gradientWindowHeader: false
|
||||
}, me.account.clientSettings);
|
||||
}, me.clientSettings);
|
||||
|
||||
// ローカルストレージにキャッシュ
|
||||
localStorage.setItem('me', JSON.stringify(me));
|
||||
|
@ -329,7 +329,7 @@ export default class MiOS extends EventEmitter {
|
|||
fetched(cachedMe);
|
||||
|
||||
// 後から新鮮なデータをフェッチ
|
||||
fetchme(cachedMe.account.token, freshData => {
|
||||
fetchme(cachedMe.token, freshData => {
|
||||
merge(cachedMe, freshData);
|
||||
});
|
||||
} else {
|
||||
|
@ -437,7 +437,7 @@ export default class MiOS extends EventEmitter {
|
|||
}
|
||||
|
||||
// Append a credential
|
||||
if (this.isSignedIn) (data as any).i = this.i.account.token;
|
||||
if (this.isSignedIn) (data as any).i = this.i.token;
|
||||
|
||||
// TODO
|
||||
//const viaStream = localStorage.getItem('enableExperimental') == 'true';
|
||||
|
|
|
@ -8,7 +8,7 @@ import MiOS from '../../mios';
|
|||
export class DriveStream extends Stream {
|
||||
constructor(os: MiOS, me) {
|
||||
super(os, 'drive', {
|
||||
i: me.account.token
|
||||
i: me.token
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,13 +10,13 @@ import MiOS from '../../mios';
|
|||
export class HomeStream extends Stream {
|
||||
constructor(os: MiOS, me) {
|
||||
super(os, '', {
|
||||
i: me.account.token
|
||||
i: me.token
|
||||
});
|
||||
|
||||
// 最終利用日時を更新するため定期的にaliveメッセージを送信
|
||||
setInterval(() => {
|
||||
this.send({ type: 'alive' });
|
||||
me.account.lastUsedAt = new Date();
|
||||
me.lastUsedAt = new Date();
|
||||
}, 1000 * 60);
|
||||
|
||||
// 自分の情報が更新されたとき
|
||||
|
|
|
@ -8,7 +8,7 @@ import MiOS from '../../mios';
|
|||
export class MessagingIndexStream extends Stream {
|
||||
constructor(os: MiOS, me) {
|
||||
super(os, 'messaging-index', {
|
||||
i: me.account.token
|
||||
i: me.token
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,13 +7,13 @@ import MiOS from '../../mios';
|
|||
export class MessagingStream extends Stream {
|
||||
constructor(os: MiOS, me, otherparty) {
|
||||
super(os, 'messaging', {
|
||||
i: me.account.token,
|
||||
i: me.token,
|
||||
otherparty
|
||||
});
|
||||
|
||||
(this as any).on('_connected_', () => {
|
||||
this.send({
|
||||
i: me.account.token
|
||||
i: me.token
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import MiOS from '../../mios';
|
|||
export class OthelloGameStream extends Stream {
|
||||
constructor(os: MiOS, me, game) {
|
||||
super(os, 'othello-game', {
|
||||
i: me ? me.account.token : null,
|
||||
i: me ? me.token : null,
|
||||
game: game.id
|
||||
});
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import MiOS from '../../mios';
|
|||
export class OthelloStream extends Stream {
|
||||
constructor(os: MiOS, me) {
|
||||
super(os, 'othello', {
|
||||
i: me.account.token
|
||||
i: me.token
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<label class="password">
|
||||
<input v-model="password" type="password" placeholder="%i18n:common.tags.mk-signin.password%" required/>%fa:lock%
|
||||
</label>
|
||||
<label class="token" v-if="user && user.account.twoFactorEnabled">
|
||||
<label class="token" v-if="user && user.twoFactorEnabled">
|
||||
<input v-model="token" type="number" placeholder="%i18n:common.tags.mk-signin.token%" required/>%fa:lock%
|
||||
</label>
|
||||
<button type="submit" :disabled="signing">{{ signing ? '%i18n:common.tags.mk-signin.signing-in%' : '%i18n:common.tags.mk-signin.signin%' }}</button>
|
||||
|
@ -43,7 +43,7 @@ export default Vue.extend({
|
|||
(this as any).api('signin', {
|
||||
username: this.username,
|
||||
password: this.password,
|
||||
token: this.user && this.user.account.twoFactorEnabled ? this.token : undefined
|
||||
token: this.user && this.user.twoFactorEnabled ? this.token : undefined
|
||||
}).then(() => {
|
||||
location.reload();
|
||||
}).catch(() => {
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
<template>
|
||||
<div class="mk-twitter-setting">
|
||||
<p>%i18n:common.tags.mk-twitter-setting.description%<a :href="`${docsUrl}/link-to-twitter`" target="_blank">%i18n:common.tags.mk-twitter-setting.detail%</a></p>
|
||||
<p class="account" v-if="os.i.account.twitter" :title="`Twitter ID: ${os.i.account.twitter.userId}`">%i18n:common.tags.mk-twitter-setting.connected-to%: <a :href="`https://twitter.com/${os.i.account.twitter.screenName}`" target="_blank">@{{ os.i.account.twitter.screenName }}</a></p>
|
||||
<p class="account" v-if="os.i.twitter" :title="`Twitter ID: ${os.i.twitter.userId}`">%i18n:common.tags.mk-twitter-setting.connected-to%: <a :href="`https://twitter.com/${os.i.twitter.screenName}`" target="_blank">@{{ os.i.twitter.screenName }}</a></p>
|
||||
<p>
|
||||
<a :href="`${apiUrl}/connect/twitter`" target="_blank" @click.prevent="connect">{{ os.i.account.twitter ? '%i18n:common.tags.mk-twitter-setting.reconnect%' : '%i18n:common.tags.mk-twitter-setting.connect%' }}</a>
|
||||
<span v-if="os.i.account.twitter"> or </span>
|
||||
<a :href="`${apiUrl}/disconnect/twitter`" target="_blank" v-if="os.i.account.twitter" @click.prevent="disconnect">%i18n:common.tags.mk-twitter-setting.disconnect%</a>
|
||||
<a :href="`${apiUrl}/connect/twitter`" target="_blank" @click.prevent="connect">{{ os.i.twitter ? '%i18n:common.tags.mk-twitter-setting.reconnect%' : '%i18n:common.tags.mk-twitter-setting.connect%' }}</a>
|
||||
<span v-if="os.i.twitter"> or </span>
|
||||
<a :href="`${apiUrl}/disconnect/twitter`" target="_blank" v-if="os.i.twitter" @click.prevent="disconnect">%i18n:common.tags.mk-twitter-setting.disconnect%</a>
|
||||
</p>
|
||||
<p class="id" v-if="os.i.account.twitter">Twitter ID: {{ os.i.account.twitter.userId }}</p>
|
||||
<p class="id" v-if="os.i.twitter">Twitter ID: {{ os.i.twitter.userId }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -25,7 +25,7 @@ export default Vue.extend({
|
|||
},
|
||||
mounted() {
|
||||
this.$watch('os.i', () => {
|
||||
if ((this as any).os.i.account.twitter) {
|
||||
if ((this as any).os.i.twitter) {
|
||||
if (this.form) this.form.close();
|
||||
}
|
||||
}, {
|
||||
|
|
|
@ -50,7 +50,7 @@ export default Vue.extend({
|
|||
reader.readAsDataURL(file);
|
||||
|
||||
const data = new FormData();
|
||||
data.append('i', (this as any).os.i.account.token);
|
||||
data.append('i', (this as any).os.i.token);
|
||||
data.append('file', file);
|
||||
|
||||
if (folder) data.append('folderId', folder);
|
||||
|
|
|
@ -16,7 +16,7 @@ export default (os: OS) => (cb, file = null) => {
|
|||
|
||||
w.$once('cropped', blob => {
|
||||
const data = new FormData();
|
||||
data.append('i', os.i.account.token);
|
||||
data.append('i', os.i.token);
|
||||
data.append('file', blob, file.name + '.cropped.png');
|
||||
|
||||
os.api('drive/folders/find', {
|
||||
|
|
|
@ -16,7 +16,7 @@ export default (os: OS) => (cb, file = null) => {
|
|||
|
||||
w.$once('cropped', blob => {
|
||||
const data = new FormData();
|
||||
data.append('i', os.i.account.token);
|
||||
data.append('i', os.i.token);
|
||||
data.append('file', blob, file.name + '.cropped.png');
|
||||
|
||||
os.api('drive/folders/find', {
|
||||
|
|
|
@ -53,7 +53,7 @@
|
|||
<div class="main">
|
||||
<a @click="hint">カスタマイズのヒント</a>
|
||||
<div>
|
||||
<mk-post-form v-if="os.i.account.clientSettings.showPostFormOnTopOfTl"/>
|
||||
<mk-post-form v-if="os.i.clientSettings.showPostFormOnTopOfTl"/>
|
||||
<mk-timeline ref="tl" @loaded="onTlLoaded"/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -63,7 +63,7 @@
|
|||
<component v-for="widget in widgets[place]" :is="`mkw-${widget.name}`" :key="widget.id" :ref="widget.id" :widget="widget" @chosen="warp"/>
|
||||
</div>
|
||||
<div class="main">
|
||||
<mk-post-form v-if="os.i.account.clientSettings.showPostFormOnTopOfTl"/>
|
||||
<mk-post-form v-if="os.i.clientSettings.showPostFormOnTopOfTl"/>
|
||||
<mk-timeline ref="tl" @loaded="onTlLoaded" v-if="mode == 'timeline'"/>
|
||||
<mk-mentions @loaded="onTlLoaded" v-if="mode == 'mentions'"/>
|
||||
</div>
|
||||
|
@ -82,7 +82,10 @@ export default Vue.extend({
|
|||
XDraggable
|
||||
},
|
||||
props: {
|
||||
customize: Boolean,
|
||||
customize: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'timeline'
|
||||
|
@ -104,16 +107,16 @@ export default Vue.extend({
|
|||
home: {
|
||||
get(): any[] {
|
||||
//#region 互換性のため
|
||||
(this as any).os.i.account.clientSettings.home.forEach(w => {
|
||||
(this as any).os.i.clientSettings.home.forEach(w => {
|
||||
if (w.name == 'rss-reader') w.name = 'rss';
|
||||
if (w.name == 'user-recommendation') w.name = 'users';
|
||||
if (w.name == 'recommended-polls') w.name = 'polls';
|
||||
});
|
||||
//#endregion
|
||||
return (this as any).os.i.account.clientSettings.home;
|
||||
return (this as any).os.i.clientSettings.home;
|
||||
},
|
||||
set(value) {
|
||||
(this as any).os.i.account.clientSettings.home = value;
|
||||
(this as any).os.i.clientSettings.home = value;
|
||||
}
|
||||
},
|
||||
left(): any[] {
|
||||
|
@ -126,7 +129,7 @@ export default Vue.extend({
|
|||
created() {
|
||||
this.widgets.left = this.left;
|
||||
this.widgets.right = this.right;
|
||||
this.$watch('os.i.account.clientSettings', i => {
|
||||
this.$watch('os.i.clientSettings', i => {
|
||||
this.widgets.left = this.left;
|
||||
this.widgets.right = this.right;
|
||||
}, {
|
||||
|
@ -161,17 +164,17 @@ export default Vue.extend({
|
|||
},
|
||||
onHomeUpdated(data) {
|
||||
if (data.home) {
|
||||
(this as any).os.i.account.clientSettings.home = data.home;
|
||||
(this as any).os.i.clientSettings.home = data.home;
|
||||
this.widgets.left = data.home.filter(w => w.place == 'left');
|
||||
this.widgets.right = data.home.filter(w => w.place == 'right');
|
||||
} else {
|
||||
const w = (this as any).os.i.account.clientSettings.home.find(w => w.id == data.id);
|
||||
const w = (this as any).os.i.clientSettings.home.find(w => w.id == data.id);
|
||||
if (w != null) {
|
||||
w.data = data.data;
|
||||
this.$refs[w.id][0].preventSave = true;
|
||||
this.$refs[w.id][0].props = w.data;
|
||||
this.widgets.left = (this as any).os.i.account.clientSettings.home.filter(w => w.place == 'left');
|
||||
this.widgets.right = (this as any).os.i.account.clientSettings.home.filter(w => w.place == 'right');
|
||||
this.widgets.left = (this as any).os.i.clientSettings.home.filter(w => w.place == 'left');
|
||||
this.widgets.right = (this as any).os.i.clientSettings.home.filter(w => w.place == 'right');
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -115,7 +115,7 @@ export default Vue.extend({
|
|||
isRenote(): boolean {
|
||||
return (this.note.renote &&
|
||||
this.note.text == null &&
|
||||
this.note.mediaIds == null &&
|
||||
this.note.mediaIds.length == 0 &&
|
||||
this.note.poll == null);
|
||||
},
|
||||
p(): any {
|
||||
|
@ -168,7 +168,7 @@ export default Vue.extend({
|
|||
|
||||
// Draw map
|
||||
if (this.p.geo) {
|
||||
const shouldShowMap = (this as any).os.isSignedIn ? (this as any).os.i.account.clientSettings.showMaps : true;
|
||||
const shouldShowMap = (this as any).os.isSignedIn ? (this as any).os.i.clientSettings.showMaps : true;
|
||||
if (shouldShowMap) {
|
||||
(this as any).os.getGoogleMaps().then(maps => {
|
||||
const uluru = new maps.LatLng(this.p.geo.coordinates[1], this.p.geo.coordinates[0]);
|
||||
|
|
|
@ -5,25 +5,25 @@
|
|||
</div>
|
||||
<div class="renote" v-if="isRenote">
|
||||
<p>
|
||||
<router-link class="avatar-anchor" :to="`/@${acct}`" v-user-preview="note.userId">
|
||||
<router-link class="avatar-anchor" :to="`/@${getAcct(note.user)}`" v-user-preview="note.userId">
|
||||
<img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=32`" alt="avatar"/>
|
||||
</router-link>
|
||||
%fa:retweet%
|
||||
<span>{{ '%i18n:desktop.tags.mk-timeline-note.reposted-by%'.substr(0, '%i18n:desktop.tags.mk-timeline-note.reposted-by%'.indexOf('{')) }}</span>
|
||||
<a class="name" :href="`/@${acct}`" v-user-preview="note.userId">{{ getUserName(note.user) }}</a>
|
||||
<a class="name" :href="`/@${getAcct(note.user)}`" v-user-preview="note.userId">{{ getUserName(note.user) }}</a>
|
||||
<span>{{ '%i18n:desktop.tags.mk-timeline-note.reposted-by%'.substr('%i18n:desktop.tags.mk-timeline-note.reposted-by%'.indexOf('}') + 1) }}</span>
|
||||
</p>
|
||||
<mk-time :time="note.createdAt"/>
|
||||
</div>
|
||||
<article>
|
||||
<router-link class="avatar-anchor" :to="`/@${acct}`">
|
||||
<router-link class="avatar-anchor" :to="`/@${getAcct(p.user)}`">
|
||||
<img class="avatar" :src="`${p.user.avatarUrl}?thumbnail&size=64`" alt="avatar" v-user-preview="p.user.id"/>
|
||||
</router-link>
|
||||
<div class="main">
|
||||
<header>
|
||||
<router-link class="name" :to="`/@${acct}`" v-user-preview="p.user.id">{{ acct }}</router-link>
|
||||
<span class="is-bot" v-if="p.user.host === null && p.user.account.isBot">bot</span>
|
||||
<span class="username">@{{ acct }}</span>
|
||||
<router-link class="name" :to="`/@${getAcct(p.user)}`" v-user-preview="p.user.id">{{ getUserName(p) }}</router-link>
|
||||
<span class="is-bot" v-if="p.user.host === null && p.user.isBot">bot</span>
|
||||
<span class="username">@{{ getAcct(p.user) }}</span>
|
||||
<div class="info">
|
||||
<span class="app" v-if="p.app">via <b>{{ p.app.name }}</b></span>
|
||||
<span class="mobile" v-if="p.viaMobile">%fa:mobile-alt%</span>
|
||||
|
@ -117,21 +117,18 @@ export default Vue.extend({
|
|||
return {
|
||||
isDetailOpened: false,
|
||||
connection: null,
|
||||
connectionId: null
|
||||
connectionId: null,
|
||||
getAcct,
|
||||
getUserName
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
acct(): string {
|
||||
return getAcct(this.p.user);
|
||||
},
|
||||
name(): string {
|
||||
return getUserName(this.p.user);
|
||||
},
|
||||
|
||||
isRenote(): boolean {
|
||||
return (this.note.renote &&
|
||||
this.note.text == null &&
|
||||
this.note.mediaIds == null &&
|
||||
this.note.mediaIds.length == 0 &&
|
||||
this.note.poll == null);
|
||||
},
|
||||
p(): any {
|
||||
|
@ -178,7 +175,7 @@ export default Vue.extend({
|
|||
|
||||
// Draw map
|
||||
if (this.p.geo) {
|
||||
const shouldShowMap = (this as any).os.isSignedIn ? (this as any).os.i.account.clientSettings.showMaps : true;
|
||||
const shouldShowMap = (this as any).os.isSignedIn ? (this as any).os.i.clientSettings.showMaps : true;
|
||||
if (shouldShowMap) {
|
||||
(this as any).os.getGoogleMaps().then(maps => {
|
||||
const uluru = new maps.LatLng(this.p.geo.coordinates[1], this.p.geo.coordinates[0]);
|
||||
|
|
|
@ -115,7 +115,7 @@ export default Vue.extend({
|
|||
isRenote(): boolean {
|
||||
return (this.note.renote &&
|
||||
this.note.text == null &&
|
||||
this.note.mediaIds == null &&
|
||||
this.note.mediaIds.length == 0 &&
|
||||
this.note.poll == null);
|
||||
},
|
||||
p(): any {
|
||||
|
@ -168,7 +168,7 @@ export default Vue.extend({
|
|||
|
||||
// Draw map
|
||||
if (this.p.geo) {
|
||||
const shouldShowMap = (this as any).os.isSignedIn ? (this as any).os.i.account.clientSettings.showMaps : true;
|
||||
const shouldShowMap = (this as any).os.isSignedIn ? (this as any).os.i.clientSettings.showMaps : true;
|
||||
if (shouldShowMap) {
|
||||
(this as any).os.getGoogleMaps().then(maps => {
|
||||
const uluru = new maps.LatLng(this.p.geo.coordinates[1], this.p.geo.coordinates[0]);
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
<div class="main">
|
||||
<header>
|
||||
<router-link class="name" :to="`/@${acct}`" v-user-preview="p.user.id">{{ acct }}</router-link>
|
||||
<span class="is-bot" v-if="p.user.host === null && p.user.account.isBot">bot</span>
|
||||
<span class="is-bot" v-if="p.user.host === null && p.user.isBot">bot</span>
|
||||
<span class="username">@{{ acct }}</span>
|
||||
<div class="info">
|
||||
<span class="app" v-if="p.app">via <b>{{ p.app.name }}</b></span>
|
||||
|
@ -131,7 +131,7 @@ export default Vue.extend({
|
|||
isRenote(): boolean {
|
||||
return (this.note.renote &&
|
||||
this.note.text == null &&
|
||||
this.note.mediaIds == null &&
|
||||
this.note.mediaIds.length == 0 &&
|
||||
this.note.poll == null);
|
||||
},
|
||||
p(): any {
|
||||
|
@ -178,7 +178,7 @@ export default Vue.extend({
|
|||
|
||||
// Draw map
|
||||
if (this.p.geo) {
|
||||
const shouldShowMap = (this as any).os.isSignedIn ? (this as any).os.i.account.clientSettings.showMaps : true;
|
||||
const shouldShowMap = (this as any).os.isSignedIn ? (this as any).os.i.clientSettings.showMaps : true;
|
||||
if (shouldShowMap) {
|
||||
(this as any).os.getGoogleMaps().then(maps => {
|
||||
const uluru = new maps.LatLng(this.p.geo.coordinates[1], this.p.geo.coordinates[0]);
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
<div class="2fa">
|
||||
<p>%i18n:desktop.tags.mk-2fa-setting.intro%<a href="%i18n:desktop.tags.mk-2fa-setting.url%" target="_blank">%i18n:desktop.tags.mk-2fa-setting.detail%</a></p>
|
||||
<div class="ui info warn"><p>%fa:exclamation-triangle%%i18n:desktop.tags.mk-2fa-setting.caution%</p></div>
|
||||
<p v-if="!data && !os.i.account.twoFactorEnabled"><button @click="register" class="ui primary">%i18n:desktop.tags.mk-2fa-setting.register%</button></p>
|
||||
<template v-if="os.i.account.twoFactorEnabled">
|
||||
<p v-if="!data && !os.i.twoFactorEnabled"><button @click="register" class="ui primary">%i18n:desktop.tags.mk-2fa-setting.register%</button></p>
|
||||
<template v-if="os.i.twoFactorEnabled">
|
||||
<p>%i18n:desktop.tags.mk-2fa-setting.already-registered%</p>
|
||||
<button @click="unregister" class="ui">%i18n:desktop.tags.mk-2fa-setting.unregister%</button>
|
||||
</template>
|
||||
|
@ -54,7 +54,7 @@ export default Vue.extend({
|
|||
password: password
|
||||
}).then(() => {
|
||||
(this as any).apis.notify('%i18n:desktop.tags.mk-2fa-setting.unregistered%');
|
||||
(this as any).os.i.account.twoFactorEnabled = false;
|
||||
(this as any).os.i.twoFactorEnabled = false;
|
||||
});
|
||||
});
|
||||
},
|
||||
|
@ -64,7 +64,7 @@ export default Vue.extend({
|
|||
token: this.token
|
||||
}).then(() => {
|
||||
(this as any).apis.notify('%i18n:desktop.tags.mk-2fa-setting.success%');
|
||||
(this as any).os.i.account.twoFactorEnabled = true;
|
||||
(this as any).os.i.twoFactorEnabled = true;
|
||||
}).catch(() => {
|
||||
(this as any).apis.notify('%i18n:desktop.tags.mk-2fa-setting.failed%');
|
||||
});
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div class="root api">
|
||||
<p>Token: <code>{{ os.i.account.token }}</code></p>
|
||||
<p>Token: <code>{{ os.i.token }}</code></p>
|
||||
<p>%i18n:desktop.tags.mk-api-info.intro%</p>
|
||||
<div class="ui info warn"><p>%fa:exclamation-triangle%%i18n:desktop.tags.mk-api-info.caution%</p></div>
|
||||
<p>%i18n:desktop.tags.mk-api-info.regeneration-of-token%</p>
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
<button class="ui primary" @click="save">%i18n:desktop.tags.mk-profile-setting.save%</button>
|
||||
<section>
|
||||
<h2>その他</h2>
|
||||
<mk-switch v-model="os.i.account.isBot" @change="onChangeIsBot" text="このアカウントはbotです"/>
|
||||
<mk-switch v-model="os.i.isBot" @change="onChangeIsBot" text="このアカウントはbotです"/>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -43,9 +43,9 @@ export default Vue.extend({
|
|||
},
|
||||
created() {
|
||||
this.name = (this as any).os.i.name || '';
|
||||
this.location = (this as any).os.i.account.profile.location;
|
||||
this.location = (this as any).os.i.profile.location;
|
||||
this.description = (this as any).os.i.description;
|
||||
this.birthday = (this as any).os.i.account.profile.birthday;
|
||||
this.birthday = (this as any).os.i.profile.birthday;
|
||||
},
|
||||
methods: {
|
||||
updateAvatar() {
|
||||
|
@ -63,7 +63,7 @@ export default Vue.extend({
|
|||
},
|
||||
onChangeIsBot() {
|
||||
(this as any).api('i/update', {
|
||||
isBot: (this as any).os.i.account.isBot
|
||||
isBot: (this as any).os.i.isBot
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
|
||||
<section class="web" v-show="page == 'web'">
|
||||
<h1>動作</h1>
|
||||
<mk-switch v-model="os.i.account.clientSettings.fetchOnScroll" @change="onChangeFetchOnScroll" text="スクロールで自動読み込み">
|
||||
<mk-switch v-model="os.i.clientSettings.fetchOnScroll" @change="onChangeFetchOnScroll" text="スクロールで自動読み込み">
|
||||
<span>ページを下までスクロールしたときに自動で追加のコンテンツを読み込みます。</span>
|
||||
</mk-switch>
|
||||
<mk-switch v-model="autoPopout" text="ウィンドウの自動ポップアウト">
|
||||
|
@ -33,11 +33,11 @@
|
|||
<div class="div">
|
||||
<button class="ui button" @click="customizeHome">ホームをカスタマイズ</button>
|
||||
</div>
|
||||
<mk-switch v-model="os.i.account.clientSettings.showPostFormOnTopOfTl" @change="onChangeShowPostFormOnTopOfTl" text="タイムライン上部に投稿フォームを表示する"/>
|
||||
<mk-switch v-model="os.i.account.clientSettings.showMaps" @change="onChangeShowMaps" text="マップの自動展開">
|
||||
<mk-switch v-model="os.i.clientSettings.showPostFormOnTopOfTl" @change="onChangeShowPostFormOnTopOfTl" text="タイムライン上部に投稿フォームを表示する"/>
|
||||
<mk-switch v-model="os.i.clientSettings.showMaps" @change="onChangeShowMaps" text="マップの自動展開">
|
||||
<span>位置情報が添付された投稿のマップを自動的に展開します。</span>
|
||||
</mk-switch>
|
||||
<mk-switch v-model="os.i.account.clientSettings.gradientWindowHeader" @change="onChangeGradientWindowHeader" text="ウィンドウのタイトルバーにグラデーションを使用"/>
|
||||
<mk-switch v-model="os.i.clientSettings.gradientWindowHeader" @change="onChangeGradientWindowHeader" text="ウィンドウのタイトルバーにグラデーションを使用"/>
|
||||
</section>
|
||||
|
||||
<section class="web" v-show="page == 'web'">
|
||||
|
@ -57,7 +57,7 @@
|
|||
|
||||
<section class="web" v-show="page == 'web'">
|
||||
<h1>モバイル</h1>
|
||||
<mk-switch v-model="os.i.account.clientSettings.disableViaMobile" @change="onChangeDisableViaMobile" text="「モバイルからの投稿」フラグを付けない"/>
|
||||
<mk-switch v-model="os.i.clientSettings.disableViaMobile" @change="onChangeDisableViaMobile" text="「モバイルからの投稿」フラグを付けない"/>
|
||||
</section>
|
||||
|
||||
<section class="web" v-show="page == 'web'">
|
||||
|
@ -86,7 +86,7 @@
|
|||
|
||||
<section class="notification" v-show="page == 'notification'">
|
||||
<h1>通知</h1>
|
||||
<mk-switch v-model="os.i.account.settings.autoWatch" @change="onChangeAutoWatch" text="投稿の自動ウォッチ">
|
||||
<mk-switch v-model="os.i.settings.autoWatch" @change="onChangeAutoWatch" text="投稿の自動ウォッチ">
|
||||
<span>リアクションしたり返信したりした投稿に関する通知を自動的に受け取るようにします。</span>
|
||||
</mk-switch>
|
||||
</section>
|
||||
|
|
|
@ -107,7 +107,7 @@ export default Vue.extend({
|
|||
this.fetch();
|
||||
},
|
||||
onScroll() {
|
||||
if ((this as any).os.i.account.clientSettings.fetchOnScroll !== false) {
|
||||
if ((this as any).os.i.clientSettings.fetchOnScroll !== false) {
|
||||
const current = window.scrollY + window.innerHeight;
|
||||
if (current > document.body.offsetHeight - 8) this.more();
|
||||
}
|
||||
|
|
|
@ -37,8 +37,8 @@ import getUserName from '../../../../../renderers/get-user-name';
|
|||
|
||||
export default Vue.extend({
|
||||
computed: {
|
||||
name() {
|
||||
return getUserName(this.os.i);
|
||||
name(): string {
|
||||
return getUserName((this as any).os.i);
|
||||
}
|
||||
},
|
||||
components: {
|
||||
|
@ -51,9 +51,9 @@ export default Vue.extend({
|
|||
},
|
||||
mounted() {
|
||||
if ((this as any).os.isSignedIn) {
|
||||
const ago = (new Date().getTime() - new Date((this as any).os.i.account.lastUsedAt).getTime()) / 1000
|
||||
const ago = (new Date().getTime() - new Date((this as any).os.i.lastUsedAt).getTime()) / 1000
|
||||
const isHisasiburi = ago >= 3600;
|
||||
(this as any).os.i.account.lastUsedAt = new Date();
|
||||
(this as any).os.i.lastUsedAt = new Date();
|
||||
if (isHisasiburi) {
|
||||
(this.$refs.welcomeback as any).style.display = 'block';
|
||||
(this.$refs.main as any).style.overflow = 'hidden';
|
||||
|
|
|
@ -24,8 +24,8 @@ export default Vue.extend({
|
|||
computed: {
|
||||
withGradient(): boolean {
|
||||
return (this as any).os.isSignedIn
|
||||
? (this as any).os.i.account.clientSettings.gradientWindowHeader != null
|
||||
? (this as any).os.i.account.clientSettings.gradientWindowHeader
|
||||
? (this as any).os.i.clientSettings.gradientWindowHeader != null
|
||||
? (this as any).os.i.clientSettings.gradientWindowHeader
|
||||
: false
|
||||
: false;
|
||||
}
|
||||
|
|
|
@ -92,8 +92,8 @@ export default Vue.extend({
|
|||
},
|
||||
withGradient(): boolean {
|
||||
return (this as any).os.isSignedIn
|
||||
? (this as any).os.i.account.clientSettings.gradientWindowHeader != null
|
||||
? (this as any).os.i.account.clientSettings.gradientWindowHeader
|
||||
? (this as any).os.i.clientSettings.gradientWindowHeader != null
|
||||
? (this as any).os.i.clientSettings.gradientWindowHeader
|
||||
: false
|
||||
: false;
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
<div class="title">
|
||||
<p class="name">{{ name }}</p>
|
||||
<p class="username">@{{ acct }}</p>
|
||||
<p class="location" v-if="user.host === null && user.account.profile.location">%fa:map-marker%{{ user.account.profile.location }}</p>
|
||||
<p class="location" v-if="user.host === null && user.profile.location">%fa:map-marker%{{ user.profile.location }}</p>
|
||||
</div>
|
||||
<footer>
|
||||
<router-link :to="`/@${acct}`" :data-active="$parent.page == 'home'">%fa:home%概要</router-link>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<x-profile :user="user"/>
|
||||
<x-photos :user="user"/>
|
||||
<x-followers-you-know v-if="os.isSignedIn && os.i.id != user.id" :user="user"/>
|
||||
<p v-if="user.host === null">%i18n:desktop.tags.mk-user.last-used-at%: <b><mk-time :time="user.account.lastUsedAt"/></b></p>
|
||||
<p v-if="user.host === null">%i18n:desktop.tags.mk-user.last-used-at%: <b><mk-time :time="user.lastUsedAt"/></b></p>
|
||||
</div>
|
||||
</div>
|
||||
<main>
|
||||
|
|
|
@ -7,11 +7,11 @@
|
|||
<p v-if="!user.isMuted"><a @click="mute">%i18n:desktop.tags.mk-user.mute%</a></p>
|
||||
</div>
|
||||
<div class="description" v-if="user.description">{{ user.description }}</div>
|
||||
<div class="birthday" v-if="user.host === null && user.account.profile.birthday">
|
||||
<p>%fa:birthday-cake%{{ user.account.profile.birthday.replace('-', '年').replace('-', '月') + '日' }} ({{ age }}歳)</p>
|
||||
<div class="birthday" v-if="user.host === null && user.profile.birthday">
|
||||
<p>%fa:birthday-cake%{{ user.profile.birthday.replace('-', '年').replace('-', '月') + '日' }} ({{ age }}歳)</p>
|
||||
</div>
|
||||
<div class="twitter" v-if="user.host === null && user.account.twitter">
|
||||
<p>%fa:B twitter%<a :href="`https://twitter.com/${user.account.twitter.screenName}`" target="_blank">@{{ user.account.twitter.screenName }}</a></p>
|
||||
<div class="twitter" v-if="user.host === null && user.twitter">
|
||||
<p>%fa:B twitter%<a :href="`https://twitter.com/${user.twitter.screenName}`" target="_blank">@{{ user.twitter.screenName }}</a></p>
|
||||
</div>
|
||||
<div class="status">
|
||||
<p class="notes-count">%fa:angle-right%<a>{{ user.notesCount }}</a><b>投稿</b></p>
|
||||
|
@ -31,7 +31,7 @@ export default Vue.extend({
|
|||
props: ['user'],
|
||||
computed: {
|
||||
age(): number {
|
||||
return age(this.user.account.profile.birthday);
|
||||
return age(this.user.profile.birthday);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
|
|
@ -127,7 +127,7 @@ export default Vue.extend({
|
|||
isRenote(): boolean {
|
||||
return (this.note.renote &&
|
||||
this.note.text == null &&
|
||||
this.note.mediaIds == null &&
|
||||
this.note.mediaIds.length == 0 &&
|
||||
this.note.poll == null);
|
||||
},
|
||||
p(): any {
|
||||
|
@ -165,7 +165,7 @@ export default Vue.extend({
|
|||
|
||||
// Draw map
|
||||
if (this.p.geo) {
|
||||
const shouldShowMap = (this as any).os.isSignedIn ? (this as any).os.i.account.clientSettings.showMaps : true;
|
||||
const shouldShowMap = (this as any).os.isSignedIn ? (this as any).os.i.clientSettings.showMaps : true;
|
||||
if (shouldShowMap) {
|
||||
(this as any).os.getGoogleMaps().then(maps => {
|
||||
const uluru = new maps.LatLng(this.p.geo.coordinates[1], this.p.geo.coordinates[0]);
|
||||
|
|
|
@ -5,25 +5,25 @@
|
|||
</div>
|
||||
<div class="renote" v-if="isRenote">
|
||||
<p>
|
||||
<router-link class="avatar-anchor" :to="`/@${acct}`">
|
||||
<router-link class="avatar-anchor" :to="`/@${getAcct(note.user)}`">
|
||||
<img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
|
||||
</router-link>
|
||||
%fa:retweet%
|
||||
<span>{{ '%i18n:mobile.tags.mk-timeline-note.reposted-by%'.substr(0, '%i18n:mobile.tags.mk-timeline-note.reposted-by%'.indexOf('{')) }}</span>
|
||||
<router-link class="name" :to="`/@${acct}`">{{ name }}</router-link>
|
||||
<router-link class="name" :to="`/@${getAcct(note.user)}`">{{ getUserName(note.user) }}</router-link>
|
||||
<span>{{ '%i18n:mobile.tags.mk-timeline-note.reposted-by%'.substr('%i18n:mobile.tags.mk-timeline-note.reposted-by%'.indexOf('}') + 1) }}</span>
|
||||
</p>
|
||||
<mk-time :time="note.createdAt"/>
|
||||
</div>
|
||||
<article>
|
||||
<router-link class="avatar-anchor" :to="`/@${pAcct}`">
|
||||
<router-link class="avatar-anchor" :to="`/@${getAcct(p.user)}`">
|
||||
<img class="avatar" :src="`${p.user.avatarUrl}?thumbnail&size=96`" alt="avatar"/>
|
||||
</router-link>
|
||||
<div class="main">
|
||||
<header>
|
||||
<router-link class="name" :to="`/@${pAcct}`">{{ pName }}</router-link>
|
||||
<span class="is-bot" v-if="p.user.host === null && p.user.account.isBot">bot</span>
|
||||
<span class="username">@{{ pAcct }}</span>
|
||||
<router-link class="name" :to="`/@${getAcct(p.user)}`">{{ getUserName(p.user) }}</router-link>
|
||||
<span class="is-bot" v-if="p.user.host === null && p.user.isBot">bot</span>
|
||||
<span class="username">@{{ getAcct(p.user) }}</span>
|
||||
<div class="info">
|
||||
<span class="mobile" v-if="p.viaMobile">%fa:mobile-alt%</span>
|
||||
<router-link class="created-at" :to="url">
|
||||
|
@ -95,27 +95,17 @@ export default Vue.extend({
|
|||
data() {
|
||||
return {
|
||||
connection: null,
|
||||
connectionId: null
|
||||
connectionId: null,
|
||||
getAcct,
|
||||
getUserName
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
acct(): string {
|
||||
return getAcct(this.note.user);
|
||||
},
|
||||
name(): string {
|
||||
return getUserName(this.note.user);
|
||||
},
|
||||
pAcct(): string {
|
||||
return getAcct(this.p.user);
|
||||
},
|
||||
pName(): string {
|
||||
return getUserName(this.p.user);
|
||||
},
|
||||
isRenote(): boolean {
|
||||
return (this.note.renote &&
|
||||
this.note.text == null &&
|
||||
this.note.mediaIds == null &&
|
||||
this.note.mediaIds.length == 0 &&
|
||||
this.note.poll == null);
|
||||
},
|
||||
p(): any {
|
||||
|
@ -159,7 +149,7 @@ export default Vue.extend({
|
|||
|
||||
// Draw map
|
||||
if (this.p.geo) {
|
||||
const shouldShowMap = (this as any).os.isSignedIn ? (this as any).os.i.account.clientSettings.showMaps : true;
|
||||
const shouldShowMap = (this as any).os.isSignedIn ? (this as any).os.i.clientSettings.showMaps : true;
|
||||
if (shouldShowMap) {
|
||||
(this as any).os.getGoogleMaps().then(maps => {
|
||||
const uluru = new maps.LatLng(this.p.geo.coordinates[1], this.p.geo.coordinates[0]);
|
||||
|
|
|
@ -1,462 +0,0 @@
|
|||
<template>
|
||||
<div class="mk-note-detail">
|
||||
<button
|
||||
class="more"
|
||||
v-if="p.reply && p.reply.replyId && context == null"
|
||||
@click="fetchContext"
|
||||
:disabled="fetchingContext"
|
||||
>
|
||||
<template v-if="!contextFetching">%fa:ellipsis-v%</template>
|
||||
<template v-if="contextFetching">%fa:spinner .pulse%</template>
|
||||
</button>
|
||||
<div class="context">
|
||||
<x-sub v-for="note in context" :key="note.id" :note="note"/>
|
||||
</div>
|
||||
<div class="reply-to" v-if="p.reply">
|
||||
<x-sub :note="p.reply"/>
|
||||
</div>
|
||||
<div class="renote" v-if="isRenote">
|
||||
<p>
|
||||
<router-link class="avatar-anchor" :to="`/@${acct}`">
|
||||
<img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=32`" alt="avatar"/>
|
||||
</router-link>
|
||||
%fa:retweet%
|
||||
<router-link class="name" :to="`/@${acct}`">
|
||||
{{ name }}
|
||||
</router-link>
|
||||
がRenote
|
||||
</p>
|
||||
</div>
|
||||
<article>
|
||||
<header>
|
||||
<router-link class="avatar-anchor" :to="`/@${pAcct}`">
|
||||
<img class="avatar" :src="`${p.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
|
||||
</router-link>
|
||||
<div>
|
||||
<router-link class="name" :to="`/@${pAcct}`">{{ pName }}</router-link>
|
||||
<span class="username">@{{ pAcct }}</span>
|
||||
</div>
|
||||
</header>
|
||||
<div class="body">
|
||||
<mk-note-html v-if="p.text" :ast="p.text" :i="os.i" :class="$style.text"/>
|
||||
<div class="tags" v-if="p.tags && p.tags.length > 0">
|
||||
<router-link v-for="tag in p.tags" :key="tag" :to="`/search?q=#${tag}`">{{ tag }}</router-link>
|
||||
</div>
|
||||
<div class="media" v-if="p.media.length > 0">
|
||||
<mk-media-list :media-list="p.media"/>
|
||||
</div>
|
||||
<mk-poll v-if="p.poll" :note="p"/>
|
||||
<mk-url-preview v-for="url in urls" :url="url" :key="url"/>
|
||||
<a class="location" v-if="p.geo" :href="`http://maps.google.com/maps?q=${p.geo.coordinates[1]},${p.geo.coordinates[0]}`" target="_blank">%fa:map-marker-alt% 位置情報</a>
|
||||
<div class="map" v-if="p.geo" ref="map"></div>
|
||||
<div class="renote" v-if="p.renote">
|
||||
<mk-note-preview :note="p.renote"/>
|
||||
</div>
|
||||
</div>
|
||||
<router-link class="time" :to="`/@${pAcct}/${p.id}`">
|
||||
<mk-time :time="p.createdAt" mode="detail"/>
|
||||
</router-link>
|
||||
<footer>
|
||||
<mk-reactions-viewer :note="p"/>
|
||||
<button @click="reply" title="%i18n:mobile.tags.mk-note-detail.reply%">
|
||||
%fa:reply%<p class="count" v-if="p.repliesCount > 0">{{ p.repliesCount }}</p>
|
||||
</button>
|
||||
<button @click="renote" title="Renote">
|
||||
%fa:retweet%<p class="count" v-if="p.renoteCount > 0">{{ p.renoteCount }}</p>
|
||||
</button>
|
||||
<button :class="{ reacted: p.myReaction != null }" @click="react" ref="reactButton" title="%i18n:mobile.tags.mk-note-detail.reaction%">
|
||||
%fa:plus%<p class="count" v-if="p.reactions_count > 0">{{ p.reactions_count }}</p>
|
||||
</button>
|
||||
<button @click="menu" ref="menuButton">
|
||||
%fa:ellipsis-h%
|
||||
</button>
|
||||
</footer>
|
||||
</article>
|
||||
<div class="replies" v-if="!compact">
|
||||
<x-sub v-for="note in replies" :key="note.id" :note="note"/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import getAcct from '../../../../../acct/render';
|
||||
import getUserName from '../../../../../renderers/get-user-name';
|
||||
import parse from '../../../../../text/parse';
|
||||
|
||||
import MkNoteMenu from '../../../common/views/components/note-menu.vue';
|
||||
import MkReactionPicker from '../../../common/views/components/reaction-picker.vue';
|
||||
import XSub from './note-detail.sub.vue';
|
||||
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
XSub
|
||||
},
|
||||
|
||||
props: {
|
||||
note: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
compact: {
|
||||
default: false
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
context: [],
|
||||
contextFetching: false,
|
||||
replies: []
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
acct(): string {
|
||||
return getAcct(this.note.user);
|
||||
},
|
||||
name(): string {
|
||||
return getUserName(this.note.user);
|
||||
},
|
||||
pAcct(): string {
|
||||
return getAcct(this.p.user);
|
||||
},
|
||||
pName(): string {
|
||||
return getUserName(this.p.user);
|
||||
},
|
||||
isRenote(): boolean {
|
||||
return (this.note.renote &&
|
||||
this.note.text == null &&
|
||||
this.note.mediaIds == null &&
|
||||
this.note.poll == null);
|
||||
},
|
||||
p(): any {
|
||||
return this.isRenote ? this.note.renote : this.note;
|
||||
},
|
||||
reactionsCount(): number {
|
||||
return this.p.reactionCounts
|
||||
? Object.keys(this.p.reactionCounts)
|
||||
.map(key => this.p.reactionCounts[key])
|
||||
.reduce((a, b) => a + b)
|
||||
: 0;
|
||||
},
|
||||
urls(): string[] {
|
||||
if (this.p.text) {
|
||||
const ast = parse(this.p.text);
|
||||
return ast
|
||||
.filter(t => (t.type == 'url' || t.type == 'link') && !t.silent)
|
||||
.map(t => t.url);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
// Get replies
|
||||
if (!this.compact) {
|
||||
(this as any).api('notes/replies', {
|
||||
noteId: this.p.id,
|
||||
limit: 8
|
||||
}).then(replies => {
|
||||
this.replies = replies;
|
||||
});
|
||||
}
|
||||
|
||||
// Draw map
|
||||
if (this.p.geo) {
|
||||
const shouldShowMap = (this as any).os.isSignedIn ? (this as any).os.i.account.clientSettings.showMaps : true;
|
||||
if (shouldShowMap) {
|
||||
(this as any).os.getGoogleMaps().then(maps => {
|
||||
const uluru = new maps.LatLng(this.p.geo.coordinates[1], this.p.geo.coordinates[0]);
|
||||
const map = new maps.Map(this.$refs.map, {
|
||||
center: uluru,
|
||||
zoom: 15
|
||||
});
|
||||
new maps.Marker({
|
||||
position: uluru,
|
||||
map: map
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
fetchContext() {
|
||||
this.contextFetching = true;
|
||||
|
||||
// Fetch context
|
||||
(this as any).api('notes/context', {
|
||||
noteId: this.p.replyId
|
||||
}).then(context => {
|
||||
this.contextFetching = false;
|
||||
this.context = context.reverse();
|
||||
});
|
||||
},
|
||||
reply() {
|
||||
(this as any).apis.post({
|
||||
reply: this.p
|
||||
});
|
||||
},
|
||||
renote() {
|
||||
(this as any).apis.post({
|
||||
renote: this.p
|
||||
});
|
||||
},
|
||||
react() {
|
||||
(this as any).os.new(MkReactionPicker, {
|
||||
source: this.$refs.reactButton,
|
||||
note: this.p,
|
||||
compact: true
|
||||
});
|
||||
},
|
||||
menu() {
|
||||
(this as any).os.new(MkNoteMenu, {
|
||||
source: this.$refs.menuButton,
|
||||
note: this.p,
|
||||
compact: true
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
@import '~const.styl'
|
||||
|
||||
.mk-note-detail
|
||||
overflow hidden
|
||||
margin 0 auto
|
||||
padding 0
|
||||
width 100%
|
||||
text-align left
|
||||
background #fff
|
||||
border-radius 8px
|
||||
box-shadow 0 0 0 1px rgba(0, 0, 0, 0.2)
|
||||
|
||||
> .fetching
|
||||
padding 64px 0
|
||||
|
||||
> .more
|
||||
display block
|
||||
margin 0
|
||||
padding 10px 0
|
||||
width 100%
|
||||
font-size 1em
|
||||
text-align center
|
||||
color #999
|
||||
cursor pointer
|
||||
background #fafafa
|
||||
outline none
|
||||
border none
|
||||
border-bottom solid 1px #eef0f2
|
||||
border-radius 6px 6px 0 0
|
||||
box-shadow none
|
||||
|
||||
&:hover
|
||||
background #f6f6f6
|
||||
|
||||
&:active
|
||||
background #f0f0f0
|
||||
|
||||
&:disabled
|
||||
color #ccc
|
||||
|
||||
> .context
|
||||
> *
|
||||
border-bottom 1px solid #eef0f2
|
||||
|
||||
> .renote
|
||||
color #9dbb00
|
||||
background linear-gradient(to bottom, #edfde2 0%, #fff 100%)
|
||||
|
||||
> p
|
||||
margin 0
|
||||
padding 16px 32px
|
||||
|
||||
.avatar-anchor
|
||||
display inline-block
|
||||
|
||||
.avatar
|
||||
vertical-align bottom
|
||||
min-width 28px
|
||||
min-height 28px
|
||||
max-width 28px
|
||||
max-height 28px
|
||||
margin 0 8px 0 0
|
||||
border-radius 6px
|
||||
|
||||
[data-fa]
|
||||
margin-right 4px
|
||||
|
||||
.name
|
||||
font-weight bold
|
||||
|
||||
& + article
|
||||
padding-top 8px
|
||||
|
||||
> .reply-to
|
||||
border-bottom 1px solid #eef0f2
|
||||
|
||||
> article
|
||||
padding 14px 16px 9px 16px
|
||||
|
||||
@media (min-width 500px)
|
||||
padding 28px 32px 18px 32px
|
||||
|
||||
&:after
|
||||
content ""
|
||||
display block
|
||||
clear both
|
||||
|
||||
&:hover
|
||||
> .main > footer > button
|
||||
color #888
|
||||
|
||||
> header
|
||||
display flex
|
||||
line-height 1.1
|
||||
|
||||
> .avatar-anchor
|
||||
display block
|
||||
padding 0 .5em 0 0
|
||||
|
||||
> .avatar
|
||||
display block
|
||||
width 54px
|
||||
height 54px
|
||||
margin 0
|
||||
border-radius 8px
|
||||
vertical-align bottom
|
||||
|
||||
@media (min-width 500px)
|
||||
width 60px
|
||||
height 60px
|
||||
|
||||
> div
|
||||
|
||||
> .name
|
||||
display inline-block
|
||||
margin .4em 0
|
||||
color #777
|
||||
font-size 16px
|
||||
font-weight bold
|
||||
text-align left
|
||||
text-decoration none
|
||||
|
||||
&:hover
|
||||
text-decoration underline
|
||||
|
||||
> .username
|
||||
display block
|
||||
text-align left
|
||||
margin 0
|
||||
color #ccc
|
||||
|
||||
> .body
|
||||
padding 8px 0
|
||||
|
||||
> .renote
|
||||
margin 8px 0
|
||||
|
||||
> .mk-note-preview
|
||||
padding 16px
|
||||
border dashed 1px #c0dac6
|
||||
border-radius 8px
|
||||
|
||||
> .location
|
||||
margin 4px 0
|
||||
font-size 12px
|
||||
color #ccc
|
||||
|
||||
> .map
|
||||
width 100%
|
||||
height 200px
|
||||
|
||||
&:empty
|
||||
display none
|
||||
|
||||
> .mk-url-preview
|
||||
margin-top 8px
|
||||
|
||||
> .media
|
||||
> img
|
||||
display block
|
||||
max-width 100%
|
||||
|
||||
> .tags
|
||||
margin 4px 0 0 0
|
||||
|
||||
> *
|
||||
display inline-block
|
||||
margin 0 8px 0 0
|
||||
padding 2px 8px 2px 16px
|
||||
font-size 90%
|
||||
color #8d969e
|
||||
background #edf0f3
|
||||
border-radius 4px
|
||||
|
||||
&:before
|
||||
content ""
|
||||
display block
|
||||
position absolute
|
||||
top 0
|
||||
bottom 0
|
||||
left 4px
|
||||
width 8px
|
||||
height 8px
|
||||
margin auto 0
|
||||
background #fff
|
||||
border-radius 100%
|
||||
|
||||
> .time
|
||||
font-size 16px
|
||||
color #c0c0c0
|
||||
|
||||
> footer
|
||||
font-size 1.2em
|
||||
|
||||
> button
|
||||
margin 0
|
||||
padding 8px
|
||||
background transparent
|
||||
border none
|
||||
box-shadow none
|
||||
font-size 1em
|
||||
color #ddd
|
||||
cursor pointer
|
||||
|
||||
&:not(:last-child)
|
||||
margin-right 28px
|
||||
|
||||
&:hover
|
||||
color #666
|
||||
|
||||
> .count
|
||||
display inline
|
||||
margin 0 0 0 8px
|
||||
color #999
|
||||
|
||||
&.reacted
|
||||
color $theme-color
|
||||
|
||||
> .replies
|
||||
> *
|
||||
border-top 1px solid #eef0f2
|
||||
|
||||
</style>
|
||||
|
||||
<style lang="stylus" module>
|
||||
.text
|
||||
display block
|
||||
margin 0
|
||||
padding 0
|
||||
overflow-wrap break-word
|
||||
font-size 16px
|
||||
color #717171
|
||||
|
||||
@media (min-width 500px)
|
||||
font-size 24px
|
||||
|
||||
</style>
|
|
@ -111,7 +111,7 @@ export default Vue.extend({
|
|||
},
|
||||
post() {
|
||||
this.posting = true;
|
||||
const viaMobile = (this as any).os.i.account.clientSettings.disableViaMobile !== true;
|
||||
const viaMobile = (this as any).os.i.clientSettings.disableViaMobile !== true;
|
||||
(this as any).api('notes/create', {
|
||||
text: this.text == '' ? undefined : this.text,
|
||||
mediaIds: this.files.length > 0 ? this.files.map(f => f.id) : undefined,
|
||||
|
|
|
@ -1,540 +0,0 @@
|
|||
<template>
|
||||
<div class="note" :class="{ renote: isRenote }">
|
||||
<div class="reply-to" v-if="p.reply">
|
||||
<x-sub :note="p.reply"/>
|
||||
</div>
|
||||
<div class="renote" v-if="isRenote">
|
||||
<p>
|
||||
<router-link class="avatar-anchor" :to="`/@${acct}`">
|
||||
<img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
|
||||
</router-link>
|
||||
%fa:retweet%
|
||||
<span>{{ '%i18n:mobile.tags.mk-timeline-note.reposted-by%'.substr(0, '%i18n:mobile.tags.mk-timeline-note.reposted-by%'.indexOf('{')) }}</span>
|
||||
<router-link class="name" :to="`/@${acct}`">{{ name }}</router-link>
|
||||
<span>{{ '%i18n:mobile.tags.mk-timeline-note.reposted-by%'.substr('%i18n:mobile.tags.mk-timeline-note.reposted-by%'.indexOf('}') + 1) }}</span>
|
||||
</p>
|
||||
<mk-time :time="note.createdAt"/>
|
||||
</div>
|
||||
<article>
|
||||
<router-link class="avatar-anchor" :to="`/@${pAcct}`">
|
||||
<img class="avatar" :src="`${p.user.avatarUrl}?thumbnail&size=96`" alt="avatar"/>
|
||||
</router-link>
|
||||
<div class="main">
|
||||
<header>
|
||||
<router-link class="name" :to="`/@${pAcct}`">{{ pName }}</router-link>
|
||||
<span class="is-bot" v-if="p.user.host === null && p.user.account.isBot">bot</span>
|
||||
<span class="username">@{{ pAcct }}</span>
|
||||
<div class="info">
|
||||
<span class="mobile" v-if="p.viaMobile">%fa:mobile-alt%</span>
|
||||
<router-link class="created-at" :to="url">
|
||||
<mk-time :time="p.createdAt"/>
|
||||
</router-link>
|
||||
</div>
|
||||
</header>
|
||||
<div class="body">
|
||||
<p class="channel" v-if="p.channel != null"><a target="_blank">{{ p.channel.title }}</a>:</p>
|
||||
<div class="text">
|
||||
<a class="reply" v-if="p.reply">
|
||||
%fa:reply%
|
||||
</a>
|
||||
<mk-note-html v-if="p.text" :text="p.text" :i="os.i" :class="$style.text"/>
|
||||
<a class="rp" v-if="p.renote != null">RP:</a>
|
||||
</div>
|
||||
<div class="media" v-if="p.media.length > 0">
|
||||
<mk-media-list :media-list="p.media"/>
|
||||
</div>
|
||||
<mk-poll v-if="p.poll" :note="p" ref="pollViewer"/>
|
||||
<div class="tags" v-if="p.tags && p.tags.length > 0">
|
||||
<router-link v-for="tag in p.tags" :key="tag" :to="`/search?q=#${tag}`">{{ tag }}</router-link>
|
||||
</div>
|
||||
<mk-url-preview v-for="url in urls" :url="url" :key="url"/>
|
||||
<a class="location" v-if="p.geo" :href="`http://maps.google.com/maps?q=${p.geo.coordinates[1]},${p.geo.coordinates[0]}`" target="_blank">%fa:map-marker-alt% 位置情報</a>
|
||||
<div class="map" v-if="p.geo" ref="map"></div>
|
||||
<span class="app" v-if="p.app">via <b>{{ p.app.name }}</b></span>
|
||||
<div class="renote" v-if="p.renote">
|
||||
<mk-note-preview :note="p.renote"/>
|
||||
</div>
|
||||
</div>
|
||||
<footer>
|
||||
<mk-reactions-viewer :note="p" ref="reactionsViewer"/>
|
||||
<button @click="reply">
|
||||
%fa:reply%<p class="count" v-if="p.repliesCount > 0">{{ p.repliesCount }}</p>
|
||||
</button>
|
||||
<button @click="renote" title="Renote">
|
||||
%fa:retweet%<p class="count" v-if="p.renoteCount > 0">{{ p.renoteCount }}</p>
|
||||
</button>
|
||||
<button :class="{ reacted: p.myReaction != null }" @click="react" ref="reactButton">
|
||||
%fa:plus%<p class="count" v-if="p.reactions_count > 0">{{ p.reactions_count }}</p>
|
||||
</button>
|
||||
<button class="menu" @click="menu" ref="menuButton">
|
||||
%fa:ellipsis-h%
|
||||
</button>
|
||||
</footer>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import getAcct from '../../../../../acct/render';
|
||||
import getUserName from '../../../../../renderers/get-user-name';
|
||||
import parse from '../../../../../text/parse';
|
||||
|
||||
import MkNoteMenu from '../../../common/views/components/note-menu.vue';
|
||||
import MkReactionPicker from '../../../common/views/components/reaction-picker.vue';
|
||||
import XSub from './note.sub.vue';
|
||||
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
XSub
|
||||
},
|
||||
|
||||
props: ['note'],
|
||||
|
||||
data() {
|
||||
return {
|
||||
connection: null,
|
||||
connectionId: null
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
acct(): string {
|
||||
return getAcct(this.note.user);
|
||||
},
|
||||
name(): string {
|
||||
return getUserName(this.note.user);
|
||||
},
|
||||
pAcct(): string {
|
||||
return getAcct(this.p.user);
|
||||
},
|
||||
pName(): string {
|
||||
return getUserName(this.p.user);
|
||||
},
|
||||
isRenote(): boolean {
|
||||
return (this.note.renote &&
|
||||
this.note.text == null &&
|
||||
this.note.mediaIds == null &&
|
||||
this.note.poll == null);
|
||||
},
|
||||
p(): any {
|
||||
return this.isRenote ? this.note.renote : this.note;
|
||||
},
|
||||
reactionsCount(): number {
|
||||
return this.p.reactionCounts
|
||||
? Object.keys(this.p.reactionCounts)
|
||||
.map(key => this.p.reactionCounts[key])
|
||||
.reduce((a, b) => a + b)
|
||||
: 0;
|
||||
},
|
||||
url(): string {
|
||||
return `/@${this.pAcct}/${this.p.id}`;
|
||||
},
|
||||
urls(): string[] {
|
||||
if (this.p.text) {
|
||||
const ast = parse(this.p.text);
|
||||
return ast
|
||||
.filter(t => (t.type == 'url' || t.type == 'link') && !t.silent)
|
||||
.map(t => t.url);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
if ((this as any).os.isSignedIn) {
|
||||
this.connection = (this as any).os.stream.getConnection();
|
||||
this.connectionId = (this as any).os.stream.use();
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.capture(true);
|
||||
|
||||
if ((this as any).os.isSignedIn) {
|
||||
this.connection.on('_connected_', this.onStreamConnected);
|
||||
}
|
||||
|
||||
// Draw map
|
||||
if (this.p.geo) {
|
||||
const shouldShowMap = (this as any).os.isSignedIn ? (this as any).os.i.account.clientSettings.showMaps : true;
|
||||
if (shouldShowMap) {
|
||||
(this as any).os.getGoogleMaps().then(maps => {
|
||||
const uluru = new maps.LatLng(this.p.geo.coordinates[1], this.p.geo.coordinates[0]);
|
||||
const map = new maps.Map(this.$refs.map, {
|
||||
center: uluru,
|
||||
zoom: 15
|
||||
});
|
||||
new maps.Marker({
|
||||
position: uluru,
|
||||
map: map
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
this.decapture(true);
|
||||
|
||||
if ((this as any).os.isSignedIn) {
|
||||
this.connection.off('_connected_', this.onStreamConnected);
|
||||
(this as any).os.stream.dispose(this.connectionId);
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
capture(withHandler = false) {
|
||||
if ((this as any).os.isSignedIn) {
|
||||
this.connection.send({
|
||||
type: 'capture',
|
||||
id: this.p.id
|
||||
});
|
||||
if (withHandler) this.connection.on('note-updated', this.onStreamNoteUpdated);
|
||||
}
|
||||
},
|
||||
decapture(withHandler = false) {
|
||||
if ((this as any).os.isSignedIn) {
|
||||
this.connection.send({
|
||||
type: 'decapture',
|
||||
id: this.p.id
|
||||
});
|
||||
if (withHandler) this.connection.off('note-updated', this.onStreamNoteUpdated);
|
||||
}
|
||||
},
|
||||
onStreamConnected() {
|
||||
this.capture();
|
||||
},
|
||||
onStreamNoteUpdated(data) {
|
||||
const note = data.note;
|
||||
if (note.id == this.note.id) {
|
||||
this.$emit('update:note', note);
|
||||
} else if (note.id == this.note.renoteId) {
|
||||
this.note.renote = note;
|
||||
}
|
||||
},
|
||||
reply() {
|
||||
(this as any).apis.post({
|
||||
reply: this.p
|
||||
});
|
||||
},
|
||||
renote() {
|
||||
(this as any).apis.post({
|
||||
renote: this.p
|
||||
});
|
||||
},
|
||||
react() {
|
||||
(this as any).os.new(MkReactionPicker, {
|
||||
source: this.$refs.reactButton,
|
||||
note: this.p,
|
||||
compact: true
|
||||
});
|
||||
},
|
||||
menu() {
|
||||
(this as any).os.new(MkNoteMenu, {
|
||||
source: this.$refs.menuButton,
|
||||
note: this.p,
|
||||
compact: true
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
@import '~const.styl'
|
||||
|
||||
.note
|
||||
font-size 12px
|
||||
border-bottom solid 1px #eaeaea
|
||||
|
||||
&:first-child
|
||||
border-radius 8px 8px 0 0
|
||||
|
||||
> .renote
|
||||
border-radius 8px 8px 0 0
|
||||
|
||||
&:last-of-type
|
||||
border-bottom none
|
||||
|
||||
@media (min-width 350px)
|
||||
font-size 14px
|
||||
|
||||
@media (min-width 500px)
|
||||
font-size 16px
|
||||
|
||||
> .renote
|
||||
color #9dbb00
|
||||
background linear-gradient(to bottom, #edfde2 0%, #fff 100%)
|
||||
|
||||
> p
|
||||
margin 0
|
||||
padding 8px 16px
|
||||
line-height 28px
|
||||
|
||||
@media (min-width 500px)
|
||||
padding 16px
|
||||
|
||||
.avatar-anchor
|
||||
display inline-block
|
||||
|
||||
.avatar
|
||||
vertical-align bottom
|
||||
width 28px
|
||||
height 28px
|
||||
margin 0 8px 0 0
|
||||
border-radius 6px
|
||||
|
||||
[data-fa]
|
||||
margin-right 4px
|
||||
|
||||
.name
|
||||
font-weight bold
|
||||
|
||||
> .mk-time
|
||||
position absolute
|
||||
top 8px
|
||||
right 16px
|
||||
font-size 0.9em
|
||||
line-height 28px
|
||||
|
||||
@media (min-width 500px)
|
||||
top 16px
|
||||
|
||||
& + article
|
||||
padding-top 8px
|
||||
|
||||
> .reply-to
|
||||
background rgba(0, 0, 0, 0.0125)
|
||||
|
||||
> .mk-note-preview
|
||||
background transparent
|
||||
|
||||
> article
|
||||
padding 14px 16px 9px 16px
|
||||
|
||||
&:after
|
||||
content ""
|
||||
display block
|
||||
clear both
|
||||
|
||||
> .avatar-anchor
|
||||
display block
|
||||
float left
|
||||
margin 0 10px 8px 0
|
||||
position -webkit-sticky
|
||||
position sticky
|
||||
top 62px
|
||||
|
||||
@media (min-width 500px)
|
||||
margin-right 16px
|
||||
|
||||
> .avatar
|
||||
display block
|
||||
width 48px
|
||||
height 48px
|
||||
margin 0
|
||||
border-radius 6px
|
||||
vertical-align bottom
|
||||
|
||||
@media (min-width 500px)
|
||||
width 58px
|
||||
height 58px
|
||||
border-radius 8px
|
||||
|
||||
> .main
|
||||
float left
|
||||
width calc(100% - 58px)
|
||||
|
||||
@media (min-width 500px)
|
||||
width calc(100% - 74px)
|
||||
|
||||
> header
|
||||
display flex
|
||||
align-items center
|
||||
white-space nowrap
|
||||
|
||||
@media (min-width 500px)
|
||||
margin-bottom 2px
|
||||
|
||||
> .name
|
||||
display block
|
||||
margin 0 0.5em 0 0
|
||||
padding 0
|
||||
overflow hidden
|
||||
color #627079
|
||||
font-size 1em
|
||||
font-weight bold
|
||||
text-decoration none
|
||||
text-overflow ellipsis
|
||||
|
||||
&:hover
|
||||
text-decoration underline
|
||||
|
||||
> .is-bot
|
||||
margin 0 0.5em 0 0
|
||||
padding 1px 6px
|
||||
font-size 12px
|
||||
color #aaa
|
||||
border solid 1px #ddd
|
||||
border-radius 3px
|
||||
|
||||
> .username
|
||||
margin 0 0.5em 0 0
|
||||
color #ccc
|
||||
|
||||
> .info
|
||||
margin-left auto
|
||||
font-size 0.9em
|
||||
|
||||
> .mobile
|
||||
margin-right 6px
|
||||
color #c0c0c0
|
||||
|
||||
> .created-at
|
||||
color #c0c0c0
|
||||
|
||||
> .body
|
||||
|
||||
> .text
|
||||
display block
|
||||
margin 0
|
||||
padding 0
|
||||
overflow-wrap break-word
|
||||
font-size 1.1em
|
||||
color #717171
|
||||
|
||||
>>> .quote
|
||||
margin 8px
|
||||
padding 6px 12px
|
||||
color #aaa
|
||||
border-left solid 3px #eee
|
||||
|
||||
> .reply
|
||||
margin-right 8px
|
||||
color #717171
|
||||
|
||||
> .rp
|
||||
margin-left 4px
|
||||
font-style oblique
|
||||
color #a0bf46
|
||||
|
||||
[data-is-me]:after
|
||||
content "you"
|
||||
padding 0 4px
|
||||
margin-left 4px
|
||||
font-size 80%
|
||||
color $theme-color-foreground
|
||||
background $theme-color
|
||||
border-radius 4px
|
||||
|
||||
.mk-url-preview
|
||||
margin-top 8px
|
||||
|
||||
> .channel
|
||||
margin 0
|
||||
|
||||
> .tags
|
||||
margin 4px 0 0 0
|
||||
|
||||
> *
|
||||
display inline-block
|
||||
margin 0 8px 0 0
|
||||
padding 2px 8px 2px 16px
|
||||
font-size 90%
|
||||
color #8d969e
|
||||
background #edf0f3
|
||||
border-radius 4px
|
||||
|
||||
&:before
|
||||
content ""
|
||||
display block
|
||||
position absolute
|
||||
top 0
|
||||
bottom 0
|
||||
left 4px
|
||||
width 8px
|
||||
height 8px
|
||||
margin auto 0
|
||||
background #fff
|
||||
border-radius 100%
|
||||
|
||||
> .media
|
||||
> img
|
||||
display block
|
||||
max-width 100%
|
||||
|
||||
> .location
|
||||
margin 4px 0
|
||||
font-size 12px
|
||||
color #ccc
|
||||
|
||||
> .map
|
||||
width 100%
|
||||
height 200px
|
||||
|
||||
&:empty
|
||||
display none
|
||||
|
||||
> .app
|
||||
font-size 12px
|
||||
color #ccc
|
||||
|
||||
> .mk-poll
|
||||
font-size 80%
|
||||
|
||||
> .renote
|
||||
margin 8px 0
|
||||
|
||||
> .mk-note-preview
|
||||
padding 16px
|
||||
border dashed 1px #c0dac6
|
||||
border-radius 8px
|
||||
|
||||
> footer
|
||||
> button
|
||||
margin 0
|
||||
padding 8px
|
||||
background transparent
|
||||
border none
|
||||
box-shadow none
|
||||
font-size 1em
|
||||
color #ddd
|
||||
cursor pointer
|
||||
|
||||
&:not(:last-child)
|
||||
margin-right 28px
|
||||
|
||||
&:hover
|
||||
color #666
|
||||
|
||||
> .count
|
||||
display inline
|
||||
margin 0 0 0 8px
|
||||
color #999
|
||||
|
||||
&.reacted
|
||||
color $theme-color
|
||||
|
||||
&.menu
|
||||
@media (max-width 350px)
|
||||
display none
|
||||
|
||||
</style>
|
||||
|
||||
<style lang="stylus" module>
|
||||
.text
|
||||
code
|
||||
padding 4px 8px
|
||||
margin 0 0.5em
|
||||
font-size 80%
|
||||
color #525252
|
||||
background #f8f8f8
|
||||
border-radius 2px
|
||||
|
||||
pre > code
|
||||
padding 16px
|
||||
margin 0
|
||||
</style>
|
|
@ -63,9 +63,9 @@ export default Vue.extend({
|
|||
}
|
||||
});
|
||||
|
||||
const ago = (new Date().getTime() - new Date((this as any).os.i.account.lastUsedAt).getTime()) / 1000
|
||||
const ago = (new Date().getTime() - new Date((this as any).os.i.lastUsedAt).getTime()) / 1000
|
||||
const isHisasiburi = ago >= 3600;
|
||||
(this as any).os.i.account.lastUsedAt = new Date();
|
||||
(this as any).os.i.lastUsedAt = new Date();
|
||||
if (isHisasiburi) {
|
||||
(this.$refs.welcomeback as any).style.display = 'block';
|
||||
(this.$refs.main as any).style.overflow = 'hidden';
|
||||
|
|
|
@ -82,8 +82,8 @@ export default Vue.extend({
|
|||
};
|
||||
},
|
||||
created() {
|
||||
if ((this as any).os.i.account.clientSettings.mobileHome == null) {
|
||||
Vue.set((this as any).os.i.account.clientSettings, 'mobileHome', [{
|
||||
if ((this as any).os.i.clientSettings.mobileHome == null) {
|
||||
Vue.set((this as any).os.i.clientSettings, 'mobileHome', [{
|
||||
name: 'calendar',
|
||||
id: 'a', data: {}
|
||||
}, {
|
||||
|
@ -105,14 +105,14 @@ export default Vue.extend({
|
|||
name: 'version',
|
||||
id: 'g', data: {}
|
||||
}]);
|
||||
this.widgets = (this as any).os.i.account.clientSettings.mobileHome;
|
||||
this.widgets = (this as any).os.i.clientSettings.mobileHome;
|
||||
this.saveHome();
|
||||
} else {
|
||||
this.widgets = (this as any).os.i.account.clientSettings.mobileHome;
|
||||
this.widgets = (this as any).os.i.clientSettings.mobileHome;
|
||||
}
|
||||
|
||||
this.$watch('os.i.account.clientSettings', i => {
|
||||
this.widgets = (this as any).os.i.account.clientSettings.mobileHome;
|
||||
this.$watch('os.i.clientSettings', i => {
|
||||
this.widgets = (this as any).os.i.clientSettings.mobileHome;
|
||||
}, {
|
||||
deep: true
|
||||
});
|
||||
|
@ -157,15 +157,15 @@ export default Vue.extend({
|
|||
},
|
||||
onHomeUpdated(data) {
|
||||
if (data.home) {
|
||||
(this as any).os.i.account.clientSettings.mobileHome = data.home;
|
||||
(this as any).os.i.clientSettings.mobileHome = data.home;
|
||||
this.widgets = data.home;
|
||||
} else {
|
||||
const w = (this as any).os.i.account.clientSettings.mobileHome.find(w => w.id == data.id);
|
||||
const w = (this as any).os.i.clientSettings.mobileHome.find(w => w.id == data.id);
|
||||
if (w != null) {
|
||||
w.data = data.data;
|
||||
this.$refs[w.id][0].preventSave = true;
|
||||
this.$refs[w.id][0].props = w.data;
|
||||
this.widgets = (this as any).os.i.account.clientSettings.mobileHome;
|
||||
this.widgets = (this as any).os.i.clientSettings.mobileHome;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -194,7 +194,7 @@ export default Vue.extend({
|
|||
this.saveHome();
|
||||
},
|
||||
saveHome() {
|
||||
(this as any).os.i.account.clientSettings.mobileHome = this.widgets;
|
||||
(this as any).os.i.clientSettings.mobileHome = this.widgets;
|
||||
(this as any).api('i/update_mobile_home', {
|
||||
home: this.widgets
|
||||
});
|
||||
|
|
|
@ -53,9 +53,9 @@ export default Vue.extend({
|
|||
},
|
||||
created() {
|
||||
this.name = (this as any).os.i.name || '';
|
||||
this.location = (this as any).os.i.account.profile.location;
|
||||
this.location = (this as any).os.i.profile.location;
|
||||
this.description = (this as any).os.i.description;
|
||||
this.birthday = (this as any).os.i.account.profile.birthday;
|
||||
this.birthday = (this as any).os.i.profile.birthday;
|
||||
},
|
||||
mounted() {
|
||||
document.title = 'Misskey | %i18n:mobile.tags.mk-profile-setting-page.title%';
|
||||
|
|
|
@ -18,11 +18,11 @@
|
|||
</div>
|
||||
<div class="description">{{ user.description }}</div>
|
||||
<div class="info">
|
||||
<p class="location" v-if="user.host === null && user.account.profile.location">
|
||||
%fa:map-marker%{{ user.account.profile.location }}
|
||||
<p class="location" v-if="user.host === null && user.profile.location">
|
||||
%fa:map-marker%{{ user.profile.location }}
|
||||
</p>
|
||||
<p class="birthday" v-if="user.host === null && user.account.profile.birthday">
|
||||
%fa:birthday-cake%{{ user.account.profile.birthday.replace('-', '年').replace('-', '月') + '日' }} ({{ age }}歳)
|
||||
<p class="birthday" v-if="user.host === null && user.profile.birthday">
|
||||
%fa:birthday-cake%{{ user.profile.birthday.replace('-', '年').replace('-', '月') + '日' }} ({{ age }}歳)
|
||||
</p>
|
||||
</div>
|
||||
<div class="status">
|
||||
|
@ -81,7 +81,7 @@ export default Vue.extend({
|
|||
return this.getAcct(this.user);
|
||||
},
|
||||
age(): number {
|
||||
return age(this.user.account.profile.birthday);
|
||||
return age(this.user.profile.birthday);
|
||||
},
|
||||
name() {
|
||||
return getUserName(this.user);
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
<x-followers-you-know :user="user"/>
|
||||
</div>
|
||||
</section>
|
||||
<p v-if="user.host === null">%i18n:mobile.tags.mk-user-overview.last-used-at%: <b><mk-time :time="user.account.lastUsedAt"/></b></p>
|
||||
<p v-if="user.host === null">%i18n:mobile.tags.mk-user-overview.last-used-at%: <b><mk-time :time="user.lastUsedAt"/></b></p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<form @submit.prevent="onSubmit">
|
||||
<input v-model="username" type="text" pattern="^[a-zA-Z0-9_]+$" placeholder="ユーザー名" autofocus required @change="onUsernameChange"/>
|
||||
<input v-model="password" type="password" placeholder="パスワード" required/>
|
||||
<input v-if="user && user.account.twoFactorEnabled" v-model="token" type="number" placeholder="トークン" required/>
|
||||
<input v-if="user && user.twoFactorEnabled" v-model="token" type="number" placeholder="トークン" required/>
|
||||
<button type="submit" :disabled="signing">{{ signing ? 'ログインしています' : 'ログイン' }}</button>
|
||||
</form>
|
||||
<div>
|
||||
|
@ -70,7 +70,7 @@ export default Vue.extend({
|
|||
(this as any).api('signin', {
|
||||
username: this.username,
|
||||
password: this.password,
|
||||
token: this.user && this.user.account.twoFactorEnabled ? this.token : undefined
|
||||
token: this.user && this.user.twoFactorEnabled ? this.token : undefined
|
||||
}).then(() => {
|
||||
location.reload();
|
||||
}).catch(() => {
|
||||
|
|
|
@ -11,7 +11,7 @@ import config from '../config';
|
|||
const User = db.get<IUser>('users');
|
||||
|
||||
User.createIndex('username');
|
||||
User.createIndex('account.token');
|
||||
User.createIndex('token');
|
||||
|
||||
export default User;
|
||||
|
||||
|
@ -40,45 +40,41 @@ type IUserBase = {
|
|||
|
||||
export interface ILocalUser extends IUserBase {
|
||||
host: null;
|
||||
account: {
|
||||
keypair: string;
|
||||
email: string;
|
||||
links: string[];
|
||||
password: string;
|
||||
token: string;
|
||||
twitter: {
|
||||
accessToken: string;
|
||||
accessTokenSecret: string;
|
||||
userId: string;
|
||||
screenName: string;
|
||||
};
|
||||
line: {
|
||||
userId: string;
|
||||
};
|
||||
profile: {
|
||||
location: string;
|
||||
birthday: string; // 'YYYY-MM-DD'
|
||||
tags: string[];
|
||||
};
|
||||
lastUsedAt: Date;
|
||||
isBot: boolean;
|
||||
isPro: boolean;
|
||||
twoFactorSecret: string;
|
||||
twoFactorEnabled: boolean;
|
||||
twoFactorTempSecret: string;
|
||||
clientSettings: any;
|
||||
settings: any;
|
||||
keypair: string;
|
||||
email: string;
|
||||
links: string[];
|
||||
password: string;
|
||||
token: string;
|
||||
twitter: {
|
||||
accessToken: string;
|
||||
accessTokenSecret: string;
|
||||
userId: string;
|
||||
screenName: string;
|
||||
};
|
||||
line: {
|
||||
userId: string;
|
||||
};
|
||||
profile: {
|
||||
location: string;
|
||||
birthday: string; // 'YYYY-MM-DD'
|
||||
tags: string[];
|
||||
};
|
||||
lastUsedAt: Date;
|
||||
isBot: boolean;
|
||||
isPro: boolean;
|
||||
twoFactorSecret: string;
|
||||
twoFactorEnabled: boolean;
|
||||
twoFactorTempSecret: string;
|
||||
clientSettings: any;
|
||||
settings: any;
|
||||
}
|
||||
|
||||
export interface IRemoteUser extends IUserBase {
|
||||
account: {
|
||||
inbox: string;
|
||||
uri: string;
|
||||
publicKey: {
|
||||
id: string;
|
||||
publicKeyPem: string;
|
||||
};
|
||||
inbox: string;
|
||||
uri: string;
|
||||
publicKey: {
|
||||
id: string;
|
||||
publicKeyPem: string;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -150,11 +146,11 @@ export const pack = (
|
|||
|
||||
const fields = opts.detail ? {
|
||||
} : {
|
||||
'account.settings': false,
|
||||
'account.clientSettings': false,
|
||||
'account.profile': false,
|
||||
'account.keywords': false,
|
||||
'account.domains': false
|
||||
settings: false,
|
||||
clientSettings: false,
|
||||
profile: false,
|
||||
keywords: false,
|
||||
domains: false
|
||||
};
|
||||
|
||||
// Populate the user if 'user' is ID
|
||||
|
@ -188,29 +184,29 @@ export const pack = (
|
|||
// Remove needless properties
|
||||
delete _user.latestNote;
|
||||
|
||||
if (!_user.host) {
|
||||
if (_user.host == null) {
|
||||
// Remove private properties
|
||||
delete _user.account.keypair;
|
||||
delete _user.account.password;
|
||||
delete _user.account.token;
|
||||
delete _user.account.twoFactorTempSecret;
|
||||
delete _user.account.twoFactorSecret;
|
||||
delete _user.keypair;
|
||||
delete _user.password;
|
||||
delete _user.token;
|
||||
delete _user.twoFactorTempSecret;
|
||||
delete _user.twoFactorSecret;
|
||||
delete _user.usernameLower;
|
||||
if (_user.account.twitter) {
|
||||
delete _user.account.twitter.accessToken;
|
||||
delete _user.account.twitter.accessTokenSecret;
|
||||
if (_user.twitter) {
|
||||
delete _user.twitter.accessToken;
|
||||
delete _user.twitter.accessTokenSecret;
|
||||
}
|
||||
delete _user.account.line;
|
||||
delete _user.line;
|
||||
|
||||
// Visible via only the official client
|
||||
if (!opts.includeSecrets) {
|
||||
delete _user.account.email;
|
||||
delete _user.account.settings;
|
||||
delete _user.account.clientSettings;
|
||||
delete _user.email;
|
||||
delete _user.settings;
|
||||
delete _user.clientSettings;
|
||||
}
|
||||
|
||||
if (!opts.detail) {
|
||||
delete _user.account.twoFactorEnabled;
|
||||
delete _user.twoFactorEnabled;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -36,7 +36,7 @@ export default async (job: kue.Job, done): Promise<void> => {
|
|||
} else {
|
||||
user = await User.findOne({
|
||||
host: { $ne: null },
|
||||
'account.publicKey.id': signature.keyId
|
||||
'publicKey.id': signature.keyId
|
||||
}) as IRemoteUser;
|
||||
|
||||
// アクティビティを送信してきたユーザーがまだMisskeyサーバーに登録されていなかったら登録する
|
||||
|
@ -50,7 +50,7 @@ export default async (job: kue.Job, done): Promise<void> => {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!verifySignature(signature, user.account.publicKey.publicKeyPem)) {
|
||||
if (!verifySignature(signature, user.publicKey.publicKeyPem)) {
|
||||
console.warn('signature verification failed');
|
||||
done();
|
||||
return;
|
||||
|
|
|
@ -7,7 +7,7 @@ import { IDriveFile } from '../../../../models/drive-file';
|
|||
const log = debug('misskey:activitypub');
|
||||
|
||||
export default async function(actor: IRemoteUser, image): Promise<IDriveFile> {
|
||||
if ('attributedTo' in image && actor.account.uri !== image.attributedTo) {
|
||||
if ('attributedTo' in image && actor.uri !== image.attributedTo) {
|
||||
log(`invalid image: ${JSON.stringify(image, null, 2)}`);
|
||||
throw new Error('invalid image');
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ import { ICreate } from '../../type';
|
|||
const log = debug('misskey:activitypub');
|
||||
|
||||
export default async (actor: IRemoteUser, activity: ICreate): Promise<void> => {
|
||||
if ('actor' in activity && actor.account.uri !== activity.actor) {
|
||||
if ('actor' in activity && actor.uri !== activity.actor) {
|
||||
throw new Error('invalid actor');
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ import { IRemoteUser } from '../../../../models/user';
|
|||
* 削除アクティビティを捌きます
|
||||
*/
|
||||
export default async (actor: IRemoteUser, activity): Promise<void> => {
|
||||
if ('actor' in activity && actor.account.uri !== activity.actor) {
|
||||
if ('actor' in activity && actor.uri !== activity.actor) {
|
||||
throw new Error('invalid actor');
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ import Resolver from '../../resolver';
|
|||
const log = debug('misskey:activitypub');
|
||||
|
||||
export default async (actor: IRemoteUser, activity: IUndo): Promise<void> => {
|
||||
if ('actor' in activity && actor.account.uri !== activity.actor) {
|
||||
if ('actor' in activity && actor.uri !== activity.actor) {
|
||||
throw new Error('invalid actor');
|
||||
}
|
||||
|
||||
|
|
|
@ -4,5 +4,5 @@ import { IRemoteUser } from '../../../models/user';
|
|||
export default ({ username }, followee: IRemoteUser) => ({
|
||||
type: 'Follow',
|
||||
actor: `${config.url}/@${username}`,
|
||||
object: followee.account.uri
|
||||
object: followee.uri
|
||||
});
|
||||
|
|
|
@ -6,5 +6,5 @@ export default (user: ILocalUser) => ({
|
|||
id: `${config.url}/@${user.username}/publickey`,
|
||||
type: 'Key',
|
||||
owner: `${config.url}/@${user.username}`,
|
||||
publicKeyPem: extractPublic(user.account.keypair)
|
||||
publicKeyPem: extractPublic(user.keypair)
|
||||
});
|
||||
|
|
|
@ -4,10 +4,11 @@ import { URL } from 'url';
|
|||
import * as debug from 'debug';
|
||||
|
||||
import config from '../config';
|
||||
import { ILocalUser } from '../models/user';
|
||||
|
||||
const log = debug('misskey:activitypub:deliver');
|
||||
|
||||
export default ({ account, username }, url, object) => new Promise((resolve, reject) => {
|
||||
export default (user: ILocalUser, url, object) => new Promise((resolve, reject) => {
|
||||
log(`--> ${url}`);
|
||||
|
||||
const { protocol, hostname, port, pathname, search } = new URL(url);
|
||||
|
@ -35,8 +36,8 @@ export default ({ account, username }, url, object) => new Promise((resolve, rej
|
|||
|
||||
sign(req, {
|
||||
authorizationHeaderName: 'Signature',
|
||||
key: account.keypair,
|
||||
keyId: `acct:${username}@${config.host}`
|
||||
key: user.keypair,
|
||||
keyId: `acct:${user.username}@${config.host}`
|
||||
});
|
||||
|
||||
req.end(JSON.stringify(object));
|
||||
|
|
|
@ -11,8 +11,7 @@ export default function(user: IUser): string {
|
|||
`${user.notesCount}投稿、${user.followingCount}フォロー、${user.followersCount}フォロワー\n`;
|
||||
|
||||
if (isLocalUser(user)) {
|
||||
const account = user.account;
|
||||
string += `場所: ${account.profile.location}、誕生日: ${account.profile.birthday}\n`;
|
||||
string += `場所: ${user.profile.location}、誕生日: ${user.profile.birthday}\n`;
|
||||
}
|
||||
|
||||
return string + `「${user.description}」`;
|
||||
|
|
|
@ -34,7 +34,7 @@ export default (req: express.Request) => new Promise<IAuthContext>(async (resolv
|
|||
|
||||
if (isNativeToken(token)) {
|
||||
const user: IUser = await User
|
||||
.findOne({ 'account.token': token });
|
||||
.findOne({ 'token': token });
|
||||
|
||||
if (user === null) {
|
||||
return reject('user not found');
|
||||
|
|
|
@ -226,7 +226,7 @@ class SigninContext extends Context {
|
|||
}
|
||||
} else {
|
||||
// Compare password
|
||||
const same = await bcrypt.compare(query, this.temporaryUser.account.password);
|
||||
const same = await bcrypt.compare(query, this.temporaryUser.password);
|
||||
|
||||
if (same) {
|
||||
this.bot.signin(this.temporaryUser);
|
||||
|
|
|
@ -112,11 +112,11 @@ class LineBot extends BotCore {
|
|||
data: `showtl|${user.id}`
|
||||
});
|
||||
|
||||
if (user.account.twitter) {
|
||||
if (user.twitter) {
|
||||
actions.push({
|
||||
type: 'uri',
|
||||
label: 'Twitterアカウントを見る',
|
||||
uri: `https://twitter.com/${user.account.twitter.screenName}`
|
||||
uri: `https://twitter.com/${user.twitter.screenName}`
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -174,7 +174,7 @@ module.exports = async (app: express.Application) => {
|
|||
if (session == null) {
|
||||
const user = await User.findOne({
|
||||
host: null,
|
||||
'account.line': {
|
||||
'line': {
|
||||
userId: sourceId
|
||||
}
|
||||
});
|
||||
|
@ -184,7 +184,7 @@ module.exports = async (app: express.Application) => {
|
|||
bot.on('signin', user => {
|
||||
User.update(user._id, {
|
||||
$set: {
|
||||
'account.line': {
|
||||
'line': {
|
||||
userId: sourceId
|
||||
}
|
||||
}
|
||||
|
@ -194,7 +194,7 @@ module.exports = async (app: express.Application) => {
|
|||
bot.on('signout', user => {
|
||||
User.update(user._id, {
|
||||
$set: {
|
||||
'account.line': {
|
||||
'line': {
|
||||
userId: null
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import config from '../../../config';
|
|||
|
||||
export default function(res, user, redirect: boolean) {
|
||||
const expires = 1000 * 60 * 60 * 24 * 365; // One Year
|
||||
res.cookie('i', user.account.token, {
|
||||
res.cookie('i', user.token, {
|
||||
path: '/',
|
||||
domain: `.${config.hostname}`,
|
||||
secure: config.url.substr(0, 5) === 'https',
|
||||
|
|
|
@ -31,7 +31,7 @@ module.exports = (params, user) => new Promise(async (res, rej) => {
|
|||
}, {
|
||||
fields: {
|
||||
data: false,
|
||||
'account.profile': false
|
||||
'profile': false
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ module.exports = (params, user) => new Promise(async (res, rej) => {
|
|||
}, {
|
||||
fields: {
|
||||
data: false,
|
||||
'account.profile': false
|
||||
'profile': false
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -5,12 +5,6 @@ import User, { pack } from '../../../models/user';
|
|||
|
||||
/**
|
||||
* Show myself
|
||||
*
|
||||
* @param {any} params
|
||||
* @param {any} user
|
||||
* @param {any} app
|
||||
* @param {Boolean} isSecure
|
||||
* @return {Promise<any>}
|
||||
*/
|
||||
module.exports = (params, user, _, isSecure) => new Promise(async (res, rej) => {
|
||||
// Serialize
|
||||
|
@ -22,7 +16,7 @@ module.exports = (params, user, _, isSecure) => new Promise(async (res, rej) =>
|
|||
// Update lastUsedAt
|
||||
User.update({ _id: user._id }, {
|
||||
$set: {
|
||||
'account.lastUsedAt': new Date()
|
||||
lastUsedAt: new Date()
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -28,8 +28,8 @@ module.exports = async (params, user) => new Promise(async (res, rej) => {
|
|||
|
||||
await User.update(user._id, {
|
||||
$set: {
|
||||
'account.twoFactorSecret': user.twoFactorTempSecret,
|
||||
'account.twoFactorEnabled': true
|
||||
'twoFactorSecret': user.twoFactorTempSecret,
|
||||
'twoFactorEnabled': true
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ module.exports = async (params, user) => new Promise(async (res, rej) => {
|
|||
if (passwordErr) return rej('invalid password param');
|
||||
|
||||
// Compare password
|
||||
const same = await bcrypt.compare(password, user.account.password);
|
||||
const same = await bcrypt.compare(password, user.password);
|
||||
|
||||
if (!same) {
|
||||
return rej('incorrect password');
|
||||
|
|
|
@ -11,7 +11,7 @@ module.exports = async (params, user) => new Promise(async (res, rej) => {
|
|||
if (passwordErr) return rej('invalid password param');
|
||||
|
||||
// Compare password
|
||||
const same = await bcrypt.compare(password, user.account.password);
|
||||
const same = await bcrypt.compare(password, user.password);
|
||||
|
||||
if (!same) {
|
||||
return rej('incorrect password');
|
||||
|
@ -19,8 +19,8 @@ module.exports = async (params, user) => new Promise(async (res, rej) => {
|
|||
|
||||
await User.update(user._id, {
|
||||
$set: {
|
||||
'account.twoFactorSecret': null,
|
||||
'account.twoFactorEnabled': false
|
||||
'twoFactorSecret': null,
|
||||
'twoFactorEnabled': false
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ module.exports = async (params, user) => new Promise(async (res, rej) => {
|
|||
if (newPasswordErr) return rej('invalid newPassword param');
|
||||
|
||||
// Compare password
|
||||
const same = await bcrypt.compare(currentPassword, user.account.password);
|
||||
const same = await bcrypt.compare(currentPassword, user.password);
|
||||
|
||||
if (!same) {
|
||||
return rej('incorrect password');
|
||||
|
@ -34,7 +34,7 @@ module.exports = async (params, user) => new Promise(async (res, rej) => {
|
|||
|
||||
await User.update(user._id, {
|
||||
$set: {
|
||||
'account.password': hash
|
||||
'password': hash
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ module.exports = async (params, user) => new Promise(async (res, rej) => {
|
|||
if (passwordErr) return rej('invalid password param');
|
||||
|
||||
// Compare password
|
||||
const same = await bcrypt.compare(password, user.account.password);
|
||||
const same = await bcrypt.compare(password, user.password);
|
||||
|
||||
if (!same) {
|
||||
return rej('incorrect password');
|
||||
|
@ -31,7 +31,7 @@ module.exports = async (params, user) => new Promise(async (res, rej) => {
|
|||
|
||||
await User.update(user._id, {
|
||||
$set: {
|
||||
'account.token': secret
|
||||
'token': secret
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -29,12 +29,12 @@ module.exports = async (params, user, _, isSecure) => new Promise(async (res, re
|
|||
// Get 'location' parameter
|
||||
const [location, locationErr] = $(params.location).optional.nullable.string().pipe(isValidLocation).$;
|
||||
if (locationErr) return rej('invalid location param');
|
||||
if (location !== undefined) user.account.profile.location = location;
|
||||
if (location !== undefined) user.profile.location = location;
|
||||
|
||||
// Get 'birthday' parameter
|
||||
const [birthday, birthdayErr] = $(params.birthday).optional.nullable.string().pipe(isValidBirthday).$;
|
||||
if (birthdayErr) return rej('invalid birthday param');
|
||||
if (birthday !== undefined) user.account.profile.birthday = birthday;
|
||||
if (birthday !== undefined) user.profile.birthday = birthday;
|
||||
|
||||
// Get 'avatarId' parameter
|
||||
const [avatarId, avatarIdErr] = $(params.avatarId).optional.id().$;
|
||||
|
@ -49,12 +49,12 @@ module.exports = async (params, user, _, isSecure) => new Promise(async (res, re
|
|||
// Get 'isBot' parameter
|
||||
const [isBot, isBotErr] = $(params.isBot).optional.boolean().$;
|
||||
if (isBotErr) return rej('invalid isBot param');
|
||||
if (isBot != null) user.account.isBot = isBot;
|
||||
if (isBot != null) user.isBot = isBot;
|
||||
|
||||
// Get 'autoWatch' parameter
|
||||
const [autoWatch, autoWatchErr] = $(params.autoWatch).optional.boolean().$;
|
||||
if (autoWatchErr) return rej('invalid autoWatch param');
|
||||
if (autoWatch != null) user.account.settings.autoWatch = autoWatch;
|
||||
if (autoWatch != null) user.settings.autoWatch = autoWatch;
|
||||
|
||||
await User.update(user._id, {
|
||||
$set: {
|
||||
|
@ -62,9 +62,9 @@ module.exports = async (params, user, _, isSecure) => new Promise(async (res, re
|
|||
description: user.description,
|
||||
avatarId: user.avatarId,
|
||||
bannerId: user.bannerId,
|
||||
'account.profile': user.account.profile,
|
||||
'account.isBot': user.account.isBot,
|
||||
'account.settings': user.account.settings
|
||||
'profile': user.profile,
|
||||
'isBot': user.isBot,
|
||||
'settings': user.settings
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -22,14 +22,14 @@ module.exports = async (params, user) => new Promise(async (res, rej) => {
|
|||
if (valueErr) return rej('invalid value param');
|
||||
|
||||
const x = {};
|
||||
x[`account.clientSettings.${name}`] = value;
|
||||
x[`clientSettings.${name}`] = value;
|
||||
|
||||
await User.update(user._id, {
|
||||
$set: x
|
||||
});
|
||||
|
||||
// Serialize
|
||||
user.account.clientSettings[name] = value;
|
||||
user.clientSettings[name] = value;
|
||||
const iObj = await pack(user, user, {
|
||||
detail: true,
|
||||
includeSecrets: true
|
||||
|
|
|
@ -26,7 +26,7 @@ module.exports = async (params, user) => new Promise(async (res, rej) => {
|
|||
if (home) {
|
||||
await User.update(user._id, {
|
||||
$set: {
|
||||
'account.clientSettings.home': home
|
||||
'clientSettings.home': home
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -38,7 +38,7 @@ module.exports = async (params, user) => new Promise(async (res, rej) => {
|
|||
} else {
|
||||
if (id == null && data == null) return rej('you need to set id and data params if home param unset');
|
||||
|
||||
const _home = user.account.clientSettings.home;
|
||||
const _home = user.clientSettings.home;
|
||||
const widget = _home.find(w => w.id == id);
|
||||
|
||||
if (widget == null) return rej('widget not found');
|
||||
|
@ -47,7 +47,7 @@ module.exports = async (params, user) => new Promise(async (res, rej) => {
|
|||
|
||||
await User.update(user._id, {
|
||||
$set: {
|
||||
'account.clientSettings.home': _home
|
||||
'clientSettings.home': _home
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ module.exports = async (params, user) => new Promise(async (res, rej) => {
|
|||
if (home) {
|
||||
await User.update(user._id, {
|
||||
$set: {
|
||||
'account.clientSettings.mobileHome': home
|
||||
'clientSettings.mobileHome': home
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -37,7 +37,7 @@ module.exports = async (params, user) => new Promise(async (res, rej) => {
|
|||
} else {
|
||||
if (id == null && data == null) return rej('you need to set id and data params if home param unset');
|
||||
|
||||
const _home = user.account.clientSettings.mobileHome || [];
|
||||
const _home = user.clientSettings.mobileHome || [];
|
||||
const widget = _home.find(w => w.id == id);
|
||||
|
||||
if (widget == null) return rej('widget not found');
|
||||
|
@ -46,7 +46,7 @@ module.exports = async (params, user) => new Promise(async (res, rej) => {
|
|||
|
||||
await User.update(user._id, {
|
||||
$set: {
|
||||
'account.clientSettings.mobileHome': _home
|
||||
'clientSettings.mobileHome': _home
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ module.exports = (params, user) => new Promise(async (res, rej) => {
|
|||
}, {
|
||||
fields: {
|
||||
data: false,
|
||||
'account.profile': false
|
||||
'profile': false
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ module.exports = (params, user) => new Promise(async (res, rej) => {
|
|||
}, {
|
||||
fields: {
|
||||
data: false,
|
||||
'account.profile': false
|
||||
'profile': false
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -100,7 +100,7 @@ module.exports = (params, user) => new Promise(async (res, rej) => {
|
|||
});
|
||||
|
||||
// この投稿をWatchする
|
||||
if (user.account.settings.autoWatch !== false) {
|
||||
if (user.settings.autoWatch !== false) {
|
||||
watch(user._id, note);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -32,7 +32,7 @@ module.exports = (params, me) => new Promise(async (res, rej) => {
|
|||
},
|
||||
$or: [
|
||||
{
|
||||
'account.lastUsedAt': {
|
||||
'lastUsedAt': {
|
||||
$gte: new Date(Date.now() - ms('7days'))
|
||||
}
|
||||
}, {
|
||||
|
|
|
@ -37,7 +37,7 @@ export default async (req: express.Request, res: express.Response) => {
|
|||
}, {
|
||||
fields: {
|
||||
data: false,
|
||||
'account.profile': false
|
||||
'profile': false
|
||||
}
|
||||
}) as ILocalUser;
|
||||
|
||||
|
@ -48,15 +48,13 @@ export default async (req: express.Request, res: express.Response) => {
|
|||
return;
|
||||
}
|
||||
|
||||
const account = user.account;
|
||||
|
||||
// Compare password
|
||||
const same = await bcrypt.compare(password, account.password);
|
||||
const same = await bcrypt.compare(password, password);
|
||||
|
||||
if (same) {
|
||||
if (account.twoFactorEnabled) {
|
||||
if (user.twoFactorEnabled) {
|
||||
const verified = (speakeasy as any).totp.verify({
|
||||
secret: account.twoFactorSecret,
|
||||
secret: user.twoFactorSecret,
|
||||
encoding: 'base32',
|
||||
token: token
|
||||
});
|
||||
|
|
|
@ -119,44 +119,29 @@ export default async (req: express.Request, res: express.Response) => {
|
|||
usernameLower: username.toLowerCase(),
|
||||
host: null,
|
||||
hostLower: null,
|
||||
account: {
|
||||
keypair: generateKeypair(),
|
||||
token: secret,
|
||||
email: null,
|
||||
links: null,
|
||||
password: hash,
|
||||
profile: {
|
||||
bio: null,
|
||||
birthday: null,
|
||||
blood: null,
|
||||
gender: null,
|
||||
handedness: null,
|
||||
height: null,
|
||||
location: null,
|
||||
weight: null
|
||||
},
|
||||
settings: {
|
||||
autoWatch: true
|
||||
},
|
||||
clientSettings: {
|
||||
home: homeData
|
||||
}
|
||||
keypair: generateKeypair(),
|
||||
token: secret,
|
||||
email: null,
|
||||
links: null,
|
||||
password: hash,
|
||||
profile: {
|
||||
bio: null,
|
||||
birthday: null,
|
||||
blood: null,
|
||||
gender: null,
|
||||
handedness: null,
|
||||
height: null,
|
||||
location: null,
|
||||
weight: null
|
||||
},
|
||||
settings: {
|
||||
autoWatch: true
|
||||
},
|
||||
clientSettings: {
|
||||
home: homeData
|
||||
}
|
||||
});
|
||||
|
||||
// Response
|
||||
res.send(await pack(account));
|
||||
|
||||
// Create search index
|
||||
if (config.elasticsearch.enable) {
|
||||
const es = require('../../db/elasticsearch');
|
||||
es.index({
|
||||
index: 'misskey',
|
||||
type: 'user',
|
||||
id: account._id.toString(),
|
||||
body: {
|
||||
username: username
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
@ -40,10 +40,10 @@ module.exports = (app: express.Application) => {
|
|||
|
||||
const user = await User.findOneAndUpdate({
|
||||
host: null,
|
||||
'account.token': userToken
|
||||
'token': userToken
|
||||
}, {
|
||||
$set: {
|
||||
'account.twitter': null
|
||||
'twitter': null
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -128,7 +128,7 @@ module.exports = (app: express.Application) => {
|
|||
|
||||
const user = await User.findOne({
|
||||
host: null,
|
||||
'account.twitter.userId': result.userId
|
||||
'twitter.userId': result.userId
|
||||
});
|
||||
|
||||
if (user == null) {
|
||||
|
@ -151,10 +151,10 @@ module.exports = (app: express.Application) => {
|
|||
|
||||
const user = await User.findOneAndUpdate({
|
||||
host: null,
|
||||
'account.token': userToken
|
||||
'token': userToken
|
||||
}, {
|
||||
$set: {
|
||||
'account.twitter': {
|
||||
'twitter': {
|
||||
accessToken: result.accessToken,
|
||||
accessTokenSecret: result.accessTokenSecret,
|
||||
userId: result.userId,
|
||||
|
|
|
@ -74,7 +74,7 @@ export default async function(request: websocket.request, connection: websocket.
|
|||
// Update lastUsedAt
|
||||
User.update({ _id: user._id }, {
|
||||
$set: {
|
||||
'account.lastUsedAt': new Date()
|
||||
'lastUsedAt': new Date()
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
|
|
@ -97,7 +97,7 @@ function authenticate(token: string): Promise<IUser> {
|
|||
const user: IUser = await User
|
||||
.findOne({
|
||||
host: null,
|
||||
'account.token': token
|
||||
'token': token
|
||||
});
|
||||
|
||||
resolve(user);
|
||||
|
|
|
@ -60,13 +60,13 @@ export default async function(follower: IUser, followee: IUser, activity?) {
|
|||
const content = renderFollow(follower, followee);
|
||||
content['@context'] = context;
|
||||
|
||||
deliver(follower, content, followee.account.inbox).save();
|
||||
deliver(follower, content, followee.inbox).save();
|
||||
}
|
||||
|
||||
if (isRemoteUser(follower) && isLocalUser(followee)) {
|
||||
const content = renderAccept(activity);
|
||||
content['@context'] = context;
|
||||
|
||||
deliver(followee, content, follower.account.inbox).save();
|
||||
deliver(followee, content, follower.inbox).save();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -59,6 +59,6 @@ export default async function(follower: IUser, followee: IUser, activity?) {
|
|||
const content = renderUndo(renderFollow(follower, followee));
|
||||
content['@context'] = context;
|
||||
|
||||
deliver(follower, content, followee.account.inbox).save();
|
||||
deliver(follower, content, followee.inbox).save();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -78,7 +78,7 @@ export default async (user: IUser, data: {
|
|||
host: user.host,
|
||||
hostLower: user.hostLower,
|
||||
account: isLocalUser(user) ? {} : {
|
||||
inbox: user.account.inbox
|
||||
inbox: user.inbox
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -133,7 +133,7 @@ export default async (user: IUser, data: {
|
|||
|
||||
// 投稿がリプライかつ投稿者がローカルユーザーかつリプライ先の投稿の投稿者がリモートユーザーなら配送
|
||||
if (data.reply && isLocalUser(user) && isRemoteUser(data.reply._user)) {
|
||||
deliver(user, content, data.reply._user.account.inbox).save();
|
||||
deliver(user, content, data.reply._user.inbox).save();
|
||||
}
|
||||
|
||||
Promise.all(followers.map(follower => {
|
||||
|
@ -145,7 +145,7 @@ export default async (user: IUser, data: {
|
|||
} else {
|
||||
// フォロワーがリモートユーザーかつ投稿者がローカルユーザーなら投稿を配信
|
||||
if (isLocalUser(user)) {
|
||||
deliver(user, content, follower.account.inbox).save();
|
||||
deliver(user, content, follower.inbox).save();
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
@ -242,7 +242,7 @@ export default async (user: IUser, data: {
|
|||
});
|
||||
|
||||
// この投稿をWatchする
|
||||
if (isLocalUser(user) && user.account.settings.autoWatch !== false) {
|
||||
if (isLocalUser(user) && user.settings.autoWatch !== false) {
|
||||
watch(user._id, data.reply);
|
||||
}
|
||||
|
||||
|
@ -277,7 +277,7 @@ export default async (user: IUser, data: {
|
|||
});
|
||||
|
||||
// この投稿をWatchする
|
||||
if (isLocalUser(user) && user.account.settings.autoWatch !== false) {
|
||||
if (isLocalUser(user) && user.settings.autoWatch !== false) {
|
||||
watch(user._id, data.renote);
|
||||
}
|
||||
|
||||
|
|
|
@ -78,7 +78,7 @@ export default async (user: IUser, note: INote, reaction: string) => new Promise
|
|||
});
|
||||
|
||||
// ユーザーがローカルユーザーかつ自動ウォッチ設定がオンならばこの投稿をWatchする
|
||||
if (isLocalUser(user) && user.account.settings.autoWatch !== false) {
|
||||
if (isLocalUser(user) && user.settings.autoWatch !== false) {
|
||||
watch(user._id, note);
|
||||
}
|
||||
|
||||
|
@ -88,7 +88,7 @@ export default async (user: IUser, note: INote, reaction: string) => new Promise
|
|||
|
||||
// リアクターがローカルユーザーかつリアクション対象がリモートユーザーの投稿なら配送
|
||||
if (isLocalUser(user) && isRemoteUser(note._user)) {
|
||||
deliver(user, content, note._user.account.inbox).save();
|
||||
deliver(user, content, note._user.inbox).save();
|
||||
}
|
||||
//#endregion
|
||||
});
|
||||
|
|
16
test/api.ts
16
test/api.ts
|
@ -32,7 +32,7 @@ const async = fn => (done) => {
|
|||
|
||||
const request = (endpoint, params, me?) => new Promise<any>((ok, ng) => {
|
||||
const auth = me ? {
|
||||
i: me.account.token
|
||||
i: me.token
|
||||
} : {};
|
||||
|
||||
_chai.request(server)
|
||||
|
@ -157,10 +157,10 @@ describe('API', () => {
|
|||
res.should.have.status(200);
|
||||
res.body.should.be.a('object');
|
||||
res.body.should.have.property('name').eql(myName);
|
||||
res.body.should.have.nested.property('account.profile').a('object');
|
||||
res.body.should.have.nested.property('account.profile.location').eql(myLocation);
|
||||
res.body.should.have.nested.property('account.profile.birthday').eql(myBirthday);
|
||||
res.body.should.have.nested.property('account.profile.gender').eql('female');
|
||||
res.body.should.have.nested.property('profile').a('object');
|
||||
res.body.should.have.nested.property('profile.location').eql(myLocation);
|
||||
res.body.should.have.nested.property('profile.birthday').eql(myBirthday);
|
||||
res.body.should.have.nested.property('profile.gender').eql('female');
|
||||
}));
|
||||
|
||||
it('名前を空白にできない', async(async () => {
|
||||
|
@ -180,8 +180,8 @@ describe('API', () => {
|
|||
}, me);
|
||||
res.should.have.status(200);
|
||||
res.body.should.be.a('object');
|
||||
res.body.should.have.nested.property('account.profile').a('object');
|
||||
res.body.should.have.nested.property('account.profile.birthday').eql(null);
|
||||
res.body.should.have.nested.property('profile').a('object');
|
||||
res.body.should.have.nested.property('profile.birthday').eql(null);
|
||||
}));
|
||||
|
||||
it('不正な誕生日の形式で怒られる', async(async () => {
|
||||
|
@ -736,7 +736,7 @@ describe('API', () => {
|
|||
const me = await insertSakurako();
|
||||
const res = await _chai.request(server)
|
||||
.post('/drive/files/create')
|
||||
.field('i', me.account.token)
|
||||
.field('i', me.token)
|
||||
.attach('file', fs.readFileSync(__dirname + '/resources/Lenna.png'), 'Lenna.png');
|
||||
res.should.have.status(200);
|
||||
res.body.should.be.a('object');
|
||||
|
|
58
tools/migration/nighthike/12.js
Normal file
58
tools/migration/nighthike/12.js
Normal file
|
@ -0,0 +1,58 @@
|
|||
// for Node.js interpret
|
||||
|
||||
const { default: User } = require('../../../built/models/user');
|
||||
const { generate } = require('../../../built/crypto_key');
|
||||
const { default: zip } = require('@prezzemolo/zip')
|
||||
|
||||
const migrate = async (user) => {
|
||||
const result = await User.update(user._id, {
|
||||
$unset: {
|
||||
account: ''
|
||||
},
|
||||
$set: {
|
||||
host: null,
|
||||
hostLower: null,
|
||||
email: user.account.email,
|
||||
links: user.account.links,
|
||||
password: user.account.password,
|
||||
token: user.account.token,
|
||||
twitter: user.account.twitter,
|
||||
line: user.account.line,
|
||||
profile: user.account.profile,
|
||||
lastUsedAt: user.account.lastUsedAt,
|
||||
isBot: user.account.isBot,
|
||||
isPro: user.account.isPro,
|
||||
twoFactorSecret: user.account.twoFactorSecret,
|
||||
twoFactorEnabled: user.account.twoFactorEnabled,
|
||||
clientSettings: user.account.clientSettings,
|
||||
settings: user.account.settings,
|
||||
keypair: user.account.keypair
|
||||
}
|
||||
});
|
||||
return result.ok === 1;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const count = await User.count({});
|
||||
|
||||
const dop = Number.parseInt(process.argv[2]) || 5
|
||||
const idop = ((count - (count % dop)) / dop) + 1
|
||||
|
||||
return zip(
|
||||
1,
|
||||
async (time) => {
|
||||
console.log(`${time} / ${idop}`)
|
||||
const doc = await User.find({}, {
|
||||
limit: dop, skip: time * dop
|
||||
})
|
||||
return Promise.all(doc.map(migrate))
|
||||
},
|
||||
idop
|
||||
).then(a => {
|
||||
const rv = []
|
||||
a.forEach(e => rv.push(...e))
|
||||
return rv
|
||||
})
|
||||
}
|
||||
|
||||
main().then(console.dir).catch(console.error)
|
Loading…
Reference in a new issue