Merge branch 'develop'
This commit is contained in:
commit
796252357e
|
@ -84,40 +84,54 @@ redis:
|
|||
drive:
|
||||
storage: 'fs'
|
||||
|
||||
# OR
|
||||
# OR
|
||||
|
||||
# storage: 'minio'
|
||||
# bucket:
|
||||
# prefix:
|
||||
# config:
|
||||
# endPoint:
|
||||
# port:
|
||||
# useSSL:
|
||||
# accessKey:
|
||||
# secretKey:
|
||||
#drive:
|
||||
# storage: 'minio'
|
||||
# bucket:
|
||||
# prefix:
|
||||
# config:
|
||||
# endPoint:
|
||||
# port:
|
||||
# useSSL:
|
||||
# accessKey:
|
||||
# secretKey:
|
||||
|
||||
# S3 example
|
||||
# storage: 'minio'
|
||||
# bucket: bucket-name
|
||||
# prefix: files
|
||||
# config:
|
||||
# endPoint: s3-us-west-2.amazonaws.com
|
||||
# region: us-west-2
|
||||
# useSSL: true
|
||||
# accessKey: XXX
|
||||
# secretKey: YYY
|
||||
# S3/GCS example
|
||||
#
|
||||
# * Replace <endpoint> to
|
||||
# S3: see https://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region
|
||||
# GCS: use 'storage.googleapis.com'
|
||||
#
|
||||
# * Replace <region> to
|
||||
# S3: see https://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region
|
||||
# GCS: not needed (just delete the region line)
|
||||
#
|
||||
#drive:
|
||||
# storage: 'minio'
|
||||
# bucket: bucket-name
|
||||
# prefix: files
|
||||
# baseUrl: https://bucket-name.<endpoint>
|
||||
# config:
|
||||
# endPoint: <endpoint>
|
||||
# region: <region>
|
||||
# useSSL: true
|
||||
# accessKey: XXX
|
||||
# secretKey: YYY
|
||||
|
||||
# S3 example (with CDN, custom domain)
|
||||
# storage: 'minio'
|
||||
# bucket: drive.example.com
|
||||
# prefix: files
|
||||
# baseUrl: https://drive.example.com
|
||||
# config:
|
||||
# endPoint: s3-us-west-2.amazonaws.com
|
||||
# region: us-west-2
|
||||
# useSSL: true
|
||||
# accessKey: XXX
|
||||
# secretKey: YYY
|
||||
# S3/GCS example (with CDN, custom domain)
|
||||
#
|
||||
#drive:
|
||||
# storage: 'minio'
|
||||
# bucket: drive.example.com
|
||||
# prefix: files
|
||||
# baseUrl: https://drive.example.com
|
||||
# config:
|
||||
# endPoint: <endpoint>
|
||||
# region: <region>
|
||||
# useSSL: true
|
||||
# accessKey: XXX
|
||||
# secretKey: YYY
|
||||
|
||||
# ┌───────────────┐
|
||||
#───┘ ID generation └───────────────────────────────────────────
|
||||
|
|
20
CHANGELOG.md
20
CHANGELOG.md
|
@ -73,6 +73,26 @@ mongodb:
|
|||
8. master ブランチに戻す
|
||||
9. enjoy
|
||||
|
||||
11.12.0 (2019/05/10)
|
||||
--------------------
|
||||
### 注意
|
||||
このアップデートを適用した後、プロセスを起動(もしくは再起動)する前に[マイグレーション](#migration)の手順を実行してください
|
||||
|
||||
### Improvements
|
||||
* インスタンス運営者がおすすめアカウントを設定できるように
|
||||
* MisskeyPagesでNAME環境変数がNULLにならないように
|
||||
* MisskeyPagesにNULL環境変数を追加
|
||||
* MisskeyPagesで変数を並べ替えられるように
|
||||
* MisskeyPagesのテキストのリスト内で変数埋め込みできるように
|
||||
* 自分の指定した投稿のRenoteを全て解除するAPIを追加
|
||||
|
||||
### Fixes
|
||||
* Noteをpull取得した時にhost名がvalidateされていない問題を修正
|
||||
* みつけるで人気のタグが表示されない問題を修正
|
||||
|
||||
### その他
|
||||
* アカウントのisVerifiedフラグを廃止
|
||||
|
||||
11.11.2 (2019/05/07)
|
||||
--------------------
|
||||
### Fixes
|
||||
|
|
12
README.md
12
README.md
|
@ -105,6 +105,7 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
|
|||
<table><tr>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5888816/36da0f7c15954df0ab13f9abdf227f66/1.jpeg?token-time=2145916800&token-hash=at8QpJXJ8C0zINY_NmoMKv-MhXVoUK-YzTgaJPJzJYU%3D" alt="Hiroshi Seki" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12190916/fb7fa7983c14425f890369535b1506a4/3.png?token-time=2145916800&token-hash=oH_i7gJjNT7Ot6j9JiVwy7ZJIBqACVnzLqlz4YrDAZA%3D" alt="weepjp" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/19045173/cb91c0f345c24d4ebfd05f19906d5e26/1.png?token-time=2145916800&token-hash=o_zKBytJs_AxHwSYw_5R8eD0eSJe3RoTR3kR3Q0syN0%3D" alt="kiritan" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/13099460/43cecdbaa63a40d79bf50a96b9910b9d/1.jpe?token-time=2145916800&token-hash=bqwLTk0Wo0hUJJ8J5y7ii05bLzz-_CDA7Bo0Mp4RFU0%3D" alt="ne_moni" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12913507/f7181eacafe8469a93033d85f5969c29/4.jpe?token-time=2145916800&token-hash=zEyJqVM7u9d8Ri-65fJYSJcWF1jBH1nJ5a3taRzrTmw%3D" alt="Melilot" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5670915/ee175f0bfb6347ffa4ea101a8c097bff/1.jpg?token-time=2145916800&token-hash=mPLM9CA-riFHx-myr3bLZJuH2xBRHA9se5VbHhLIOuA%3D" alt="osapon" width="100"></td>
|
||||
|
@ -112,6 +113,7 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
|
|||
</tr><tr>
|
||||
<td><a href="https://www.patreon.com/rane_hs">Hiroshi Seki</a></td>
|
||||
<td><a href="https://www.patreon.com/weepjp">weepjp</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=19045173">kiritan</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=13099460">ne_moni</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=12913507">Melilot</a></td>
|
||||
<td><a href="https://www.patreon.com/osapon">osapon</a></td>
|
||||
|
@ -127,7 +129,6 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
|
|||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5881381/6235ca5d3fb04c8e95ef5b4ff2abcc18/3.png?token-time=2145916800&token-hash=KjfQL8nf3AIf6WqzLshBYAyX44piAqOAZiYXgZS_H6A%3D" alt="YUKIMOCHI" width="100"></td>
|
||||
<td><img src="https://c8.patreon.com/2/200/17463605" alt="Sampot" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/19356899/496b4681d33b4520bd7688e0fd19c04d/2.jpeg?token-time=2145916800&token-hash=_sTj3dUBOhn9qwiJ7F19Qd-yWWfUqJC_0jG1h0agEqQ%3D" alt="sheeta.s" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/13737140/1adf7835017d479280d90fe8d30aade2/1.png?token-time=2145916800&token-hash=0pdle8h5pDZrww0BDOjdz6zO-HudeGTh36a3qi1biVU%3D" alt="Satsuki Yanagi" width="100"></td>
|
||||
</tr><tr>
|
||||
<td><a href="https://www.patreon.com/Yuzulia">YuzuRyo61</a></td>
|
||||
<td><a href="https://www.patreon.com/gutfuckllc">gutfuckllc</a></td>
|
||||
|
@ -138,9 +139,9 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
|
|||
<td><a href="https://www.patreon.com/yukimochi">YUKIMOCHI</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=17463605">Sampot</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=19356899">sheeta.s</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=13737140">Satsuki Yanagi</a></td>
|
||||
</tr></table>
|
||||
<table><tr>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/13737140/1adf7835017d479280d90fe8d30aade2/1.png?token-time=2145916800&token-hash=0pdle8h5pDZrww0BDOjdz6zO-HudeGTh36a3qi1biVU%3D" alt="Satsuki Yanagi" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/17880724/311738c8a48f4a6b9443c2445a75adde/1.jpe?token-time=2145916800&token-hash=CPxGQhKIlEaa6WUcgbyHixyKEhakiw9RFdOhsIJBQ_o%3D" alt="takimura" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/17195955/be45e5e14c3e48b2bee0456c84e19df4/4.jpe?token-time=2145916800&token-hash=UslrPVM-8TXOe8AapuNiaFYjcIJgPNcU-fKpGbfGJNI%3D" alt="Damillora" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/16900731/83884b38afc74d4cbe83c30a13b10edd/1.png?token-time=2145916800&token-hash=R5Tog8RWg0rguRoCIoir3lThokrdPvs8Utfikhc0nhY%3D" alt="Atsuko Tominaga" width="100"></td>
|
||||
|
@ -149,8 +150,8 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
|
|||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/2384390/5681180e1efb46a8b28e0e8d4c8b9037/1.jpg?token-time=2145916800&token-hash=SJcMy-Q1BcS940-LFUVOMfR7-5SgrzsEQGhYb3yowFk%3D" alt="CG" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/18072312/98e894d960314fa7bc236a72a39488fe/1.jpe?token-time=2145916800&token-hash=qA8j97lIZNc-74AuZ0p4F3ms6sKPeKjtNt2vEuwpsyo%3D" alt="Hekovic" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/4503830/ccf2cc867ea64de0b524bb2e24b9a1cb/1.jpeg?token-time=2145916800&token-hash=L55UhJ0rcuNAH3w_ryeeGN4hC6taoOixyAhraEi0bzw%3D" alt="dansup" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/619786/32cf01444db24e578cd1982c197f6fc6/1.jpeg?token-time=2145916800&token-hash=d8jBQLMOHD87KtXs5C9fk1o58DMF73pQ-dYH3uZJPBE%3D" alt="Gargron" width="100"></td>
|
||||
</tr><tr>
|
||||
<td><a href="https://www.patreon.com/user?u=13737140">Satsuki Yanagi</a></td>
|
||||
<td><a href="https://www.patreon.com/takimura">takimura</a></td>
|
||||
<td><a href="https://www.patreon.com/damillora">Damillora</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=16900731">Atsuko Tominaga</a></td>
|
||||
|
@ -159,17 +160,18 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
|
|||
<td><a href="https://www.patreon.com/Corset">CG</a></td>
|
||||
<td><a href="https://www.patreon.com/hekovic">Hekovic</a></td>
|
||||
<td><a href="https://www.patreon.com/dansup">dansup</a></td>
|
||||
<td><a href="https://www.patreon.com/mastodon">Gargron</a></td>
|
||||
</tr></table>
|
||||
<table><tr>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/619786/32cf01444db24e578cd1982c197f6fc6/1.jpeg?token-time=2145916800&token-hash=d8jBQLMOHD87KtXs5C9fk1o58DMF73pQ-dYH3uZJPBE%3D" alt="Gargron" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5731881/4b6038e6cda34c04b83a5fcce3806a93/1.png?token-time=2145916800&token-hash=hBayGfOmQH3kRMdNnDe4oCZD_9fsJWSt29xXR3KRMVk%3D" alt="Nokotaro Takeda" width="100"></td>
|
||||
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12531784/93a45137841849329ba692da92ac7c60/1.jpeg?token-time=2145916800&token-hash=vGe7wXGqmA8Q7m-kDNb6fyGdwk-Dxk4F-ut8ZZu51RM%3D" alt="Takashi Shibuya" width="100"></td>
|
||||
</tr><tr>
|
||||
<td><a href="https://www.patreon.com/mastodon">Gargron</a></td>
|
||||
<td><a href="https://www.patreon.com/takenoko">Nokotaro Takeda</a></td>
|
||||
<td><a href="https://www.patreon.com/user?u=12531784">Takashi Shibuya</a></td>
|
||||
</tr></table>
|
||||
|
||||
**Last updated:** Fri, 03 May 2019 05:33:07 UTC
|
||||
**Last updated:** Tue, 07 May 2019 11:55:07 UTC
|
||||
<!-- PATREON_END -->
|
||||
|
||||
:four_leaf_clover: Copyright
|
||||
|
|
|
@ -263,7 +263,6 @@ common:
|
|||
update-available-title: "更新があります"
|
||||
update-available: "Misskeyの新しいバージョンがあります({newer}。現在{current}を利用中)。ページを再度読み込みすると更新が適用されます。"
|
||||
my-token-regenerated: "あなたのトークンが更新されたのでサインアウトします。"
|
||||
verified-user: "公式アカウント"
|
||||
hide-password: "パスワードを隠す"
|
||||
show-password: "パスワードを表示する"
|
||||
|
||||
|
@ -339,7 +338,7 @@ auth/views/index.vue:
|
|||
sign-in: "サインインしてください"
|
||||
|
||||
common/views/pages/explore.vue:
|
||||
verified-users: "公式アカウント"
|
||||
pinned-users: "ピン留めされたユーザー"
|
||||
popular-users: "人気のユーザー"
|
||||
recently-updated-users: "最近投稿したユーザー"
|
||||
recently-registered-users: "新規ユーザー"
|
||||
|
@ -1264,7 +1263,7 @@ admin/views/instance.vue:
|
|||
invite: "招待"
|
||||
save: "保存"
|
||||
saved: "保存しました"
|
||||
user-recommendation-config: "おすすめユーザー"
|
||||
pinned-users: "ピン留めユーザー"
|
||||
email-config: "メールサーバーの設定"
|
||||
email-config-info: "メールアドレス確認やパスワードリセットの際に使われます。"
|
||||
enable-email: "メール配信を有効にする"
|
||||
|
@ -1351,12 +1350,6 @@ admin/views/users.vue:
|
|||
silence-confirm: "サイレンスしますか?"
|
||||
unmake-silence: "サイレンスの解除"
|
||||
unsilence-confirm: "サイレンスを解除しますか?"
|
||||
verify: "公式アカウントにする"
|
||||
verify-confirm: "公式アカウントにしますか?"
|
||||
verified: "公式アカウントにしました"
|
||||
unverify: "公式アカウントを解除する"
|
||||
unverify-confirm: "公式アカウントを解除しますか?"
|
||||
unverified: "公式アカウントを解除しました"
|
||||
update-remote-user: "リモートユーザー情報の更新"
|
||||
remote-user-updated: "リモートユーザー情報を更新しました"
|
||||
users:
|
||||
|
@ -1373,7 +1366,6 @@ admin/views/users.vue:
|
|||
admin: "管理者"
|
||||
moderator: "モデレーター"
|
||||
adminOrModerator: "管理者+モデレーター"
|
||||
verified: "公式アカウント"
|
||||
silenced: "サイレンス済み"
|
||||
suspended: "凍結済み"
|
||||
origin:
|
||||
|
|
13
migration/1557476068003-PinnedUsers.ts
Normal file
13
migration/1557476068003-PinnedUsers.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
import {MigrationInterface, QueryRunner} from "typeorm";
|
||||
|
||||
export class PinnedUsers1557476068003 implements MigrationInterface {
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<any> {
|
||||
await queryRunner.query(`ALTER TABLE "meta" ADD "pinnedUsers" character varying(256) array NOT NULL DEFAULT '{}'::varchar[]`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<any> {
|
||||
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "pinnedUsers"`);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "misskey",
|
||||
"author": "syuilo <i@syuilo.com>",
|
||||
"version": "11.11.2",
|
||||
"version": "11.12.0",
|
||||
"codename": "daybreak",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
|
@ -82,6 +82,14 @@
|
|||
</section>
|
||||
</ui-card>
|
||||
|
||||
<ui-card>
|
||||
<template #title>{{ $t('pinned-users') }}</template>
|
||||
<section>
|
||||
<ui-textarea v-model="pinnedUsers"></ui-textarea>
|
||||
<ui-button @click="updateMeta">{{ $t('save') }}</ui-button>
|
||||
</section>
|
||||
</ui-card>
|
||||
|
||||
<ui-card>
|
||||
<template #title>{{ $t('invite') }}</template>
|
||||
<section>
|
||||
|
@ -190,6 +198,7 @@ export default Vue.extend({
|
|||
enableServiceWorker: false,
|
||||
swPublicKey: null,
|
||||
swPrivateKey: null,
|
||||
pinnedUsers: [],
|
||||
faHeadset, faShieldAlt, faGhost, faUserPlus, farEnvelope, faBolt
|
||||
};
|
||||
},
|
||||
|
@ -239,6 +248,7 @@ export default Vue.extend({
|
|||
this.enableServiceWorker = meta.enableServiceWorker;
|
||||
this.swPublicKey = meta.swPublickey;
|
||||
this.swPrivateKey = meta.swPrivateKey;
|
||||
this.pinnedUsers = meta.pinnedUsers.join('\n');
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -297,7 +307,8 @@ export default Vue.extend({
|
|||
smtpPass: this.smtpAuth ? this.smtpPass : '',
|
||||
enableServiceWorker: this.enableServiceWorker,
|
||||
swPublicKey: this.swPublicKey,
|
||||
swPrivateKey: this.swPrivateKey
|
||||
swPrivateKey: this.swPrivateKey,
|
||||
pinnedUsers: this.pinnedUsers.split('\n')
|
||||
}).then(() => {
|
||||
this.$root.dialog({
|
||||
type: 'success',
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
<span class="username">@{{ user | acct }}</span>
|
||||
<span class="is-admin" v-if="user.isAdmin">admin</span>
|
||||
<span class="is-moderator" v-if="user.isModerator">moderator</span>
|
||||
<span class="is-verified" v-if="user.isVerified" :title="$t('@.verified-user')"><fa icon="star"/></span>
|
||||
<span class="is-silenced" v-if="user.isSilenced" :title="$t('@.silenced-user')"><fa :icon="faMicrophoneSlash"/></span>
|
||||
<span class="is-suspended" v-if="user.isSuspended" :title="$t('@.suspended-user')"><fa :icon="faSnowflake"/></span>
|
||||
</header>
|
||||
|
@ -77,7 +76,6 @@ export default Vue.extend({
|
|||
background var(--noteHeaderAdminBg)
|
||||
color var(--noteHeaderAdminFg)
|
||||
|
||||
> .is-verified
|
||||
> .is-silenced
|
||||
> .is-suspended
|
||||
margin 0 0 0 .5em
|
||||
|
|
|
@ -12,10 +12,6 @@
|
|||
<x-user :user='user'/>
|
||||
<div class="actions">
|
||||
<ui-button @click="resetPassword"><fa :icon="faKey"/> {{ $t('reset-password') }}</ui-button>
|
||||
<ui-horizon-group>
|
||||
<ui-button @click="verifyUser" :disabled="verifying"><fa :icon="faCertificate"/> {{ $t('verify') }}</ui-button>
|
||||
<ui-button @click="unverifyUser" :disabled="unverifying">{{ $t('unverify') }}</ui-button>
|
||||
</ui-horizon-group>
|
||||
<ui-horizon-group>
|
||||
<ui-button @click="silenceUser"><fa :icon="faMicrophoneSlash"/> {{ $t('make-silence') }}</ui-button>
|
||||
<ui-button @click="unsilenceUser">{{ $t('unmake-silence') }}</ui-button>
|
||||
|
@ -47,7 +43,6 @@
|
|||
<option value="all">{{ $t('users.state.all') }}</option>
|
||||
<option value="admin">{{ $t('users.state.admin') }}</option>
|
||||
<option value="moderator">{{ $t('users.state.moderator') }}</option>
|
||||
<option value="verified">{{ $t('users.state.verified') }}</option>
|
||||
<option value="silenced">{{ $t('users.state.silenced') }}</option>
|
||||
<option value="suspended">{{ $t('users.state.suspended') }}</option>
|
||||
</ui-select>
|
||||
|
@ -71,7 +66,7 @@
|
|||
import Vue from 'vue';
|
||||
import i18n from '../../i18n';
|
||||
import parseAcct from "../../../../misc/acct/parse";
|
||||
import { faCertificate, faUsers, faTerminal, faSearch, faKey, faSync, faMicrophoneSlash } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faUsers, faTerminal, faSearch, faKey, faSync, faMicrophoneSlash } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faSnowflake } from '@fortawesome/free-regular-svg-icons';
|
||||
import XUser from './users.user.vue';
|
||||
|
||||
|
@ -84,8 +79,6 @@ export default Vue.extend({
|
|||
return {
|
||||
user: null,
|
||||
target: null,
|
||||
verifying: false,
|
||||
unverifying: false,
|
||||
suspending: false,
|
||||
unsuspending: false,
|
||||
sort: '+createdAt',
|
||||
|
@ -95,7 +88,7 @@ export default Vue.extend({
|
|||
offset: 0,
|
||||
users: [],
|
||||
existMore: false,
|
||||
faTerminal, faCertificate, faUsers, faSnowflake, faSearch, faKey, faSync, faMicrophoneSlash
|
||||
faTerminal, faUsers, faSnowflake, faSearch, faKey, faSync, faMicrophoneSlash
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -181,56 +174,6 @@ export default Vue.extend({
|
|||
});
|
||||
},
|
||||
|
||||
async verifyUser() {
|
||||
if (!await this.getConfirmed(this.$t('verify-confirm'))) return;
|
||||
|
||||
this.verifying = true;
|
||||
|
||||
const process = async () => {
|
||||
await this.$root.api('admin/verify-user', { userId: this.user.id });
|
||||
this.$root.dialog({
|
||||
type: 'success',
|
||||
text: this.$t('verified')
|
||||
});
|
||||
};
|
||||
|
||||
await process().catch(e => {
|
||||
this.$root.dialog({
|
||||
type: 'error',
|
||||
text: e.toString()
|
||||
});
|
||||
});
|
||||
|
||||
this.verifying = false;
|
||||
|
||||
this.refreshUser();
|
||||
},
|
||||
|
||||
async unverifyUser() {
|
||||
if (!await this.getConfirmed(this.$t('unverify-confirm'))) return;
|
||||
|
||||
this.unverifying = true;
|
||||
|
||||
const process = async () => {
|
||||
await this.$root.api('admin/unverify-user', { userId: this.user.id });
|
||||
this.$root.dialog({
|
||||
type: 'success',
|
||||
text: this.$t('unverified')
|
||||
});
|
||||
};
|
||||
|
||||
await process().catch(e => {
|
||||
this.$root.dialog({
|
||||
type: 'error',
|
||||
text: e.toString()
|
||||
});
|
||||
});
|
||||
|
||||
this.unverifying = false;
|
||||
|
||||
this.refreshUser();
|
||||
},
|
||||
|
||||
async silenceUser() {
|
||||
if (!await this.getConfirmed(this.$t('silence-confirm'))) return;
|
||||
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
<span class="is-bot" v-if="note.user.isBot">bot</span>
|
||||
<span class="is-cat" v-if="note.user.isCat">cat</span>
|
||||
<span class="username"><mk-acct :user="note.user"/></span>
|
||||
<span class="is-verified" v-if="note.user.isVerified" :title="$t('@.verified-user')"><fa icon="star"/></span>
|
||||
<div class="info">
|
||||
<span class="app" v-if="note.app && !mini && $store.state.settings.showVia">via <b>{{ note.app.name }}</b></span>
|
||||
<span class="mobile" v-if="note.viaMobile"><fa icon="mobile-alt"/></span>
|
||||
|
@ -95,10 +94,6 @@ export default Vue.extend({
|
|||
color var(--noteHeaderAcct)
|
||||
flex-shrink 2147483647
|
||||
|
||||
> .is-verified
|
||||
margin 0 .5em 0 0
|
||||
color #4dabf7
|
||||
|
||||
> .info
|
||||
margin-left auto
|
||||
font-size 0.9em
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<x-container :removable="removable" @remove="() => $emit('remove')" :error="error" :warn="warn">
|
||||
<x-container :removable="removable" @remove="() => $emit('remove')" :error="error" :warn="warn" :draggable="draggable">
|
||||
<template #header><fa v-if="icon" :icon="icon"/> <template v-if="title">{{ title }} <span class="turmquns" v-if="typeText">({{ typeText }})</span></template><template v-else-if="typeText">{{ typeText }}</template></template>
|
||||
<template #func>
|
||||
<button @click="changeType()">
|
||||
|
@ -93,6 +93,10 @@ export default Vue.extend({
|
|||
fnSlots: {
|
||||
required: false,
|
||||
},
|
||||
draggable: {
|
||||
required: false,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
|
|
|
@ -53,20 +53,19 @@
|
|||
<ui-container :body-togglable="true">
|
||||
<template #header><fa :icon="faMagic"/> {{ $t('variables') }}</template>
|
||||
<div class="qmuvgica">
|
||||
<div class="variables" v-show="variables.length > 0">
|
||||
<template v-for="variable in variables">
|
||||
<x-variable
|
||||
:value="variable"
|
||||
:removable="true"
|
||||
@input="v => updateVariable(v)"
|
||||
@remove="() => removeVariable(variable)"
|
||||
:key="variable.name"
|
||||
:ai-script="aiScript"
|
||||
:name="variable.name"
|
||||
:title="variable.name"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
<x-draggable tag="div" class="variables" v-show="variables.length > 0" :list="variables" handle=".drag-handle" :group="{ name: 'variables' }" animation="150" swap-threshold="0.5">
|
||||
<x-variable v-for="variable in variables"
|
||||
:value="variable"
|
||||
:removable="true"
|
||||
@input="v => updateVariable(v)"
|
||||
@remove="() => removeVariable(variable)"
|
||||
:key="variable.name"
|
||||
:ai-script="aiScript"
|
||||
:name="variable.name"
|
||||
:title="variable.name"
|
||||
:draggable="true"
|
||||
/>
|
||||
</x-draggable>
|
||||
|
||||
<ui-button @click="addVariable()" class="add" v-if="!readonly"><fa :icon="faPlus"/></ui-button>
|
||||
|
||||
|
@ -92,6 +91,7 @@
|
|||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import * as XDraggable from 'vuedraggable';
|
||||
import { faICursor, faPlus, faMagic, faCog, faCode, faExternalLinkSquareAlt } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faSave, faStickyNote, faTrashAlt } from '@fortawesome/free-regular-svg-icons';
|
||||
import i18n from '../../../../i18n';
|
||||
|
@ -107,7 +107,7 @@ export default Vue.extend({
|
|||
i18n: i18n('pages'),
|
||||
|
||||
components: {
|
||||
XVariable, XBlocks
|
||||
XDraggable, XVariable, XBlocks
|
||||
},
|
||||
|
||||
props: {
|
||||
|
|
|
@ -13,8 +13,8 @@
|
|||
<template #header><fa :icon="faHashtag" fixed-width/>{{ $t('popular-tags') }}</template>
|
||||
|
||||
<div class="vxjfqztj">
|
||||
<router-link v-for="tag in tagsLocal" :to="`/explore/tags/${tag.name}`" :key="'local:' + tag.name" class="local">{{ tag.name }}</router-link>
|
||||
<router-link v-for="tag in tagsRemote" :to="`/explore/tags/${tag.name}`" :key="'remote:' + tag.name">{{ tag.name }}</router-link>
|
||||
<router-link v-for="tag in tagsLocal" :to="`/explore/tags/${tag.tag}`" :key="'local:' + tag.tag" class="local">{{ tag.tag }}</router-link>
|
||||
<router-link v-for="tag in tagsRemote" :to="`/explore/tags/${tag.tag}`" :key="'remote:' + tag.tag">{{ tag.tag }}</router-link>
|
||||
</div>
|
||||
</ui-container>
|
||||
|
||||
|
@ -26,8 +26,8 @@
|
|||
</mk-user-list>
|
||||
|
||||
<template v-if="tag == null">
|
||||
<mk-user-list :make-promise="verifiedUsers">
|
||||
<fa :icon="faBookmark" fixed-width/>{{ $t('verified-users') }}
|
||||
<mk-user-list :make-promise="pinnedUsers">
|
||||
<fa :icon="faBookmark" fixed-width/>{{ $t('pinned-users') }}
|
||||
</mk-user-list>
|
||||
<mk-user-list :make-promise="popularUsers">
|
||||
<fa :icon="faChartLine" fixed-width/>{{ $t('popular-users') }}
|
||||
|
@ -60,12 +60,7 @@ export default Vue.extend({
|
|||
|
||||
data() {
|
||||
return {
|
||||
verifiedUsers: () => this.$root.api('users', {
|
||||
state: 'verified',
|
||||
origin: 'local',
|
||||
sort: '+follower',
|
||||
limit: 10
|
||||
}),
|
||||
pinnedUsers: () => this.$root.api('pinned-users'),
|
||||
popularUsers: () => this.$root.api('users', {
|
||||
state: 'alive',
|
||||
origin: 'local',
|
||||
|
|
|
@ -31,7 +31,7 @@ export class ASEvaluator {
|
|||
VERSION: opts.version,
|
||||
URL: opts.page ? `${opts.url}/@${opts.page.user.username}/pages/${opts.page.name}` : '',
|
||||
LOGIN: opts.visitor != null,
|
||||
NAME: opts.visitor ? opts.visitor.name : '',
|
||||
NAME: opts.visitor ? opts.visitor.name || opts.visitor.username : '',
|
||||
USERNAME: opts.visitor ? opts.visitor.username : '',
|
||||
USERID: opts.visitor ? opts.visitor.id : '',
|
||||
NOTES_COUNT: opts.visitor ? opts.visitor.notesCount : 0,
|
||||
|
@ -42,7 +42,8 @@ export class ASEvaluator {
|
|||
MY_FOLLOWERS_COUNT: opts.user ? opts.user.followersCount : 0,
|
||||
MY_FOLLOWING_COUNT: opts.user ? opts.user.followingCount : 0,
|
||||
SEED: opts.randomSeed ? opts.randomSeed : '',
|
||||
YMD: `${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()}`
|
||||
YMD: `${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()}`,
|
||||
NULL: null
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -104,7 +105,7 @@ export class ASEvaluator {
|
|||
}
|
||||
|
||||
if (block.type === 'textList') {
|
||||
return block.value.trim().split('\n');
|
||||
return this.interpolate(block.value || '', scope).trim().split('\n');
|
||||
}
|
||||
|
||||
if (block.type === 'ref') {
|
||||
|
|
|
@ -127,6 +127,7 @@ export const envVarsDef: Record<string, Type> = {
|
|||
MY_FOLLOWING_COUNT: 'number',
|
||||
SEED: null,
|
||||
YMD: 'string',
|
||||
NULL: null,
|
||||
};
|
||||
|
||||
export function isLiteralBlock(v: Block) {
|
||||
|
|
|
@ -69,6 +69,11 @@ export class Meta {
|
|||
})
|
||||
public langs: string[];
|
||||
|
||||
@Column('varchar', {
|
||||
length: 256, array: true, default: '{}'
|
||||
})
|
||||
public pinnedUsers: string[];
|
||||
|
||||
@Column('varchar', {
|
||||
length: 256, array: true, default: '{}'
|
||||
})
|
||||
|
|
|
@ -157,11 +157,6 @@ export class User {
|
|||
})
|
||||
public isModerator: boolean;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
public isVerified: boolean;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 128, array: true, default: '{}'
|
||||
})
|
||||
|
|
|
@ -87,7 +87,6 @@ export class UserRepository extends Repository<User> {
|
|||
isAdmin: user.isAdmin || falsy,
|
||||
isBot: user.isBot || falsy,
|
||||
isCat: user.isCat || falsy,
|
||||
isVerified: user.isVerified || falsy,
|
||||
|
||||
// カスタム絵文字添付
|
||||
emojis: user.emojis.length > 0 ? Emojis.find({
|
||||
|
@ -369,10 +368,6 @@ export const packedUserSchema = {
|
|||
nullable: bool.false, optional: bool.true,
|
||||
description: 'Whether this account is a moderator.'
|
||||
},
|
||||
isVerified: {
|
||||
type: types.boolean,
|
||||
nullable: bool.false, optional: bool.true,
|
||||
},
|
||||
isLocked: {
|
||||
type: types.boolean,
|
||||
nullable: bool.false, optional: bool.true,
|
||||
|
|
|
@ -25,6 +25,28 @@ import { ensure } from '../../../prelude/ensure';
|
|||
|
||||
const logger = apLogger;
|
||||
|
||||
export function validateNote(object: any, uri: string) {
|
||||
const expectHost = extractDbHost(uri);
|
||||
|
||||
if (object == null) {
|
||||
return new Error('invalid Note: object is null');
|
||||
}
|
||||
|
||||
if (!['Note', 'Question', 'Article'].includes(object.type)) {
|
||||
return new Error(`invalid Note: invalied object type ${object.type}`);
|
||||
}
|
||||
|
||||
if (object.id && extractDbHost(object.id) !== expectHost) {
|
||||
return new Error(`invalid Note: id has different host. expected: ${expectHost}, actual: ${extractDbHost(object.id)}`);
|
||||
}
|
||||
|
||||
if (object.attributedTo && extractDbHost(object.attributedTo) !== expectHost) {
|
||||
return new Error(`invalid Note: attributedTo has different host. expected: ${expectHost}, actual: ${extractDbHost(object.attributedTo)}`);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Noteをフェッチします。
|
||||
*
|
||||
|
@ -59,8 +81,10 @@ export async function createNote(value: any, resolver?: Resolver, silent = false
|
|||
|
||||
const object: any = await resolver.resolve(value);
|
||||
|
||||
if (!object || !['Note', 'Question', 'Article'].includes(object.type)) {
|
||||
logger.error(`invalid note: ${value}`, {
|
||||
const entryUri = value.id || value;
|
||||
const err = validateNote(object, entryUri);
|
||||
if (err) {
|
||||
logger.error(`${err.message}`, {
|
||||
resolver: {
|
||||
history: resolver.getHistory()
|
||||
},
|
||||
|
|
|
@ -36,7 +36,6 @@ export const meta = {
|
|||
'admin',
|
||||
'moderator',
|
||||
'adminOrModerator',
|
||||
'verified',
|
||||
'silenced',
|
||||
'suspended',
|
||||
]),
|
||||
|
@ -61,7 +60,6 @@ export default define(meta, async (ps, me) => {
|
|||
case 'admin': query.where('user.isAdmin = TRUE'); break;
|
||||
case 'moderator': query.where('user.isModerator = TRUE'); break;
|
||||
case 'adminOrModerator': query.where('user.isAdmin = TRUE OR isModerator = TRUE'); break;
|
||||
case 'verified': query.where('user.isVerified = TRUE'); break;
|
||||
case 'alive': query.where('user.updatedAt > :date', { date: new Date(Date.now() - 1000 * 60 * 60 * 24 * 5) }); break;
|
||||
case 'silenced': query.where('user.isSilenced = TRUE'); break;
|
||||
case 'suspended': query.where('user.isSuspended = TRUE'); break;
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
import $ from 'cafy';
|
||||
import { ID } from '../../../../misc/cafy-id';
|
||||
import define from '../../define';
|
||||
import { Users } from '../../../../models';
|
||||
|
||||
export const meta = {
|
||||
desc: {
|
||||
'ja-JP': '指定したユーザーの公式アカウントを解除します。',
|
||||
'en-US': 'Mark a user as unverified.'
|
||||
},
|
||||
|
||||
tags: ['admin'],
|
||||
|
||||
requireCredential: true,
|
||||
requireModerator: true,
|
||||
|
||||
params: {
|
||||
userId: {
|
||||
validator: $.type(ID),
|
||||
desc: {
|
||||
'ja-JP': '対象のユーザーID',
|
||||
'en-US': 'The user ID which you want to unverify'
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
export default define(meta, async (ps) => {
|
||||
const user = await Users.findOne(ps.userId as string);
|
||||
|
||||
if (user == null) {
|
||||
throw new Error('user not found');
|
||||
}
|
||||
|
||||
await Users.update(user.id, {
|
||||
isVerified: false
|
||||
});
|
||||
});
|
|
@ -56,6 +56,13 @@ export const meta = {
|
|||
}
|
||||
},
|
||||
|
||||
pinnedUsers: {
|
||||
validator: $.optional.nullable.arr($.str),
|
||||
desc: {
|
||||
'ja-JP': 'ピン留めユーザー'
|
||||
}
|
||||
},
|
||||
|
||||
hiddenTags: {
|
||||
validator: $.optional.nullable.arr($.str),
|
||||
desc: {
|
||||
|
@ -353,6 +360,10 @@ export default define(meta, async (ps) => {
|
|||
set.useStarForReactionFallback = ps.useStarForReactionFallback;
|
||||
}
|
||||
|
||||
if (Array.isArray(ps.pinnedUsers)) {
|
||||
set.pinnedUsers = ps.pinnedUsers;
|
||||
}
|
||||
|
||||
if (Array.isArray(ps.hiddenTags)) {
|
||||
set.hiddenTags = ps.hiddenTags;
|
||||
}
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
import $ from 'cafy';
|
||||
import { ID } from '../../../../misc/cafy-id';
|
||||
import define from '../../define';
|
||||
import { Users } from '../../../../models';
|
||||
|
||||
export const meta = {
|
||||
desc: {
|
||||
'ja-JP': '指定したユーザーを公式アカウントにします。',
|
||||
'en-US': 'Mark a user as verified.'
|
||||
},
|
||||
|
||||
tags: ['admin'],
|
||||
|
||||
requireCredential: true,
|
||||
requireModerator: true,
|
||||
|
||||
params: {
|
||||
userId: {
|
||||
validator: $.type(ID),
|
||||
desc: {
|
||||
'ja-JP': '対象のユーザーID',
|
||||
'en-US': 'The user ID which you want to verify'
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
export default define(meta, async (ps) => {
|
||||
const user = await Users.findOne(ps.userId as string);
|
||||
|
||||
if (user == null) {
|
||||
throw new Error('user not found');
|
||||
}
|
||||
|
||||
await Users.update(user.id, {
|
||||
isVerified: true
|
||||
});
|
||||
});
|
|
@ -160,6 +160,7 @@ export default define(meta, async (ps, me) => {
|
|||
|
||||
if (me && (me.isAdmin || me.isModerator)) {
|
||||
response.useStarForReactionFallback = instance.useStarForReactionFallback;
|
||||
response.pinnedUsers = instance.pinnedUsers;
|
||||
response.hiddenTags = instance.hiddenTags;
|
||||
response.recaptchaSecretKey = instance.recaptchaSecretKey;
|
||||
response.proxyAccount = instance.proxyAccount;
|
||||
|
|
60
src/server/api/endpoints/notes/unrenote.ts
Normal file
60
src/server/api/endpoints/notes/unrenote.ts
Normal file
|
@ -0,0 +1,60 @@
|
|||
import $ from 'cafy';
|
||||
import { ID } from '../../../../misc/cafy-id';
|
||||
import deleteNote from '../../../../services/note/delete';
|
||||
import define from '../../define';
|
||||
import * as ms from 'ms';
|
||||
import { getNote } from '../../common/getters';
|
||||
import { ApiError } from '../../error';
|
||||
import { Notes } from '../../../../models';
|
||||
|
||||
export const meta = {
|
||||
desc: {
|
||||
'ja-JP': '指定した投稿のRenoteを解除します。',
|
||||
},
|
||||
|
||||
tags: ['notes'],
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
kind: 'write:notes',
|
||||
|
||||
limit: {
|
||||
duration: ms('1hour'),
|
||||
max: 300,
|
||||
minInterval: ms('1sec')
|
||||
},
|
||||
|
||||
params: {
|
||||
noteId: {
|
||||
validator: $.type(ID),
|
||||
desc: {
|
||||
'ja-JP': '対象の投稿のID',
|
||||
'en-US': 'Target note ID.'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
errors: {
|
||||
noSuchNote: {
|
||||
message: 'No such note.',
|
||||
code: 'NO_SUCH_NOTE',
|
||||
id: 'efd4a259-2442-496b-8dd7-b255aa1a160f'
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
export default define(meta, async (ps, user) => {
|
||||
const note = await getNote(ps.noteId).catch(e => {
|
||||
if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
|
||||
throw e;
|
||||
});
|
||||
|
||||
const renotes = await Notes.find({
|
||||
userId: user.id,
|
||||
renoteId: note.id
|
||||
});
|
||||
|
||||
for (const note of renotes) {
|
||||
deleteNote(user, note);
|
||||
}
|
||||
});
|
33
src/server/api/endpoints/pinned-users.ts
Normal file
33
src/server/api/endpoints/pinned-users.ts
Normal file
|
@ -0,0 +1,33 @@
|
|||
import define from '../define';
|
||||
import { Users } from '../../../models';
|
||||
import { types, bool } from '../../../misc/schema';
|
||||
import { fetchMeta } from '../../../misc/fetch-meta';
|
||||
import parseAcct from '../../../misc/acct/parse';
|
||||
import { User } from '../../../models/entities/user';
|
||||
|
||||
export const meta = {
|
||||
tags: ['users'],
|
||||
|
||||
requireCredential: false,
|
||||
|
||||
params: {
|
||||
},
|
||||
|
||||
res: {
|
||||
type: types.array,
|
||||
optional: bool.false, nullable: bool.false,
|
||||
items: {
|
||||
type: types.object,
|
||||
optional: bool.false, nullable: bool.false,
|
||||
ref: 'User',
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default define(meta, async (ps, me) => {
|
||||
const meta = await fetchMeta();
|
||||
|
||||
const users = await Promise.all(meta.pinnedUsers.map(acct => Users.findOne(parseAcct(acct))));
|
||||
|
||||
return await Users.packMany(users.filter(x => x !== undefined) as User[], me, { detail: true });
|
||||
});
|
|
@ -37,7 +37,6 @@ export const meta = {
|
|||
'admin',
|
||||
'moderator',
|
||||
'adminOrModerator',
|
||||
'verified',
|
||||
'alive'
|
||||
]),
|
||||
default: 'all'
|
||||
|
@ -71,7 +70,6 @@ export default define(meta, async (ps, me) => {
|
|||
case 'admin': query.where('user.isAdmin = TRUE'); break;
|
||||
case 'moderator': query.where('user.isModerator = TRUE'); break;
|
||||
case 'adminOrModerator': query.where('user.isAdmin = TRUE OR isModerator = TRUE'); break;
|
||||
case 'verified': query.where('user.isVerified = TRUE'); break;
|
||||
case 'alive': query.where('user.updatedAt > :date', { date: new Date(Date.now() - 1000 * 60 * 60 * 24 * 5) }); break;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue