Compare commits
52 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 14b16b4733 | |||
| 256c216dfb | |||
| 13cee2b4f5 | |||
| eb45eeb1ae | |||
| 3154350b64 | |||
| 519c9c4499 | |||
| c2f6b09969 | |||
| c8e2b22942 | |||
| f433182c4c | |||
| 957392aaae | |||
| bd8d06e133 | |||
| c617b60f70 | |||
| 018837db0b | |||
| 00d5fdfc13 | |||
| 2debb0c2ca | |||
| 148d6737cc | |||
| c15cb8d28c | |||
| 1d5471db70 | |||
| 086b83c1fe | |||
| c509e0b86c | |||
| e8a1ad9823 | |||
| 6446b1cfb1 | |||
| 2b9216c441 | |||
| 28656a701f | |||
| cd628eaf54 | |||
| 3d62bd8007 | |||
| 7ad9560f53 | |||
| c135d02895 | |||
| 0757f67bde | |||
| c5dc25cb9e | |||
| 0fcf422dec | |||
| 21e801a325 | |||
| 183faf3b2a | |||
| 9fc66a2d1e | |||
| 12cf598e6b | |||
| dd6cd6332c | |||
| 20ee2118ee | |||
| 1546160f6a | |||
| dd77a6194e | |||
| 203fba0216 | |||
| 826a2466b7 | |||
| 273bc6507a | |||
| 2b13969b26 | |||
| 2f10ff1a2e | |||
| 1f52aea193 | |||
| 0a0e76679e | |||
| 845f3436cc | |||
| f6a8c8cf76 | |||
| 53298933e4 | |||
| e7cfae3ccb | |||
| ebb03113ab | |||
| b3a0fe823e |
@@ -1,6 +1,33 @@
|
||||
ChangeLog
|
||||
=========
|
||||
|
||||
10.77.0
|
||||
----------
|
||||
* ローカルタイムライン無効オプションをグローバルタイムライン無効オプションと分離
|
||||
* モデレータはLTL無効時でもUIからLTLを消さない
|
||||
* インスタンス情報ページに各種タイムラインの有効/無効を表示
|
||||
|
||||
10.76.0
|
||||
----------
|
||||
* disableLocalTimeline機能を強化
|
||||
* インスタンス情報ページの強化
|
||||
* ハッシュタグ判定の強化
|
||||
* SVGサムネイルを表示するように
|
||||
* CWの引き継ぎ機能を無効化
|
||||
|
||||
10.75.0
|
||||
----------
|
||||
* ダイレクトを非公開のように使えるように
|
||||
* モデレーターを凍結できないように
|
||||
* モデレーター登録を解除できるように
|
||||
* NSFWなメディアをユーザーページなどで表示しないように
|
||||
* 管理画面でユーザーを状態でフィルタできるように
|
||||
* 管理者がサインイン履歴を参照できるツール
|
||||
* Renote数を再度表示するように
|
||||
* インスタンス情報ページの追加
|
||||
* テーマの調整
|
||||
* UIの改善
|
||||
|
||||
10.74.0
|
||||
----------
|
||||
* Pleromaとのフェデレーションを修正
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
[](https://david-dm.org/syuilo/misskey)
|
||||
[](http://makeapullrequest.com)
|
||||
|
||||
**Sophisticated microblogging platform, evolving forever.**
|
||||
**A forever evolving, sophisticated microblogging platform.**
|
||||
|
||||
<p align="justify">
|
||||
<a href="https://misskey.xyz">Misskey</a> is a decentralized microblogging platform born on Earth.
|
||||
@@ -27,7 +27,7 @@ Why don't you take a short break from the hustle and bustle of the city, and div
|
||||
|
||||
<h3 align="left">Posting</h3>
|
||||
<p align="justify">
|
||||
Just post your idea, hot topics and anything you want to share. You may decorate your words, attach your favorite pictures or movies, and create a poll - those are all supported in Misskey!
|
||||
Post your ideas, discussion topics, fun moments, or anything else you want to share! Misskey supports text, emoji, pictures, videos, and polls!
|
||||
</p>
|
||||
|
||||
---
|
||||
@@ -36,7 +36,7 @@ Just post your idea, hot topics and anything you want to share. You may decorate
|
||||
|
||||
<h3 align="right">Reactions</h3>
|
||||
<p align="justify">
|
||||
The simplest way to tell your emotions to the posts. You can choose the best reaction from various reactions. Reactions on Misskey has much more expressive than other social media which only allows pushing “likes”.
|
||||
Reactions are the simplest way to respond to others' posts. Simply pick a reaction emote from the list! Reactions on Misskey are much more expressive than other social media services which only allow “liking”.
|
||||
</p>
|
||||
|
||||
---
|
||||
@@ -45,7 +45,7 @@ The simplest way to tell your emotions to the posts. You can choose the best rea
|
||||
|
||||
<h3 align="left">Interface</h3>
|
||||
<p align="justify">
|
||||
Highly customizable UI for your taste. We understand no UI fits for everyone. Make your graceful home by editing, adjusting layouts of timeline, and placing widgets.
|
||||
Customize the UI to your own tastes! No UI will work for everyone, so Misskey is completely customizable. Make Misskey *yours* by editing the style, adjusting timeline layouts, and placing widgets.
|
||||
</p>
|
||||
|
||||
---
|
||||
@@ -54,20 +54,20 @@ Highly customizable UI for your taste. We understand no UI fits for everyone. Ma
|
||||
|
||||
<h3 align="right">Misskey Drive</h3>
|
||||
<p align="justify">
|
||||
Organized uploaded files. Wanna post a picture you have already uploaded? Wish to create a folder for your files? Misskey Drive is the best solution for you.
|
||||
Organize and store your files! Want to post a picture you have already uploaded? Wish you could organize your files into folders? Misskey Drive is a solution!
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
and more! Now it's time to experience the world with your own eyes at [misskey.xyz](https://misskey.xyz) or [other instances](https://joinmisskey.github.io/).
|
||||
...and more! Experience Misskey with your own eyes at [misskey.xyz](https://misskey.xyz) or join one of the [other instances](https://joinmisskey.github.io/) that are available.
|
||||
|
||||
:package: Create your own instance
|
||||
:package: Create Your Own Instance
|
||||
----------------------------------------------------------------
|
||||
Please see [Setup and installation guide](./docs/setup.en.md).
|
||||
Please see the [Setup and Installation Guide](./docs/setup.en.md).
|
||||
|
||||
:wrench: Contribution
|
||||
----------------------------------------------------------------
|
||||
Please see [Contribution guide](./CONTRIBUTING.md).
|
||||
Please see the [Contribution Guide](./CONTRIBUTING.md).
|
||||
|
||||
:heart: Backers & Sponsors
|
||||
----------------------------------------------------------------
|
||||
@@ -123,7 +123,7 @@ Please see [Contribution guide](./CONTRIBUTING.md).
|
||||
----------------------------------------------------------------
|
||||
> Copyright (c) 2014-2019 syuilo
|
||||
|
||||
Misskey is an open-source software licensed under the [GNU AGPLv3](LICENSE).
|
||||
Misskey is open-source software licensed under the [GNU AGPLv3](LICENSE).
|
||||
|
||||
[![][agpl-3.0-badge]][AGPL-3.0]
|
||||
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@ This guide describes how to install and setup Misskey with Docker.
|
||||
----------------------------------------------------------------
|
||||
1. `cp .config/example.yml .config/default.yml` Copy the `.config/example.yml` and rename it to `default.yml`.
|
||||
2. `cp .config/mongo_initdb_example.js .config/mongo_initdb.js` Copy the `.config/mongo_initdb_example.js` and rename it to `mongo_initdb.js`.
|
||||
2. Edit `default.yml` and `mongo_initdb.js`.
|
||||
3. Edit `default.yml` and `mongo_initdb.js`.
|
||||
|
||||
*3.* Configure Docker
|
||||
----------------------------------------------------------------
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
Guide Docker
|
||||
================================================================
|
||||
|
||||
Ce guide explique comment installer et configurer Misskey avec Docker.
|
||||
|
||||
[Version japonaise également disponible - Japanese version also available - 日本語版もあります](./docker.ja.md)
|
||||
[Version anglaise également disponible - English version also available - 英語版もあります](./docker.en.md)
|
||||
|
||||
----------------------------------------------------------------
|
||||
|
||||
*1.* Télécharger Misskey
|
||||
----------------------------------------------------------------
|
||||
1. `git clone -b master git://github.com/syuilo/misskey.git` Clone le dépôt de Misskey sur la branche master.
|
||||
2. `cd misskey` Naviguez dans le dossier du dépôt.
|
||||
3. `git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)` Checkout sur le tag de la [dernière version](https://github.com/syuilo/misskey/releases/latest).
|
||||
|
||||
*2.* Configuration de Misskey
|
||||
----------------------------------------------------------------
|
||||
1. `cp .config/example.yml .config/default.yml` Copiez le fichier `.config/example.yml` et renommez-le `default.yml`.
|
||||
2. `cp .config/mongo_initdb_example.js .config/mongo_initdb.js` Copie le fichier `.config/mongo_initdb_example.js` et le renomme en `mongo_initdb.js`.
|
||||
3. Editez `default.yml` et `mongo_initdb.js`.
|
||||
|
||||
*3.* Configurer Docker
|
||||
----------------------------------------------------------------
|
||||
Editez `docker-compose.yml`.
|
||||
|
||||
*4.* Contruire Misskey
|
||||
----------------------------------------------------------------
|
||||
Contruire l'image Docker avec:
|
||||
|
||||
`docker-compose build`
|
||||
|
||||
*5.* C'est tout !
|
||||
----------------------------------------------------------------
|
||||
Parfait, Vous avez un environnement prêt pour démarrer Misskey.
|
||||
|
||||
### Lancer normalement
|
||||
Utilisez la commande `docker-compose up -d`. GLHF!
|
||||
|
||||
### How to update your Misskey server to the latest version
|
||||
1. `git fetch`
|
||||
2. `git stash`
|
||||
3. `git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)`
|
||||
4. `git stash pop`
|
||||
5. `docker-compose build`
|
||||
6. Consultez le [ChangeLog](../CHANGELOG.md) pour avoir les éventuelles informations de migration
|
||||
7. `docker-compose stop && docker-compose up -d`
|
||||
|
||||
### Comment exécuter des [commandes](manage.fr.md)
|
||||
`docker-compose run --rm web node cli/mark-admin @example`
|
||||
|
||||
### Configuration d'ElasticSearch (pour la fonction de recherche)
|
||||
*1.* Préparation de l'environnement
|
||||
----------------------------------------------------------------
|
||||
1. `mkdir elasticsearch && chown 1000:1000 elasticsearch` Permet de créer le dossier d'accueil de la base ElasticSearch aves les bons droits
|
||||
2. `sysctl -w vm.max_map_count=262144` Augmente la valeur max du paramètre map_count du système (valeur minimum pour pouvoir lancer ES)
|
||||
|
||||
*2.* Après lancement du docker-compose, initialisation de la base ElasticSearch
|
||||
----------------------------------------------------------------
|
||||
1. `docker-compose -it web /bin/sh` Connexion dans le conteneur web
|
||||
2. `apk add curl` Ajout du paquet curl
|
||||
3. `curl -X PUT "es:9200/misskey" -H 'Content-Type: application/json' -d'{ "settings" : { "index" : { } }}'` Création de la base ES
|
||||
4. `exit`
|
||||
|
||||
----------------------------------------------------------------
|
||||
|
||||
Si vous avez des questions ou des problèmes, n'hésitez pas à nous contacter !
|
||||
@@ -0,0 +1,18 @@
|
||||
# Guide d'administration
|
||||
|
||||
## Vérifier le status de la file d'attente des taches
|
||||
coming soon
|
||||
|
||||
## Marquer un utilisateur en tant que 'admin'
|
||||
``` shell
|
||||
node cli/mark-admin (ID utilisateur ou nom d'utilisateur)
|
||||
```
|
||||
|
||||
Exemple :
|
||||
``` shell
|
||||
# Par id
|
||||
node cli/mark-admin 57d01a501fdf2d07be417afe
|
||||
|
||||
# Par nom d'utilisateur
|
||||
node cli/suspend @syuilo
|
||||
```
|
||||
+9
-9
@@ -10,8 +10,8 @@ Ce guide décrit les étapes à suivre afin d'installer et de configurer une ins
|
||||
|
||||
*1.* Création de l'utilisateur Misskey
|
||||
----------------------------------------------------------------
|
||||
Lancer misskey en tant qu'utilisateur est une mauvaise idée, nous avons besoin de créer un utilisateur dédié.
|
||||
Sur Debian, à titre d'exemple :
|
||||
Executer misskey en tant que super-utilisateur étant une mauvaise idée, nous allons créer un utilisateur dédié.
|
||||
Sous Debian, par exemple :
|
||||
|
||||
```
|
||||
adduser --disabled-password --disabled-login misskey
|
||||
@@ -32,10 +32,10 @@ Installez les paquets suivants :
|
||||
|
||||
*3.* Paramètrage de MongoDB
|
||||
----------------------------------------------------------------
|
||||
En mode root :
|
||||
1. `mongo` Accédez au shell de mango
|
||||
En root :
|
||||
1. `mongo` Ouvrez le shell mongo
|
||||
2. `use misskey` Utilisez la base de données misskey
|
||||
3. `db.users.save( {dummy:"dummy"} )` Write dummy data to initialize the db.
|
||||
3. `db.users.save( {dummy:"dummy"} )` Écrivez une donnée factice pour initialiser la base de données.
|
||||
4. `db.createUser( { user: "misskey", pwd: "<password>", roles: [ { role: "readWrite", db: "misskey" } ] } )` Créez l'utilisateur misskey.
|
||||
5. `exit` Vous avez terminé !
|
||||
|
||||
@@ -44,12 +44,12 @@ En mode root :
|
||||
1. `su - misskey` Basculez vers l'utilisateur misskey.
|
||||
2. `git clone -b master git://github.com/syuilo/misskey.git` Clonez la branche master du dépôt misskey.
|
||||
3. `cd misskey` Accédez au dossier misskey.
|
||||
4. `git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)` Télécharge la [version la plus récente](https://github.com/syuilo/misskey/releases/latest)
|
||||
4. `git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)` Checkout sur le tag de la [version la plus récente](https://github.com/syuilo/misskey/releases/latest)
|
||||
5. `npm install` Installez les dépendances de misskey.
|
||||
|
||||
*5.* Création du fichier de configuration
|
||||
----------------------------------------------------------------
|
||||
1. `cp .config/example.yml .config/default.yml` Copiez le fichier `.config/example.yml` et renommez-le `default.yml`.
|
||||
1. `cp .config/example.yml .config/default.yml` Copiez le fichier `.config/example.yml` et renommez-le`default.yml`.
|
||||
2. Editez le fichier `default.yml`
|
||||
|
||||
*6.* Construction de Misskey
|
||||
@@ -59,7 +59,7 @@ Construisez Misskey comme ceci :
|
||||
|
||||
`npm run build`
|
||||
|
||||
Si vous êtes sous Debian, vous serez amené à installer les paquets `build-essential`, `python`.
|
||||
Si vous êtes sous Debian, vous serez amené à installer les paquets `build-essential` et `python`.
|
||||
|
||||
Si vous rencontrez des erreurs concernant certains modules, utilisez node-gyp:
|
||||
|
||||
@@ -77,7 +77,7 @@ Lancez tout simplement `npm start`. Bonne chance et amusez-vous bien !
|
||||
|
||||
### Démarrage avec systemd
|
||||
|
||||
1. Créez une service systemd sur : `/etc/systemd/system/misskey.service`
|
||||
1. Créez un service systemd sur : `/etc/systemd/system/misskey.service`
|
||||
2. Editez-le puis copiez et coller ceci dans le fichier :
|
||||
|
||||
```
|
||||
|
||||
@@ -132,6 +132,8 @@ common:
|
||||
line-width-thin: "細い"
|
||||
line-width-normal: "普通"
|
||||
line-width-thick: "太い"
|
||||
hide-password: "パスワードを隠す"
|
||||
show-password: "パスワードを表示する"
|
||||
|
||||
do-not-use-in-production: "これは開発ビルドです。本番環境で使用しないでください。"
|
||||
user-suspended: "このユーザーは凍結されています。"
|
||||
@@ -1161,6 +1163,7 @@ admin/views/instance.vue:
|
||||
max-note-text-length: "投稿の最大文字数"
|
||||
disable-registration: "ユーザー登録の受付を停止する"
|
||||
disable-local-timeline: "ローカルタイムラインを無効にする"
|
||||
disable-global-timeline: "グローバルタイムラインを無効にする"
|
||||
invite: "招待"
|
||||
save: "保存"
|
||||
saved: "保存しました"
|
||||
@@ -1253,6 +1256,14 @@ admin/views/users.vue:
|
||||
createdAtDesc: "登録日時が新しい順"
|
||||
updatedAtAsc: "更新日時が古い順"
|
||||
updatedAtDesc: "更新日時が新しい順"
|
||||
state:
|
||||
title: "状態"
|
||||
all: "すべて"
|
||||
admin: "管理者"
|
||||
moderator: "モデレーター"
|
||||
adminOrModerator: "管理者+モデレーター"
|
||||
verified: "公式アカウント"
|
||||
suspended: "凍結済み"
|
||||
origin:
|
||||
title: "オリジン"
|
||||
combined: "ローカル+リモート"
|
||||
@@ -1266,6 +1277,8 @@ admin/views/moderators.vue:
|
||||
title: "モデレーターの登録"
|
||||
add: "登録"
|
||||
added: "モデレーターを登録しました"
|
||||
remove: "解除"
|
||||
removed: "モデレーター登録を解除しました"
|
||||
|
||||
admin/views/emoji.vue:
|
||||
add-emoji:
|
||||
|
||||
+15
-13
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "misskey",
|
||||
"author": "syuilo <i@syuilo.com>",
|
||||
"version": "10.74.0",
|
||||
"clientVersion": "2.0.13345",
|
||||
"version": "10.77.0",
|
||||
"clientVersion": "2.0.13397",
|
||||
"codename": "nighthike",
|
||||
"main": "./built/index.js",
|
||||
"private": true,
|
||||
@@ -43,6 +43,7 @@
|
||||
"@types/gulp-uglify": "3.0.6",
|
||||
"@types/gulp-util": "3.0.34",
|
||||
"@types/is-root": "1.0.0",
|
||||
"@types/is-svg": "3.0.0",
|
||||
"@types/is-url": "1.2.28",
|
||||
"@types/js-yaml": "3.11.4",
|
||||
"@types/katex": "0.5.0",
|
||||
@@ -60,7 +61,7 @@
|
||||
"@types/minio": "7.0.1",
|
||||
"@types/mkdirp": "0.5.2",
|
||||
"@types/mocha": "5.2.5",
|
||||
"@types/mongodb": "3.1.14",
|
||||
"@types/mongodb": "3.1.18",
|
||||
"@types/ms": "0.7.30",
|
||||
"@types/node": "10.12.18",
|
||||
"@types/nodemailer": "4.6.5",
|
||||
@@ -76,7 +77,7 @@
|
||||
"@types/rimraf": "2.0.2",
|
||||
"@types/seedrandom": "2.4.27",
|
||||
"@types/sharp": "0.21.0",
|
||||
"@types/showdown": "1.9.0",
|
||||
"@types/showdown": "1.9.1",
|
||||
"@types/speakeasy": "2.0.3",
|
||||
"@types/systeminformation": "3.23.1",
|
||||
"@types/tinycolor2": "1.4.1",
|
||||
@@ -96,12 +97,12 @@
|
||||
"bootstrap-vue": "2.0.0-rc.11",
|
||||
"cafy": "12.0.0",
|
||||
"chai": "4.2.0",
|
||||
"chai-http": "4.2.0",
|
||||
"chalk": "2.4.1",
|
||||
"chalk": "2.4.2",
|
||||
"chai-http": "4.2.1",
|
||||
"commander": "2.19.0",
|
||||
"crc-32": "1.2.0",
|
||||
"css-loader": "1.0.1",
|
||||
"cssnano": "4.1.7",
|
||||
"cssnano": "4.1.8",
|
||||
"dateformat": "3.0.3",
|
||||
"debug": "4.1.0",
|
||||
"deep-equal": "1.0.1",
|
||||
@@ -111,7 +112,7 @@
|
||||
"elasticsearch": "15.2.0",
|
||||
"emojilib": "2.4.0",
|
||||
"escape-regexp": "0.0.1",
|
||||
"eslint": "5.11.1",
|
||||
"eslint": "5.12.0",
|
||||
"eslint-plugin-vue": "5.0.0",
|
||||
"eventemitter3": "3.1.0",
|
||||
"feed": "2.0.2",
|
||||
@@ -136,6 +137,7 @@
|
||||
"http-signature": "1.2.0",
|
||||
"insert-text-at-cursor": "0.1.1",
|
||||
"is-root": "2.0.0",
|
||||
"is-svg": "3.0.0",
|
||||
"is-url": "1.2.4",
|
||||
"js-yaml": "3.12.0",
|
||||
"jsdom": "13.1.0",
|
||||
@@ -157,7 +159,7 @@
|
||||
"langmap": "0.0.16",
|
||||
"loader-utils": "1.2.3",
|
||||
"lookup-dns-cache": "2.1.0",
|
||||
"minio": "7.0.2",
|
||||
"minio": "7.0.3",
|
||||
"mkdirp": "0.5.1",
|
||||
"mocha": "5.2.0",
|
||||
"moji": "0.5.1",
|
||||
@@ -206,11 +208,11 @@
|
||||
"summaly": "2.2.0",
|
||||
"systeminformation": "3.52.2",
|
||||
"syuilo-password-strength": "0.0.1",
|
||||
"terser-webpack-plugin": "1.1.0",
|
||||
"terser-webpack-plugin": "1.2.1",
|
||||
"textarea-caret": "3.1.0",
|
||||
"tinycolor2": "1.4.1",
|
||||
"tmp": "0.0.33",
|
||||
"ts-loader": "5.3.1",
|
||||
"ts-loader": "5.3.3",
|
||||
"ts-node": "7.0.1",
|
||||
"tslint": "5.12.0",
|
||||
"tslint-sonarts": "1.8.0",
|
||||
@@ -224,7 +226,7 @@
|
||||
"vue-color": "2.7.0",
|
||||
"vue-content-loading": "1.5.3",
|
||||
"vue-cropperjs": "3.0.0",
|
||||
"vue-i18n": "8.6.0",
|
||||
"vue-i18n": "8.7.0",
|
||||
"vue-js-modal": "1.3.28",
|
||||
"vue-loader": "15.4.2",
|
||||
"vue-marquee-text-component": "1.1.1",
|
||||
@@ -240,7 +242,7 @@
|
||||
"web-push": "3.3.3",
|
||||
"webfinger.js": "2.7.0",
|
||||
"webpack": "4.28.3",
|
||||
"webpack-cli": "3.1.2",
|
||||
"webpack-cli": "3.2.1",
|
||||
"websocket": "1.0.28",
|
||||
"ws": "6.1.2",
|
||||
"xev": "2.0.1"
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
<section>
|
||||
<ui-switch v-model="disableRegistration">{{ $t('disable-registration') }}</ui-switch>
|
||||
<ui-switch v-model="disableLocalTimeline">{{ $t('disable-local-timeline') }}</ui-switch>
|
||||
<ui-switch v-model="disableGlobalTimeline">{{ $t('disable-global-timeline') }}</ui-switch>
|
||||
</section>
|
||||
<section class="fit-bottom">
|
||||
<header><fa icon="cloud"/> {{ $t('drive-config') }}</header>
|
||||
@@ -54,7 +55,7 @@
|
||||
</ui-horizon-group>
|
||||
<ui-horizon-group inputs>
|
||||
<ui-input v-model="smtpUser" :disabled="!enableEmail">{{ $t('smtp-user') }}</ui-input>
|
||||
<ui-input v-model="smtpPass" :disabled="!enableEmail">{{ $t('smtp-pass') }}</ui-input>
|
||||
<ui-input v-model="smtpPass" type="password" :withPasswordToggle="true" :disabled="!enableEmail">{{ $t('smtp-pass') }}</ui-input>
|
||||
</ui-horizon-group>
|
||||
<ui-switch v-model="smtpSecure" :disabled="!enableEmail">{{ $t('smtp-secure') }}<span slot="desc">{{ $t('smtp-secure-info') }}</span></ui-switch>
|
||||
</section>
|
||||
@@ -150,6 +151,7 @@ export default Vue.extend({
|
||||
maintainerEmail: null,
|
||||
disableRegistration: false,
|
||||
disableLocalTimeline: false,
|
||||
disableGlobalTimeline: false,
|
||||
mascotImageUrl: null,
|
||||
bannerUrl: null,
|
||||
errorImageUrl: null,
|
||||
@@ -198,6 +200,7 @@ export default Vue.extend({
|
||||
this.maintainerEmail = meta.maintainer.email;
|
||||
this.disableRegistration = meta.disableRegistration;
|
||||
this.disableLocalTimeline = meta.disableLocalTimeline;
|
||||
this.disableGlobalTimeline = meta.disableGlobalTimeline;
|
||||
this.mascotImageUrl = meta.mascotImageUrl;
|
||||
this.bannerUrl = meta.bannerUrl;
|
||||
this.errorImageUrl = meta.errorImageUrl;
|
||||
@@ -256,6 +259,7 @@ export default Vue.extend({
|
||||
maintainerEmail: this.maintainerEmail,
|
||||
disableRegistration: this.disableRegistration,
|
||||
disableLocalTimeline: this.disableLocalTimeline,
|
||||
disableGlobalTimeline: this.disableGlobalTimeline,
|
||||
mascotImageUrl: this.mascotImageUrl,
|
||||
bannerUrl: this.bannerUrl,
|
||||
errorImageUrl: this.errorImageUrl,
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
<ui-input v-model="username" type="text">
|
||||
<span slot="prefix">@</span>
|
||||
</ui-input>
|
||||
<ui-button @click="add" :disabled="adding">{{ $t('add-moderator.add') }}</ui-button>
|
||||
<ui-button @click="add" :disabled="changing">{{ $t('add-moderator.add') }}</ui-button>
|
||||
<ui-button @click="remove" :disabled="changing">{{ $t('add-moderator.remove') }}</ui-button>
|
||||
</section>
|
||||
</ui-card>
|
||||
</div>
|
||||
@@ -23,13 +24,13 @@ export default Vue.extend({
|
||||
data() {
|
||||
return {
|
||||
username: '',
|
||||
adding: false
|
||||
changing: false
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
async add() {
|
||||
this.adding = true;
|
||||
this.changing = true;
|
||||
|
||||
const process = async () => {
|
||||
const user = await this.$root.api('users/show', parseAcct(this.username));
|
||||
@@ -47,7 +48,29 @@ export default Vue.extend({
|
||||
});
|
||||
});
|
||||
|
||||
this.adding = false;
|
||||
this.changing = false;
|
||||
},
|
||||
|
||||
async remove() {
|
||||
this.changing = true;
|
||||
|
||||
const process = async () => {
|
||||
const user = await this.$root.api('users/show', parseAcct(this.username));
|
||||
await this.$root.api('admin/moderators/remove', { userId: user.id });
|
||||
this.$root.dialog({
|
||||
type: 'success',
|
||||
text: this.$t('add-moderator.removed')
|
||||
});
|
||||
};
|
||||
|
||||
await process().catch(e => {
|
||||
this.$root.dialog({
|
||||
type: 'error',
|
||||
text: e.toString()
|
||||
});
|
||||
});
|
||||
|
||||
this.changing = false;
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
@@ -31,6 +31,14 @@
|
||||
<option value="-updatedAt">{{ $t('users.sort.updatedAtAsc') }}</option>
|
||||
<option value="+updatedAt">{{ $t('users.sort.updatedAtDesc') }}</option>
|
||||
</ui-select>
|
||||
<ui-select v-model="state">
|
||||
<span slot="label">{{ $t('users.state.title') }}</span>
|
||||
<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="suspended">{{ $t('users.state.suspended') }}</option>
|
||||
</ui-select>
|
||||
<ui-select v-model="origin">
|
||||
<span slot="label">{{ $t('users.origin.title') }}</span>
|
||||
<option value="combined">{{ $t('users.origin.combined') }}</option>
|
||||
@@ -39,7 +47,7 @@
|
||||
</ui-select>
|
||||
</ui-horizon-group>
|
||||
<sequential-entrance animation="entranceFromTop" delay="25">
|
||||
<div class="kofvwchc" v-for="user in users">
|
||||
<div class="kofvwchc" v-for="user in users" :key="user.id">
|
||||
<div>
|
||||
<a :href="user | userPage(null, true)">
|
||||
<mk-avatar class="avatar" :user="user" :disable-link="true"/>
|
||||
@@ -49,6 +57,10 @@
|
||||
<header>
|
||||
<b><mk-user-name :user="user"/></b>
|
||||
<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-suspended" v-if="user.isSuspended" :title="$t('@.suspended-user')"><fa :icon="faSnowflake"/></span>
|
||||
</header>
|
||||
<div>
|
||||
<span>{{ $t('users.updatedAt') }}: <mk-time :time="user.updatedAt" mode="detail"/></span>
|
||||
@@ -84,6 +96,7 @@ export default Vue.extend({
|
||||
suspending: false,
|
||||
unsuspending: false,
|
||||
sort: '+createdAt',
|
||||
state: 'all',
|
||||
origin: 'combined',
|
||||
limit: 10,
|
||||
offset: 0,
|
||||
@@ -100,6 +113,12 @@ export default Vue.extend({
|
||||
this.fetchUsers();
|
||||
},
|
||||
|
||||
state() {
|
||||
this.users = [];
|
||||
this.offset = 0;
|
||||
this.fetchUsers();
|
||||
},
|
||||
|
||||
origin() {
|
||||
this.users = [];
|
||||
this.offset = 0;
|
||||
@@ -236,7 +255,8 @@ export default Vue.extend({
|
||||
},
|
||||
|
||||
fetchUsers() {
|
||||
this.$root.api('users', {
|
||||
this.$root.api('admin/show-users', {
|
||||
state: this.state,
|
||||
origin: this.origin,
|
||||
sort: this.sort,
|
||||
offset: this.offset,
|
||||
@@ -284,4 +304,19 @@ export default Vue.extend({
|
||||
margin-left 8px
|
||||
opacity 0.7
|
||||
|
||||
> .is-admin
|
||||
> .is-moderator
|
||||
flex-shrink 0
|
||||
align-self center
|
||||
margin 0 0 0 .5em
|
||||
padding 1px 6px
|
||||
font-size 80%
|
||||
border-radius 3px
|
||||
background var(--noteHeaderAdminBg)
|
||||
color var(--noteHeaderAdminFg)
|
||||
|
||||
> .is-verified
|
||||
> .is-suspended
|
||||
margin 0 0 0 .5em
|
||||
color #4dabf7
|
||||
</style>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<span class="visibility" v-if="note.visibility != 'public'">
|
||||
<fa v-if="note.visibility == 'home'" icon="home"/>
|
||||
<fa v-if="note.visibility == 'followers'" icon="unlock"/>
|
||||
<fa v-if="note.visibility == 'specified'" icon="lock"/>
|
||||
<fa v-if="note.visibility == 'specified'" icon="envelope"/>
|
||||
</span>
|
||||
<span class="localOnly" v-if="note.localOnly == true"><fa icon="heart"/></span>
|
||||
</div>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<span class="visibility" v-if="note.visibility != 'public'">
|
||||
<fa v-if="note.visibility == 'home'" icon="home"/>
|
||||
<fa v-if="note.visibility == 'followers'" icon="unlock"/>
|
||||
<fa v-if="note.visibility == 'specified'" icon="lock"/>
|
||||
<fa v-if="note.visibility == 'specified'" icon="envelope"/>
|
||||
</span>
|
||||
<span class="localOnly" v-if="note.localOnly == true"><fa icon="heart"/></span>
|
||||
</div>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<span slot="prefix">@</span>
|
||||
<span slot="suffix">@{{ host }}</span>
|
||||
</ui-input>
|
||||
<ui-input v-model="password" type="password" required styl="fill">
|
||||
<ui-input v-model="password" type="password" :with-password-toggle="true" required styl="fill">
|
||||
<span>{{ $t('password') }}</span>
|
||||
<span slot="prefix"><fa icon="lock"/></span>
|
||||
</ui-input>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="vnxwkwuf" :class="{ inputs, noGrow }">
|
||||
<div class="vnxwkwuf" :class="{ inputs, noGrow }" :data-children-count="children">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
@@ -21,6 +21,16 @@ export default Vue.extend({
|
||||
required: false,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
children: 0
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.children = this.$slots.default.length;
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -48,4 +58,19 @@ export default Vue.extend({
|
||||
> *:not(:last-child)
|
||||
margin-right 16px !important
|
||||
|
||||
&[data-children-count="3"]
|
||||
@media (max-width 600px)
|
||||
display block
|
||||
|
||||
> *
|
||||
display block
|
||||
width 100% !important
|
||||
margin 16px 0 !important
|
||||
|
||||
&:first-child
|
||||
margin-top 0 !important
|
||||
|
||||
&:last-child
|
||||
margin-bottom 0 !important
|
||||
|
||||
</style>
|
||||
|
||||
@@ -39,6 +39,12 @@
|
||||
</template>
|
||||
<div class="suffix" ref="suffix"><slot name="suffix"></slot></div>
|
||||
</div>
|
||||
<div class="toggle" v-if="withPasswordToggle">
|
||||
<a @click='togglePassword'>
|
||||
<span v-if="type == 'password'"><fa :icon="['fa', 'eye']"/> {{ $t('@.show-password') }}</span>
|
||||
<span v-if="type != 'password'"><fa :icon="['far', 'eye-slash']"/> {{ $t('@.hide-password') }}</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="desc"><slot name="desc"></slot></div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -97,6 +103,11 @@ export default Vue.extend({
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
withPasswordToggle: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
inline: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
@@ -178,6 +189,13 @@ export default Vue.extend({
|
||||
focus() {
|
||||
this.$refs.input.focus();
|
||||
},
|
||||
togglePassword() {
|
||||
if (this.type == 'password') {
|
||||
this.type = 'text'
|
||||
} else {
|
||||
this.type = 'password'
|
||||
}
|
||||
},
|
||||
chooseFile() {
|
||||
this.$refs.file.click();
|
||||
},
|
||||
@@ -356,6 +374,17 @@ root(fill)
|
||||
if fill
|
||||
padding-right 12px
|
||||
|
||||
> .toggle
|
||||
cursor pointer
|
||||
padding-left 0.5em
|
||||
font-size 0.7em
|
||||
opacity 0.7
|
||||
text-align left
|
||||
|
||||
> a
|
||||
color var(--inputLabel)
|
||||
text-decoration none
|
||||
|
||||
> .desc
|
||||
margin 6px 0
|
||||
font-size 13px
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div @click="choose('specified')" :class="{ active: v == 'specified' }">
|
||||
<div><fa icon="lock"/></div>
|
||||
<div><fa icon="envelope"/></div>
|
||||
<div>
|
||||
<span>{{ $t('specified') }}</span>
|
||||
<span>{{ $t('specified-desc') }}</span>
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
<span class="visibility" v-if="appearNote.visibility != 'public'">
|
||||
<fa v-if="appearNote.visibility == 'home'" icon="home"/>
|
||||
<fa v-if="appearNote.visibility == 'followers'" icon="unlock"/>
|
||||
<fa v-if="appearNote.visibility == 'specified'" icon="lock"/>
|
||||
<fa v-if="appearNote.visibility == 'specified'" icon="envelope"/>
|
||||
</span>
|
||||
<span class="localOnly" v-if="appearNote.localOnly == true"><fa icon="heart"/></span>
|
||||
</div>
|
||||
@@ -70,7 +70,7 @@
|
||||
<p class="count" v-if="appearNote.repliesCount > 0">{{ appearNote.repliesCount }}</p>
|
||||
</button>
|
||||
<button v-if="['public', 'home'].includes(appearNote.visibility)" class="renoteButton" @click="renote()" :title="$t('renote')">
|
||||
<fa icon="retweet"/>
|
||||
<fa icon="retweet"/><p class="count" v-if="appearNote.renoteCount > 0">{{ appearNote.renoteCount }}</p>
|
||||
</button>
|
||||
<button v-else class="inhibitedButton">
|
||||
<fa icon="ban"/>
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
<p class="count" v-if="appearNote.repliesCount > 0">{{ appearNote.repliesCount }}</p>
|
||||
</button>
|
||||
<button v-if="['public', 'home'].includes(appearNote.visibility)" class="renoteButton" @click="renote()" :title="$t('renote')">
|
||||
<fa icon="retweet"/>
|
||||
<fa icon="retweet"/><p class="count" v-if="appearNote.renoteCount > 0">{{ appearNote.renoteCount }}</p>
|
||||
</button>
|
||||
<button v-else class="inhibitedButton">
|
||||
<fa icon="ban"/>
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
<span v-if="visibility === 'public'"><fa icon="globe"/></span>
|
||||
<span v-if="visibility === 'home'"><fa icon="home"/></span>
|
||||
<span v-if="visibility === 'followers'"><fa icon="unlock"/></span>
|
||||
<span v-if="visibility === 'specified'"><fa icon="lock"/></span>
|
||||
<span v-if="visibility === 'specified'"><fa icon="envelope"/></span>
|
||||
</button>
|
||||
<p class="text-count" :class="{ over: trimmedLength(text) > maxNoteTextLength }">{{ maxNoteTextLength - trimmedLength(text) }}</p>
|
||||
<ui-button primary :wait="posting" class="submit" :disabled="!canPost" @click="post">
|
||||
@@ -222,12 +222,6 @@ export default Vue.extend({
|
||||
});
|
||||
}
|
||||
|
||||
// keep cw when reply
|
||||
if (this.reply && this.reply.cw != null) {
|
||||
this.useCw = true;
|
||||
this.cw = this.reply.cw;
|
||||
}
|
||||
|
||||
this.$nextTick(() => {
|
||||
// 書きかけの投稿を復元
|
||||
if (!this.instant && !this.mention) {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<span :data-active="src == 'home'" @click="src = 'home'"><fa icon="home"/> {{ $t('home') }}</span>
|
||||
<span :data-active="src == 'local'" @click="src = 'local'" v-if="enableLocalTimeline"><fa :icon="['far', 'comments']"/> {{ $t('local') }}</span>
|
||||
<span :data-active="src == 'hybrid'" @click="src = 'hybrid'" v-if="enableLocalTimeline"><fa icon="share-alt"/> {{ $t('hybrid') }}</span>
|
||||
<span :data-active="src == 'global'" @click="src = 'global'"><fa icon="globe"/> {{ $t('global') }}</span>
|
||||
<span :data-active="src == 'global'" @click="src = 'global'" v-if="enableGlobalTimeline"><fa icon="globe"/> {{ $t('global') }}</span>
|
||||
<span :data-active="src == 'tag'" @click="src = 'tag'" v-if="tagTl"><fa icon="hashtag"/> {{ tagTl.title }}</span>
|
||||
<span :data-active="src == 'list'" @click="src = 'list'" v-if="list"><fa icon="list"/> {{ list.title }}</span>
|
||||
<div class="buttons">
|
||||
@@ -43,7 +43,8 @@ export default Vue.extend({
|
||||
src: 'home',
|
||||
list: null,
|
||||
tagTl: null,
|
||||
enableLocalTimeline: false
|
||||
enableLocalTimeline: false,
|
||||
enableGlobalTimeline: false,
|
||||
};
|
||||
},
|
||||
|
||||
@@ -65,7 +66,8 @@ export default Vue.extend({
|
||||
|
||||
created() {
|
||||
this.$root.getMeta().then(meta => {
|
||||
this.enableLocalTimeline = !meta.disableLocalTimeline;
|
||||
this.enableLocalTimeline = !meta.disableLocalTimeline || this.$store.state.i.isModerator || this.$store.state.i.isAdmin;
|
||||
this.enableGlobalTimeline = !meta.disableGlobalTimeline || this.$store.state.i.isModerator || this.$store.state.i.isAdmin;
|
||||
});
|
||||
|
||||
if (this.$store.state.device.tl) {
|
||||
|
||||
@@ -169,6 +169,7 @@ export default Vue.extend({
|
||||
this.$root.api('users/notes', {
|
||||
userId: this.user.id,
|
||||
fileType: image,
|
||||
excludeNsfw: !this.$store.state.device.alwaysShowNsfw,
|
||||
limit: 9,
|
||||
untilDate: new Date().getTime() + 1000 * 86400 * 365
|
||||
}).then(notes => {
|
||||
|
||||
@@ -29,9 +29,11 @@ export default Vue.extend({
|
||||
'image/png',
|
||||
'image/gif'
|
||||
];
|
||||
|
||||
this.$root.api('users/notes', {
|
||||
userId: this.user.id,
|
||||
fileType: image,
|
||||
excludeNsfw: !this.$store.state.device.alwaysShowNsfw,
|
||||
limit: 9,
|
||||
untilDate: new Date().getTime() + 1000 * 86400 * 365
|
||||
}).then(notes => {
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
</div>
|
||||
<div class="menu">
|
||||
<div>
|
||||
<ui-input readonly :value="file.url">URL</ui-input>
|
||||
<ui-button link :href="`${file.url}?download`" :download="file.name"><fa icon="download"/> {{ $t('download') }}</ui-button>
|
||||
<ui-button @click="rename"><fa icon="pencil-alt"/> {{ $t('rename') }}</ui-button>
|
||||
<ui-button @click="move"><fa :icon="['far', 'folder-open']"/> {{ $t('move') }}</ui-button>
|
||||
@@ -200,7 +201,7 @@ export default Vue.extend({
|
||||
color #bf4633
|
||||
|
||||
> .menu
|
||||
padding 14px
|
||||
padding 0 14px 14px 14px
|
||||
border-top solid 1px var(--faceDivider)
|
||||
|
||||
> div
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
<span class="visibility" v-if="appearNote.visibility != 'public'">
|
||||
<fa v-if="appearNote.visibility == 'home'" icon="home"/>
|
||||
<fa v-if="appearNote.visibility == 'followers'" icon="unlock"/>
|
||||
<fa v-if="appearNote.visibility == 'specified'" icon="lock"/>
|
||||
<fa v-if="appearNote.visibility == 'specified'" icon="envelope"/>
|
||||
</span>
|
||||
<span class="localOnly" v-if="appearNote.localOnly == true"><fa icon="heart"/></span>
|
||||
</div>
|
||||
@@ -66,7 +66,7 @@
|
||||
<p class="count" v-if="appearNote.repliesCount > 0">{{ appearNote.repliesCount }}</p>
|
||||
</button>
|
||||
<button v-if="['public', 'home'].includes(appearNote.visibility)" @click="renote()" title="Renote">
|
||||
<fa icon="retweet"/>
|
||||
<fa icon="retweet"/><p class="count" v-if="appearNote.renoteCount > 0">{{ appearNote.renoteCount }}</p>
|
||||
</button>
|
||||
<button v-else>
|
||||
<fa icon="ban"/>
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
<p class="count" v-if="appearNote.repliesCount > 0">{{ appearNote.repliesCount }}</p>
|
||||
</button>
|
||||
<button v-if="['public', 'home'].includes(appearNote.visibility)" @click="renote()" title="Renote">
|
||||
<fa icon="retweet"/>
|
||||
<fa icon="retweet"/><p class="count" v-if="appearNote.renoteCount > 0">{{ appearNote.renoteCount }}</p>
|
||||
</button>
|
||||
<button v-else>
|
||||
<fa icon="ban"/>
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
<span v-if="visibility === 'public'"><fa icon="globe"/></span>
|
||||
<span v-if="visibility === 'home'"><fa icon="home"/></span>
|
||||
<span v-if="visibility === 'followers'"><fa icon="unlock"/></span>
|
||||
<span v-if="visibility === 'specified'"><fa icon="lock"/></span>
|
||||
<span v-if="visibility === 'specified'"><fa icon="envelope"/></span>
|
||||
</button>
|
||||
</footer>
|
||||
<input ref="file" class="file" type="file" multiple="multiple" @change="onChangeFile"/>
|
||||
@@ -211,12 +211,6 @@ export default Vue.extend({
|
||||
});
|
||||
}
|
||||
|
||||
// keep cw when reply
|
||||
if (this.reply && this.reply.cw != null) {
|
||||
this.useCw = true;
|
||||
this.cw = this.reply.cw;
|
||||
}
|
||||
|
||||
this.focus();
|
||||
|
||||
this.$nextTick(() => {
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
<span :data-active="src == 'home'" @click="src = 'home'"><fa icon="home"/> {{ $t('home') }}</span>
|
||||
<span :data-active="src == 'local'" @click="src = 'local'" v-if="enableLocalTimeline"><fa :icon="['far', 'comments']"/> {{ $t('local') }}</span>
|
||||
<span :data-active="src == 'hybrid'" @click="src = 'hybrid'" v-if="enableLocalTimeline"><fa icon="share-alt"/> {{ $t('hybrid') }}</span>
|
||||
<span :data-active="src == 'global'" @click="src = 'global'"><fa icon="globe"/> {{ $t('global') }}</span>
|
||||
<span :data-active="src == 'global'" @click="src = 'global'" v-if="enableGlobalTimeline"><fa icon="globe"/> {{ $t('global') }}</span>
|
||||
<div class="hr"></div>
|
||||
<span :data-active="src == 'mentions'" @click="src = 'mentions'"><fa icon="at"/> {{ $t('mentions') }}<i class="badge" v-if="$store.state.i.hasUnreadMentions"><fa icon="circle"/></i></span>
|
||||
<span :data-active="src == 'messages'" @click="src = 'messages'"><fa :icon="['far', 'envelope']"/> {{ $t('messages') }}<i class="badge" v-if="$store.state.i.hasUnreadSpecifiedNotes"><fa icon="circle"/></i></span>
|
||||
@@ -79,7 +79,8 @@ export default Vue.extend({
|
||||
lists: null,
|
||||
tagTl: null,
|
||||
showNav: false,
|
||||
enableLocalTimeline: false
|
||||
enableLocalTimeline: false,
|
||||
enableGlobalTimeline: false,
|
||||
};
|
||||
},
|
||||
|
||||
@@ -112,7 +113,8 @@ export default Vue.extend({
|
||||
|
||||
created() {
|
||||
this.$root.getMeta().then(meta => {
|
||||
this.enableLocalTimeline = !meta.disableLocalTimeline;
|
||||
this.enableLocalTimeline = !meta.disableLocalTimeline || this.$store.state.i.isModerator || this.$store.state.i.isAdmin;
|
||||
this.enableGlobalTimeline = !meta.disableGlobalTimeline || this.$store.state.i.isModerator || this.$store.state.i.isAdmin;
|
||||
});
|
||||
|
||||
if (this.$store.state.device.tl) {
|
||||
|
||||
@@ -34,6 +34,7 @@ export default Vue.extend({
|
||||
this.$root.api('users/notes', {
|
||||
userId: this.user.id,
|
||||
fileType: image,
|
||||
excludeNsfw: !this.$store.state.device.alwaysShowNsfw,
|
||||
limit: 9,
|
||||
untilDate: new Date().getTime() + 1000 * 86400 * 365
|
||||
}).then(notes => {
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 49 KiB |
@@ -16,5 +16,6 @@
|
||||
renoteGradient: '#ffe1c7',
|
||||
renoteText: '$primary',
|
||||
quoteBorder: '$primary',
|
||||
mfmMention: '#56907b',
|
||||
},
|
||||
}
|
||||
|
||||
+1
-1
@@ -205,7 +205,7 @@ const mfm = P.createLanguage({
|
||||
hashtag: r =>
|
||||
P((input, i) => {
|
||||
const text = input.substr(i);
|
||||
const match = text.match(/^#([^\s\.,!\?#]+)/i);
|
||||
const match = text.match(/^#([^\s\.,!\?#:]+)/i);
|
||||
if (!match) return P.makeFailure(i, 'not a hashtag');
|
||||
let hashtag = match[1];
|
||||
hashtag = removeOrphanedBrackets(hashtag);
|
||||
|
||||
@@ -184,6 +184,7 @@ export type IMeta = {
|
||||
|
||||
disableRegistration?: boolean;
|
||||
disableLocalTimeline?: boolean;
|
||||
disableGlobalTimeline?: boolean;
|
||||
hidedTags?: string[];
|
||||
mascotImageUrl?: string;
|
||||
bannerUrl?: string;
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
import $ from 'cafy';
|
||||
import User, { pack } from '../../../../models/user';
|
||||
import define from '../../define';
|
||||
|
||||
export const meta = {
|
||||
requireCredential: true,
|
||||
requireModerator: true,
|
||||
|
||||
params: {
|
||||
limit: {
|
||||
validator: $.num.optional.range(1, 100),
|
||||
default: 10
|
||||
},
|
||||
|
||||
offset: {
|
||||
validator: $.num.optional.min(0),
|
||||
default: 0
|
||||
},
|
||||
|
||||
sort: {
|
||||
validator: $.str.optional.or([
|
||||
'+follower',
|
||||
'-follower',
|
||||
'+createdAt',
|
||||
'-createdAt',
|
||||
'+updatedAt',
|
||||
'-updatedAt',
|
||||
]),
|
||||
},
|
||||
|
||||
state: {
|
||||
validator: $.str.optional.or([
|
||||
'all',
|
||||
'admin',
|
||||
'moderator',
|
||||
'adminOrModerator',
|
||||
'verified',
|
||||
'suspended',
|
||||
]),
|
||||
default: 'all'
|
||||
},
|
||||
|
||||
origin: {
|
||||
validator: $.str.optional.or([
|
||||
'combined',
|
||||
'local',
|
||||
'remote',
|
||||
]),
|
||||
default: 'local'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default define(meta, (ps, me) => new Promise(async (res, rej) => {
|
||||
let _sort;
|
||||
if (ps.sort) {
|
||||
if (ps.sort == '+follower') {
|
||||
_sort = {
|
||||
followersCount: -1
|
||||
};
|
||||
} else if (ps.sort == '-follower') {
|
||||
_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 = {
|
||||
_id: -1
|
||||
};
|
||||
}
|
||||
|
||||
const q = {
|
||||
$and: []
|
||||
} as any;
|
||||
|
||||
// state
|
||||
q.$and.push(
|
||||
ps.state == 'admin' ? { isAdmin: true } :
|
||||
ps.state == 'moderator' ? { isModerator: true } :
|
||||
ps.state == 'adminOrModerator' ? {
|
||||
$or: [{
|
||||
isAdmin: true
|
||||
}, {
|
||||
isModerator: true
|
||||
}]
|
||||
} :
|
||||
ps.state == 'verified' ? { isVerified: true } :
|
||||
ps.state == 'suspended' ? { isSuspended: true } :
|
||||
{}
|
||||
);
|
||||
|
||||
// origin
|
||||
q.$and.push(
|
||||
ps.origin == 'local' ? { host: null } :
|
||||
ps.origin == 'remote' ? { host: { $ne: null } } :
|
||||
{}
|
||||
);
|
||||
|
||||
const users = await User
|
||||
.find(q, {
|
||||
limit: ps.limit,
|
||||
sort: _sort,
|
||||
skip: ps.offset
|
||||
});
|
||||
|
||||
res(await Promise.all(users.map(user => pack(user, me, { detail: true }))));
|
||||
}));
|
||||
@@ -37,6 +37,10 @@ export default define(meta, (ps) => new Promise(async (res, rej) => {
|
||||
return rej('cannot suspend admin');
|
||||
}
|
||||
|
||||
if (user.isModerator) {
|
||||
return rej('cannot suspend moderator');
|
||||
}
|
||||
|
||||
await User.findOneAndUpdate({
|
||||
_id: user._id
|
||||
}, {
|
||||
|
||||
@@ -32,6 +32,13 @@ export const meta = {
|
||||
}
|
||||
},
|
||||
|
||||
disableGlobalTimeline: {
|
||||
validator: $.bool.optional.nullable,
|
||||
desc: {
|
||||
'ja-JP': 'グローバルタイムラインを無効にするか否か'
|
||||
}
|
||||
},
|
||||
|
||||
hidedTags: {
|
||||
validator: $.arr($.str).optional.nullable,
|
||||
desc: {
|
||||
@@ -331,6 +338,10 @@ export default define(meta, (ps) => new Promise(async (res, rej) => {
|
||||
set.disableLocalTimeline = ps.disableLocalTimeline;
|
||||
}
|
||||
|
||||
if (typeof ps.disableGlobalTimeline === 'boolean') {
|
||||
set.disableGlobalTimeline = ps.disableGlobalTimeline;
|
||||
}
|
||||
|
||||
if (Array.isArray(ps.hidedTags)) {
|
||||
set.hidedTags = ps.hidedTags;
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@ export default define(meta, (ps, me) => new Promise(async (res, rej) => {
|
||||
broadcasts: instance.broadcasts || [],
|
||||
disableRegistration: instance.disableRegistration,
|
||||
disableLocalTimeline: instance.disableLocalTimeline,
|
||||
disableGlobalTimeline: instance.disableGlobalTimeline,
|
||||
driveCapacityPerLocalUserMb: instance.localDriveCapacityMb,
|
||||
driveCapacityPerRemoteUserMb: instance.remoteDriveCapacityMb,
|
||||
cacheRemoteFiles: instance.cacheRemoteFiles,
|
||||
@@ -81,6 +82,7 @@ export default define(meta, (ps, me) => new Promise(async (res, rej) => {
|
||||
response.features = {
|
||||
registration: !instance.disableRegistration,
|
||||
localTimeLine: !instance.disableLocalTimeline,
|
||||
globalTimeLine: !instance.disableGlobalTimeline,
|
||||
elasticsearch: config.elasticsearch ? true : false,
|
||||
recaptcha: instance.enableRecaptcha,
|
||||
objectStorage: config.drive && config.drive.storage === 'minio',
|
||||
|
||||
@@ -4,6 +4,7 @@ import Mute from '../../../../models/mute';
|
||||
import { packMany } from '../../../../models/note';
|
||||
import define from '../../define';
|
||||
import { countIf } from '../../../../prelude/array';
|
||||
import fetchMeta from '../../../../misc/fetch-meta';
|
||||
|
||||
export const meta = {
|
||||
desc: {
|
||||
@@ -51,6 +52,13 @@ export const meta = {
|
||||
};
|
||||
|
||||
export default define(meta, (ps, user) => new Promise(async (res, rej) => {
|
||||
const meta = await fetchMeta();
|
||||
if (meta.disableGlobalTimeline) {
|
||||
if (user == null || (!user.isAdmin && !user.isModerator)) {
|
||||
return rej('global timeline disabled');
|
||||
}
|
||||
}
|
||||
|
||||
// Check if only one of sinceId, untilId, sinceDate, untilDate specified
|
||||
if (countIf(x => x != null, [ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate]) > 1) {
|
||||
return rej('only one of sinceId, untilId, sinceDate, untilDate can be specified');
|
||||
|
||||
@@ -5,6 +5,7 @@ import { getFriends } from '../../common/get-friends';
|
||||
import { packMany } from '../../../../models/note';
|
||||
import define from '../../define';
|
||||
import { countIf } from '../../../../prelude/array';
|
||||
import fetchMeta from '../../../../misc/fetch-meta';
|
||||
|
||||
export const meta = {
|
||||
desc: {
|
||||
@@ -91,6 +92,11 @@ export const meta = {
|
||||
};
|
||||
|
||||
export default define(meta, (ps, user) => new Promise(async (res, rej) => {
|
||||
const meta = await fetchMeta();
|
||||
if (meta.disableLocalTimeline && !user.isAdmin && !user.isModerator) {
|
||||
return rej('local timeline disabled');
|
||||
}
|
||||
|
||||
// Check if only one of sinceId, untilId, sinceDate, untilDate specified
|
||||
if (countIf(x => x != null, [ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate]) > 1) {
|
||||
return rej('only one of sinceId, untilId, sinceDate, untilDate can be specified');
|
||||
|
||||
@@ -4,6 +4,7 @@ import Mute from '../../../../models/mute';
|
||||
import { packMany } from '../../../../models/note';
|
||||
import define from '../../define';
|
||||
import { countIf } from '../../../../prelude/array';
|
||||
import fetchMeta from '../../../../misc/fetch-meta';
|
||||
|
||||
export const meta = {
|
||||
desc: {
|
||||
@@ -66,6 +67,13 @@ export const meta = {
|
||||
};
|
||||
|
||||
export default define(meta, (ps, user) => new Promise(async (res, rej) => {
|
||||
const meta = await fetchMeta();
|
||||
if (meta.disableLocalTimeline) {
|
||||
if (user == null || (!user.isAdmin && !user.isModerator)) {
|
||||
return rej('local timeline disabled');
|
||||
}
|
||||
}
|
||||
|
||||
// Check if only one of sinceId, untilId, sinceDate, untilDate specified
|
||||
if (countIf(x => x != null, [ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate]) > 1) {
|
||||
return rej('only one of sinceId, untilId, sinceDate, untilDate can be specified');
|
||||
|
||||
@@ -124,6 +124,14 @@ export const meta = {
|
||||
'ja-JP': '指定された種類のファイルが添付された投稿のみを取得します'
|
||||
}
|
||||
},
|
||||
|
||||
excludeNsfw: {
|
||||
validator: $.bool.optional,
|
||||
default: false,
|
||||
desc: {
|
||||
'ja-JP': 'true にすると、NSFW指定されたファイルを除外します(fileTypeが指定されている場合のみ有効)'
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
@@ -233,6 +241,12 @@ export default define(meta, (ps, me) => new Promise(async (res, rej) => {
|
||||
query['_files.contentType'] = {
|
||||
$in: ps.fileType
|
||||
};
|
||||
|
||||
if (ps.excludeNsfw) {
|
||||
query['_files.metadata.isSensitive'] = {
|
||||
$ne: true
|
||||
};
|
||||
}
|
||||
}
|
||||
//#endregion
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import Mute from '../../../../models/mute';
|
||||
import { pack } from '../../../../models/note';
|
||||
import shouldMuteThisNote from '../../../../misc/should-mute-this-note';
|
||||
import Channel from '../channel';
|
||||
import fetchMeta from '../../../../misc/fetch-meta';
|
||||
|
||||
export default class extends Channel {
|
||||
public readonly chName = 'globalTimeline';
|
||||
@@ -13,6 +14,11 @@ export default class extends Channel {
|
||||
|
||||
@autobind
|
||||
public async init(params: any) {
|
||||
const meta = await fetchMeta();
|
||||
if (meta.disableGlobalTimeline) {
|
||||
if (this.user == null || (!this.user.isAdmin && !this.user.isModerator)) return;
|
||||
}
|
||||
|
||||
// Subscribe events
|
||||
this.subscriber.on('globalTimeline', this.onNote);
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import Mute from '../../../../models/mute';
|
||||
import { pack } from '../../../../models/note';
|
||||
import shouldMuteThisNote from '../../../../misc/should-mute-this-note';
|
||||
import Channel from '../channel';
|
||||
import fetchMeta from '../../../../misc/fetch-meta';
|
||||
|
||||
export default class extends Channel {
|
||||
public readonly chName = 'hybridTimeline';
|
||||
@@ -13,6 +14,9 @@ export default class extends Channel {
|
||||
|
||||
@autobind
|
||||
public async init(params: any) {
|
||||
const meta = await fetchMeta();
|
||||
if (meta.disableLocalTimeline && !this.user.isAdmin && !this.user.isModerator) return;
|
||||
|
||||
// Subscribe events
|
||||
this.subscriber.on('hybridTimeline', this.onNewNote);
|
||||
this.subscriber.on(`hybridTimeline:${this.user._id}`, this.onNewNote);
|
||||
|
||||
@@ -3,6 +3,7 @@ import Mute from '../../../../models/mute';
|
||||
import { pack } from '../../../../models/note';
|
||||
import shouldMuteThisNote from '../../../../misc/should-mute-this-note';
|
||||
import Channel from '../channel';
|
||||
import fetchMeta from '../../../../misc/fetch-meta';
|
||||
|
||||
export default class extends Channel {
|
||||
public readonly chName = 'localTimeline';
|
||||
@@ -13,6 +14,11 @@ export default class extends Channel {
|
||||
|
||||
@autobind
|
||||
public async init(params: any) {
|
||||
const meta = await fetchMeta();
|
||||
if (meta.disableLocalTimeline) {
|
||||
if (this.user == null || (!this.user.isAdmin && !this.user.isModerator)) return;
|
||||
}
|
||||
|
||||
// Subscribe events
|
||||
this.subscriber.on('localTimeline', this.onNote);
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Web Client Server
|
||||
*/
|
||||
|
||||
import * as os from 'os';
|
||||
import ms = require('ms');
|
||||
import * as Koa from 'koa';
|
||||
import * as Router from 'koa-router';
|
||||
@@ -18,6 +19,8 @@ import config from '../../config';
|
||||
import Note, { pack as packNote } from '../../models/note';
|
||||
import getNoteSummary from '../../misc/get-note-summary';
|
||||
import fetchMeta from '../../misc/fetch-meta';
|
||||
import Emoji from '../../models/emoji';
|
||||
const pkg = require('../../../package.json');
|
||||
|
||||
const client = `${__dirname}/../../client/`;
|
||||
|
||||
@@ -195,6 +198,27 @@ router.get('/notes/:note', async ctx => {
|
||||
});
|
||||
//#endregion
|
||||
|
||||
router.get('/info', async ctx => {
|
||||
const meta = await fetchMeta();
|
||||
const emojis = await Emoji.find({ host: null }, {
|
||||
fields: {
|
||||
_id: false
|
||||
}
|
||||
});
|
||||
await ctx.render('info', {
|
||||
version: pkg.version,
|
||||
machine: os.hostname(),
|
||||
os: os.platform(),
|
||||
node: process.version,
|
||||
cpu: {
|
||||
model: os.cpus()[0].model,
|
||||
cores: os.cpus().length
|
||||
},
|
||||
emojis: emojis,
|
||||
meta: meta
|
||||
});
|
||||
});
|
||||
|
||||
// Render base html for all requests
|
||||
router.get('*', async ctx => {
|
||||
const meta = await fetchMeta();
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
doctype html
|
||||
|
||||
html
|
||||
|
||||
head
|
||||
meta(charset='utf-8')
|
||||
meta(name='application-name' content='Misskey')
|
||||
title Misskey
|
||||
style.
|
||||
html {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
main {
|
||||
max-width: 934px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
header {
|
||||
padding: 5px;
|
||||
background: rgb(153, 153, 204);
|
||||
border: 1px solid #000;
|
||||
}
|
||||
header:after {
|
||||
content: '';
|
||||
display: block;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
header > h1 {
|
||||
float: left;
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
header > img {
|
||||
float: right;
|
||||
width: 220px;
|
||||
}
|
||||
|
||||
table {
|
||||
margin: 1em 0;
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
table tr th {
|
||||
background-color: #ccf;
|
||||
border: 1px solid #000;
|
||||
width: 300px;
|
||||
font-weight: bold;
|
||||
padding: 4px 5px;
|
||||
text-align: left;
|
||||
}
|
||||
table tr td {
|
||||
background-color: #ddd;
|
||||
border: 1px solid #000;
|
||||
padding: 4px 5px;
|
||||
}
|
||||
|
||||
body
|
||||
main
|
||||
header
|
||||
h1 Misskey Version #{version}
|
||||
img(src='/assets/misskey-php-like-logo.png' alt='')
|
||||
table
|
||||
tr
|
||||
th Instance
|
||||
td= meta.name
|
||||
tr
|
||||
th Description
|
||||
td= meta.description
|
||||
tr
|
||||
th Maintainer
|
||||
td
|
||||
= meta.maintainer.name
|
||||
| <#{meta.maintainer.email}>
|
||||
tr
|
||||
th System
|
||||
td= os
|
||||
tr
|
||||
th Node version
|
||||
td= node
|
||||
tr
|
||||
th Machine
|
||||
td= machine
|
||||
tr
|
||||
th CPU
|
||||
td= cpu.model
|
||||
tr
|
||||
th Original users
|
||||
td= meta.stats.originalUsersCount
|
||||
tr
|
||||
th Original notes
|
||||
td= meta.stats.originalNotesCount
|
||||
tr
|
||||
th Registration
|
||||
td= !meta.disableRegistration ? 'yes' : 'no'
|
||||
tr
|
||||
th reCAPTCHA enabled
|
||||
td= meta.enableRecaptcha ? 'enabled' : 'disabled'
|
||||
tr
|
||||
th LTL(STL) enabled
|
||||
td= !meta.disableLocalTimeline ? 'enabled' : 'disabled'
|
||||
tr
|
||||
th GTL enabled
|
||||
td= !meta.disableGlobalTimeline ? 'enabled' : 'disabled'
|
||||
tr
|
||||
th Cache remote files
|
||||
td= meta.cacheRemoteFiles ? 'yes' : 'no'
|
||||
tr
|
||||
th Drive capacity per local user
|
||||
td
|
||||
= meta.localDriveCapacityMb
|
||||
| MB
|
||||
tr
|
||||
th Drive capacity per remote user
|
||||
td
|
||||
= meta.remoteDriveCapacityMb
|
||||
| MB
|
||||
tr
|
||||
th Max text length
|
||||
td= meta.maxNoteTextLength
|
||||
tr
|
||||
th Emojis
|
||||
td
|
||||
each emoji in emojis
|
||||
| :#{emoji.name}:
|
||||
= ' '
|
||||
|
||||
@@ -4,10 +4,11 @@ import * as fs from 'fs';
|
||||
import * as mongodb from 'mongodb';
|
||||
import * as crypto from 'crypto';
|
||||
import * as debug from 'debug';
|
||||
import fileType = require('file-type');
|
||||
import * as Minio from 'minio';
|
||||
import * as uuid from 'uuid';
|
||||
import * as sharp from 'sharp';
|
||||
import * as fileType from 'file-type';
|
||||
import * as isSvg from 'is-svg';
|
||||
|
||||
import DriveFile, { IMetadata, getDriveFileBucket, IDriveFile } from '../../models/drive-file';
|
||||
import DriveFolder from '../../models/drive-folder';
|
||||
@@ -320,6 +321,8 @@ export default async function(
|
||||
const type = fileType(buffer);
|
||||
if (type) {
|
||||
res([type.mime, type.ext]);
|
||||
} else if (isSvg(buffer)) {
|
||||
res(['image/svg+xml', 'svg'])
|
||||
} else {
|
||||
// 種類が同定できなかったら application/octet-stream にする
|
||||
res(['application/octet-stream', null]);
|
||||
|
||||
@@ -183,11 +183,6 @@ export default async (user: IUser, data: Option, silent = false) => new Promise<
|
||||
data.visibleUsers.push(u);
|
||||
}
|
||||
}
|
||||
|
||||
// ダイレクト投稿でユーザーが指定されていなかったらreject
|
||||
if (data.visibleUsers.length === 0) {
|
||||
return rej('Target user is not specified');
|
||||
}
|
||||
}
|
||||
|
||||
const note = await insertNote(user, data, tags, emojis, mentionedUsers);
|
||||
@@ -546,7 +541,7 @@ async function publishToFollowers(note: INote, user: IUser, noteActivity: any) {
|
||||
if (!following.stalk) {
|
||||
// この投稿が返信ならスキップ
|
||||
if (note.replyId && !note._reply.userId.equals(following.followerId) && !note._reply.userId.equals(note.userId))
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Publish event to followers stream
|
||||
|
||||
@@ -1,31 +1,17 @@
|
||||
import * as mongo from 'mongodb';
|
||||
import redis from './db/redis';
|
||||
import Xev from 'xev';
|
||||
import { IMeta } from './models/meta';
|
||||
import fetchMeta from './misc/fetch-meta';
|
||||
|
||||
type ID = string | mongo.ObjectID;
|
||||
|
||||
class Publisher {
|
||||
private ev: Xev;
|
||||
private meta: IMeta;
|
||||
|
||||
constructor() {
|
||||
// Redisがインストールされてないときはプロセス間通信を使う
|
||||
if (redis == null) {
|
||||
this.ev = new Xev();
|
||||
}
|
||||
|
||||
setInterval(async () => {
|
||||
this.meta = await fetchMeta();
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
public fetchMeta = async () => {
|
||||
if (this.meta != null) return this.meta;
|
||||
|
||||
this.meta = await fetchMeta();
|
||||
return this.meta;
|
||||
}
|
||||
|
||||
private publish = (channel: string, type: string, value?: any): void => {
|
||||
@@ -83,14 +69,10 @@ class Publisher {
|
||||
}
|
||||
|
||||
public publishLocalTimelineStream = async (note: any): Promise<void> => {
|
||||
const meta = await this.fetchMeta();
|
||||
if (meta.disableLocalTimeline) return;
|
||||
this.publish('localTimeline', null, note);
|
||||
}
|
||||
|
||||
public publishHybridTimelineStream = async (userId: ID, note: any): Promise<void> => {
|
||||
const meta = await this.fetchMeta();
|
||||
if (meta.disableLocalTimeline) return;
|
||||
this.publish(userId ? `hybridTimeline:${userId}` : 'hybridTimeline', null, note);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
// node built/tools/show-signin-history username
|
||||
// => {Success} {Date} {IPAddrsss}
|
||||
|
||||
// node built/tools/show-signin-history username user-agent,x-forwarded-for
|
||||
// with user-agent and x-forwarded-for
|
||||
|
||||
// node built/tools/show-signin-history username all
|
||||
// with full request headers
|
||||
|
||||
import User from '../models/user';
|
||||
import Signin from '../models/signin';
|
||||
|
||||
async function main(username: string, headers: string[]) {
|
||||
const user = await User.findOne({
|
||||
host: null,
|
||||
usernameLower: username.toLowerCase(),
|
||||
});
|
||||
|
||||
if (user === null) throw 'User not found';
|
||||
|
||||
const history = await Signin.find({
|
||||
userId: user._id
|
||||
});
|
||||
|
||||
for (const signin of history) {
|
||||
console.log(`${signin.success ? 'OK' : 'NG'} ${signin.createdAt ? signin.createdAt.toISOString() : 'Unknown'} ${signin.ip}`);
|
||||
|
||||
// headers
|
||||
if (headers != null) {
|
||||
for (const key of Object.keys(signin.headers)) {
|
||||
if (headers.includes('all') || headers.includes(key)) {
|
||||
console.log(` ${key}: ${signin.headers[key]}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get args
|
||||
const args = process.argv.slice(2);
|
||||
|
||||
let username = args[0];
|
||||
let headers: string[];
|
||||
|
||||
if (args[1] != null) {
|
||||
headers = args[1].split(/,/).map(header => header.toLowerCase());
|
||||
}
|
||||
|
||||
// normalize args
|
||||
username = username.replace(/^@/, '');
|
||||
|
||||
main(username, headers).then(() => {
|
||||
process.exit(0);
|
||||
}).catch(e => {
|
||||
console.warn(e);
|
||||
process.exit(1);
|
||||
});
|
||||
+14
@@ -808,6 +808,20 @@ describe('API', () => {
|
||||
|
||||
expect(res).have.status(400);
|
||||
}));
|
||||
|
||||
it('SVGファイルを作成できる', async(async () => {
|
||||
const izumi = await signup({ username: 'izumi' });
|
||||
|
||||
const res = await assert.request(server)
|
||||
.post('/drive/files/create')
|
||||
.field('i', izumi.token)
|
||||
.attach('file', fs.readFileSync(__dirname + '/resources/image.svg'), 'image.svg');
|
||||
|
||||
expect(res).have.status(200);
|
||||
expect(res.body).be.a('object');
|
||||
expect(res.body).have.property('name').eql('image.svg');
|
||||
expect(res.body).have.property('type').eql('image/svg+xml');
|
||||
}));
|
||||
});
|
||||
|
||||
describe('drive/files/update', () => {
|
||||
|
||||
@@ -357,6 +357,14 @@ describe('MFM', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('ignore colon', () => {
|
||||
const tokens = analyze('#Foo:');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
leaf('hashtag', { hashtag: 'Foo' }),
|
||||
text(':'),
|
||||
]);
|
||||
});
|
||||
|
||||
it('allow including number', () => {
|
||||
const tokens = analyze('#foo123');
|
||||
assert.deepStrictEqual(tokens, [
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 505 B |
Reference in New Issue
Block a user