@@ -22,6 +24,11 @@
import Vue from 'vue';
export default Vue.extend({
+ inject: {
+ horizonGrouped: {
+ default: false
+ }
+ },
props: {
value: {
required: false
@@ -30,11 +37,22 @@ export default Vue.extend({
type: Boolean,
required: false
},
+ disabled: {
+ type: Boolean,
+ required: false
+ },
styl: {
type: String,
required: false,
default: 'line'
- }
+ },
+ inline: {
+ type: Boolean,
+ required: false,
+ default(): boolean {
+ return this.horizonGrouped;
+ }
+ },
},
data() {
return {
@@ -122,7 +140,7 @@ root(fill)
transition-duration 0.3s
font-size 16px
line-height 32px
- color rgba(#000, 0.54)
+ color var(--inputLabel)
pointer-events none
//will-change transform
transform-origin top left
@@ -171,6 +189,9 @@ root(fill)
margin 6px 0
font-size 13px
+ &:empty
+ display none
+
*
margin 0
@@ -200,4 +221,14 @@ root(fill)
&:not(.fill)
root(false)
+ &.inline
+ display inline-block
+ margin 0
+
+ &.disabled
+ opacity 0.7
+
+ &, *
+ cursor not-allowed !important
+
diff --git a/src/client/app/common/views/filters/index.ts b/src/client/app/common/views/filters/index.ts
index 1759c19c2c..3dccbfc923 100644
--- a/src/client/app/common/views/filters/index.ts
+++ b/src/client/app/common/views/filters/index.ts
@@ -1,3 +1,10 @@
+import Vue from 'vue';
+import * as JSON5 from 'json5';
+
+Vue.filter('json5', x => {
+ return JSON5.stringify(x, null, 2);
+});
+
require('./bytes');
require('./number');
require('./user');
diff --git a/src/client/app/common/views/filters/user.ts b/src/client/app/common/views/filters/user.ts
index e5220229b7..9d4ae5c58b 100644
--- a/src/client/app/common/views/filters/user.ts
+++ b/src/client/app/common/views/filters/user.ts
@@ -1,6 +1,7 @@
import Vue from 'vue';
import getAcct from '../../../../../misc/acct/render';
import getUserName from '../../../../../misc/get-user-name';
+import { url } from '../../../config';
Vue.filter('acct', user => {
return getAcct(user);
@@ -10,6 +11,6 @@ Vue.filter('userName', user => {
return getUserName(user);
});
-Vue.filter('userPage', (user, path?) => {
- return `/@${Vue.filter('acct')(user)}${(path ? `/${path}` : '')}`;
+Vue.filter('userPage', (user, path?, absolute = false) => {
+ return `${absolute ? url : ''}/@${Vue.filter('acct')(user)}${(path ? `/${path}` : '')}`;
});
diff --git a/src/misc/acct/parse.ts b/src/misc/acct/parse.ts
index 0c00fccef6..164bd7bcd7 100644
--- a/src/misc/acct/parse.ts
+++ b/src/misc/acct/parse.ts
@@ -1,4 +1,5 @@
export default (acct: string) => {
+ if (acct.startsWith('@')) acct = acct.substr(1);
const splitted = acct.split('@', 2);
return { username: splitted[0], host: splitted[1] || null };
};
diff --git a/src/models/user.ts b/src/models/user.ts
index 22eecb571b..bea0261dc2 100644
--- a/src/models/user.ts
+++ b/src/models/user.ts
@@ -26,6 +26,7 @@ export default User;
type IUserBase = {
_id: mongo.ObjectID;
createdAt: Date;
+ updatedAt?: Date;
deletedAt?: Date;
followersCount: number;
followingCount: number;
@@ -104,7 +105,6 @@ export interface ILocalUser extends IUserBase {
birthday: string; // 'YYYY-MM-DD'
tags: string[];
};
- lastUsedAt: Date;
isCat: boolean;
isAdmin?: boolean;
isModerator?: boolean;
@@ -132,7 +132,7 @@ export interface IRemoteUser extends IUserBase {
id: string;
publicKeyPem: string;
};
- updatedAt: Date;
+ lastFetchedAt: Date;
isAdmin: false;
isModerator: false;
}
diff --git a/src/remote/activitypub/models/note.ts b/src/remote/activitypub/models/note.ts
index 48a02e79bd..82d6d267c2 100644
--- a/src/remote/activitypub/models/note.ts
+++ b/src/remote/activitypub/models/note.ts
@@ -104,7 +104,7 @@ export async function createNote(value: any, resolver?: Resolver, silent = false
});
// ユーザーの情報が古かったらついでに更新しておく
- if (actor.updatedAt == null || Date.now() - actor.updatedAt.getTime() > 1000 * 60 * 60 * 24) {
+ if (actor.lastFetchedAt == null || Date.now() - actor.lastFetchedAt.getTime() > 1000 * 60 * 60 * 24) {
updatePerson(note.attributedTo);
}
diff --git a/src/remote/activitypub/models/person.ts b/src/remote/activitypub/models/person.ts
index d78bc15c95..b2ca2eccae 100644
--- a/src/remote/activitypub/models/person.ts
+++ b/src/remote/activitypub/models/person.ts
@@ -143,7 +143,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise
new Promise(async (res, rej) => {
+ const user = await User.findOne({
+ _id: ps.userId
+ });
+
+ if (user == null) {
+ return rej('user not found');
+ }
+
+ if (user.isAdmin) {
+ return rej('cannot reset password of admin');
+ }
+
+ const passwd = rndstr('a-zA-Z0-9', 8);
+
+ // Generate hash of password
+ const hash = bcrypt.hashSync(passwd);
+
+ await User.findOneAndUpdate({
+ _id: user._id
+ }, {
+ $set: {
+ password: hash
+ }
+ });
+
+ res({
+ password: passwd
+ });
+}));
diff --git a/src/server/api/endpoints/admin/show-user.ts b/src/server/api/endpoints/admin/show-user.ts
new file mode 100644
index 0000000000..490b685352
--- /dev/null
+++ b/src/server/api/endpoints/admin/show-user.ts
@@ -0,0 +1,40 @@
+import $ from 'cafy';
+import ID, { transform } from '../../../../misc/cafy-id';
+import define from '../../define';
+import User from '../../../../models/user';
+
+export const meta = {
+ desc: {
+ 'ja-JP': '指定したユーザーの情報を取得します。',
+ },
+
+ requireCredential: true,
+ requireModerator: true,
+
+ params: {
+ userId: {
+ validator: $.type(ID),
+ transform: transform,
+ desc: {
+ 'ja-JP': '対象のユーザーID',
+ 'en-US': 'The user ID which you want to suspend'
+ }
+ },
+ }
+};
+
+export default define(meta, (ps, me) => new Promise(async (res, rej) => {
+ const user = await User.findOne({
+ _id: ps.userId
+ });
+
+ if (user == null) {
+ return rej('user not found');
+ }
+
+ if (me.isModerator && user.isAdmin) {
+ return rej('cannot show info of admin');
+ }
+
+ res(user);
+}));
diff --git a/src/server/api/endpoints/users.ts b/src/server/api/endpoints/users.ts
index 203b4a53c8..aef5bd8507 100644
--- a/src/server/api/endpoints/users.ts
+++ b/src/server/api/endpoints/users.ts
@@ -17,7 +17,23 @@ export const meta = {
},
sort: {
- validator: $.str.optional.or('+follower|-follower'),
+ validator: $.str.optional.or([
+ '+follower',
+ '-follower',
+ '+createdAt',
+ '-createdAt',
+ '+updatedAt',
+ '-updatedAt',
+ ]),
+ },
+
+ origin: {
+ validator: $.str.optional.or([
+ 'combined',
+ 'local',
+ 'remote',
+ ]),
+ default: 'local'
}
}
};
@@ -33,6 +49,22 @@ export default define(meta, (ps, me) => new Promise(async (res, rej) => {
_sort = {
followersCount: 1
};
+ } else if (ps.sort == '+createdAt') {
+ _sort = {
+ createdAt: -1
+ };
+ } else if (ps.sort == '+updatedAt') {
+ _sort = {
+ updatedAt: -1
+ };
+ } else if (ps.sort == '-createdAt') {
+ _sort = {
+ createdAt: 1
+ };
+ } else if (ps.sort == '-updatedAt') {
+ _sort = {
+ updatedAt: 1
+ };
}
} else {
_sort = {
@@ -40,14 +72,17 @@ export default define(meta, (ps, me) => new Promise(async (res, rej) => {
};
}
+ const q =
+ ps.origin == 'local' ? { host: null } :
+ ps.origin == 'remote' ? { host: { $ne: null } } :
+ {};
+
const users = await User
- .find({
- host: null
- }, {
+ .find(q, {
limit: ps.limit,
sort: _sort,
skip: ps.offset
});
- res(await Promise.all(users.map(user => pack(user, me))));
+ res(await Promise.all(users.map(user => pack(user, me, { detail: true }))));
}));
diff --git a/src/server/api/endpoints/users/show.ts b/src/server/api/endpoints/users/show.ts
index 6e4cf514de..fd26554709 100644
--- a/src/server/api/endpoints/users/show.ts
+++ b/src/server/api/endpoints/users/show.ts
@@ -80,7 +80,7 @@ export default define(meta, (ps, me) => new Promise(async (res, rej) => {
}));
if (isRemoteUser(user)) {
- if (user.updatedAt == null || Date.now() - user.updatedAt.getTime() > 1000 * 60 * 60 * 24) {
+ if (user.lastFetchedAt == null || Date.now() - user.lastFetchedAt.getTime() > 1000 * 60 * 60 * 24) {
resolveRemoteUser(ps.username, ps.host, { }, true);
}
}
diff --git a/src/services/note/create.ts b/src/services/note/create.ts
index b512fe2dda..eac0185e7a 100644
--- a/src/services/note/create.ts
+++ b/src/services/note/create.ts
@@ -633,6 +633,9 @@ function saveReply(reply: INote, note: INote) {
function incNotesCountOfUser(user: IUser) {
User.update({ _id: user._id }, {
+ $set: {
+ updatedAt: new Date()
+ },
$inc: {
notesCount: 1
}