Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
80105239dc | ||
|
|
baad11288a | ||
|
|
7e50646ede | ||
|
|
d4b8e47bcb | ||
|
|
0eefd2922c | ||
|
|
30c0f98691 | ||
|
|
06a7c2e138 | ||
|
|
3537b3de8e | ||
|
|
ed6450244d | ||
|
|
e813880392 | ||
|
|
9a57efa6d9 | ||
|
|
31de530497 | ||
|
|
16b6b1f2b3 | ||
|
|
a2a25eb5f8 | ||
|
|
274cf1af1c | ||
|
|
7d11c8b767 | ||
|
|
7e50e03cfb | ||
|
|
89d5df20a5 | ||
|
|
c09a2a37fe | ||
|
|
b5745877ca | ||
|
|
6e04549a9b | ||
|
|
38139ee6c9 | ||
|
|
6b96bd0185 | ||
|
|
f2b9863eea | ||
|
|
f56adce51f | ||
|
|
35362ed3c7 | ||
|
|
cd0b9a8e3f | ||
|
|
110aadd65c | ||
|
|
ff76c815b1 | ||
|
|
1b9b8912ae | ||
|
|
579b61a806 | ||
|
|
1e2b484929 | ||
|
|
25dd19dd8c |
@@ -7,27 +7,51 @@ maintainer:
|
|||||||
repository_url: https://github.com/syuilo/misskey # Repository URL
|
repository_url: https://github.com/syuilo/misskey # Repository URL
|
||||||
feedback_url: https://github.com/syuilo/misskey/issues # Feedback URL (e.g. github issue)
|
feedback_url: https://github.com/syuilo/misskey/issues # Feedback URL (e.g. github issue)
|
||||||
|
|
||||||
# URL and Port settings overview
|
|
||||||
# e.g., If you want to realize following structure:
|
|
||||||
#
|
|
||||||
# +--- https://example.com:123 ----------+
|
|
||||||
# +------+ |+-------------+ +---------------+|
|
|
||||||
# | User | ---> || Proxy (123) | ---> | Misskey (456) ||
|
|
||||||
# +------+ |+-------------+ +---------------+|
|
|
||||||
# +--------------------------------------+
|
|
||||||
#
|
|
||||||
# You need to set 'https://example.com:123' to 'url' prop and
|
|
||||||
# You need to set 456 to 'port' prop.
|
|
||||||
#
|
|
||||||
# In other words, the 'url' prop should be the final accessible URL seen by a user.
|
|
||||||
# 'port' prop is a port that the Misskey server should actually listen
|
|
||||||
# on and it is not necessarily the port that a user accesses.
|
|
||||||
|
|
||||||
url: http://localhost/
|
# Final accessible URL seen by a user.
|
||||||
|
url: https://example.tld/
|
||||||
|
|
||||||
|
|
||||||
|
### Port and TLS settings ######################################
|
||||||
|
#
|
||||||
|
# Misskey supports two deployment options for public.
|
||||||
|
#
|
||||||
|
|
||||||
|
# Option 1: With Reverse Proxy
|
||||||
|
#
|
||||||
|
# +----- https://example.tld/ ------------+
|
||||||
|
# +------+ |+-------------+ +----------------+|
|
||||||
|
# | User | ---> || Proxy (443) | ---> | Misskey (3000) ||
|
||||||
|
# +------+ |+-------------+ +----------------+|
|
||||||
|
# +---------------------------------------+
|
||||||
|
#
|
||||||
|
# You need to setup reverse proxy. (eg. Nginx)
|
||||||
|
# You do not define 'https' section.
|
||||||
|
|
||||||
|
# Option 2: Standalone
|
||||||
|
#
|
||||||
|
# +- https://example.tld/ -+
|
||||||
|
# +------+ | +---------------+ |
|
||||||
|
# | User | ---> | | Misskey (443) | |
|
||||||
|
# +------+ | +---------------+ |
|
||||||
|
# +------------------------+
|
||||||
|
#
|
||||||
|
# You need to run Misskey as root.
|
||||||
|
# You need to set Certificate in 'https' section.
|
||||||
|
|
||||||
|
# To use option 1, uncomment below line.
|
||||||
|
# port: 3000 # A port that your Misskey server should listen.
|
||||||
|
|
||||||
|
# To use option 2, uncomment below lines.
|
||||||
|
# port: 443
|
||||||
|
#
|
||||||
|
# https:
|
||||||
|
# # path for certification
|
||||||
|
# key: /etc/letsencrypt/live/example.tld/privkey.pem
|
||||||
|
# cert: /etc/letsencrypt/live/example.tld/fullchain.pem
|
||||||
|
|
||||||
|
################################################################
|
||||||
|
|
||||||
# A port that your Misskey server should listen.
|
|
||||||
# This value is not a port to use when accessing with a browser.
|
|
||||||
port: 80
|
|
||||||
|
|
||||||
mongodb:
|
mongodb:
|
||||||
host: localhost
|
host: localhost
|
||||||
@@ -98,12 +122,6 @@ drive:
|
|||||||
# Below settings are optional
|
# Below settings are optional
|
||||||
#
|
#
|
||||||
|
|
||||||
# TLS
|
|
||||||
# https:
|
|
||||||
# # path for certification
|
|
||||||
# key: example-tls-key
|
|
||||||
# cert: example-tls-cert
|
|
||||||
|
|
||||||
# Elasticsearch
|
# Elasticsearch
|
||||||
# elasticsearch:
|
# elasticsearch:
|
||||||
# host: localhost
|
# host: localhost
|
||||||
|
|||||||
@@ -5,6 +5,12 @@ ChangeLog
|
|||||||
|
|
||||||
This document describes breaking changes only.
|
This document describes breaking changes only.
|
||||||
|
|
||||||
|
9.0.0
|
||||||
|
-----
|
||||||
|
|
||||||
|
Misskey v8.64.0 を使っている方は、9.0.0に際しては特にすべきことはありません。
|
||||||
|
Misskey v8.64.0 に満たないバージョンをお使いの方は、一旦8.64.0にアップデートして(そして起動して)から9.0.0に再度アップデートしてください。
|
||||||
|
|
||||||
8.0.0
|
8.0.0
|
||||||
-----
|
-----
|
||||||
|
|
||||||
|
|||||||
12
README.md
12
README.md
@@ -73,17 +73,17 @@ Please see [Contribution guide](./CONTRIBUTING.md).
|
|||||||
<table><tr>
|
<table><tr>
|
||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12731202/0995c46cdcb54153ab5f073f5869b70a/1?token-time=2145916800&token-hash=Yd60FK_SWfQO56SeiJpy1tDHOnCV4xdEywQe8gn5_Wo%3D" alt="negao"></td>
|
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12731202/0995c46cdcb54153ab5f073f5869b70a/1?token-time=2145916800&token-hash=Yd60FK_SWfQO56SeiJpy1tDHOnCV4xdEywQe8gn5_Wo%3D" alt="negao"></td>
|
||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/13099460/43cecdbaa63a40d79bf50a96b9910b9d/1?token-time=2145916800&token-hash=d6P5MWHHsCMxUuBAEPAoVc5wLUR19mIhqAq7Ma9h9rI%3D" alt="ne_moni"></td>
|
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/13099460/43cecdbaa63a40d79bf50a96b9910b9d/1?token-time=2145916800&token-hash=d6P5MWHHsCMxUuBAEPAoVc5wLUR19mIhqAq7Ma9h9rI%3D" alt="ne_moni"></td>
|
||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12913507/f7181eacafe8469a93033d85f5969c29/1?token-time=2145916800&token-hash=f03BFb4S2FUx9YEt87TnEmifb4h33OywGBW2akQVtQY%3D" alt="Melilot"></td>
|
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12913507/f7181eacafe8469a93033d85f5969c29/2?token-time=2145916800&token-hash=mgPdX9TqZxEg4TTPuc477dxhIgYk9246qafjWZEqZ7g%3D" alt="Melilot"></td>
|
||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12999811/5f349fafcce44dd1824a8b1ebbec4564/2?token-time=2145916800&token-hash=rwZ8qvbm_kpA4ib3kc07tVKupXeySpY5ATQFGxfL9v0%3D" alt="Axella"></td>
|
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12999811/5f349fafcce44dd1824a8b1ebbec4564/2?token-time=2145916800&token-hash=rwZ8qvbm_kpA4ib3kc07tVKupXeySpY5ATQFGxfL9v0%3D" alt="Xeltica"></td>
|
||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/3384329/8b713330cb27404ea6e9fac50ff96efe/1?token-time=2145916800&token-hash=0eu4-m1gTWA9PhptVZt6rdKcusqcD7RB87rJT23VVFI%3D" alt="べすれい"></td>
|
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/3384329/8b713330cb27404ea6e9fac50ff96efe/1?token-time=2145916800&token-hash=0eu4-m1gTWA9PhptVZt6rdKcusqcD7RB87rJT23VVFI%3D" alt="べすれい"></td>
|
||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12021162/963128bb8d14476dbd8407943db8f31a/1?token-time=2145916800&token-hash=GgJ_NmUB6_nnRNLVGUWjV-WX91On7BOu59LKncYV9fE%3D" alt="gutfuckllc"></td>
|
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12021162/963128bb8d14476dbd8407943db8f31a/1?token-time=2145916800&token-hash=GgJ_NmUB6_nnRNLVGUWjV-WX91On7BOu59LKncYV9fE%3D" alt="gutfuckllc"></td>
|
||||||
<td><img src="https://c8.patreon.com/2/100/12718187" alt="Peter G."></td>
|
<td><img src="https://c8.patreon.com/2/100/12718187" alt="Peter G."></td>
|
||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/13039004/509d0c412eb14ae08d6a812a3054f7d6/1?token-time=2145916800&token-hash=zwSu01tOtn5xTUucDZHuPsCxF2HBEMVs9ROJKTlEV_o%3D" alt="nemu"></td>
|
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/13039004/509d0c412eb14ae08d6a812a3054f7d6/1?token-time=2145916800&token-hash=zwSu01tOtn5xTUucDZHuPsCxF2HBEMVs9ROJKTlEV_o%3D" alt="nemu"></td>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<td><a href="https://www.patreon.com/user?u=12731202">negao</a></td>
|
<td><a href="https://www.patreon.com/negao">negao</a></td>
|
||||||
<td><a href="https://www.patreon.com/user?u=13099460">ne_moni</a></td>
|
<td><a href="https://www.patreon.com/user?u=13099460">ne_moni</a></td>
|
||||||
<td><a href="https://www.patreon.com/user?u=12913507">Melilot</a></td>
|
<td><a href="https://www.patreon.com/user?u=12913507">Melilot</a></td>
|
||||||
<td><a href="https://www.patreon.com/AxellaMC">Axella</a></td>
|
<td><a href="https://www.patreon.com/AxellaMC">Xeltica</a></td>
|
||||||
<td><a href="https://www.patreon.com/user?u=3384329">べすれい</a></td>
|
<td><a href="https://www.patreon.com/user?u=3384329">べすれい</a></td>
|
||||||
<td><a href="https://www.patreon.com/gutfuckllc">gutfuckllc</a></td>
|
<td><a href="https://www.patreon.com/gutfuckllc">gutfuckllc</a></td>
|
||||||
<td><a href="https://www.patreon.com/user?u=12718187">Peter G.</a></td>
|
<td><a href="https://www.patreon.com/user?u=12718187">Peter G.</a></td>
|
||||||
@@ -91,19 +91,17 @@ Please see [Contribution guide](./CONTRIBUTING.md).
|
|||||||
</tr></table>
|
</tr></table>
|
||||||
<table><tr>
|
<table><tr>
|
||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/5881381/6235ca5d3fb04c8e95ef5b4ff2abcc18/2?token-time=2145916800&token-hash=zElv7ZcPL3viGsXbNG_KWiKrbV0vvw1gk0panx8DJoo%3D" alt="Naoki Kosaka"></td>
|
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/5881381/6235ca5d3fb04c8e95ef5b4ff2abcc18/2?token-time=2145916800&token-hash=zElv7ZcPL3viGsXbNG_KWiKrbV0vvw1gk0panx8DJoo%3D" alt="Naoki Kosaka"></td>
|
||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12931605/ead494101f364dffa90efe49e36fb494/1?token-time=2145916800&token-hash=NzSFPjIlodXyv41rwK61aZWVZWfI4surJaNj8vWKvqM%3D" alt="Reiju"></td>
|
|
||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/13034746/c711c7f58e204ecfbc2fd646bc8a4eee/1?token-time=2145916800&token-hash=UERBN4OyP7Nh5XwwdDg0N0IE5cD6_qUQMO81Z5Wizso%3D" alt="Hiratake"></td>
|
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/13034746/c711c7f58e204ecfbc2fd646bc8a4eee/1?token-time=2145916800&token-hash=UERBN4OyP7Nh5XwwdDg0N0IE5cD6_qUQMO81Z5Wizso%3D" alt="Hiratake"></td>
|
||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/4503830/ccf2cc867ea64de0b524bb2e24b9a1cb/1?token-time=2145916800&token-hash=S1zP0QyLU52Dqq6dtc9qNYyWfW86XrYHiR4NMbeOrnA%3D" alt="dansup"></td>
|
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/4503830/ccf2cc867ea64de0b524bb2e24b9a1cb/1?token-time=2145916800&token-hash=S1zP0QyLU52Dqq6dtc9qNYyWfW86XrYHiR4NMbeOrnA%3D" alt="dansup"></td>
|
||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12531784/93a45137841849329ba692da92ac7c60/1?token-time=2145916800&token-hash=tMosUojzUYJCH_3t--tvYA-SMCyrS__hzSndyaRSnbo%3D" alt="Takashi Shibuya"></td>
|
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12531784/93a45137841849329ba692da92ac7c60/1?token-time=2145916800&token-hash=tMosUojzUYJCH_3t--tvYA-SMCyrS__hzSndyaRSnbo%3D" alt="Takashi Shibuya"></td>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<td><a href="https://www.patreon.com/user?u=5881381">Naoki Kosaka</a></td>
|
<td><a href="https://www.patreon.com/user?u=5881381">Naoki Kosaka</a></td>
|
||||||
<td><a href="https://www.patreon.com/user?u=12931605">Reiju</a></td>
|
|
||||||
<td><a href="https://www.patreon.com/hiratake">Hiratake</a></td>
|
<td><a href="https://www.patreon.com/hiratake">Hiratake</a></td>
|
||||||
<td><a href="https://www.patreon.com/dansup">dansup</a></td>
|
<td><a href="https://www.patreon.com/dansup">dansup</a></td>
|
||||||
<td><a href="https://www.patreon.com/user?u=12531784">Takashi Shibuya</a></td>
|
<td><a href="https://www.patreon.com/user?u=12531784">Takashi Shibuya</a></td>
|
||||||
</tr></table>
|
</tr></table>
|
||||||
|
|
||||||
**Last updated:** Sun, 02 Sep 2018 05:30:06 UTC
|
**Last updated:** Tue, 02 Oct 2018 09:25:07 UTC
|
||||||
<!-- PATREON_END -->
|
<!-- PATREON_END -->
|
||||||
|
|
||||||
:four_leaf_clover: Copyright
|
:four_leaf_clover: Copyright
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
* Gulp tasks
|
* Gulp tasks
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as fs from 'fs';
|
|
||||||
import * as gulp from 'gulp';
|
import * as gulp from 'gulp';
|
||||||
import * as gutil from 'gulp-util';
|
import * as gutil from 'gulp-util';
|
||||||
import * as ts from 'gulp-typescript';
|
import * as ts from 'gulp-typescript';
|
||||||
@@ -166,9 +165,7 @@ gulp.task('build:client:pug', [
|
|||||||
.pipe(pug({
|
.pipe(pug({
|
||||||
locals: {
|
locals: {
|
||||||
themeColor: constants.themeColor,
|
themeColor: constants.themeColor,
|
||||||
facss: fa.dom.css(),
|
facss: fa.dom.css()
|
||||||
//hljscss: fs.readFileSync('./node_modules/highlight.js/styles/default.css', 'utf8')
|
|
||||||
hljscss: fs.readFileSync('./src/client/assets/code-highlight.css', 'utf8')
|
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
.pipe(htmlmin({
|
.pipe(htmlmin({
|
||||||
|
|||||||
@@ -439,6 +439,7 @@ common/views/pages/follow.vue:
|
|||||||
following: "Following"
|
following: "Following"
|
||||||
follow: "Follow"
|
follow: "Follow"
|
||||||
request-pending: "Pending follow request"
|
request-pending: "Pending follow request"
|
||||||
|
follow-processing: "Processing follow"
|
||||||
follow-request: "Follow request"
|
follow-request: "Follow request"
|
||||||
desktop:
|
desktop:
|
||||||
banner-crop-title: "Crop the part that appears as a banner"
|
banner-crop-title: "Crop the part that appears as a banner"
|
||||||
@@ -565,6 +566,7 @@ desktop/views/components/follow-button.vue:
|
|||||||
following: "Following"
|
following: "Following"
|
||||||
follow: "Follow"
|
follow: "Follow"
|
||||||
request-pending: "Pending follow request"
|
request-pending: "Pending follow request"
|
||||||
|
follow-processing: "Processing follow"
|
||||||
follow-request: "Follow request"
|
follow-request: "Follow request"
|
||||||
desktop/views/components/followers-window.vue:
|
desktop/views/components/followers-window.vue:
|
||||||
followers: "{}'s followers"
|
followers: "{}'s followers"
|
||||||
@@ -1044,6 +1046,7 @@ mobile/views/components/follow-button.vue:
|
|||||||
following: "Following"
|
following: "Following"
|
||||||
follow: "Follow"
|
follow: "Follow"
|
||||||
request-pending: "Pending follow request"
|
request-pending: "Pending follow request"
|
||||||
|
follow-processing: "Processing follow"
|
||||||
follow-request: "Follow request"
|
follow-request: "Follow request"
|
||||||
mobile/views/components/friends-maker.vue:
|
mobile/views/components/friends-maker.vue:
|
||||||
title: "Let's follow them"
|
title: "Let's follow them"
|
||||||
|
|||||||
@@ -291,6 +291,7 @@ common/views/components/theme.vue:
|
|||||||
install-a-theme: "テーマのインストール"
|
install-a-theme: "テーマのインストール"
|
||||||
theme-code: "テーマコード"
|
theme-code: "テーマコード"
|
||||||
install: "インストール"
|
install: "インストール"
|
||||||
|
installed: "「{}」をインストールしました"
|
||||||
create-a-theme: "テーマの作成"
|
create-a-theme: "テーマの作成"
|
||||||
save-created-theme: "テーマを保存"
|
save-created-theme: "テーマを保存"
|
||||||
primary-color: "プライマリ カラー"
|
primary-color: "プライマリ カラー"
|
||||||
@@ -306,6 +307,14 @@ common/views/components/theme.vue:
|
|||||||
saved: "保存しました"
|
saved: "保存しました"
|
||||||
installed-themes: "インストールされたテーマ"
|
installed-themes: "インストールされたテーマ"
|
||||||
select-theme: "テーマを選択してください"
|
select-theme: "テーマを選択してください"
|
||||||
|
uninstall: "アンインストール"
|
||||||
|
uninstalled: "「{}」をアンインストールしました"
|
||||||
|
author: "作者"
|
||||||
|
desc: "説明"
|
||||||
|
export: "エクスポート"
|
||||||
|
import: "インポート"
|
||||||
|
import-by-code: "またはコードをペースト"
|
||||||
|
theme-name-required: "テーマ名は必須です。"
|
||||||
|
|
||||||
common/views/components/cw-button.vue:
|
common/views/components/cw-button.vue:
|
||||||
hide: "隠す"
|
hide: "隠す"
|
||||||
@@ -511,6 +520,7 @@ common/views/pages/follow.vue:
|
|||||||
following: "フォロー中"
|
following: "フォロー中"
|
||||||
follow: "フォロー"
|
follow: "フォロー"
|
||||||
request-pending: "フォロー許可待ち"
|
request-pending: "フォロー許可待ち"
|
||||||
|
follow-processing: "フォロー処理中"
|
||||||
follow-request: "フォロー申請"
|
follow-request: "フォロー申請"
|
||||||
|
|
||||||
desktop:
|
desktop:
|
||||||
@@ -653,6 +663,7 @@ desktop/views/components/follow-button.vue:
|
|||||||
following: "フォロー中"
|
following: "フォロー中"
|
||||||
follow: "フォロー"
|
follow: "フォロー"
|
||||||
request-pending: "フォロー許可待ち"
|
request-pending: "フォロー許可待ち"
|
||||||
|
follow-processing: "フォロー処理中"
|
||||||
follow-request: "フォロー申請"
|
follow-request: "フォロー申請"
|
||||||
|
|
||||||
desktop/views/components/followers-window.vue:
|
desktop/views/components/followers-window.vue:
|
||||||
@@ -1229,6 +1240,7 @@ mobile/views/components/follow-button.vue:
|
|||||||
following: "フォロー中"
|
following: "フォロー中"
|
||||||
follow: "フォロー"
|
follow: "フォロー"
|
||||||
request-pending: "フォロー許可待ち"
|
request-pending: "フォロー許可待ち"
|
||||||
|
follow-processing: "フォロー処理中"
|
||||||
follow-request: "フォロー申請"
|
follow-request: "フォロー申請"
|
||||||
|
|
||||||
mobile/views/components/friends-maker.vue:
|
mobile/views/components/friends-maker.vue:
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "misskey",
|
"name": "misskey",
|
||||||
"author": "syuilo <i@syuilo.com>",
|
"author": "syuilo <i@syuilo.com>",
|
||||||
"version": "8.63.0",
|
"version": "9.4.0",
|
||||||
"clientVersion": "1.0.10040",
|
"clientVersion": "1.0.10062",
|
||||||
"codename": "nighthike",
|
"codename": "nighthike",
|
||||||
"main": "./built/index.js",
|
"main": "./built/index.js",
|
||||||
"private": true,
|
"private": true,
|
||||||
@@ -134,6 +134,8 @@
|
|||||||
"is-url": "1.2.4",
|
"is-url": "1.2.4",
|
||||||
"js-yaml": "3.12.0",
|
"js-yaml": "3.12.0",
|
||||||
"jsdom": "11.12.0",
|
"jsdom": "11.12.0",
|
||||||
|
"json5": "2.1.0",
|
||||||
|
"json5-loader": "1.0.1",
|
||||||
"koa": "2.5.1",
|
"koa": "2.5.1",
|
||||||
"koa-bodyparser": "4.2.1",
|
"koa-bodyparser": "4.2.1",
|
||||||
"koa-compress": "3.0.0",
|
"koa-compress": "3.0.0",
|
||||||
|
|||||||
@@ -5,9 +5,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import { url, lang } from './config';
|
import { url, lang } from './config';
|
||||||
import applyTheme from './common/scripts/theme';
|
|
||||||
const darkTheme = require('../theme/dark');
|
|
||||||
const halloweenTheme = require('../theme/halloween');
|
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
computed: {
|
computed: {
|
||||||
|
|||||||
@@ -34,9 +34,6 @@ html
|
|||||||
//- FontAwesome style
|
//- FontAwesome style
|
||||||
style #{facss}
|
style #{facss}
|
||||||
|
|
||||||
//- highlight.js style
|
|
||||||
style #{hljscss}
|
|
||||||
|
|
||||||
body
|
body
|
||||||
noscript: p
|
noscript: p
|
||||||
| JavaScriptを有効にしてください
|
| JavaScriptを有効にしてください
|
||||||
|
|||||||
@@ -24,7 +24,6 @@
|
|||||||
const theme = localStorage.getItem('theme');
|
const theme = localStorage.getItem('theme');
|
||||||
if (theme) {
|
if (theme) {
|
||||||
Object.entries(JSON.parse(theme)).forEach(([k, v]) => {
|
Object.entries(JSON.parse(theme)).forEach(([k, v]) => {
|
||||||
if (k == 'meta') return;
|
|
||||||
document.documentElement.style.setProperty(`--${k}`, v.toString());
|
document.documentElement.style.setProperty(`--${k}`, v.toString());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
<template>
|
<template>
|
||||||
<form class="mk-signin" :class="{ signing }" @submit.prevent="onSubmit">
|
<form class="mk-signin" :class="{ signing }" @submit.prevent="onSubmit">
|
||||||
<div class="avatar" :style="{ backgroundImage: user ? `url('${ user.avatarUrl }')` : null }" v-show="withAvatar"></div>
|
<div class="avatar" :style="{ backgroundImage: user ? `url('${ user.avatarUrl }')` : null }" v-show="withAvatar"></div>
|
||||||
<ui-input v-model="username" type="text" pattern="^[a-zA-Z0-9_]+$" spellcheck="false" autofocus required @input="onUsernameChange">
|
<ui-input v-model="username" type="text" pattern="^[a-zA-Z0-9_]+$" spellcheck="false" autofocus required @input="onUsernameChange" styl="fill">
|
||||||
<span>%i18n:@username%</span>
|
<span>%i18n:@username%</span>
|
||||||
<span slot="prefix">@</span>
|
<span slot="prefix">@</span>
|
||||||
<span slot="suffix">@{{ host }}</span>
|
<span slot="suffix">@{{ host }}</span>
|
||||||
</ui-input>
|
</ui-input>
|
||||||
<ui-input v-model="password" type="password" required>
|
<ui-input v-model="password" type="password" required styl="fill">
|
||||||
<span>%i18n:@password%</span>
|
<span>%i18n:@password%</span>
|
||||||
<span slot="prefix">%fa:lock%</span>
|
<span slot="prefix">%fa:lock%</span>
|
||||||
</ui-input>
|
</ui-input>
|
||||||
<ui-input v-if="user && user.twoFactorEnabled" v-model="token" type="number" required/>
|
<ui-input v-if="user && user.twoFactorEnabled" v-model="token" type="number" required styl="fill"/>
|
||||||
<ui-button type="submit" :disabled="signing">{{ signing ? '%i18n:@signing-in%' : '%i18n:@signin%' }}</ui-button>
|
<ui-button type="submit" :disabled="signing">{{ signing ? '%i18n:@signing-in%' : '%i18n:@signin%' }}</ui-button>
|
||||||
<p style="margin: 8px 0;">%i18n:@or% <a :href="`${apiUrl}/signin/twitter`">%i18n:@signin-with-twitter%</a></p>
|
<p style="margin: 8px 0;">%i18n:@or% <a :href="`${apiUrl}/signin/twitter`">%i18n:@signin-with-twitter%</a></p>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<form class="mk-signup" @submit.prevent="onSubmit" :autocomplete="Math.random()">
|
<form class="mk-signup" @submit.prevent="onSubmit" :autocomplete="Math.random()">
|
||||||
<template v-if="meta">
|
<template v-if="meta">
|
||||||
<ui-input v-if="meta.disableRegistration" v-model="invitationCode" type="text" :autocomplete="Math.random()" spellcheck="false" required>
|
<ui-input v-if="meta.disableRegistration" v-model="invitationCode" type="text" :autocomplete="Math.random()" spellcheck="false" required styl="fill">
|
||||||
<span>%i18n:@invitation-code%</span>
|
<span>%i18n:@invitation-code%</span>
|
||||||
<span slot="prefix">%fa:id-card-alt%</span>
|
<span slot="prefix">%fa:id-card-alt%</span>
|
||||||
<p slot="text" v-html="'%i18n:@invitation-info%'.replace('{}', meta.maintainer.url)"></p>
|
<p slot="text" v-html="'%i18n:@invitation-info%'.replace('{}', meta.maintainer.url)"></p>
|
||||||
</ui-input>
|
</ui-input>
|
||||||
<ui-input v-model="username" type="text" pattern="^[a-zA-Z0-9_]{1,20}$" :autocomplete="Math.random()" spellcheck="false" required @input="onChangeUsername">
|
<ui-input v-model="username" type="text" pattern="^[a-zA-Z0-9_]{1,20}$" :autocomplete="Math.random()" spellcheck="false" required @input="onChangeUsername" styl="fill">
|
||||||
<span>%i18n:@username%</span>
|
<span>%i18n:@username%</span>
|
||||||
<span slot="prefix">@</span>
|
<span slot="prefix">@</span>
|
||||||
<span slot="suffix">@{{ host }}</span>
|
<span slot="suffix">@{{ host }}</span>
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
<p slot="text" v-if="usernameState == 'min-range'" style="color:#FF1161">%fa:exclamation-triangle .fw% %i18n:@too-short%</p>
|
<p slot="text" v-if="usernameState == 'min-range'" style="color:#FF1161">%fa:exclamation-triangle .fw% %i18n:@too-short%</p>
|
||||||
<p slot="text" v-if="usernameState == 'max-range'" style="color:#FF1161">%fa:exclamation-triangle .fw% %i18n:@too-long%</p>
|
<p slot="text" v-if="usernameState == 'max-range'" style="color:#FF1161">%fa:exclamation-triangle .fw% %i18n:@too-long%</p>
|
||||||
</ui-input>
|
</ui-input>
|
||||||
<ui-input v-model="password" type="password" :autocomplete="Math.random()" required @input="onChangePassword" :with-password-meter="true">
|
<ui-input v-model="password" type="password" :autocomplete="Math.random()" required @input="onChangePassword" :with-password-meter="true" styl="fill">
|
||||||
<span>%i18n:@password%</span>
|
<span>%i18n:@password%</span>
|
||||||
<span slot="prefix">%fa:lock%</span>
|
<span slot="prefix">%fa:lock%</span>
|
||||||
<div slot="text">
|
<div slot="text">
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
<p slot="text" v-if="passwordStrength == 'high'" style="color:#3CB7B5">%fa:check .fw% %i18n:@strong-password%</p>
|
<p slot="text" v-if="passwordStrength == 'high'" style="color:#3CB7B5">%fa:check .fw% %i18n:@strong-password%</p>
|
||||||
</div>
|
</div>
|
||||||
</ui-input>
|
</ui-input>
|
||||||
<ui-input v-model="retypedPassword" type="password" :autocomplete="Math.random()" required @input="onChangePasswordRetype">
|
<ui-input v-model="retypedPassword" type="password" :autocomplete="Math.random()" required @input="onChangePasswordRetype" styl="fill">
|
||||||
<span>%i18n:@password% (%i18n:@retype%)</span>
|
<span>%i18n:@password% (%i18n:@retype%)</span>
|
||||||
<span slot="prefix">%fa:lock%</span>
|
<span slot="prefix">%fa:lock%</span>
|
||||||
<div slot="text">
|
<div slot="text">
|
||||||
|
|||||||
@@ -3,19 +3,19 @@
|
|||||||
<label>
|
<label>
|
||||||
<span>%i18n:@light-theme%</span>
|
<span>%i18n:@light-theme%</span>
|
||||||
<ui-select v-model="light" placeholder="%i18n:@light-theme%">
|
<ui-select v-model="light" placeholder="%i18n:@light-theme%">
|
||||||
<option v-for="x in themes" :value="x.meta.id" :key="x.meta.id">{{ x.meta.name }}</option>
|
<option v-for="x in themes" :value="x.id" :key="x.id">{{ x.name }}</option>
|
||||||
</ui-select>
|
</ui-select>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label>
|
<label>
|
||||||
<span>%i18n:@dark-theme%</span>
|
<span>%i18n:@dark-theme%</span>
|
||||||
<ui-select v-model="dark" placeholder="%i18n:@dark-theme%">
|
<ui-select v-model="dark" placeholder="%i18n:@dark-theme%">
|
||||||
<option v-for="x in themes" :value="x.meta.id" :key="x.meta.id">{{ x.meta.name }}</option>
|
<option v-for="x in themes" :value="x.id" :key="x.id">{{ x.name }}</option>
|
||||||
</ui-select>
|
</ui-select>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<details class="creator">
|
<details class="creator">
|
||||||
<summary>%i18n:@create-a-theme%</summary>
|
<summary>%fa:palette% %i18n:@create-a-theme%</summary>
|
||||||
<div>
|
<div>
|
||||||
<span>%i18n:@base-theme%:</span>
|
<span>%i18n:@base-theme%:</span>
|
||||||
<ui-radio v-model="myThemeBase" value="light">%i18n:@base-theme-light%</ui-radio>
|
<ui-radio v-model="myThemeBase" value="light">%i18n:@base-theme-light%</ui-radio>
|
||||||
@@ -25,6 +25,9 @@
|
|||||||
<ui-input v-model="myThemeName">
|
<ui-input v-model="myThemeName">
|
||||||
<span>%i18n:@theme-name%</span>
|
<span>%i18n:@theme-name%</span>
|
||||||
</ui-input>
|
</ui-input>
|
||||||
|
<ui-textarea v-model="myThemeDesc">
|
||||||
|
<span>%i18n:@desc%</span>
|
||||||
|
</ui-textarea>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div style="padding-bottom:8px;">%i18n:@primary-color%:</div>
|
<div style="padding-bottom:8px;">%i18n:@primary-color%:</div>
|
||||||
@@ -38,37 +41,64 @@
|
|||||||
<div style="padding-bottom:8px;">%i18n:@text-color%:</div>
|
<div style="padding-bottom:8px;">%i18n:@text-color%:</div>
|
||||||
<color-picker v-model="myThemeText"/>
|
<color-picker v-model="myThemeText"/>
|
||||||
</div>
|
</div>
|
||||||
<ui-button @click="preview()">%i18n:@preview-created-theme%</ui-button>
|
<ui-button @click="preview()">%fa:eye% %i18n:@preview-created-theme%</ui-button>
|
||||||
<ui-button primary @click="gen()">%i18n:@save-created-theme%</ui-button>
|
<ui-button primary @click="gen()">%fa:save R% %i18n:@save-created-theme%</ui-button>
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>%i18n:@install-a-theme%</summary>
|
<summary>%fa:download% %i18n:@install-a-theme%</summary>
|
||||||
|
<ui-button @click="import_()">%fa:file-import% %i18n:@import%</ui-button>
|
||||||
|
<input ref="file" type="file" accept=".misskeytheme" style="display:none;" @change="onUpdateImportFile"/>
|
||||||
|
<p>%i18n:@import-by-code%:</p>
|
||||||
<ui-textarea v-model="installThemeCode">
|
<ui-textarea v-model="installThemeCode">
|
||||||
<span>%i18n:@theme-code%</span>
|
<span>%i18n:@theme-code%</span>
|
||||||
</ui-textarea>
|
</ui-textarea>
|
||||||
<ui-button @click="install()">%i18n:@install%</ui-button>
|
<ui-button @click="() => install(this.installThemeCode)">%fa:check% %i18n:@install%</ui-button>
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>%i18n:@installed-themes%</summary>
|
<summary>%fa:folder-open% %i18n:@installed-themes%</summary>
|
||||||
<ui-select v-model="selectedInstalledTheme" placeholder="%i18n:@select-theme%">
|
<ui-select v-model="selectedInstalledThemeId" placeholder="%i18n:@select-theme%">
|
||||||
<option v-for="x in installedThemes" :value="x.meta.id" :key="x.meta.id">{{ x.meta.name }}</option>
|
<option v-for="x in installedThemes" :value="x.id" :key="x.id">{{ x.name }}</option>
|
||||||
</ui-select>
|
</ui-select>
|
||||||
<ui-textarea readonly :value="selectedInstalledThemeCode">
|
<template v-if="selectedInstalledTheme">
|
||||||
<span>%i18n:@theme-code%</span>
|
<ui-input readonly :value="selectedInstalledTheme.author">
|
||||||
</ui-textarea>
|
<span>%i18n:@author%</span>
|
||||||
|
</ui-input>
|
||||||
|
<ui-textarea v-if="selectedInstalledTheme.desc" readonly :value="selectedInstalledTheme.desc">
|
||||||
|
<span>%i18n:@desc%</span>
|
||||||
|
</ui-textarea>
|
||||||
|
<ui-textarea readonly :value="selectedInstalledThemeCode">
|
||||||
|
<span>%i18n:@theme-code%</span>
|
||||||
|
</ui-textarea>
|
||||||
|
<ui-button @click="export_()" link :download="`${selectedInstalledTheme.name}.misskeytheme`" ref="export">%fa:box% %i18n:@export%</ui-button>
|
||||||
|
<ui-button @click="uninstall()">%fa:trash-alt R% %i18n:@uninstall%</ui-button>
|
||||||
|
</template>
|
||||||
</details>
|
</details>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import { apiUrl, docsUrl } from '../../../config';
|
import { lightTheme, darkTheme, builtinThemes, applyTheme, Theme } from '../../../theme';
|
||||||
import { lightTheme, darkTheme, builtinThemes, applyTheme } from '../../../theme';
|
|
||||||
import { Chrome } from 'vue-color';
|
import { Chrome } from 'vue-color';
|
||||||
import * as uuid from 'uuid';
|
import * as uuid from 'uuid';
|
||||||
import * as tinycolor from 'tinycolor2';
|
import * as tinycolor from 'tinycolor2';
|
||||||
|
import * as JSON5 from 'json5';
|
||||||
|
|
||||||
|
// 後方互換性のため
|
||||||
|
function convertOldThemedefinition(t) {
|
||||||
|
const t2 = {
|
||||||
|
id: t.meta.id,
|
||||||
|
name: t.meta.name,
|
||||||
|
author: t.meta.author,
|
||||||
|
base: t.meta.base,
|
||||||
|
vars: t.meta.vars,
|
||||||
|
props: t
|
||||||
|
};
|
||||||
|
delete t2.props.meta;
|
||||||
|
return t2;
|
||||||
|
}
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
components: {
|
components: {
|
||||||
@@ -78,21 +108,22 @@ export default Vue.extend({
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
installThemeCode: null,
|
installThemeCode: null,
|
||||||
selectedInstalledTheme: null,
|
selectedInstalledThemeId: null,
|
||||||
myThemeBase: 'light',
|
myThemeBase: 'light',
|
||||||
myThemeName: '',
|
myThemeName: '',
|
||||||
myThemePrimary: lightTheme.meta.vars.primary,
|
myThemeDesc: '',
|
||||||
myThemeSecondary: lightTheme.meta.vars.secondary,
|
myThemePrimary: lightTheme.vars.primary,
|
||||||
myThemeText: lightTheme.meta.vars.text
|
myThemeSecondary: lightTheme.vars.secondary,
|
||||||
|
myThemeText: lightTheme.vars.text
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
themes(): any {
|
themes(): Theme[] {
|
||||||
return this.$store.state.device.themes.concat(builtinThemes);
|
return this.$store.state.device.themes.concat(builtinThemes);
|
||||||
},
|
},
|
||||||
|
|
||||||
installedThemes(): any {
|
installedThemes(): Theme[] {
|
||||||
return this.$store.state.device.themes;
|
return this.$store.state.device.themes;
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -106,22 +137,26 @@ export default Vue.extend({
|
|||||||
set(value) { this.$store.commit('device/set', { key: 'darkTheme', value }); }
|
set(value) { this.$store.commit('device/set', { key: 'darkTheme', value }); }
|
||||||
},
|
},
|
||||||
|
|
||||||
|
selectedInstalledTheme() {
|
||||||
|
if (this.selectedInstalledThemeId == null) return null;
|
||||||
|
return this.installedThemes.find(x => x.id == this.selectedInstalledThemeId);
|
||||||
|
},
|
||||||
|
|
||||||
selectedInstalledThemeCode() {
|
selectedInstalledThemeCode() {
|
||||||
if (this.selectedInstalledTheme == null) return null;
|
if (this.selectedInstalledTheme == null) return null;
|
||||||
return JSON.stringify(this.installedThemes.find(x => x.meta.id == this.selectedInstalledTheme));
|
return JSON5.stringify(this.selectedInstalledTheme, null, '\t');
|
||||||
},
|
},
|
||||||
|
|
||||||
myTheme(): any {
|
myTheme(): any {
|
||||||
return {
|
return {
|
||||||
meta: {
|
name: this.myThemeName,
|
||||||
name: this.myThemeName,
|
author: this.$store.state.i.username,
|
||||||
author: this.$store.state.i.name,
|
desc: this.myThemeDesc,
|
||||||
base: this.myThemeBase,
|
base: this.myThemeBase,
|
||||||
vars: {
|
vars: {
|
||||||
primary: tinycolor(typeof this.myThemePrimary == 'string' ? this.myThemePrimary : this.myThemePrimary.rgba).toRgbString(),
|
primary: tinycolor(typeof this.myThemePrimary == 'string' ? this.myThemePrimary : this.myThemePrimary.rgba).toRgbString(),
|
||||||
secondary: tinycolor(typeof this.myThemeSecondary == 'string' ? this.myThemeSecondary : this.myThemeSecondary.rgba).toRgbString(),
|
secondary: tinycolor(typeof this.myThemeSecondary == 'string' ? this.myThemeSecondary : this.myThemeSecondary.rgba).toRgbString(),
|
||||||
text: tinycolor(typeof this.myThemeText == 'string' ? this.myThemeText : this.myThemeText.rgba).toRgbString()
|
text: tinycolor(typeof this.myThemeText == 'string' ? this.myThemeText : this.myThemeText.rgba).toRgbString()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -130,27 +165,90 @@ export default Vue.extend({
|
|||||||
watch: {
|
watch: {
|
||||||
myThemeBase(v) {
|
myThemeBase(v) {
|
||||||
const theme = v == 'light' ? lightTheme : darkTheme;
|
const theme = v == 'light' ? lightTheme : darkTheme;
|
||||||
this.myThemePrimary = theme.meta.vars.primary;
|
this.myThemePrimary = theme.vars.primary;
|
||||||
this.myThemeSecondary = theme.meta.vars.secondary;
|
this.myThemeSecondary = theme.vars.secondary;
|
||||||
this.myThemeText = theme.meta.vars.text;
|
this.myThemeText = theme.vars.text;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
beforeCreate() {
|
||||||
|
// migrate old theme definitions
|
||||||
|
// 後方互換性のため
|
||||||
|
this.$store.commit('device/set', {
|
||||||
|
key: 'themes', value: this.$store.state.device.themes.map(t => {
|
||||||
|
if (t.id == null) {
|
||||||
|
return convertOldThemedefinition(t);
|
||||||
|
} else {
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
install() {
|
install(code) {
|
||||||
const theme = JSON.parse(this.installThemeCode);
|
let theme;
|
||||||
if (theme.meta == null || theme.meta.id == null) {
|
|
||||||
|
try {
|
||||||
|
theme = JSON5.parse(code);
|
||||||
|
} catch (e) {
|
||||||
alert('%i18n:@invalid-theme%');
|
alert('%i18n:@invalid-theme%');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.$store.state.device.themes.some(t => t.meta.id == theme.meta.id)) {
|
|
||||||
|
// 後方互換性のため
|
||||||
|
if (theme.id == null && theme.meta != null) {
|
||||||
|
theme = convertOldThemedefinition(theme);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (theme.id == null) {
|
||||||
|
alert('%i18n:@invalid-theme%');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.$store.state.device.themes.some(t => t.id == theme.id)) {
|
||||||
alert('%i18n:@already-installed%');
|
alert('%i18n:@already-installed%');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const themes = this.$store.state.device.themes.concat(theme);
|
const themes = this.$store.state.device.themes.concat(theme);
|
||||||
this.$store.commit('device/set', {
|
this.$store.commit('device/set', {
|
||||||
key: 'themes', value: themes
|
key: 'themes', value: themes
|
||||||
});
|
});
|
||||||
|
|
||||||
|
alert('%i18n:@installed%'.replace('{}', theme.name));
|
||||||
|
},
|
||||||
|
|
||||||
|
uninstall() {
|
||||||
|
const theme = this.selectedInstalledTheme;
|
||||||
|
const themes = this.$store.state.device.themes.filter(t => t.id != theme.id);
|
||||||
|
this.$store.commit('device/set', {
|
||||||
|
key: 'themes', value: themes
|
||||||
|
});
|
||||||
|
alert('%i18n:@uninstalled%'.replace('{}', theme.name));
|
||||||
|
},
|
||||||
|
|
||||||
|
import_() {
|
||||||
|
(this.$refs.file as any).click();
|
||||||
|
}
|
||||||
|
|
||||||
|
export_() {
|
||||||
|
const blob = new Blob([this.selectedInstalledThemeCode], {
|
||||||
|
type: 'application/json5'
|
||||||
|
});
|
||||||
|
this.$refs.export.$el.href = window.URL.createObjectURL(blob);
|
||||||
|
},
|
||||||
|
|
||||||
|
onUpdateImportFile() {
|
||||||
|
const f = (this.$refs.file as any).files[0];
|
||||||
|
|
||||||
|
const reader = new FileReader();
|
||||||
|
|
||||||
|
reader.onload = e => {
|
||||||
|
this.install(e.target.result);
|
||||||
|
};
|
||||||
|
|
||||||
|
reader.readAsText(f);
|
||||||
},
|
},
|
||||||
|
|
||||||
preview() {
|
preview() {
|
||||||
@@ -159,7 +257,11 @@ export default Vue.extend({
|
|||||||
|
|
||||||
gen() {
|
gen() {
|
||||||
const theme = this.myTheme;
|
const theme = this.myTheme;
|
||||||
theme.meta.id = uuid();
|
if (theme.name == null || theme.name.trim() == '') {
|
||||||
|
alert('%i18n:@theme-name-required%');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
theme.id = uuid();
|
||||||
const themes = this.$store.state.device.themes.concat(theme);
|
const themes = this.$store.state.device.themes.concat(theme);
|
||||||
this.$store.commit('device/set', {
|
this.$store.commit('device/set', {
|
||||||
key: 'themes', value: themes
|
key: 'themes', value: themes
|
||||||
@@ -172,6 +274,15 @@ export default Vue.extend({
|
|||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
.nicnklzforebnpfgasiypmpdaaglujqm
|
.nicnklzforebnpfgasiypmpdaaglujqm
|
||||||
|
> details
|
||||||
|
border-top solid 1px var(--faceDivider)
|
||||||
|
|
||||||
|
> summary
|
||||||
|
padding 16px 0
|
||||||
|
|
||||||
|
> *:last-child
|
||||||
|
margin-bottom 16px
|
||||||
|
|
||||||
> .creator
|
> .creator
|
||||||
> div
|
> div
|
||||||
padding 16px 0
|
padding 16px 0
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<button class="dmtdnykelhudezerjlfpbhgovrgnqqgr" :class="[styl, { inline, primary }]" :type="type" @click="$emit('click')">
|
<component class="dmtdnykelhudezerjlfpbhgovrgnqqgr" :is="link ? 'a' : 'button'" :class="[styl, { inline, primary }]" :type="type" @click="$emit('click')">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</button>
|
</component>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
@@ -21,6 +21,11 @@ export default Vue.extend({
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
default: false
|
default: false
|
||||||
|
},
|
||||||
|
link: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
@@ -35,15 +40,20 @@ export default Vue.extend({
|
|||||||
.dmtdnykelhudezerjlfpbhgovrgnqqgr
|
.dmtdnykelhudezerjlfpbhgovrgnqqgr
|
||||||
display block
|
display block
|
||||||
width 100%
|
width 100%
|
||||||
min-height 40px
|
|
||||||
margin 0
|
margin 0
|
||||||
padding 0
|
padding 8px
|
||||||
|
text-align center
|
||||||
font-weight normal
|
font-weight normal
|
||||||
font-size 16px
|
font-size 16px
|
||||||
border none
|
border none
|
||||||
border-radius 6px
|
border-radius 6px
|
||||||
outline none
|
outline none
|
||||||
box-shadow none
|
box-shadow none
|
||||||
|
text-decoration none
|
||||||
|
user-select none
|
||||||
|
|
||||||
|
*
|
||||||
|
pointer-events none
|
||||||
|
|
||||||
&:focus
|
&:focus
|
||||||
&:after
|
&:after
|
||||||
|
|||||||
@@ -71,14 +71,18 @@ export default Vue.extend({
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
default: false
|
default: false
|
||||||
|
},
|
||||||
|
styl: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: 'line'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
v: this.value,
|
v: this.value,
|
||||||
focused: false,
|
focused: false,
|
||||||
passwordStrength: '',
|
passwordStrength: ''
|
||||||
styl: 'fill'
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -117,14 +121,6 @@ export default Vue.extend({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
inject: {
|
|
||||||
isCardChild: { default: false }
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
if (this.isCardChild) {
|
|
||||||
this.styl = 'line';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
mounted() {
|
||||||
if (this.$refs.prefix) {
|
if (this.$refs.prefix) {
|
||||||
this.$refs.label.style.left = (this.$refs.prefix.offsetLeft + this.$refs.prefix.offsetWidth) + 'px';
|
this.$refs.label.style.left = (this.$refs.prefix.offsetLeft + this.$refs.prefix.offsetWidth) + 'px';
|
||||||
|
|||||||
@@ -29,13 +29,17 @@ export default Vue.extend({
|
|||||||
required: {
|
required: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false
|
required: false
|
||||||
|
},
|
||||||
|
styl: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: 'line'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
v: this.value,
|
v: this.value,
|
||||||
focused: false,
|
focused: false
|
||||||
styl: 'fill'
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -48,14 +52,6 @@ export default Vue.extend({
|
|||||||
this.v = v;
|
this.v = v;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
inject: {
|
|
||||||
isCardChild: { default: false }
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
if (this.isCardChild) {
|
|
||||||
this.styl = 'line';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
mounted() {
|
||||||
if (this.$refs.prefix) {
|
if (this.$refs.prefix) {
|
||||||
this.$refs.label.style.left = (this.$refs.prefix.offsetLeft + this.$refs.prefix.offsetWidth) + 'px';
|
this.$refs.label.style.left = (this.$refs.prefix.offsetLeft + this.$refs.prefix.offsetWidth) + 'px';
|
||||||
|
|||||||
@@ -19,7 +19,8 @@
|
|||||||
@click="onClick"
|
@click="onClick"
|
||||||
:disabled="followWait">
|
:disabled="followWait">
|
||||||
<template v-if="!followWait">
|
<template v-if="!followWait">
|
||||||
<template v-if="user.hasPendingFollowRequestFromYou">%fa:hourglass-half% %i18n:@request-pending%</template>
|
<template v-if="user.hasPendingFollowRequestFromYou && user.isLocked">%fa:hourglass-half% %i18n:@request-pending%</template>
|
||||||
|
<template v-else-if="user.hasPendingFollowRequestFromYou && !user.isLocked">%fa:hourglass-start% %i18n:@follow-processing%</template>
|
||||||
<template v-else-if="user.isFollowing">%fa:minus% %i18n:@following%</template>
|
<template v-else-if="user.isFollowing">%fa:minus% %i18n:@following%</template>
|
||||||
<template v-else-if="!user.isFollowing && user.isLocked">%fa:plus% %i18n:@follow-request%</template>
|
<template v-else-if="!user.isFollowing && user.isLocked">%fa:plus% %i18n:@follow-request%</template>
|
||||||
<template v-else-if="!user.isFollowing && !user.isLocked">%fa:plus% %i18n:@follow%</template>
|
<template v-else-if="!user.isFollowing && !user.isLocked">%fa:plus% %i18n:@follow%</template>
|
||||||
|
|||||||
@@ -5,7 +5,8 @@
|
|||||||
:disabled="wait"
|
:disabled="wait"
|
||||||
>
|
>
|
||||||
<template v-if="!wait">
|
<template v-if="!wait">
|
||||||
<template v-if="u.hasPendingFollowRequestFromYou">%fa:hourglass-half%<template v-if="size == 'big'"> %i18n:@request-pending%</template></template>
|
<template v-if="u.hasPendingFollowRequestFromYou && u.isLocked">%fa:hourglass-half%<template v-if="size == 'big'"> %i18n:@request-pending%</template></template>
|
||||||
|
<template v-else-if="u.hasPendingFollowRequestFromYou && !u.isLocked">%fa:hourglass-start%<template v-if="size == 'big'"> %i18n:@follow-processing%</template></template>
|
||||||
<template v-else-if="u.isFollowing">%fa:minus%<template v-if="size == 'big'"> %i18n:@following%</template></template>
|
<template v-else-if="u.isFollowing">%fa:minus%<template v-if="size == 'big'"> %i18n:@following%</template></template>
|
||||||
<template v-else-if="!u.isFollowing && u.isLocked">%fa:plus%<template v-if="size == 'big'"> %i18n:@follow-request%</template></template>
|
<template v-else-if="!u.isFollowing && u.isLocked">%fa:plus%<template v-if="size == 'big'"> %i18n:@follow-request%</template></template>
|
||||||
<template v-else-if="!u.isFollowing && !u.isLocked">%fa:plus%<template v-if="size == 'big'"> %i18n:@follow%</template></template>
|
<template v-else-if="!u.isFollowing && !u.isLocked">%fa:plus%<template v-if="size == 'big'"> %i18n:@follow%</template></template>
|
||||||
|
|||||||
@@ -450,6 +450,7 @@ export default Vue.extend({
|
|||||||
display block
|
display block
|
||||||
padding 16px
|
padding 16px
|
||||||
background var(--desktopPostFormBg)
|
background var(--desktopPostFormBg)
|
||||||
|
overflow hidden
|
||||||
|
|
||||||
&:after
|
&:after
|
||||||
content ""
|
content ""
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="qldxjjsrseehkusjuoooapmsprvfrxyl mk-admin-card">
|
<div class="qldxjjsrseehkusjuoooapmsprvfrxyl mk-admin-card">
|
||||||
<header>%i18n:@announcements%</header>
|
<header>%i18n:@announcements%</header>
|
||||||
<textarea v-model="broadcasts"></textarea>
|
<textarea v-model="broadcasts" placeholder='[ { "title": "Title1", "text": "Text1" }, { "title": "Title2", "text": "Text2" } ]'></textarea>
|
||||||
<button class="ui" @click="save">%i18n:@save%</button>
|
<button class="ui" @click="save">%i18n:@save%</button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -22,8 +22,21 @@ export default Vue.extend({
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
save() {
|
save() {
|
||||||
|
let json;
|
||||||
|
|
||||||
|
try {
|
||||||
|
json = JSON.parse(this.broadcasts);
|
||||||
|
} catch (e) {
|
||||||
|
(this as any).os.apis.dialog({ text: `Failed: ${e}` });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
(this as any).api('admin/update-meta', {
|
(this as any).api('admin/update-meta', {
|
||||||
broadcasts: JSON.parse(this.broadcasts)
|
broadcasts: json
|
||||||
|
}).then(() => {
|
||||||
|
(this as any).os.apis.dialog({ text: `Saved` });
|
||||||
|
}.catch(e => {
|
||||||
|
(this as any).os.apis.dialog({ text: `Failed ${e}` });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
<x-cpu-memory :connection="connection"/>
|
<x-cpu-memory :connection="connection"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form">
|
<div v-if="this.$store.state.i && this.$store.state.i.isAdmin" class="form">
|
||||||
<div>
|
<div>
|
||||||
<label>
|
<label>
|
||||||
<p>%i18n:@banner-url%</p>
|
<p>%i18n:@banner-url%</p>
|
||||||
@@ -81,6 +81,8 @@ export default Vue.extend({
|
|||||||
invite() {
|
invite() {
|
||||||
(this as any).api('admin/invite').then(x => {
|
(this as any).api('admin/invite').then(x => {
|
||||||
this.inviteCode = x.code;
|
this.inviteCode = x.code;
|
||||||
|
}).catch(e => {
|
||||||
|
(this as any).os.apis.dialog({ text: `Failed ${e}` });
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
updateMeta() {
|
updateMeta() {
|
||||||
@@ -88,6 +90,10 @@ export default Vue.extend({
|
|||||||
disableRegistration: this.disableRegistration,
|
disableRegistration: this.disableRegistration,
|
||||||
disableLocalTimeline: this.disableLocalTimeline,
|
disableLocalTimeline: this.disableLocalTimeline,
|
||||||
bannerUrl: this.bannerUrl
|
bannerUrl: this.bannerUrl
|
||||||
|
}).then(() => {
|
||||||
|
(this as any).os.apis.dialog({ text: `Saved` });
|
||||||
|
}).catch(e => {
|
||||||
|
(this as any).os.apis.dialog({ text: `Failed ${e}` });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,10 @@ export default Vue.extend({
|
|||||||
save() {
|
save() {
|
||||||
(this as any).api('admin/update-meta', {
|
(this as any).api('admin/update-meta', {
|
||||||
hidedTags: this.hidedTags.split('\n')
|
hidedTags: this.hidedTags.split('\n')
|
||||||
|
}).then(() => {
|
||||||
|
(this as any).os.apis.dialog({ text: `Saved` });
|
||||||
|
}).catch(e => {
|
||||||
|
(this as any).os.apis.dialog({ text: `Failed ${e}` });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,18 +21,24 @@ export default Vue.extend({
|
|||||||
async suspendUser() {
|
async suspendUser() {
|
||||||
this.suspending = true;
|
this.suspending = true;
|
||||||
|
|
||||||
const user = await (this as any).os.api(
|
const process = async () => {
|
||||||
"users/show",
|
const user = await (this as any).os.api(
|
||||||
parseAcct(this.username)
|
"users/show",
|
||||||
);
|
parseAcct(this.username)
|
||||||
|
);
|
||||||
|
|
||||||
await (this as any).os.api("admin/suspend-user", {
|
await (this as any).os.api("admin/suspend-user", {
|
||||||
userId: user.id
|
userId: user.id
|
||||||
|
});
|
||||||
|
|
||||||
|
(this as any).os.apis.dialog({ text: "%i18n:@suspended%" });
|
||||||
|
};
|
||||||
|
|
||||||
|
await process().catch(e => {
|
||||||
|
(this as any).os.apis.dialog({ text: `Failed: ${e}` });
|
||||||
});
|
});
|
||||||
|
|
||||||
this.suspending = false;
|
this.suspending = false;
|
||||||
|
|
||||||
(this as any).os.apis.dialog({ text: "%i18n:@suspended%" });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -21,18 +21,25 @@ export default Vue.extend({
|
|||||||
async unsuspendUser() {
|
async unsuspendUser() {
|
||||||
this.unsuspending = true;
|
this.unsuspending = true;
|
||||||
|
|
||||||
const user = await (this as any).os.api(
|
const process = async () => {
|
||||||
"users/show",
|
const user = await (this as any).os.api(
|
||||||
parseAcct(this.username)
|
"users/show",
|
||||||
);
|
parseAcct(this.username)
|
||||||
|
);
|
||||||
|
|
||||||
await (this as any).os.api("admin/unsuspend-user", {
|
await (this as any).os.api("admin/unsuspend-user", {
|
||||||
userId: user.id
|
userId: user.id
|
||||||
|
});
|
||||||
|
|
||||||
|
(this as any).os.apis.dialog({ text: "%i18n:@unsuspended%" });
|
||||||
|
};
|
||||||
|
|
||||||
|
await process().catch(e => {
|
||||||
|
(this as any).os.apis.dialog({ text: `Failed: ${e}` });
|
||||||
});
|
});
|
||||||
|
|
||||||
this.unsuspending = false;
|
this.unsuspending = false;
|
||||||
|
|
||||||
(this as any).os.apis.dialog({ text: "%i18n:@unsuspended%" });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -21,18 +21,24 @@ export default Vue.extend({
|
|||||||
async unverifyUser() {
|
async unverifyUser() {
|
||||||
this.unverifying = true;
|
this.unverifying = true;
|
||||||
|
|
||||||
const user = await (this as any).os.api(
|
const process = async () => {
|
||||||
"users/show",
|
const user = await (this as any).os.api(
|
||||||
parseAcct(this.username)
|
"users/show",
|
||||||
);
|
parseAcct(this.username)
|
||||||
|
);
|
||||||
|
|
||||||
await (this as any).os.api("admin/unverify-user", {
|
await (this as any).os.api("admin/unverify-user", {
|
||||||
userId: user.id
|
userId: user.id
|
||||||
|
});
|
||||||
|
|
||||||
|
(this as any).os.apis.dialog({ text: "%i18n:@unverified%" });
|
||||||
|
};
|
||||||
|
|
||||||
|
await process().catch(e => {
|
||||||
|
(this as any).os.apis.dialog({ text: `Failed: ${e}` });
|
||||||
});
|
});
|
||||||
|
|
||||||
this.unverifying = false;
|
this.unverifying = false;
|
||||||
|
|
||||||
(this as any).os.apis.dialog({ text: "%i18n:@unverified%" });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -21,18 +21,24 @@ export default Vue.extend({
|
|||||||
async verifyUser() {
|
async verifyUser() {
|
||||||
this.verifying = true;
|
this.verifying = true;
|
||||||
|
|
||||||
const user = await (this as any).os.api(
|
const process = async () => {
|
||||||
"users/show",
|
const user = await (this as any).os.api(
|
||||||
parseAcct(this.username)
|
"users/show",
|
||||||
);
|
parseAcct(this.username)
|
||||||
|
);
|
||||||
|
|
||||||
await (this as any).os.api("admin/verify-user", {
|
await (this as any).os.api("admin/verify-user", {
|
||||||
userId: user.id
|
userId: user.id
|
||||||
|
});
|
||||||
|
|
||||||
|
(this as any).os.apis.dialog({ text: "%i18n:@verified%" });
|
||||||
|
};
|
||||||
|
|
||||||
|
await process().catch(e => {
|
||||||
|
(this as any).os.apis.dialog({ text: `Failed: ${e}` });
|
||||||
});
|
});
|
||||||
|
|
||||||
this.verifying = false;
|
this.verifying = false;
|
||||||
|
|
||||||
(this as any).os.apis.dialog({ text: "%i18n:@verified%" });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,9 +3,15 @@
|
|||||||
<nav>
|
<nav>
|
||||||
<ul>
|
<ul>
|
||||||
<li @click="nav('dashboard')" :class="{ active: page == 'dashboard' }">%fa:chalkboard .fw%%i18n:@dashboard%</li>
|
<li @click="nav('dashboard')" :class="{ active: page == 'dashboard' }">%fa:chalkboard .fw%%i18n:@dashboard%</li>
|
||||||
<li @click="nav('users')" :class="{ active: page == 'users' }">%fa:users .fw%%i18n:@users%</li>
|
|
||||||
<li @click="nav('announcements')" :class="{ active: page == 'announcements' }">%fa:broadcast-tower .fw%%i18n:@announcements%</li>
|
<li v-if="this.$store.state.i && this.$store.state.i.isAdmin"
|
||||||
<li @click="nav('hashtags')" :class="{ active: page == 'hashtags' }">%fa:hashtag .fw%%i18n:@hashtags%</li>
|
@click="nav('users')" :class="{ active: page == 'users' }">%fa:users .fw%%i18n:@users%</li>
|
||||||
|
|
||||||
|
<li v-if="this.$store.state.i && this.$store.state.i.isAdmin"
|
||||||
|
@click="nav('announcements')" :class="{ active: page == 'announcements' }">%fa:broadcast-tower .fw%%i18n:@announcements%</li>
|
||||||
|
|
||||||
|
<li v-if="this.$store.state.i && this.$store.state.i.isAdmin"
|
||||||
|
@click="nav('hashtags')" :class="{ active: page == 'hashtags' }">%fa:hashtag .fw%%i18n:@hashtags%</li>
|
||||||
|
|
||||||
<!-- <li @click="nav('drive')" :class="{ active: page == 'drive' }">%fa:cloud .fw%%i18n:@drive%</li> -->
|
<!-- <li @click="nav('drive')" :class="{ active: page == 'drive' }">%fa:cloud .fw%%i18n:@drive%</li> -->
|
||||||
<!-- <li @click="nav('update')" :class="{ active: page == 'update' }">%i18n:@update%</li> -->
|
<!-- <li @click="nav('update')" :class="{ active: page == 'update' }">%i18n:@update%</li> -->
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ export default define({
|
|||||||
|
|
||||||
> .banner
|
> .banner
|
||||||
height 100px
|
height 100px
|
||||||
background-color var(--primaryDarken10)
|
background-color var(--primaryAlpha01)
|
||||||
background-size cover
|
background-size cover
|
||||||
background-position center
|
background-position center
|
||||||
cursor pointer
|
cursor pointer
|
||||||
|
|||||||
@@ -14,8 +14,7 @@ import App from './app.vue';
|
|||||||
import checkForUpdate from './common/scripts/check-for-update';
|
import checkForUpdate from './common/scripts/check-for-update';
|
||||||
import MiOS, { API } from './mios';
|
import MiOS, { API } from './mios';
|
||||||
import { version, codename, lang } from './config';
|
import { version, codename, lang } from './config';
|
||||||
import { builtinThemes, applyTheme } from './theme';
|
import { builtinThemes, lightTheme, applyTheme } from './theme';
|
||||||
const lightTheme = require('../theme/light.json');
|
|
||||||
|
|
||||||
if (localStorage.getItem('theme') == null) {
|
if (localStorage.getItem('theme') == null) {
|
||||||
applyTheme(lightTheme);
|
applyTheme(lightTheme);
|
||||||
@@ -97,15 +96,15 @@ export default (callback: (launch: (router: VueRouter, api?: (os: MiOS) => API)
|
|||||||
return s.device.darkmode;
|
return s.device.darkmode;
|
||||||
}, v => {
|
}, v => {
|
||||||
const themes = os.store.state.device.themes.concat(builtinThemes);
|
const themes = os.store.state.device.themes.concat(builtinThemes);
|
||||||
const dark = themes.find(t => t.meta.id == os.store.state.device.darkTheme);
|
const dark = themes.find(t => t.id == os.store.state.device.darkTheme);
|
||||||
const light = themes.find(t => t.meta.id == os.store.state.device.lightTheme);
|
const light = themes.find(t => t.id == os.store.state.device.lightTheme);
|
||||||
applyTheme(v ? dark : light);
|
applyTheme(v ? dark : light);
|
||||||
});
|
});
|
||||||
os.store.watch(s => {
|
os.store.watch(s => {
|
||||||
return s.device.lightTheme;
|
return s.device.lightTheme;
|
||||||
}, v => {
|
}, v => {
|
||||||
const themes = os.store.state.device.themes.concat(builtinThemes);
|
const themes = os.store.state.device.themes.concat(builtinThemes);
|
||||||
const theme = themes.find(t => t.meta.id == v);
|
const theme = themes.find(t => t.id == v);
|
||||||
if (!os.store.state.device.darkmode) {
|
if (!os.store.state.device.darkmode) {
|
||||||
applyTheme(theme);
|
applyTheme(theme);
|
||||||
}
|
}
|
||||||
@@ -114,7 +113,7 @@ export default (callback: (launch: (router: VueRouter, api?: (os: MiOS) => API)
|
|||||||
return s.device.darkTheme;
|
return s.device.darkTheme;
|
||||||
}, v => {
|
}, v => {
|
||||||
const themes = os.store.state.device.themes.concat(builtinThemes);
|
const themes = os.store.state.device.themes.concat(builtinThemes);
|
||||||
const theme = themes.find(t => t.meta.id == v);
|
const theme = themes.find(t => t.id == v);
|
||||||
if (os.store.state.device.darkmode) {
|
if (os.store.state.device.darkmode) {
|
||||||
applyTheme(theme);
|
applyTheme(theme);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,8 @@
|
|||||||
:disabled="wait"
|
:disabled="wait"
|
||||||
>
|
>
|
||||||
<template v-if="!wait">
|
<template v-if="!wait">
|
||||||
<template v-if="u.hasPendingFollowRequestFromYou">%fa:hourglass-half% %i18n:@request-pending%</template>
|
<template v-if="u.hasPendingFollowRequestFromYou && u.isLocked">%fa:hourglass-half% %i18n:@request-pending%</template>
|
||||||
|
<template v-else-if="u.hasPendingFollowRequestFromYou && !u.isLocked">%fa:hourglass-start% %i18n:@follow-processing%</template>
|
||||||
<template v-else-if="u.isFollowing">%fa:minus% %i18n:@following%</template>
|
<template v-else-if="u.isFollowing">%fa:minus% %i18n:@following%</template>
|
||||||
<template v-else-if="!u.isFollowing && u.isLocked">%fa:plus% %i18n:@follow-request%</template>
|
<template v-else-if="!u.isFollowing && u.isLocked">%fa:plus% %i18n:@follow-request%</template>
|
||||||
<template v-else-if="!u.isFollowing && !u.isLocked">%fa:plus% %i18n:@follow%</template>
|
<template v-else-if="!u.isFollowing && !u.isLocked">%fa:plus% %i18n:@follow%</template>
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ export default Vue.extend({
|
|||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
document.title = `${(this as any).os.instanceName} Drive`;
|
document.title = `${(this as any).os.instanceName} Drive`;
|
||||||
document.documentElement.style.background = '#fff';
|
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
window.removeEventListener('popstate', this.onPopState);
|
window.removeEventListener('popstate', this.onPopState);
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import Vue from 'vue';
|
|||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
mounted() {
|
mounted() {
|
||||||
document.title = `${(this as any).os.instanceName} %i18n:@reversi%`;
|
document.title = `${(this as any).os.instanceName} %i18n:@reversi%`;
|
||||||
document.documentElement.style.background = '#fff';
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
nav(game, actualNav) {
|
nav(game, actualNav) {
|
||||||
|
|||||||
@@ -12,16 +12,6 @@ if (!('fetch' in window)) {
|
|||||||
'To run Misskey, please update your browser to latest version or try other browsers.');
|
'To run Misskey, please update your browser to latest version or try other browsers.');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detect Edge
|
|
||||||
if (navigator.userAgent.toLowerCase().indexOf('edge') != -1) {
|
|
||||||
alert(
|
|
||||||
'現在、お使いのブラウザ(Microsoft Edge)ではMisskeyは正しく動作しません。' +
|
|
||||||
'サポートしているブラウザ: Google Chrome, Mozilla Firefox, Apple Safari など' +
|
|
||||||
'\n\n' +
|
|
||||||
'Currently, Misskey cannot run correctly on your browser (Microsoft Edge). ' +
|
|
||||||
'Supported browsers: Google Chrome, Mozilla Firefox, Apple Safari, etc');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check whether cookie enabled
|
// Check whether cookie enabled
|
||||||
if (!navigator.cookieEnabled) {
|
if (!navigator.cookieEnabled) {
|
||||||
alert(
|
alert(
|
||||||
|
|||||||
@@ -1,27 +1,40 @@
|
|||||||
import * as tinycolor from 'tinycolor2';
|
import * as tinycolor from 'tinycolor2';
|
||||||
|
|
||||||
type Theme = {
|
export type Theme = {
|
||||||
meta: {
|
id: string;
|
||||||
id: string;
|
name: string;
|
||||||
name: string;
|
author: string;
|
||||||
author: string;
|
desc?: string;
|
||||||
base?: string;
|
base?: 'dark' | 'light';
|
||||||
vars: any;
|
vars: { [key: string]: string };
|
||||||
};
|
props: { [key: string]: string };
|
||||||
} & {
|
|
||||||
[key: string]: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const lightTheme: Theme = require('../theme/light.json5');
|
||||||
|
export const darkTheme: Theme = require('../theme/dark.json5');
|
||||||
|
export const pinkTheme: Theme = require('../theme/pink.json5');
|
||||||
|
export const halloweenTheme: Theme = require('../theme/halloween.json5');
|
||||||
|
|
||||||
|
export const builtinThemes = [
|
||||||
|
lightTheme,
|
||||||
|
darkTheme,
|
||||||
|
pinkTheme,
|
||||||
|
halloweenTheme
|
||||||
|
];
|
||||||
|
|
||||||
export function applyTheme(theme: Theme, persisted = true) {
|
export function applyTheme(theme: Theme, persisted = true) {
|
||||||
if (theme.meta.base) {
|
// Deep copy
|
||||||
const base = [lightTheme, darkTheme].find(x => x.meta.id == theme.meta.base);
|
const _theme = JSON.parse(JSON.stringify(theme));
|
||||||
theme = Object.assign({}, base, theme);
|
|
||||||
|
if (_theme.base) {
|
||||||
|
const base = [lightTheme, darkTheme].find(x => x.id == _theme.base);
|
||||||
|
_theme.vars = Object.assign({}, base.vars, _theme.vars);
|
||||||
|
_theme.props = Object.assign({}, base.props, _theme.props);
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = compile(theme);
|
const props = compile(_theme);
|
||||||
|
|
||||||
Object.entries(props).forEach(([k, v]) => {
|
Object.entries(props).forEach(([k, v]) => {
|
||||||
if (k == 'meta') return;
|
|
||||||
document.documentElement.style.setProperty(`--${k}`, v.toString());
|
document.documentElement.style.setProperty(`--${k}`, v.toString());
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -34,10 +47,10 @@ function compile(theme: Theme): { [key: string]: string } {
|
|||||||
function getColor(code: string): tinycolor.Instance {
|
function getColor(code: string): tinycolor.Instance {
|
||||||
// ref
|
// ref
|
||||||
if (code[0] == '@') {
|
if (code[0] == '@') {
|
||||||
return getColor(theme[code.substr(1)]);
|
return getColor(theme.props[code.substr(1)]);
|
||||||
}
|
}
|
||||||
if (code[0] == '$') {
|
if (code[0] == '$') {
|
||||||
return getColor(theme.meta.vars[code.substr(1)]);
|
return getColor(theme.vars[code.substr(1)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// func
|
// func
|
||||||
@@ -59,8 +72,7 @@ function compile(theme: Theme): { [key: string]: string } {
|
|||||||
|
|
||||||
const props = {};
|
const props = {};
|
||||||
|
|
||||||
Object.entries(theme).forEach(([k, v]) => {
|
Object.entries(theme.props).forEach(([k, v]) => {
|
||||||
if (k == 'meta') return;
|
|
||||||
const c = getColor(v);
|
const c = getColor(v);
|
||||||
props[k] = genValue(c);
|
props[k] = genValue(c);
|
||||||
});
|
});
|
||||||
@@ -88,15 +100,3 @@ function compile(theme: Theme): { [key: string]: string } {
|
|||||||
function genValue(c: tinycolor.Instance): string {
|
function genValue(c: tinycolor.Instance): string {
|
||||||
return c.toRgbString();
|
return c.toRgbString();
|
||||||
}
|
}
|
||||||
|
|
||||||
export const lightTheme = require('../theme/light.json');
|
|
||||||
export const darkTheme = require('../theme/dark.json');
|
|
||||||
export const pinkTheme = require('../theme/pink.json');
|
|
||||||
export const halloweenTheme = require('../theme/halloween.json');
|
|
||||||
|
|
||||||
export const builtinThemes = [
|
|
||||||
lightTheme,
|
|
||||||
darkTheme,
|
|
||||||
pinkTheme,
|
|
||||||
halloweenTheme
|
|
||||||
];
|
|
||||||
|
|||||||
@@ -1,93 +0,0 @@
|
|||||||
.hljs {
|
|
||||||
font-family: Consolas, 'Courier New', Courier, Monaco, monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hljs,
|
|
||||||
.hljs-subst {
|
|
||||||
color: #444;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hljs-comment {
|
|
||||||
color: #888888;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hljs-keyword {
|
|
||||||
color: #2973b7;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hljs-number {
|
|
||||||
color: #ae81ff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hljs-string {
|
|
||||||
color: #e96900;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hljs-regexp {
|
|
||||||
color: #e9003f;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hljs-attribute,
|
|
||||||
.hljs-selector-tag,
|
|
||||||
.hljs-meta-keyword,
|
|
||||||
.hljs-doctag,
|
|
||||||
.hljs-name {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hljs-type,
|
|
||||||
.hljs-selector-id,
|
|
||||||
.hljs-selector-class,
|
|
||||||
.hljs-quote,
|
|
||||||
.hljs-template-tag,
|
|
||||||
.hljs-deletion {
|
|
||||||
color: #880000;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hljs-title,
|
|
||||||
.hljs-section {
|
|
||||||
color: #880000;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hljs-symbol,
|
|
||||||
.hljs-variable,
|
|
||||||
.hljs-template-variable,
|
|
||||||
.hljs-link,
|
|
||||||
.hljs-selector-attr,
|
|
||||||
.hljs-selector-pseudo {
|
|
||||||
color: #BC6060;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Language color: hue: 90; */
|
|
||||||
|
|
||||||
.hljs-literal {
|
|
||||||
color: #78A960;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hljs-built_in,
|
|
||||||
.hljs-bullet,
|
|
||||||
.hljs-code,
|
|
||||||
.hljs-addition {
|
|
||||||
color: #397300;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Meta color: hue: 200 */
|
|
||||||
|
|
||||||
.hljs-meta {
|
|
||||||
color: #1f7199;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hljs-meta-string {
|
|
||||||
color: #4d99bf;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Misc effects */
|
|
||||||
|
|
||||||
.hljs-emphasis {
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hljs-strong {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
@@ -1,204 +0,0 @@
|
|||||||
{
|
|
||||||
"meta": {
|
|
||||||
"id": "dark",
|
|
||||||
"name": "Dark",
|
|
||||||
"author": "syuilo",
|
|
||||||
"vars": {
|
|
||||||
"primary": "#fb4e4e",
|
|
||||||
"secondary": "#282C37",
|
|
||||||
"text": "#d6dae0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
"primary": "$primary",
|
|
||||||
"primaryForeground": "#fff",
|
|
||||||
"secondary": "$secondary",
|
|
||||||
"bg": ":darken<8<$secondary",
|
|
||||||
"text": "$text",
|
|
||||||
|
|
||||||
"scrollbarTrack": ":darken<5<$secondary",
|
|
||||||
"scrollbarHandle": ":lighten<5<$secondary",
|
|
||||||
"scrollbarHandleHover": ":lighten<10<$secondary",
|
|
||||||
|
|
||||||
"face": "$secondary",
|
|
||||||
"faceText": "#fff",
|
|
||||||
"faceHeader": ":lighten<5<$secondary",
|
|
||||||
"faceHeaderText": "#e3e5e8",
|
|
||||||
"faceDivider": "rgba(0, 0, 0, 0.3)",
|
|
||||||
"faceTextButton": "#9baec8",
|
|
||||||
"faceTextButtonHover": "#b2c1d5",
|
|
||||||
"faceTextButtonActive": "#b2c1d5",
|
|
||||||
"faceClearButtonHover": "rgba(0, 0, 0, 0.1)",
|
|
||||||
"faceClearButtonActive": "rgba(0, 0, 0, 0.2)",
|
|
||||||
"popupBg": ":lighten<5<$secondary",
|
|
||||||
"popupFg": "#d6dce2",
|
|
||||||
|
|
||||||
"subNoteBg": "rgba(0, 0, 0, 0.18)",
|
|
||||||
"subNoteText": ":alpha<0.7<$text",
|
|
||||||
"renoteGradient": "#314027",
|
|
||||||
"renoteText": "#9dbb00",
|
|
||||||
"quoteBorder": "#4e945e",
|
|
||||||
"noteText": "#fff",
|
|
||||||
"noteHeaderName": "#fff",
|
|
||||||
"noteHeaderBadgeFg": "#758188",
|
|
||||||
"noteHeaderBadgeBg": "rgba(0, 0, 0, 0.25)",
|
|
||||||
"noteHeaderAdminFg": "#f15f71",
|
|
||||||
"noteHeaderAdminBg": "#5d282e",
|
|
||||||
"noteHeaderAcct": "#606984",
|
|
||||||
"noteHeaderInfo": "#606984",
|
|
||||||
|
|
||||||
"noteActions": "#606984",
|
|
||||||
"noteActionsHover": "#a1a8bf",
|
|
||||||
"noteActionsReplyHover": "#0af",
|
|
||||||
"noteActionsRenoteHover": "#8d0",
|
|
||||||
"noteActionsReactionHover": "#fa0",
|
|
||||||
"noteActionsHighlighted": "#707b97",
|
|
||||||
|
|
||||||
"noteAttachedFile": "rgba(255, 255, 255, 0.1)",
|
|
||||||
|
|
||||||
"modalBackdrop": "rgba(0, 0, 0, 0.5)",
|
|
||||||
|
|
||||||
"dateDividerBg": ":darken<2<$secondary",
|
|
||||||
"dateDividerFg": ":alpha<0.7<$text",
|
|
||||||
|
|
||||||
"switchTrack": "rgba(255, 255, 255, 0.15)",
|
|
||||||
"radioBorder": "rgba(255, 255, 255, 0.6)",
|
|
||||||
"inputBorder": "rgba(255, 255, 255, 0.7)",
|
|
||||||
"inputLabel": "rgba(255, 255, 255, 0.7)",
|
|
||||||
"inputText": "#fff",
|
|
||||||
|
|
||||||
"buttonBg": "rgba(255, 255, 255, 0.05)",
|
|
||||||
"buttonHoverBg": "rgba(255, 255, 255, 0.1)",
|
|
||||||
"buttonActiveBg": "rgba(255, 255, 255, 0.15)",
|
|
||||||
|
|
||||||
"autocompleteItemHoverBg": "rgba(255, 255, 255, 0.1)",
|
|
||||||
"autocompleteItemText": "rgba(255, 255, 255, 0.8)",
|
|
||||||
"autocompleteItemTextSub": "rgba(255, 255, 255, 0.3)",
|
|
||||||
|
|
||||||
"cwButtonBg": "#687390",
|
|
||||||
"cwButtonFg": "#393f4f",
|
|
||||||
"cwButtonHoverBg": "#707b97",
|
|
||||||
|
|
||||||
"reactionPickerButtonHoverBg": "rgba(255, 255, 255, 0.18)",
|
|
||||||
|
|
||||||
"reactionViewerBorder": "rgba(255, 255, 255, 0.1)",
|
|
||||||
|
|
||||||
"pollEditorInputBg": "rgba(0, 0, 0, 0.25)",
|
|
||||||
|
|
||||||
"pollChoiceText": "#fff",
|
|
||||||
"pollChoiceBorder": "rgba(255, 255, 255, 0.1)",
|
|
||||||
|
|
||||||
"urlPreviewBorder": "rgba(0, 0, 0, 0.4)",
|
|
||||||
"urlPreviewBorderHover": "rgba(255, 255, 255, 0.2)",
|
|
||||||
"urlPreviewTitle": "$text",
|
|
||||||
"urlPreviewText": ":alpha<0.7<$text",
|
|
||||||
"urlPreviewInfo": ":alpha<0.8<$text",
|
|
||||||
|
|
||||||
"calendarWeek": "#43d5dc",
|
|
||||||
"calendarSaturdayOrSunday": "#ff6679",
|
|
||||||
"calendarDay": "#c5ced6",
|
|
||||||
|
|
||||||
"materBg": "rgba(0, 0, 0, 0.3)",
|
|
||||||
|
|
||||||
"chartCaption": ":alpha<0.6<$text",
|
|
||||||
|
|
||||||
"announcementsBg": "#253a50",
|
|
||||||
"announcementsTitle": "#539eff",
|
|
||||||
"announcementsText": "#fff",
|
|
||||||
|
|
||||||
"donationBg": "#5d5242",
|
|
||||||
"donationFg": "#e4dbce",
|
|
||||||
|
|
||||||
"googleSearchBg": "rgba(0, 0, 0, 0.2)",
|
|
||||||
"googleSearchFg": "#dee4e8",
|
|
||||||
"googleSearchBorder": "rgba(255, 255, 255, 0.2)",
|
|
||||||
"googleSearchHoverBorder": "rgba(255, 255, 255, 0.3)",
|
|
||||||
"googleSearchHoverButton": "rgba(255, 255, 255, 0.1)",
|
|
||||||
|
|
||||||
"mfmTitleBg": "rgba(0, 0, 0, 0.2)",
|
|
||||||
"mfmQuote": ":alpha<0.7<$text",
|
|
||||||
"mfmQuoteLine": ":alpha<0.6<$text",
|
|
||||||
|
|
||||||
"suspendedInfoBg": "#611d1d",
|
|
||||||
"suspendedInfoFg": "#ffb4b4",
|
|
||||||
"remoteInfoBg": "#42321c",
|
|
||||||
"remoteInfoFg": "#ffbd3e",
|
|
||||||
|
|
||||||
"messagingRoomBg": "@bg",
|
|
||||||
"messagingRoomInfo": "#fff",
|
|
||||||
"messagingRoomDateDividerLine": "rgba(255, 255, 255, 0.1)",
|
|
||||||
"messagingRoomDateDividerText": "rgba(255, 255, 255, 0.3)",
|
|
||||||
"messagingRoomMessageInfo": "rgba(255, 255, 255, 0.4)",
|
|
||||||
"messagingRoomMessageBg": "$secondary",
|
|
||||||
"messagingRoomMessageFg": "#fff",
|
|
||||||
|
|
||||||
"formButtonBorder": "rgba(255, 255, 255, 0.1)",
|
|
||||||
"formButtonHoverBg": ":alpha<0.2<$primary",
|
|
||||||
"formButtonHoverBorder": ":alpha<0.5<$primary",
|
|
||||||
"formButtonActiveBg": ":alpha<0.12<$primary",
|
|
||||||
|
|
||||||
"desktopHeaderBg": ":lighten<5<$secondary",
|
|
||||||
"desktopHeaderFg": "$text",
|
|
||||||
"desktopHeaderHoverFg": "#fff",
|
|
||||||
"desktopHeaderSearchBg": "rgba(0, 0, 0, 0.1)",
|
|
||||||
"desktopHeaderSearchHoverBg": "rgba(255, 255, 255, 0.04)",
|
|
||||||
"desktopHeaderSearchFg": "#fff",
|
|
||||||
"desktopNotificationBg": ":alpha<0.9<$secondary",
|
|
||||||
"desktopNotificationFg": ":alpha<0.7<$text",
|
|
||||||
"desktopNotificationShadow": "rgba(0, 0, 0, 0.4)",
|
|
||||||
"desktopPostFormBg": "@face",
|
|
||||||
"desktopPostFormTextareaBg": "rgba(0, 0, 0, 0.25)",
|
|
||||||
"desktopPostFormTextareaFg": "#fff",
|
|
||||||
"desktopPostFormTransparentButtonFg": "$primary",
|
|
||||||
"desktopPostFormTransparentButtonActiveGradientStart": ":darken<8<$secondary",
|
|
||||||
"desktopPostFormTransparentButtonActiveGradientEnd": ":darken<3<$secondary",
|
|
||||||
"desktopRenoteFormFooter": ":lighten<5<$secondary",
|
|
||||||
"desktopTimelineHeaderShadow": "rgba(0, 0, 0, 0.15)",
|
|
||||||
"desktopTimelineSrc": "@faceTextButton",
|
|
||||||
"desktopTimelineSrcHover": "@faceTextButtonHover",
|
|
||||||
"desktopWindowTitle": "@faceHeaderText",
|
|
||||||
"desktopWindowShadow": "rgba(0, 0, 0, 0.5)",
|
|
||||||
"desktopDriveBg": "@bg",
|
|
||||||
"desktopDriveFolderBg": ":alpha<0.2<$primary",
|
|
||||||
"desktopDriveFolderHoverBg": ":alpha<0.3<$primary",
|
|
||||||
"desktopDriveFolderActiveBg": ":alpha<0.3<:darken<10<$primary",
|
|
||||||
"desktopDriveFolderFg": "#fff",
|
|
||||||
"desktopSettingsNavItem": ":alpha<0.8<$text",
|
|
||||||
"desktopSettingsNavItemHover": ":lighten<10<$text",
|
|
||||||
|
|
||||||
"deckAcrylicColumnBg": "rgba(0, 0, 0, 0.25)",
|
|
||||||
|
|
||||||
"mobileHeaderBg": ":lighten<5<$secondary",
|
|
||||||
"mobileHeaderFg": "$text",
|
|
||||||
"mobileNavBackdrop": "rgba(0, 0, 0, 0.7)",
|
|
||||||
"mobilePostFormDivider": "rgba(0, 0, 0, 0.2)",
|
|
||||||
"mobilePostFormTextareaBg": "rgba(0, 0, 0, 0.3)",
|
|
||||||
"mobileDriveNavBg": ":alpha<0.75<$secondary",
|
|
||||||
"mobileHomeTlItemHover": "rgba(255, 255, 255, 0.1)",
|
|
||||||
"mobileUserPageName": "#fff",
|
|
||||||
"mobileUserPageAcct": "$text",
|
|
||||||
"mobileUserPageDescription": "$text",
|
|
||||||
"mobileUserPageFollowedBg": "rgba(0, 0, 0, 0.3)",
|
|
||||||
"mobileUserPageFollowedFg": "$text",
|
|
||||||
"mobileUserPageStatusHighlight": "#fff",
|
|
||||||
"mobileUserPageHeaderShadow": "rgba(0, 0, 0, 0.3)",
|
|
||||||
"mobileAnnouncement": "rgba(30, 129, 216, 0.2)",
|
|
||||||
"mobileAnnouncementFg": "#fff",
|
|
||||||
"mobileSignedInAsBg": "#273c34",
|
|
||||||
"mobileSignedInAsFg": "#49ab63",
|
|
||||||
"mobileSignoutBg": "#652222",
|
|
||||||
"mobileSignoutFg": "#ff5f56",
|
|
||||||
|
|
||||||
"reversiBannerGradientStart": "#45730e",
|
|
||||||
"reversiBannerGradientEnd": "#464300",
|
|
||||||
"reversiDescBg": "rgba(255, 255, 255, 0.1)",
|
|
||||||
"reversiListItemShadow": "rgba(0, 0, 0, 0.7)",
|
|
||||||
"reversiMapSelectBorder": "rgba(255, 255, 255, 0.1)",
|
|
||||||
"reversiMapSelectHoverBorder": "rgba(255, 255, 255, 0.2)",
|
|
||||||
"reversiRoomFormShadow": "rgba(0, 0, 0, 0.7)",
|
|
||||||
"reversiRoomFooterBg": ":alpha<0.9<$secondary",
|
|
||||||
"reversiGameHeaderLine": ":alpha<0.5<$secondary",
|
|
||||||
"reversiGameEmptyCell": ":lighten<2<$secondary",
|
|
||||||
"reversiGameEmptyCellMyTurn": ":lighten<5<$secondary",
|
|
||||||
"reversiGameEmptyCellCanPut": ":lighten<4<$secondary"
|
|
||||||
}
|
|
||||||
207
src/client/theme/dark.json5
Normal file
207
src/client/theme/dark.json5
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
{
|
||||||
|
id: 'dark',
|
||||||
|
|
||||||
|
name: 'Dark',
|
||||||
|
author: 'syuilo',
|
||||||
|
desc: 'Default dark theme',
|
||||||
|
|
||||||
|
vars: {
|
||||||
|
primary: '#fb4e4e',
|
||||||
|
secondary: '#282C37',
|
||||||
|
text: '#d6dae0',
|
||||||
|
},
|
||||||
|
|
||||||
|
props: {
|
||||||
|
primary: '$primary',
|
||||||
|
primaryForeground: '#fff',
|
||||||
|
secondary: '$secondary',
|
||||||
|
bg: ':darken<8<$secondary',
|
||||||
|
text: '$text',
|
||||||
|
|
||||||
|
scrollbarTrack: ':darken<5<$secondary',
|
||||||
|
scrollbarHandle: ':lighten<5<$secondary',
|
||||||
|
scrollbarHandleHover: ':lighten<10<$secondary',
|
||||||
|
|
||||||
|
face: '$secondary',
|
||||||
|
faceText: '#fff',
|
||||||
|
faceHeader: ':lighten<5<$secondary',
|
||||||
|
faceHeaderText: '#e3e5e8',
|
||||||
|
faceDivider: 'rgba(0, 0, 0, 0.3)',
|
||||||
|
faceTextButton: '$text',
|
||||||
|
faceTextButtonHover: ':lighten<10<$text',
|
||||||
|
faceTextButtonActive: ':darken<10<$text',
|
||||||
|
faceClearButtonHover: 'rgba(0, 0, 0, 0.1)',
|
||||||
|
faceClearButtonActive: 'rgba(0, 0, 0, 0.2)',
|
||||||
|
popupBg: ':lighten<5<$secondary',
|
||||||
|
popupFg: '#d6dce2',
|
||||||
|
|
||||||
|
subNoteBg: 'rgba(0, 0, 0, 0.18)',
|
||||||
|
subNoteText: ':alpha<0.7<$text',
|
||||||
|
renoteGradient: '#314027',
|
||||||
|
renoteText: '#9dbb00',
|
||||||
|
quoteBorder: '#4e945e',
|
||||||
|
noteText: '#fff',
|
||||||
|
noteHeaderName: '#fff',
|
||||||
|
noteHeaderBadgeFg: '#758188',
|
||||||
|
noteHeaderBadgeBg: 'rgba(0, 0, 0, 0.25)',
|
||||||
|
noteHeaderAdminFg: '#f15f71',
|
||||||
|
noteHeaderAdminBg: '#5d282e',
|
||||||
|
noteHeaderAcct: '#606984',
|
||||||
|
noteHeaderInfo: '#606984',
|
||||||
|
|
||||||
|
noteActions: '#606984',
|
||||||
|
noteActionsHover: '#a1a8bf',
|
||||||
|
noteActionsReplyHover: '#0af',
|
||||||
|
noteActionsRenoteHover: '#8d0',
|
||||||
|
noteActionsReactionHover: '#fa0',
|
||||||
|
noteActionsHighlighted: '#707b97',
|
||||||
|
|
||||||
|
noteAttachedFile: 'rgba(255, 255, 255, 0.1)',
|
||||||
|
|
||||||
|
modalBackdrop: 'rgba(0, 0, 0, 0.5)',
|
||||||
|
|
||||||
|
dateDividerBg: ':darken<2<$secondary',
|
||||||
|
dateDividerFg: ':alpha<0.7<$text',
|
||||||
|
|
||||||
|
switchTrack: 'rgba(255, 255, 255, 0.15)',
|
||||||
|
radioBorder: 'rgba(255, 255, 255, 0.6)',
|
||||||
|
inputBorder: 'rgba(255, 255, 255, 0.7)',
|
||||||
|
inputLabel: 'rgba(255, 255, 255, 0.7)',
|
||||||
|
inputText: '#fff',
|
||||||
|
|
||||||
|
buttonBg: 'rgba(255, 255, 255, 0.05)',
|
||||||
|
buttonHoverBg: 'rgba(255, 255, 255, 0.1)',
|
||||||
|
buttonActiveBg: 'rgba(255, 255, 255, 0.15)',
|
||||||
|
|
||||||
|
autocompleteItemHoverBg: 'rgba(255, 255, 255, 0.1)',
|
||||||
|
autocompleteItemText: 'rgba(255, 255, 255, 0.8)',
|
||||||
|
autocompleteItemTextSub: 'rgba(255, 255, 255, 0.3)',
|
||||||
|
|
||||||
|
cwButtonBg: '#687390',
|
||||||
|
cwButtonFg: '#393f4f',
|
||||||
|
cwButtonHoverBg: '#707b97',
|
||||||
|
|
||||||
|
reactionPickerButtonHoverBg: 'rgba(255, 255, 255, 0.18)',
|
||||||
|
|
||||||
|
reactionViewerBorder: 'rgba(255, 255, 255, 0.1)',
|
||||||
|
|
||||||
|
pollEditorInputBg: 'rgba(0, 0, 0, 0.25)',
|
||||||
|
|
||||||
|
pollChoiceText: '#fff',
|
||||||
|
pollChoiceBorder: 'rgba(255, 255, 255, 0.1)',
|
||||||
|
|
||||||
|
urlPreviewBorder: 'rgba(0, 0, 0, 0.4)',
|
||||||
|
urlPreviewBorderHover: 'rgba(255, 255, 255, 0.2)',
|
||||||
|
urlPreviewTitle: '$text',
|
||||||
|
urlPreviewText: ':alpha<0.7<$text',
|
||||||
|
urlPreviewInfo: ':alpha<0.8<$text',
|
||||||
|
|
||||||
|
calendarWeek: '#43d5dc',
|
||||||
|
calendarSaturdayOrSunday: '#ff6679',
|
||||||
|
calendarDay: '#c5ced6',
|
||||||
|
|
||||||
|
materBg: 'rgba(0, 0, 0, 0.3)',
|
||||||
|
|
||||||
|
chartCaption: ':alpha<0.6<$text',
|
||||||
|
|
||||||
|
announcementsBg: '#253a50',
|
||||||
|
announcementsTitle: '#539eff',
|
||||||
|
announcementsText: '#fff',
|
||||||
|
|
||||||
|
donationBg: '#5d5242',
|
||||||
|
donationFg: '#e4dbce',
|
||||||
|
|
||||||
|
googleSearchBg: 'rgba(0, 0, 0, 0.2)',
|
||||||
|
googleSearchFg: '#dee4e8',
|
||||||
|
googleSearchBorder: 'rgba(255, 255, 255, 0.2)',
|
||||||
|
googleSearchHoverBorder: 'rgba(255, 255, 255, 0.3)',
|
||||||
|
googleSearchHoverButton: 'rgba(255, 255, 255, 0.1)',
|
||||||
|
|
||||||
|
mfmTitleBg: 'rgba(0, 0, 0, 0.2)',
|
||||||
|
mfmQuote: ':alpha<0.7<$text',
|
||||||
|
mfmQuoteLine: ':alpha<0.6<$text',
|
||||||
|
|
||||||
|
suspendedInfoBg: '#611d1d',
|
||||||
|
suspendedInfoFg: '#ffb4b4',
|
||||||
|
remoteInfoBg: '#42321c',
|
||||||
|
remoteInfoFg: '#ffbd3e',
|
||||||
|
|
||||||
|
messagingRoomBg: '@bg',
|
||||||
|
messagingRoomInfo: '#fff',
|
||||||
|
messagingRoomDateDividerLine: 'rgba(255, 255, 255, 0.1)',
|
||||||
|
messagingRoomDateDividerText: 'rgba(255, 255, 255, 0.3)',
|
||||||
|
messagingRoomMessageInfo: 'rgba(255, 255, 255, 0.4)',
|
||||||
|
messagingRoomMessageBg: '$secondary',
|
||||||
|
messagingRoomMessageFg: '#fff',
|
||||||
|
|
||||||
|
formButtonBorder: 'rgba(255, 255, 255, 0.1)',
|
||||||
|
formButtonHoverBg: ':alpha<0.2<$primary',
|
||||||
|
formButtonHoverBorder: ':alpha<0.5<$primary',
|
||||||
|
formButtonActiveBg: ':alpha<0.12<$primary',
|
||||||
|
|
||||||
|
desktopHeaderBg: ':lighten<5<$secondary',
|
||||||
|
desktopHeaderFg: '$text',
|
||||||
|
desktopHeaderHoverFg: '#fff',
|
||||||
|
desktopHeaderSearchBg: 'rgba(0, 0, 0, 0.1)',
|
||||||
|
desktopHeaderSearchHoverBg: 'rgba(255, 255, 255, 0.04)',
|
||||||
|
desktopHeaderSearchFg: '#fff',
|
||||||
|
desktopNotificationBg: ':alpha<0.9<$secondary',
|
||||||
|
desktopNotificationFg: ':alpha<0.7<$text',
|
||||||
|
desktopNotificationShadow: 'rgba(0, 0, 0, 0.4)',
|
||||||
|
desktopPostFormBg: '@face',
|
||||||
|
desktopPostFormTextareaBg: 'rgba(0, 0, 0, 0.25)',
|
||||||
|
desktopPostFormTextareaFg: '#fff',
|
||||||
|
desktopPostFormTransparentButtonFg: '$primary',
|
||||||
|
desktopPostFormTransparentButtonActiveGradientStart: ':darken<8<$secondary',
|
||||||
|
desktopPostFormTransparentButtonActiveGradientEnd: ':darken<3<$secondary',
|
||||||
|
desktopRenoteFormFooter: ':lighten<5<$secondary',
|
||||||
|
desktopTimelineHeaderShadow: 'rgba(0, 0, 0, 0.15)',
|
||||||
|
desktopTimelineSrc: '@faceTextButton',
|
||||||
|
desktopTimelineSrcHover: '@faceTextButtonHover',
|
||||||
|
desktopWindowTitle: '@faceHeaderText',
|
||||||
|
desktopWindowShadow: 'rgba(0, 0, 0, 0.5)',
|
||||||
|
desktopDriveBg: '@bg',
|
||||||
|
desktopDriveFolderBg: ':alpha<0.2<$primary',
|
||||||
|
desktopDriveFolderHoverBg: ':alpha<0.3<$primary',
|
||||||
|
desktopDriveFolderActiveBg: ':alpha<0.3<:darken<10<$primary',
|
||||||
|
desktopDriveFolderFg: '#fff',
|
||||||
|
desktopSettingsNavItem: ':alpha<0.8<$text',
|
||||||
|
desktopSettingsNavItemHover: ':lighten<10<$text',
|
||||||
|
|
||||||
|
deckAcrylicColumnBg: 'rgba(0, 0, 0, 0.25)',
|
||||||
|
|
||||||
|
mobileHeaderBg: ':lighten<5<$secondary',
|
||||||
|
mobileHeaderFg: '$text',
|
||||||
|
mobileNavBackdrop: 'rgba(0, 0, 0, 0.7)',
|
||||||
|
mobilePostFormDivider: 'rgba(0, 0, 0, 0.2)',
|
||||||
|
mobilePostFormTextareaBg: 'rgba(0, 0, 0, 0.3)',
|
||||||
|
mobileDriveNavBg: ':alpha<0.75<$secondary',
|
||||||
|
mobileHomeTlItemHover: 'rgba(255, 255, 255, 0.1)',
|
||||||
|
mobileUserPageName: '#fff',
|
||||||
|
mobileUserPageAcct: '$text',
|
||||||
|
mobileUserPageDescription: '$text',
|
||||||
|
mobileUserPageFollowedBg: 'rgba(0, 0, 0, 0.3)',
|
||||||
|
mobileUserPageFollowedFg: '$text',
|
||||||
|
mobileUserPageStatusHighlight: '#fff',
|
||||||
|
mobileUserPageHeaderShadow: 'rgba(0, 0, 0, 0.3)',
|
||||||
|
mobileAnnouncement: 'rgba(30, 129, 216, 0.2)',
|
||||||
|
mobileAnnouncementFg: '#fff',
|
||||||
|
mobileSignedInAsBg: '#273c34',
|
||||||
|
mobileSignedInAsFg: '#49ab63',
|
||||||
|
mobileSignoutBg: '#652222',
|
||||||
|
mobileSignoutFg: '#ff5f56',
|
||||||
|
|
||||||
|
reversiBannerGradientStart: '#45730e',
|
||||||
|
reversiBannerGradientEnd: '#464300',
|
||||||
|
reversiDescBg: 'rgba(255, 255, 255, 0.1)',
|
||||||
|
reversiListItemShadow: 'rgba(0, 0, 0, 0.7)',
|
||||||
|
reversiMapSelectBorder: 'rgba(255, 255, 255, 0.1)',
|
||||||
|
reversiMapSelectHoverBorder: 'rgba(255, 255, 255, 0.2)',
|
||||||
|
reversiRoomFormShadow: 'rgba(0, 0, 0, 0.7)',
|
||||||
|
reversiRoomFooterBg: ':alpha<0.9<$secondary',
|
||||||
|
reversiGameHeaderLine: ':alpha<0.5<$secondary',
|
||||||
|
reversiGameEmptyCell: ':lighten<2<$secondary',
|
||||||
|
reversiGameEmptyCellMyTurn: ':lighten<5<$secondary',
|
||||||
|
reversiGameEmptyCellCanPut: ':lighten<4<$secondary',
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
{
|
|
||||||
"meta": {
|
|
||||||
"id": "42e4f09b-67d5-498c-af7d-29faa54745b0",
|
|
||||||
"name": "Halloween",
|
|
||||||
"author": "syuilo",
|
|
||||||
"base": "dark",
|
|
||||||
"vars": {
|
|
||||||
"primary": "#d67036",
|
|
||||||
"secondary": "#1f1d30",
|
|
||||||
"text": "#b1bee3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
"renoteGradient": "#5d2d1a",
|
|
||||||
"renoteText": "#ff6c00",
|
|
||||||
"quoteBorder": "#c3631c"
|
|
||||||
}
|
|
||||||
21
src/client/theme/halloween.json5
Normal file
21
src/client/theme/halloween.json5
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
id: '42e4f09b-67d5-498c-af7d-29faa54745b0',
|
||||||
|
|
||||||
|
name: 'Halloween',
|
||||||
|
author: 'syuilo',
|
||||||
|
desc: 'Hello, Happy Halloween!',
|
||||||
|
|
||||||
|
base: 'dark',
|
||||||
|
|
||||||
|
vars: {
|
||||||
|
primary: '#d67036',
|
||||||
|
secondary: '#1f1d30',
|
||||||
|
text: '#b1bee3',
|
||||||
|
},
|
||||||
|
|
||||||
|
props: {
|
||||||
|
renoteGradient: '#5d2d1a',
|
||||||
|
renoteText: '#ff6c00',
|
||||||
|
quoteBorder: '#c3631c',
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -1,204 +0,0 @@
|
|||||||
{
|
|
||||||
"meta": {
|
|
||||||
"id": "light",
|
|
||||||
"name": "Light",
|
|
||||||
"author": "syuilo",
|
|
||||||
"vars": {
|
|
||||||
"primary": "#fb4e4e",
|
|
||||||
"secondary": "#fff",
|
|
||||||
"text": "#666"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
"primary": "$primary",
|
|
||||||
"primaryForeground": "#fff",
|
|
||||||
"secondary": "$secondary",
|
|
||||||
"bg": ":darken<8<$secondary",
|
|
||||||
"text": "$text",
|
|
||||||
|
|
||||||
"scrollbarTrack": "#fff",
|
|
||||||
"scrollbarHandle": "#00000033",
|
|
||||||
"scrollbarHandleHover": "#00000066",
|
|
||||||
|
|
||||||
"face": "$secondary",
|
|
||||||
"faceText": "#444",
|
|
||||||
"faceHeader": ":lighten<5<$secondary",
|
|
||||||
"faceHeaderText": "#888",
|
|
||||||
"faceDivider": "rgba(0, 0, 0, 0.082)",
|
|
||||||
"faceTextButton": "#ccc",
|
|
||||||
"faceTextButtonHover": "#aaa",
|
|
||||||
"faceTextButtonActive": "#999",
|
|
||||||
"faceClearButtonHover": "rgba(0, 0, 0, 0.025)",
|
|
||||||
"faceClearButtonActive": "rgba(0, 0, 0, 0.05)",
|
|
||||||
"popupBg": ":lighten<5<$secondary",
|
|
||||||
"popupFg": "#586069",
|
|
||||||
|
|
||||||
"subNoteBg": "rgba(0, 0, 0, 0.01)",
|
|
||||||
"subNoteText": ":alpha<0.7<$text",
|
|
||||||
"renoteGradient": "#edfde2",
|
|
||||||
"renoteText": "#9dbb00",
|
|
||||||
"quoteBorder": "#c0dac6",
|
|
||||||
"noteText": "#717171",
|
|
||||||
"noteHeaderName": ":darken<2<$text",
|
|
||||||
"noteHeaderBadgeFg": "#aaa",
|
|
||||||
"noteHeaderBadgeBg": "rgba(0, 0, 0, 0.05)",
|
|
||||||
"noteHeaderAdminFg": "#f15f71",
|
|
||||||
"noteHeaderAdminBg": "#ffdfdf",
|
|
||||||
"noteHeaderAcct": ":alpha<0.7<@noteHeaderName",
|
|
||||||
"noteHeaderInfo": ":alpha<0.7<@noteHeaderName",
|
|
||||||
|
|
||||||
"noteActions": ":alpha<0.3<$text",
|
|
||||||
"noteActionsHover": ":alpha<0.9<$text",
|
|
||||||
"noteActionsReplyHover": "#0af",
|
|
||||||
"noteActionsRenoteHover": "#8d0",
|
|
||||||
"noteActionsReactionHover": "#fa0",
|
|
||||||
"noteActionsHighlighted": "#888",
|
|
||||||
|
|
||||||
"noteAttachedFile": "rgba(0, 0, 0, 0.05)",
|
|
||||||
|
|
||||||
"modalBackdrop": "rgba(0, 0, 0, 0.1)",
|
|
||||||
|
|
||||||
"dateDividerBg": ":darken<2<$secondary",
|
|
||||||
"dateDividerFg": ":alpha<0.7<$text",
|
|
||||||
|
|
||||||
"switchTrack": "rgba(0, 0, 0, 0.25)",
|
|
||||||
"radioBorder": "rgba(0, 0, 0, 0.4)",
|
|
||||||
"inputBorder": "rgba(0, 0, 0, 0.42)",
|
|
||||||
"inputLabel": "rgba(0, 0, 0, 0.54)",
|
|
||||||
"inputText": "#000",
|
|
||||||
|
|
||||||
"buttonBg": "rgba(0, 0, 0, 0.05)",
|
|
||||||
"buttonHoverBg": "rgba(0, 0, 0, 0.1)",
|
|
||||||
"buttonActiveBg": "rgba(0, 0, 0, 0.15)",
|
|
||||||
|
|
||||||
"autocompleteItemHoverBg": "rgba(0, 0, 0, 0.1)",
|
|
||||||
"autocompleteItemText": "rgba(0, 0, 0, 0.8)",
|
|
||||||
"autocompleteItemTextSub": "rgba(0, 0, 0, 0.3)",
|
|
||||||
|
|
||||||
"cwButtonBg": "#b1b9c1",
|
|
||||||
"cwButtonFg": "#fff",
|
|
||||||
"cwButtonHoverBg": "#bbc4ce",
|
|
||||||
|
|
||||||
"reactionPickerButtonHoverBg": "#eee",
|
|
||||||
|
|
||||||
"reactionViewerBorder": "rgba(0, 0, 0, 0.1)",
|
|
||||||
|
|
||||||
"pollEditorInputBg": "#fff",
|
|
||||||
|
|
||||||
"pollChoiceText": "#000",
|
|
||||||
"pollChoiceBorder": "rgba(0, 0, 0, 0.1)",
|
|
||||||
|
|
||||||
"urlPreviewBorder": "rgba(0, 0, 0, 0.1)",
|
|
||||||
"urlPreviewBorderHover": "rgba(0, 0, 0, 0.2)",
|
|
||||||
"urlPreviewTitle": "$text",
|
|
||||||
"urlPreviewText": ":alpha<0.7<$text",
|
|
||||||
"urlPreviewInfo": ":alpha<0.8<$text",
|
|
||||||
|
|
||||||
"calendarWeek": "#19a2a9",
|
|
||||||
"calendarSaturdayOrSunday": "#ef95a0",
|
|
||||||
"calendarDay": "#777",
|
|
||||||
|
|
||||||
"materBg": "rgba(0, 0, 0, 0.1)",
|
|
||||||
|
|
||||||
"chartCaption": ":alpha<0.6<$text",
|
|
||||||
|
|
||||||
"announcementsBg": "#f3f9ff",
|
|
||||||
"announcementsTitle": "#4078c0",
|
|
||||||
"announcementsText": "#57616f",
|
|
||||||
|
|
||||||
"donationBg": "#fbead4",
|
|
||||||
"donationFg": "#777d71",
|
|
||||||
|
|
||||||
"googleSearchBg": "#fff",
|
|
||||||
"googleSearchFg": "#55595c",
|
|
||||||
"googleSearchBorder": "rgba(0, 0, 0, 0.2)",
|
|
||||||
"googleSearchHoverBorder": "rgba(0, 0, 0, 0.3)",
|
|
||||||
"googleSearchHoverButton": "rgba(0, 0, 0, 0.05)",
|
|
||||||
|
|
||||||
"mfmTitleBg": "rgba(0, 0, 0, 0.07)",
|
|
||||||
"mfmQuote": ":alpha<0.6<$text",
|
|
||||||
"mfmQuoteLine": ":alpha<0.5<$text",
|
|
||||||
|
|
||||||
"suspendedInfoBg": "#ffdbdb",
|
|
||||||
"suspendedInfoFg": "#570808",
|
|
||||||
"remoteInfoBg": "#fff0db",
|
|
||||||
"remoteInfoFg": "#573c08",
|
|
||||||
|
|
||||||
"messagingRoomBg": "#fff",
|
|
||||||
"messagingRoomInfo": "#000",
|
|
||||||
"messagingRoomDateDividerLine": "rgba(0, 0, 0, 0.1)",
|
|
||||||
"messagingRoomDateDividerText": "rgba(0, 0, 0, 0.3)",
|
|
||||||
"messagingRoomMessageInfo": "rgba(0, 0, 0, 0.4)",
|
|
||||||
"messagingRoomMessageBg": "#eee",
|
|
||||||
"messagingRoomMessageFg": "#333",
|
|
||||||
|
|
||||||
"formButtonBorder": "rgba(0, 0, 0, 0.1)",
|
|
||||||
"formButtonHoverBg": ":alpha<0.12<$primary",
|
|
||||||
"formButtonHoverBorder": ":alpha<0.3<$primary",
|
|
||||||
"formButtonActiveBg": ":alpha<0.12<$primary",
|
|
||||||
|
|
||||||
"desktopHeaderBg": ":lighten<5<$secondary",
|
|
||||||
"desktopHeaderFg": "$text",
|
|
||||||
"desktopHeaderHoverFg": "#7b8c88",
|
|
||||||
"desktopHeaderSearchBg": "rgba(0, 0, 0, 0.05)",
|
|
||||||
"desktopHeaderSearchHoverBg": "rgba(0, 0, 0, 0.08)",
|
|
||||||
"desktopHeaderSearchFg": "#000",
|
|
||||||
"desktopNotificationBg": ":alpha<0.9<$secondary",
|
|
||||||
"desktopNotificationFg": ":alpha<0.7<$text",
|
|
||||||
"desktopNotificationShadow": "rgba(0, 0, 0, 0.2)",
|
|
||||||
"desktopPostFormBg": ":lighten<33<$primary",
|
|
||||||
"desktopPostFormTextareaBg": "#fff",
|
|
||||||
"desktopPostFormTextareaFg": "#333",
|
|
||||||
"desktopPostFormTransparentButtonFg": ":alpha<0.5<$primary",
|
|
||||||
"desktopPostFormTransparentButtonActiveGradientStart": ":lighten<30<$primary",
|
|
||||||
"desktopPostFormTransparentButtonActiveGradientEnd": ":lighten<33<$primary",
|
|
||||||
"desktopRenoteFormFooter": ":lighten<33<$primary",
|
|
||||||
"desktopTimelineHeaderShadow": "rgba(0, 0, 0, 0.08)",
|
|
||||||
"desktopTimelineSrc": "#6f7477",
|
|
||||||
"desktopTimelineSrcHover": "#525a5f",
|
|
||||||
"desktopWindowTitle": "#666",
|
|
||||||
"desktopWindowShadow": "rgba(0, 0, 0, 0.2)",
|
|
||||||
"desktopDriveBg": "#fff",
|
|
||||||
"desktopDriveFolderBg": ":lighten<31<$primary",
|
|
||||||
"desktopDriveFolderHoverBg": ":lighten<27<$primary",
|
|
||||||
"desktopDriveFolderActiveBg": ":lighten<25<$primary",
|
|
||||||
"desktopDriveFolderFg": ":darken<10<$primary",
|
|
||||||
"desktopSettingsNavItem": ":alpha<0.8<$text",
|
|
||||||
"desktopSettingsNavItemHover": ":darken<10<$text",
|
|
||||||
|
|
||||||
"deckAcrylicColumnBg": "rgba(0, 0, 0, 0.1)",
|
|
||||||
|
|
||||||
"mobileHeaderBg": ":lighten<5<$secondary",
|
|
||||||
"mobileHeaderFg": "$text",
|
|
||||||
"mobileNavBackdrop": "rgba(0, 0, 0, 0.2)",
|
|
||||||
"mobilePostFormDivider": "rgba(0, 0, 0, 0.1)",
|
|
||||||
"mobilePostFormTextareaBg": "#fff",
|
|
||||||
"mobileDriveNavBg": ":alpha<0.75<$secondary",
|
|
||||||
"mobileHomeTlItemHover": "rgba(0, 0, 0, 0.05)",
|
|
||||||
"mobileUserPageName": "#757c82",
|
|
||||||
"mobileUserPageAcct": "#969ea5",
|
|
||||||
"mobileUserPageDescription": "#757c82",
|
|
||||||
"mobileUserPageFollowedBg": "#a7bec7",
|
|
||||||
"mobileUserPageFollowedFg": "#fff",
|
|
||||||
"mobileUserPageStatusHighlight": "#787e86",
|
|
||||||
"mobileUserPageHeaderShadow": "rgba(0, 0, 0, 0.07)",
|
|
||||||
"mobileAnnouncement": "rgba(155, 196, 232, 0.2)",
|
|
||||||
"mobileAnnouncementFg": "#3f4967",
|
|
||||||
"mobileSignedInAsBg": "#fcfff5",
|
|
||||||
"mobileSignedInAsFg": "#2c662d",
|
|
||||||
"mobileSignoutBg": "#fff6f5",
|
|
||||||
"mobileSignoutFg": "#cc2727",
|
|
||||||
|
|
||||||
"reversiBannerGradientStart": "#8bca3e",
|
|
||||||
"reversiBannerGradientEnd": "#d6cf31",
|
|
||||||
"reversiDescBg": "rgba(0, 0, 0, 0.1)",
|
|
||||||
"reversiListItemShadow": "rgba(0, 0, 0, 0.15)",
|
|
||||||
"reversiMapSelectBorder": "rgba(0, 0, 0, 0.1)",
|
|
||||||
"reversiMapSelectHoverBorder": "rgba(0, 0, 0, 0.2)",
|
|
||||||
"reversiRoomFormShadow": "rgba(0, 0, 0, 0.1)",
|
|
||||||
"reversiRoomFooterBg": ":alpha<0.9<$secondary",
|
|
||||||
"reversiGameHeaderLine": "#c4cdd4",
|
|
||||||
"reversiGameEmptyCell": "rgba(0, 0, 0, 0.06)",
|
|
||||||
"reversiGameEmptyCellMyTurn": "rgba(0, 0, 0, 0.12)",
|
|
||||||
"reversiGameEmptyCellCanPut": "rgba(0, 0, 0, 0.9)"
|
|
||||||
}
|
|
||||||
207
src/client/theme/light.json5
Normal file
207
src/client/theme/light.json5
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
{
|
||||||
|
id: 'light',
|
||||||
|
|
||||||
|
name: 'Light',
|
||||||
|
author: 'syuilo',
|
||||||
|
desc: 'Default light theme',
|
||||||
|
|
||||||
|
vars: {
|
||||||
|
primary: '#fb4e4e',
|
||||||
|
secondary: '#fff',
|
||||||
|
text: '#666',
|
||||||
|
},
|
||||||
|
|
||||||
|
props: {
|
||||||
|
primary: '$primary',
|
||||||
|
primaryForeground: '#fff',
|
||||||
|
secondary: '$secondary',
|
||||||
|
bg: ':darken<8<$secondary',
|
||||||
|
text: '$text',
|
||||||
|
|
||||||
|
scrollbarTrack: '#fff',
|
||||||
|
scrollbarHandle: '#00000033',
|
||||||
|
scrollbarHandleHover: '#00000066',
|
||||||
|
|
||||||
|
face: '$secondary',
|
||||||
|
faceText: '#444',
|
||||||
|
faceHeader: ':lighten<5<$secondary',
|
||||||
|
faceHeaderText: '#888',
|
||||||
|
faceDivider: 'rgba(0, 0, 0, 0.082)',
|
||||||
|
faceTextButton: '#ccc',
|
||||||
|
faceTextButtonHover: '#aaa',
|
||||||
|
faceTextButtonActive: '#999',
|
||||||
|
faceClearButtonHover: 'rgba(0, 0, 0, 0.025)',
|
||||||
|
faceClearButtonActive: 'rgba(0, 0, 0, 0.05)',
|
||||||
|
popupBg: ':lighten<5<$secondary',
|
||||||
|
popupFg: '#586069',
|
||||||
|
|
||||||
|
subNoteBg: 'rgba(0, 0, 0, 0.01)',
|
||||||
|
subNoteText: ':alpha<0.7<$text',
|
||||||
|
renoteGradient: '#edfde2',
|
||||||
|
renoteText: '#9dbb00',
|
||||||
|
quoteBorder: '#c0dac6',
|
||||||
|
noteText: '#717171',
|
||||||
|
noteHeaderName: ':darken<2<$text',
|
||||||
|
noteHeaderBadgeFg: '#aaa',
|
||||||
|
noteHeaderBadgeBg: 'rgba(0, 0, 0, 0.05)',
|
||||||
|
noteHeaderAdminFg: '#f15f71',
|
||||||
|
noteHeaderAdminBg: '#ffdfdf',
|
||||||
|
noteHeaderAcct: ':alpha<0.7<@noteHeaderName',
|
||||||
|
noteHeaderInfo: ':alpha<0.7<@noteHeaderName',
|
||||||
|
|
||||||
|
noteActions: ':alpha<0.3<$text',
|
||||||
|
noteActionsHover: ':alpha<0.9<$text',
|
||||||
|
noteActionsReplyHover: '#0af',
|
||||||
|
noteActionsRenoteHover: '#8d0',
|
||||||
|
noteActionsReactionHover: '#fa0',
|
||||||
|
noteActionsHighlighted: '#888',
|
||||||
|
|
||||||
|
noteAttachedFile: 'rgba(0, 0, 0, 0.05)',
|
||||||
|
|
||||||
|
modalBackdrop: 'rgba(0, 0, 0, 0.1)',
|
||||||
|
|
||||||
|
dateDividerBg: ':darken<2<$secondary',
|
||||||
|
dateDividerFg: ':alpha<0.7<$text',
|
||||||
|
|
||||||
|
switchTrack: 'rgba(0, 0, 0, 0.25)',
|
||||||
|
radioBorder: 'rgba(0, 0, 0, 0.4)',
|
||||||
|
inputBorder: 'rgba(0, 0, 0, 0.42)',
|
||||||
|
inputLabel: 'rgba(0, 0, 0, 0.54)',
|
||||||
|
inputText: '#000',
|
||||||
|
|
||||||
|
buttonBg: 'rgba(0, 0, 0, 0.05)',
|
||||||
|
buttonHoverBg: 'rgba(0, 0, 0, 0.1)',
|
||||||
|
buttonActiveBg: 'rgba(0, 0, 0, 0.15)',
|
||||||
|
|
||||||
|
autocompleteItemHoverBg: 'rgba(0, 0, 0, 0.1)',
|
||||||
|
autocompleteItemText: 'rgba(0, 0, 0, 0.8)',
|
||||||
|
autocompleteItemTextSub: 'rgba(0, 0, 0, 0.3)',
|
||||||
|
|
||||||
|
cwButtonBg: '#b1b9c1',
|
||||||
|
cwButtonFg: '#fff',
|
||||||
|
cwButtonHoverBg: '#bbc4ce',
|
||||||
|
|
||||||
|
reactionPickerButtonHoverBg: '#eee',
|
||||||
|
|
||||||
|
reactionViewerBorder: 'rgba(0, 0, 0, 0.1)',
|
||||||
|
|
||||||
|
pollEditorInputBg: '#fff',
|
||||||
|
|
||||||
|
pollChoiceText: '#000',
|
||||||
|
pollChoiceBorder: 'rgba(0, 0, 0, 0.1)',
|
||||||
|
|
||||||
|
urlPreviewBorder: 'rgba(0, 0, 0, 0.1)',
|
||||||
|
urlPreviewBorderHover: 'rgba(0, 0, 0, 0.2)',
|
||||||
|
urlPreviewTitle: '$text',
|
||||||
|
urlPreviewText: ':alpha<0.7<$text',
|
||||||
|
urlPreviewInfo: ':alpha<0.8<$text',
|
||||||
|
|
||||||
|
calendarWeek: '#19a2a9',
|
||||||
|
calendarSaturdayOrSunday: '#ef95a0',
|
||||||
|
calendarDay: '#777',
|
||||||
|
|
||||||
|
materBg: 'rgba(0, 0, 0, 0.1)',
|
||||||
|
|
||||||
|
chartCaption: ':alpha<0.6<$text',
|
||||||
|
|
||||||
|
announcementsBg: '#f3f9ff',
|
||||||
|
announcementsTitle: '#4078c0',
|
||||||
|
announcementsText: '#57616f',
|
||||||
|
|
||||||
|
donationBg: '#fbead4',
|
||||||
|
donationFg: '#777d71',
|
||||||
|
|
||||||
|
googleSearchBg: '#fff',
|
||||||
|
googleSearchFg: '#55595c',
|
||||||
|
googleSearchBorder: 'rgba(0, 0, 0, 0.2)',
|
||||||
|
googleSearchHoverBorder: 'rgba(0, 0, 0, 0.3)',
|
||||||
|
googleSearchHoverButton: 'rgba(0, 0, 0, 0.05)',
|
||||||
|
|
||||||
|
mfmTitleBg: 'rgba(0, 0, 0, 0.07)',
|
||||||
|
mfmQuote: ':alpha<0.6<$text',
|
||||||
|
mfmQuoteLine: ':alpha<0.5<$text',
|
||||||
|
|
||||||
|
suspendedInfoBg: '#ffdbdb',
|
||||||
|
suspendedInfoFg: '#570808',
|
||||||
|
remoteInfoBg: '#fff0db',
|
||||||
|
remoteInfoFg: '#573c08',
|
||||||
|
|
||||||
|
messagingRoomBg: '#fff',
|
||||||
|
messagingRoomInfo: '#000',
|
||||||
|
messagingRoomDateDividerLine: 'rgba(0, 0, 0, 0.1)',
|
||||||
|
messagingRoomDateDividerText: 'rgba(0, 0, 0, 0.3)',
|
||||||
|
messagingRoomMessageInfo: 'rgba(0, 0, 0, 0.4)',
|
||||||
|
messagingRoomMessageBg: '#eee',
|
||||||
|
messagingRoomMessageFg: '#333',
|
||||||
|
|
||||||
|
formButtonBorder: 'rgba(0, 0, 0, 0.1)',
|
||||||
|
formButtonHoverBg: ':alpha<0.12<$primary',
|
||||||
|
formButtonHoverBorder: ':alpha<0.3<$primary',
|
||||||
|
formButtonActiveBg: ':alpha<0.12<$primary',
|
||||||
|
|
||||||
|
desktopHeaderBg: ':lighten<5<$secondary',
|
||||||
|
desktopHeaderFg: '$text',
|
||||||
|
desktopHeaderHoverFg: '#7b8c88',
|
||||||
|
desktopHeaderSearchBg: 'rgba(0, 0, 0, 0.05)',
|
||||||
|
desktopHeaderSearchHoverBg: 'rgba(0, 0, 0, 0.08)',
|
||||||
|
desktopHeaderSearchFg: '#000',
|
||||||
|
desktopNotificationBg: ':alpha<0.9<$secondary',
|
||||||
|
desktopNotificationFg: ':alpha<0.7<$text',
|
||||||
|
desktopNotificationShadow: 'rgba(0, 0, 0, 0.2)',
|
||||||
|
desktopPostFormBg: ':lighten<33<$primary',
|
||||||
|
desktopPostFormTextareaBg: '#fff',
|
||||||
|
desktopPostFormTextareaFg: '#333',
|
||||||
|
desktopPostFormTransparentButtonFg: ':alpha<0.5<$primary',
|
||||||
|
desktopPostFormTransparentButtonActiveGradientStart: ':lighten<30<$primary',
|
||||||
|
desktopPostFormTransparentButtonActiveGradientEnd: ':lighten<33<$primary',
|
||||||
|
desktopRenoteFormFooter: ':lighten<33<$primary',
|
||||||
|
desktopTimelineHeaderShadow: 'rgba(0, 0, 0, 0.08)',
|
||||||
|
desktopTimelineSrc: '#6f7477',
|
||||||
|
desktopTimelineSrcHover: '#525a5f',
|
||||||
|
desktopWindowTitle: '#666',
|
||||||
|
desktopWindowShadow: 'rgba(0, 0, 0, 0.2)',
|
||||||
|
desktopDriveBg: '#fff',
|
||||||
|
desktopDriveFolderBg: ':lighten<31<$primary',
|
||||||
|
desktopDriveFolderHoverBg: ':lighten<27<$primary',
|
||||||
|
desktopDriveFolderActiveBg: ':lighten<25<$primary',
|
||||||
|
desktopDriveFolderFg: ':darken<10<$primary',
|
||||||
|
desktopSettingsNavItem: ':alpha<0.8<$text',
|
||||||
|
desktopSettingsNavItemHover: ':darken<10<$text',
|
||||||
|
|
||||||
|
deckAcrylicColumnBg: 'rgba(0, 0, 0, 0.1)',
|
||||||
|
|
||||||
|
mobileHeaderBg: ':lighten<5<$secondary',
|
||||||
|
mobileHeaderFg: '$text',
|
||||||
|
mobileNavBackdrop: 'rgba(0, 0, 0, 0.2)',
|
||||||
|
mobilePostFormDivider: 'rgba(0, 0, 0, 0.1)',
|
||||||
|
mobilePostFormTextareaBg: '#fff',
|
||||||
|
mobileDriveNavBg: ':alpha<0.75<$secondary',
|
||||||
|
mobileHomeTlItemHover: 'rgba(0, 0, 0, 0.05)',
|
||||||
|
mobileUserPageName: '#757c82',
|
||||||
|
mobileUserPageAcct: '#969ea5',
|
||||||
|
mobileUserPageDescription: '#757c82',
|
||||||
|
mobileUserPageFollowedBg: '#a7bec7',
|
||||||
|
mobileUserPageFollowedFg: '#fff',
|
||||||
|
mobileUserPageStatusHighlight: '#787e86',
|
||||||
|
mobileUserPageHeaderShadow: 'rgba(0, 0, 0, 0.07)',
|
||||||
|
mobileAnnouncement: 'rgba(155, 196, 232, 0.2)',
|
||||||
|
mobileAnnouncementFg: '#3f4967',
|
||||||
|
mobileSignedInAsBg: '#fcfff5',
|
||||||
|
mobileSignedInAsFg: '#2c662d',
|
||||||
|
mobileSignoutBg: '#fff6f5',
|
||||||
|
mobileSignoutFg: '#cc2727',
|
||||||
|
|
||||||
|
reversiBannerGradientStart: '#8bca3e',
|
||||||
|
reversiBannerGradientEnd: '#d6cf31',
|
||||||
|
reversiDescBg: 'rgba(0, 0, 0, 0.1)',
|
||||||
|
reversiListItemShadow: 'rgba(0, 0, 0, 0.15)',
|
||||||
|
reversiMapSelectBorder: 'rgba(0, 0, 0, 0.1)',
|
||||||
|
reversiMapSelectHoverBorder: 'rgba(0, 0, 0, 0.2)',
|
||||||
|
reversiRoomFormShadow: 'rgba(0, 0, 0, 0.1)',
|
||||||
|
reversiRoomFooterBg: ':alpha<0.9<$secondary',
|
||||||
|
reversiGameHeaderLine: '#c4cdd4',
|
||||||
|
reversiGameEmptyCell: 'rgba(0, 0, 0, 0.06)',
|
||||||
|
reversiGameEmptyCellMyTurn: 'rgba(0, 0, 0, 0.12)',
|
||||||
|
reversiGameEmptyCellCanPut: 'rgba(0, 0, 0, 0.9)',
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
{
|
|
||||||
"meta": {
|
|
||||||
"id": "e9c8c01d-9c15-48d0-9b5c-3d00843b5b36",
|
|
||||||
"name": "Pink",
|
|
||||||
"author": "syuilo",
|
|
||||||
"base": "light",
|
|
||||||
"vars": {
|
|
||||||
"primary": "rgb(251, 78, 112)",
|
|
||||||
"secondary": "rgb(255, 218, 240)",
|
|
||||||
"text": "rgb(113, 91, 102)"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
"renoteGradient": "#ffb1c9",
|
|
||||||
"renoteText": "#ff588d",
|
|
||||||
"quoteBorder": "#ff6c9b"
|
|
||||||
}
|
|
||||||
20
src/client/theme/pink.json5
Normal file
20
src/client/theme/pink.json5
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
id: 'e9c8c01d-9c15-48d0-9b5c-3d00843b5b36',
|
||||||
|
|
||||||
|
name: 'Strawberry Milk',
|
||||||
|
author: 'syuilo',
|
||||||
|
|
||||||
|
base: 'light',
|
||||||
|
|
||||||
|
vars: {
|
||||||
|
primary: 'rgb(251, 78, 112)',
|
||||||
|
secondary: 'rgb(255, 218, 240)',
|
||||||
|
text: 'rgb(113, 91, 102)',
|
||||||
|
},
|
||||||
|
|
||||||
|
props: {
|
||||||
|
renoteGradient: '#ffb1c9',
|
||||||
|
renoteText: '#ff588d',
|
||||||
|
quoteBorder: '#ff6c9b',
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -27,21 +27,6 @@ Note.createIndex({
|
|||||||
});
|
});
|
||||||
export default Note;
|
export default Note;
|
||||||
|
|
||||||
// 後方互換性のため
|
|
||||||
Note.findOne({
|
|
||||||
fileIds: { $exists: true }
|
|
||||||
}).then(n => {
|
|
||||||
if (n == null) {
|
|
||||||
Note.update({}, {
|
|
||||||
$rename: {
|
|
||||||
mediaIds: 'fileIds'
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
multi: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export function isValidText(text: string): boolean {
|
export function isValidText(text: string): boolean {
|
||||||
return length(text.trim()) <= 1000 && text.trim() != '';
|
return length(text.trim()) <= 1000 && text.trim() != '';
|
||||||
}
|
}
|
||||||
@@ -241,6 +226,17 @@ export const hideNote = async (packedNote: any, meId: mongo.ObjectID) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const packMany = async (
|
||||||
|
notes: (string | mongo.ObjectID | INote)[],
|
||||||
|
me?: string | mongo.ObjectID | IUser,
|
||||||
|
options?: {
|
||||||
|
detail?: boolean;
|
||||||
|
skipHide?: boolean;
|
||||||
|
}
|
||||||
|
) => {
|
||||||
|
return (await Promise.all(notes.map(n => pack(n, me, options)))).filter(x => x != null);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pack a note for API response
|
* Pack a note for API response
|
||||||
*
|
*
|
||||||
@@ -286,7 +282,11 @@ export const pack = async (
|
|||||||
_note = deepcopy(note);
|
_note = deepcopy(note);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_note) throw `invalid note arg ${note}`;
|
// 投稿がデータベース上に見つからなかったとき
|
||||||
|
if (_note == null) {
|
||||||
|
console.warn(`note not found on database: ${note}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const id = _note._id;
|
const id = _note._id;
|
||||||
|
|
||||||
@@ -309,7 +309,7 @@ export const pack = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Populate files
|
// Populate files
|
||||||
_note.files = Promise.all(_note.fileIds.map((fileId: mongo.ObjectID) =>
|
_note.files = Promise.all((_note.fileIds || []).map((fileId: mongo.ObjectID) =>
|
||||||
packFile(fileId)
|
packFile(fileId)
|
||||||
));
|
));
|
||||||
|
|
||||||
|
|||||||
@@ -3,11 +3,6 @@ import db from '../db/mongodb';
|
|||||||
|
|
||||||
const Stats = db.get<IStats>('stats');
|
const Stats = db.get<IStats>('stats');
|
||||||
|
|
||||||
// 後方互換性のため
|
|
||||||
Stats.dropIndex({ date: -1 } as any).catch((e: mongo.MongoError) => {
|
|
||||||
if (e.code !== 27) throw e;
|
|
||||||
});
|
|
||||||
|
|
||||||
Stats.createIndex({ span: -1, date: -1 }, { unique: true });
|
Stats.createIndex({ span: -1, date: -1 }, { unique: true });
|
||||||
export default Stats;
|
export default Stats;
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ const deepcopy = require('deepcopy');
|
|||||||
const sequential = require('promise-sequential');
|
const sequential = require('promise-sequential');
|
||||||
import rap from '@prezzemolo/rap';
|
import rap from '@prezzemolo/rap';
|
||||||
import db from '../db/mongodb';
|
import db from '../db/mongodb';
|
||||||
import Note, { pack as packNote, deleteNote } from './note';
|
import Note, { packMany as packNoteMany, deleteNote } from './note';
|
||||||
import Following, { deleteFollowing } from './following';
|
import Following, { deleteFollowing } from './following';
|
||||||
import Mute, { deleteMute } from './mute';
|
import Mute, { deleteMute } from './mute';
|
||||||
import { getFriendIds } from '../server/api/common/get-friends';
|
import { getFriendIds } from '../server/api/common/get-friends';
|
||||||
@@ -35,28 +35,6 @@ User.createIndex('uri', { sparse: true, unique: true });
|
|||||||
|
|
||||||
export default User;
|
export default User;
|
||||||
|
|
||||||
// 後方互換性のため
|
|
||||||
User.findOne({
|
|
||||||
pinnedNoteId: { $exists: true }
|
|
||||||
}).then(async x => {
|
|
||||||
if (x == null) return;
|
|
||||||
|
|
||||||
const users = await User.find({
|
|
||||||
pinnedNoteId: { $exists: true }
|
|
||||||
});
|
|
||||||
|
|
||||||
users.forEach(u => {
|
|
||||||
User.update({ _id: u._id }, {
|
|
||||||
$set: {
|
|
||||||
pinnedNoteIds: [(u as any).pinnedNoteId]
|
|
||||||
},
|
|
||||||
$unset: {
|
|
||||||
pinnedNoteId: ''
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
type IUserBase = {
|
type IUserBase = {
|
||||||
_id: mongo.ObjectID;
|
_id: mongo.ObjectID;
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
@@ -135,6 +113,7 @@ export interface ILocalUser extends IUserBase {
|
|||||||
export interface IRemoteUser extends IUserBase {
|
export interface IRemoteUser extends IUserBase {
|
||||||
inbox: string;
|
inbox: string;
|
||||||
sharedInbox?: string;
|
sharedInbox?: string;
|
||||||
|
featured?: string;
|
||||||
endpoints: string[];
|
endpoints: string[];
|
||||||
uri: string;
|
uri: string;
|
||||||
url?: string;
|
url?: string;
|
||||||
@@ -382,9 +361,11 @@ export const pack = (
|
|||||||
_user = deepcopy(user);
|
_user = deepcopy(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: ここでエラーにするのではなくダミーのユーザーデータを返す
|
// ユーザーがデータベース上に見つからなかったとき
|
||||||
// SEE: https://github.com/syuilo/misskey/issues/1432
|
if (_user == null) {
|
||||||
if (!_user) return reject('invalid user arg.');
|
console.warn(`user not found on database: ${user}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// Me
|
// Me
|
||||||
const meId: mongo.ObjectID = me
|
const meId: mongo.ObjectID = me
|
||||||
@@ -489,9 +470,9 @@ export const pack = (
|
|||||||
if (opts.detail) {
|
if (opts.detail) {
|
||||||
if (_user.pinnedNoteIds) {
|
if (_user.pinnedNoteIds) {
|
||||||
// Populate pinned notes
|
// Populate pinned notes
|
||||||
_user.pinnedNotes = Promise.all(_user.pinnedNoteIds.map((id: mongo.ObjectId) => packNote(id, meId, {
|
_user.pinnedNotes = packNoteMany(_user.pinnedNoteIds, meId, {
|
||||||
detail: true
|
detail: true
|
||||||
})));
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (meId && !meId.equals(_user.id)) {
|
if (meId && !meId.equals(_user.id)) {
|
||||||
|
|||||||
22
src/remote/activitypub/kernel/add/index.ts
Normal file
22
src/remote/activitypub/kernel/add/index.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { IRemoteUser } from '../../../../models/user';
|
||||||
|
import { IAdd } from '../../type';
|
||||||
|
import { resolveNote } from '../../models/note';
|
||||||
|
import { addPinned } from '../../../../services/i/pin';
|
||||||
|
|
||||||
|
export default async (actor: IRemoteUser, activity: IAdd): Promise<void> => {
|
||||||
|
if ('actor' in activity && actor.uri !== activity.actor) {
|
||||||
|
throw new Error('invalid actor');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activity.target == null) {
|
||||||
|
throw new Error('target is null');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activity.target === actor.featured) {
|
||||||
|
const note = await resolveNote(activity.object);
|
||||||
|
await addPinned(actor, note._id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`unknown target: ${activity.target}`);
|
||||||
|
};
|
||||||
@@ -8,6 +8,8 @@ import like from './like';
|
|||||||
import announce from './announce';
|
import announce from './announce';
|
||||||
import accept from './accept';
|
import accept from './accept';
|
||||||
import reject from './reject';
|
import reject from './reject';
|
||||||
|
import add from './add';
|
||||||
|
import remove from './remove';
|
||||||
|
|
||||||
const self = async (actor: IRemoteUser, activity: Object): Promise<void> => {
|
const self = async (actor: IRemoteUser, activity: Object): Promise<void> => {
|
||||||
switch (activity.type) {
|
switch (activity.type) {
|
||||||
@@ -31,6 +33,14 @@ const self = async (actor: IRemoteUser, activity: Object): Promise<void> => {
|
|||||||
await reject(actor, activity);
|
await reject(actor, activity);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'Add':
|
||||||
|
await add(actor, activity).catch(err => console.log(err));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'Remove':
|
||||||
|
await remove(actor, activity).catch(err => console.log(err));
|
||||||
|
break;
|
||||||
|
|
||||||
case 'Announce':
|
case 'Announce':
|
||||||
await announce(actor, activity);
|
await announce(actor, activity);
|
||||||
break;
|
break;
|
||||||
|
|||||||
22
src/remote/activitypub/kernel/remove/index.ts
Normal file
22
src/remote/activitypub/kernel/remove/index.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { IRemoteUser } from '../../../../models/user';
|
||||||
|
import { IRemove } from '../../type';
|
||||||
|
import { resolveNote } from '../../models/note';
|
||||||
|
import { removePinned } from '../../../../services/i/pin';
|
||||||
|
|
||||||
|
export default async (actor: IRemoteUser, activity: IRemove): Promise<void> => {
|
||||||
|
if ('actor' in activity && actor.uri !== activity.actor) {
|
||||||
|
throw new Error('invalid actor');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activity.target == null) {
|
||||||
|
throw new Error('target is null');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activity.target === actor.featured) {
|
||||||
|
const note = await resolveNote(activity.object);
|
||||||
|
await removePinned(actor, note._id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`unknown target: ${activity.target}`);
|
||||||
|
};
|
||||||
@@ -56,7 +56,7 @@ export async function createNote(value: any, resolver?: Resolver, silent = false
|
|||||||
log(`Creating the Note: ${note.id}`);
|
log(`Creating the Note: ${note.id}`);
|
||||||
|
|
||||||
// 投稿者をフェッチ
|
// 投稿者をフェッチ
|
||||||
const actor = await resolvePerson(note.attributedTo) as IRemoteUser;
|
const actor = await resolvePerson(note.attributedTo, null, resolver) as IRemoteUser;
|
||||||
|
|
||||||
// 投稿者が凍結されていたらスキップ
|
// 投稿者が凍結されていたらスキップ
|
||||||
if (actor.isSuspended) {
|
if (actor.isSuspended) {
|
||||||
@@ -73,7 +73,7 @@ export async function createNote(value: any, resolver?: Resolver, silent = false
|
|||||||
visibility = 'followers';
|
visibility = 'followers';
|
||||||
} else {
|
} else {
|
||||||
visibility = 'specified';
|
visibility = 'specified';
|
||||||
visibleUsers = await Promise.all(note.to.map(uri => resolvePerson(uri)));
|
visibleUsers = await Promise.all(note.to.map(uri => resolvePerson(uri, null, resolver)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//#endergion
|
//#endergion
|
||||||
|
|||||||
@@ -3,15 +3,16 @@ import { toUnicode } from 'punycode';
|
|||||||
import * as debug from 'debug';
|
import * as debug from 'debug';
|
||||||
|
|
||||||
import config from '../../../config';
|
import config from '../../../config';
|
||||||
import User, { validateUsername, isValidName, IUser, IRemoteUser } from '../../../models/user';
|
import User, { validateUsername, isValidName, IUser, IRemoteUser, isRemoteUser } from '../../../models/user';
|
||||||
import Resolver from '../resolver';
|
import Resolver from '../resolver';
|
||||||
import { resolveImage } from './image';
|
import { resolveImage } from './image';
|
||||||
import { isCollectionOrOrderedCollection, IPerson } from '../type';
|
import { isCollectionOrOrderedCollection, isCollection, IPerson } from '../type';
|
||||||
import { IDriveFile } from '../../../models/drive-file';
|
import { IDriveFile } from '../../../models/drive-file';
|
||||||
import Meta from '../../../models/meta';
|
import Meta from '../../../models/meta';
|
||||||
import htmlToMFM from '../../../mfm/html-to-mfm';
|
import htmlToMFM from '../../../mfm/html-to-mfm';
|
||||||
import { updateUserStats } from '../../../services/update-chart';
|
import { updateUserStats } from '../../../services/update-chart';
|
||||||
import { URL } from 'url';
|
import { URL } from 'url';
|
||||||
|
import { resolveNote } from './note';
|
||||||
|
|
||||||
const log = debug('misskey:activitypub');
|
const log = debug('misskey:activitypub');
|
||||||
|
|
||||||
@@ -155,6 +156,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<IU
|
|||||||
},
|
},
|
||||||
inbox: person.inbox,
|
inbox: person.inbox,
|
||||||
sharedInbox: person.sharedInbox,
|
sharedInbox: person.sharedInbox,
|
||||||
|
featured: person.featured,
|
||||||
endpoints: person.endpoints,
|
endpoints: person.endpoints,
|
||||||
uri: person.id,
|
uri: person.id,
|
||||||
url: person.url,
|
url: person.url,
|
||||||
@@ -211,6 +213,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<IU
|
|||||||
user.bannerUrl = bannerUrl;
|
user.bannerUrl = bannerUrl;
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
|
await updateFeatured(user._id).catch(err => console.log(err));
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -282,6 +285,7 @@ export async function updatePerson(uri: string, resolver?: Resolver, hint?: obje
|
|||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
inbox: person.inbox,
|
inbox: person.inbox,
|
||||||
sharedInbox: person.sharedInbox,
|
sharedInbox: person.sharedInbox,
|
||||||
|
featured: person.featured,
|
||||||
avatarId: avatar ? avatar._id : null,
|
avatarId: avatar ? avatar._id : null,
|
||||||
bannerId: banner ? banner._id : null,
|
bannerId: banner ? banner._id : null,
|
||||||
avatarUrl: (avatar && avatar.metadata.thumbnailUrl) ? avatar.metadata.thumbnailUrl : (avatar && avatar.metadata.url) ? avatar.metadata.url : null,
|
avatarUrl: (avatar && avatar.metadata.thumbnailUrl) ? avatar.metadata.thumbnailUrl : (avatar && avatar.metadata.url) ? avatar.metadata.url : null,
|
||||||
@@ -303,6 +307,8 @@ export async function updatePerson(uri: string, resolver?: Resolver, hint?: obje
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await updateFeatured(exist._id).catch(err => console.log(err));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -311,7 +317,7 @@ export async function updatePerson(uri: string, resolver?: Resolver, hint?: obje
|
|||||||
* Misskeyに対象のPersonが登録されていればそれを返し、そうでなければ
|
* Misskeyに対象のPersonが登録されていればそれを返し、そうでなければ
|
||||||
* リモートサーバーからフェッチしてMisskeyに登録しそれを返します。
|
* リモートサーバーからフェッチしてMisskeyに登録しそれを返します。
|
||||||
*/
|
*/
|
||||||
export async function resolvePerson(uri: string, verifier?: string): Promise<IUser> {
|
export async function resolvePerson(uri: string, verifier?: string, resolver?: Resolver): Promise<IUser> {
|
||||||
if (typeof uri !== 'string') throw 'uri is not string';
|
if (typeof uri !== 'string') throw 'uri is not string';
|
||||||
|
|
||||||
//#region このサーバーに既に登録されていたらそれを返す
|
//#region このサーバーに既に登録されていたらそれを返す
|
||||||
@@ -323,5 +329,37 @@ export async function resolvePerson(uri: string, verifier?: string): Promise<IUs
|
|||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
// リモートサーバーからフェッチしてきて登録
|
// リモートサーバーからフェッチしてきて登録
|
||||||
return await createPerson(uri);
|
if (resolver == null) resolver = new Resolver();
|
||||||
|
return await createPerson(uri, resolver);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateFeatured(userId: mongo.ObjectID) {
|
||||||
|
const user = await User.findOne({ _id: userId });
|
||||||
|
if (!isRemoteUser(user)) return;
|
||||||
|
if (!user.featured) return;
|
||||||
|
|
||||||
|
log(`Updating the featured: ${user.uri}`);
|
||||||
|
|
||||||
|
const resolver = new Resolver();
|
||||||
|
|
||||||
|
// Resolve to (Ordered)Collection Object
|
||||||
|
const collection = await resolver.resolveCollection(user.featured);
|
||||||
|
if (!isCollectionOrOrderedCollection(collection)) throw new Error(`Object is not Collection or OrderedCollection`);
|
||||||
|
|
||||||
|
// Resolve to Object(may be Note) arrays
|
||||||
|
const unresolvedItems = isCollection(collection) ? collection.items : collection.orderedItems;
|
||||||
|
const items = await resolver.resolve(unresolvedItems);
|
||||||
|
if (!Array.isArray(items)) throw new Error(`Collection items is not an array`);
|
||||||
|
|
||||||
|
// Resolve and regist Notes
|
||||||
|
const featuredNotes = await Promise.all(items
|
||||||
|
.filter(item => item.type === 'Note')
|
||||||
|
.slice(0, 5)
|
||||||
|
.map(item => resolveNote(item, resolver)));
|
||||||
|
|
||||||
|
await User.update({ _id: user._id }, {
|
||||||
|
$set: {
|
||||||
|
pinnedNoteIds: featuredNotes.map(note => note._id)
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,11 +19,11 @@ export default class Resolver {
|
|||||||
|
|
||||||
switch (collection.type) {
|
switch (collection.type) {
|
||||||
case 'Collection':
|
case 'Collection':
|
||||||
collection.objects = collection.object.items;
|
collection.objects = collection.items;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'OrderedCollection':
|
case 'OrderedCollection':
|
||||||
collection.objects = collection.object.orderedItems;
|
collection.objects = collection.orderedItems;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -91,6 +91,14 @@ export interface IReject extends IActivity {
|
|||||||
type: 'Reject';
|
type: 'Reject';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IAdd extends IActivity {
|
||||||
|
type: 'Add';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IRemove extends IActivity {
|
||||||
|
type: 'Remove';
|
||||||
|
}
|
||||||
|
|
||||||
export interface ILike extends IActivity {
|
export interface ILike extends IActivity {
|
||||||
type: 'Like';
|
type: 'Like';
|
||||||
_misskey_reaction: string;
|
_misskey_reaction: string;
|
||||||
@@ -109,5 +117,7 @@ export type Object =
|
|||||||
IFollow |
|
IFollow |
|
||||||
IAccept |
|
IAccept |
|
||||||
IReject |
|
IReject |
|
||||||
|
IAdd |
|
||||||
|
IRemove |
|
||||||
ILike |
|
ILike |
|
||||||
IAnnounce;
|
IAnnounce;
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import $ from 'cafy'; import ID from '../../../../misc/cafy-id';
|
import $ from 'cafy'; import ID from '../../../../misc/cafy-id';
|
||||||
import User, { ILocalUser } from '../../../../models/user';
|
import { ILocalUser } from '../../../../models/user';
|
||||||
import Note from '../../../../models/note';
|
|
||||||
import { pack } from '../../../../models/user';
|
import { pack } from '../../../../models/user';
|
||||||
import { deliverPinnedChange } from '../../../../services/i/pin';
|
import { addPinned } from '../../../../services/i/pin';
|
||||||
import getParams from '../../get-params';
|
import getParams from '../../get-params';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
@@ -27,41 +26,18 @@ export default async (params: any, user: ILocalUser) => new Promise(async (res,
|
|||||||
const [ps, psErr] = getParams(meta, params);
|
const [ps, psErr] = getParams(meta, params);
|
||||||
if (psErr) return rej(psErr);
|
if (psErr) return rej(psErr);
|
||||||
|
|
||||||
// Fetch pinee
|
// Processing
|
||||||
const note = await Note.findOne({
|
try {
|
||||||
_id: ps.noteId,
|
await addPinned(user, ps.noteId);
|
||||||
userId: user._id
|
} catch (e) {
|
||||||
});
|
return rej(e.message);
|
||||||
|
|
||||||
if (note === null) {
|
|
||||||
return rej('note not found');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const pinnedNoteIds = user.pinnedNoteIds || [];
|
// Serialize
|
||||||
|
|
||||||
if (pinnedNoteIds.length > 5) {
|
|
||||||
return rej('cannot pin more notes');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pinnedNoteIds.some(id => id.equals(note._id))) {
|
|
||||||
return rej('already exists');
|
|
||||||
}
|
|
||||||
|
|
||||||
pinnedNoteIds.unshift(note._id);
|
|
||||||
|
|
||||||
await User.update(user._id, {
|
|
||||||
$set: {
|
|
||||||
pinnedNoteIds: pinnedNoteIds
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const iObj = await pack(user, user, {
|
const iObj = await pack(user, user, {
|
||||||
detail: true
|
detail: true
|
||||||
});
|
});
|
||||||
|
|
||||||
// Send response
|
// Send response
|
||||||
res(iObj);
|
res(iObj);
|
||||||
|
|
||||||
// Send Add to followers
|
|
||||||
deliverPinnedChange(user._id, note._id, true);
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import $ from 'cafy'; import ID from '../../../../misc/cafy-id';
|
import $ from 'cafy'; import ID from '../../../../misc/cafy-id';
|
||||||
import User, { ILocalUser } from '../../../../models/user';
|
import { ILocalUser } from '../../../../models/user';
|
||||||
import Note from '../../../../models/note';
|
|
||||||
import { pack } from '../../../../models/user';
|
import { pack } from '../../../../models/user';
|
||||||
import { deliverPinnedChange } from '../../../../services/i/pin';
|
import { removePinned } from '../../../../services/i/pin';
|
||||||
import getParams from '../../get-params';
|
import getParams from '../../get-params';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
@@ -27,31 +26,18 @@ export default async (params: any, user: ILocalUser) => new Promise(async (res,
|
|||||||
const [ps, psErr] = getParams(meta, params);
|
const [ps, psErr] = getParams(meta, params);
|
||||||
if (psErr) return rej(psErr);
|
if (psErr) return rej(psErr);
|
||||||
|
|
||||||
// Fetch unpinee
|
// Processing
|
||||||
const note = await Note.findOne({
|
try {
|
||||||
_id: ps.noteId,
|
await removePinned(user, ps.noteId);
|
||||||
userId: user._id
|
} catch (e) {
|
||||||
});
|
return rej(e.message);
|
||||||
|
|
||||||
if (note === null) {
|
|
||||||
return rej('note not found');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const pinnedNoteIds = (user.pinnedNoteIds || []).filter(id => !id.equals(note._id));
|
// Serialize
|
||||||
|
|
||||||
await User.update(user._id, {
|
|
||||||
$set: {
|
|
||||||
pinnedNoteIds: pinnedNoteIds
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const iObj = await pack(user, user, {
|
const iObj = await pack(user, user, {
|
||||||
detail: true
|
detail: true
|
||||||
});
|
});
|
||||||
|
|
||||||
// Send response
|
// Send response
|
||||||
res(iObj);
|
res(iObj);
|
||||||
|
|
||||||
// Send Remove to followers
|
|
||||||
deliverPinnedChange(user._id, note._id, false);
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -39,6 +39,15 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) =>
|
|||||||
recaptchaSitekey: config.recaptcha ? config.recaptcha.site_key : null,
|
recaptchaSitekey: config.recaptcha ? config.recaptcha.site_key : null,
|
||||||
swPublickey: config.sw ? config.sw.public_key : null,
|
swPublickey: config.sw ? config.sw.public_key : null,
|
||||||
hidedTags: (me && me.isAdmin) ? meta.hidedTags : undefined,
|
hidedTags: (me && me.isAdmin) ? meta.hidedTags : undefined,
|
||||||
bannerUrl: meta.bannerUrl
|
bannerUrl: meta.bannerUrl,
|
||||||
|
features: {
|
||||||
|
registration: !meta.disableRegistration,
|
||||||
|
localTimeLine: !meta.disableLocalTimeline,
|
||||||
|
elasticsearch: config.elasticsearch ? true : false,
|
||||||
|
recaptcha: config.recaptcha ? true : false,
|
||||||
|
objectStorage: config.drive && config.drive.storage === 'minio',
|
||||||
|
twitter: config.twitter ? true : false,
|
||||||
|
serviceWorker: config.sw ? true : false
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import $ from 'cafy'; import ID from '../../../misc/cafy-id';
|
import $ from 'cafy'; import ID from '../../../misc/cafy-id';
|
||||||
import Note, { pack } from '../../../models/note';
|
import Note, { packMany } from '../../../models/note';
|
||||||
import getParams from '../get-params';
|
import getParams from '../get-params';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
@@ -116,5 +116,5 @@ export default (params: any) => new Promise(async (res, rej) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Serialize
|
// Serialize
|
||||||
res(await Promise.all(notes.map(note => pack(note))));
|
res(await packMany(notes));
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import $ from 'cafy'; import ID from '../../../../misc/cafy-id';
|
import $ from 'cafy'; import ID from '../../../../misc/cafy-id';
|
||||||
import Note, { pack, INote } from '../../../../models/note';
|
import Note, { packMany, INote } from '../../../../models/note';
|
||||||
import { ILocalUser } from '../../../../models/user';
|
import { ILocalUser } from '../../../../models/user';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -52,5 +52,5 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) =
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Serialize
|
// Serialize
|
||||||
res(await Promise.all(conversation.map(note => pack(note, user))));
|
res(await packMany(conversation, user));
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import $ from 'cafy'; import ID from '../../../../misc/cafy-id';
|
import $ from 'cafy'; import ID from '../../../../misc/cafy-id';
|
||||||
import Note from '../../../../models/note';
|
import Note from '../../../../models/note';
|
||||||
import Mute from '../../../../models/mute';
|
import Mute from '../../../../models/mute';
|
||||||
import { pack } from '../../../../models/note';
|
import { packMany } from '../../../../models/note';
|
||||||
import { ILocalUser } from '../../../../models/user';
|
import { ILocalUser } from '../../../../models/user';
|
||||||
import getParams from '../../get-params';
|
import getParams from '../../get-params';
|
||||||
import { countIf } from '../../../../prelude/array';
|
import { countIf } from '../../../../prelude/array';
|
||||||
@@ -113,5 +113,5 @@ export default async (params: any, user: ILocalUser) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Serialize
|
// Serialize
|
||||||
return await Promise.all(timeline.map(note => pack(note, user)));
|
return await packMany(timeline, user);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import $ from 'cafy'; import ID from '../../../../misc/cafy-id';
|
|||||||
import Note from '../../../../models/note';
|
import Note from '../../../../models/note';
|
||||||
import Mute from '../../../../models/mute';
|
import Mute from '../../../../models/mute';
|
||||||
import { getFriends } from '../../common/get-friends';
|
import { getFriends } from '../../common/get-friends';
|
||||||
import { pack } from '../../../../models/note';
|
import { packMany } from '../../../../models/note';
|
||||||
import { ILocalUser } from '../../../../models/user';
|
import { ILocalUser } from '../../../../models/user';
|
||||||
import getParams from '../../get-params';
|
import getParams from '../../get-params';
|
||||||
import { countIf } from '../../../../prelude/array';
|
import { countIf } from '../../../../prelude/array';
|
||||||
@@ -240,5 +240,5 @@ export default async (params: any, user: ILocalUser) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Serialize
|
// Serialize
|
||||||
return await Promise.all(timeline.map(note => pack(note, user)));
|
return await packMany(timeline, user);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import $ from 'cafy'; import ID from '../../../../misc/cafy-id';
|
import $ from 'cafy'; import ID from '../../../../misc/cafy-id';
|
||||||
import Note from '../../../../models/note';
|
import Note from '../../../../models/note';
|
||||||
import Mute from '../../../../models/mute';
|
import Mute from '../../../../models/mute';
|
||||||
import { pack } from '../../../../models/note';
|
import { packMany } from '../../../../models/note';
|
||||||
import { ILocalUser } from '../../../../models/user';
|
import { ILocalUser } from '../../../../models/user';
|
||||||
import getParams from '../../get-params';
|
import getParams from '../../get-params';
|
||||||
import { countIf } from '../../../../prelude/array';
|
import { countIf } from '../../../../prelude/array';
|
||||||
@@ -141,5 +141,5 @@ export default async (params: any, user: ILocalUser) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Serialize
|
// Serialize
|
||||||
return await Promise.all(timeline.map(note => pack(note, user)));
|
return await packMany(timeline, user);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import $ from 'cafy'; import ID from '../../../../misc/cafy-id';
|
import $ from 'cafy'; import ID from '../../../../misc/cafy-id';
|
||||||
import Note from '../../../../models/note';
|
import Note from '../../../../models/note';
|
||||||
import { getFriendIds } from '../../common/get-friends';
|
import { getFriendIds } from '../../common/get-friends';
|
||||||
import { pack } from '../../../../models/note';
|
import { packMany } from '../../../../models/note';
|
||||||
import { ILocalUser } from '../../../../models/user';
|
import { ILocalUser } from '../../../../models/user';
|
||||||
import getParams from '../../get-params';
|
import getParams from '../../get-params';
|
||||||
import read from '../../../../services/note/read';
|
import read from '../../../../services/note/read';
|
||||||
@@ -89,5 +89,5 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) =
|
|||||||
mentions.forEach(note => read(user._id, note._id));
|
mentions.forEach(note => read(user._id, note._id));
|
||||||
|
|
||||||
// Serialize
|
// Serialize
|
||||||
res(await Promise.all(mentions.map(mention => pack(mention, user))));
|
res(await packMany(mentions, user));
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import $ from 'cafy'; import ID from '../../../../misc/cafy-id';
|
import $ from 'cafy'; import ID from '../../../../misc/cafy-id';
|
||||||
import Note, { pack } from '../../../../models/note';
|
import Note, { packMany } from '../../../../models/note';
|
||||||
import { ILocalUser } from '../../../../models/user';
|
import { ILocalUser } from '../../../../models/user';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -30,5 +30,5 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) =
|
|||||||
const ids = (note._replyIds || []).slice(offset, offset + limit);
|
const ids = (note._replyIds || []).slice(offset, offset + limit);
|
||||||
|
|
||||||
// Serialize
|
// Serialize
|
||||||
res(await Promise.all(ids.map(id => pack(id, user))));
|
res(await packMany(ids, user));
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import $ from 'cafy'; import ID from '../../../../misc/cafy-id';
|
import $ from 'cafy'; import ID from '../../../../misc/cafy-id';
|
||||||
import Note, { pack } from '../../../../models/note';
|
import Note, { packMany } from '../../../../models/note';
|
||||||
import { ILocalUser } from '../../../../models/user';
|
import { ILocalUser } from '../../../../models/user';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -62,6 +62,5 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) =
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Serialize
|
// Serialize
|
||||||
res(await Promise.all(renotes.map(async note =>
|
res(await packMany(renotes, user));
|
||||||
await pack(note, user))));
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import $ from 'cafy';
|
|||||||
import * as mongo from 'mongodb';
|
import * as mongo from 'mongodb';
|
||||||
import Note from '../../../../models/note';
|
import Note from '../../../../models/note';
|
||||||
import { ILocalUser } from '../../../../models/user';
|
import { ILocalUser } from '../../../../models/user';
|
||||||
import { pack } from '../../../../models/note';
|
import { packMany } from '../../../../models/note';
|
||||||
import es from '../../../../db/elasticsearch';
|
import es from '../../../../db/elasticsearch';
|
||||||
|
|
||||||
export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => {
|
export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => {
|
||||||
@@ -60,6 +60,6 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) =>
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
res(await Promise.all(notes.map(note => pack(note, me))));
|
res(await packMany(notes, me));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import Note from '../../../../models/note';
|
|||||||
import User, { ILocalUser } from '../../../../models/user';
|
import User, { ILocalUser } from '../../../../models/user';
|
||||||
import Mute from '../../../../models/mute';
|
import Mute from '../../../../models/mute';
|
||||||
import { getFriendIds } from '../../common/get-friends';
|
import { getFriendIds } from '../../common/get-friends';
|
||||||
import { pack } from '../../../../models/note';
|
import { packMany } from '../../../../models/note';
|
||||||
import getParams from '../../get-params';
|
import getParams from '../../get-params';
|
||||||
import { erase } from '../../../../prelude/array';
|
import { erase } from '../../../../prelude/array';
|
||||||
|
|
||||||
@@ -363,5 +363,5 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) =>
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Serialize
|
// Serialize
|
||||||
res(await Promise.all(notes.map(note => pack(note, me))));
|
res(await packMany(notes, me));
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import $ from 'cafy'; import ID from '../../../../misc/cafy-id';
|
|||||||
import Note from '../../../../models/note';
|
import Note from '../../../../models/note';
|
||||||
import Mute from '../../../../models/mute';
|
import Mute from '../../../../models/mute';
|
||||||
import { getFriends } from '../../common/get-friends';
|
import { getFriends } from '../../common/get-friends';
|
||||||
import { pack } from '../../../../models/note';
|
import { packMany } from '../../../../models/note';
|
||||||
import { ILocalUser } from '../../../../models/user';
|
import { ILocalUser } from '../../../../models/user';
|
||||||
import getParams from '../../get-params';
|
import getParams from '../../get-params';
|
||||||
import { countIf } from '../../../../prelude/array';
|
import { countIf } from '../../../../prelude/array';
|
||||||
@@ -237,5 +237,5 @@ export default async (params: any, user: ILocalUser) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Serialize
|
// Serialize
|
||||||
return await Promise.all(timeline.map(note => pack(note, user)));
|
return await packMany(timeline, user);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import $ from 'cafy'; import ID from '../../../../misc/cafy-id';
|
import $ from 'cafy'; import ID from '../../../../misc/cafy-id';
|
||||||
import Note from '../../../../models/note';
|
import Note from '../../../../models/note';
|
||||||
import Mute from '../../../../models/mute';
|
import Mute from '../../../../models/mute';
|
||||||
import { pack } from '../../../../models/note';
|
import { packMany } from '../../../../models/note';
|
||||||
import UserList from '../../../../models/user-list';
|
import UserList from '../../../../models/user-list';
|
||||||
import { ILocalUser } from '../../../../models/user';
|
import { ILocalUser } from '../../../../models/user';
|
||||||
import getParams from '../../get-params';
|
import getParams from '../../get-params';
|
||||||
@@ -242,5 +242,5 @@ export default async (params: any, user: ILocalUser) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Serialize
|
// Serialize
|
||||||
return await Promise.all(timeline.map(note => pack(note, user)));
|
return await packMany(timeline, user);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import $ from 'cafy'; import ID from '../../../../misc/cafy-id';
|
import $ from 'cafy'; import ID from '../../../../misc/cafy-id';
|
||||||
import getHostLower from '../../common/get-host-lower';
|
import getHostLower from '../../common/get-host-lower';
|
||||||
import Note, { pack } from '../../../../models/note';
|
import Note, { packMany } from '../../../../models/note';
|
||||||
import User, { ILocalUser } from '../../../../models/user';
|
import User, { ILocalUser } from '../../../../models/user';
|
||||||
import getParams from '../../get-params';
|
import getParams from '../../get-params';
|
||||||
import { countIf } from '../../../../prelude/array';
|
import { countIf } from '../../../../prelude/array';
|
||||||
@@ -181,5 +181,5 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) =>
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Serialize
|
// Serialize
|
||||||
res(await Promise.all(notes.map(note => pack(note, me))));
|
res(await packMany(notes, me));
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,12 +1,83 @@
|
|||||||
import config from '../../config';
|
import config from '../../config';
|
||||||
import * as mongo from 'mongodb';
|
import * as mongo from 'mongodb';
|
||||||
import User, { isLocalUser, isRemoteUser, ILocalUser } from '../../models/user';
|
import User, { isLocalUser, isRemoteUser, ILocalUser, IUser } from '../../models/user';
|
||||||
|
import Note from '../../models/note';
|
||||||
import Following from '../../models/following';
|
import Following from '../../models/following';
|
||||||
import renderAdd from '../../remote/activitypub/renderer/add';
|
import renderAdd from '../../remote/activitypub/renderer/add';
|
||||||
import renderRemove from '../../remote/activitypub/renderer/remove';
|
import renderRemove from '../../remote/activitypub/renderer/remove';
|
||||||
import packAp from '../../remote/activitypub/renderer';
|
import packAp from '../../remote/activitypub/renderer';
|
||||||
import { deliver } from '../../queue';
|
import { deliver } from '../../queue';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 指定した投稿をピン留めします
|
||||||
|
* @param user
|
||||||
|
* @param noteId
|
||||||
|
*/
|
||||||
|
export async function addPinned(user: IUser, noteId: mongo.ObjectID) {
|
||||||
|
// Fetch pinee
|
||||||
|
const note = await Note.findOne({
|
||||||
|
_id: noteId,
|
||||||
|
userId: user._id
|
||||||
|
});
|
||||||
|
|
||||||
|
if (note === null) {
|
||||||
|
throw new Error('note not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
const pinnedNoteIds = user.pinnedNoteIds || [];
|
||||||
|
|
||||||
|
if (pinnedNoteIds.length >= 5) {
|
||||||
|
throw new Error('cannot pin more notes');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pinnedNoteIds.some(id => id.equals(note._id))) {
|
||||||
|
throw new Error('already exists');
|
||||||
|
}
|
||||||
|
|
||||||
|
pinnedNoteIds.unshift(note._id);
|
||||||
|
|
||||||
|
await User.update(user._id, {
|
||||||
|
$set: {
|
||||||
|
pinnedNoteIds: pinnedNoteIds
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Deliver to remote followers
|
||||||
|
if (isLocalUser(user)) {
|
||||||
|
deliverPinnedChange(user._id, note._id, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 指定した投稿のピン留めを解除します
|
||||||
|
* @param user
|
||||||
|
* @param noteId
|
||||||
|
*/
|
||||||
|
export async function removePinned(user: IUser, noteId: mongo.ObjectID) {
|
||||||
|
// Fetch unpinee
|
||||||
|
const note = await Note.findOne({
|
||||||
|
_id: noteId,
|
||||||
|
userId: user._id
|
||||||
|
});
|
||||||
|
|
||||||
|
if (note === null) {
|
||||||
|
throw new Error('note not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
const pinnedNoteIds = (user.pinnedNoteIds || []).filter(id => !id.equals(note._id));
|
||||||
|
|
||||||
|
await User.update(user._id, {
|
||||||
|
$set: {
|
||||||
|
pinnedNoteIds: pinnedNoteIds
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Deliver to remote followers
|
||||||
|
if (isLocalUser(user)) {
|
||||||
|
deliverPinnedChange(user._id, noteId, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function deliverPinnedChange(userId: mongo.ObjectID, noteId: mongo.ObjectID, isAddition: boolean) {
|
export async function deliverPinnedChange(userId: mongo.ObjectID, noteId: mongo.ObjectID, isAddition: boolean) {
|
||||||
const user = await User.findOne({
|
const user = await User.findOne({
|
||||||
_id: userId
|
_id: userId
|
||||||
|
|||||||
51
test/mfm.ts
51
test/mfm.ts
@@ -54,20 +54,45 @@ describe('Text', () => {
|
|||||||
], tokens2);
|
], tokens2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('mention', () => {
|
describe('mention', () => {
|
||||||
const tokens = analyze('@himawari お腹ペコい');
|
it('local', () => {
|
||||||
assert.deepEqual([
|
const tokens = analyze('@himawari お腹ペコい');
|
||||||
{ type: 'mention', content: '@himawari', username: 'himawari', host: null },
|
assert.deepEqual([
|
||||||
{ type: 'text', content: ' お腹ペコい' }
|
{ type: 'mention', content: '@himawari', username: 'himawari', host: null },
|
||||||
], tokens);
|
{ type: 'text', content: ' お腹ペコい' }
|
||||||
});
|
], tokens);
|
||||||
|
});
|
||||||
|
|
||||||
it('remote mention', () => {
|
it('remote', () => {
|
||||||
const tokens = analyze('@hima_sub@namori.net お腹ペコい');
|
const tokens = analyze('@hima_sub@namori.net お腹ペコい');
|
||||||
assert.deepEqual([
|
assert.deepEqual([
|
||||||
{ type: 'mention', content: '@hima_sub@namori.net', username: 'hima_sub', host: 'namori.net' },
|
{ type: 'mention', content: '@hima_sub@namori.net', username: 'hima_sub', host: 'namori.net' },
|
||||||
{ type: 'text', content: ' お腹ペコい' }
|
{ type: 'text', content: ' お腹ペコい' }
|
||||||
], tokens);
|
], tokens);
|
||||||
|
});
|
||||||
|
/*
|
||||||
|
it('ignore', () => {
|
||||||
|
const tokens = analyze('idolm@ster');
|
||||||
|
assert.deepEqual([
|
||||||
|
{ type: 'text', content: 'idolm@ster' }
|
||||||
|
], tokens);
|
||||||
|
|
||||||
|
const tokens2 = analyze('@a\n@b\n@c');
|
||||||
|
assert.deepEqual([
|
||||||
|
{ type: 'mention', content: '@a', username: 'a', host: null },
|
||||||
|
{ type: 'text', content: '\n' },
|
||||||
|
{ type: 'mention', content: '@b', username: 'b', host: null },
|
||||||
|
{ type: 'text', content: '\n' },
|
||||||
|
{ type: 'mention', content: '@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 }
|
||||||
|
], tokens3);
|
||||||
|
});
|
||||||
|
*/
|
||||||
});
|
});
|
||||||
|
|
||||||
it('hashtag', () => {
|
it('hashtag', () => {
|
||||||
|
|||||||
@@ -196,6 +196,9 @@ module.exports = {
|
|||||||
}, {
|
}, {
|
||||||
test: /\.(eot|woff|woff2|svg|ttf)([\?]?.*)$/,
|
test: /\.(eot|woff|woff2|svg|ttf)([\?]?.*)$/,
|
||||||
loader: 'url-loader'
|
loader: 'url-loader'
|
||||||
|
}, {
|
||||||
|
test: /\.json5$/,
|
||||||
|
loader: 'json5-loader'
|
||||||
}, {
|
}, {
|
||||||
test: /\.ts$/,
|
test: /\.ts$/,
|
||||||
exclude: /node_modules/,
|
exclude: /node_modules/,
|
||||||
|
|||||||
Reference in New Issue
Block a user