Compare commits

..

46 Commits

Author SHA1 Message Date
syuilo
cb0874f15a 10.54.0 2018-11-16 23:19:34 +09:00
Aya Morisawa
9239e37b45 Fix #3273 (#3277) 2018-11-16 23:14:11 +09:00
Aya Morisawa
57d80932a4 Add an optional setting to remain deleted note (#3271)
Co-authored-by: Aya Morisawa <AyaMorisawa4869@gmail.com>
Co-authored-by: syuilo <syuilotan@yahoo.co.jp>
2018-11-16 23:13:37 +09:00
syuilo
8569970fbe Update github-bot.ts 2018-11-16 23:10:48 +09:00
syuilo
713e9ad5f4 Resolve #3274 2018-11-16 23:09:33 +09:00
syuilo
59e229d962 Update github-bot.ts 2018-11-16 23:02:01 +09:00
syuilo
3c5f09cda2 Clean up 2018-11-16 23:01:14 +09:00
syuilo
e42aa2530d Update github-bot.ts
follow dvelop instead master
2018-11-16 23:00:34 +09:00
Zero King
1fd298ac9c Update docker.en.md (#3272) 2018-11-16 22:47:10 +09:00
syuilo
1a73f52541 Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2018-11-16 22:32:17 +09:00
syuilo
27e458f884 Do not use camelCase in html 2018-11-16 22:32:06 +09:00
Zero King
ba3e2a9371 Update setup.en.md (#3270)
* [doc] run as root

* [doc] update setup guide headings
2018-11-16 22:31:53 +09:00
syuilo
831e8f8583 Refactoring 2018-11-16 22:31:28 +09:00
syuilo
0ff390ed80 [MFM] Improve various parsing
Resolve #2779
Resolve #3053
2018-11-16 21:57:19 +09:00
syuilo
e3b8495431 [MFM] Better URL parsing 2018-11-16 21:30:01 +09:00
syuilo
da10ba3fea [Client] Add missing icon
Resolve #3267
2018-11-16 18:34:52 +09:00
syuilo
cc7de853b4 [Client] Wrap formula component to split code 2018-11-16 18:31:25 +09:00
syuilo
23d8235197 Revert "[Client] Load katex async to reduce bundle size"
This reverts commit 37e3d60ade.
2018-11-16 18:26:19 +09:00
syuilo
37e3d60ade [Client] Load katex async to reduce bundle size 2018-11-16 18:22:44 +09:00
syuilo
83a3426dd5 Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2018-11-16 17:56:08 +09:00
syuilo
a2549192ca Update misskey-flavored-markdown.ts 2018-11-16 17:55:48 +09:00
Aya Morisawa
9d3a1cab6e Fix #3224 (#3265) 2018-11-16 17:41:52 +09:00
Aya Morisawa
06f8d8f0a3 Show server version on server.info.vue (#3264) 2018-11-16 17:24:03 +09:00
MeiMei
fa66b79e2d Update the locale when version or language changed (#3263) 2018-11-16 17:13:22 +09:00
Aya Morisawa
81312f5a93 Remove a trailing whitespace (#3261) 2018-11-16 17:04:28 +09:00
Aya Morisawa
ad84901f39 Support math rendering on MFM (#3260) 2018-11-16 17:03:52 +09:00
MeiMei
d2385a0e52 Do not show duplicate url-preview (#3259) 2018-11-16 16:35:13 +09:00
syuilo
39285fc2d0 10.53.0 2018-11-16 05:56:58 +09:00
syuilo
6e491c1466 Refactoring 2018-11-16 05:53:17 +09:00
dependabot[bot]
84152aa663 Update typescript-eslint-parser requirement from 20.1.1 to 21.0.0 (#3237)
Updates the requirements on [typescript-eslint-parser](https://github.com/eslint/typescript-eslint-parser) to permit the latest version.
- [Release notes](https://github.com/eslint/typescript-eslint-parser/releases)
- [Changelog](https://github.com/eslint/typescript-eslint-parser/blob/master/CHANGELOG.md)
- [Commits](https://github.com/eslint/typescript-eslint-parser/commits/v21.0.0)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2018-11-16 05:49:21 +09:00
dependabot[bot]
600fc65c2f Update showdown requirement from 1.8.7 to 1.9.0 (#3252)
Updates the requirements on [showdown](https://github.com/showdownjs/showdown) to permit the latest version.
- [Release notes](https://github.com/showdownjs/showdown/releases)
- [Changelog](https://github.com/showdownjs/showdown/blob/master/CHANGELOG.md)
- [Commits](https://github.com/showdownjs/showdown/commits/1.9.0)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2018-11-16 05:49:07 +09:00
dependabot[bot]
40e2733424 Update mongodb requirement from 3.1.8 to 3.1.9 (#3253)
Updates the requirements on [mongodb](https://github.com/mongodb/node-mongodb-native) to permit the latest version.
- [Release notes](https://github.com/mongodb/node-mongodb-native/releases)
- [Changelog](https://github.com/mongodb/node-mongodb-native/blob/master/HISTORY.md)
- [Commits](https://github.com/mongodb/node-mongodb-native/commits/v3.1.9)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2018-11-16 05:48:39 +09:00
MeiMei
bceb02d760 local only visibility (#3254)
* local only visibility

* fix UI
2018-11-16 05:47:29 +09:00
syuilo
aaaaf2681a [Client] Add missing icon 2018-11-16 05:38:16 +09:00
syuilo
3c3ef9bba0 [Client] ✌️ 2018-11-16 05:36:52 +09:00
syuilo
338f3a981f Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2018-11-16 05:26:48 +09:00
syuilo
a86ae9fa50 Better UX 2018-11-16 05:26:36 +09:00
syuilo
a3c4e8a1bc [Client] Improve admin dashboard 2018-11-16 05:21:52 +09:00
syuilo
6a7c18e8db Merge pull request #3250 from syuilo/l10n_develop
New Crowdin translations
2018-11-16 05:10:41 +09:00
syuilo
672b7a4c3d 🎨 2018-11-16 05:07:59 +09:00
MeiMei
bf3fee4481 Check the port is configured (#3251) 2018-11-16 03:25:35 +09:00
syuilo
a3c8d1d732 New translations ja-JP.yml (Chinese Simplified) 2018-11-16 00:15:45 +09:00
syuilo
bb03d8c49a New translations ja-JP.yml (Chinese Simplified) 2018-11-15 23:59:41 +09:00
syuilo
fbd5abe3b6 New translations ja-JP.yml (Chinese Simplified) 2018-11-15 23:43:21 +09:00
syuilo
5ac390abe9 New translations ja-JP.yml (Chinese Simplified) 2018-11-15 23:38:10 +09:00
Aya Morisawa
766cae2299 Check Node.js version (#3245)
* Check Node.js version

* Fix bug
2018-11-15 22:17:06 +09:00
48 changed files with 564 additions and 228 deletions

View File

@@ -13,7 +13,7 @@ This guide describes how to install and setup Misskey with Docker.
2. `cd misskey` Move to misskey directory.
3. `git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)` Checkout to the [latest release](https://github.com/syuilo/misskey/releases/latest) tag.
*2.* Make configuration files
*2.* Configure Misskey
----------------------------------------------------------------
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`.
@@ -31,12 +31,12 @@ Build misskey with the following:
*5.* That is it.
----------------------------------------------------------------
Well done! Now, you have an environment that run to Misskey.
Well done! Now you have an environment to run Misskey.
### Launch normally
Just `docker-compose up -d`. GLHF!
### Way to Update to latest version of your Misskey
### 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)`
@@ -45,9 +45,9 @@ Just `docker-compose up -d`. GLHF!
6. Check [ChangeLog](../CHANGELOG.md) for migration information
7. `docker-compose stop && docker-compose up -d`
### Way to execute cli command:
### How to execute [cli commands](manage.en.md):
`docker-compose run --rm web node cli/mark-admin @example`
----------------------------------------------------------------
If you have any questions or troubles, feel free to contact us!
If you have any questions or trouble, feel free to contact us!

View File

@@ -10,7 +10,7 @@ This guide describes how to install and setup Misskey.
*1.* Create Misskey user
----------------------------------------------------------------
Running misskey on root is not a good idea so we create a user for that.
Running misskey as root is not a good idea so we create a user for that.
In debian for exemple :
```
@@ -32,7 +32,7 @@ Please install and setup these softwares:
*3.* Setup MongoDB
----------------------------------------------------------------
In root :
As root:
1. `mongo` Go to the mongo shell
2. `use misskey` Use the misskey database
3. `db.users.save( {dummy:"dummy"} )` Write dummy data to initialize the db.
@@ -47,17 +47,17 @@ In root :
4. `git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)` Checkout to the [latest release](https://github.com/syuilo/misskey/releases/latest)
5. `npm install` Install misskey dependencies.
*(optional)* Generating VAPID keys
*(optional)* Generate VAPID keys
----------------------------------------------------------------
If you want to enable ServiceWorker, you need to generate VAPID keys:
Unless you have set your global node_modules location elsewhere, you need to run this in root.
Unless you have set your global node_modules location elsewhere, you need to run this as root.
``` shell
npm install web-push -g
web-push generate-vapid-keys
```
*5.* Make configuration file
*5.* Configure Misskey
----------------------------------------------------------------
1. `cp .config/example.yml .config/default.yml` Copy the `.config/example.yml` and rename it to `default.yml`.
2. Edit `default.yml`
@@ -114,7 +114,7 @@ WantedBy=multi-user.target
You can check if the service is running with `systemctl status misskey`.
### Way to Update to latest version of your Misskey
### How to update your Misskey server to the latest version
1. `git fetch`
2. `git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)`
3. `npm install`

View File

@@ -96,6 +96,9 @@ common:
specified: "ダイレクト"
specified-desc: "指定したユーザーにのみ公開"
private: "非公開"
local-public: "公開(ローカルのみ)"
local-home: "ホーム(ローカルのみ)"
local-followers: "フォロワー(ローカルのみ)"
note-placeholders:
a: "今どうしてる?"
@@ -471,6 +474,9 @@ common/views/components/visibility-chooser.vue:
specified: "ダイレクト"
specified-desc: "指定したユーザーにのみ公開"
private: "非公開"
local-public: "公開(ローカルのみ)"
local-home: "ホーム(ローカルのみ)"
local-followers: "フォロワー(ローカルのみ)"
common/views/components/trends.vue:
count: "{}人が投稿"
@@ -761,6 +767,7 @@ desktop/views/components/post-form.vue:
create-poll: "アンケートを作成"
text-remain: "残り{}文字"
recent-tags: "最近"
local-only-message: "この投稿はローカルにのみ公開されます"
click-to-tagging: "クリックでタグ付け"
visibility: "公開範囲"
geolocation-alert: "お使いの端末は位置情報に対応していません"
@@ -799,6 +806,7 @@ desktop/views/components/settings.vue:
profile: "プロフィール"
notification: "通知"
apps: "アプリ"
tags: "ハッシュタグ"
mute-and-block: "ミュート/ブロック"
blocking: "ブロック"
security: "セキュリティ"
@@ -843,6 +851,7 @@ desktop/views/components/settings.vue:
show-renoted-my-notes: "自分の投稿のRenoteをタイムラインに表示する"
show-local-renotes: "ローカルの投稿のRenoteをタイムラインに表示する"
show-maps: "マップの自動展開"
remain-deleted-note: "削除された投稿を表示し続ける"
deck-column-align: "デッキのカラムの位置"
deck-column-align-center: "中央"
deck-column-align-left: "左"
@@ -1045,7 +1054,7 @@ admin/views/index.vue:
emoji: "カスタム絵文字"
moderators: "モデレーター"
users: "ユーザー"
update: "更新"
federation: "連合"
announcements: "お知らせ"
hashtags: "ハッシュタグ"
back-to-misskey: "Misskeyに戻る"

View File

@@ -137,11 +137,11 @@ common:
widgets:
analog-clock: "指针时钟"
profile: "简介"
calendar: "カレンダー"
timemachine: "カレンダー(タイムマシン)"
activity: "アクティビティ"
rss: "RSSリーダー"
memo: "付箋"
calendar: "日历"
timemachine: "时光机"
activity: "动态"
rss: "RSS 订阅"
memo: "便签"
trends: "トレンド"
photo-stream: "フォトストリーム"
posts-monitor: "投稿チャート"
@@ -195,60 +195,60 @@ common/views/components/games/reversi/reversi.game.vue:
can-put-everywhere: "どこでも置けるモード"
common/views/components/games/reversi/reversi.index.vue:
title: "Misskey Reversi"
sub-title: "他のMisskeyユーザーとリバーシで対戦しよう"
invite: "招待"
rule: "遊び方"
rule-desc: "リバーシは、相手と交互に石をボードに置いて、相手の石を挟んで自分の色に変えてゆき、最終的に残った石が多い方が勝ちというボードゲームです。"
mode-invite: "招待"
mode-invite-desc: "指定したユーザーと対戦するモードです。"
invitations: "対局の招待があります!"
my-games: "自分の対局"
all-games: "みんなの対局"
enter-username: "ユーザー名を入力してください"
sub-title: "和你的朋友一起玩 Reversi!"
invite: "邀请"
rule: "游戏说明"
rule-desc: "Reversi是与对方交替地把石头放在板上把对方的石头夹在自己的颜色上最终留下的石头多的人获胜。"
mode-invite: "邀请"
mode-invite-desc: "邀请指定用户参加游戏"
invitations: "您收到了一则邀请!"
my-games: "我的游戏"
all-games: "所有游戏"
enter-username: "输入用户名"
game-state:
ended: "終了"
playing: "行中"
ended: "完成"
playing: "行中"
common/views/components/games/reversi/reversi.room.vue:
settings-of-the-game: "ゲームの設定"
choose-map: "マップを選択"
random: "ランダム"
black-or-white: "先手/後手"
black-is: "{}が黒"
rules: "ルール"
is-llotheo: "石の少ない方が勝ち(ロセオ)"
looped-map: "ループマップ"
can-put-everywhere: "どこでも置けるモード"
settings-of-the-bot: "Botの設定"
this-game-is-started-soon: "ゲームは数秒後に開始されます"
waiting-for-other: "相手の準備が完了するのを待っています"
waiting-for-me: "あなたの準備が完了するのを待っています"
waiting-for-both: "準備中"
cancel: "キャンセル"
ready: "準備完了"
cancel-ready: "準備続行"
settings-of-the-game: "游戏设置"
choose-map: "选择一个地图"
random: "随机"
black-or-white: "黑/白"
black-is: "{}是黑"
rules: "规则"
is-llotheo: "较少一方获胜"
looped-map: "环状地图"
can-put-everywhere: "可以随意放置"
settings-of-the-bot: "机器人设定"
this-game-is-started-soon: "游戏即将在数秒后开始"
waiting-for-other: "等待对手准备"
waiting-for-me: "等待您的准备"
waiting-for-both: "准备中"
cancel: "取消"
ready: "准备好啦"
cancel-ready: "取消准备"
common/views/components/connect-failed.vue:
title: "サーバーに接続できません"
description: "インターネット回線に問題があるか、サーバーがダウンまたはメンテナンスしている可能性があります。しばらくしてから{再度お試し}ください。"
thanks: "いつもMisskeyをご利用いただきありがとうございます。"
troubleshoot: "トラブルシュート"
title: "无法连接至服务器"
description: "您的网络连接可能出现了问题, 或是远程服务器暂时不可用. 请稍后{重试}."
thanks: "感谢您使用 Misskey"
troubleshoot: "故障排除"
common/views/components/connect-failed.troubleshooter.vue:
title: "トラブルシューティング"
network: "ネットワーク接続"
checking-network: "ネットワーク接続を確認中"
internet: "インターネット接続"
checking-internet: "インターネット接続を確認中"
server: "サーバー接続"
checking-server: "サーバー接続を確認中"
finding: "問題を調べています"
no-network: "ネットワークに接続されていません"
no-network-desc: "お使いのPCのネットワーク接続が正常か確認してください。"
no-internet: "インターネットに接続されていません"
no-internet-desc: "ネットワークには接続されていますが、インターネットには接続されていないようです。お使いのPCのインターネット接続が正常か確認してください。"
no-server: "Misskeyのサーバーに接続できません"
no-server-desc: "お使いのPCのインターネット接続は正常ですが、Misskeyのサーバーには接続できませんでした。サーバーがダウンまたはメンテナンスしている可能性があるので、しばらくしてから再度御アクセスください。"
success: "Misskeyのサーバーに接続できました"
success-desc: "正常に接続できるようです。ページを再度読み込みしてください。"
flush: "キャッシュの削除"
title: "正在排除故障"
network: "网络已连接"
checking-network: "正在检查网络连接"
internet: "网络连接"
checking-internet: "正在检查网络连接"
server: "已连接至服务器"
checking-server: "正在检查与服务器的连接"
finding: "搜索问题"
no-network: "无网络连接"
no-network-desc: "请确保您已连接至互联网"
no-internet: "无网络连接"
no-internet-desc: "请确保您已连接至互联网"
no-server: "无法连接到 Misskey 服务器"
no-server-desc: "您设备与互联网的网络连接正常, 但是无法连接至 Misskey 服务器. 这可能是服务器暂时不可用或正在维护. 请稍后再试."
success: "成功连接至 Misskey 服务器"
success-desc: "看起来我们连接正常. 请刷新网页."
flush: "清除缓存"
set-version: "バージョン指定"
common/views/components/media-banner.vue:
sensitive: "閲覧注意"
@@ -305,36 +305,36 @@ common/views/components/messaging-room.form.vue:
input-message-here: "ここにメッセージを入力"
send: "送信"
attach-from-local: "PCからファイルを添付する"
attach-from-drive: "ドライブからファイルを添付する"
only-one-file-attached: "メッセージに添付できるのはひとつのファイルのみです"
attach-from-drive: "从云盘中添加文件"
only-one-file-attached: "在信息中只允许添加一个附件"
common/views/components/messaging-room.message.vue:
is-read: "既読"
deleted: "このメッセージは削除されました"
is-read: "已阅"
deleted: "这条信息已被删除"
common/views/components/nav.vue:
about: "Misskeyについて"
stats: "統計"
status: "ステータス"
wiki: "Wiki"
donors: "ドナー"
repository: "リポジトリ"
develop: "開発者"
feedback: "フィードバック"
about: "关于"
stats: "统计"
status: "状态"
wiki: "百科 (Wiki)"
donors: "捐赠者"
repository: ""
develop: "开发者"
feedback: "反馈"
common/views/components/note-menu.vue:
detail: "詳細"
copy-link: "リンクをコピー"
favorite: "お気に入り"
unfavorite: "お気に入り解除"
pin: "ピン留め"
unpin: "ピン留め解除"
delete: "除"
delete-confirm: "この投稿を削除しますか?"
remote: "投稿元で見る"
detail: "详细信息"
copy-link: "复制链接"
favorite: "收藏这个投稿"
unfavorite: "取消收藏"
pin: "固定个人资料"
unpin: "解除固定"
delete: "除"
delete-confirm: "确定删除这个投稿吗?"
remote: "显示原始投稿"
common/views/components/poll.vue:
vote-to: "「{}」に投票する"
vote-to: "为\"{}\"投票"
vote-count: "{}票"
total-users: "{}人投票"
vote: "投票する"
show-result: "結果を見る"
total-users: "{} 人投票"
vote: "投票"
show-result: "显示结果"
voted: "投票済み"
common/views/components/poll-editor.vue:
no-only-one-choice: "アンケートには、選択肢が最低2つ必要です"

View File

@@ -1,8 +1,8 @@
{
"name": "misskey",
"author": "syuilo <i@syuilo.com>",
"version": "10.52.0",
"clientVersion": "2.0.11848",
"version": "10.54.0",
"clientVersion": "2.0.11894",
"codename": "nighthike",
"main": "./built/index.js",
"private": true,
@@ -46,6 +46,7 @@
"@types/is-root": "1.0.0",
"@types/is-url": "1.2.28",
"@types/js-yaml": "3.11.2",
"@types/katex": "0.5.0",
"@types/koa": "2.0.46",
"@types/koa-bodyparser": "5.0.1",
"@types/koa-compress": "2.0.8",
@@ -140,6 +141,7 @@
"jsdom": "13.0.0",
"json5": "2.1.0",
"json5-loader": "1.0.1",
"katex": "0.10.0",
"koa": "2.6.1",
"koa-bodyparser": "4.2.1",
"koa-compress": "3.0.0",
@@ -158,7 +160,7 @@
"mocha": "5.2.0",
"moji": "0.5.1",
"moment": "2.22.2",
"mongodb": "3.1.8",
"mongodb": "3.1.9",
"monk": "6.0.6",
"ms": "2.1.1",
"nan": "2.11.1",
@@ -189,7 +191,7 @@
"s-age": "1.1.2",
"seedrandom": "2.4.4",
"sharp": "0.21.0",
"showdown": "1.8.7",
"showdown": "1.9.0",
"showdown-highlightjs-extension": "0.1.2",
"speakeasy": "2.0.0",
"stringz": "1.0.0",
@@ -207,7 +209,7 @@
"ts-node": "7.0.1",
"tslint": "5.10.0",
"typescript": "3.1.6",
"typescript-eslint-parser": "20.1.1",
"typescript-eslint-parser": "21.0.0",
"uglify-es": "3.3.9",
"url-loader": "1.1.2",
"uuid": "3.3.2",

View File

@@ -8,13 +8,19 @@
<p>{{ $t('@.ai-chan-kawaii') }}</p>
</header>
<marquee-text v-if="instances.length > 0" class="instances" :repeat="10" :duration="60">
<span v-for="instance in instances" class="instance">
<b :style="{ background: instance.bg }">{{ instance.host }}</b>{{ instance.notesCount | number }} / {{ instance.usersCount | number }}
</span>
</marquee-text>
<div v-if="stats" class="stats">
<div>
<div>
<div><fa icon="user"/></div>
<div>
<span>{{ $t('accounts') }}</span>
<b class="primary">{{ stats.originalUsersCount | number }}</b>
<b>{{ stats.originalUsersCount | number }}</b>
</div>
</div>
<div>
@@ -27,7 +33,7 @@
<div><fa icon="pencil-alt"/></div>
<div>
<span>{{ $t('notes') }}</span>
<b class="primary">{{ stats.originalNotesCount | number }}</b>
<b>{{ stats.originalNotesCount | number }}</b>
</div>
</div>
<div>
@@ -84,6 +90,8 @@ import XCpuMemory from "./cpu-memory.vue";
import XCharts from "./charts.vue";
import XApLog from "./ap-log.vue";
import { faDatabase } from '@fortawesome/free-solid-svg-icons';
import MarqueeText from 'vue-marquee-text-component';
import randomColor from 'randomcolor';
export default Vue.extend({
i18n: i18n('admin/views/dashboard.vue'),
@@ -91,7 +99,8 @@ export default Vue.extend({
components: {
XCpuMemory,
XCharts,
XApLog
XApLog,
MarqueeText
},
data() {
@@ -99,6 +108,8 @@ export default Vue.extend({
stats: null,
connection: null,
meta: null,
instances: [],
clock: null,
faDatabase
};
},
@@ -106,22 +117,40 @@ export default Vue.extend({
created() {
this.connection = this.$root.stream.useSharedConnection('serverStats');
this.updateStats();
this.clock = setInterval(this.updateStats, 1000);
this.$root.getMeta().then(meta => {
this.meta = meta;
});
this.$root.api('stats').then(stats => {
this.stats = stats;
this.$root.api('instances', {
sort: '+notes'
}).then(instances => {
instances.forEach(i => {
i.bg = randomColor({
seed: i.host,
luminosity: 'dark'
});
});
this.instances = instances;
});
},
beforeDestroy() {
this.connection.dispose();
clearInterval(this.clock);
},
methods: {
setChartSrc(src) {
this.$refs.charts.setSrc(src);
},
updateStats() {
this.$root.api('stats', {}, false, true).then(stats => {
this.stats = stats;
});
}
}
});
@@ -136,7 +165,6 @@ export default Vue.extend({
> header
display flex
margin-bottom 16px
padding-bottom 16px
border-bottom solid 1px var(--adminDashboardHeaderBorder)
color var(--adminDashboardHeaderFg)
@@ -161,6 +189,20 @@ export default Vue.extend({
margin-left auto
margin-right 0
> .instances
padding 16px
color var(--adminDashboardHeaderFg)
font-size 13px
>>> .instance
margin 0 10px
> b
padding 2px 6px
margin-right 4px
border-radius 4px
color #fff
> .stats
display flex
justify-content space-between
@@ -201,9 +243,6 @@ export default Vue.extend({
> b
display block
&.primary
color var(--primary)
> div:last-child
display flex
padding 6px 16px

View File

@@ -22,12 +22,12 @@
<li @click="nav('instance')" :class="{ active: page == 'instance' }"><fa icon="cog" fixed-width/>{{ $t('instance') }}</li>
<li @click="nav('moderators')" :class="{ active: page == 'moderators' }"><fa :icon="faHeadset" fixed-width/>{{ $t('moderators') }}</li>
<li @click="nav('users')" :class="{ active: page == 'users' }"><fa icon="users" fixed-width/>{{ $t('users') }}</li>
<!-- <li @click="nav('federation')" :class="{ active: page == 'federation' }"><fa :icon="faShareAlt" fixed-width/>{{ $t('federation') }}</li> -->
<li @click="nav('emoji')" :class="{ active: page == 'emoji' }"><fa :icon="faGrin" fixed-width/>{{ $t('emoji') }}</li>
<li @click="nav('announcements')" :class="{ active: page == 'announcements' }"><fa icon="broadcast-tower" fixed-width/>{{ $t('announcements') }}</li>
<li @click="nav('hashtags')" :class="{ active: page == 'hashtags' }"><fa icon="hashtag" fixed-width/>{{ $t('hashtags') }}</li>
<!-- <li @click="nav('drive')" :class="{ active: page == 'drive' }"><fa icon="cloud" fixed-width/>{{ $t('@.drive') }}</li> -->
<!-- <li @click="nav('update')" :class="{ active: page == 'update' }">{{ $t('update') }}</li> -->
</ul>
<div class="back-to-misskey">
<a href="/"><fa :icon="faArrowLeft"/> {{ $t('back-to-misskey') }}</a>
@@ -37,11 +37,6 @@
</div>
</nav>
<main>
<marquee-text v-if="instances.length > 0" class="instances" :repeat="10" :duration="30">
<span v-for="instance in instances" class="instance">
<b :style="{ background: instance.bg }">{{ instance.host }}</b>{{ instance.notesCount | number }} / {{ instance.usersCount | number }}
</span>
</marquee-text>
<div class="page">
<div v-if="page == 'dashboard'"><x-dashboard/></div>
<div v-if="page == 'instance'"><x-instance/></div>
@@ -68,10 +63,8 @@ import XEmoji from "./emoji.vue";
import XAnnouncements from "./announcements.vue";
import XHashtags from "./hashtags.vue";
import XUsers from "./users.vue";
import { faHeadset, faArrowLeft } from '@fortawesome/free-solid-svg-icons';
import { faHeadset, faArrowLeft, faShareAlt } from '@fortawesome/free-solid-svg-icons';
import { faGrin } from '@fortawesome/free-regular-svg-icons';
import MarqueeText from 'vue-marquee-text-component';
import randomColor from 'randomcolor';
// Detect the user agent
const ua = navigator.userAgent.toLowerCase();
@@ -86,8 +79,7 @@ export default Vue.extend({
XEmoji,
XAnnouncements,
XHashtags,
XUsers,
MarqueeText
XUsers
},
provide: {
isMobile
@@ -98,25 +90,12 @@ export default Vue.extend({
version,
isMobile,
navOpend: !isMobile,
instances: [],
faGrin,
faArrowLeft,
faHeadset
faHeadset,
faShareAlt
};
},
created() {
this.$root.api('instances', {
sort: '+notes'
}).then(instances => {
instances.forEach(i => {
i.bg = randomColor({
seed: i.host,
luminosity: 'dark'
});
});
this.instances = instances;
});
},
methods: {
nav(page: string) {
this.page = page;
@@ -287,22 +266,8 @@ export default Vue.extend({
width 100%
padding 0 0 0 250px
> .instances
padding 10px
background #000
color #fff
font-size 13px
>>> .instance
margin 0 10px
> b
padding 0px 6px
margin-right 4px
border-radius 4px
> .page
max-width 1300px
max-width 1150px
&.isMobile
> main

View File

@@ -69,11 +69,14 @@
//#endregion
let locale = localStorage.getItem('locale');
if (locale == null) {
const localeKey = localStorage.getItem('localeKey');
if (locale == null || localeKey != `${ver}.${lang}`) {
const locale = await fetch(`/assets/locales/${lang}.json?ver=${ver}`)
.then(response => response.json());
localStorage.setItem('locale', JSON.stringify(locale));
localStorage.setItem('localeKey', `${ver}.${lang}`);
}
// Detect the user agent

View File

@@ -1,5 +1,5 @@
import parse from '../../../../mfm/parse';
import { sum } from '../../../../prelude/array';
import { sum, unique } from '../../../../prelude/array';
import shouldMuteNote from './should-mute-note';
import MkNoteMenu from '../views/components/note-menu.vue';
import MkReactionPicker from '../views/components/reaction-picker.vue';
@@ -78,9 +78,9 @@ export default (opts: Opts = {}) => ({
urls(): string[] {
if (this.appearNote.text) {
const ast = parse(this.appearNote.text);
return ast
return unique(ast
.filter(t => (t.type == 'url' || t.type == 'link') && !t.silent)
.map(t => t.url);
.map(t => t.url));
} else {
return null;
}

View File

@@ -0,0 +1,26 @@
<template>
<span v-html="compiledFormula"></span>
</template>
<script lang="ts">
import Vue from 'vue';
import * as katex from 'katex';
export default Vue.extend({
props: {
formula: {
type: String,
required: true
}
},
computed: {
compiledFormula(): any {
return katex.renderToString(this.formula);
}
}
});
</script>
<style>
@import "../../../../../../node_modules/katex/dist/katex.min.css";
</style>

View File

@@ -0,0 +1,20 @@
<template>
<x-formula :formula="formula"/>
</template>
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
components: {
XFormula: () => import('./formula-core.vue').then(m => m.default)
},
props: {
formula: {
type: String,
required: true
}
}
});
</script>

View File

@@ -34,6 +34,7 @@
import Vue from 'vue';
import i18n from '../../../i18n';
import parse from '../../../../../mfm/parse';
import { unique } from '../../../../../prelude/array';
export default Vue.extend({
i18n: i18n('common/views/components/messaging-room.message.vue'),
@@ -49,9 +50,9 @@ export default Vue.extend({
urls(): string[] {
if (this.message.text) {
const ast = parse(this.message.text);
return ast
return unique(ast
.filter(t => (t.type == 'url' || t.type == 'link') && !t.silent)
.map(t => t.url);
.map(t => t.url));
} else {
return null;
}

View File

@@ -3,8 +3,9 @@ import { length } from 'stringz';
import parse from '../../../../../mfm/parse';
import getAcct from '../../../../../misc/acct/render';
import MkUrl from './url.vue';
import MkGoogle from './google.vue';
import { concat } from '../../../../../prelude/array';
import MkFormula from './formula.vue';
import MkGoogle from './google.vue';
export default Vue.component('misskey-flavored-markdown', {
props: {
@@ -199,7 +200,17 @@ export default Vue.component('misskey-flavored-markdown', {
})];
}
case 'math': {
//const MkFormula = () => import('./formula.vue').then(m => m.default);
return [createElement(MkFormula, {
props: {
formula: token.formula
}
})];
}
case 'search': {
//const MkGoogle = () => import('./google.vue').then(m => m.default);
return [createElement(MkGoogle, {
props: {
q: token.query

View File

@@ -14,11 +14,12 @@
<mk-time :time="note.createdAt"/>
</router-link>
<span class="visibility" v-if="note.visibility != 'public'">
<template v-if="note.visibility == 'home'"><fa icon="home"/></template>
<template v-if="note.visibility == 'followers'"><fa icon="unlock"/></template>
<template v-if="note.visibility == 'specified'"><fa icon="envelope"/></template>
<template v-if="note.visibility == 'private'"><fa icon="lock"/></template>
<fa v-if="note.visibility == 'home'" icon="home"/>
<fa v-if="note.visibility == 'followers'" icon="unlock"/>
<fa v-if="note.visibility == 'specified'" icon="envelope"/>
<fa v-if="note.visibility == 'private'" icon="lock"/>
</span>
<span class="localOnly" v-if="note.localOnly == true"><fa icon="heart"/></span>
</div>
</header>
</template>
@@ -115,4 +116,7 @@ export default Vue.extend({
> .visibility
margin-left 8px
> .localOnly
margin-left 4px
</style>

View File

@@ -35,6 +35,24 @@
<span>{{ $t('private') }}</span>
</div>
</div>
<div @click="choose('local-public')" :class="{ active: v == 'local-public' }">
<div><fa icon="globe"/></div>
<div>
<span>{{ $t('local-public') }}</span>
</div>
</div>
<div @click="choose('local-home')" :class="{ active: v == 'local-home' }">
<div><fa icon="home"/></div>
<div>
<span>{{ $t('local-home') }}</span>
</div>
</div>
<div @click="choose('local-followers')" :class="{ active: v == 'local-followers' }">
<div><fa icon="unlock"/></div>
<div>
<span>{{ $t('local-followers') }}</span>
</div>
</div>
</div>
</div>
</template>

View File

@@ -3,6 +3,7 @@
<p>Maintainer: <b><a :href="'mailto:' + meta.maintainer.email" target="_blank">{{ meta.maintainer.name }}</a></b></p>
<p>Machine: {{ meta.machine }}</p>
<p>Node: {{ meta.node }}</p>
<p>Version: {{ meta.version }} </p>
</div>
</template>

View File

@@ -94,7 +94,7 @@ import MkRenoteFormWindow from './renote-form-window.vue';
import MkNoteMenu from '../../../common/views/components/note-menu.vue';
import MkReactionPicker from '../../../common/views/components/reaction-picker.vue';
import XSub from './note.sub.vue';
import { sum } from '../../../../../prelude/array';
import { sum, unique } from '../../../../../prelude/array';
import noteSubscriber from '../../../common/scripts/note-subscriber';
export default Vue.extend({
@@ -149,9 +149,9 @@ export default Vue.extend({
urls(): string[] {
if (this.p.text) {
const ast = parse(this.p.text);
return ast
return unique(ast
.filter(t => (t.type == 'url' || t.type == 'link') && !t.silent)
.map(t => t.url);
.map(t => t.url));
} else {
return null;
}

View File

@@ -2,7 +2,7 @@
<div
class="note"
:class="{ mini }"
v-show="appearNote.deletedAt == null && !hideThisNote"
v-show="(this.$store.state.settings.remainDeletedNote || appearNote.deletedAt == null) && !hideThisNote"
:tabindex="appearNote.deletedAt == null ? '-1' : null"
v-hotkey="keymap"
:title="title"
@@ -20,12 +20,19 @@
<router-link class="name" :to="note.user | userPage" v-user-preview="note.userId">{{ note.user | userName }}</router-link>
<span>{{ this.$t('reposted-by').substr(this.$t('reposted-by').indexOf('}') + 1) }}</span>
<mk-time :time="note.createdAt"/>
<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="envelope"/>
<fa v-if="note.visibility == 'private'" icon="lock"/>
</span>
<span class="localOnly" v-if="note.localOnly == true"><fa icon="heart"/></span>
</div>
<article>
<mk-avatar class="avatar" :user="appearNote.user"/>
<div class="main">
<mk-note-header class="header" :note="appearNote" :mini="mini"/>
<div class="body">
<div class="body" v-if="appearNote.deletedAt == null">
<p v-if="appearNote.cw != null" class="cw">
<span class="text" v-if="appearNote.cw != ''">{{ appearNote.cw }}</span>
<mk-cw-button v-model="showContent"/>
@@ -46,7 +53,7 @@
<mk-url-preview v-for="url in urls" :url="url" :key="url" :mini="mini"/>
</div>
</div>
<footer>
<footer v-if="appearNote.deletedAt == null">
<span class="app" v-if="appearNote.app && mini && $store.state.settings.showVia">via <b>{{ appearNote.app.name }}</b></span>
<mk-reactions-viewer :note="appearNote" ref="reactionsViewer"/>
<button class="replyButton" @click="reply()" :title="$t('reply')">
@@ -64,6 +71,7 @@
<fa icon="ellipsis-h"/>
</button>
</footer>
<div class="deleted" v-if="appearNote.deletedAt != null">{{ $t('deleted') }}</div>
</div>
</article>
<div class="replies" v-if="detail && replies.length > 0">
@@ -82,6 +90,7 @@ import noteSubscriber from '../../../common/scripts/note-subscriber';
export default Vue.extend({
i18n: i18n('desktop/views/components/note.vue'),
components: {
XSub
},
@@ -199,9 +208,6 @@ export default Vue.extend({
> span
flex-shrink 0
&:last-of-type
margin-right 8px
.name
overflow hidden
flex-shrink 1
@@ -215,6 +221,18 @@ export default Vue.extend({
flex-shrink 0
font-size 0.9em
> .visibility
margin-left 8px
[data-icon]
margin-right 0
> .localOnly
margin-left 4px
[data-icon]
margin-right 0
& + article
padding-top 8px
@@ -327,6 +345,7 @@ export default Vue.extend({
margin-left 0.5em
color var(--noteHeaderInfo)
font-size 0.8em
> button
margin 0 28px 0 0
padding 0 8px
@@ -360,6 +379,10 @@ export default Vue.extend({
&.reacted, &.reacted:hover
color var(--noteActionsReactionHover)
> .deleted
color var(--noteText)
opacity 0.7
</style>
<style lang="stylus" module>

View File

@@ -14,6 +14,7 @@
<b>{{ $t('recent-tags') }}:</b>
<a v-for="tag in recentHashtags.slice(0, 5)" @click="addTag(tag)" :title="$t('click-to-tagging')">#{{ tag }}</a>
</div>
<div class="local-only" v-if="this.localOnly == true">{{ $t('local-only-message') }}</div>
<input v-show="useCw" v-model="cw" :placeholder="$t('annotations')">
<div class="textarea">
<textarea :class="{ with: (files.length != 0 || poll) }"
@@ -112,6 +113,7 @@ export default Vue.extend({
geo: null,
visibility: this.$store.state.settings.rememberNoteVisibility ? (this.$store.state.device.visibility || this.$store.state.settings.defaultNoteVisibility) : this.$store.state.settings.defaultNoteVisibility,
visibleUsers: [],
localOnly: false,
autocomplete: null,
draghover: false,
recentHashtags: JSON.parse(localStorage.getItem('hashtags') || '[]'),
@@ -363,7 +365,14 @@ export default Vue.extend({
source: this.$refs.visibilityButton
});
w.$once('chosen', v => {
this.visibility = v;
const m = v.match(/^local-(.+)/);
if (m) {
this.localOnly = true;
this.visibility = m[1];
} else {
this.localOnly = false;
this.visibility = v;
}
});
},
@@ -407,6 +416,7 @@ export default Vue.extend({
cw: this.useCw ? this.cw || '' : undefined,
visibility: this.visibility,
visibleUserIds: this.visibility == 'specified' ? this.visibleUsers.map(u => u.id) : undefined,
localOnly: this.localOnly,
geo: this.geo ? {
coordinates: [this.geo.longitude, this.geo.latitude],
altitude: this.geo.altitude,
@@ -498,6 +508,7 @@ export default Vue.extend({
border solid 1px var(--primaryAlpha01)
border-radius 4px
transition border-color .2s ease
padding-right 30px
&:hover
border-color var(--primaryAlpha02)
@@ -640,6 +651,10 @@ export default Vue.extend({
margin-right 8px
white-space nowrap
> .local-only
margin 0 0 8px 0
color var(--primary)
> .mk-uploader
margin 8px 0 0 0
padding 8px

View File

@@ -130,6 +130,7 @@
<ui-switch v-model="showReplyTarget">{{ $t('show-reply-target') }}</ui-switch>
<ui-switch v-model="showMaps">{{ $t('show-maps') }}</ui-switch>
<ui-switch v-model="disableAnimatedMfm">{{ $t('@.disable-animated-mfm') }}</ui-switch>
<ui-switch v-model="remainDeletedNote">{{ $t('remain-deleted-note') }}</ui-switch>
</section>
<section>
<header>{{ $t('deck-column-align') }}</header>
@@ -529,6 +530,11 @@ export default Vue.extend({
disableAnimatedMfm: {
get() { return this.$store.state.settings.disableAnimatedMfm; },
set(value) { this.$store.dispatch('settings/set', { key: 'disableAnimatedMfm', value }); }
},
remainDeletedNote: {
get() { return this.$store.state.settings.remainDeletedNote; },
set(value) { this.$store.dispatch('settings/set', { key: 'remainDeletedNote', value }); }
}
},
created() {

View File

@@ -1,12 +1,12 @@
<template>
<x-column :menu="menu" :name="name" :column="column" :is-stacked="isStacked">
<span slot="header">
<template v-if="column.type == 'home'"><fa icon="home"/></template>
<template v-if="column.type == 'local'"><fa :icon="['far', 'comments']"/></template>
<template v-if="column.type == 'hybrid'"><fa icon="share-alt"/></template>
<template v-if="column.type == 'global'"><fa icon="globe"/></template>
<template v-if="column.type == 'list'"><fa icon="list"/></template>
<template v-if="column.type == 'hashtag'"><fa icon="hashtag"/></template>
<fa v-if="column.type == 'home'" icon="home"/>
<fa v-if="column.type == 'local'" :icon="['far', 'comments']"/>
<fa v-if="column.type == 'hybrid'" icon="share-alt"/>
<fa v-if="column.type == 'global'" icon="globe"/>
<fa v-if="column.type == 'list'" icon="list"/>
<fa v-if="column.type == 'hashtag'" icon="hashtag"/>
<span>{{ name }}</span>
</span>

View File

@@ -227,6 +227,7 @@ export default define({
background var(--desktopPostFormTextareaBg)
border none
border-bottom solid 1px var(--faceDivider)
padding-right 30px
&:focus
& + .emoji

View File

@@ -118,6 +118,7 @@ import {
faUserPlus,
faExternalLinkSquareAlt,
faSync,
faArrowLeft,
} from '@fortawesome/free-solid-svg-icons';
import {
@@ -138,6 +139,7 @@ import {
faClock as farClock,
faCalendarAlt as farCalendarAlt,
faHdd as farHdd,
faMoon as farMoon,
} from '@fortawesome/free-regular-svg-icons';
import {
@@ -240,6 +242,7 @@ library.add(
faUserPlus,
faExternalLinkSquareAlt,
faSync,
faArrowLeft,
farBell,
farEnvelope,
@@ -258,6 +261,7 @@ library.add(
farClock,
farCalendarAlt,
farHdd,
farMoon,
fabTwitter,
fabGithub,

View File

@@ -385,15 +385,19 @@ export default class MiOS extends EventEmitter {
* @param data パラメータ
*/
@autobind
public api(endpoint: string, data: { [x: string]: any } = {}, forceFetch = false): Promise<{ [x: string]: any }> {
if (++pending === 1) {
spinner = document.createElement('div');
spinner.setAttribute('id', 'wait');
document.body.appendChild(spinner);
public api(endpoint: string, data: { [x: string]: any } = {}, forceFetch = false, silent = false): Promise<{ [x: string]: any }> {
if (!silent) {
if (++pending === 1) {
spinner = document.createElement('div');
spinner.setAttribute('id', 'wait');
document.body.appendChild(spinner);
}
}
const onFinally = () => {
if (--pending === 0) spinner.parentNode.removeChild(spinner);
if (!silent) {
if (--pending === 0) spinner.parentNode.removeChild(spinner);
}
};
const promise = new Promise((resolve, reject) => {

View File

@@ -92,7 +92,7 @@ import parse from '../../../../../mfm/parse';
import MkNoteMenu from '../../../common/views/components/note-menu.vue';
import MkReactionPicker from '../../../common/views/components/reaction-picker.vue';
import XSub from './note.sub.vue';
import { sum } from '../../../../../prelude/array';
import { sum, unique } from '../../../../../prelude/array';
import noteSubscriber from '../../../common/scripts/note-subscriber';
export default Vue.extend({
@@ -143,9 +143,9 @@ export default Vue.extend({
urls(): string[] {
if (this.p.text) {
const ast = parse(this.p.text);
return ast
return unique(ast
.filter(t => (t.type == 'url' || t.type == 'link') && !t.silent)
.map(t => t.url);
.map(t => t.url));
} else {
return null;
}

View File

@@ -16,12 +16,19 @@
<router-link class="name" :to="note.user | userPage">{{ note.user | userName }}</router-link>
<span>{{ this.$t('reposted-by').substr(this.$t('reposted-by').indexOf('}') + 1) }}</span>
<mk-time :time="note.createdAt"/>
<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="envelope"/>
<fa v-if="note.visibility == 'private'" icon="lock"/>
</span>
<span class="localOnly" v-if="note.localOnly == true"><fa icon="heart"/></span>
</div>
<article>
<mk-avatar class="avatar" :user="appearNote.user" v-if="$store.state.device.postStyle != 'smart'"/>
<div class="main">
<mk-note-header class="header" :note="appearNote" :mini="true"/>
<div class="body">
<div class="body" v-if="appearNote.deletedAt == null">
<p v-if="appearNote.cw != null" class="cw">
<span class="text" v-if="appearNote.cw != ''">{{ appearNote.cw }}</span>
<mk-cw-button v-model="showContent"/>
@@ -30,7 +37,7 @@
<div class="text">
<span v-if="appearNote.isHidden" style="opacity: 0.5">({{ $t('private') }})</span>
<a class="reply" v-if="appearNote.reply"><fa icon="reply"/></a>
<misskey-flavored-markdown v-if="appearNote.text" :text="appearNote.text" :i="$store.state.i" :class="$style.text" :customEmojis="appearNote.emojis"/>
<misskey-flavored-markdown v-if="appearNote.text" :text="appearNote.text" :i="$store.state.i" :class="$style.text" :custom-emojis="appearNote.emojis"/>
<a class="rp" v-if="appearNote.renote != null">RN:</a>
</div>
<div class="files" v-if="appearNote.files.length > 0">
@@ -43,7 +50,7 @@
</div>
<span class="app" v-if="appearNote.app && $store.state.settings.showVia">via <b>{{ appearNote.app.name }}</b></span>
</div>
<footer>
<footer v-if="appearNote.deletedAt == null">
<mk-reactions-viewer :note="appearNote" ref="reactionsViewer"/>
<button @click="reply()">
<template v-if="appearNote.reply"><fa icon="reply-all"/></template>
@@ -60,6 +67,7 @@
<fa icon="ellipsis-h"/>
</button>
</footer>
<div class="deleted" v-if="appearNote.deletedAt != null">{{ $t('deleted') }}</div>
</div>
</article>
</div>
@@ -163,9 +171,6 @@ export default Vue.extend({
> span
flex-shrink 0
&:last-of-type
margin-right 8px
.name
overflow hidden
flex-shrink 1
@@ -179,6 +184,18 @@ export default Vue.extend({
flex-shrink 0
font-size 0.9em
> .visibility
margin-left 8px
[data-icon]
margin-right 0
> .localOnly
margin-left 4px
[data-icon]
margin-right 0
& + article
padding-top 8px
@@ -339,6 +356,10 @@ export default Vue.extend({
&.reacted
color var(--primary)
> .deleted
color var(--noteText)
opacity 0.7
</style>
<style lang="stylus" module>

View File

@@ -102,6 +102,7 @@ export default Vue.extend({
geo: null,
visibility: this.$store.state.settings.rememberNoteVisibility ? (this.$store.state.device.visibility || this.$store.state.settings.defaultNoteVisibility) : this.$store.state.settings.defaultNoteVisibility,
visibleUsers: [],
localOnly: false,
useCw: false,
cw: null,
recentHashtags: JSON.parse(localStorage.getItem('hashtags') || '[]'),
@@ -274,7 +275,14 @@ export default Vue.extend({
compact: true
});
w.$once('chosen', v => {
this.visibility = v;
const m = v.match(/^local-(.+)/);
if (m) {
this.localOnly = true;
this.visibility = m[1];
} else {
this.localOnly = false;
this.visibility = v;
}
});
},
@@ -320,6 +328,7 @@ export default Vue.extend({
} : null,
visibility: this.visibility,
visibleUserIds: this.visibility == 'specified' ? this.visibleUsers.map(u => u.id) : undefined,
localOnly: this.localOnly,
viaMobile: viaMobile
}).then(data => {
this.$emit('posted');

View File

@@ -15,6 +15,7 @@ const defaultSettings = {
tagTimelines: [],
fetchOnScroll: true,
showMaps: true,
remainDeletedNote: false,
showPostFormOnTopOfTl: false,
suggestRecentHashtags: true,
showClockOnHeader: true,

View File

@@ -26,6 +26,13 @@ props:
ja-JP: "モバイル端末から投稿したか否か(自己申告であることに留意)"
en-US: "Whether this note sent via a mobile device"
localOnly:
type: "boolean"
optional: true
desc:
ja-JP: "ローカルのみに公開する投稿か否か"
en-US: "Whether this note is no federation"
text:
type: "string"
optional: true

View File

@@ -112,7 +112,12 @@ async function init(): Promise<Config> {
Logger.info('Welcome to Misskey!');
Logger.info(`<<< Misskey v${pkg.version} >>>`);
new Logger('Deps').info(`Node.js ${process.version}`);
new Logger('Nodejs').info(`Version ${process.version}`);
if (lessThan(process.version.slice(1).split('.').map(x => parseInt(x, 10)), [10, 0, 0])) {
new Logger('Nodejs').error(`Node.js version is less than 10.0.0. Please upgrade it.`);
process.exit(1);
}
await MachineInfo.show();
EnvironmentInfo.show();
@@ -135,6 +140,11 @@ async function init(): Promise<Config> {
configLogger.succ('Loaded');
if (config.port == null) {
Logger.error('The port is not configured. Please configure port.');
process.exit(1);
}
if (process.platform === 'linux' && !isRoot() && config.port < 1024) {
Logger.error('You need root privileges to listen on port below 1024 on Linux');
process.exit(1);

View File

@@ -53,6 +53,12 @@ const handlers: { [key: string]: (window: any, token: any, mentionedRemoteUsers:
document.body.appendChild(element);
},
math({ document }, { formula }) {
const element = document.createElement('code');
element.textContent = formula;
document.body.appendChild(element);
},
link({ document }, { url, title }) {
const a = document.createElement('a');
a.href = url;

View File

@@ -8,7 +8,9 @@ export type TextElementHashtag = {
hashtag: string;
};
export default function(text: string, isBegin: boolean) {
export default function(text: string, before: string) {
const isBegin = before == '';
if (!(/^\s#[^\s\.,!\?#]+/.test(text) || (isBegin && /^#[^\s\.,!\?#]+/.test(text)))) return null;
const isHead = text.startsWith('#');
const hashtag = text.match(/^\s?#[^\s\.,!\?#]+/)[0];

View File

@@ -0,0 +1,20 @@
/**
* Math
*/
export type TextElementMath = {
type: 'math';
content: string;
formula: string;
};
export default function(text: string) {
const match = text.match(/^\$(.+?)\$/);
if (!match) return null;
const math = match[0];
return {
type: 'math',
content: math,
formula: match[1]
} as TextElementMath;
}

View File

@@ -12,9 +12,10 @@ export type TextElementMention = {
host: string;
};
export default function(text: string) {
export default function(text: string, before: string) {
const match = text.match(/^@[a-z0-9_]+(?:@[a-z0-9\.\-]+[a-z0-9])?/i);
if (!match) return null;
if (/[a-zA-Z0-9]$/.test(before)) return null;
const mention = match[0];
const { username, host } = parseAcct(mention.substr(1));
const canonical = host != null ? `@${username}@${toUnicode(host)}` : mention;

View File

@@ -8,7 +8,9 @@ export type TextElementQuote = {
quote: string;
};
export default function(text: string, isBegin: boolean) {
export default function(text: string, before: string) {
const isBegin = before == '';
const match = text.match(/^"([\s\S]+?)\n"/) || text.match(/^\n>([\s\S]+?)(\n\n|$)/) ||
(isBegin ? text.match(/^>([\s\S]+?)(\n\n|$)/) : null);

View File

@@ -8,7 +8,9 @@ export type TextElementTitle = {
title: string;
};
export default function(text: string, isBegin: boolean) {
export default function(text: string, before: string) {
const isBegin = before == '';
const match = isBegin ? text.match(/^(【|\[)(.+?)(】|])\n/) : text.match(/^\n(【|\[)(.+?)(】|])\n/);
if (!match) return null;
return {

View File

@@ -8,10 +8,13 @@ export type TextElementUrl = {
url: string;
};
export default function(text: string) {
const match = text.match(/^https?:\/\/[\w\/:%#@\$&\?!\(\)\[\]~\.=\+\-]+/);
export default function(text: string, before: string) {
const match = text.match(/^https?:\/\/[\w\/:%#@\$&\?!\(\)\[\]~\.,=\+\-]+/);
if (!match) return null;
const url = match[0];
let url = match[0];
if (url.endsWith('.')) url = url.substr(0, url.lastIndexOf('.'));
if (url.endsWith(',')) url = url.substr(0, url.lastIndexOf(','));
if (url.endsWith(')') && before.endsWith('(')) url = url.substr(0, url.lastIndexOf(')'));
return {
type: 'url',
content: url,

View File

@@ -8,6 +8,7 @@ import { TextElementCode } from './elements/code';
import { TextElementEmoji } from './elements/emoji';
import { TextElementHashtag } from './elements/hashtag';
import { TextElementInlineCode } from './elements/inline-code';
import { TextElementMath } from './elements/math';
import { TextElementLink } from './elements/link';
import { TextElementMention } from './elements/mention';
import { TextElementQuote } from './elements/quote';
@@ -29,6 +30,7 @@ const elements = [
require('./elements/hashtag'),
require('./elements/code'),
require('./elements/inline-code'),
require('./elements/math'),
require('./elements/quote'),
require('./elements/emoji'),
require('./elements/search'),
@@ -42,6 +44,7 @@ export type TextElement = { type: 'text', content: string }
| TextElementEmoji
| TextElementHashtag
| TextElementInlineCode
| TextElementMath
| TextElementLink
| TextElementMention
| TextElementQuote
@@ -49,7 +52,7 @@ export type TextElement = { type: 'text', content: string }
| TextElementTitle
| TextElementUrl
| TextElementMotion;
export type TextElementProcessor = (text: string, isBegin: boolean) => TextElement | TextElement[];
export type TextElementProcessor = (text: string, before: string) => TextElement | TextElement[];
export default (source: string): TextElement[] => {
if (source == null || source == '') {
@@ -65,12 +68,10 @@ export default (source: string): TextElement[] => {
}
}
let i = 0;
// パース
while (source != '') {
const parsed = elements.some(el => {
let _tokens = el(source, i == 0);
let _tokens = el(source, tokens.map(token => token.content).join(''));
if (_tokens) {
if (!Array.isArray(_tokens)) {
_tokens = [_tokens];
@@ -88,8 +89,6 @@ export default (source: string): TextElement[] => {
content: source[0]
});
}
i++;
}
const combineText = (es: TextElement[]): TextElement =>

View File

@@ -50,6 +50,7 @@ export type INote = {
userId: mongo.ObjectID;
appId: mongo.ObjectID;
viaMobile: boolean;
localOnly: boolean;
renoteCount: number;
repliesCount: number;
reactionCounts: any;

View File

@@ -6,6 +6,8 @@ export function createHttpJob(data: any) {
}
export function deliver(user: ILocalUser, content: any, to: any) {
if (content == null) return;
createHttpJob({
type: 'deliver',
user,

View File

@@ -92,7 +92,7 @@ export default async (job: bq.Job, done: any): Promise<void> => {
done();
return;
}
//#region Log
publishApLogStream({
direction: 'in',

View File

@@ -116,6 +116,7 @@ export async function createNote(value: any, resolver?: Resolver, silent = false
cw: note.summary,
text: text,
viaMobile: false,
localOnly: false,
geo: undefined,
visibility,
visibleUsers,

View File

@@ -66,7 +66,8 @@ router.get('/notes/:note', async (ctx, next) => {
const note = await Note.findOne({
_id: new mongo.ObjectID(ctx.params.note),
visibility: { $in: ['public', 'home'] }
visibility: { $in: ['public', 'home'] },
localOnly: { $ne: true }
});
if (note === null) {
@@ -83,7 +84,8 @@ router.get('/notes/:note', async (ctx, next) => {
router.get('/notes/:note/activity', async ctx => {
const note = await Note.findOne({
_id: new mongo.ObjectID(ctx.params.note),
visibility: { $in: ['public', 'home'] }
visibility: { $in: ['public', 'home'] },
localOnly: { $ne: true }
});
if (note === null) {

View File

@@ -55,7 +55,8 @@ export default async (ctx: Router.IRouterContext) => {
const query = {
userId: user._id,
visibility: { $in: ['public', 'home'] }
visibility: { $in: ['public', 'home'] },
localOnly: { $ne: true }
} as any;
if (sinceId) {

View File

@@ -74,6 +74,14 @@ export const meta = {
}
},
localOnly: {
validator: $.bool.optional,
default: false,
desc: {
'ja-JP': 'ローカルのみに投稿か否か。'
}
},
geo: {
validator: $.obj({
coordinates: $.arr().length(2)
@@ -226,6 +234,7 @@ export default define(meta, (ps, user, app) => new Promise(async (res, rej) => {
cw: ps.cw,
app,
viaMobile: ps.viaMobile,
localOnly: ps.localOnly,
visibility: ps.visibility,
visibleUsers,
geo: ps.geo

View File

@@ -75,9 +75,9 @@ handler.on('status', event => {
const parentState = parentStatuses[0].state;
const stillFailed = parentState == 'failure' || parentState == 'error';
if (stillFailed) {
post(`**BUILD STILL FAILED⚠**: ?[${commit.commit.message}](${commit.html_url})`);
post(`⚠️**BUILD STILL FAILED**⚠️: ?[${commit.commit.message}](${commit.html_url})`);
} else {
post(`**🚨BUILD FAILED🚨**: →→→?[${commit.commit.message}](${commit.html_url})←←←`);
post(`🚨**BUILD FAILED**🚨: →→→?[${commit.commit.message}](${commit.html_url})←←←`);
}
});
break;
@@ -87,7 +87,7 @@ handler.on('status', event => {
handler.on('push', event => {
const ref = event.ref;
switch (ref) {
case 'refs/heads/master':
case 'refs/heads/develop':
const pusher = event.pusher;
const compare = event.compare;
const commits: any[] = event.commits;
@@ -96,10 +96,6 @@ handler.on('push', event => {
commits.reverse().map(commit => `・[?[${commit.id.substr(0, 7)}](${commit.url})] ${commit.message.split('\n')[0]}`).join('\n'),
].join('\n'));
break;
case 'refs/heads/release':
const commit = event.commits[0];
post(`RELEASED: ${commit.message}`);
break;
}
});
@@ -128,6 +124,17 @@ handler.on('issue_comment', event => {
post(text);
});
handler.on('release', event => {
const action = event.action;
const release = event.release;
let text: string;
switch (action) {
case 'published': text = `🎁 **NEW RELEASE**: [${release.tag_name}](${release.html_url}) is out now. Enjoy!`; break;
default: return;
}
post(text);
});
handler.on('watch', event => {
const sender = event.sender;
post(`(((⭐️))) Starred by **${sender.login}** (((⭐️)))`, false);

View File

@@ -95,6 +95,7 @@ type Option = {
geo?: any;
poll?: any;
viaMobile?: boolean;
localOnly?: boolean;
cw?: string;
visibility?: string;
visibleUsers?: IUser[];
@@ -109,6 +110,7 @@ export default async (user: IUser, data: Option, silent = false) => new Promise<
if (data.createdAt == null) data.createdAt = new Date();
if (data.visibility == null) data.visibility = 'public';
if (data.viaMobile == null) data.viaMobile = false;
if (data.localOnly == null) data.localOnly = false;
if (data.visibleUsers) {
data.visibleUsers = erase(null, data.visibleUsers);
@@ -139,6 +141,16 @@ export default async (user: IUser, data: Option, silent = false) => new Promise<
return rej('Renote target is private of others');
}
// ローカルのみをRenoteしたらローカルのみにする
if (data.renote && data.renote.localOnly) {
data.localOnly = true;
}
// ローカルのみにリプライしたらローカルのみにする
if (data.reply && data.reply.localOnly) {
data.localOnly = true;
}
if (data.text) {
data.text = data.text.trim();
}
@@ -308,6 +320,8 @@ export default async (user: IUser, data: Option, silent = false) => new Promise<
});
async function renderActivity(data: Option, note: INote) {
if (data.localOnly) return null;
const content = data.renote && data.text == null && data.poll == null && (data.files == null || data.files.length == 0)
? renderAnnounce(data.renote.uri ? data.renote.uri : `${config.url}/notes/${data.renote._id}`, note)
: renderCreate(await renderNote(note, false), note);
@@ -389,6 +403,7 @@ async function insertNote(user: IUser, data: Option, tags: string[], emojis: str
emojis,
userId: user._id,
viaMobile: data.viaMobile,
localOnly: data.localOnly,
geo: data.geo || null,
appId: data.app ? data.app._id : null,
visibility: data.visibility,

View File

@@ -82,7 +82,7 @@ describe('Text', () => {
{ type: 'text', content: ' お腹ペコい' }
], tokens);
});
/*
it('ignore', () => {
const tokens = analyze('idolm@ster');
assert.deepEqual([
@@ -91,20 +91,19 @@ describe('Text', () => {
const tokens2 = analyze('@a\n@b\n@c');
assert.deepEqual([
{ type: 'mention', content: '@a', username: 'a', host: null },
{ type: 'mention', content: '@a', canonical: '@a', username: 'a', host: null },
{ type: 'text', content: '\n' },
{ type: 'mention', content: '@b', username: 'b', host: null },
{ type: 'mention', content: '@b', canonical: '@b', username: 'b', host: null },
{ type: 'text', content: '\n' },
{ type: 'mention', content: '@c', username: 'c', host: null }
{ type: 'mention', content: '@c', canonical: '@c', username: 'c', host: null }
], tokens2);
const tokens3 = analyze('**x**@a');
assert.deepEqual([
{ type: 'bold', content: '**x**', bold: 'x' },
{ type: 'mention', content: '@a', username: 'a', host: null }
{ type: 'mention', content: '@a', canonical: '@a', username: 'a', host: null }
], tokens3);
});
*/
});
it('hashtag', () => {
@@ -159,13 +158,68 @@ describe('Text', () => {
], tokens5);
});
it('url', () => {
const tokens = analyze('https://himasaku.net');
assert.deepEqual([{
type: 'url',
content: 'https://himasaku.net',
url: 'https://himasaku.net'
}], tokens);
describe('url', () => {
it('simple', () => {
const tokens = analyze('https://example.com');
assert.deepEqual([{
type: 'url',
content: 'https://example.com',
url: 'https://example.com'
}], tokens);
});
it('ignore trailing dot', () => {
const tokens = analyze('https://example.com.');
assert.deepEqual([{
type: 'url',
content: 'https://example.com',
url: 'https://example.com'
}, {
type: 'text', content: '.'
}], tokens);
});
it('with comma', () => {
const tokens = analyze('https://example.com/foo?bar=a,b');
assert.deepEqual([{
type: 'url',
content: 'https://example.com/foo?bar=a,b',
url: 'https://example.com/foo?bar=a,b'
}], tokens);
});
it('ignore trailing comma', () => {
const tokens = analyze('https://example.com/foo, bar');
assert.deepEqual([{
type: 'url',
content: 'https://example.com/foo',
url: 'https://example.com/foo'
}, {
type: 'text', content: ', bar'
}], tokens);
});
it('with brackets', () => {
const tokens = analyze('https://example.com/foo(bar)');
assert.deepEqual([{
type: 'url',
content: 'https://example.com/foo(bar)',
url: 'https://example.com/foo(bar)'
}], tokens);
});
it('ignore parent brackets', () => {
const tokens = analyze('(https://example.com/foo)');
assert.deepEqual([{
type: 'text', content: '('
}, {
type: 'url',
content: 'https://example.com/foo',
url: 'https://example.com/foo'
}, {
type: 'text', content: ')'
}], tokens);
});
});
it('link', () => {
@@ -210,6 +264,15 @@ describe('Text', () => {
assert.equal(tokens[0].content, '`var x = "Strawberry Pasta";`');
});
it('math', () => {
const fomula = 'x = {-b \\pm \\sqrt{b^2-4ac} \\over 2a}.';
const text = `$${fomula}$`;
const tokens = analyze(text);
assert.deepEqual([
{ type: 'math', content: text, formula: fomula }
], tokens);
});
it('search', () => {
const tokens1 = analyze('a b c 検索');
assert.deepEqual([