Compare commits
46 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cb0874f15a | ||
|
|
9239e37b45 | ||
|
|
57d80932a4 | ||
|
|
8569970fbe | ||
|
|
713e9ad5f4 | ||
|
|
59e229d962 | ||
|
|
3c5f09cda2 | ||
|
|
e42aa2530d | ||
|
|
1fd298ac9c | ||
|
|
1a73f52541 | ||
|
|
27e458f884 | ||
|
|
ba3e2a9371 | ||
|
|
831e8f8583 | ||
|
|
0ff390ed80 | ||
|
|
e3b8495431 | ||
|
|
da10ba3fea | ||
|
|
cc7de853b4 | ||
|
|
23d8235197 | ||
|
|
37e3d60ade | ||
|
|
83a3426dd5 | ||
|
|
a2549192ca | ||
|
|
9d3a1cab6e | ||
|
|
06f8d8f0a3 | ||
|
|
fa66b79e2d | ||
|
|
81312f5a93 | ||
|
|
ad84901f39 | ||
|
|
d2385a0e52 | ||
|
|
39285fc2d0 | ||
|
|
6e491c1466 | ||
|
|
84152aa663 | ||
|
|
600fc65c2f | ||
|
|
40e2733424 | ||
|
|
bceb02d760 | ||
|
|
aaaaf2681a | ||
|
|
3c3ef9bba0 | ||
|
|
338f3a981f | ||
|
|
a86ae9fa50 | ||
|
|
a3c4e8a1bc | ||
|
|
6a7c18e8db | ||
|
|
672b7a4c3d | ||
|
|
bf3fee4481 | ||
|
|
a3c8d1d732 | ||
|
|
bb03d8c49a | ||
|
|
fbd5abe3b6 | ||
|
|
5ac390abe9 | ||
|
|
766cae2299 |
@@ -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!
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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に戻る"
|
||||
|
||||
@@ -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つ必要です"
|
||||
|
||||
12
package.json
12
package.json
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
26
src/client/app/common/views/components/formula-core.vue
Normal file
26
src/client/app/common/views/components/formula-core.vue
Normal 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>
|
||||
20
src/client/app/common/views/components/formula.vue
Normal file
20
src/client/app/common/views/components/formula.vue
Normal 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>
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -227,6 +227,7 @@ export default define({
|
||||
background var(--desktopPostFormTextareaBg)
|
||||
border none
|
||||
border-bottom solid 1px var(--faceDivider)
|
||||
padding-right 30px
|
||||
|
||||
&:focus
|
||||
& + .emoji
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -15,6 +15,7 @@ const defaultSettings = {
|
||||
tagTimelines: [],
|
||||
fetchOnScroll: true,
|
||||
showMaps: true,
|
||||
remainDeletedNote: false,
|
||||
showPostFormOnTopOfTl: false,
|
||||
suggestRecentHashtags: true,
|
||||
showClockOnHeader: true,
|
||||
|
||||
@@ -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
|
||||
|
||||
12
src/index.ts
12
src/index.ts
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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];
|
||||
|
||||
20
src/mfm/parse/elements/math.ts
Normal file
20
src/mfm/parse/elements/math.ts
Normal 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;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 =>
|
||||
|
||||
@@ -50,6 +50,7 @@ export type INote = {
|
||||
userId: mongo.ObjectID;
|
||||
appId: mongo.ObjectID;
|
||||
viaMobile: boolean;
|
||||
localOnly: boolean;
|
||||
renoteCount: number;
|
||||
repliesCount: number;
|
||||
reactionCounts: any;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -92,7 +92,7 @@ export default async (job: bq.Job, done: any): Promise<void> => {
|
||||
done();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
//#region Log
|
||||
publishApLogStream({
|
||||
direction: 'in',
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
89
test/mfm.ts
89
test/mfm.ts
@@ -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([
|
||||
|
||||
Reference in New Issue
Block a user