Compare commits
208 Commits
Author | SHA1 | Date | |
---|---|---|---|
116faf26e6 | |||
2582b8d132 | |||
63f7941073 | |||
676f026085 | |||
2d6b20d34b | |||
99073b56df | |||
5dce81c0db | |||
be82d845a4 | |||
f49ccd0cd3 | |||
69d83f535d | |||
c7988fb6f5 | |||
3961fd08c9 | |||
e3faf64061 | |||
ed83993e15 | |||
0f8847bb74 | |||
a72cfa7535 | |||
514b74a19d | |||
a2c124306f | |||
273f67e268 | |||
2870a7e463 | |||
65e5cfa68e | |||
10e59957d1 | |||
4f74373df3 | |||
2d414bbf86 | |||
a199969b81 | |||
3aef5e6748 | |||
2b536a7443 | |||
20fe68de05 | |||
c7684b59de | |||
a7237d157a | |||
35f91fa280 | |||
299ac32225 | |||
a038738d72 | |||
2b0a919fb5 | |||
946c706913 | |||
89b5d976ee | |||
6f679bb6b4 | |||
db4e7b0e16 | |||
9ca942490d | |||
ebcf249c8b | |||
939c487503 | |||
981a8b267e | |||
9531da80a0 | |||
e1109b168c | |||
b7c70039aa | |||
17b6f6cf2a | |||
dd88483ba4 | |||
0ff27f65b3 | |||
b1655740df | |||
6d562aece1 | |||
2182c3372b | |||
d3331bfe82 | |||
cfc4a2e8b4 | |||
36c41c8eb3 | |||
d255157e6e | |||
c12e07277d | |||
06b4fb5095 | |||
8fafdcb428 | |||
537a606bb6 | |||
3dc7a4463c | |||
fd6ff05b60 | |||
1a159e41b8 | |||
23533cdd16 | |||
2f598b8fa1 | |||
bca349fec1 | |||
719fac6480 | |||
1012b2b2c7 | |||
5149be4b1b | |||
d12deeb0d8 | |||
9df81d1939 | |||
3be0079868 | |||
9b253ccb3a | |||
dded76099c | |||
41a7ec7d3d | |||
168c773ba0 | |||
9abed92196 | |||
4a75e3602a | |||
1a689f6641 | |||
08d7ae11d6 | |||
9535759787 | |||
f8fc31f14a | |||
b74bf97761 | |||
a090b908bd | |||
3046821026 | |||
e94c73efe2 | |||
e85f9f4aa5 | |||
ad67886f96 | |||
5df0e102fd | |||
a04f0e3545 | |||
dff9c7ac48 | |||
3a80b59986 | |||
07560a4fdd | |||
7edca21c05 | |||
34105abd9d | |||
1bbca48a0b | |||
21f6a86772 | |||
6559197c55 | |||
05f9ad11bb | |||
f06d586680 | |||
4f45e8125c | |||
cc2843503d | |||
324a974dec | |||
4d4ffd70ac | |||
bf98a11b65 | |||
1117ce4b54 | |||
57e93b9b4e | |||
9e4b061ed0 | |||
1067bef7d6 | |||
8bff529acd | |||
4b08677839 | |||
70997cb551 | |||
bf0ef17e23 | |||
7dae5107f8 | |||
2dea88a147 | |||
f44c2a3e4f | |||
1fad3cbaae | |||
40d2e3e97c | |||
2efabe612e | |||
3107cbd6b9 | |||
3a061ed1c3 | |||
d4f0e6461a | |||
3285687652 | |||
51c53f64d0 | |||
1d582f5ad2 | |||
8a62748e39 | |||
b9290a021b | |||
129ce93868 | |||
5f41e5d6d0 | |||
c706d030ea | |||
34716a34f8 | |||
6db3d6dfb6 | |||
38e2853dcf | |||
ba5a540ca3 | |||
fb1e05c2e9 | |||
aba84612a7 | |||
9bebbf4e03 | |||
e41b3f9c10 | |||
890dc05022 | |||
375f86ec82 | |||
db248a69c8 | |||
5951288159 | |||
17b92c9db2 | |||
962d1060d9 | |||
cb2640d961 | |||
29aeb0f082 | |||
990347f856 | |||
7a406c1f13 | |||
9432af2ab5 | |||
136b13e7ca | |||
ba1c823fb1 | |||
f1301a4780 | |||
7957cd4963 | |||
ee6590d03f | |||
f2a1238b20 | |||
e9ce84f368 | |||
52e84decb4 | |||
e893002bb6 | |||
3792103e80 | |||
7a861c9481 | |||
942b565224 | |||
88390d7a9a | |||
966fc4c5d7 | |||
84dbdf1196 | |||
211e7f90d9 | |||
e54b8e3fb2 | |||
836c89ed33 | |||
c7c73afea1 | |||
7b9ca63b1e | |||
c464183329 | |||
389f420cad | |||
6b2888383c | |||
3c38a867b4 | |||
7f5a69f4d8 | |||
bb9ab31d5e | |||
9def80af8a | |||
9256bcdbe4 | |||
9b775022bc | |||
32371ed2bd | |||
8b98c08a81 | |||
7cf72f7447 | |||
913385b10d | |||
7306468d08 | |||
11e5667778 | |||
38cc02e261 | |||
d52cf46cc1 | |||
c6110dd996 | |||
51d8de2c38 | |||
4455a1aa9d | |||
040d395ddb | |||
8296cac636 | |||
3eafe8b87d | |||
c01512e261 | |||
e5cf3aecd5 | |||
a8f90b41b7 | |||
b79169b975 | |||
437d52e2ed | |||
1329721440 | |||
6affb4fe97 | |||
15395686aa | |||
5cf1956135 | |||
fff307d4bb | |||
2b7782ba03 | |||
96d961ee80 | |||
9f064d76d9 | |||
2e39106c4b | |||
04650464f3 | |||
3bc7e1e35c | |||
7019ddbfc7 |
@ -60,11 +60,6 @@ mongodb:
|
|||||||
user: example-misskey-user
|
user: example-misskey-user
|
||||||
pass: example-misskey-pass
|
pass: example-misskey-pass
|
||||||
|
|
||||||
redis:
|
|
||||||
host: localhost
|
|
||||||
port: 6379
|
|
||||||
pass: example-pass
|
|
||||||
|
|
||||||
# Drive capacity of a local user (MB)
|
# Drive capacity of a local user (MB)
|
||||||
localDriveCapacityMb: 256
|
localDriveCapacityMb: 256
|
||||||
|
|
||||||
@ -122,40 +117,50 @@ drive:
|
|||||||
# Below settings are optional
|
# Below settings are optional
|
||||||
#
|
#
|
||||||
|
|
||||||
|
# Redis
|
||||||
|
#redis:
|
||||||
|
# host: localhost
|
||||||
|
# port: 6379
|
||||||
|
# pass: example-pass
|
||||||
|
|
||||||
# Elasticsearch
|
# Elasticsearch
|
||||||
# elasticsearch:
|
#elasticsearch:
|
||||||
# host: localhost
|
# host: localhost
|
||||||
# port: 9200
|
# port: 9200
|
||||||
# pass: null
|
# pass: null
|
||||||
|
|
||||||
# reCAPTCHA
|
# reCAPTCHA
|
||||||
# recaptcha:
|
#recaptcha:
|
||||||
# site_key: example-site-key
|
# site_key: example-site-key
|
||||||
# secret_key: example-secret-key
|
# secret_key: example-secret-key
|
||||||
|
|
||||||
# ServiceWorker
|
# ServiceWorker
|
||||||
# sw:
|
#sw:
|
||||||
# # Public key of VAPID
|
# # Public key of VAPID
|
||||||
# public_key: example-sw-public-key
|
# public_key: example-sw-public-key
|
||||||
|
#
|
||||||
# # Private key of VAPID
|
# # Private key of VAPID
|
||||||
# private_key: example-sw-private-key
|
# private_key: example-sw-private-key
|
||||||
|
|
||||||
# google_maps_api_key: example-google-maps-api-key
|
|
||||||
|
|
||||||
# Twitter integration
|
# Twitter integration
|
||||||
# You need to set the oauth callback url as : https://<your-misskey-instance>/api/tw/cb
|
# You need to set the oauth callback url as : https://<your-misskey-instance>/api/tw/cb
|
||||||
# twitter:
|
#twitter:
|
||||||
# consumer_key: example-twitter-consumer-key
|
# consumer_key: example-twitter-consumer-key
|
||||||
# consumer_secret: example-twitter-consumer-secret-key
|
# consumer_secret: example-twitter-consumer-secret-key
|
||||||
|
|
||||||
# Ghost
|
# Ghost
|
||||||
# Ghost account is an account used for the purpose of delegating
|
# Ghost account is an account used for the purpose of delegating
|
||||||
# followers when putting users in the list.
|
# followers when putting users in the list.
|
||||||
# ghost: user-id-of-your-ghost-account
|
#ghost: user-id-of-your-ghost-account
|
||||||
|
|
||||||
# Clustering
|
# Clustering
|
||||||
# clusterLimit: 1
|
#clusterLimit: 1
|
||||||
|
|
||||||
# Summaly proxy
|
# Summaly proxy
|
||||||
# summalyProxy: "http://example.com"
|
#summalyProxy: "http://example.com"
|
||||||
|
|
||||||
|
# User recommendation
|
||||||
|
#user_recommendation:
|
||||||
|
# external: true
|
||||||
|
# engine: http://vinayaka.distsn.org/cgi-bin/vinayaka-user-match-misskey-api.cgi?{{host}}+{{user}}+{{limit}}+{{offset}}
|
||||||
|
# timeout: 300000
|
||||||
|
13
.config/mongo_initdb_example.js
Normal file
13
.config/mongo_initdb_example.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
var user = {
|
||||||
|
user: 'example-misskey-user',
|
||||||
|
pwd: 'example-misskey-pass',
|
||||||
|
roles: [
|
||||||
|
{
|
||||||
|
role: 'readWrite',
|
||||||
|
db: 'misskey'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
db.createUser(user);
|
||||||
|
|
12
.dockerignore
Executable file
12
.dockerignore
Executable file
@ -0,0 +1,12 @@
|
|||||||
|
.autogen
|
||||||
|
.git
|
||||||
|
.github
|
||||||
|
.travis
|
||||||
|
.vscode
|
||||||
|
Dockerfile
|
||||||
|
build/
|
||||||
|
docker-compose.yml
|
||||||
|
node_modules/
|
||||||
|
mongo/
|
||||||
|
redis/
|
||||||
|
elasticsearch/
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,5 +1,6 @@
|
|||||||
/.config/*
|
/.config/*
|
||||||
!/.config/example.yml
|
!/.config/example.yml
|
||||||
|
!/.config/mongo_initdb_example.js
|
||||||
/.vscode
|
/.vscode
|
||||||
/node_modules
|
/node_modules
|
||||||
/build
|
/build
|
||||||
@ -12,3 +13,6 @@ npm-debug.log
|
|||||||
run.bat
|
run.bat
|
||||||
api-docs.json
|
api-docs.json
|
||||||
*.log
|
*.log
|
||||||
|
/redis
|
||||||
|
/mongo
|
||||||
|
/elasticsearch
|
||||||
|
28
Dockerfile
Normal file
28
Dockerfile
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
FROM alpine:latest AS base
|
||||||
|
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
|
||||||
|
RUN apk add --no-cache nodejs nodejs-npm
|
||||||
|
RUN apk add vips fftw --update-cache --repository https://dl-3.alpinelinux.org/alpine/edge/testing/
|
||||||
|
WORKDIR /misskey
|
||||||
|
COPY . ./
|
||||||
|
|
||||||
|
FROM base AS builder
|
||||||
|
|
||||||
|
RUN apk add --no-cache gcc g++ python autoconf automake file make nasm
|
||||||
|
RUN apk add vips-dev fftw-dev --update-cache --repository https://dl-3.alpinelinux.org/alpine/edge/testing/
|
||||||
|
RUN npm install \
|
||||||
|
&& npm install -g node-gyp \
|
||||||
|
&& node-gyp configure \
|
||||||
|
&& node-gyp build \
|
||||||
|
&& npm run build
|
||||||
|
|
||||||
|
FROM base AS runner
|
||||||
|
|
||||||
|
COPY --from=builder /misskey/built ./built
|
||||||
|
COPY --from=builder /misskey/node_modules ./node_modules
|
||||||
|
|
||||||
|
RUN apk add --no-cache tini
|
||||||
|
ENTRYPOINT ["/sbin/tini", "--"]
|
||||||
|
|
||||||
|
CMD ["npm", "start"]
|
@ -71,6 +71,7 @@ Please see [Contribution guide](./CONTRIBUTING.md).
|
|||||||
----------------------------------------------------------------
|
----------------------------------------------------------------
|
||||||
<!-- PATREON_START -->
|
<!-- PATREON_START -->
|
||||||
<table><tr>
|
<table><tr>
|
||||||
|
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/619786/32cf01444db24e578cd1982c197f6fc6/1?token-time=2145916800&token-hash=CXe9AqlZy9AsYfiWd3OBYVOzvODoN47Litz0Tu4BFpU%3D" alt="Gargron"></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/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/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/12913507/f7181eacafe8469a93033d85f5969c29/2?token-time=2145916800&token-hash=mgPdX9TqZxEg4TTPuc477dxhIgYk9246qafjWZEqZ7g%3D" alt="Melilot"></td>
|
||||||
@ -80,6 +81,7 @@ Please see [Contribution guide](./CONTRIBUTING.md).
|
|||||||
<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/mastodon">Gargron</a></td>
|
||||||
<td><a href="https://www.patreon.com/negao">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>
|
||||||
|
52
docker-compose.yml
Normal file
52
docker-compose.yml
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
version: "3"
|
||||||
|
|
||||||
|
services:
|
||||||
|
web:
|
||||||
|
build: .
|
||||||
|
restart: always
|
||||||
|
links:
|
||||||
|
- mongo
|
||||||
|
- redis
|
||||||
|
# - es
|
||||||
|
ports:
|
||||||
|
- "127.0.0.1:3000:3000"
|
||||||
|
networks:
|
||||||
|
- internal_network
|
||||||
|
- external_network
|
||||||
|
|
||||||
|
redis:
|
||||||
|
restart: always
|
||||||
|
image: redis:4.0-alpine
|
||||||
|
networks:
|
||||||
|
- internal_network
|
||||||
|
### Uncomment to enable Redis persistance
|
||||||
|
# volumes:
|
||||||
|
# - ./redis:/data
|
||||||
|
|
||||||
|
mongo:
|
||||||
|
restart: always
|
||||||
|
image: mongo:4.1-bionic
|
||||||
|
networks:
|
||||||
|
- internal_network
|
||||||
|
environment:
|
||||||
|
MONGO_INITDB_DATABASE: "misskey"
|
||||||
|
volumes:
|
||||||
|
- ./.config/mongo_initdb.js:/docker-entrypoint-initdb.d/mongo_initdb.js:ro
|
||||||
|
### Uncomment to enable MongoDB persistance
|
||||||
|
# - ./mongo:/data
|
||||||
|
|
||||||
|
# es:
|
||||||
|
# restart: always
|
||||||
|
# image: docker.elastic.co/elasticsearch/elasticsearch-oss:6.4.2
|
||||||
|
# environment:
|
||||||
|
# - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
|
||||||
|
# networks:
|
||||||
|
# - internal_network
|
||||||
|
#### Uncomment to enable ES persistence
|
||||||
|
## volumes:
|
||||||
|
## - ./elasticsearch:/usr/share/elasticsearch/data
|
||||||
|
|
||||||
|
networks:
|
||||||
|
internal_network:
|
||||||
|
internal: true
|
||||||
|
external_network:
|
47
docs/docker.en.md
Normal file
47
docs/docker.en.md
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
Docker Guide
|
||||||
|
================================================================
|
||||||
|
|
||||||
|
This guide describes how to install and setup Misskey with Docker.
|
||||||
|
|
||||||
|
[Japanese version also available - 日本語版もあります](./docker.ja.md)
|
||||||
|
|
||||||
|
----------------------------------------------------------------
|
||||||
|
|
||||||
|
*1.* Make configuration files
|
||||||
|
----------------------------------------------------------------
|
||||||
|
1. `cp .config/example.yml .config/default.yml` Copy the `.config/example.yml` and rename it to `default.yml`.
|
||||||
|
2. `cp .config/mongo_initdb_example.js .config/mongo_initdb.js` Copy the `.config/mongo_initdb_example.js` and rename it to `mongo_initdb.js`.
|
||||||
|
2. Edit `default.yml` and `mongo_initdb.js`.
|
||||||
|
|
||||||
|
*2.* Configure Docker
|
||||||
|
----------------------------------------------------------------
|
||||||
|
Edit `docker-compose.yml`.
|
||||||
|
|
||||||
|
*3.* Build Misskey
|
||||||
|
----------------------------------------------------------------
|
||||||
|
Build misskey with the following:
|
||||||
|
|
||||||
|
`docker-compose build`
|
||||||
|
|
||||||
|
*4.* That is it.
|
||||||
|
----------------------------------------------------------------
|
||||||
|
Well done! Now, you have an environment that run to Misskey.
|
||||||
|
|
||||||
|
### Launch normally
|
||||||
|
Just `docker-compose up -d`. GLHF!
|
||||||
|
|
||||||
|
### Way to Update to latest version of your Misskey
|
||||||
|
1. `git fetch`
|
||||||
|
2. `git stash`
|
||||||
|
3. `git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)`
|
||||||
|
4. `git stash pop`
|
||||||
|
5. `docker-compose build`
|
||||||
|
6. Check [ChangeLog](../CHANGELOG.md) for migration information
|
||||||
|
7. `docker-compose stop && docker-compose up -d`
|
||||||
|
|
||||||
|
### Way to execute cli command:
|
||||||
|
`docker-compose run --rm web node cli/mark-admin @example`
|
||||||
|
|
||||||
|
----------------------------------------------------------------
|
||||||
|
|
||||||
|
If you have any questions or troubles, feel free to contact us!
|
48
docs/docker.ja.md
Normal file
48
docs/docker.ja.md
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
Dockerを使ったMisskey構築方法
|
||||||
|
================================================================
|
||||||
|
|
||||||
|
このガイドはDockerを使ったMisskeyセットアップ方法について解説します。
|
||||||
|
|
||||||
|
[英語版もあります - English version also available](./docker.en.md)
|
||||||
|
|
||||||
|
----------------------------------------------------------------
|
||||||
|
|
||||||
|
*1.* 設定ファイルを作成する
|
||||||
|
----------------------------------------------------------------
|
||||||
|
1. `cp .config/example.yml .config/default.yml` `.config/example.yml`をコピーし名前を`default.yml`にする
|
||||||
|
2. `cp .config/mongo_initdb_example.js .config/mongo_initdb.js` `.config/mongo_initdb_example.js`をコピーし名前を`mongo_initdb.js`にする
|
||||||
|
3. `default.yml`と`mongo_initdb.js`を編集する
|
||||||
|
|
||||||
|
*2.* Dockerの設定
|
||||||
|
----------------------------------------------------------------
|
||||||
|
`docker-compose.yml`を編集してください。
|
||||||
|
|
||||||
|
*3.* Misskeyのビルド
|
||||||
|
----------------------------------------------------------------
|
||||||
|
次のコマンドでMisskeyをビルドしてください:
|
||||||
|
|
||||||
|
`docker-compose build`
|
||||||
|
|
||||||
|
*4.* 以上です!
|
||||||
|
----------------------------------------------------------------
|
||||||
|
お疲れ様でした。これでMisskeyを動かす準備は整いました。
|
||||||
|
|
||||||
|
### 通常起動
|
||||||
|
`docker-compose up -d`するだけです。GLHF!
|
||||||
|
|
||||||
|
### Misskeyを最新バージョンにアップデートする方法:
|
||||||
|
1. `git fetch`
|
||||||
|
2. `git stash`
|
||||||
|
3. `git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)`
|
||||||
|
4. `git stash pop`
|
||||||
|
5. `docker-compose build`
|
||||||
|
6. [ChangeLog](../CHANGELOG.md)でマイグレーション情報を確認する
|
||||||
|
7. `docker-compose stop && docker-compose up -d`
|
||||||
|
|
||||||
|
### cliコマンドを実行する方法:
|
||||||
|
|
||||||
|
`docker-compose run --rm web node cli/mark-admin @example`
|
||||||
|
|
||||||
|
----------------------------------------------------------------
|
||||||
|
|
||||||
|
なにかお困りのことがありましたらお気軽にご連絡ください。
|
@ -24,12 +24,12 @@ Please install and setup these softwares:
|
|||||||
#### Dependencies :package:
|
#### Dependencies :package:
|
||||||
* **[Node.js](https://nodejs.org/en/)**
|
* **[Node.js](https://nodejs.org/en/)**
|
||||||
* **[MongoDB](https://www.mongodb.com/)** >= 3.6
|
* **[MongoDB](https://www.mongodb.com/)** >= 3.6
|
||||||
* **[Redis](https://redis.io/)**
|
|
||||||
|
|
||||||
##### Optional
|
##### Optional
|
||||||
|
* [Redis](https://redis.io/)
|
||||||
|
* Redis is optional, but we strongly recommended to install it
|
||||||
* [Elasticsearch](https://www.elastic.co/) - used to provide searching feature instead of MongoDB
|
* [Elasticsearch](https://www.elastic.co/) - used to provide searching feature instead of MongoDB
|
||||||
|
|
||||||
|
|
||||||
*3.* Setup MongoDB
|
*3.* Setup MongoDB
|
||||||
----------------------------------------------------------------
|
----------------------------------------------------------------
|
||||||
In root :
|
In root :
|
||||||
|
@ -24,10 +24,17 @@ adduser --disabled-password --disabled-login misskey
|
|||||||
#### 依存関係 :package:
|
#### 依存関係 :package:
|
||||||
* **[Node.js](https://nodejs.org/en/)**
|
* **[Node.js](https://nodejs.org/en/)**
|
||||||
* **[MongoDB](https://www.mongodb.com/)** (3.6以上)
|
* **[MongoDB](https://www.mongodb.com/)** (3.6以上)
|
||||||
* **[Redis](https://redis.io/)**
|
|
||||||
|
|
||||||
##### オプション
|
##### オプション
|
||||||
* [Elasticsearch](https://www.elastic.co/) - 検索機能を向上させるために用います。
|
* [Redis](https://redis.io/)
|
||||||
|
* Redisはオプションですが、インストールすることを強く推奨します。
|
||||||
|
* インストールしなくていいのは、あなたのインスタンスが自分専用のときだけとお考えください。
|
||||||
|
* 具体的には、Redisをインストールしないと、次の事が出来なくなります:
|
||||||
|
* Misskeyプロセスを複数起動しての負荷分散
|
||||||
|
* レートリミット
|
||||||
|
* Twitter連携
|
||||||
|
* [Elasticsearch](https://www.elastic.co/)
|
||||||
|
* 検索機能を有効にするためにはインストールが必要です。
|
||||||
|
|
||||||
*3.* MongoDBの設定
|
*3.* MongoDBの設定
|
||||||
----------------------------------------------------------------
|
----------------------------------------------------------------
|
||||||
|
@ -286,6 +286,9 @@ common/views/components/theme.vue:
|
|||||||
invalid-theme: "テーマが正しくありません。"
|
invalid-theme: "テーマが正しくありません。"
|
||||||
already-installed: "既にそのテーマはインストールされています。"
|
already-installed: "既にそのテーマはインストールされています。"
|
||||||
saved: "保存しました"
|
saved: "保存しました"
|
||||||
|
manage-themes: "テーマの管理"
|
||||||
|
builtin-themes: "標準テーマ"
|
||||||
|
my-themes: "マイテーマ"
|
||||||
installed-themes: "インストールされたテーマ"
|
installed-themes: "インストールされたテーマ"
|
||||||
select-theme: "テーマを選択してください"
|
select-theme: "テーマを選択してください"
|
||||||
uninstall: "アンインストール"
|
uninstall: "アンインストール"
|
||||||
@ -332,6 +335,7 @@ common/views/components/note-menu.vue:
|
|||||||
detail: "詳細"
|
detail: "詳細"
|
||||||
copy-link: "リンクをコピー"
|
copy-link: "リンクをコピー"
|
||||||
favorite: "お気に入り"
|
favorite: "お気に入り"
|
||||||
|
unfavorite: "お気に入り解除"
|
||||||
pin: "ピン留め"
|
pin: "ピン留め"
|
||||||
unpin: "ピン留め解除"
|
unpin: "ピン留め解除"
|
||||||
delete: "削除"
|
delete: "削除"
|
||||||
|
@ -286,6 +286,9 @@ common/views/components/theme.vue:
|
|||||||
invalid-theme: "テーマが正しくありません。"
|
invalid-theme: "テーマが正しくありません。"
|
||||||
already-installed: "既にそのテーマはインストールされています。"
|
already-installed: "既にそのテーマはインストールされています。"
|
||||||
saved: "保存しました"
|
saved: "保存しました"
|
||||||
|
manage-themes: "テーマの管理"
|
||||||
|
builtin-themes: "標準テーマ"
|
||||||
|
my-themes: "マイテーマ"
|
||||||
installed-themes: "インストールされたテーマ"
|
installed-themes: "インストールされたテーマ"
|
||||||
select-theme: "テーマを選択してください"
|
select-theme: "テーマを選択してください"
|
||||||
uninstall: "アンインストール"
|
uninstall: "アンインストール"
|
||||||
@ -332,6 +335,7 @@ common/views/components/note-menu.vue:
|
|||||||
detail: "詳細"
|
detail: "詳細"
|
||||||
copy-link: "リンクをコピー"
|
copy-link: "リンクをコピー"
|
||||||
favorite: "Diese Anmerkung favorisieren"
|
favorite: "Diese Anmerkung favorisieren"
|
||||||
|
unfavorite: "お気に入り解除"
|
||||||
pin: "An die Profilseite pinnen"
|
pin: "An die Profilseite pinnen"
|
||||||
unpin: "ピン留め解除"
|
unpin: "ピン留め解除"
|
||||||
delete: "Löschen"
|
delete: "Löschen"
|
||||||
|
@ -265,37 +265,40 @@ common/views/components/media-banner.vue:
|
|||||||
sensitive: "NSFW"
|
sensitive: "NSFW"
|
||||||
click-to-show: "Click to show"
|
click-to-show: "Click to show"
|
||||||
common/views/components/theme.vue:
|
common/views/components/theme.vue:
|
||||||
light-theme: "非ダークモード時に使用するテーマ"
|
light-theme: "Theme during non-dark mode"
|
||||||
dark-theme: "ダークモード時に使用するテーマ"
|
dark-theme: "Theme during dark mode"
|
||||||
light-themes: "明るいテーマ"
|
light-themes: "Light theme"
|
||||||
dark-themes: "暗いテーマ"
|
dark-themes: "Dark theme"
|
||||||
install-a-theme: "テーマのインストール"
|
install-a-theme: "Install a theme"
|
||||||
theme-code: "テーマコード"
|
theme-code: "Theme code"
|
||||||
install: "インストール"
|
install: "Install"
|
||||||
installed: "「{}」をインストールしました"
|
installed: "\"{}\" has been installed"
|
||||||
create-a-theme: "テーマの作成"
|
create-a-theme: "Create a theme"
|
||||||
save-created-theme: "テーマを保存"
|
save-created-theme: "Save a theme"
|
||||||
primary-color: "プライマリ カラー"
|
primary-color: "Primary color"
|
||||||
secondary-color: "セカンダリ カラー"
|
secondary-color: "Secondary color"
|
||||||
text-color: "文字色"
|
text-color: "Text color"
|
||||||
base-theme: "ベーステーマ"
|
base-theme: "Base theme"
|
||||||
base-theme-light: "Light"
|
base-theme-light: "Light"
|
||||||
base-theme-dark: "Dark"
|
base-theme-dark: "Dark"
|
||||||
theme-name: "テーマ名"
|
theme-name: "Theme name"
|
||||||
preview-created-theme: "プレビュー"
|
preview-created-theme: "Preview"
|
||||||
invalid-theme: "テーマが正しくありません。"
|
invalid-theme: "Not valid theme"
|
||||||
already-installed: "既にそのテーマはインストールされています。"
|
already-installed: "This theme is already installed."
|
||||||
saved: "保存しました"
|
saved: "Saved"
|
||||||
installed-themes: "インストールされたテーマ"
|
manage-themes: "Themes manager"
|
||||||
select-theme: "テーマを選択してください"
|
builtin-themes: "Standard themes"
|
||||||
uninstall: "アンインストール"
|
my-themes: "My themes"
|
||||||
uninstalled: "「{}」をアンインストールしました"
|
installed-themes: "Installed themes"
|
||||||
author: "作者"
|
select-theme: "Select your theme"
|
||||||
desc: "説明"
|
uninstall: "Uninstall"
|
||||||
export: "エクスポート"
|
uninstalled: "\"{}\" has been uninstalled"
|
||||||
import: "インポート"
|
author: "Author"
|
||||||
import-by-code: "またはコードをペースト"
|
desc: "Description"
|
||||||
theme-name-required: "テーマ名は必須です。"
|
export: "Export"
|
||||||
|
import: "Import"
|
||||||
|
import-by-code: "or paste code"
|
||||||
|
theme-name-required: "Theme name is required"
|
||||||
common/views/components/cw-button.vue:
|
common/views/components/cw-button.vue:
|
||||||
hide: "Hide"
|
hide: "Hide"
|
||||||
show: "See more"
|
show: "See more"
|
||||||
@ -332,8 +335,9 @@ common/views/components/note-menu.vue:
|
|||||||
detail: "Details"
|
detail: "Details"
|
||||||
copy-link: "Copy link"
|
copy-link: "Copy link"
|
||||||
favorite: "Favorite this note"
|
favorite: "Favorite this note"
|
||||||
|
unfavorite: "Unfavorite"
|
||||||
pin: "Pin to your profile"
|
pin: "Pin to your profile"
|
||||||
unpin: "ピン留め解除"
|
unpin: "Unpin"
|
||||||
delete: "Delete"
|
delete: "Delete"
|
||||||
delete-confirm: "Delete this post?"
|
delete-confirm: "Delete this post?"
|
||||||
remote: "Show original note"
|
remote: "Show original note"
|
||||||
@ -511,13 +515,13 @@ desktop/views/components/charts.vue:
|
|||||||
notes: "The number of posts: increase/decrease (Combined)"
|
notes: "The number of posts: increase/decrease (Combined)"
|
||||||
local-notes: "The number of posts: increase/decrease (Local)"
|
local-notes: "The number of posts: increase/decrease (Local)"
|
||||||
remote-notes: "The number of posts: increase/decrease (Remote)"
|
remote-notes: "The number of posts: increase/decrease (Remote)"
|
||||||
notes-total: "投稿の積算"
|
notes-total: "Total posts"
|
||||||
users: "The number of users: increase/decrease"
|
users: "The number of users: increase/decrease"
|
||||||
users-total: "ユーザーの積算"
|
users-total: "Total users"
|
||||||
drive: "Capacity used as the storage: increase/decrease"
|
drive: "Capacity used as the storage: increase/decrease"
|
||||||
drive-total: "ドライブ使用量の積算"
|
drive-total: "Total usage of Drive"
|
||||||
drive-files: "The number of files on the storage: increase/decrease"
|
drive-files: "The number of files on the storage: increase/decrease"
|
||||||
drive-files-total: "ドライブのファイル数の積算"
|
drive-files-total: "Total number of files on Drive"
|
||||||
network-requests: "Requests"
|
network-requests: "Requests"
|
||||||
network-time: "Response time"
|
network-time: "Response time"
|
||||||
network-usage: "Traffic"
|
network-usage: "Traffic"
|
||||||
@ -727,8 +731,8 @@ desktop/views/components/settings.vue:
|
|||||||
choose-wallpaper: "Choose a background"
|
choose-wallpaper: "Choose a background"
|
||||||
delete-wallpaper: "Remove background"
|
delete-wallpaper: "Remove background"
|
||||||
dark-mode: "Dark Mode"
|
dark-mode: "Dark Mode"
|
||||||
use-shadow: "UIに影を使用"
|
use-shadow: "Use shadows in the UI"
|
||||||
rounded-corners: "UIの角を丸める"
|
rounded-corners: "Round corners of UI"
|
||||||
circle-icons: "Use circle icons"
|
circle-icons: "Use circle icons"
|
||||||
contrasted-acct: "Add contrast to username"
|
contrasted-acct: "Add contrast to username"
|
||||||
post-form-on-timeline: "Display post form at the top of the timeline"
|
post-form-on-timeline: "Display post form at the top of the timeline"
|
||||||
|
@ -286,6 +286,9 @@ common/views/components/theme.vue:
|
|||||||
invalid-theme: "テーマが正しくありません。"
|
invalid-theme: "テーマが正しくありません。"
|
||||||
already-installed: "既にそのテーマはインストールされています。"
|
already-installed: "既にそのテーマはインストールされています。"
|
||||||
saved: "保存しました"
|
saved: "保存しました"
|
||||||
|
manage-themes: "テーマの管理"
|
||||||
|
builtin-themes: "標準テーマ"
|
||||||
|
my-themes: "マイテーマ"
|
||||||
installed-themes: "インストールされたテーマ"
|
installed-themes: "インストールされたテーマ"
|
||||||
select-theme: "テーマを選択してください"
|
select-theme: "テーマを選択してください"
|
||||||
uninstall: "アンインストール"
|
uninstall: "アンインストール"
|
||||||
@ -332,6 +335,7 @@ common/views/components/note-menu.vue:
|
|||||||
detail: "Detalles"
|
detail: "Detalles"
|
||||||
copy-link: "Copiar enlace"
|
copy-link: "Copiar enlace"
|
||||||
favorite: "Me gusta esta nota"
|
favorite: "Me gusta esta nota"
|
||||||
|
unfavorite: "お気に入り解除"
|
||||||
pin: "Fijar en el perfil"
|
pin: "Fijar en el perfil"
|
||||||
unpin: "ピン留め解除"
|
unpin: "ピン留め解除"
|
||||||
delete: "Borrar"
|
delete: "Borrar"
|
||||||
|
@ -34,7 +34,7 @@ common:
|
|||||||
paragraph4: "Pour terminer la personnalisation, cliquez sur \"Terminer\" dans le coin supérieur droit."
|
paragraph4: "Pour terminer la personnalisation, cliquez sur \"Terminer\" dans le coin supérieur droit."
|
||||||
gotit: "Compris !"
|
gotit: "Compris !"
|
||||||
notification:
|
notification:
|
||||||
file-uploaded: "Le fichier a été téléversé !"
|
file-uploaded: "Le fichier a été transféré !"
|
||||||
message-from: "Message de {} :"
|
message-from: "Message de {} :"
|
||||||
reversi-invited: "Invité à jouer"
|
reversi-invited: "Invité à jouer"
|
||||||
reversi-invited-by: "Invité par {} :"
|
reversi-invited-by: "Invité par {} :"
|
||||||
@ -83,17 +83,17 @@ common:
|
|||||||
pudding: "Pudding"
|
pudding: "Pudding"
|
||||||
note-visibility:
|
note-visibility:
|
||||||
public: "Public"
|
public: "Public"
|
||||||
home: "Accueil"
|
home: "Principal"
|
||||||
home-desc: "Publier sur le fil local uniquement"
|
home-desc: "Publier sur le fil principal uniquement"
|
||||||
followers: "Abonnés·es"
|
followers: "Abonnés·es"
|
||||||
followers-desc: "Publier à vos abonnés·es uniquement"
|
followers-desc: "Publier à vos abonnés·es uniquement"
|
||||||
specified: "Direct"
|
specified: "Direct"
|
||||||
specified-desc: "Publier aux utilisateurs·trices mentionnés·es"
|
specified-desc: "Publier uniquement aux utilisateurs·rices mentionnés·es"
|
||||||
private: "Privé"
|
private: "Privé"
|
||||||
note-placeholders:
|
note-placeholders:
|
||||||
a: "Que faites-vous maintenant ?"
|
a: "Que faites-vous maintenant ?"
|
||||||
b: "Quoi de neuf ?"
|
b: "Quoi de neuf ?"
|
||||||
c: "Qu'avez-vous en tête ?"
|
c: "Qu’avez-vous en tête ?"
|
||||||
d: "Désirez-vous publier quelques mots ?"
|
d: "Désirez-vous publier quelques mots ?"
|
||||||
e: "Écrivez ici"
|
e: "Écrivez ici"
|
||||||
f: "En attente de vos écrits"
|
f: "En attente de vos écrits"
|
||||||
@ -103,7 +103,7 @@ common:
|
|||||||
ok: "OK"
|
ok: "OK"
|
||||||
update-available-title: "Mise à jour disponible"
|
update-available-title: "Mise à jour disponible"
|
||||||
update-available: "Une nouvelle version de Misskey est disponible ({newer}, version actuelle: {current}). Veuillez recharger la page pour appliquer la mise à jour."
|
update-available: "Une nouvelle version de Misskey est disponible ({newer}, version actuelle: {current}). Veuillez recharger la page pour appliquer la mise à jour."
|
||||||
my-token-regenerated: "Votre token vient d'être généré, vous allez maintenant être déconnecté."
|
my-token-regenerated: "Votre jeton vient d’être généré, vous allez maintenant être déconnecté."
|
||||||
i-like-sushi: "Je préfère les sushis plutôt que le pudding"
|
i-like-sushi: "Je préfère les sushis plutôt que le pudding"
|
||||||
show-reversi-board-labels: "Afficher les étiquettes des lignes et colonnes dans Reversi"
|
show-reversi-board-labels: "Afficher les étiquettes des lignes et colonnes dans Reversi"
|
||||||
use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける"
|
use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける"
|
||||||
@ -120,7 +120,7 @@ common:
|
|||||||
my-turn: "C’est votre tour"
|
my-turn: "C’est votre tour"
|
||||||
opponent-turn: "Tour de l’adversaire"
|
opponent-turn: "Tour de l’adversaire"
|
||||||
turn-of: "C’est le tour de {}"
|
turn-of: "C’est le tour de {}"
|
||||||
past-turn-of: "C'est au tour de {}"
|
past-turn-of: "C’est au tour de {}"
|
||||||
won: "{} a gagné"
|
won: "{} a gagné"
|
||||||
black: "Noirs"
|
black: "Noirs"
|
||||||
white: "Blancs"
|
white: "Blancs"
|
||||||
@ -136,7 +136,7 @@ common:
|
|||||||
memo: "Pense-bête"
|
memo: "Pense-bête"
|
||||||
trends: "Tendances"
|
trends: "Tendances"
|
||||||
photo-stream: "Flux de photos"
|
photo-stream: "Flux de photos"
|
||||||
posts-monitor: "Graphe des publications"
|
posts-monitor: "Graph des publications"
|
||||||
slideshow: "Diaporama"
|
slideshow: "Diaporama"
|
||||||
version: "Version"
|
version: "Version"
|
||||||
broadcast: "Diffusion"
|
broadcast: "Diffusion"
|
||||||
@ -267,8 +267,8 @@ common/views/components/media-banner.vue:
|
|||||||
common/views/components/theme.vue:
|
common/views/components/theme.vue:
|
||||||
light-theme: "非ダークモード時に使用するテーマ"
|
light-theme: "非ダークモード時に使用するテーマ"
|
||||||
dark-theme: "ダークモード時に使用するテーマ"
|
dark-theme: "ダークモード時に使用するテーマ"
|
||||||
light-themes: "明るいテーマ"
|
light-themes: "Thème clair"
|
||||||
dark-themes: "暗いテーマ"
|
dark-themes: "Thème sombre"
|
||||||
install-a-theme: "Installer un thème"
|
install-a-theme: "Installer un thème"
|
||||||
theme-code: "Code du thème"
|
theme-code: "Code du thème"
|
||||||
install: "Installation"
|
install: "Installation"
|
||||||
@ -286,13 +286,16 @@ common/views/components/theme.vue:
|
|||||||
invalid-theme: "Thème n’est pas valide."
|
invalid-theme: "Thème n’est pas valide."
|
||||||
already-installed: "Le thème est déjà installé."
|
already-installed: "Le thème est déjà installé."
|
||||||
saved: "enregistré"
|
saved: "enregistré"
|
||||||
|
manage-themes: "Gestion des thèmes"
|
||||||
|
builtin-themes: "Thèmes standards"
|
||||||
|
my-themes: "Mes thèmes"
|
||||||
installed-themes: "Thèmes installés"
|
installed-themes: "Thèmes installés"
|
||||||
select-theme: "Veuillez sélectionner un thème"
|
select-theme: "Veuillez sélectionner un thème"
|
||||||
uninstall: "Désinstaller"
|
uninstall: "Désinstaller"
|
||||||
uninstalled: "« {} » a été désinstallé"
|
uninstalled: "« {} » a été désinstallé"
|
||||||
author: "Auteur"
|
author: "Auteur"
|
||||||
desc: "Description"
|
desc: "Description"
|
||||||
export: "エクスポート"
|
export: "Exporter"
|
||||||
import: "Importer"
|
import: "Importer"
|
||||||
import-by-code: "Ou coller du code"
|
import-by-code: "Ou coller du code"
|
||||||
theme-name-required: "Nom du thème est obligatoire."
|
theme-name-required: "Nom du thème est obligatoire."
|
||||||
@ -326,12 +329,13 @@ common/views/components/nav.vue:
|
|||||||
wiki: "Wiki"
|
wiki: "Wiki"
|
||||||
donors: "Donateur·rice·s"
|
donors: "Donateur·rice·s"
|
||||||
repository: "Dépôt"
|
repository: "Dépôt"
|
||||||
develop: "Développeur·se·s"
|
develop: "Développeurs"
|
||||||
feedback: "Remarques"
|
feedback: "Suggestions"
|
||||||
common/views/components/note-menu.vue:
|
common/views/components/note-menu.vue:
|
||||||
detail: "Détails"
|
detail: "Détails"
|
||||||
copy-link: "Copier le lien"
|
copy-link: "Copier le lien"
|
||||||
favorite: "Mettre cette note en favoris"
|
favorite: "Mettre cette note en favoris"
|
||||||
|
unfavorite: "お気に入り解除"
|
||||||
pin: "Épingler sur votre profil"
|
pin: "Épingler sur votre profil"
|
||||||
unpin: "Désépingler"
|
unpin: "Désépingler"
|
||||||
delete: "Supprimer"
|
delete: "Supprimer"
|
||||||
@ -407,10 +411,10 @@ common/views/components/visibility-chooser.vue:
|
|||||||
followers: "Abonné·e·s"
|
followers: "Abonné·e·s"
|
||||||
followers-desc: "Publier à vos abonné·e·s uniquement"
|
followers-desc: "Publier à vos abonné·e·s uniquement"
|
||||||
specified: "Direct"
|
specified: "Direct"
|
||||||
specified-desc: "Publier aux utilisateur·rice·s mentionné·e·s"
|
specified-desc: "Publier uniquement aux utilisateurs·rices mentionné·e·s"
|
||||||
private: "Privé"
|
private: "Privé"
|
||||||
common/views/components/trends.vue:
|
common/views/components/trends.vue:
|
||||||
count: "{} utilisateurs·trices mentionnés·es"
|
count: "{} utilisateurs·rices mentionnés·es"
|
||||||
empty: "Aucune tendance"
|
empty: "Aucune tendance"
|
||||||
common/views/widgets/broadcast.vue:
|
common/views/widgets/broadcast.vue:
|
||||||
fetching: "Récupération"
|
fetching: "Récupération"
|
||||||
@ -431,7 +435,7 @@ common/views/widgets/photo-stream.vue:
|
|||||||
title: "Flux de photos"
|
title: "Flux de photos"
|
||||||
no-photos: "Pas de photo"
|
no-photos: "Pas de photo"
|
||||||
common/views/widgets/posts-monitor.vue:
|
common/views/widgets/posts-monitor.vue:
|
||||||
title: "Graphe des publications"
|
title: "Graph des publications"
|
||||||
toggle: "Basculer entre les vues"
|
toggle: "Basculer entre les vues"
|
||||||
common/views/widgets/hashtags.vue:
|
common/views/widgets/hashtags.vue:
|
||||||
title: "Hashtags"
|
title: "Hashtags"
|
||||||
@ -511,7 +515,7 @@ desktop/views/components/charts.vue:
|
|||||||
notes: "投稿の増減 (統合)"
|
notes: "投稿の増減 (統合)"
|
||||||
local-notes: "投稿の増減 (ローカル)"
|
local-notes: "投稿の増減 (ローカル)"
|
||||||
remote-notes: "投稿の増減 (リモート)"
|
remote-notes: "投稿の増減 (リモート)"
|
||||||
notes-total: "投稿の積算"
|
notes-total: "Total des notes"
|
||||||
users: "Nombre d’utilisateurs·trices : augmentation/diminution"
|
users: "Nombre d’utilisateurs·trices : augmentation/diminution"
|
||||||
users-total: "ユーザーの積算"
|
users-total: "ユーザーの積算"
|
||||||
drive: "ドライブ使用量の増減"
|
drive: "ドライブ使用量の増減"
|
||||||
@ -579,7 +583,7 @@ desktop/views/components/drive.vue:
|
|||||||
unable-to-process: "L'opération n'a pas pu être complétée"
|
unable-to-process: "L'opération n'a pas pu être complétée"
|
||||||
circular-reference-detected: "Le dossier de destination est un sous-dossier du dossier que vous souhaitez déplacer."
|
circular-reference-detected: "Le dossier de destination est un sous-dossier du dossier que vous souhaitez déplacer."
|
||||||
unhandled-error: "Erreur inconnue"
|
unhandled-error: "Erreur inconnue"
|
||||||
url-upload: "Uploader d'un URL"
|
url-upload: "Téléverser via une URL"
|
||||||
url-of-file: "URL de l'image que vous souhaitez uploader."
|
url-of-file: "URL de l'image que vous souhaitez uploader."
|
||||||
url-upload-requested: "Upload requested"
|
url-upload-requested: "Upload requested"
|
||||||
may-take-time: "L'upload de votre fichier peut prendre un certain temps."
|
may-take-time: "L'upload de votre fichier peut prendre un certain temps."
|
||||||
@ -587,8 +591,8 @@ desktop/views/components/drive.vue:
|
|||||||
folder-name: "Nom du dossier"
|
folder-name: "Nom du dossier"
|
||||||
contextmenu:
|
contextmenu:
|
||||||
create-folder: "Créer un dossier"
|
create-folder: "Créer un dossier"
|
||||||
upload: "Uploader un fichier"
|
upload: "Transférer un fichier"
|
||||||
url-upload: "Uploader d'un URL"
|
url-upload: "Transférer à partir d’une URL"
|
||||||
desktop/views/components/media-image.vue:
|
desktop/views/components/media-image.vue:
|
||||||
sensitive: "Le contenu est NSFW"
|
sensitive: "Le contenu est NSFW"
|
||||||
click-to-show: "Cliquer pour afficher"
|
click-to-show: "Cliquer pour afficher"
|
||||||
@ -655,21 +659,21 @@ desktop/views/components/post-form.vue:
|
|||||||
add-visible-user: "+Ajouter un utilisateur"
|
add-visible-user: "+Ajouter un utilisateur"
|
||||||
attach-location-information: "Attacher des informations de localisation"
|
attach-location-information: "Attacher des informations de localisation"
|
||||||
hide-contents: "Masquer les contenus"
|
hide-contents: "Masquer les contenus"
|
||||||
reply-placeholder: "Répondre à cette note"
|
reply-placeholder: "Répondre à cette note …"
|
||||||
quote-placeholder: "Citer cette note"
|
quote-placeholder: "Citer cette note …"
|
||||||
submit: "Poster"
|
submit: "Publier"
|
||||||
reply: "Répondre"
|
reply: "Répondre"
|
||||||
renote: "Republier"
|
renote: "Republier"
|
||||||
posted: "Posté!"
|
posted: "Publié !"
|
||||||
replied: "Répondu!"
|
replied: "Répondu !"
|
||||||
reposted: "Reposté!"
|
reposted: "Reposté !"
|
||||||
note-failed: "La note à échoué"
|
note-failed: "La note à échoué"
|
||||||
reply-failed: "La réponse à échoué"
|
reply-failed: "La réponse à échoué"
|
||||||
renote-failed: "La renote à échoué"
|
renote-failed: "Échec lors de la republication"
|
||||||
posting: "Publication..."
|
posting: "Publication …"
|
||||||
attach-media-from-local: "Joindre un media depuis votre PC"
|
attach-media-from-local: "Joindre un média depuis votre appareil"
|
||||||
attach-media-from-drive: "Joindre un media depuis votre Drive"
|
attach-media-from-drive: "Joindre un média depuis votre Drive"
|
||||||
attach-cancel: "Annuler la jointure de fichier"
|
attach-cancel: "Annuler le fichier attaché"
|
||||||
insert-a-kao: "v('ω')v"
|
insert-a-kao: "v('ω')v"
|
||||||
create-poll: "Créer un sondage"
|
create-poll: "Créer un sondage"
|
||||||
text-remain: "{} charactères restants"
|
text-remain: "{} charactères restants"
|
||||||
@ -684,15 +688,15 @@ desktop/views/components/post-form-window.vue:
|
|||||||
note: "Nouvelle note"
|
note: "Nouvelle note"
|
||||||
reply: "Répondre"
|
reply: "Répondre"
|
||||||
attaches: "{} media joint(s)"
|
attaches: "{} media joint(s)"
|
||||||
uploading-media: "Upload du media {}"
|
uploading-media: "Transfert du média {}"
|
||||||
desktop/views/components/progress-dialog.vue:
|
desktop/views/components/progress-dialog.vue:
|
||||||
waiting: "En attente"
|
waiting: "En attente"
|
||||||
desktop/views/components/renote-form.vue:
|
desktop/views/components/renote-form.vue:
|
||||||
quote: "Citer..."
|
quote: "Citer..."
|
||||||
cancel: "Annuler"
|
cancel: "Annuler"
|
||||||
renote: "Republier"
|
renote: "Republier"
|
||||||
reposting: "Repost en cours..."
|
reposting: "Republication en cours …"
|
||||||
success: "Reposté!"
|
success: "Republié !"
|
||||||
failure: "La renote a échoué"
|
failure: "La renote a échoué"
|
||||||
desktop/views/components/renote-form-window.vue:
|
desktop/views/components/renote-form-window.vue:
|
||||||
title: "Êtes vous sûr de vouloir renote cette note?"
|
title: "Êtes vous sûr de vouloir renote cette note?"
|
||||||
@ -855,7 +859,7 @@ desktop/views/components/timeline.vue:
|
|||||||
list-name: "Nom de la liste"
|
list-name: "Nom de la liste"
|
||||||
desktop/views/components/ui.header.vue:
|
desktop/views/components/ui.header.vue:
|
||||||
welcome-back: "Content de vous revoir !"
|
welcome-back: "Content de vous revoir !"
|
||||||
adjective: "さん"
|
adjective: "M."
|
||||||
desktop/views/components/ui.header.account.vue:
|
desktop/views/components/ui.header.account.vue:
|
||||||
profile: "Votre profil"
|
profile: "Votre profil"
|
||||||
drive: "Drive"
|
drive: "Drive"
|
||||||
@ -875,7 +879,7 @@ desktop/views/components/ui.header.nav.vue:
|
|||||||
desktop/views/components/ui.header.notifications.vue:
|
desktop/views/components/ui.header.notifications.vue:
|
||||||
title: "Notifications"
|
title: "Notifications"
|
||||||
desktop/views/components/ui.header.post.vue:
|
desktop/views/components/ui.header.post.vue:
|
||||||
post: "Composer un nouveau post"
|
post: "Rédiger une nouvelle publication"
|
||||||
desktop/views/components/ui.header.search.vue:
|
desktop/views/components/ui.header.search.vue:
|
||||||
placeholder: "Chercher"
|
placeholder: "Chercher"
|
||||||
desktop/views/components/received-follow-requests-window.vue:
|
desktop/views/components/received-follow-requests-window.vue:
|
||||||
@ -908,9 +912,9 @@ desktop/views/pages/admin/admin.vue:
|
|||||||
desktop/views/pages/admin/admin.dashboard.vue:
|
desktop/views/pages/admin/admin.dashboard.vue:
|
||||||
dashboard: "Tableau de bord"
|
dashboard: "Tableau de bord"
|
||||||
all-users: "Toutes les utilisateurrices"
|
all-users: "Toutes les utilisateurrices"
|
||||||
original-users: "Utilisateurrices sur cette instance"
|
original-users: "Utilisateur·rice·s sur cette instance"
|
||||||
all-notes: "Toutes les publications"
|
all-notes: "Toutes les publications"
|
||||||
original-notes: "Publication sur cette instance"
|
original-notes: "Publications sur cette instance"
|
||||||
invite: "Invitation"
|
invite: "Invitation"
|
||||||
desktop/views/pages/admin/admin.suspend-user.vue:
|
desktop/views/pages/admin/admin.suspend-user.vue:
|
||||||
suspend-user: "Suspendre un·e utilisateur·rice"
|
suspend-user: "Suspendre un·e utilisateur·rice"
|
||||||
@ -938,9 +942,9 @@ desktop/views/pages/deck/deck.note.vue:
|
|||||||
deleted: "cette publication a été supprimée"
|
deleted: "cette publication a été supprimée"
|
||||||
desktop/views/pages/stats/stats.vue:
|
desktop/views/pages/stats/stats.vue:
|
||||||
all-users: "Toutes les utilisateurrices"
|
all-users: "Toutes les utilisateurrices"
|
||||||
original-users: "Utilisateurrices sur cette instance"
|
original-users: "Utilisateur·rice·s sur cette instance"
|
||||||
all-notes: "Toutes les publications"
|
all-notes: "Toutes les publications"
|
||||||
original-notes: "Publication sur cette instance"
|
original-notes: "Publications sur cette instance"
|
||||||
desktop/views/pages/welcome.vue:
|
desktop/views/pages/welcome.vue:
|
||||||
about: "à propos"
|
about: "à propos"
|
||||||
gotit: "J'ai compris !"
|
gotit: "J'ai compris !"
|
||||||
@ -966,7 +970,7 @@ desktop/views/pages/selectdrive.vue:
|
|||||||
title: "Choisir fichier(s)"
|
title: "Choisir fichier(s)"
|
||||||
ok: "OK"
|
ok: "OK"
|
||||||
cancel: "Annuler"
|
cancel: "Annuler"
|
||||||
upload: "Uploader un ou plusieurs fichier(s) depuis votre PC"
|
upload: "Téléverser des fichiers à partir de votre ordinateur"
|
||||||
desktop/views/pages/search.vue:
|
desktop/views/pages/search.vue:
|
||||||
not-available: "La fonction de recherche est désactivée dans les paramètres de l’instance."
|
not-available: "La fonction de recherche est désactivée dans les paramètres de l’instance."
|
||||||
not-found: "Aucun message trouvé pour '{}'"
|
not-found: "Aucun message trouvé pour '{}'"
|
||||||
@ -983,13 +987,13 @@ desktop/views/pages/user/user.followers-you-know.vue:
|
|||||||
loading: "Chargement en cours"
|
loading: "Chargement en cours"
|
||||||
no-users: "Pas d'utilisateurs"
|
no-users: "Pas d'utilisateurs"
|
||||||
desktop/views/pages/user/user.friends.vue:
|
desktop/views/pages/user/user.friends.vue:
|
||||||
title: "Personnes qui répondent le plus"
|
title: "Mentions fréquentes"
|
||||||
loading: "Chargement en cours"
|
loading: "Chargement en cours"
|
||||||
no-users: "Pas d'utilisateurs"
|
no-users: "Pas d'utilisateurs"
|
||||||
desktop/views/pages/user/user.vue:
|
desktop/views/pages/user/user.vue:
|
||||||
is-suspended: "Ce compte a été suspendu."
|
is-suspended: "Ce compte a été suspendu."
|
||||||
is-remote: "Cet utilisateur n'est pas un utilisateur de Misskey. Certaines informations peuvent être erronées"
|
is-remote: "Cet utilisateur n'est pas un utilisateur Misskey. Certaines informations peuvent ne pas refléter ce profil dans sa totalité."
|
||||||
view-remote: "Voir les informations détaillées"
|
view-remote: "Consulter le profil complet"
|
||||||
desktop/views/pages/user/user.home.vue:
|
desktop/views/pages/user/user.home.vue:
|
||||||
last-used-at: "Last used at"
|
last-used-at: "Last used at"
|
||||||
desktop/views/pages/user/user.photos.vue:
|
desktop/views/pages/user/user.photos.vue:
|
||||||
@ -997,7 +1001,7 @@ desktop/views/pages/user/user.photos.vue:
|
|||||||
loading: "Chargement en cours"
|
loading: "Chargement en cours"
|
||||||
no-photos: "Pas de photos"
|
no-photos: "Pas de photos"
|
||||||
desktop/views/pages/user/user.profile.vue:
|
desktop/views/pages/user/user.profile.vue:
|
||||||
follows-you: "Vous suis"
|
follows-you: "Vous suit"
|
||||||
stalk: "Traquer"
|
stalk: "Traquer"
|
||||||
stalking: "ストーキングしています"
|
stalking: "ストーキングしています"
|
||||||
unstalk: "ストーク解除"
|
unstalk: "ストーク解除"
|
||||||
@ -1026,8 +1030,8 @@ desktop/views/widgets/polls.vue:
|
|||||||
refresh: "Afficher d'autres"
|
refresh: "Afficher d'autres"
|
||||||
nothing: "Rien"
|
nothing: "Rien"
|
||||||
desktop/views/widgets/post-form.vue:
|
desktop/views/widgets/post-form.vue:
|
||||||
title: "Post"
|
title: "Publication"
|
||||||
note: "Post"
|
note: "Publication"
|
||||||
desktop/views/widgets/profile.vue:
|
desktop/views/widgets/profile.vue:
|
||||||
update-banner: "Cliquer pour éditer votre bannière"
|
update-banner: "Cliquer pour éditer votre bannière"
|
||||||
update-avatar: "Cliquer pour éditer votre avatar"
|
update-avatar: "Cliquer pour éditer votre avatar"
|
||||||
@ -1089,7 +1093,7 @@ mobile/views/components/friends-maker.vue:
|
|||||||
refresh: "Voir plus"
|
refresh: "Voir plus"
|
||||||
close: "Fermer"
|
close: "Fermer"
|
||||||
mobile/views/components/note.vue:
|
mobile/views/components/note.vue:
|
||||||
reposted-by: "Renoté par {}"
|
reposted-by: "Republié par {}"
|
||||||
private: "cette publication est privée"
|
private: "cette publication est privée"
|
||||||
deleted: "cette publication a été supprimée"
|
deleted: "cette publication a été supprimée"
|
||||||
location: "Géolocalisation"
|
location: "Géolocalisation"
|
||||||
@ -1116,7 +1120,7 @@ mobile/views/components/notifications.vue:
|
|||||||
empty: "Pas de notifications"
|
empty: "Pas de notifications"
|
||||||
mobile/views/components/post-form.vue:
|
mobile/views/components/post-form.vue:
|
||||||
add-visible-user: "Ajouter un utilisateur"
|
add-visible-user: "Ajouter un utilisateur"
|
||||||
submit: "Poster"
|
submit: "Publier"
|
||||||
reply: "Répondre"
|
reply: "Répondre"
|
||||||
renote: "Republier"
|
renote: "Republier"
|
||||||
quote-placeholder: "Citer ce billet ... (Facultatif)"
|
quote-placeholder: "Citer ce billet ... (Facultatif)"
|
||||||
@ -1168,7 +1172,7 @@ mobile/views/pages/drive.vue:
|
|||||||
drive: "Drive"
|
drive: "Drive"
|
||||||
more: "Afficher plus ..."
|
more: "Afficher plus ..."
|
||||||
mobile/views/pages/signup.vue:
|
mobile/views/pages/signup.vue:
|
||||||
lets-start: "Commençons ! 📦"
|
lets-start: "Votre compte est prêt ! 📦"
|
||||||
mobile/views/pages/followers.vue:
|
mobile/views/pages/followers.vue:
|
||||||
followers-of: "Abonné·e·s de {}"
|
followers-of: "Abonné·e·s de {}"
|
||||||
mobile/views/pages/following.vue:
|
mobile/views/pages/following.vue:
|
||||||
@ -1283,7 +1287,7 @@ mobile/views/pages/settings.vue:
|
|||||||
sound: "Sons"
|
sound: "Sons"
|
||||||
enable-sounds: "Activer les sons"
|
enable-sounds: "Activer les sons"
|
||||||
mobile/views/pages/user.vue:
|
mobile/views/pages/user.vue:
|
||||||
follows-you: "vous suit"
|
follows-you: "Vous suit"
|
||||||
following: "Abonnements"
|
following: "Abonnements"
|
||||||
followers: "Abonné·e·s"
|
followers: "Abonné·e·s"
|
||||||
notes: "Notes"
|
notes: "Notes"
|
||||||
@ -1291,8 +1295,8 @@ mobile/views/pages/user.vue:
|
|||||||
timeline: "Fil d'actualité"
|
timeline: "Fil d'actualité"
|
||||||
media: "Media"
|
media: "Media"
|
||||||
is-suspended: "This account has been suspended."
|
is-suspended: "This account has been suspended."
|
||||||
is-remote: "Cet utilisateur n'est pas un utilisateur de Misskey. Certaines informations peuvent être erronées "
|
is-remote: "Ceci est le profil d’un utilisateur·rice distant·e. Certaines informations peuvent ne pas refléter ce profil dans sa totalité."
|
||||||
view-remote: "Voir les informations détaillées"
|
view-remote: "Consulter son profil complet"
|
||||||
mobile/views/pages/user/home.vue:
|
mobile/views/pages/user/home.vue:
|
||||||
recent-notes: "Notes récentes"
|
recent-notes: "Notes récentes"
|
||||||
images: "Images"
|
images: "Images"
|
||||||
@ -1316,7 +1320,7 @@ mobile/views/pages/user/home.photos.vue:
|
|||||||
no-photos: "Pas de photos"
|
no-photos: "Pas de photos"
|
||||||
docs:
|
docs:
|
||||||
edit-this-page-on-github: "Vous avez trouvé une erreur ou vous voulez contribuer à la documentation?"
|
edit-this-page-on-github: "Vous avez trouvé une erreur ou vous voulez contribuer à la documentation?"
|
||||||
edit-this-page-on-github-link: "Modifiez cette page sur github!"
|
edit-this-page-on-github-link: "Éditez cette page sur Github !"
|
||||||
api:
|
api:
|
||||||
entities:
|
entities:
|
||||||
properties: "Propriétés"
|
properties: "Propriétés"
|
||||||
|
@ -286,6 +286,9 @@ common/views/components/theme.vue:
|
|||||||
invalid-theme: "テーマが正しくありません。"
|
invalid-theme: "テーマが正しくありません。"
|
||||||
already-installed: "既にそのテーマはインストールされています。"
|
already-installed: "既にそのテーマはインストールされています。"
|
||||||
saved: "保存しました"
|
saved: "保存しました"
|
||||||
|
manage-themes: "テーマの管理"
|
||||||
|
builtin-themes: "標準テーマ"
|
||||||
|
my-themes: "マイテーマ"
|
||||||
installed-themes: "インストールされたテーマ"
|
installed-themes: "インストールされたテーマ"
|
||||||
select-theme: "テーマを選択してください"
|
select-theme: "テーマを選択してください"
|
||||||
uninstall: "アンインストール"
|
uninstall: "アンインストール"
|
||||||
@ -332,6 +335,7 @@ common/views/components/note-menu.vue:
|
|||||||
detail: "詳細"
|
detail: "詳細"
|
||||||
copy-link: "リンクをコピー"
|
copy-link: "リンクをコピー"
|
||||||
favorite: "お気に入り"
|
favorite: "お気に入り"
|
||||||
|
unfavorite: "お気に入り解除"
|
||||||
pin: "ピン留め"
|
pin: "ピン留め"
|
||||||
unpin: "ピン留め解除"
|
unpin: "ピン留め解除"
|
||||||
delete: "削除"
|
delete: "削除"
|
||||||
|
@ -307,6 +307,9 @@ common/views/components/theme.vue:
|
|||||||
invalid-theme: "テーマが正しくありません。"
|
invalid-theme: "テーマが正しくありません。"
|
||||||
already-installed: "既にそのテーマはインストールされています。"
|
already-installed: "既にそのテーマはインストールされています。"
|
||||||
saved: "保存しました"
|
saved: "保存しました"
|
||||||
|
manage-themes: "テーマの管理"
|
||||||
|
builtin-themes: "標準テーマ"
|
||||||
|
my-themes: "マイテーマ"
|
||||||
installed-themes: "インストールされたテーマ"
|
installed-themes: "インストールされたテーマ"
|
||||||
select-theme: "テーマを選択してください"
|
select-theme: "テーマを選択してください"
|
||||||
uninstall: "アンインストール"
|
uninstall: "アンインストール"
|
||||||
@ -360,6 +363,7 @@ common/views/components/note-menu.vue:
|
|||||||
detail: "詳細"
|
detail: "詳細"
|
||||||
copy-link: "リンクをコピー"
|
copy-link: "リンクをコピー"
|
||||||
favorite: "お気に入り"
|
favorite: "お気に入り"
|
||||||
|
unfavorite: "お気に入り解除"
|
||||||
pin: "ピン留め"
|
pin: "ピン留め"
|
||||||
unpin: "ピン留め解除"
|
unpin: "ピン留め解除"
|
||||||
delete: "削除"
|
delete: "削除"
|
||||||
@ -879,6 +883,11 @@ desktop/views/components/settings.vue:
|
|||||||
task-manager: "タスクマネージャ"
|
task-manager: "タスクマネージャ"
|
||||||
third-parties: "サードパーティ"
|
third-parties: "サードパーティ"
|
||||||
|
|
||||||
|
navbar-position: "ナビゲーションバーの位置"
|
||||||
|
navbar-position-top: "上"
|
||||||
|
navbar-position-left: "左"
|
||||||
|
navbar-position-right: "右"
|
||||||
|
|
||||||
desktop/views/components/settings.2fa.vue:
|
desktop/views/components/settings.2fa.vue:
|
||||||
intro: "二段階認証を設定すると、サインイン時にパスワードだけでなく、予め登録しておいた物理的なデバイス(例えばあなたのスマートフォンなど)も必要になり、よりセキュリティが向上します。"
|
intro: "二段階認証を設定すると、サインイン時にパスワードだけでなく、予め登録しておいた物理的なデバイス(例えばあなたのスマートフォンなど)も必要になり、よりセキュリティが向上します。"
|
||||||
detail: "詳細..."
|
detail: "詳細..."
|
||||||
@ -934,6 +943,7 @@ desktop/views/components/settings.profile.vue:
|
|||||||
save: "保存"
|
save: "保存"
|
||||||
locked-account: "アカウントの保護"
|
locked-account: "アカウントの保護"
|
||||||
is-locked: "フォローを承認制にする"
|
is-locked: "フォローを承認制にする"
|
||||||
|
careful-bot: "Botからのフォローだけ承認制にする"
|
||||||
other: "その他"
|
other: "その他"
|
||||||
is-bot: "このアカウントはBotです"
|
is-bot: "このアカウントはBotです"
|
||||||
is-cat: "このアカウントはCatです"
|
is-cat: "このアカウントはCatです"
|
||||||
@ -1416,6 +1426,7 @@ mobile/views/pages/settings/settings.profile.vue:
|
|||||||
banner: "バナー"
|
banner: "バナー"
|
||||||
is-cat: "このアカウントはCatです"
|
is-cat: "このアカウントはCatです"
|
||||||
is-locked: "フォローを承認制にする"
|
is-locked: "フォローを承認制にする"
|
||||||
|
careful-bot: "Botからのフォローだけ承認制にする"
|
||||||
advanced: "その他"
|
advanced: "その他"
|
||||||
privacy: "プライバシー"
|
privacy: "プライバシー"
|
||||||
save: "保存"
|
save: "保存"
|
||||||
|
@ -265,37 +265,40 @@ common/views/components/media-banner.vue:
|
|||||||
sensitive: "見せたらあかん"
|
sensitive: "見せたらあかん"
|
||||||
click-to-show: "押してみ、見せたるわ"
|
click-to-show: "押してみ、見せたるわ"
|
||||||
common/views/components/theme.vue:
|
common/views/components/theme.vue:
|
||||||
light-theme: "非ダークモード時に使用するテーマ"
|
light-theme: "ナイトゲームちゃう時のテーマどないする?"
|
||||||
dark-theme: "ダークモード時に使用するテーマ"
|
dark-theme: "ナイトゲームの時のテーマどないする?"
|
||||||
light-themes: "明るいテーマ"
|
light-themes: "デイゲーム"
|
||||||
dark-themes: "暗いテーマ"
|
dark-themes: "ナイトゲーム"
|
||||||
install-a-theme: "テーマのインストール"
|
install-a-theme: "テーマ入れるで"
|
||||||
theme-code: "テーマコード"
|
theme-code: "テーマコード"
|
||||||
install: "インストール"
|
install: "インストール"
|
||||||
installed: "「{}」をインストールしました"
|
installed: "「{}」を入れたで!"
|
||||||
create-a-theme: "テーマの作成"
|
create-a-theme: "テーマ作る"
|
||||||
save-created-theme: "テーマを保存"
|
save-created-theme: "テーマ保存"
|
||||||
primary-color: "プライマリ カラー"
|
primary-color: "この色一番重要や"
|
||||||
secondary-color: "セカンダリ カラー"
|
secondary-color: "次はこの色出したって"
|
||||||
text-color: "文字色"
|
text-color: "文字はこの色や!"
|
||||||
base-theme: "ベーステーマ"
|
base-theme: "この色が背景や!"
|
||||||
base-theme-light: "Light"
|
base-theme-light: "Light"
|
||||||
base-theme-dark: "Dark"
|
base-theme-dark: "Dark"
|
||||||
theme-name: "テーマ名"
|
theme-name: "テーマ名"
|
||||||
preview-created-theme: "プレビュー"
|
preview-created-theme: "試してみる"
|
||||||
invalid-theme: "テーマが正しくありません。"
|
invalid-theme: "このテーマあかんわ、なんか間違うとる"
|
||||||
already-installed: "既にそのテーマはインストールされています。"
|
already-installed: "このテーマもうあるで"
|
||||||
saved: "保存しました"
|
saved: "保存したで!"
|
||||||
installed-themes: "インストールされたテーマ"
|
manage-themes: "テーマの管理"
|
||||||
select-theme: "テーマを選択してください"
|
builtin-themes: "いつものテーマ"
|
||||||
uninstall: "アンインストール"
|
my-themes: "ワイのテーマ"
|
||||||
uninstalled: "「{}」をアンインストールしました"
|
installed-themes: "入れたテーマ"
|
||||||
author: "作者"
|
select-theme: "テーマ選んでや!"
|
||||||
|
uninstall: "ほかす"
|
||||||
|
uninstalled: "「{}」をほかしてもうたわ"
|
||||||
|
author: "作った人"
|
||||||
desc: "説明"
|
desc: "説明"
|
||||||
export: "エクスポート"
|
export: "エクスポート"
|
||||||
import: "インポート"
|
import: "インポート"
|
||||||
import-by-code: "またはコードをペースト"
|
import-by-code: "それかコードを貼っつける"
|
||||||
theme-name-required: "テーマ名は必須です。"
|
theme-name-required: "テーマ名は絶対要るで"
|
||||||
common/views/components/cw-button.vue:
|
common/views/components/cw-button.vue:
|
||||||
hide: "もうええわ"
|
hide: "もうええわ"
|
||||||
show: "見たいやろ?"
|
show: "見たいやろ?"
|
||||||
@ -332,6 +335,7 @@ common/views/components/note-menu.vue:
|
|||||||
detail: "もっと"
|
detail: "もっと"
|
||||||
copy-link: "リンクをコピー"
|
copy-link: "リンクをコピー"
|
||||||
favorite: "お気に入り"
|
favorite: "お気に入り"
|
||||||
|
unfavorite: "お気に入りやめる"
|
||||||
pin: "ピン留め"
|
pin: "ピン留め"
|
||||||
unpin: "ピン留めやめる"
|
unpin: "ピン留めやめる"
|
||||||
delete: "ほかす"
|
delete: "ほかす"
|
||||||
@ -472,7 +476,7 @@ common/views/pages/follow.vue:
|
|||||||
following: "フォローしとる"
|
following: "フォローしとる"
|
||||||
follow: "フォロー"
|
follow: "フォロー"
|
||||||
request-pending: "フォローの許し待っとる"
|
request-pending: "フォローの許し待っとる"
|
||||||
follow-processing: "フォロー処理中"
|
follow-processing: "今フォロー処理やっとる‥"
|
||||||
follow-request: "フォロー許してくれや!言うてみる"
|
follow-request: "フォロー許してくれや!言うてみる"
|
||||||
desktop:
|
desktop:
|
||||||
banner-crop-title: "どこバナーとして出す?"
|
banner-crop-title: "どこバナーとして出す?"
|
||||||
@ -599,7 +603,7 @@ desktop/views/components/follow-button.vue:
|
|||||||
following: "フォローしとる"
|
following: "フォローしとる"
|
||||||
follow: "フォロー"
|
follow: "フォロー"
|
||||||
request-pending: "フォローの許し待っとる"
|
request-pending: "フォローの許し待っとる"
|
||||||
follow-processing: "フォロー処理中"
|
follow-processing: "今フォロー処理やっとる‥"
|
||||||
follow-request: "フォロー許してくれや!言うてみる"
|
follow-request: "フォロー許してくれや!言うてみる"
|
||||||
desktop/views/components/followers-window.vue:
|
desktop/views/components/followers-window.vue:
|
||||||
followers: "{} のフォロワー"
|
followers: "{} のフォロワー"
|
||||||
@ -1080,7 +1084,7 @@ mobile/views/components/follow-button.vue:
|
|||||||
following: "フォローしとる"
|
following: "フォローしとる"
|
||||||
follow: "フォロー"
|
follow: "フォロー"
|
||||||
request-pending: "フォローの許し待っとる"
|
request-pending: "フォローの許し待っとる"
|
||||||
follow-processing: "フォロー処理中"
|
follow-processing: "今フォロー処理やっとる‥"
|
||||||
follow-request: "フォロー許してくれや!言うてみる"
|
follow-request: "フォロー許してくれや!言うてみる"
|
||||||
mobile/views/components/friends-maker.vue:
|
mobile/views/components/friends-maker.vue:
|
||||||
title: "おもろそうやな"
|
title: "おもろそうやな"
|
||||||
@ -1220,9 +1224,9 @@ mobile/views/pages/settings/settings.profile.vue:
|
|||||||
avatar: "アイコン"
|
avatar: "アイコン"
|
||||||
banner: "バナー"
|
banner: "バナー"
|
||||||
is-cat: "このアカウントはCatや"
|
is-cat: "このアカウントはCatや"
|
||||||
is-locked: "他人のフォローは許してからや!"
|
is-locked: "他人のフォローは許可してからや!"
|
||||||
advanced: "その他"
|
advanced: "その他"
|
||||||
privacy: "プライバシー⇔オカンの年齢"
|
privacy: "プライバシーってなんや?オカンの年齢か?"
|
||||||
save: "保存"
|
save: "保存"
|
||||||
saved: "プロフィールを保存したで"
|
saved: "プロフィールを保存したで"
|
||||||
uploading: "アップロードしとるで…"
|
uploading: "アップロードしとるで…"
|
||||||
@ -1242,7 +1246,7 @@ mobile/views/pages/settings.vue:
|
|||||||
specify-language: "言語選びや"
|
specify-language: "言語選びや"
|
||||||
design: "見た感じ"
|
design: "見た感じ"
|
||||||
dark-mode: "ナイトゲームや!"
|
dark-mode: "ナイトゲームや!"
|
||||||
i-am-under-limited-internet: "電波がバァーっといけへんねん"
|
i-am-under-limited-internet: "電波と阪神がザコいんや"
|
||||||
circle-icons: "アイコンもタコ焼きも丸いやんな?"
|
circle-icons: "アイコンもタコ焼きも丸いやんな?"
|
||||||
contrasted-acct: "ユーザー名ようわからんし見やすしといて"
|
contrasted-acct: "ユーザー名ようわからんし見やすしといて"
|
||||||
timeline: "タイムライン"
|
timeline: "タイムライン"
|
||||||
@ -1254,8 +1258,8 @@ mobile/views/pages/settings.vue:
|
|||||||
post-style-standard: "標準"
|
post-style-standard: "標準"
|
||||||
post-style-smart: "べっぴんさん"
|
post-style-smart: "べっぴんさん"
|
||||||
notification-position: "通知どこ見せる?"
|
notification-position: "通知どこ見せる?"
|
||||||
notification-position-bottom: "ミナミ"
|
notification-position-bottom: "ミナミの方"
|
||||||
notification-position-top: "キタ"
|
notification-position-top: "キタの方"
|
||||||
theme: "テーマ"
|
theme: "テーマ"
|
||||||
behavior: "動き"
|
behavior: "動き"
|
||||||
fetch-on-scroll: "スクロールしたらもっと見せてや"
|
fetch-on-scroll: "スクロールしたらもっと見せてや"
|
||||||
|
@ -3,20 +3,20 @@ meta:
|
|||||||
lang: "한국어"
|
lang: "한국어"
|
||||||
divider: ""
|
divider: ""
|
||||||
common:
|
common:
|
||||||
misskey: "A ⭐ of fediverse"
|
misskey: "연합우주의 ⭐"
|
||||||
about-title: "A ⭐ of fediverse."
|
about-title: "연합우주의 ⭐."
|
||||||
about: "Misskey를 찾아 주셔서 감사합니다. Misskey은 지구에서 태어난 <b>분산 마이크로 블로그 SNS </b> 입니다. Fediverse (다양한 SNS로 구성되는 우주)에 존재하는 다른 SNS와 상호 연결되어 있습니다. 잠시 도시의 번잡함에서 벗어나 새로운 인터넷에 다이브 해 보지 않겠습니까."
|
about: "Misskey를 찾아 주셔서 감사합니다. Misskey은 지구에서 태어난 <b>분산 마이크로 블로그 SNS </b> 입니다. Fediverse (다양한 SNS로 구성되는 우주)에 존재하는 다른 SNS와 상호 연결되어 있습니다. 잠시 도시의 번잡함에서 벗어나 새로운 인터넷에 다이브 해 보지 않겠습니까."
|
||||||
intro:
|
intro:
|
||||||
title: "Misskeyって?"
|
title: "Misskey란?"
|
||||||
about: "Misskeyはオープンソースの<b>分散型マイクロブログSNS</b>です。リッチで高度にカスタマイズできるUI、投稿へのリアクション、ファイルを一元管理できるドライブなど、先進的な機能を揃えています。また、Fediverseと呼ばれるネットワークに接続できるため、他のSNSともやり取りできます。例えば、あなたが何か投稿すると、その投稿はMisskeyだけでなく他のSNSにも伝わります。ちょうどある惑星から他の惑星に電波を発信している様子をイメージしてください。"
|
about: "Misskeyはオープンソースの<b>分散型マイクロブログSNS</b>です。リッチで高度にカスタマイズできるUI、投稿へのリアクション、ファイルを一元管理できるドライブなど、先進的な機能を揃えています。また、Fediverseと呼ばれるネットワークに接続できるため、他のSNSともやり取りできます。例えば、あなたが何か投稿すると、その投稿はMisskeyだけでなく他のSNSにも伝わります。ちょうどある惑星から他の惑星に電波を発信している様子をイメージしてください。"
|
||||||
features: "特徴"
|
features: "특징"
|
||||||
rich-contents: "投稿"
|
rich-contents: "게시"
|
||||||
rich-contents-desc: "自分の考え、話題の出来事、皆と共有したいことについて発信してください。必要であれば、様々な構文を使って投稿を装飾したり、好きな画像、動画などのファイルやアンケートを添付することもできます。"
|
rich-contents-desc: "自分の考え、話題の出来事、皆と共有したいことについて発信してください。必要であれば、様々な構文を使って投稿を装飾したり、好きな画像、動画などのファイルやアンケートを添付することもできます。"
|
||||||
reaction: "リアクション"
|
reaction: "반응"
|
||||||
reaction-desc: "あなたの気持ちを伝える最も簡単な方法です。Misskeyは、他のユーザーの投稿に様々なリアクションを付けることができます。いちどMisskeyのリアクション機能を体験してしまうと、もう「いいね」の概念しか存在しないSNSには戻れなくなるかもしれません。"
|
reaction-desc: "あなたの気持ちを伝える最も簡単な方法です。Misskeyは、他のユーザーの投稿に様々なリアクションを付けることができます。いちどMisskeyのリアクション機能を体験してしまうと、もう「いいね」の概念しか存在しないSNSには戻れなくなるかもしれません。"
|
||||||
ui: "インターフェース"
|
ui: "인터페이스"
|
||||||
ui-desc: "どのようなUIが使いやすいかは人それぞれです。だから、Misskeyは自由度の高いUIを持っています。レイアウトやデザインを調整したり、カスタマイズ可能な様々なウィジェットを配置したりして、自分だけのホームを作ってください。"
|
ui-desc: "どのようなUIが使いやすいかは人それぞれです。だから、Misskeyは自由度の高いUIを持っています。レイアウトやデザインを調整したり、カスタマイズ可能な様々なウィジェットを配置したりして、自分だけのホームを作ってください。"
|
||||||
drive: "ドライブ"
|
drive: "드라이브"
|
||||||
drive-desc: "以前投稿したことのある画像をまた投稿したくなったことはありませんか?もしくは、アップロードしたファイルをフォルダ分けして整理したくなったことはありませんか?Misskeyの根幹に組み込まれたドライブ機能によってそれらが解決します。ファイルの共有も簡単です。"
|
drive-desc: "以前投稿したことのある画像をまた投稿したくなったことはありませんか?もしくは、アップロードしたファイルをフォルダ分けして整理したくなったことはありませんか?Misskeyの根幹に組み込まれたドライブ機能によってそれらが解決します。ファイルの共有も簡単です。"
|
||||||
outro: "他にもMisskeyにしかない機能はまだまだあるので、ぜひあなた自身の目で確かめてください。Misskeyは分散型SNSなので、このインスタンスが気に入らなければ他のインスタンスを試すこともできます。それでは、GLHF!"
|
outro: "他にもMisskeyにしかない機能はまだまだあるので、ぜひあなた自身の目で確かめてください。Misskeyは分散型SNSなので、このインスタンスが気に入らなければ他のインスタンスを試すこともできます。それでは、GLHF!"
|
||||||
adblock:
|
adblock:
|
||||||
@ -71,7 +71,7 @@ common:
|
|||||||
friday: "금요일"
|
friday: "금요일"
|
||||||
saturday: "토요일"
|
saturday: "토요일"
|
||||||
reactions:
|
reactions:
|
||||||
like: "いいね"
|
like: "좋아요"
|
||||||
love: "좋아"
|
love: "좋아"
|
||||||
laugh: "크크"
|
laugh: "크크"
|
||||||
hmm: "음..."
|
hmm: "음..."
|
||||||
@ -82,14 +82,14 @@ common:
|
|||||||
rip: "RIP"
|
rip: "RIP"
|
||||||
pudding: "Pudding"
|
pudding: "Pudding"
|
||||||
note-visibility:
|
note-visibility:
|
||||||
public: "公開"
|
public: "공개"
|
||||||
home: "ホーム"
|
home: "홈"
|
||||||
home-desc: "ホームタイムラインにのみ公開"
|
home-desc: "홈 타임라인에만 공개"
|
||||||
followers: "フォロワー"
|
followers: "팔로워"
|
||||||
followers-desc: "自分のフォロワーにのみ公開"
|
followers-desc: "자신의 팔로워에게만 공개"
|
||||||
specified: "ダイレクト"
|
specified: "다이렉트"
|
||||||
specified-desc: "指定したユーザーにのみ公開"
|
specified-desc: "지정한 사용자에게만 공개"
|
||||||
private: "非公開"
|
private: "비공개"
|
||||||
note-placeholders:
|
note-placeholders:
|
||||||
a: "지금 어떻게하고있어?"
|
a: "지금 어떻게하고있어?"
|
||||||
b: "뭔가 있었습니까?"
|
b: "뭔가 있었습니까?"
|
||||||
@ -107,14 +107,14 @@ common:
|
|||||||
i-like-sushi: "나는(푸딩보다 오히려)스시가 좋아"
|
i-like-sushi: "나는(푸딩보다 오히려)스시가 좋아"
|
||||||
show-reversi-board-labels: "리버시 보드의 행과 열 레이블을 표시"
|
show-reversi-board-labels: "리버시 보드의 행과 열 레이블을 표시"
|
||||||
use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける"
|
use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける"
|
||||||
verified-user: "公式アカウント"
|
verified-user: "공식 계정"
|
||||||
disable-animated-mfm: "게시물의 문자 애니메이션을 비활성화 할"
|
disable-animated-mfm: "게시물의 문자 애니메이션을 비활성화 할"
|
||||||
always-show-nsfw: "常に閲覧注意のメディアを表示する"
|
always-show-nsfw: "항상 열람주의 미디어를 표시"
|
||||||
always-mark-nsfw: "常にメディアを閲覧注意として投稿"
|
always-mark-nsfw: "항상 미디어를 열람주의로 설정하여 게시"
|
||||||
show-full-acct: "ユーザー名のホストを省略しない"
|
show-full-acct: "ユーザー名のホストを省略しない"
|
||||||
reduce-motion: "UIの動きを減らす"
|
reduce-motion: "UIの動きを減らす"
|
||||||
this-setting-is-this-device-only: "このデバイスのみ"
|
this-setting-is-this-device-only: "이 장치만"
|
||||||
do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。'
|
do-not-use-in-production: '이것은 개발 빌드입니다. 프로덕션 환경에서 사용하지 마십시오.'
|
||||||
reversi:
|
reversi:
|
||||||
drawn: "무승부"
|
drawn: "무승부"
|
||||||
my-turn: "당신의 차례입니다"
|
my-turn: "당신의 차례입니다"
|
||||||
@ -155,19 +155,19 @@ common:
|
|||||||
home: "홈"
|
home: "홈"
|
||||||
local: "로컬"
|
local: "로컬"
|
||||||
hybrid: "소셜"
|
hybrid: "소셜"
|
||||||
hashtag: "ハッシュタグ"
|
hashtag: "해시태그"
|
||||||
global: "글로벌"
|
global: "글로벌"
|
||||||
mentions: "あなた宛て"
|
mentions: "あなた宛て"
|
||||||
direct: "ダイレクト投稿"
|
direct: "ダイレクト投稿"
|
||||||
notifications: "통지"
|
notifications: "통지"
|
||||||
list: "목록"
|
list: "목록"
|
||||||
swap-left: "左に移動"
|
swap-left: "왼쪽으로 이동"
|
||||||
swap-right: "右に移動"
|
swap-right: "오른쪽으로 이동"
|
||||||
swap-up: "上に移動"
|
swap-up: "위로 이동"
|
||||||
swap-down: "下に移動"
|
swap-down: "아래로 이동"
|
||||||
remove: "カラムを削除"
|
remove: "칼럼 제거"
|
||||||
add-column: "カラムを追加"
|
add-column: "칼럼 추가"
|
||||||
rename: "名前を変更"
|
rename: "이름 변경"
|
||||||
stack-left: "左に重ねる"
|
stack-left: "左に重ねる"
|
||||||
pop-right: "右に出す"
|
pop-right: "右に出す"
|
||||||
auth/views/form.vue:
|
auth/views/form.vue:
|
||||||
@ -248,44 +248,47 @@ common/views/components/connect-failed.troubleshooter.vue:
|
|||||||
checking-network: "ネットワーク接続を確認中"
|
checking-network: "ネットワーク接続を確認中"
|
||||||
internet: "インターネット接続"
|
internet: "インターネット接続"
|
||||||
checking-internet: "インターネット接続を確認中"
|
checking-internet: "インターネット接続を確認中"
|
||||||
server: "サーバー接続"
|
server: "서버 연결"
|
||||||
checking-server: "サーバー接続を確認中"
|
checking-server: "サーバー接続を確認中"
|
||||||
finding: "問題を調べています"
|
finding: "問題を調べています"
|
||||||
no-network: "ネットワークに接続されていません"
|
no-network: "ネットワークに接続されていません"
|
||||||
no-network-desc: "お使いのPCのネットワーク接続が正常か確認してください。"
|
no-network-desc: "お使いのPCのネットワーク接続が正常か確認してください。"
|
||||||
no-internet: "インターネットに接続されていません"
|
no-internet: "インターネットに接続されていません"
|
||||||
no-internet-desc: "ネットワークには接続されていますが、インターネットには接続されていないようです。お使いのPCのインターネット接続が正常か確認してください。"
|
no-internet-desc: "ネットワークには接続されていますが、インターネットには接続されていないようです。お使いのPCのインターネット接続が正常か確認してください。"
|
||||||
no-server: "Misskeyのサーバーに接続できません"
|
no-server: "Misskey 서버에 연결할 수 없습니다."
|
||||||
no-server-desc: "お使いのPCのインターネット接続は正常ですが、Misskeyのサーバーには接続できませんでした。サーバーがダウンまたはメンテナンスしている可能性があるので、しばらくしてから再度御アクセスください。"
|
no-server-desc: "お使いのPCのインターネット接続は正常ですが、Misskeyのサーバーには接続できませんでした。サーバーがダウンまたはメンテナンスしている可能性があるので、しばらくしてから再度御アクセスください。"
|
||||||
success: "Misskeyのサーバーに接続できました"
|
success: "Misskeyのサーバーに接続できました"
|
||||||
success-desc: "正常に接続できるようです。ページを再度読み込みしてください。"
|
success-desc: "正常に接続できるようです。ページを再度読み込みしてください。"
|
||||||
flush: "キャッシュの削除"
|
flush: "캐시 삭제"
|
||||||
set-version: "バージョン指定"
|
set-version: "버전 지정"
|
||||||
common/views/components/media-banner.vue:
|
common/views/components/media-banner.vue:
|
||||||
sensitive: "閲覧注意"
|
sensitive: "열람주의"
|
||||||
click-to-show: "クリックして表示"
|
click-to-show: "클릭하여 표시"
|
||||||
common/views/components/theme.vue:
|
common/views/components/theme.vue:
|
||||||
light-theme: "非ダークモード時に使用するテーマ"
|
light-theme: "非ダークモード時に使用するテーマ"
|
||||||
dark-theme: "ダークモード時に使用するテーマ"
|
dark-theme: "ダークモード時に使用するテーマ"
|
||||||
light-themes: "明るいテーマ"
|
light-themes: "밝은 테마"
|
||||||
dark-themes: "暗いテーマ"
|
dark-themes: "어두운 테마"
|
||||||
install-a-theme: "テーマのインストール"
|
install-a-theme: "테마 설치"
|
||||||
theme-code: "テーマコード"
|
theme-code: "테마 코드"
|
||||||
install: "インストール"
|
install: "설치"
|
||||||
installed: "「{}」をインストールしました"
|
installed: "「{}」をインストールしました"
|
||||||
create-a-theme: "テーマの作成"
|
create-a-theme: "테마 만들기"
|
||||||
save-created-theme: "テーマを保存"
|
save-created-theme: "테마 저장"
|
||||||
primary-color: "プライマリ カラー"
|
primary-color: "기본 색"
|
||||||
secondary-color: "セカンダリ カラー"
|
secondary-color: "보조 색"
|
||||||
text-color: "文字色"
|
text-color: "글자 색상"
|
||||||
base-theme: "ベーステーマ"
|
base-theme: "ベーステーマ"
|
||||||
base-theme-light: "Light"
|
base-theme-light: "Light"
|
||||||
base-theme-dark: "Dark"
|
base-theme-dark: "Dark"
|
||||||
theme-name: "テーマ名"
|
theme-name: "테마명"
|
||||||
preview-created-theme: "プレビュー"
|
preview-created-theme: "미리보기"
|
||||||
invalid-theme: "テーマが正しくありません。"
|
invalid-theme: "テーマが正しくありません。"
|
||||||
already-installed: "既にそのテーマはインストールされています。"
|
already-installed: "既にそのテーマはインストールされています。"
|
||||||
saved: "保存しました"
|
saved: "保存しました"
|
||||||
|
manage-themes: "テーマの管理"
|
||||||
|
builtin-themes: "標準テーマ"
|
||||||
|
my-themes: "マイテーマ"
|
||||||
installed-themes: "インストールされたテーマ"
|
installed-themes: "インストールされたテーマ"
|
||||||
select-theme: "テーマを選択してください"
|
select-theme: "テーマを選択してください"
|
||||||
uninstall: "アンインストール"
|
uninstall: "アンインストール"
|
||||||
@ -305,7 +308,7 @@ common/views/components/messaging.vue:
|
|||||||
no-history: "履歴はありません"
|
no-history: "履歴はありません"
|
||||||
common/views/components/messaging-room.vue:
|
common/views/components/messaging-room.vue:
|
||||||
empty: "このユーザーと話したことはありません"
|
empty: "このユーザーと話したことはありません"
|
||||||
more: "もっと読む"
|
more: "더 보기"
|
||||||
no-history: "これより過去の履歴はありません"
|
no-history: "これより過去の履歴はありません"
|
||||||
resize-form: "ドラッグしてフォームの広さを調整"
|
resize-form: "ドラッグしてフォームの広さを調整"
|
||||||
new-message: "新しいメッセージがあります"
|
new-message: "新しいメッセージがあります"
|
||||||
@ -320,18 +323,19 @@ common/views/components/messaging-room.message.vue:
|
|||||||
is-read: "읽음"
|
is-read: "읽음"
|
||||||
deleted: "このメッセージは削除されました"
|
deleted: "このメッセージは削除されました"
|
||||||
common/views/components/nav.vue:
|
common/views/components/nav.vue:
|
||||||
about: "Misskeyについて"
|
about: "Misskey에 대하여"
|
||||||
stats: "統計"
|
stats: "통계"
|
||||||
status: "ステータス"
|
status: "ステータス"
|
||||||
wiki: "Wiki"
|
wiki: "Wiki"
|
||||||
donors: "ドナー"
|
donors: "기증자"
|
||||||
repository: "リポジトリ"
|
repository: "저장소"
|
||||||
develop: "開発者"
|
develop: "개발자"
|
||||||
feedback: "フィードバック"
|
feedback: "피드백"
|
||||||
common/views/components/note-menu.vue:
|
common/views/components/note-menu.vue:
|
||||||
detail: "詳細"
|
detail: "詳細"
|
||||||
copy-link: "リンクをコピー"
|
copy-link: "링크 복사"
|
||||||
favorite: "お気に入り"
|
favorite: "お気に入り"
|
||||||
|
unfavorite: "お気に入り解除"
|
||||||
pin: "ピン留め"
|
pin: "ピン留め"
|
||||||
unpin: "ピン留め解除"
|
unpin: "ピン留め解除"
|
||||||
delete: "削除"
|
delete: "削除"
|
||||||
|
@ -286,6 +286,9 @@ common/views/components/theme.vue:
|
|||||||
invalid-theme: "テーマが正しくありません。"
|
invalid-theme: "テーマが正しくありません。"
|
||||||
already-installed: "既にそのテーマはインストールされています。"
|
already-installed: "既にそのテーマはインストールされています。"
|
||||||
saved: "保存しました"
|
saved: "保存しました"
|
||||||
|
manage-themes: "テーマの管理"
|
||||||
|
builtin-themes: "標準テーマ"
|
||||||
|
my-themes: "マイテーマ"
|
||||||
installed-themes: "インストールされたテーマ"
|
installed-themes: "インストールされたテーマ"
|
||||||
select-theme: "テーマを選択してください"
|
select-theme: "テーマを選択してください"
|
||||||
uninstall: "アンインストール"
|
uninstall: "アンインストール"
|
||||||
@ -332,6 +335,7 @@ common/views/components/note-menu.vue:
|
|||||||
detail: "詳細"
|
detail: "詳細"
|
||||||
copy-link: "リンクをコピー"
|
copy-link: "リンクをコピー"
|
||||||
favorite: "Deze notitie toevoegen aan favorieten"
|
favorite: "Deze notitie toevoegen aan favorieten"
|
||||||
|
unfavorite: "お気に入り解除"
|
||||||
pin: "Vastmaken aan profielpagina"
|
pin: "Vastmaken aan profielpagina"
|
||||||
unpin: "ピン留め解除"
|
unpin: "ピン留め解除"
|
||||||
delete: "削除"
|
delete: "削除"
|
||||||
|
@ -286,6 +286,9 @@ common/views/components/theme.vue:
|
|||||||
invalid-theme: "テーマが正しくありません。"
|
invalid-theme: "テーマが正しくありません。"
|
||||||
already-installed: "既にそのテーマはインストールされています。"
|
already-installed: "既にそのテーマはインストールされています。"
|
||||||
saved: "保存しました"
|
saved: "保存しました"
|
||||||
|
manage-themes: "テーマの管理"
|
||||||
|
builtin-themes: "標準テーマ"
|
||||||
|
my-themes: "マイテーマ"
|
||||||
installed-themes: "インストールされたテーマ"
|
installed-themes: "インストールされたテーマ"
|
||||||
select-theme: "テーマを選択してください"
|
select-theme: "テーマを選択してください"
|
||||||
uninstall: "アンインストール"
|
uninstall: "アンインストール"
|
||||||
@ -332,6 +335,7 @@ common/views/components/note-menu.vue:
|
|||||||
detail: "Detaljer"
|
detail: "Detaljer"
|
||||||
copy-link: "リンクをコピー"
|
copy-link: "リンクをコピー"
|
||||||
favorite: "Merket som favoritt"
|
favorite: "Merket som favoritt"
|
||||||
|
unfavorite: "お気に入り解除"
|
||||||
pin: "Fest til profilen din"
|
pin: "Fest til profilen din"
|
||||||
unpin: "ピン留め解除"
|
unpin: "ピン留め解除"
|
||||||
delete: "Slett"
|
delete: "Slett"
|
||||||
|
@ -286,6 +286,9 @@ common/views/components/theme.vue:
|
|||||||
invalid-theme: "テーマが正しくありません。"
|
invalid-theme: "テーマが正しくありません。"
|
||||||
already-installed: "既にそのテーマはインストールされています。"
|
already-installed: "既にそのテーマはインストールされています。"
|
||||||
saved: "保存しました"
|
saved: "保存しました"
|
||||||
|
manage-themes: "テーマの管理"
|
||||||
|
builtin-themes: "標準テーマ"
|
||||||
|
my-themes: "マイテーマ"
|
||||||
installed-themes: "インストールされたテーマ"
|
installed-themes: "インストールされたテーマ"
|
||||||
select-theme: "テーマを選択してください"
|
select-theme: "テーマを選択してください"
|
||||||
uninstall: "アンインストール"
|
uninstall: "アンインストール"
|
||||||
@ -332,6 +335,7 @@ common/views/components/note-menu.vue:
|
|||||||
detail: "詳細"
|
detail: "詳細"
|
||||||
copy-link: "リンクをコピー"
|
copy-link: "リンクをコピー"
|
||||||
favorite: "Dodaj do ulubionych"
|
favorite: "Dodaj do ulubionych"
|
||||||
|
unfavorite: "お気に入り解除"
|
||||||
pin: "Przypnij do profilu"
|
pin: "Przypnij do profilu"
|
||||||
unpin: "ピン留め解除"
|
unpin: "ピン留め解除"
|
||||||
delete: "Usuń"
|
delete: "Usuń"
|
||||||
|
@ -286,6 +286,9 @@ common/views/components/theme.vue:
|
|||||||
invalid-theme: "テーマが正しくありません。"
|
invalid-theme: "テーマが正しくありません。"
|
||||||
already-installed: "既にそのテーマはインストールされています。"
|
already-installed: "既にそのテーマはインストールされています。"
|
||||||
saved: "保存しました"
|
saved: "保存しました"
|
||||||
|
manage-themes: "テーマの管理"
|
||||||
|
builtin-themes: "標準テーマ"
|
||||||
|
my-themes: "マイテーマ"
|
||||||
installed-themes: "インストールされたテーマ"
|
installed-themes: "インストールされたテーマ"
|
||||||
select-theme: "テーマを選択してください"
|
select-theme: "テーマを選択してください"
|
||||||
uninstall: "アンインストール"
|
uninstall: "アンインストール"
|
||||||
@ -332,6 +335,7 @@ common/views/components/note-menu.vue:
|
|||||||
detail: "詳細"
|
detail: "詳細"
|
||||||
copy-link: "リンクをコピー"
|
copy-link: "リンクをコピー"
|
||||||
favorite: "お気に入り"
|
favorite: "お気に入り"
|
||||||
|
unfavorite: "お気に入り解除"
|
||||||
pin: "ピン留め"
|
pin: "ピン留め"
|
||||||
unpin: "ピン留め解除"
|
unpin: "ピン留め解除"
|
||||||
delete: "削除"
|
delete: "削除"
|
||||||
|
@ -286,6 +286,9 @@ common/views/components/theme.vue:
|
|||||||
invalid-theme: "テーマが正しくありません。"
|
invalid-theme: "テーマが正しくありません。"
|
||||||
already-installed: "既にそのテーマはインストールされています。"
|
already-installed: "既にそのテーマはインストールされています。"
|
||||||
saved: "保存しました"
|
saved: "保存しました"
|
||||||
|
manage-themes: "テーマの管理"
|
||||||
|
builtin-themes: "標準テーマ"
|
||||||
|
my-themes: "マイテーマ"
|
||||||
installed-themes: "インストールされたテーマ"
|
installed-themes: "インストールされたテーマ"
|
||||||
select-theme: "テーマを選択してください"
|
select-theme: "テーマを選択してください"
|
||||||
uninstall: "アンインストール"
|
uninstall: "アンインストール"
|
||||||
@ -332,6 +335,7 @@ common/views/components/note-menu.vue:
|
|||||||
detail: "詳細"
|
detail: "詳細"
|
||||||
copy-link: "リンクをコピー"
|
copy-link: "リンクをコピー"
|
||||||
favorite: "お気に入り"
|
favorite: "お気に入り"
|
||||||
|
unfavorite: "お気に入り解除"
|
||||||
pin: "ピン留め"
|
pin: "ピン留め"
|
||||||
unpin: "ピン留め解除"
|
unpin: "ピン留め解除"
|
||||||
delete: "削除"
|
delete: "削除"
|
||||||
|
@ -286,6 +286,9 @@ common/views/components/theme.vue:
|
|||||||
invalid-theme: "テーマが正しくありません。"
|
invalid-theme: "テーマが正しくありません。"
|
||||||
already-installed: "既にそのテーマはインストールされています。"
|
already-installed: "既にそのテーマはインストールされています。"
|
||||||
saved: "保存しました"
|
saved: "保存しました"
|
||||||
|
manage-themes: "テーマの管理"
|
||||||
|
builtin-themes: "標準テーマ"
|
||||||
|
my-themes: "マイテーマ"
|
||||||
installed-themes: "インストールされたテーマ"
|
installed-themes: "インストールされたテーマ"
|
||||||
select-theme: "テーマを選択してください"
|
select-theme: "テーマを選択してください"
|
||||||
uninstall: "アンインストール"
|
uninstall: "アンインストール"
|
||||||
@ -332,6 +335,7 @@ common/views/components/note-menu.vue:
|
|||||||
detail: "詳細"
|
detail: "詳細"
|
||||||
copy-link: "リンクをコピー"
|
copy-link: "リンクをコピー"
|
||||||
favorite: "お気に入り"
|
favorite: "お気に入り"
|
||||||
|
unfavorite: "お気に入り解除"
|
||||||
pin: "ピン留め"
|
pin: "ピン留め"
|
||||||
unpin: "ピン留め解除"
|
unpin: "ピン留め解除"
|
||||||
delete: "削除"
|
delete: "削除"
|
||||||
|
33
package.json
33
package.json
@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "misskey",
|
"name": "misskey",
|
||||||
"author": "syuilo <i@syuilo.com>",
|
"author": "syuilo <i@syuilo.com>",
|
||||||
"version": "10.0.0",
|
"version": "10.17.0",
|
||||||
"clientVersion": "1.0.10328",
|
"clientVersion": "1.0.10536",
|
||||||
"codename": "nighthike",
|
"codename": "nighthike",
|
||||||
"main": "./built/index.js",
|
"main": "./built/index.js",
|
||||||
"private": true,
|
"private": true,
|
||||||
@ -32,7 +32,7 @@
|
|||||||
"@types/debug": "0.0.31",
|
"@types/debug": "0.0.31",
|
||||||
"@types/deep-equal": "1.0.1",
|
"@types/deep-equal": "1.0.1",
|
||||||
"@types/double-ended-queue": "2.1.0",
|
"@types/double-ended-queue": "2.1.0",
|
||||||
"@types/elasticsearch": "5.0.26",
|
"@types/elasticsearch": "5.0.27",
|
||||||
"@types/file-type": "5.2.1",
|
"@types/file-type": "5.2.1",
|
||||||
"@types/gulp": "3.8.36",
|
"@types/gulp": "3.8.36",
|
||||||
"@types/gulp-htmlmin": "1.3.32",
|
"@types/gulp-htmlmin": "1.3.32",
|
||||||
@ -48,7 +48,7 @@
|
|||||||
"@types/koa-bodyparser": "5.0.1",
|
"@types/koa-bodyparser": "5.0.1",
|
||||||
"@types/koa-compress": "2.0.8",
|
"@types/koa-compress": "2.0.8",
|
||||||
"@types/koa-favicon": "2.0.19",
|
"@types/koa-favicon": "2.0.19",
|
||||||
"@types/koa-logger": "3.1.0",
|
"@types/koa-logger": "3.1.1",
|
||||||
"@types/koa-mount": "3.0.1",
|
"@types/koa-mount": "3.0.1",
|
||||||
"@types/koa-multer": "1.0.0",
|
"@types/koa-multer": "1.0.0",
|
||||||
"@types/koa-router": "7.0.32",
|
"@types/koa-router": "7.0.32",
|
||||||
@ -58,14 +58,14 @@
|
|||||||
"@types/minio": "7.0.0",
|
"@types/minio": "7.0.0",
|
||||||
"@types/mkdirp": "0.5.2",
|
"@types/mkdirp": "0.5.2",
|
||||||
"@types/mocha": "5.2.3",
|
"@types/mocha": "5.2.3",
|
||||||
"@types/mongodb": "3.1.10",
|
"@types/mongodb": "3.1.12",
|
||||||
"@types/ms": "0.7.30",
|
"@types/ms": "0.7.30",
|
||||||
"@types/node": "10.11.4",
|
"@types/node": "10.11.7",
|
||||||
"@types/portscanner": "2.1.0",
|
"@types/portscanner": "2.1.0",
|
||||||
"@types/pug": "2.0.4",
|
"@types/pug": "2.0.4",
|
||||||
"@types/qrcode": "1.3.0",
|
"@types/qrcode": "1.3.0",
|
||||||
"@types/ratelimiter": "2.1.28",
|
"@types/ratelimiter": "2.1.28",
|
||||||
"@types/redis": "2.8.6",
|
"@types/redis": "2.8.7",
|
||||||
"@types/request": "2.47.1",
|
"@types/request": "2.47.1",
|
||||||
"@types/request-promise-native": "1.0.15",
|
"@types/request-promise-native": "1.0.15",
|
||||||
"@types/rimraf": "2.0.2",
|
"@types/rimraf": "2.0.2",
|
||||||
@ -78,7 +78,7 @@
|
|||||||
"@types/tinycolor2": "1.4.1",
|
"@types/tinycolor2": "1.4.1",
|
||||||
"@types/tmp": "0.0.33",
|
"@types/tmp": "0.0.33",
|
||||||
"@types/uuid": "3.4.4",
|
"@types/uuid": "3.4.4",
|
||||||
"@types/webpack": "4.4.14",
|
"@types/webpack": "4.4.16",
|
||||||
"@types/webpack-stream": "3.2.10",
|
"@types/webpack-stream": "3.2.10",
|
||||||
"@types/websocket": "0.0.40",
|
"@types/websocket": "0.0.40",
|
||||||
"@types/ws": "6.0.1",
|
"@types/ws": "6.0.1",
|
||||||
@ -92,11 +92,11 @@
|
|||||||
"cafy": "11.3.0",
|
"cafy": "11.3.0",
|
||||||
"chalk": "2.4.1",
|
"chalk": "2.4.1",
|
||||||
"chart.js": "2.7.2",
|
"chart.js": "2.7.2",
|
||||||
"commander": "2.17.1",
|
"commander": "2.19.0",
|
||||||
"crc-32": "1.2.0",
|
"crc-32": "1.2.0",
|
||||||
"css-loader": "1.0.0",
|
"css-loader": "1.0.0",
|
||||||
"dateformat": "3.0.3",
|
"dateformat": "3.0.3",
|
||||||
"debug": "4.0.1",
|
"debug": "4.1.0",
|
||||||
"deep-equal": "1.0.1",
|
"deep-equal": "1.0.1",
|
||||||
"deepcopy": "0.6.3",
|
"deepcopy": "0.6.3",
|
||||||
"diskusage": "0.2.5",
|
"diskusage": "0.2.5",
|
||||||
@ -169,13 +169,14 @@
|
|||||||
"parse5": "5.1.0",
|
"parse5": "5.1.0",
|
||||||
"portscanner": "2.2.0",
|
"portscanner": "2.2.0",
|
||||||
"progress-bar-webpack-plugin": "1.11.0",
|
"progress-bar-webpack-plugin": "1.11.0",
|
||||||
|
"promise-limit": "2.7.0",
|
||||||
"promise-sequential": "1.1.1",
|
"promise-sequential": "1.1.1",
|
||||||
"pug": "2.0.3",
|
"pug": "2.0.3",
|
||||||
"punycode": "2.1.1",
|
"punycode": "2.1.1",
|
||||||
"qrcode": "1.3.0",
|
"qrcode": "1.3.0",
|
||||||
"ratelimiter": "3.2.0",
|
"ratelimiter": "3.2.0",
|
||||||
"recaptcha-promise": "0.1.3",
|
"recaptcha-promise": "0.1.3",
|
||||||
"reconnecting-websocket": "4.1.5",
|
"reconnecting-websocket": "4.1.8",
|
||||||
"redis": "2.8.0",
|
"redis": "2.8.0",
|
||||||
"request": "2.88.0",
|
"request": "2.88.0",
|
||||||
"request-promise-native": "1.0.5",
|
"request-promise-native": "1.0.5",
|
||||||
@ -191,7 +192,7 @@
|
|||||||
"single-line-log": "1.1.2",
|
"single-line-log": "1.1.2",
|
||||||
"speakeasy": "2.0.0",
|
"speakeasy": "2.0.0",
|
||||||
"stringz": "1.0.0",
|
"stringz": "1.0.0",
|
||||||
"style-loader": "0.23.0",
|
"style-loader": "0.23.1",
|
||||||
"stylus": "0.54.5",
|
"stylus": "0.54.5",
|
||||||
"stylus-loader": "3.0.2",
|
"stylus-loader": "3.0.2",
|
||||||
"summaly": "2.2.0",
|
"summaly": "2.2.0",
|
||||||
@ -204,14 +205,15 @@
|
|||||||
"ts-node": "7.0.1",
|
"ts-node": "7.0.1",
|
||||||
"tslint": "5.10.0",
|
"tslint": "5.10.0",
|
||||||
"typescript": "2.9.2",
|
"typescript": "2.9.2",
|
||||||
"typescript-eslint-parser": "19.0.2",
|
"typescript-eslint-parser": "20.0.0",
|
||||||
"uglify-es": "3.3.9",
|
"uglify-es": "3.3.9",
|
||||||
"url-loader": "1.1.1",
|
"url-loader": "1.1.2",
|
||||||
"uuid": "3.3.2",
|
"uuid": "3.3.2",
|
||||||
"v-animate-css": "0.0.2",
|
"v-animate-css": "0.0.2",
|
||||||
"vue": "2.5.17",
|
"vue": "2.5.17",
|
||||||
"vue-chartjs": "3.4.0",
|
"vue-chartjs": "3.4.0",
|
||||||
"vue-color": "2.6.0",
|
"vue-color": "2.7.0",
|
||||||
|
"vue-content-loading": "1.5.3",
|
||||||
"vue-cropperjs": "2.2.2",
|
"vue-cropperjs": "2.2.2",
|
||||||
"vue-js-modal": "1.3.26",
|
"vue-js-modal": "1.3.26",
|
||||||
"vue-json-tree-view": "2.1.4",
|
"vue-json-tree-view": "2.1.4",
|
||||||
@ -219,6 +221,7 @@
|
|||||||
"vue-router": "3.0.1",
|
"vue-router": "3.0.1",
|
||||||
"vue-style-loader": "4.1.2",
|
"vue-style-loader": "4.1.2",
|
||||||
"vue-svg-inline-loader": "1.2.0",
|
"vue-svg-inline-loader": "1.2.0",
|
||||||
|
"vue-sweetalert2": "1.5.5",
|
||||||
"vue-template-compiler": "2.5.17",
|
"vue-template-compiler": "2.5.17",
|
||||||
"vuedraggable": "2.16.0",
|
"vuedraggable": "2.16.0",
|
||||||
"vuewordcloud": "18.7.11",
|
"vuewordcloud": "18.7.11",
|
||||||
|
@ -130,3 +130,29 @@ pre
|
|||||||
|
|
||||||
[data-fa]
|
[data-fa]
|
||||||
display inline-block
|
display inline-block
|
||||||
|
|
||||||
|
.swal2-container
|
||||||
|
z-index 10000 !important
|
||||||
|
|
||||||
|
&.swal2-shown
|
||||||
|
background-color rgba(0, 0, 0, 0.5) !important
|
||||||
|
|
||||||
|
.swal2-popup
|
||||||
|
background var(--face) !important
|
||||||
|
|
||||||
|
.swal2-content
|
||||||
|
color var(--text) !important
|
||||||
|
|
||||||
|
.swal2-confirm
|
||||||
|
background-color var(--primary) !important
|
||||||
|
border-left-color var(--primary) !important
|
||||||
|
border-right-color var(--primary) !important
|
||||||
|
color var(--primaryForeground) !important
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
background-image none !important
|
||||||
|
background-color var(--primaryDarken5) !important
|
||||||
|
|
||||||
|
&:active
|
||||||
|
background-image none !important
|
||||||
|
background-color var(--primaryDarken5) !important
|
||||||
|
@ -142,7 +142,7 @@
|
|||||||
localStorage.setItem('shouldFlush', 'false');
|
localStorage.setItem('shouldFlush', 'false');
|
||||||
|
|
||||||
// Random
|
// Random
|
||||||
localStorage.setItem('salt', Math.random().toString());
|
localStorage.setItem('salt', Math.random().toString().substr(2, 8));
|
||||||
|
|
||||||
// Clear cache (service worker)
|
// Clear cache (service worker)
|
||||||
try {
|
try {
|
||||||
|
178
src/client/app/common/scripts/note-mixin.ts
Normal file
178
src/client/app/common/scripts/note-mixin.ts
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
import parse from '../../../../mfm/parse';
|
||||||
|
import { sum } from '../../../../prelude/array';
|
||||||
|
import MkNoteMenu from '../views/components/note-menu.vue';
|
||||||
|
import MkReactionPicker from '../views/components/reaction-picker.vue';
|
||||||
|
import Ok from '../views/components/ok.vue';
|
||||||
|
|
||||||
|
function focus(el, fn) {
|
||||||
|
const target = fn(el);
|
||||||
|
if (target) {
|
||||||
|
if (target.hasAttribute('tabindex')) {
|
||||||
|
target.focus();
|
||||||
|
} else {
|
||||||
|
focus(target, fn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Opts = {
|
||||||
|
mobile?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default (opts: Opts = {}) => ({
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
showContent: false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
keymap(): any {
|
||||||
|
return {
|
||||||
|
'r|left': () => this.reply(true),
|
||||||
|
'e|a|plus': () => this.react(true),
|
||||||
|
'q|right': () => this.renote(true),
|
||||||
|
'f|b': this.favorite,
|
||||||
|
'delete|ctrl+d': this.del,
|
||||||
|
'ctrl+q|ctrl+right': this.renoteDirectly,
|
||||||
|
'up|k|shift+tab': this.focusBefore,
|
||||||
|
'down|j|tab': this.focusAfter,
|
||||||
|
'esc': this.blur,
|
||||||
|
'm|o': () => this.menu(true),
|
||||||
|
's': this.toggleShowContent,
|
||||||
|
'1': () => this.reactDirectly('like'),
|
||||||
|
'2': () => this.reactDirectly('love'),
|
||||||
|
'3': () => this.reactDirectly('laugh'),
|
||||||
|
'4': () => this.reactDirectly('hmm'),
|
||||||
|
'5': () => this.reactDirectly('surprise'),
|
||||||
|
'6': () => this.reactDirectly('congrats'),
|
||||||
|
'7': () => this.reactDirectly('angry'),
|
||||||
|
'8': () => this.reactDirectly('confused'),
|
||||||
|
'9': () => this.reactDirectly('rip'),
|
||||||
|
'0': () => this.reactDirectly('pudding'),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
isRenote(): boolean {
|
||||||
|
return (this.note.renote &&
|
||||||
|
this.note.text == null &&
|
||||||
|
this.note.fileIds.length == 0 &&
|
||||||
|
this.note.poll == null);
|
||||||
|
},
|
||||||
|
|
||||||
|
appearNote(): any {
|
||||||
|
return this.isRenote ? this.note.renote : this.note;
|
||||||
|
},
|
||||||
|
|
||||||
|
reactionsCount(): number {
|
||||||
|
return this.appearNote.reactionCounts
|
||||||
|
? sum(Object.values(this.appearNote.reactionCounts))
|
||||||
|
: 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
title(): string {
|
||||||
|
return new Date(this.appearNote.createdAt).toLocaleString();
|
||||||
|
},
|
||||||
|
|
||||||
|
urls(): string[] {
|
||||||
|
if (this.appearNote.text) {
|
||||||
|
const ast = parse(this.appearNote.text);
|
||||||
|
return ast
|
||||||
|
.filter(t => (t.type == 'url' || t.type == 'link') && !t.silent)
|
||||||
|
.map(t => t.url);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
reply(viaKeyboard = false) {
|
||||||
|
(this as any).apis.post({
|
||||||
|
reply: this.appearNote,
|
||||||
|
animation: !viaKeyboard,
|
||||||
|
cb: () => {
|
||||||
|
this.focus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
renote(viaKeyboard = false) {
|
||||||
|
(this as any).apis.post({
|
||||||
|
renote: this.appearNote,
|
||||||
|
animation: !viaKeyboard,
|
||||||
|
cb: () => {
|
||||||
|
this.focus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
renoteDirectly() {
|
||||||
|
(this as any).api('notes/create', {
|
||||||
|
renoteId: this.appearNote.id
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
react(viaKeyboard = false) {
|
||||||
|
this.blur();
|
||||||
|
(this as any).os.new(MkReactionPicker, {
|
||||||
|
source: this.$refs.reactButton,
|
||||||
|
note: this.appearNote,
|
||||||
|
showFocus: viaKeyboard,
|
||||||
|
animation: !viaKeyboard,
|
||||||
|
compact: opts.mobile,
|
||||||
|
big: opts.mobile
|
||||||
|
}).$once('closed', this.focus);
|
||||||
|
},
|
||||||
|
|
||||||
|
reactDirectly(reaction) {
|
||||||
|
(this as any).api('notes/reactions/create', {
|
||||||
|
noteId: this.appearNote.id,
|
||||||
|
reaction: reaction
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
favorite() {
|
||||||
|
(this as any).api('notes/favorites/create', {
|
||||||
|
noteId: this.appearNote.id
|
||||||
|
}).then(() => {
|
||||||
|
(this as any).os.new(Ok);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
del() {
|
||||||
|
(this as any).api('notes/delete', {
|
||||||
|
noteId: this.appearNote.id
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
menu(viaKeyboard = false) {
|
||||||
|
(this as any).os.new(MkNoteMenu, {
|
||||||
|
source: this.$refs.menuButton,
|
||||||
|
note: this.appearNote,
|
||||||
|
animation: !viaKeyboard,
|
||||||
|
compact: opts.mobile,
|
||||||
|
}).$once('closed', this.focus);
|
||||||
|
},
|
||||||
|
|
||||||
|
toggleShowContent() {
|
||||||
|
this.showContent = !this.showContent;
|
||||||
|
},
|
||||||
|
|
||||||
|
focus() {
|
||||||
|
this.$el.focus();
|
||||||
|
},
|
||||||
|
|
||||||
|
blur() {
|
||||||
|
this.$el.blur();
|
||||||
|
},
|
||||||
|
|
||||||
|
focusBefore() {
|
||||||
|
focus(this.$el, e => e.previousElementSibling);
|
||||||
|
},
|
||||||
|
|
||||||
|
focusAfter() {
|
||||||
|
focus(this.$el, e => e.nextElementSibling);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
@ -13,14 +13,14 @@ export default prop => ({
|
|||||||
},
|
},
|
||||||
|
|
||||||
$_ns_isRenote(): boolean {
|
$_ns_isRenote(): boolean {
|
||||||
return (this.$_ns_note_.renote &&
|
return (this.$_ns_note_.renote != null &&
|
||||||
this.$_ns_note_.text == null &&
|
this.$_ns_note_.text == null &&
|
||||||
this.$_ns_note_.fileIds.length == 0 &&
|
this.$_ns_note_.fileIds.length == 0 &&
|
||||||
this.$_ns_note_.poll == null);
|
this.$_ns_note_.poll == null);
|
||||||
},
|
},
|
||||||
|
|
||||||
$_ns_target(): any {
|
$_ns_target(): any {
|
||||||
return this._ns_isRenote ? this.$_ns_note_.renote : this.$_ns_note_;
|
return this.$_ns_isRenote ? this.$_ns_note_.renote : this.$_ns_note_;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -86,8 +86,20 @@ export default prop => ({
|
|||||||
switch (type) {
|
switch (type) {
|
||||||
case 'reacted': {
|
case 'reacted': {
|
||||||
const reaction = body.reaction;
|
const reaction = body.reaction;
|
||||||
if (this.$_ns_target.reactionCounts == null) Vue.set(this.$_ns_target, 'reactionCounts', {});
|
|
||||||
this.$_ns_target.reactionCounts[reaction] = (this.$_ns_target.reactionCounts[reaction] || 0) + 1;
|
if (this.$_ns_target.reactionCounts == null) {
|
||||||
|
Vue.set(this.$_ns_target, 'reactionCounts', {});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.$_ns_target.reactionCounts[reaction] == null) {
|
||||||
|
Vue.set(this.$_ns_target.reactionCounts, reaction, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$_ns_target.reactionCounts[reaction]++;
|
||||||
|
|
||||||
|
if (body.userId == this.$store.state.i.id) {
|
||||||
|
Vue.set(this.$_ns_target, 'myReaction', reaction);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,8 +9,8 @@ import MiOS from '../../mios';
|
|||||||
*/
|
*/
|
||||||
export default class Stream extends EventEmitter {
|
export default class Stream extends EventEmitter {
|
||||||
private stream: ReconnectingWebsocket;
|
private stream: ReconnectingWebsocket;
|
||||||
private state: string;
|
public state: string;
|
||||||
private buffer: any[];
|
private sharedConnectionPools: Pool[] = [];
|
||||||
private sharedConnections: SharedConnection[] = [];
|
private sharedConnections: SharedConnection[] = [];
|
||||||
private nonSharedConnections: NonSharedConnection[] = [];
|
private nonSharedConnections: NonSharedConnection[] = [];
|
||||||
|
|
||||||
@ -18,7 +18,6 @@ export default class Stream extends EventEmitter {
|
|||||||
super();
|
super();
|
||||||
|
|
||||||
this.state = 'initializing';
|
this.state = 'initializing';
|
||||||
this.buffer = [];
|
|
||||||
|
|
||||||
const user = os.store.state.i;
|
const user = os.store.state.i;
|
||||||
|
|
||||||
@ -26,114 +25,34 @@ export default class Stream extends EventEmitter {
|
|||||||
this.stream.addEventListener('open', this.onOpen);
|
this.stream.addEventListener('open', this.onOpen);
|
||||||
this.stream.addEventListener('close', this.onClose);
|
this.stream.addEventListener('close', this.onClose);
|
||||||
this.stream.addEventListener('message', this.onMessage);
|
this.stream.addEventListener('message', this.onMessage);
|
||||||
|
|
||||||
if (user) {
|
|
||||||
const main = this.useSharedConnection('main');
|
|
||||||
|
|
||||||
// 自分の情報が更新されたとき
|
|
||||||
main.on('meUpdated', i => {
|
|
||||||
os.store.dispatch('mergeMe', i);
|
|
||||||
});
|
|
||||||
|
|
||||||
main.on('readAllNotifications', () => {
|
|
||||||
os.store.dispatch('mergeMe', {
|
|
||||||
hasUnreadNotification: false
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
main.on('unreadNotification', () => {
|
|
||||||
os.store.dispatch('mergeMe', {
|
|
||||||
hasUnreadNotification: true
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
main.on('readAllMessagingMessages', () => {
|
|
||||||
os.store.dispatch('mergeMe', {
|
|
||||||
hasUnreadMessagingMessage: false
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
main.on('unreadMessagingMessage', () => {
|
|
||||||
os.store.dispatch('mergeMe', {
|
|
||||||
hasUnreadMessagingMessage: true
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
main.on('unreadMention', () => {
|
|
||||||
os.store.dispatch('mergeMe', {
|
|
||||||
hasUnreadMentions: true
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
main.on('readAllUnreadMentions', () => {
|
|
||||||
os.store.dispatch('mergeMe', {
|
|
||||||
hasUnreadMentions: false
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
main.on('unreadSpecifiedNote', () => {
|
|
||||||
os.store.dispatch('mergeMe', {
|
|
||||||
hasUnreadSpecifiedNotes: true
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
main.on('readAllUnreadSpecifiedNotes', () => {
|
|
||||||
os.store.dispatch('mergeMe', {
|
|
||||||
hasUnreadSpecifiedNotes: false
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
main.on('clientSettingUpdated', x => {
|
|
||||||
os.store.commit('settings/set', {
|
|
||||||
key: x.key,
|
|
||||||
value: x.value
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
main.on('homeUpdated', x => {
|
|
||||||
os.store.commit('settings/setHome', x);
|
|
||||||
});
|
|
||||||
|
|
||||||
main.on('mobileHomeUpdated', x => {
|
|
||||||
os.store.commit('settings/setMobileHome', x);
|
|
||||||
});
|
|
||||||
|
|
||||||
main.on('widgetUpdated', x => {
|
|
||||||
os.store.commit('settings/setWidget', {
|
|
||||||
id: x.id,
|
|
||||||
data: x.data
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// トークンが再生成されたとき
|
|
||||||
// このままではMisskeyが利用できないので強制的にサインアウトさせる
|
|
||||||
main.on('myTokenRegenerated', () => {
|
|
||||||
alert('%i18n:common.my-token-regenerated%');
|
|
||||||
os.signout();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public useSharedConnection = (channel: string): SharedConnection => {
|
@autobind
|
||||||
const existConnection = this.sharedConnections.find(c => c.channel === channel);
|
public useSharedConnection(channel: string): SharedConnection {
|
||||||
|
let pool = this.sharedConnectionPools.find(p => p.channel === channel);
|
||||||
|
|
||||||
if (existConnection) {
|
if (pool == null) {
|
||||||
existConnection.use();
|
pool = new Pool(this, channel);
|
||||||
return existConnection;
|
this.sharedConnectionPools.push(pool);
|
||||||
} else {
|
|
||||||
const connection = new SharedConnection(this, channel);
|
|
||||||
connection.use();
|
|
||||||
this.sharedConnections.push(connection);
|
|
||||||
return connection;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const connection = new SharedConnection(this, channel, pool);
|
||||||
|
this.sharedConnections.push(connection);
|
||||||
|
return connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
public removeSharedConnection(connection: SharedConnection) {
|
public removeSharedConnection(connection: SharedConnection) {
|
||||||
this.sharedConnections = this.sharedConnections.filter(c => c.id !== connection.id);
|
this.sharedConnections = this.sharedConnections.filter(c => c !== connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
public connectToChannel = (channel: string, params?: any): NonSharedConnection => {
|
@autobind
|
||||||
|
public removeSharedConnectionPool(pool: Pool) {
|
||||||
|
this.sharedConnectionPools = this.sharedConnectionPools.filter(p => p !== pool);
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
public connectToChannel(channel: string, params?: any): NonSharedConnection {
|
||||||
const connection = new NonSharedConnection(this, channel, params);
|
const connection = new NonSharedConnection(this, channel, params);
|
||||||
this.nonSharedConnections.push(connection);
|
this.nonSharedConnections.push(connection);
|
||||||
return connection;
|
return connection;
|
||||||
@ -141,7 +60,7 @@ export default class Stream extends EventEmitter {
|
|||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
public disconnectToChannel(connection: NonSharedConnection) {
|
public disconnectToChannel(connection: NonSharedConnection) {
|
||||||
this.nonSharedConnections = this.nonSharedConnections.filter(c => c.id !== connection.id);
|
this.nonSharedConnections = this.nonSharedConnections.filter(c => c !== connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -154,17 +73,10 @@ export default class Stream extends EventEmitter {
|
|||||||
this.state = 'connected';
|
this.state = 'connected';
|
||||||
this.emit('_connected_');
|
this.emit('_connected_');
|
||||||
|
|
||||||
// バッファーを処理
|
|
||||||
const _buffer = [].concat(this.buffer); // Shallow copy
|
|
||||||
this.buffer = []; // Clear buffer
|
|
||||||
_buffer.forEach(data => {
|
|
||||||
this.send(data); // Resend each buffered messages
|
|
||||||
});
|
|
||||||
|
|
||||||
// チャンネル再接続
|
// チャンネル再接続
|
||||||
if (isReconnect) {
|
if (isReconnect) {
|
||||||
this.sharedConnections.forEach(c => {
|
this.sharedConnectionPools.forEach(p => {
|
||||||
c.connect();
|
p.connect();
|
||||||
});
|
});
|
||||||
this.nonSharedConnections.forEach(c => {
|
this.nonSharedConnections.forEach(c => {
|
||||||
c.connect();
|
c.connect();
|
||||||
@ -177,8 +89,10 @@ export default class Stream extends EventEmitter {
|
|||||||
*/
|
*/
|
||||||
@autobind
|
@autobind
|
||||||
private onClose() {
|
private onClose() {
|
||||||
this.state = 'reconnecting';
|
if (this.state == 'connected') {
|
||||||
this.emit('_disconnected_');
|
this.state = 'reconnecting';
|
||||||
|
this.emit('_disconnected_');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -190,8 +104,18 @@ export default class Stream extends EventEmitter {
|
|||||||
|
|
||||||
if (type == 'channel') {
|
if (type == 'channel') {
|
||||||
const id = body.id;
|
const id = body.id;
|
||||||
const connection = this.sharedConnections.find(c => c.id === id) || this.nonSharedConnections.find(c => c.id === id);
|
|
||||||
connection.emit(body.type, body.body);
|
let connections: Connection[];
|
||||||
|
|
||||||
|
connections = this.sharedConnections.filter(c => c.id === id);
|
||||||
|
|
||||||
|
if (connections.length === 0) {
|
||||||
|
connections = [this.nonSharedConnections.find(c => c.id === id)];
|
||||||
|
}
|
||||||
|
|
||||||
|
connections.filter(c => c != null).forEach(c => {
|
||||||
|
c.emit(body.type, body.body);
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
this.emit(type, body);
|
this.emit(type, body);
|
||||||
}
|
}
|
||||||
@ -207,12 +131,6 @@ export default class Stream extends EventEmitter {
|
|||||||
body: payload
|
body: payload
|
||||||
};
|
};
|
||||||
|
|
||||||
// まだ接続が確立されていなかったらバッファリングして次に接続した時に送信する
|
|
||||||
if (this.state != 'connected') {
|
|
||||||
this.buffer.push(data);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.stream.send(JSON.stringify(data));
|
this.stream.send(JSON.stringify(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -226,19 +144,139 @@ export default class Stream extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class Connection extends EventEmitter {
|
class Pool {
|
||||||
public channel: string;
|
public channel: string;
|
||||||
public id: string;
|
public id: string;
|
||||||
protected params: any;
|
|
||||||
protected stream: Stream;
|
protected stream: Stream;
|
||||||
|
public users = 0;
|
||||||
|
private disposeTimerId: any;
|
||||||
|
private isConnected = false;
|
||||||
|
|
||||||
constructor(stream: Stream, channel: string, params?: any) {
|
constructor(stream: Stream, channel: string) {
|
||||||
|
this.channel = channel;
|
||||||
|
this.stream = stream;
|
||||||
|
|
||||||
|
this.id = Math.random().toString().substr(2, 8);
|
||||||
|
|
||||||
|
this.stream.on('_disconnected_', this.onStreamDisconnected);
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
private onStreamDisconnected() {
|
||||||
|
this.isConnected = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
public inc() {
|
||||||
|
if (this.users === 0 && !this.isConnected) {
|
||||||
|
this.connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.users++;
|
||||||
|
|
||||||
|
// タイマー解除
|
||||||
|
if (this.disposeTimerId) {
|
||||||
|
clearTimeout(this.disposeTimerId);
|
||||||
|
this.disposeTimerId = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
public dec() {
|
||||||
|
this.users--;
|
||||||
|
|
||||||
|
// そのコネクションの利用者が誰もいなくなったら
|
||||||
|
if (this.users === 0) {
|
||||||
|
// また直ぐに再利用される可能性があるので、一定時間待ち、
|
||||||
|
// 新たな利用者が現れなければコネクションを切断する
|
||||||
|
this.disposeTimerId = setTimeout(() => {
|
||||||
|
this.disconnect();
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
public connect() {
|
||||||
|
if (this.isConnected) return;
|
||||||
|
this.isConnected = true;
|
||||||
|
this.stream.send('connect', {
|
||||||
|
channel: this.channel,
|
||||||
|
id: this.id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
private disconnect() {
|
||||||
|
this.stream.off('_disconnected_', this.onStreamDisconnected);
|
||||||
|
this.stream.send('disconnect', { id: this.id });
|
||||||
|
this.stream.removeSharedConnectionPool(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class Connection extends EventEmitter {
|
||||||
|
public channel: string;
|
||||||
|
protected stream: Stream;
|
||||||
|
public abstract id: string;
|
||||||
|
|
||||||
|
constructor(stream: Stream, channel: string) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.stream = stream;
|
this.stream = stream;
|
||||||
this.channel = channel;
|
this.channel = channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
public send(id: string, typeOrPayload, payload?) {
|
||||||
|
const type = payload === undefined ? typeOrPayload.type : typeOrPayload;
|
||||||
|
const body = payload === undefined ? typeOrPayload.body : payload;
|
||||||
|
|
||||||
|
this.stream.send('ch', {
|
||||||
|
id: id,
|
||||||
|
type: type,
|
||||||
|
body: body
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract dispose(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
class SharedConnection extends Connection {
|
||||||
|
private pool: Pool;
|
||||||
|
|
||||||
|
public get id(): string {
|
||||||
|
return this.pool.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(stream: Stream, channel: string, pool: Pool) {
|
||||||
|
super(stream, channel);
|
||||||
|
|
||||||
|
this.pool = pool;
|
||||||
|
this.pool.inc();
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
public send(typeOrPayload, payload?) {
|
||||||
|
super.send(this.pool.id, typeOrPayload, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
public dispose() {
|
||||||
|
this.pool.dec();
|
||||||
|
this.removeAllListeners();
|
||||||
|
this.stream.removeSharedConnection(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NonSharedConnection extends Connection {
|
||||||
|
public id: string;
|
||||||
|
protected params: any;
|
||||||
|
|
||||||
|
constructor(stream: Stream, channel: string, params?: any) {
|
||||||
|
super(stream, channel);
|
||||||
|
|
||||||
this.params = params;
|
this.params = params;
|
||||||
this.id = Math.random().toString();
|
this.id = Math.random().toString().substr(2, 8);
|
||||||
|
|
||||||
this.connect();
|
this.connect();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -253,60 +291,7 @@ abstract class Connection extends EventEmitter {
|
|||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
public send(typeOrPayload, payload?) {
|
public send(typeOrPayload, payload?) {
|
||||||
const data = payload === undefined ? typeOrPayload : {
|
super.send(this.id, typeOrPayload, payload);
|
||||||
type: typeOrPayload,
|
|
||||||
body: payload
|
|
||||||
};
|
|
||||||
|
|
||||||
this.stream.send('channel', {
|
|
||||||
id: this.id,
|
|
||||||
body: data
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract dispose: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
class SharedConnection extends Connection {
|
|
||||||
private users = 0;
|
|
||||||
private disposeTimerId: any;
|
|
||||||
|
|
||||||
constructor(stream: Stream, channel: string) {
|
|
||||||
super(stream, channel);
|
|
||||||
}
|
|
||||||
|
|
||||||
@autobind
|
|
||||||
public use() {
|
|
||||||
this.users++;
|
|
||||||
|
|
||||||
// タイマー解除
|
|
||||||
if (this.disposeTimerId) {
|
|
||||||
clearTimeout(this.disposeTimerId);
|
|
||||||
this.disposeTimerId = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@autobind
|
|
||||||
public dispose() {
|
|
||||||
this.users--;
|
|
||||||
|
|
||||||
// そのコネクションの利用者が誰もいなくなったら
|
|
||||||
if (this.users === 0) {
|
|
||||||
// また直ぐに再利用される可能性があるので、一定時間待ち、
|
|
||||||
// 新たな利用者が現れなければコネクションを切断する
|
|
||||||
this.disposeTimerId = setTimeout(() => {
|
|
||||||
this.disposeTimerId = null;
|
|
||||||
this.removeAllListeners();
|
|
||||||
this.stream.send('disconnect', { id: this.id });
|
|
||||||
this.stream.removeSharedConnection(this);
|
|
||||||
}, 3000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class NonSharedConnection extends Connection {
|
|
||||||
constructor(stream: Stream, channel: string, params?: any) {
|
|
||||||
super(stream, channel, params);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
|
@ -186,9 +186,8 @@ export default Vue.extend({
|
|||||||
if (this.game.isStarted && !this.game.isEnded) {
|
if (this.game.isStarted && !this.game.isEnded) {
|
||||||
this.pollingClock = setInterval(() => {
|
this.pollingClock = setInterval(() => {
|
||||||
const crc32 = CRC32.str(this.logs.map(x => x.pos.toString()).join(''));
|
const crc32 = CRC32.str(this.logs.map(x => x.pos.toString()).join(''));
|
||||||
this.connection.send({
|
this.connection.send('check', {
|
||||||
type: 'check',
|
crc32: crc32
|
||||||
crc32
|
|
||||||
});
|
});
|
||||||
}, 3000);
|
}, 3000);
|
||||||
}
|
}
|
||||||
@ -224,9 +223,8 @@ export default Vue.extend({
|
|||||||
sound.play();
|
sound.play();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.connection.send({
|
this.connection.send('set', {
|
||||||
type: 'set',
|
pos: pos
|
||||||
pos
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.checkEnd();
|
this.checkEnd();
|
||||||
|
@ -149,9 +149,9 @@ export default Vue.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
this.connection.on('change-accepts', this.onChangeAccepts);
|
this.connection.on('changeAccepts', this.onChangeAccepts);
|
||||||
this.connection.on('update-settings', this.onUpdateSettings);
|
this.connection.on('updateSettings', this.onUpdateSettings);
|
||||||
this.connection.on('init-form', this.onInitForm);
|
this.connection.on('initForm', this.onInitForm);
|
||||||
this.connection.on('message', this.onMessage);
|
this.connection.on('message', this.onMessage);
|
||||||
|
|
||||||
if (this.game.user1Id != this.$store.state.i.id && this.game.settings.form1) this.form = this.game.settings.form1;
|
if (this.game.user1Id != this.$store.state.i.id && this.game.settings.form1) this.form = this.game.settings.form1;
|
||||||
@ -159,9 +159,9 @@ export default Vue.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
this.connection.off('change-accepts', this.onChangeAccepts);
|
this.connection.off('changeAccepts', this.onChangeAccepts);
|
||||||
this.connection.off('update-settings', this.onUpdateSettings);
|
this.connection.off('updateSettings', this.onUpdateSettings);
|
||||||
this.connection.off('init-form', this.onInitForm);
|
this.connection.off('initForm', this.onInitForm);
|
||||||
this.connection.off('message', this.onMessage);
|
this.connection.off('message', this.onMessage);
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -171,15 +171,11 @@ export default Vue.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
accept() {
|
accept() {
|
||||||
this.connection.send({
|
this.connection.send('accept', {});
|
||||||
type: 'accept'
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
cancel() {
|
cancel() {
|
||||||
this.connection.send({
|
this.connection.send('cancelAccept', {});
|
||||||
type: 'cancel-accept'
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
onChangeAccepts(accepts) {
|
onChangeAccepts(accepts) {
|
||||||
@ -189,8 +185,7 @@ export default Vue.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
updateSettings() {
|
updateSettings() {
|
||||||
this.connection.send({
|
this.connection.send('updateSettings', {
|
||||||
type: 'update-settings',
|
|
||||||
settings: this.game.settings
|
settings: this.game.settings
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -216,8 +211,7 @@ export default Vue.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
onChangeForm(item) {
|
onChangeForm(item) {
|
||||||
this.connection.send({
|
this.connection.send('updateForm', {
|
||||||
type: 'update-form',
|
|
||||||
id: item.id,
|
id: item.id,
|
||||||
value: item.value
|
value: item.value
|
||||||
});
|
});
|
||||||
@ -238,9 +232,9 @@ export default Vue.extend({
|
|||||||
const y = Math.floor(pos / this.game.settings.map[0].length);
|
const y = Math.floor(pos / this.game.settings.map[0].length);
|
||||||
const newPixel =
|
const newPixel =
|
||||||
pixel == ' ' ? '-' :
|
pixel == ' ' ? '-' :
|
||||||
pixel == '-' ? 'b' :
|
pixel == '-' ? 'b' :
|
||||||
pixel == 'b' ? 'w' :
|
pixel == 'b' ? 'w' :
|
||||||
' ';
|
' ';
|
||||||
const line = this.game.settings.map[y].split('');
|
const line = this.game.settings.map[y].split('');
|
||||||
line[x] = newPixel;
|
line[x] = newPixel;
|
||||||
this.$set(this.game.settings.map, y, line.join(''));
|
this.$set(this.game.settings.map, y, line.join(''));
|
||||||
|
@ -71,8 +71,7 @@ export default Vue.extend({
|
|||||||
|
|
||||||
this.pingClock = setInterval(() => {
|
this.pingClock = setInterval(() => {
|
||||||
if (this.matching) {
|
if (this.matching) {
|
||||||
this.connection.send({
|
this.connection.send('ping', {
|
||||||
type: 'ping',
|
|
||||||
id: this.matching.id
|
id: this.matching.id
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
|
|
||||||
|
import noteSkeleton from './note-skeleton.vue';
|
||||||
import theme from './theme.vue';
|
import theme from './theme.vue';
|
||||||
import instance from './instance.vue';
|
import instance from './instance.vue';
|
||||||
import cwButton from './cw-button.vue';
|
import cwButton from './cw-button.vue';
|
||||||
@ -44,6 +45,7 @@ import uiSelect from './ui/select.vue';
|
|||||||
import formButton from './ui/form/button.vue';
|
import formButton from './ui/form/button.vue';
|
||||||
import formRadio from './ui/form/radio.vue';
|
import formRadio from './ui/form/radio.vue';
|
||||||
|
|
||||||
|
Vue.component('mk-note-skeleton', noteSkeleton);
|
||||||
Vue.component('mk-theme', theme);
|
Vue.component('mk-theme', theme);
|
||||||
Vue.component('mk-instance', instance);
|
Vue.component('mk-instance', instance);
|
||||||
Vue.component('mk-cw-button', cwButton);
|
Vue.component('mk-cw-button', cwButton);
|
||||||
|
@ -71,7 +71,7 @@ export default Vue.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
this.connection =((this as any).os.stream.connectToChannel('messaging', { otherparty: this.user.id });
|
this.connection = (this as any).os.stream.connectToChannel('messaging', { otherparty: this.user.id });
|
||||||
|
|
||||||
this.connection.on('message', this.onMessage);
|
this.connection.on('message', this.onMessage);
|
||||||
this.connection.on('read', this.onRead);
|
this.connection.on('read', this.onRead);
|
||||||
@ -174,8 +174,7 @@ export default Vue.extend({
|
|||||||
|
|
||||||
this.messages.push(message);
|
this.messages.push(message);
|
||||||
if (message.userId != this.$store.state.i.id && !document.hidden) {
|
if (message.userId != this.$store.state.i.id && !document.hidden) {
|
||||||
this.connection.send({
|
this.connection.send('read', {
|
||||||
type: 'read',
|
|
||||||
id: message.id
|
id: message.id
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -247,8 +246,7 @@ export default Vue.extend({
|
|||||||
if (document.hidden) return;
|
if (document.hidden) return;
|
||||||
this.messages.forEach(message => {
|
this.messages.forEach(message => {
|
||||||
if (message.userId !== this.$store.state.i.id && !message.isRead) {
|
if (message.userId !== this.$store.state.i.id && !message.isRead) {
|
||||||
this.connection.send({
|
this.connection.send('read', {
|
||||||
type: 'read',
|
|
||||||
id: message.id
|
id: message.id
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -356,7 +354,7 @@ export default Vue.extend({
|
|||||||
max-width 600px
|
max-width 600px
|
||||||
margin 0 auto
|
margin 0 auto
|
||||||
padding 0
|
padding 0
|
||||||
//background rgba(var(--face), 0.95)
|
background var(--messagingRoomBg)
|
||||||
background-clip content-box
|
background-clip content-box
|
||||||
|
|
||||||
> .new-message
|
> .new-message
|
||||||
|
@ -116,16 +116,16 @@ export default Vue.component('misskey-flavored-markdown', {
|
|||||||
case 'mention': {
|
case 'mention': {
|
||||||
return (createElement as any)('a', {
|
return (createElement as any)('a', {
|
||||||
attrs: {
|
attrs: {
|
||||||
href: `${url}/@${getAcct(token)}`,
|
href: `${url}/${token.canonical}`,
|
||||||
target: '_blank',
|
target: '_blank',
|
||||||
dataIsMe: (this as any).i && getAcct((this as any).i) == getAcct(token),
|
dataIsMe: (this as any).i && getAcct((this as any).i) == getAcct(token),
|
||||||
style: 'color:var(--mfmMention);'
|
style: 'color:var(--mfmMention);'
|
||||||
},
|
},
|
||||||
directives: [{
|
directives: [{
|
||||||
name: 'user-preview',
|
name: 'user-preview',
|
||||||
value: token.content
|
value: token.canonical
|
||||||
}]
|
}]
|
||||||
}, token.content);
|
}, token.canonical);
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'hashtag': {
|
case 'hashtag': {
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import { url } from '../../../config';
|
import { url } from '../../../config';
|
||||||
import copyToClipboard from '../../../common/scripts/copy-to-clipboard';
|
import copyToClipboard from '../../../common/scripts/copy-to-clipboard';
|
||||||
|
import Ok from './ok.vue';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
props: ['note', 'source', 'compact'],
|
props: ['note', 'source', 'compact'],
|
||||||
@ -21,12 +22,34 @@ export default Vue.extend({
|
|||||||
icon: '%fa:link%',
|
icon: '%fa:link%',
|
||||||
text: '%i18n:@copy-link%',
|
text: '%i18n:@copy-link%',
|
||||||
action: this.copyLink
|
action: this.copyLink
|
||||||
}, null, {
|
|
||||||
icon: '%fa:star%',
|
|
||||||
text: '%i18n:@favorite%',
|
|
||||||
action: this.favorite
|
|
||||||
}];
|
}];
|
||||||
|
|
||||||
|
if (this.note.uri) {
|
||||||
|
items.push({
|
||||||
|
icon: '%fa:external-link-square-alt%',
|
||||||
|
text: '%i18n:@remote%',
|
||||||
|
action: () => {
|
||||||
|
window.open(this.note.uri, '_blank');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
items.push(null);
|
||||||
|
|
||||||
|
if (this.note.isFavorited) {
|
||||||
|
items.push({
|
||||||
|
icon: '%fa:star%',
|
||||||
|
text: '%i18n:@unfavorite%',
|
||||||
|
action: this.unfavorite
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
items.push({
|
||||||
|
icon: '%fa:star%',
|
||||||
|
text: '%i18n:@favorite%',
|
||||||
|
action: this.favorite
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (this.note.userId == this.$store.state.i.id) {
|
if (this.note.userId == this.$store.state.i.id) {
|
||||||
if ((this.$store.state.i.pinnedNoteIds || []).includes(this.note.id)) {
|
if ((this.$store.state.i.pinnedNoteIds || []).includes(this.note.id)) {
|
||||||
items.push({
|
items.push({
|
||||||
@ -44,6 +67,7 @@ export default Vue.extend({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.note.userId == this.$store.state.i.id || this.$store.state.i.isAdmin) {
|
if (this.note.userId == this.$store.state.i.id || this.$store.state.i.isAdmin) {
|
||||||
|
items.push(null);
|
||||||
items.push({
|
items.push({
|
||||||
icon: '%fa:trash-alt R%',
|
icon: '%fa:trash-alt R%',
|
||||||
text: '%i18n:@delete%',
|
text: '%i18n:@delete%',
|
||||||
@ -51,16 +75,6 @@ export default Vue.extend({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.note.uri) {
|
|
||||||
items.push({
|
|
||||||
icon: '%fa:external-link-square-alt%',
|
|
||||||
text: '%i18n:@remote%',
|
|
||||||
action: () => {
|
|
||||||
window.open(this.note.uri, '_blank');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -78,6 +92,7 @@ export default Vue.extend({
|
|||||||
(this as any).api('i/pin', {
|
(this as any).api('i/pin', {
|
||||||
noteId: this.note.id
|
noteId: this.note.id
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
|
(this as any).os.new(Ok);
|
||||||
this.destroyDom();
|
this.destroyDom();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -103,6 +118,16 @@ export default Vue.extend({
|
|||||||
(this as any).api('notes/favorites/create', {
|
(this as any).api('notes/favorites/create', {
|
||||||
noteId: this.note.id
|
noteId: this.note.id
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
|
(this as any).os.new(Ok);
|
||||||
|
this.destroyDom();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
unfavorite() {
|
||||||
|
(this as any).api('notes/favorites/delete', {
|
||||||
|
noteId: this.note.id
|
||||||
|
}).then(() => {
|
||||||
|
(this as any).os.new(Ok);
|
||||||
this.destroyDom();
|
this.destroyDom();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
52
src/client/app/common/views/components/note-skeleton.vue
Normal file
52
src/client/app/common/views/components/note-skeleton.vue
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<vue-content-loading v-if="width" :width="width" :height="100" :primary="primary" :secondary="secondary">
|
||||||
|
<circle cx="30" cy="30" r="30" />
|
||||||
|
<rect x="75" y="13" rx="4" ry="4" :width="150 + r1" height="15" />
|
||||||
|
<rect x="75" y="39" rx="4" ry="4" :width="260 + r2" height="10" />
|
||||||
|
<rect x="75" y="59" rx="4" ry="4" :width="230 + r3" height="10" />
|
||||||
|
</vue-content-loading>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from 'vue';
|
||||||
|
import VueContentLoading from 'vue-content-loading';
|
||||||
|
import * as tinycolor from 'tinycolor2';
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
components: {
|
||||||
|
VueContentLoading,
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
width: 0,
|
||||||
|
r1: (Math.random() * 100) - 50,
|
||||||
|
r2: (Math.random() * 100) - 50,
|
||||||
|
r3: (Math.random() * 100) - 50
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
text(): tinycolor.Instance {
|
||||||
|
const text = tinycolor(getComputedStyle(document.documentElement).getPropertyValue('--text'));
|
||||||
|
return text;
|
||||||
|
},
|
||||||
|
|
||||||
|
primary(): string {
|
||||||
|
return '#' + this.text.clone().toHex();
|
||||||
|
},
|
||||||
|
|
||||||
|
secondary(): string {
|
||||||
|
return '#' + this.text.clone().darken(20).toHex();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
let width = this.$el.clientWidth;
|
||||||
|
if (width < 400) width = 400;
|
||||||
|
this.width = width;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
175
src/client/app/common/views/components/ok.vue
Normal file
175
src/client/app/common/views/components/ok.vue
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
<template>
|
||||||
|
<div class="yvbkymdqeusiqucuuloahhiqflzinufs">
|
||||||
|
<div class="bg" ref="bg"></div>
|
||||||
|
<div class="body" ref="body">
|
||||||
|
<div class="icon">
|
||||||
|
<div class="circle left"></div>
|
||||||
|
<span class="check tip"></span>
|
||||||
|
<span class="check long"></span>
|
||||||
|
<div class="ring"></div>
|
||||||
|
<div class="fix"></div>
|
||||||
|
<div class="circle right"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from 'vue';
|
||||||
|
import * as anime from 'animejs';
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
anime({
|
||||||
|
targets: this.$refs.bg,
|
||||||
|
opacity: 1,
|
||||||
|
duration: 300,
|
||||||
|
easing: 'linear'
|
||||||
|
});
|
||||||
|
|
||||||
|
anime({
|
||||||
|
targets: this.$refs.body,
|
||||||
|
opacity: 1,
|
||||||
|
scale: [1.2, 1],
|
||||||
|
duration: 300,
|
||||||
|
easing: [0, 0.5, 0.5, 1]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
anime({
|
||||||
|
targets: this.$refs.bg,
|
||||||
|
opacity: 0,
|
||||||
|
duration: 300,
|
||||||
|
easing: 'linear'
|
||||||
|
});
|
||||||
|
|
||||||
|
anime({
|
||||||
|
targets: this.$refs.body,
|
||||||
|
opacity: 0,
|
||||||
|
scale: 0.8,
|
||||||
|
duration: 300,
|
||||||
|
easing: [0.5, 0, 1, 0.5],
|
||||||
|
complete: () => this.destroyDom()
|
||||||
|
});
|
||||||
|
}, 1250);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="stylus" scoped>
|
||||||
|
.yvbkymdqeusiqucuuloahhiqflzinufs
|
||||||
|
pointer-events none
|
||||||
|
|
||||||
|
> .bg
|
||||||
|
display block
|
||||||
|
position fixed
|
||||||
|
z-index 10000
|
||||||
|
top 0
|
||||||
|
left 0
|
||||||
|
width 100%
|
||||||
|
height 100%
|
||||||
|
background rgba(#000, 0.7)
|
||||||
|
opacity 0
|
||||||
|
|
||||||
|
> .body
|
||||||
|
position fixed
|
||||||
|
z-index 10000
|
||||||
|
top 0
|
||||||
|
right 0
|
||||||
|
left 0
|
||||||
|
bottom 0
|
||||||
|
margin auto
|
||||||
|
width 150px
|
||||||
|
height 150px
|
||||||
|
background var(--face)
|
||||||
|
border-radius 8px
|
||||||
|
opacity 0
|
||||||
|
|
||||||
|
> .icon
|
||||||
|
display flex
|
||||||
|
justify-content center
|
||||||
|
position absolute
|
||||||
|
top 0
|
||||||
|
right 0
|
||||||
|
left 0
|
||||||
|
bottom 0
|
||||||
|
width 5em
|
||||||
|
height 5em
|
||||||
|
margin auto
|
||||||
|
border .25em solid transparent
|
||||||
|
border-radius 50%
|
||||||
|
line-height 5em
|
||||||
|
cursor default
|
||||||
|
box-sizing content-box
|
||||||
|
user-select none
|
||||||
|
zoom normal
|
||||||
|
border-color #a5dc86
|
||||||
|
|
||||||
|
> .circle
|
||||||
|
position absolute
|
||||||
|
width 3.75em
|
||||||
|
height 7.5em
|
||||||
|
transform rotate(45deg)
|
||||||
|
border-radius 50%
|
||||||
|
background var(--face)
|
||||||
|
|
||||||
|
&.left
|
||||||
|
top -.4375em
|
||||||
|
left -2.0635em
|
||||||
|
transform rotate(-45deg)
|
||||||
|
transform-origin 3.75em 3.75em
|
||||||
|
border-radius 7.5em 0 0 7.5em
|
||||||
|
|
||||||
|
&.right
|
||||||
|
top -.6875em
|
||||||
|
left 1.875em
|
||||||
|
transform rotate(-45deg)
|
||||||
|
transform-origin 0 3.75em
|
||||||
|
border-radius 0 7.5em 7.5em 0
|
||||||
|
animation swal2-rotate-success-circular-line 4.25s ease-in
|
||||||
|
|
||||||
|
> .check
|
||||||
|
display block
|
||||||
|
position absolute
|
||||||
|
height .3125em
|
||||||
|
border-radius .125em
|
||||||
|
background-color #a5dc86
|
||||||
|
z-index 2
|
||||||
|
|
||||||
|
&.tip
|
||||||
|
top 2.875em
|
||||||
|
left .875em
|
||||||
|
width 1.5625em
|
||||||
|
transform rotate(45deg)
|
||||||
|
animation swal2-animate-success-line-tip .75s
|
||||||
|
|
||||||
|
&.long
|
||||||
|
top 2.375em
|
||||||
|
right .5em
|
||||||
|
width 2.9375em
|
||||||
|
transform rotate(-45deg)
|
||||||
|
animation swal2-animate-success-line-long .75s
|
||||||
|
|
||||||
|
> .fix
|
||||||
|
position absolute
|
||||||
|
top .5em
|
||||||
|
left 1.625em
|
||||||
|
width .4375em
|
||||||
|
height 5.625em
|
||||||
|
transform rotate(-45deg)
|
||||||
|
z-index 1
|
||||||
|
background var(--face)
|
||||||
|
|
||||||
|
> .ring
|
||||||
|
position absolute
|
||||||
|
top -.25em
|
||||||
|
left -.25em
|
||||||
|
width 100%
|
||||||
|
height 100%
|
||||||
|
border .25em solid rgba(165,220,134,.3)
|
||||||
|
border-radius 50%
|
||||||
|
z-index 2
|
||||||
|
box-sizing content-box
|
||||||
|
</style>
|
@ -1,16 +1,16 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="mk-reactions-viewer">
|
<div class="mk-reactions-viewer">
|
||||||
<template v-if="reactions">
|
<template v-if="reactions">
|
||||||
<span :class="{notReacted}" @click="react('like')" v-if="reactions.like"><mk-reaction-icon reaction="like"/><span>{{ reactions.like }}</span></span>
|
<span :class="{ reacted: note.myReaction == 'like' }" @click="react('like')" v-if="reactions.like"><mk-reaction-icon reaction="like"/><span>{{ reactions.like }}</span></span>
|
||||||
<span :class="{notReacted}" @click="react('love')" v-if="reactions.love"><mk-reaction-icon reaction="love"/><span>{{ reactions.love }}</span></span>
|
<span :class="{ reacted: note.myReaction == 'love' }" @click="react('love')" v-if="reactions.love"><mk-reaction-icon reaction="love"/><span>{{ reactions.love }}</span></span>
|
||||||
<span :class="{notReacted}" @click="react('laugh')" v-if="reactions.laugh"><mk-reaction-icon reaction="laugh"/><span>{{ reactions.laugh }}</span></span>
|
<span :class="{ reacted: note.myReaction == 'laugh' }" @click="react('laugh')" v-if="reactions.laugh"><mk-reaction-icon reaction="laugh"/><span>{{ reactions.laugh }}</span></span>
|
||||||
<span :class="{notReacted}" @click="react('hmm')" v-if="reactions.hmm"><mk-reaction-icon reaction="hmm"/><span>{{ reactions.hmm }}</span></span>
|
<span :class="{ reacted: note.myReaction == 'hmm' }" @click="react('hmm')" v-if="reactions.hmm"><mk-reaction-icon reaction="hmm"/><span>{{ reactions.hmm }}</span></span>
|
||||||
<span :class="{notReacted}" @click="react('surprise')" v-if="reactions.surprise"><mk-reaction-icon reaction="surprise"/><span>{{ reactions.surprise }}</span></span>
|
<span :class="{ reacted: note.myReaction == 'surprise' }" @click="react('surprise')" v-if="reactions.surprise"><mk-reaction-icon reaction="surprise"/><span>{{ reactions.surprise }}</span></span>
|
||||||
<span :class="{notReacted}" @click="react('congrats')" v-if="reactions.congrats"><mk-reaction-icon reaction="congrats"/><span>{{ reactions.congrats }}</span></span>
|
<span :class="{ reacted: note.myReaction == 'congrats' }" @click="react('congrats')" v-if="reactions.congrats"><mk-reaction-icon reaction="congrats"/><span>{{ reactions.congrats }}</span></span>
|
||||||
<span :class="{notReacted}" @click="react('angry')" v-if="reactions.angry"><mk-reaction-icon reaction="angry"/><span>{{ reactions.angry }}</span></span>
|
<span :class="{ reacted: note.myReaction == 'angry' }" @click="react('angry')" v-if="reactions.angry"><mk-reaction-icon reaction="angry"/><span>{{ reactions.angry }}</span></span>
|
||||||
<span :class="{notReacted}" @click="react('confused')" v-if="reactions.confused"><mk-reaction-icon reaction="confused"/><span>{{ reactions.confused }}</span></span>
|
<span :class="{ reacted: note.myReaction == 'confused' }" @click="react('confused')" v-if="reactions.confused"><mk-reaction-icon reaction="confused"/><span>{{ reactions.confused }}</span></span>
|
||||||
<span :class="{notReacted}" @click="react('rip')" v-if="reactions.rip"><mk-reaction-icon reaction="rip"/><span>{{ reactions.rip }}</span></span>
|
<span :class="{ reacted: note.myReaction == 'rip' }" @click="react('rip')" v-if="reactions.rip"><mk-reaction-icon reaction="rip"/><span>{{ reactions.rip }}</span></span>
|
||||||
<span :class="{notReacted}" @click="react('pudding')" v-if="reactions.pudding"><mk-reaction-icon reaction="pudding"/><span>{{ reactions.pudding }}</span></span>
|
<span :class="{ reacted: note.myReaction == 'pudding' }" @click="react('pudding')" v-if="reactions.pudding"><mk-reaction-icon reaction="pudding"/><span>{{ reactions.pudding }}</span></span>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -22,9 +22,6 @@ export default Vue.extend({
|
|||||||
computed: {
|
computed: {
|
||||||
reactions(): number {
|
reactions(): number {
|
||||||
return this.note.reactionCounts;
|
return this.note.reactionCounts;
|
||||||
},
|
|
||||||
notReacted(): boolean {
|
|
||||||
return this.note.myReaction == null;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@ -40,25 +37,42 @@ export default Vue.extend({
|
|||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
.mk-reactions-viewer
|
.mk-reactions-viewer
|
||||||
border-top dashed 1px var(--reactionViewerBorder)
|
margin 6px 0
|
||||||
border-bottom dashed 1px var(--reactionViewerBorder)
|
|
||||||
margin 4px 0
|
|
||||||
|
|
||||||
&:empty
|
&:empty
|
||||||
display none
|
display none
|
||||||
|
|
||||||
> span
|
> span
|
||||||
margin-right 8px
|
display inline-block
|
||||||
|
height 32px
|
||||||
|
margin-right 6px
|
||||||
|
padding 0 6px
|
||||||
|
border-radius 4px
|
||||||
|
|
||||||
&.notReacted
|
*
|
||||||
|
user-select none
|
||||||
|
pointer-events none
|
||||||
|
|
||||||
|
&.reacted
|
||||||
|
background var(--primary)
|
||||||
|
|
||||||
|
> span
|
||||||
|
color var(--primaryForeground)
|
||||||
|
|
||||||
|
&:not(.reacted)
|
||||||
cursor pointer
|
cursor pointer
|
||||||
|
background var(--reactionViewerButtonBg)
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
background var(--reactionViewerButtonHoverBg)
|
||||||
|
|
||||||
> .mk-reaction-icon
|
> .mk-reaction-icon
|
||||||
font-size 1.4em
|
font-size 1.4em
|
||||||
|
|
||||||
> span
|
> span
|
||||||
margin-left 4px
|
font-size 1.1em
|
||||||
font-size 1.2em
|
line-height 32px
|
||||||
|
vertical-align middle
|
||||||
color var(--text)
|
color var(--text)
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
@ -67,22 +67,30 @@
|
|||||||
</details>
|
</details>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>%fa:folder-open% %i18n:@installed-themes%</summary>
|
<summary>%fa:folder-open% %i18n:@manage-themes%</summary>
|
||||||
<ui-select v-model="selectedInstalledThemeId" placeholder="%i18n:@select-theme%">
|
<ui-select v-model="selectedThemeId" placeholder="%i18n:@select-theme%">
|
||||||
<option v-for="x in installedThemes" :value="x.id" :key="x.id">{{ x.name }}</option>
|
<optgroup label="%i18n:@builtin-themes%">
|
||||||
|
<option v-for="x in builtinThemes" :value="x.id" :key="x.id">{{ x.name }}</option>
|
||||||
|
</optgroup>
|
||||||
|
<optgroup label="%i18n:@my-themes%">
|
||||||
|
<option v-for="x in installedThemes.filter(t => t.author == this.$store.state.i.username)" :value="x.id" :key="x.id">{{ x.name }}</option>
|
||||||
|
</optgroup>
|
||||||
|
<optgroup label="%i18n:@installed-themes%">
|
||||||
|
<option v-for="x in installedThemes.filter(t => t.author != this.$store.state.i.username)" :value="x.id" :key="x.id">{{ x.name }}</option>
|
||||||
|
</optgroup>
|
||||||
</ui-select>
|
</ui-select>
|
||||||
<template v-if="selectedInstalledTheme">
|
<template v-if="selectedTheme">
|
||||||
<ui-input readonly :value="selectedInstalledTheme.author">
|
<ui-input readonly :value="selectedTheme.author">
|
||||||
<span>%i18n:@author%</span>
|
<span>%i18n:@author%</span>
|
||||||
</ui-input>
|
</ui-input>
|
||||||
<ui-textarea v-if="selectedInstalledTheme.desc" readonly :value="selectedInstalledTheme.desc">
|
<ui-textarea v-if="selectedTheme.desc" readonly :value="selectedTheme.desc">
|
||||||
<span>%i18n:@desc%</span>
|
<span>%i18n:@desc%</span>
|
||||||
</ui-textarea>
|
</ui-textarea>
|
||||||
<ui-textarea readonly :value="selectedInstalledThemeCode">
|
<ui-textarea readonly :value="selectedThemeCode">
|
||||||
<span>%i18n:@theme-code%</span>
|
<span>%i18n:@theme-code%</span>
|
||||||
</ui-textarea>
|
</ui-textarea>
|
||||||
<ui-button @click="export_()" link :download="`${selectedInstalledTheme.name}.misskeytheme`" ref="export">%fa:box% %i18n:@export%</ui-button>
|
<ui-button @click="export_()" link :download="`${selectedTheme.name}.misskeytheme`" ref="export">%fa:box% %i18n:@export%</ui-button>
|
||||||
<ui-button @click="uninstall()">%fa:trash-alt R% %i18n:@uninstall%</ui-button>
|
<ui-button @click="uninstall()" v-if="!builtinThemes.some(t => t.id == selectedTheme.id)">%fa:trash-alt R% %i18n:@uninstall%</ui-button>
|
||||||
</template>
|
</template>
|
||||||
</details>
|
</details>
|
||||||
</div>
|
</div>
|
||||||
@ -117,8 +125,9 @@ export default Vue.extend({
|
|||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
builtinThemes: builtinThemes,
|
||||||
installThemeCode: null,
|
installThemeCode: null,
|
||||||
selectedInstalledThemeId: null,
|
selectedThemeId: null,
|
||||||
myThemeBase: 'light',
|
myThemeBase: 'light',
|
||||||
myThemeName: '',
|
myThemeName: '',
|
||||||
myThemeDesc: '',
|
myThemeDesc: '',
|
||||||
@ -155,14 +164,14 @@ 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() {
|
selectedTheme() {
|
||||||
if (this.selectedInstalledThemeId == null) return null;
|
if (this.selectedThemeId == null) return null;
|
||||||
return this.installedThemes.find(x => x.id == this.selectedInstalledThemeId);
|
return this.themes.find(x => x.id == this.selectedThemeId);
|
||||||
},
|
},
|
||||||
|
|
||||||
selectedInstalledThemeCode() {
|
selectedThemeCode() {
|
||||||
if (this.selectedInstalledTheme == null) return null;
|
if (this.selectedTheme == null) return null;
|
||||||
return JSON5.stringify(this.selectedInstalledTheme, null, '\t');
|
return JSON5.stringify(this.selectedTheme, null, '\t');
|
||||||
},
|
},
|
||||||
|
|
||||||
myTheme(): any {
|
myTheme(): any {
|
||||||
@ -210,7 +219,10 @@ export default Vue.extend({
|
|||||||
try {
|
try {
|
||||||
theme = JSON5.parse(code);
|
theme = JSON5.parse(code);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
alert('%i18n:@invalid-theme%');
|
this.$swal({
|
||||||
|
type: 'error',
|
||||||
|
text: '%i18n:@invalid-theme%'
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -220,12 +232,18 @@ export default Vue.extend({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (theme.id == null) {
|
if (theme.id == null) {
|
||||||
alert('%i18n:@invalid-theme%');
|
this.$swal({
|
||||||
|
type: 'error',
|
||||||
|
text: '%i18n:@invalid-theme%'
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.$store.state.device.themes.some(t => t.id == theme.id)) {
|
if (this.$store.state.device.themes.some(t => t.id == theme.id)) {
|
||||||
alert('%i18n:@already-installed%');
|
this.$swal({
|
||||||
|
type: 'info',
|
||||||
|
text: '%i18n:@already-installed%'
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -234,16 +252,23 @@ export default Vue.extend({
|
|||||||
key: 'themes', value: themes
|
key: 'themes', value: themes
|
||||||
});
|
});
|
||||||
|
|
||||||
alert('%i18n:@installed%'.replace('{}', theme.name));
|
this.$swal({
|
||||||
|
type: 'success',
|
||||||
|
text: '%i18n:@installed%'.replace('{}', theme.name)
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
uninstall() {
|
uninstall() {
|
||||||
const theme = this.selectedInstalledTheme;
|
const theme = this.selectedTheme;
|
||||||
const themes = this.$store.state.device.themes.filter(t => t.id != theme.id);
|
const themes = this.$store.state.device.themes.filter(t => t.id != theme.id);
|
||||||
this.$store.commit('device/set', {
|
this.$store.commit('device/set', {
|
||||||
key: 'themes', value: themes
|
key: 'themes', value: themes
|
||||||
});
|
});
|
||||||
alert('%i18n:@uninstalled%'.replace('{}', theme.name));
|
|
||||||
|
this.$swal({
|
||||||
|
type: 'info',
|
||||||
|
text: '%i18n:@uninstalled%'.replace('{}', theme.name)
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
import_() {
|
import_() {
|
||||||
@ -251,7 +276,7 @@ export default Vue.extend({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export_() {
|
export_() {
|
||||||
const blob = new Blob([this.selectedInstalledThemeCode], {
|
const blob = new Blob([this.selectedThemeCode], {
|
||||||
type: 'application/json5'
|
type: 'application/json5'
|
||||||
});
|
});
|
||||||
this.$refs.export.$el.href = window.URL.createObjectURL(blob);
|
this.$refs.export.$el.href = window.URL.createObjectURL(blob);
|
||||||
@ -275,16 +300,26 @@ export default Vue.extend({
|
|||||||
|
|
||||||
gen() {
|
gen() {
|
||||||
const theme = this.myTheme;
|
const theme = this.myTheme;
|
||||||
|
|
||||||
if (theme.name == null || theme.name.trim() == '') {
|
if (theme.name == null || theme.name.trim() == '') {
|
||||||
alert('%i18n:@theme-name-required%');
|
this.$swal({
|
||||||
|
type: 'warning',
|
||||||
|
text: '%i18n:@theme-name-required%'
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
theme.id = uuid();
|
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
|
||||||
});
|
});
|
||||||
alert('%i18n:@saved%');
|
|
||||||
|
this.$swal({
|
||||||
|
type: 'success',
|
||||||
|
text: '%i18n:@saved%'
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import * as getCaretCoordinates from 'textarea-caret';
|
import * as getCaretCoordinates from 'textarea-caret';
|
||||||
import MkAutocomplete from '../components/autocomplete.vue';
|
import MkAutocomplete from '../components/autocomplete.vue';
|
||||||
import renderAcct from '../../../../../misc/acct/render';
|
import { toASCII } from 'punycode';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
bind(el, binding, vn) {
|
bind(el, binding, vn) {
|
||||||
@ -188,7 +188,7 @@ class Autocomplete {
|
|||||||
const trimmedBefore = before.substring(0, before.lastIndexOf('@'));
|
const trimmedBefore = before.substring(0, before.lastIndexOf('@'));
|
||||||
const after = source.substr(caret);
|
const after = source.substr(caret);
|
||||||
|
|
||||||
const acct = renderAcct(value);
|
const acct = value.host === null ? value.username : `${value.username}@${toASCII(value.host)}`;
|
||||||
|
|
||||||
// 挿入
|
// 挿入
|
||||||
this.text = `${trimmedBefore}@${acct} ${after}`;
|
this.text = `${trimmedBefore}@${acct} ${after}`;
|
||||||
|
@ -113,9 +113,8 @@ export default define({
|
|||||||
|
|
||||||
this.connection.on('stats', this.onStats);
|
this.connection.on('stats', this.onStats);
|
||||||
this.connection.on('statsLog', this.onStatsLog);
|
this.connection.on('statsLog', this.onStatsLog);
|
||||||
this.connection.send({
|
this.connection.send('requestLog',{
|
||||||
type: 'requestLog',
|
id: Math.random().toString().substr(2, 8)
|
||||||
id: Math.random().toString()
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
|
@ -91,9 +91,8 @@ export default Vue.extend({
|
|||||||
mounted() {
|
mounted() {
|
||||||
this.connection.on('stats', this.onStats);
|
this.connection.on('stats', this.onStats);
|
||||||
this.connection.on('statsLog', this.onStatsLog);
|
this.connection.on('statsLog', this.onStatsLog);
|
||||||
this.connection.send({
|
this.connection.send('requestLog', {
|
||||||
type: 'requestLog',
|
id: Math.random().toString().substr(2, 8)
|
||||||
id: Math.random().toString()
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
|
@ -6,13 +6,17 @@ export default (os: OS) => opts => {
|
|||||||
const o = opts || {};
|
const o = opts || {};
|
||||||
if (o.renote) {
|
if (o.renote) {
|
||||||
const vm = os.new(RenoteFormWindow, {
|
const vm = os.new(RenoteFormWindow, {
|
||||||
note: o.renote
|
note: o.renote,
|
||||||
|
animation: o.animation == null ? true : o.animation
|
||||||
});
|
});
|
||||||
|
if (o.cb) vm.$once('closed', o.cb);
|
||||||
document.body.appendChild(vm.$el);
|
document.body.appendChild(vm.$el);
|
||||||
} else {
|
} else {
|
||||||
const vm = os.new(PostFormWindow, {
|
const vm = os.new(PostFormWindow, {
|
||||||
reply: o.reply
|
reply: o.reply,
|
||||||
|
animation: o.animation == null ? true : o.animation
|
||||||
});
|
});
|
||||||
|
if (o.cb) vm.$once('closed', o.cb);
|
||||||
document.body.appendChild(vm.$el);
|
document.body.appendChild(vm.$el);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,37 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="mk-ellipsis-icon">
|
|
||||||
<div></div><div></div><div></div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
|
||||||
.mk-ellipsis-icon
|
|
||||||
width 70px
|
|
||||||
margin 0 auto
|
|
||||||
text-align center
|
|
||||||
|
|
||||||
> div
|
|
||||||
display inline-block
|
|
||||||
width 18px
|
|
||||||
height 18px
|
|
||||||
background-color rgba(#000, 0.3)
|
|
||||||
border-radius 100%
|
|
||||||
animation bounce 1.4s infinite ease-in-out both
|
|
||||||
|
|
||||||
&:nth-child(1)
|
|
||||||
animation-delay 0s
|
|
||||||
|
|
||||||
&:nth-child(2)
|
|
||||||
margin 0 6px
|
|
||||||
animation-delay 0.16s
|
|
||||||
|
|
||||||
&:nth-child(3)
|
|
||||||
animation-delay 0.32s
|
|
||||||
|
|
||||||
@keyframes bounce
|
|
||||||
0%, 80%, 100%
|
|
||||||
transform scale(0)
|
|
||||||
40%
|
|
||||||
transform scale(1)
|
|
||||||
|
|
||||||
</style>
|
|
@ -8,7 +8,6 @@
|
|||||||
<router-link class="name" :to="user | userPage" v-user-preview="user.id">{{ user | userName }}</router-link>
|
<router-link class="name" :to="user | userPage" v-user-preview="user.id">{{ user | userName }}</router-link>
|
||||||
<p class="username">@{{ user | acct }}</p>
|
<p class="username">@{{ user | acct }}</p>
|
||||||
</div>
|
</div>
|
||||||
<mk-follow-button :user="user"/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p class="empty" v-if="!fetching && users.length == 0">%i18n:@empty%</p>
|
<p class="empty" v-if="!fetching && users.length == 0">%i18n:@empty%</p>
|
||||||
|
@ -9,7 +9,6 @@ import subNoteContent from './sub-note-content.vue';
|
|||||||
import window from './window.vue';
|
import window from './window.vue';
|
||||||
import noteFormWindow from './post-form-window.vue';
|
import noteFormWindow from './post-form-window.vue';
|
||||||
import renoteFormWindow from './renote-form-window.vue';
|
import renoteFormWindow from './renote-form-window.vue';
|
||||||
import ellipsisIcon from './ellipsis-icon.vue';
|
|
||||||
import mediaImage from './media-image.vue';
|
import mediaImage from './media-image.vue';
|
||||||
import mediaImageDialog from './media-image-dialog.vue';
|
import mediaImageDialog from './media-image-dialog.vue';
|
||||||
import mediaVideo from './media-video.vue';
|
import mediaVideo from './media-video.vue';
|
||||||
@ -39,7 +38,6 @@ Vue.component('mk-sub-note-content', subNoteContent);
|
|||||||
Vue.component('mk-window', window);
|
Vue.component('mk-window', window);
|
||||||
Vue.component('mk-post-form-window', noteFormWindow);
|
Vue.component('mk-post-form-window', noteFormWindow);
|
||||||
Vue.component('mk-renote-form-window', renoteFormWindow);
|
Vue.component('mk-renote-form-window', renoteFormWindow);
|
||||||
Vue.component('mk-ellipsis-icon', ellipsisIcon);
|
|
||||||
Vue.component('mk-media-image', mediaImage);
|
Vue.component('mk-media-image', mediaImage);
|
||||||
Vue.component('mk-media-image-dialog', mediaImageDialog);
|
Vue.component('mk-media-image-dialog', mediaImageDialog);
|
||||||
Vue.component('mk-media-video', mediaVideo);
|
Vue.component('mk-media-video', mediaVideo);
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="note" tabindex="-1" v-hotkey="keymap" :title="title">
|
<div class="note" v-show="appearNote.deletedAt == null" :tabindex="appearNote.deletedAt == null ? '-1' : null" v-hotkey="keymap" :title="title">
|
||||||
<div class="reply-to" v-if="p.reply && (!$store.getters.isSignedIn || $store.state.settings.showReplyTarget)">
|
<div class="reply-to" v-if="appearNote.reply && (!$store.getters.isSignedIn || $store.state.settings.showReplyTarget)">
|
||||||
<x-sub :note="p.reply"/>
|
<x-sub :note="appearNote.reply"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="renote" v-if="isRenote">
|
<div class="renote" v-if="isRenote">
|
||||||
<mk-avatar class="avatar" :user="note.user"/>
|
<mk-avatar class="avatar" :user="note.user"/>
|
||||||
@ -12,228 +12,76 @@
|
|||||||
<mk-time :time="note.createdAt"/>
|
<mk-time :time="note.createdAt"/>
|
||||||
</div>
|
</div>
|
||||||
<article>
|
<article>
|
||||||
<mk-avatar class="avatar" :user="p.user"/>
|
<mk-avatar class="avatar" :user="appearNote.user"/>
|
||||||
<div class="main">
|
<div class="main">
|
||||||
<mk-note-header class="header" :note="p"/>
|
<mk-note-header class="header" :note="appearNote"/>
|
||||||
<div class="body">
|
<div class="body">
|
||||||
<p v-if="p.cw != null" class="cw">
|
<p v-if="appearNote.cw != null" class="cw">
|
||||||
<span class="text" v-if="p.cw != ''">{{ p.cw }}</span>
|
<span class="text" v-if="appearNote.cw != ''">{{ appearNote.cw }}</span>
|
||||||
<mk-cw-button v-model="showContent"/>
|
<mk-cw-button v-model="showContent"/>
|
||||||
</p>
|
</p>
|
||||||
<div class="content" v-show="p.cw == null || showContent">
|
<div class="content" v-show="appearNote.cw == null || showContent">
|
||||||
<div class="text">
|
<div class="text">
|
||||||
<span v-if="p.isHidden" style="opacity: 0.5">%i18n:@private%</span>
|
<span v-if="appearNote.isHidden" style="opacity: 0.5">%i18n:@private%</span>
|
||||||
<span v-if="p.deletedAt" style="opacity: 0.5">%i18n:@deleted%</span>
|
<a class="reply" v-if="appearNote.reply">%fa:reply%</a>
|
||||||
<a class="reply" v-if="p.reply">%fa:reply%</a>
|
<misskey-flavored-markdown v-if="appearNote.text" :text="appearNote.text" :i="$store.state.i" :class="$style.text"/>
|
||||||
<misskey-flavored-markdown v-if="p.text" :text="p.text" :i="$store.state.i" :class="$style.text"/>
|
<a class="rp" v-if="appearNote.renote">RP:</a>
|
||||||
<a class="rp" v-if="p.renote">RP:</a>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="files" v-if="p.files.length > 0">
|
<div class="files" v-if="appearNote.files.length > 0">
|
||||||
<mk-media-list :media-list="p.files"/>
|
<mk-media-list :media-list="appearNote.files"/>
|
||||||
</div>
|
</div>
|
||||||
<mk-poll v-if="p.poll" :note="p" ref="pollViewer"/>
|
<mk-poll v-if="appearNote.poll" :note="appearNote" ref="pollViewer"/>
|
||||||
<a class="location" v-if="p.geo" :href="`https://maps.google.com/maps?q=${p.geo.coordinates[1]},${p.geo.coordinates[0]}`" target="_blank">%fa:map-marker-alt% 位置情報</a>
|
<a class="location" v-if="appearNote.geo" :href="`https://maps.google.com/maps?q=${appearNote.geo.coordinates[1]},${appearNote.geo.coordinates[0]}`" target="_blank">%fa:map-marker-alt% 位置情報</a>
|
||||||
<div class="map" v-if="p.geo" ref="map"></div>
|
<div class="renote" v-if="appearNote.renote"><mk-note-preview :note="appearNote.renote"/></div>
|
||||||
<div class="renote" v-if="p.renote"><mk-note-preview :note="p.renote"/></div>
|
|
||||||
<mk-url-preview v-for="url in urls" :url="url" :key="url"/>
|
<mk-url-preview v-for="url in urls" :url="url" :key="url"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<footer v-if="p.deletedAt == null">
|
<footer>
|
||||||
<mk-reactions-viewer :note="p" ref="reactionsViewer"/>
|
<mk-reactions-viewer :note="appearNote" ref="reactionsViewer"/>
|
||||||
<button class="replyButton" @click="reply()" title="%i18n:@reply%">
|
<button class="replyButton" @click="reply()" title="%i18n:@reply%">
|
||||||
<template v-if="p.reply">%fa:reply-all%</template>
|
<template v-if="appearNote.reply">%fa:reply-all%</template>
|
||||||
<template v-else>%fa:reply%</template>
|
<template v-else>%fa:reply%</template>
|
||||||
<p class="count" v-if="p.repliesCount > 0">{{ p.repliesCount }}</p>
|
<p class="count" v-if="appearNote.repliesCount > 0">{{ appearNote.repliesCount }}</p>
|
||||||
</button>
|
</button>
|
||||||
<button class="renoteButton" @click="renote()" title="%i18n:@renote%">
|
<button class="renoteButton" @click="renote()" title="%i18n:@renote%">
|
||||||
%fa:retweet%<p class="count" v-if="p.renoteCount > 0">{{ p.renoteCount }}</p>
|
%fa:retweet%<p class="count" v-if="appearNote.renoteCount > 0">{{ appearNote.renoteCount }}</p>
|
||||||
</button>
|
</button>
|
||||||
<button class="reactionButton" :class="{ reacted: p.myReaction != null }" @click="react()" ref="reactButton" title="%i18n:@add-reaction%">
|
<button class="reactionButton" :class="{ reacted: appearNote.myReaction != null }" @click="react()" ref="reactButton" title="%i18n:@add-reaction%">
|
||||||
%fa:plus%<p class="count" v-if="p.reactions_count > 0">{{ p.reactions_count }}</p>
|
%fa:plus%<p class="count" v-if="appearNote.reactions_count > 0">{{ appearNote.reactions_count }}</p>
|
||||||
</button>
|
</button>
|
||||||
<button @click="menu()" ref="menuButton">
|
<button @click="menu()" ref="menuButton">
|
||||||
%fa:ellipsis-h%
|
%fa:ellipsis-h%
|
||||||
</button>
|
</button>
|
||||||
<!-- <button title="%i18n:@detail">
|
|
||||||
<template v-if="!isDetailOpened">%fa:caret-down%</template>
|
|
||||||
<template v-if="isDetailOpened">%fa:caret-up%</template>
|
|
||||||
</button> -->
|
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
<div class="detail" v-if="isDetailOpened">
|
|
||||||
<mk-note-status-graph width="462" height="130" :note="p"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import parse from '../../../../../mfm/parse';
|
|
||||||
|
|
||||||
import MkPostFormWindow from './post-form-window.vue';
|
import MkPostFormWindow from './post-form-window.vue';
|
||||||
import MkRenoteFormWindow from './renote-form-window.vue';
|
import MkRenoteFormWindow from './renote-form-window.vue';
|
||||||
import MkNoteMenu from '../../../common/views/components/note-menu.vue';
|
|
||||||
import MkReactionPicker from '../../../common/views/components/reaction-picker.vue';
|
|
||||||
import XSub from './notes.note.sub.vue';
|
import XSub from './notes.note.sub.vue';
|
||||||
import { sum } from '../../../../../prelude/array';
|
import noteMixin from '../../../common/scripts/note-mixin';
|
||||||
import noteSubscriber from '../../../common/scripts/note-subscriber';
|
import noteSubscriber from '../../../common/scripts/note-subscriber';
|
||||||
|
|
||||||
function focus(el, fn) {
|
|
||||||
const target = fn(el);
|
|
||||||
if (target) {
|
|
||||||
if (target.hasAttribute('tabindex')) {
|
|
||||||
target.focus();
|
|
||||||
} else {
|
|
||||||
focus(target, fn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
components: {
|
components: {
|
||||||
XSub
|
XSub
|
||||||
},
|
},
|
||||||
|
|
||||||
mixins: [noteSubscriber('note')],
|
mixins: [
|
||||||
|
noteMixin(),
|
||||||
|
noteSubscriber('note')
|
||||||
|
],
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
note: {
|
note: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
},
|
|
||||||
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
showContent: false,
|
|
||||||
isDetailOpened: false
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
keymap(): any {
|
|
||||||
return {
|
|
||||||
'r|left': () => this.reply(true),
|
|
||||||
'e|a|plus': () => this.react(true),
|
|
||||||
'q|right': () => this.renote(true),
|
|
||||||
'ctrl+q|ctrl+right': this.renoteDirectly,
|
|
||||||
'up|k|shift+tab': this.focusBefore,
|
|
||||||
'down|j|tab': this.focusAfter,
|
|
||||||
'esc': this.blur,
|
|
||||||
'm|o': () => this.menu(true),
|
|
||||||
's': this.toggleShowContent,
|
|
||||||
'1': () => this.reactDirectly('like'),
|
|
||||||
'2': () => this.reactDirectly('love'),
|
|
||||||
'3': () => this.reactDirectly('laugh'),
|
|
||||||
'4': () => this.reactDirectly('hmm'),
|
|
||||||
'5': () => this.reactDirectly('surprise'),
|
|
||||||
'6': () => this.reactDirectly('congrats'),
|
|
||||||
'7': () => this.reactDirectly('angry'),
|
|
||||||
'8': () => this.reactDirectly('confused'),
|
|
||||||
'9': () => this.reactDirectly('rip'),
|
|
||||||
'0': () => this.reactDirectly('pudding'),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
isRenote(): boolean {
|
|
||||||
return (this.note.renote &&
|
|
||||||
this.note.text == null &&
|
|
||||||
this.note.fileIds.length == 0 &&
|
|
||||||
this.note.poll == null);
|
|
||||||
},
|
|
||||||
|
|
||||||
p(): any {
|
|
||||||
return this.isRenote ? this.note.renote : this.note;
|
|
||||||
},
|
|
||||||
|
|
||||||
reactionsCount(): number {
|
|
||||||
return this.p.reactionCounts
|
|
||||||
? sum(Object.values(this.p.reactionCounts))
|
|
||||||
: 0;
|
|
||||||
},
|
|
||||||
|
|
||||||
title(): string {
|
|
||||||
return new Date(this.p.createdAt).toLocaleString();
|
|
||||||
},
|
|
||||||
|
|
||||||
urls(): string[] {
|
|
||||||
if (this.p.text) {
|
|
||||||
const ast = parse(this.p.text);
|
|
||||||
return ast
|
|
||||||
.filter(t => (t.type == 'url' || t.type == 'link') && !t.silent)
|
|
||||||
.map(t => t.url);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
reply(viaKeyboard = false) {
|
|
||||||
(this as any).os.new(MkPostFormWindow, {
|
|
||||||
reply: this.p,
|
|
||||||
animation: !viaKeyboard
|
|
||||||
}).$once('closed', this.focus);
|
|
||||||
},
|
|
||||||
|
|
||||||
renote(viaKeyboard = false) {
|
|
||||||
(this as any).os.new(MkRenoteFormWindow, {
|
|
||||||
note: this.p,
|
|
||||||
animation: !viaKeyboard
|
|
||||||
}).$once('closed', this.focus);
|
|
||||||
},
|
|
||||||
|
|
||||||
renoteDirectly() {
|
|
||||||
(this as any).api('notes/create', {
|
|
||||||
renoteId: this.p.id
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
react(viaKeyboard = false) {
|
|
||||||
this.blur();
|
|
||||||
(this as any).os.new(MkReactionPicker, {
|
|
||||||
source: this.$refs.reactButton,
|
|
||||||
note: this.p,
|
|
||||||
showFocus: viaKeyboard,
|
|
||||||
animation: !viaKeyboard
|
|
||||||
}).$once('closed', this.focus);
|
|
||||||
},
|
|
||||||
|
|
||||||
reactDirectly(reaction) {
|
|
||||||
(this as any).api('notes/reactions/create', {
|
|
||||||
noteId: this.p.id,
|
|
||||||
reaction: reaction
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
menu(viaKeyboard = false) {
|
|
||||||
(this as any).os.new(MkNoteMenu, {
|
|
||||||
source: this.$refs.menuButton,
|
|
||||||
note: this.p,
|
|
||||||
animation: !viaKeyboard
|
|
||||||
}).$once('closed', this.focus);
|
|
||||||
},
|
|
||||||
|
|
||||||
toggleShowContent() {
|
|
||||||
this.showContent = !this.showContent;
|
|
||||||
},
|
|
||||||
|
|
||||||
focus() {
|
|
||||||
this.$el.focus();
|
|
||||||
},
|
|
||||||
|
|
||||||
blur() {
|
|
||||||
this.$el.blur();
|
|
||||||
},
|
|
||||||
|
|
||||||
focusBefore() {
|
|
||||||
focus(this.$el, e => e.previousElementSibling);
|
|
||||||
},
|
|
||||||
|
|
||||||
focusAfter() {
|
|
||||||
focus(this.$el, e => e.nextElementSibling);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@ -445,10 +293,6 @@ export default Vue.extend({
|
|||||||
&.reacted, &.reacted:hover
|
&.reacted, &.reacted:hover
|
||||||
color var(--noteActionsReactionHover)
|
color var(--noteActionsReactionHover)
|
||||||
|
|
||||||
> .detail
|
|
||||||
padding-top 4px
|
|
||||||
background rgba(#000, 0.0125)
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style lang="stylus" module>
|
<style lang="stylus" module>
|
||||||
|
@ -9,6 +9,12 @@
|
|||||||
<button @click="resolveInitPromise">%i18n:@retry%</button>
|
<button @click="resolveInitPromise">%i18n:@retry%</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="placeholder" v-if="fetching">
|
||||||
|
<template v-for="i in 10">
|
||||||
|
<mk-note-skeleton :key="i"/>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- トランジションを有効にするとなぜかメモリリークする -->
|
<!-- トランジションを有効にするとなぜかメモリリークする -->
|
||||||
<component :is="!$store.state.device.reduceMotion ? 'transition-group' : 'div'" name="mk-notes" class="notes transition" tag="div" ref="notes">
|
<component :is="!$store.state.device.reduceMotion ? 'transition-group' : 'div'" name="mk-notes" class="notes transition" tag="div" ref="notes">
|
||||||
<template v-for="(note, i) in _notes">
|
<template v-for="(note, i) in _notes">
|
||||||
@ -226,6 +232,10 @@ export default Vue.extend({
|
|||||||
> *
|
> *
|
||||||
transition transform .3s ease, opacity .3s ease
|
transition transform .3s ease, opacity .3s ease
|
||||||
|
|
||||||
|
> .placeholder
|
||||||
|
padding 32px
|
||||||
|
opacity 0.3
|
||||||
|
|
||||||
> .notes
|
> .notes
|
||||||
> .date
|
> .date
|
||||||
display block
|
display block
|
||||||
|
@ -1,5 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="mk-notifications">
|
<div class="mk-notifications">
|
||||||
|
<div class="placeholder" v-if="fetching">
|
||||||
|
<template v-for="i in 10">
|
||||||
|
<mk-note-skeleton :key="i"/>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="notifications" v-if="notifications.length != 0">
|
<div class="notifications" v-if="notifications.length != 0">
|
||||||
<!-- トランジションを有効にするとなぜかメモリリークする -->
|
<!-- トランジションを有効にするとなぜかメモリリークする -->
|
||||||
<component :is="!$store.state.device.reduceMotion ? 'transition-group' : 'div'" name="mk-notifications" class="transition" tag="div">
|
<component :is="!$store.state.device.reduceMotion ? 'transition-group' : 'div'" name="mk-notifications" class="transition" tag="div">
|
||||||
@ -102,7 +108,6 @@
|
|||||||
<template v-if="fetchingMoreNotifications">%fa:spinner .pulse .fw%</template>{{ fetchingMoreNotifications ? '%i18n:common.loading%' : '%i18n:@more%' }}
|
<template v-if="fetchingMoreNotifications">%fa:spinner .pulse .fw%</template>{{ fetchingMoreNotifications ? '%i18n:common.loading%' : '%i18n:@more%' }}
|
||||||
</button>
|
</button>
|
||||||
<p class="empty" v-if="notifications.length == 0 && !fetching">%i18n:@empty%</p>
|
<p class="empty" v-if="notifications.length == 0 && !fetching">%i18n:@empty%</p>
|
||||||
<p class="loading" v-if="fetching">%fa:spinner .pulse .fw%%i18n:common.loading%<mk-ellipsis/></p>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -181,8 +186,7 @@ export default Vue.extend({
|
|||||||
|
|
||||||
onNotification(notification) {
|
onNotification(notification) {
|
||||||
// TODO: ユーザーが画面を見てないと思われるとき(ブラウザやタブがアクティブじゃないなど)は送信しない
|
// TODO: ユーザーが画面を見てないと思われるとき(ブラウザやタブがアクティブじゃないなど)は送信しない
|
||||||
this.connection.send({
|
(this as any).os.stream.send('readNotification', {
|
||||||
type: 'readNotification',
|
|
||||||
id: notification.id
|
id: notification.id
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -203,6 +207,10 @@ export default Vue.extend({
|
|||||||
> *
|
> *
|
||||||
transition transform .3s ease, opacity .3s ease
|
transition transform .3s ease, opacity .3s ease
|
||||||
|
|
||||||
|
> .placeholder
|
||||||
|
padding 16px
|
||||||
|
opacity 0.3
|
||||||
|
|
||||||
> .notifications
|
> .notifications
|
||||||
> div
|
> div
|
||||||
> .notification
|
> .notification
|
||||||
@ -320,13 +328,4 @@ export default Vue.extend({
|
|||||||
text-align center
|
text-align center
|
||||||
color #aaa
|
color #aaa
|
||||||
|
|
||||||
> .loading
|
|
||||||
margin 0
|
|
||||||
padding 16px
|
|
||||||
text-align center
|
|
||||||
color #aaa
|
|
||||||
|
|
||||||
> [data-fa]
|
|
||||||
margin-right 4px
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
@ -65,6 +65,7 @@ import { host } from '../../../config';
|
|||||||
import { erase, unique } from '../../../../../prelude/array';
|
import { erase, unique } from '../../../../../prelude/array';
|
||||||
import { length } from 'stringz';
|
import { length } from 'stringz';
|
||||||
import parseAcct from '../../../../../misc/acct/parse';
|
import parseAcct from '../../../../../misc/acct/parse';
|
||||||
|
import { toASCII } from 'punycode';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
components: {
|
components: {
|
||||||
@ -158,14 +159,14 @@ export default Vue.extend({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.reply && this.reply.user.host != null) {
|
if (this.reply && this.reply.user.host != null) {
|
||||||
this.text = `@${this.reply.user.username}@${this.reply.user.host} `;
|
this.text = `@${this.reply.user.username}@${toASCII(this.reply.user.host)} `;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.reply && this.reply.text != null) {
|
if (this.reply && this.reply.text != null) {
|
||||||
const ast = parse(this.reply.text);
|
const ast = parse(this.reply.text);
|
||||||
|
|
||||||
ast.filter(t => t.type == 'mention').forEach(x => {
|
ast.filter(t => t.type == 'mention').forEach(x => {
|
||||||
const mention = x.host ? `@${x.username}@${x.host}` : `@${x.username}`;
|
const mention = x.host ? `@${x.username}@${toASCII(x.host)}` : `@${x.username}`;
|
||||||
|
|
||||||
// 自分は除外
|
// 自分は除外
|
||||||
if (this.$store.state.i.username == x.username && x.host == null) return;
|
if (this.$store.state.i.username == x.username && x.host == null) return;
|
||||||
|
@ -21,12 +21,13 @@
|
|||||||
<ui-button primary @click="save">%i18n:@save%</ui-button>
|
<ui-button primary @click="save">%i18n:@save%</ui-button>
|
||||||
<section>
|
<section>
|
||||||
<h2>%i18n:@locked-account%</h2>
|
<h2>%i18n:@locked-account%</h2>
|
||||||
<ui-switch v-model="$store.state.i.isLocked" @change="onChangeIsLocked">%i18n:@is-locked%</ui-switch>
|
<ui-switch v-model="isLocked" @change="save(false)">%i18n:@is-locked%</ui-switch>
|
||||||
|
<ui-switch v-model="carefulBot" @change="save(false)">%i18n:@careful-bot%</ui-switch>
|
||||||
</section>
|
</section>
|
||||||
<section>
|
<section>
|
||||||
<h2>%i18n:@other%</h2>
|
<h2>%i18n:@other%</h2>
|
||||||
<ui-switch v-model="$store.state.i.isBot" @change="onChangeIsBot">%i18n:@is-bot%</ui-switch>
|
<ui-switch v-model="isBot" @change="save(false)">%i18n:@is-bot%</ui-switch>
|
||||||
<ui-switch v-model="$store.state.i.isCat" @change="onChangeIsCat">%i18n:@is-cat%</ui-switch>
|
<ui-switch v-model="isCat" @change="save(false)">%i18n:@is-cat%</ui-switch>
|
||||||
<ui-switch v-model="alwaysMarkNsfw">%i18n:common.always-mark-nsfw%</ui-switch>
|
<ui-switch v-model="alwaysMarkNsfw">%i18n:common.always-mark-nsfw%</ui-switch>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
@ -42,6 +43,10 @@ export default Vue.extend({
|
|||||||
location: null,
|
location: null,
|
||||||
description: null,
|
description: null,
|
||||||
birthday: null,
|
birthday: null,
|
||||||
|
isBot: false,
|
||||||
|
isCat: false,
|
||||||
|
isLocked: false,
|
||||||
|
carefulBot: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -55,34 +60,29 @@ export default Vue.extend({
|
|||||||
this.location = this.$store.state.i.profile.location;
|
this.location = this.$store.state.i.profile.location;
|
||||||
this.description = this.$store.state.i.description;
|
this.description = this.$store.state.i.description;
|
||||||
this.birthday = this.$store.state.i.profile.birthday;
|
this.birthday = this.$store.state.i.profile.birthday;
|
||||||
|
this.isCat = this.$store.state.i.isCat;
|
||||||
|
this.isBot = this.$store.state.i.isBot;
|
||||||
|
this.isLocked = this.$store.state.i.isLocked;
|
||||||
|
this.carefulBot = this.$store.state.i.carefulBot;
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
updateAvatar() {
|
updateAvatar() {
|
||||||
(this as any).apis.updateAvatar();
|
(this as any).apis.updateAvatar();
|
||||||
},
|
},
|
||||||
save() {
|
save(notify) {
|
||||||
(this as any).api('i/update', {
|
(this as any).api('i/update', {
|
||||||
name: this.name || null,
|
name: this.name || null,
|
||||||
location: this.location || null,
|
location: this.location || null,
|
||||||
description: this.description || null,
|
description: this.description || null,
|
||||||
birthday: this.birthday || null
|
birthday: this.birthday || null,
|
||||||
|
isCat: this.isCat,
|
||||||
|
isBot: this.isBot,
|
||||||
|
isLocked: this.isLocked,
|
||||||
|
carefulBot: this.carefulBot
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
(this as any).apis.notify('%i18n:@profile-updated%');
|
if (notify) {
|
||||||
});
|
(this as any).apis.notify('%i18n:@profile-updated%');
|
||||||
},
|
}
|
||||||
onChangeIsLocked() {
|
|
||||||
(this as any).api('i/update', {
|
|
||||||
isLocked: this.$store.state.i.isLocked
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onChangeIsBot() {
|
|
||||||
(this as any).api('i/update', {
|
|
||||||
isBot: this.$store.state.i.isBot
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onChangeIsCat() {
|
|
||||||
(this as any).api('i/update', {
|
|
||||||
isCat: this.$store.state.i.isCat
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -88,6 +88,13 @@
|
|||||||
<ui-switch v-model="disableAnimatedMfm">%i18n:common.disable-animated-mfm%</ui-switch>
|
<ui-switch v-model="disableAnimatedMfm">%i18n:common.disable-animated-mfm%</ui-switch>
|
||||||
<ui-switch v-model="games_reversi_showBoardLabels">%i18n:common.show-reversi-board-labels%</ui-switch>
|
<ui-switch v-model="games_reversi_showBoardLabels">%i18n:common.show-reversi-board-labels%</ui-switch>
|
||||||
<ui-switch v-model="games_reversi_useContrastStones">%i18n:common.use-contrast-reversi-stones%</ui-switch>
|
<ui-switch v-model="games_reversi_useContrastStones">%i18n:common.use-contrast-reversi-stones%</ui-switch>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<header>%i18n:@navbar-position%</header>
|
||||||
|
<ui-radio v-model="navbar" value="top">%i18n:@navbar-position-top%</ui-radio>
|
||||||
|
<ui-radio v-model="navbar" value="left">%i18n:@navbar-position-left%</ui-radio>
|
||||||
|
<ui-radio v-model="navbar" value="right">%i18n:@navbar-position-right%</ui-radio>
|
||||||
|
</section>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="web" v-show="page == 'web'">
|
<section class="web" v-show="page == 'web'">
|
||||||
@ -293,6 +300,11 @@ export default Vue.extend({
|
|||||||
set(value) { this.$store.commit('device/set', { key: 'darkmode', value }); }
|
set(value) { this.$store.commit('device/set', { key: 'darkmode', value }); }
|
||||||
},
|
},
|
||||||
|
|
||||||
|
navbar: {
|
||||||
|
get() { return this.$store.state.device.navbar; },
|
||||||
|
set(value) { this.$store.commit('device/set', { key: 'navbar', value }); }
|
||||||
|
},
|
||||||
|
|
||||||
enableSounds: {
|
enableSounds: {
|
||||||
get() { return this.$store.state.device.enableSounds; },
|
get() { return this.$store.state.device.enableSounds; },
|
||||||
set(value) { this.$store.commit('device/set', { key: 'enableSounds', value }); }
|
set(value) { this.$store.commit('device/set', { key: 'enableSounds', value }); }
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="mk-timeline-core">
|
<div class="mk-timeline-core">
|
||||||
<mk-friends-maker v-if="src == 'home' && alone"/>
|
<mk-friends-maker v-if="src == 'home' && alone"/>
|
||||||
<div class="fetching" v-if="fetching">
|
|
||||||
<mk-ellipsis-icon/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<mk-notes ref="timeline" :more="existMore ? more : null">
|
<mk-notes ref="timeline" :more="existMore ? more : null">
|
||||||
<p :class="$style.empty" slot="empty">
|
<p :class="$style.empty" slot="empty">
|
||||||
@ -170,15 +167,10 @@ export default Vue.extend({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
|
|
||||||
|
|
||||||
.mk-timeline-core
|
.mk-timeline-core
|
||||||
> .mk-friends-maker
|
> .mk-friends-maker
|
||||||
border-bottom solid 1px #eee
|
border-bottom solid 1px #eee
|
||||||
|
|
||||||
> .fetching
|
|
||||||
padding 64px 0
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style lang="stylus" module>
|
<style lang="stylus" module>
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
<li @click="list">
|
<li @click="list">
|
||||||
<p>%fa:list%<span>%i18n:@lists%</span>%fa:angle-right%</p>
|
<p>%fa:list%<span>%i18n:@lists%</span>%fa:angle-right%</p>
|
||||||
</li>
|
</li>
|
||||||
<li @click="followRequests" v-if="$store.state.i.isLocked">
|
<li @click="followRequests" v-if="($store.state.i.isLocked || $store.state.i.carefulBot)">
|
||||||
<p>%fa:envelope R%<span>%i18n:@follow-requests%<i v-if="$store.state.i.pendingReceivedFollowRequestsCount">{{ $store.state.i.pendingReceivedFollowRequestsCount }}</i></span>%fa:angle-right%</p>
|
<p>%fa:envelope R%<span>%i18n:@follow-requests%<i v-if="$store.state.i.pendingReceivedFollowRequestsCount">{{ $store.state.i.pendingReceivedFollowRequestsCount }}</i></span>%fa:angle-right%</p>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -17,8 +17,6 @@ export default Vue.extend({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
|
|
||||||
|
|
||||||
.note
|
.note
|
||||||
display inline-block
|
display inline-block
|
||||||
padding 8px
|
padding 8px
|
||||||
|
368
src/client/app/desktop/views/components/ui.sidebar.vue
Normal file
368
src/client/app/desktop/views/components/ui.sidebar.vue
Normal file
@ -0,0 +1,368 @@
|
|||||||
|
<template>
|
||||||
|
<div class="header" :class="navbar">
|
||||||
|
<div class="body">
|
||||||
|
<div class="post">
|
||||||
|
<button @click="post" title="%i18n:@post%">%fa:pencil-alt%</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="nav" v-if="$store.getters.isSignedIn">
|
||||||
|
<div class="home" :class="{ active: $route.name == 'index' }" @click="goToTop">
|
||||||
|
<router-link to="/">%fa:home%</router-link>
|
||||||
|
</div>
|
||||||
|
<div class="deck" :class="{ active: $route.name == 'deck' }" @click="goToTop">
|
||||||
|
<router-link to="/deck">%fa:columns%</router-link>
|
||||||
|
</div>
|
||||||
|
<div class="messaging">
|
||||||
|
<a @click="messaging">%fa:comments%<template v-if="hasUnreadMessagingMessage">%fa:circle%</template></a>
|
||||||
|
</div>
|
||||||
|
<div class="game">
|
||||||
|
<a @click="game">%fa:gamepad%<template v-if="hasGameInvitations">%fa:circle%</template></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="nav bottom" v-if="$store.getters.isSignedIn">
|
||||||
|
<div>
|
||||||
|
<a @click="drive">%fa:cloud%</a>
|
||||||
|
</div>
|
||||||
|
<div ref="notificationsButton" :class="{ active: showNotifications }">
|
||||||
|
<a @click="notifications">%fa:R bell%</a>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<a @click="settings">%fa:cog%</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="account">
|
||||||
|
<router-link :to="`/@${ $store.state.i.username }`">
|
||||||
|
<mk-avatar class="avatar" :user="$store.state.i"/>
|
||||||
|
</router-link>
|
||||||
|
|
||||||
|
<div class="nav menu">
|
||||||
|
<div class="signout">
|
||||||
|
<a @click="signout">%fa:power-off%</a>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<router-link to="/i/favorites">%fa:star%</router-link>
|
||||||
|
</div>
|
||||||
|
<div v-if="($store.state.i.isLocked || $store.state.i.carefulBot)">
|
||||||
|
<a @click="followRequests">%fa:envelope R%<i v-if="$store.state.i.pendingReceivedFollowRequestsCount">{{ $store.state.i.pendingReceivedFollowRequestsCount }}</i></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="nav dark">
|
||||||
|
<div>
|
||||||
|
<a @click="dark"><template v-if="$store.state.device.darkmode">%fa:moon%</template><template v-else>%fa:R moon%</template></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<transition :name="`slide-${navbar}`">
|
||||||
|
<div class="notifications" v-if="showNotifications" ref="notifications" :class="navbar">
|
||||||
|
<mk-notifications/>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from 'vue';
|
||||||
|
import MkUserListsWindow from './user-lists-window.vue';
|
||||||
|
import MkFollowRequestsWindow from './received-follow-requests-window.vue';
|
||||||
|
import MkSettingsWindow from './settings-window.vue';
|
||||||
|
import MkDriveWindow from './drive-window.vue';
|
||||||
|
import MkMessagingWindow from './messaging-window.vue';
|
||||||
|
import MkGameWindow from './game-window.vue';
|
||||||
|
import contains from '../../../common/scripts/contains';
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
hasGameInvitations: false,
|
||||||
|
connection: null,
|
||||||
|
showNotifications: false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
hasUnreadMessagingMessage(): boolean {
|
||||||
|
return this.$store.getters.isSignedIn && this.$store.state.i.hasUnreadMessagingMessage;
|
||||||
|
},
|
||||||
|
|
||||||
|
navbar(): string {
|
||||||
|
return this.$store.state.device.navbar;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
if (this.$store.getters.isSignedIn) {
|
||||||
|
this.connection = (this as any).os.stream.useSharedConnection('main');
|
||||||
|
|
||||||
|
this.connection.on('reversiInvited', this.onReversiInvited);
|
||||||
|
this.connection.on('reversi_no_invites', this.onReversiNoInvites);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
beforeDestroy() {
|
||||||
|
if (this.$store.getters.isSignedIn) {
|
||||||
|
this.connection.dispose();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
onReversiInvited() {
|
||||||
|
this.hasGameInvitations = true;
|
||||||
|
},
|
||||||
|
|
||||||
|
onReversiNoInvites() {
|
||||||
|
this.hasGameInvitations = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
messaging() {
|
||||||
|
(this as any).os.new(MkMessagingWindow);
|
||||||
|
},
|
||||||
|
|
||||||
|
game() {
|
||||||
|
(this as any).os.new(MkGameWindow);
|
||||||
|
},
|
||||||
|
|
||||||
|
post() {
|
||||||
|
(this as any).apis.post();
|
||||||
|
},
|
||||||
|
|
||||||
|
drive() {
|
||||||
|
(this as any).os.new(MkDriveWindow);
|
||||||
|
},
|
||||||
|
|
||||||
|
list() {
|
||||||
|
const w = (this as any).os.new(MkUserListsWindow);
|
||||||
|
w.$once('choosen', list => {
|
||||||
|
this.$router.push(`i/lists/${ list.id }`);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
followRequests() {
|
||||||
|
(this as any).os.new(MkFollowRequestsWindow);
|
||||||
|
},
|
||||||
|
|
||||||
|
settings() {
|
||||||
|
(this as any).os.new(MkSettingsWindow);
|
||||||
|
},
|
||||||
|
|
||||||
|
signout() {
|
||||||
|
(this as any).os.signout();
|
||||||
|
},
|
||||||
|
|
||||||
|
notifications() {
|
||||||
|
this.showNotifications ? this.closeNotifications() : this.openNotifications();
|
||||||
|
},
|
||||||
|
|
||||||
|
openNotifications() {
|
||||||
|
this.showNotifications = true;
|
||||||
|
Array.from(document.querySelectorAll('body *')).forEach(el => {
|
||||||
|
el.addEventListener('mousedown', this.onMousedown);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
closeNotifications() {
|
||||||
|
this.showNotifications = false;
|
||||||
|
Array.from(document.querySelectorAll('body *')).forEach(el => {
|
||||||
|
el.removeEventListener('mousedown', this.onMousedown);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
onMousedown(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (
|
||||||
|
!contains(this.$refs.notifications, e.target) &&
|
||||||
|
this.$refs.notifications != e.target &&
|
||||||
|
!contains(this.$refs.notificationsButton, e.target) &&
|
||||||
|
this.$refs.notificationsButton != e.target
|
||||||
|
) {
|
||||||
|
this.closeNotifications();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
dark() {
|
||||||
|
this.$store.commit('device/set', {
|
||||||
|
key: 'darkmode',
|
||||||
|
value: !this.$store.state.device.darkmode
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
goToTop() {
|
||||||
|
window.scrollTo({
|
||||||
|
top: 0,
|
||||||
|
behavior: 'smooth'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="stylus" scoped>
|
||||||
|
.header
|
||||||
|
$width = 68px
|
||||||
|
|
||||||
|
position fixed
|
||||||
|
top 0
|
||||||
|
z-index 1000
|
||||||
|
width $width
|
||||||
|
height 100%
|
||||||
|
|
||||||
|
&.left
|
||||||
|
left 0
|
||||||
|
box-shadow var(--shadowRight)
|
||||||
|
|
||||||
|
&.right
|
||||||
|
right 0
|
||||||
|
box-shadow var(--shadowLeft)
|
||||||
|
|
||||||
|
> .body
|
||||||
|
position fixed
|
||||||
|
top 0
|
||||||
|
z-index 1
|
||||||
|
width $width
|
||||||
|
height 100%
|
||||||
|
background var(--desktopHeaderBg)
|
||||||
|
|
||||||
|
> .post
|
||||||
|
width $width
|
||||||
|
height $width
|
||||||
|
padding 12px
|
||||||
|
|
||||||
|
> button
|
||||||
|
display inline-block
|
||||||
|
margin 0
|
||||||
|
padding 0
|
||||||
|
height 100%
|
||||||
|
width 100%
|
||||||
|
font-size 1.2em
|
||||||
|
font-weight normal
|
||||||
|
text-decoration none
|
||||||
|
color var(--primaryForeground)
|
||||||
|
background var(--primary) !important
|
||||||
|
outline none
|
||||||
|
border none
|
||||||
|
border-radius 100%
|
||||||
|
transition background 0.1s ease
|
||||||
|
cursor pointer
|
||||||
|
|
||||||
|
*
|
||||||
|
pointer-events none
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
background var(--primaryLighten10) !important
|
||||||
|
|
||||||
|
&:active
|
||||||
|
background var(--primaryDarken10) !important
|
||||||
|
transition background 0s ease
|
||||||
|
|
||||||
|
> .nav.bottom
|
||||||
|
position absolute
|
||||||
|
bottom 128px
|
||||||
|
left 0
|
||||||
|
|
||||||
|
> .account
|
||||||
|
position absolute
|
||||||
|
bottom 64px
|
||||||
|
left 0
|
||||||
|
width $width
|
||||||
|
height $width
|
||||||
|
padding 14px
|
||||||
|
|
||||||
|
> .menu
|
||||||
|
display none
|
||||||
|
position absolute
|
||||||
|
bottom 64px
|
||||||
|
left 0
|
||||||
|
background var(--desktopHeaderBg)
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
> .menu
|
||||||
|
display block
|
||||||
|
|
||||||
|
> *:not(.menu)
|
||||||
|
display block
|
||||||
|
width 100%
|
||||||
|
height 100%
|
||||||
|
|
||||||
|
> .avatar
|
||||||
|
pointer-events none
|
||||||
|
width 100%
|
||||||
|
height 100%
|
||||||
|
|
||||||
|
> .dark
|
||||||
|
position absolute
|
||||||
|
bottom 0
|
||||||
|
left 0
|
||||||
|
width $width
|
||||||
|
height $width
|
||||||
|
|
||||||
|
> .notifications
|
||||||
|
position fixed
|
||||||
|
top 0
|
||||||
|
width 350px
|
||||||
|
height 100%
|
||||||
|
overflow auto
|
||||||
|
background var(--face)
|
||||||
|
|
||||||
|
&.left
|
||||||
|
left $width
|
||||||
|
box-shadow var(--shadowRight)
|
||||||
|
|
||||||
|
&.right
|
||||||
|
right $width
|
||||||
|
box-shadow var(--shadowLeft)
|
||||||
|
|
||||||
|
.nav
|
||||||
|
> *
|
||||||
|
> *
|
||||||
|
display block
|
||||||
|
width $width
|
||||||
|
line-height 52px
|
||||||
|
text-align center
|
||||||
|
font-size 18px
|
||||||
|
color var(--desktopHeaderFg)
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
background rgba(0, 0, 0, 0.05)
|
||||||
|
color var(--desktopHeaderHoverFg)
|
||||||
|
text-decoration none
|
||||||
|
|
||||||
|
&:active
|
||||||
|
background rgba(0, 0, 0, 0.1)
|
||||||
|
|
||||||
|
&.left
|
||||||
|
.nav
|
||||||
|
> *
|
||||||
|
&.active
|
||||||
|
box-shadow -4px 0 var(--primary) inset
|
||||||
|
|
||||||
|
&.right
|
||||||
|
.nav
|
||||||
|
> *
|
||||||
|
&.active
|
||||||
|
box-shadow 4px 0 var(--primary) inset
|
||||||
|
|
||||||
|
.slide-left-enter-active,
|
||||||
|
.slide-left-leave-active {
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-left-enter, .slide-left-leave-to {
|
||||||
|
transform: translateX(-16px);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-right-enter-active,
|
||||||
|
.slide-right-leave-active {
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-right-enter, .slide-right-leave-to {
|
||||||
|
transform: translateX(16px);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,8 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="mk-ui" v-hotkey.global="keymap">
|
<div class="mk-ui" v-hotkey.global="keymap">
|
||||||
<div class="bg" v-if="$store.getters.isSignedIn && $store.state.i.wallpaperUrl" :style="style"></div>
|
<div class="bg" v-if="$store.getters.isSignedIn && $store.state.i.wallpaperUrl" :style="style"></div>
|
||||||
<x-header class="header" v-show="!zenMode" ref="header"/>
|
<x-header class="header" v-if="navbar == 'top'" v-show="!zenMode" ref="header"/>
|
||||||
<div class="content">
|
<x-sidebar class="sidebar" v-if="navbar != 'top'" ref="sidebar"/>
|
||||||
|
<div class="content" :class="[{ sidebar: navbar != 'top' }, navbar]">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
<mk-stream-indicator v-if="$store.getters.isSignedIn"/>
|
<mk-stream-indicator v-if="$store.getters.isSignedIn"/>
|
||||||
@ -12,10 +13,12 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import XHeader from './ui.header.vue';
|
import XHeader from './ui.header.vue';
|
||||||
|
import XSidebar from './ui.sidebar.vue';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
components: {
|
components: {
|
||||||
XHeader
|
XHeader,
|
||||||
|
XSidebar
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
@ -25,6 +28,10 @@ export default Vue.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
|
navbar(): string {
|
||||||
|
return this.$store.state.device.navbar;
|
||||||
|
},
|
||||||
|
|
||||||
style(): any {
|
style(): any {
|
||||||
if (!this.$store.getters.isSignedIn || this.$store.state.i.wallpaperUrl == null) return {};
|
if (!this.$store.getters.isSignedIn || this.$store.state.i.wallpaperUrl == null) return {};
|
||||||
return {
|
return {
|
||||||
@ -45,6 +52,12 @@ export default Vue.extend({
|
|||||||
watch: {
|
watch: {
|
||||||
'$store.state.uiHeaderHeight'() {
|
'$store.state.uiHeaderHeight'() {
|
||||||
this.$el.style.paddingTop = this.$store.state.uiHeaderHeight + 'px';
|
this.$el.style.paddingTop = this.$store.state.uiHeaderHeight + 'px';
|
||||||
|
},
|
||||||
|
|
||||||
|
navbar() {
|
||||||
|
if (this.navbar != 'top') {
|
||||||
|
this.$store.commit('setUiHeaderHeight', 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -87,4 +100,10 @@ export default Vue.extend({
|
|||||||
@media (max-width 1000px)
|
@media (max-width 1000px)
|
||||||
display none
|
display none
|
||||||
|
|
||||||
|
> .content.sidebar.left
|
||||||
|
padding-left 64px
|
||||||
|
|
||||||
|
> .content.sidebar.right
|
||||||
|
padding-right 64px
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
@ -26,12 +26,14 @@ export default Vue.extend({
|
|||||||
this.init();
|
this.init();
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
this.connection.close();
|
this.connection.dispose();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
init() {
|
init() {
|
||||||
if (this.connection) this.connection.close();
|
if (this.connection) this.connection.dispose();
|
||||||
this.connection = new UserListStream((this as any).os, this.$store.state.i, this.list.id);
|
this.connection = (this as any).os.stream.connectToChannel('userList', {
|
||||||
|
listId: this.list.id
|
||||||
|
});
|
||||||
this.connection.on('note', this.onNote);
|
this.connection.on('note', this.onNote);
|
||||||
this.connection.on('userAdded', this.onUserAdded);
|
this.connection.on('userAdded', this.onUserAdded);
|
||||||
this.connection.on('userRemoved', this.onUserRemoved);
|
this.connection.on('userRemoved', this.onUserRemoved);
|
||||||
|
@ -77,9 +77,8 @@ export default Vue.extend({
|
|||||||
mounted() {
|
mounted() {
|
||||||
this.connection.on('stats', this.onStats);
|
this.connection.on('stats', this.onStats);
|
||||||
this.connection.on('statsLog', this.onStatsLog);
|
this.connection.on('statsLog', this.onStatsLog);
|
||||||
this.connection.send({
|
this.connection.send('requestLog', {
|
||||||
type: 'requestLog',
|
id: Math.random().toString().substr(2, 8),
|
||||||
id: Math.random().toString(),
|
|
||||||
length: 200
|
length: 200
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -310,7 +310,7 @@ export default Vue.extend({
|
|||||||
|
|
||||||
> header
|
> header
|
||||||
display flex
|
display flex
|
||||||
z-index 1
|
z-index 2
|
||||||
line-height $header-height
|
line-height $header-height
|
||||||
padding 0 16px
|
padding 0 16px
|
||||||
font-size 14px
|
font-size 14px
|
||||||
|
@ -46,8 +46,10 @@ export default Vue.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
if (this.connection) this.connection.close();
|
if (this.connection) this.connection.dispose();
|
||||||
this.connection = new UserListStream((this as any).os, this.$store.state.i, this.list.id);
|
this.connection = (this as any).os.stream.connectToChannel('userList', {
|
||||||
|
listId: this.list.id
|
||||||
|
});
|
||||||
this.connection.on('note', this.onNote);
|
this.connection.on('note', this.onNote);
|
||||||
this.connection.on('userAdded', this.onUserAdded);
|
this.connection.on('userAdded', this.onUserAdded);
|
||||||
this.connection.on('userRemoved', this.onUserRemoved);
|
this.connection.on('userRemoved', this.onUserRemoved);
|
||||||
@ -56,7 +58,7 @@ export default Vue.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
this.connection.close();
|
this.connection.dispose();
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -1,7 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="!mediaView" class="zyjjkidcqjnlegkqebitfviomuqmseqk" :class="{ renote: isRenote }">
|
<div
|
||||||
<div class="reply-to" v-if="p.reply && (!$store.getters.isSignedIn || $store.state.settings.showReplyTarget)">
|
v-if="!mediaView"
|
||||||
<x-sub :note="p.reply"/>
|
v-show="appearNote.deletedAt == null"
|
||||||
|
:tabindex="appearNote.deletedAt == null ? '-1' : null"
|
||||||
|
class="zyjjkidcqjnlegkqebitfviomuqmseqk"
|
||||||
|
:class="{ renote: isRenote }"
|
||||||
|
v-hotkey="keymap"
|
||||||
|
:title="title"
|
||||||
|
>
|
||||||
|
<div class="reply-to" v-if="appearNote.reply && (!$store.getters.isSignedIn || $store.state.settings.showReplyTarget)">
|
||||||
|
<x-sub :note="appearNote.reply"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="renote" v-if="isRenote">
|
<div class="renote" v-if="isRenote">
|
||||||
<mk-avatar class="avatar" :user="note.user"/>
|
<mk-avatar class="avatar" :user="note.user"/>
|
||||||
@ -12,43 +20,42 @@
|
|||||||
<mk-time :time="note.createdAt"/>
|
<mk-time :time="note.createdAt"/>
|
||||||
</div>
|
</div>
|
||||||
<article>
|
<article>
|
||||||
<mk-avatar class="avatar" :user="p.user"/>
|
<mk-avatar class="avatar" :user="appearNote.user"/>
|
||||||
<div class="main">
|
<div class="main">
|
||||||
<mk-note-header class="header" :note="p" :mini="true"/>
|
<mk-note-header class="header" :note="appearNote" :mini="true"/>
|
||||||
<div class="body">
|
<div class="body">
|
||||||
<p v-if="p.cw != null" class="cw">
|
<p v-if="appearNote.cw != null" class="cw">
|
||||||
<span class="text" v-if="p.cw != ''">{{ p.cw }}</span>
|
<span class="text" v-if="appearNote.cw != ''">{{ appearNote.cw }}</span>
|
||||||
<mk-cw-button v-model="showContent"/>
|
<mk-cw-button v-model="showContent"/>
|
||||||
</p>
|
</p>
|
||||||
<div class="content" v-show="p.cw == null || showContent">
|
<div class="content" v-show="appearNote.cw == null || showContent">
|
||||||
<div class="text">
|
<div class="text">
|
||||||
<span v-if="p.isHidden" style="opacity: 0.5">(%i18n:@private%)</span>
|
<span v-if="appearNote.isHidden" style="opacity: 0.5">(%i18n:@private%)</span>
|
||||||
<span v-if="p.deletedAt" style="opacity: 0.5">(%i18n:@deleted%)</span>
|
<a class="reply" v-if="appearNote.reply">%fa:reply%</a>
|
||||||
<a class="reply" v-if="p.reply">%fa:reply%</a>
|
<misskey-flavored-markdown v-if="appearNote.text" :text="appearNote.text" :i="$store.state.i"/>
|
||||||
<misskey-flavored-markdown v-if="p.text" :text="p.text" :i="$store.state.i"/>
|
<a class="rp" v-if="appearNote.renote != null">RP:</a>
|
||||||
<a class="rp" v-if="p.renote != null">RP:</a>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="files" v-if="p.files.length > 0">
|
<div class="files" v-if="appearNote.files.length > 0">
|
||||||
<mk-media-list :media-list="p.files"/>
|
<mk-media-list :media-list="appearNote.files"/>
|
||||||
</div>
|
</div>
|
||||||
<mk-poll v-if="p.poll" :note="p" ref="pollViewer"/>
|
<mk-poll v-if="appearNote.poll" :note="appearNote" ref="pollViewer"/>
|
||||||
<a class="location" v-if="p.geo" :href="`https://maps.google.com/maps?q=${p.geo.coordinates[1]},${p.geo.coordinates[0]}`" target="_blank">%fa:map-marker-alt% %i18n:@location%</a>
|
<a class="location" v-if="appearNote.geo" :href="`https://maps.google.com/maps?q=${appearNote.geo.coordinates[1]},${appearNote.geo.coordinates[0]}`" target="_blank">%fa:map-marker-alt% %i18n:@location%</a>
|
||||||
<div class="renote" v-if="p.renote">
|
<div class="renote" v-if="appearNote.renote">
|
||||||
<mk-note-preview :note="p.renote" :mini="true"/>
|
<mk-note-preview :note="appearNote.renote" :mini="true"/>
|
||||||
</div>
|
</div>
|
||||||
<mk-url-preview v-for="url in urls" :url="url" :key="url" :detail="false" :mini="true"/>
|
<mk-url-preview v-for="url in urls" :url="url" :key="url" :detail="false" :mini="true"/>
|
||||||
</div>
|
</div>
|
||||||
<span class="app" v-if="p.app">via <b>{{ p.app.name }}</b></span>
|
<span class="app" v-if="appearNote.app">via <b>{{ appearNote.app.name }}</b></span>
|
||||||
</div>
|
</div>
|
||||||
<footer>
|
<footer>
|
||||||
<mk-reactions-viewer :note="p" ref="reactionsViewer"/>
|
<mk-reactions-viewer :note="appearNote" ref="reactionsViewer"/>
|
||||||
<button @click="reply">
|
<button @click="reply()">
|
||||||
<template v-if="p.reply">%fa:reply-all%</template>
|
<template v-if="appearNote.reply">%fa:reply-all%</template>
|
||||||
<template v-else>%fa:reply%</template>
|
<template v-else>%fa:reply%</template>
|
||||||
</button>
|
</button>
|
||||||
<button @click="renote" title="Renote">%fa:retweet%</button>
|
<button @click="renote()" title="Renote">%fa:retweet%</button>
|
||||||
<button :class="{ reacted: p.myReaction != null }" @click="react" ref="reactButton">%fa:plus%</button>
|
<button :class="{ reacted: appearNote.myReaction != null }" @click="react()" ref="reactButton">%fa:plus%</button>
|
||||||
<button class="menu" @click="menu" ref="menuButton">%fa:ellipsis-h%</button>
|
<button class="menu" @click="menu()" ref="menuButton">%fa:ellipsis-h%</button>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
@ -65,11 +72,10 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import parse from '../../../../../../mfm/parse';
|
import MkPostFormWindow from '../../components/post-form-window.vue';
|
||||||
|
import MkRenoteFormWindow from '../../components/renote-form-window.vue';
|
||||||
import MkNoteMenu from '../../../../common/views/components/note-menu.vue';
|
|
||||||
import MkReactionPicker from '../../../../common/views/components/reaction-picker.vue';
|
|
||||||
import XSub from './deck.note.sub.vue';
|
import XSub from './deck.note.sub.vue';
|
||||||
|
import noteMixin from '../../../../common/scripts/note-mixin';
|
||||||
import noteSubscriber from '../../../../common/scripts/note-subscriber';
|
import noteSubscriber from '../../../../common/scripts/note-subscriber';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
@ -77,7 +83,10 @@ export default Vue.extend({
|
|||||||
XSub
|
XSub
|
||||||
},
|
},
|
||||||
|
|
||||||
mixins: [noteSubscriber('note')],
|
mixins: [
|
||||||
|
noteMixin(),
|
||||||
|
noteSubscriber('note')
|
||||||
|
],
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
note: {
|
note: {
|
||||||
@ -89,66 +98,6 @@ export default Vue.extend({
|
|||||||
required: false,
|
required: false,
|
||||||
default: false
|
default: false
|
||||||
}
|
}
|
||||||
},
|
|
||||||
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
showContent: false
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
isRenote(): boolean {
|
|
||||||
return (this.note.renote &&
|
|
||||||
this.note.text == null &&
|
|
||||||
this.note.fileIds.length == 0 &&
|
|
||||||
this.note.poll == null);
|
|
||||||
},
|
|
||||||
|
|
||||||
p(): any {
|
|
||||||
return this.isRenote ? this.note.renote : this.note;
|
|
||||||
},
|
|
||||||
|
|
||||||
urls(): string[] {
|
|
||||||
if (this.p.text) {
|
|
||||||
const ast = parse(this.p.text);
|
|
||||||
return ast
|
|
||||||
.filter(t => (t.type == 'url' || t.type == 'link') && !t.silent)
|
|
||||||
.map(t => t.url);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
reply() {
|
|
||||||
(this as any).apis.post({
|
|
||||||
reply: this.p
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
renote() {
|
|
||||||
(this as any).apis.post({
|
|
||||||
renote: this.p
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
react() {
|
|
||||||
(this as any).os.new(MkReactionPicker, {
|
|
||||||
source: this.$refs.reactButton,
|
|
||||||
note: this.p,
|
|
||||||
compact: true
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
menu() {
|
|
||||||
(this as any).os.new(MkNoteMenu, {
|
|
||||||
source: this.$refs.menuButton,
|
|
||||||
note: this.p,
|
|
||||||
compact: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@ -168,6 +117,20 @@ export default Vue.extend({
|
|||||||
font-size 13px
|
font-size 13px
|
||||||
border-bottom solid 1px var(--faceDivider)
|
border-bottom solid 1px var(--faceDivider)
|
||||||
|
|
||||||
|
&:focus
|
||||||
|
z-index 1
|
||||||
|
|
||||||
|
&:after
|
||||||
|
content ""
|
||||||
|
pointer-events none
|
||||||
|
position absolute
|
||||||
|
top 2px
|
||||||
|
right 2px
|
||||||
|
bottom 2px
|
||||||
|
left 2px
|
||||||
|
border 2px solid var(--primaryAlpha03)
|
||||||
|
border-radius 4px
|
||||||
|
|
||||||
&:last-of-type
|
&:last-of-type
|
||||||
border-bottom none
|
border-bottom none
|
||||||
|
|
||||||
|
@ -2,6 +2,12 @@
|
|||||||
<div class="eamppglmnmimdhrlzhplwpvyeaqmmhxu">
|
<div class="eamppglmnmimdhrlzhplwpvyeaqmmhxu">
|
||||||
<slot name="empty" v-if="notes.length == 0 && !fetching && requestInitPromise == null"></slot>
|
<slot name="empty" v-if="notes.length == 0 && !fetching && requestInitPromise == null"></slot>
|
||||||
|
|
||||||
|
<div class="placeholder" v-if="fetching">
|
||||||
|
<template v-for="i in 10">
|
||||||
|
<mk-note-skeleton :key="i"/>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div v-if="!fetching && requestInitPromise != null">
|
<div v-if="!fetching && requestInitPromise != null">
|
||||||
<p>%i18n:@error%</p>
|
<p>%i18n:@error%</p>
|
||||||
<button @click="resolveInitPromise">%i18n:@retry%</button>
|
<button @click="resolveInitPromise">%i18n:@retry%</button>
|
||||||
@ -205,6 +211,10 @@ export default Vue.extend({
|
|||||||
> *
|
> *
|
||||||
transition transform .3s ease, opacity .3s ease
|
transition transform .3s ease, opacity .3s ease
|
||||||
|
|
||||||
|
> .placeholder
|
||||||
|
padding 16px
|
||||||
|
opacity 0.3
|
||||||
|
|
||||||
> .notes
|
> .notes
|
||||||
> .date
|
> .date
|
||||||
display block
|
display block
|
||||||
|
@ -1,5 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="oxynyeqmfvracxnglgulyqfgqxnxmehl">
|
<div class="oxynyeqmfvracxnglgulyqfgqxnxmehl">
|
||||||
|
<div class="placeholder" v-if="fetching">
|
||||||
|
<template v-for="i in 10">
|
||||||
|
<mk-note-skeleton :key="i"/>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- トランジションを有効にするとなぜかメモリリークする -->
|
<!-- トランジションを有効にするとなぜかメモリリークする -->
|
||||||
<component :is="!$store.state.device.reduceMotion ? 'transition-group' : 'div'" name="mk-notifications" class="transition notifications">
|
<component :is="!$store.state.device.reduceMotion ? 'transition-group' : 'div'" name="mk-notifications" class="transition notifications">
|
||||||
<template v-for="(notification, i) in _notifications">
|
<template v-for="(notification, i) in _notifications">
|
||||||
@ -14,7 +20,6 @@
|
|||||||
<template v-if="fetchingMoreNotifications">%fa:spinner .pulse .fw%</template>{{ fetchingMoreNotifications ? '%i18n:common.loading%' : '%i18n:@more%' }}
|
<template v-if="fetchingMoreNotifications">%fa:spinner .pulse .fw%</template>{{ fetchingMoreNotifications ? '%i18n:common.loading%' : '%i18n:@more%' }}
|
||||||
</button>
|
</button>
|
||||||
<p class="empty" v-if="notifications.length == 0 && !fetching">%i18n:@empty%</p>
|
<p class="empty" v-if="notifications.length == 0 && !fetching">%i18n:@empty%</p>
|
||||||
<p class="loading" v-if="fetching">%fa:spinner .pulse .fw%%i18n:common.loading%<mk-ellipsis/></p>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -113,8 +118,7 @@ export default Vue.extend({
|
|||||||
|
|
||||||
onNotification(notification) {
|
onNotification(notification) {
|
||||||
// TODO: ユーザーが画面を見てないと思われるとき(ブラウザやタブがアクティブじゃないなど)は送信しない
|
// TODO: ユーザーが画面を見てないと思われるとき(ブラウザやタブがアクティブじゃないなど)は送信しない
|
||||||
this.connection.send({
|
(this as any).os.stream.send('readNotification', {
|
||||||
type: 'readNotification',
|
|
||||||
id: notification.id
|
id: notification.id
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -162,6 +166,10 @@ export default Vue.extend({
|
|||||||
> *
|
> *
|
||||||
transition transform .3s ease, opacity .3s ease
|
transition transform .3s ease, opacity .3s ease
|
||||||
|
|
||||||
|
> .placeholder
|
||||||
|
padding 16px
|
||||||
|
opacity 0.3
|
||||||
|
|
||||||
> .notifications
|
> .notifications
|
||||||
|
|
||||||
> .notification:not(:last-child)
|
> .notification:not(:last-child)
|
||||||
@ -208,13 +216,4 @@ export default Vue.extend({
|
|||||||
text-align center
|
text-align center
|
||||||
color #aaa
|
color #aaa
|
||||||
|
|
||||||
> .loading
|
|
||||||
margin 0
|
|
||||||
padding 16px
|
|
||||||
text-align center
|
|
||||||
color #aaa
|
|
||||||
|
|
||||||
> [data-fa]
|
|
||||||
margin-right 4px
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<x-notes ref="timeline" :more="existMore ? more : null" :media-view="mediaView"/>
|
<x-notes ref="timeline" :more="existMore ? more : null" :media-view="mediaView"/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
@ -3,9 +3,6 @@
|
|||||||
<header :class="$style.header">
|
<header :class="$style.header">
|
||||||
<h1>{{ q }}</h1>
|
<h1>{{ q }}</h1>
|
||||||
</header>
|
</header>
|
||||||
<div :class="$style.loading" v-if="fetching">
|
|
||||||
<mk-ellipsis-icon/>
|
|
||||||
</div>
|
|
||||||
<p :class="$style.notAvailable" v-if="!fetching && notAvailable">%i18n:@not-available%</p>
|
<p :class="$style.notAvailable" v-if="!fetching && notAvailable">%i18n:@not-available%</p>
|
||||||
<p :class="$style.empty" v-if="!fetching && empty">%fa:search% {{ '%i18n:not-found%'.split('{}')[0] }}{{ q }}{{ '%i18n:not-found%'.split('{}')[1] }}</p>
|
<p :class="$style.empty" v-if="!fetching && empty">%fa:search% {{ '%i18n:not-found%'.split('{}')[0] }}{{ q }}{{ '%i18n:not-found%'.split('{}')[1] }}</p>
|
||||||
<mk-notes ref="timeline" :class="$style.notes" :more="existMore ? more : null"/>
|
<mk-notes ref="timeline" :class="$style.notes" :more="existMore ? more : null"/>
|
||||||
@ -119,9 +116,6 @@ export default Vue.extend({
|
|||||||
border-radius 6px
|
border-radius 6px
|
||||||
overflow hidden
|
overflow hidden
|
||||||
|
|
||||||
.loading
|
|
||||||
padding 64px 0
|
|
||||||
|
|
||||||
.empty
|
.empty
|
||||||
display block
|
display block
|
||||||
margin 0 auto
|
margin 0 auto
|
||||||
|
@ -3,9 +3,6 @@
|
|||||||
<header :class="$style.header">
|
<header :class="$style.header">
|
||||||
<h1>#{{ $route.params.tag }}</h1>
|
<h1>#{{ $route.params.tag }}</h1>
|
||||||
</header>
|
</header>
|
||||||
<div :class="$style.loading" v-if="fetching">
|
|
||||||
<mk-ellipsis-icon/>
|
|
||||||
</div>
|
|
||||||
<p :class="$style.empty" v-if="!fetching && empty">%fa:search% {{ '%i18n:no-posts-found%'.split('{}')[0] }}{{ q }}{{ '%i18n:no-posts-found%'.split('{}')[1] }}</p>
|
<p :class="$style.empty" v-if="!fetching && empty">%fa:search% {{ '%i18n:no-posts-found%'.split('{}')[0] }}{{ q }}{{ '%i18n:no-posts-found%'.split('{}')[1] }}</p>
|
||||||
<mk-notes ref="timeline" :class="$style.notes" :more="existMore ? more : null"/>
|
<mk-notes ref="timeline" :class="$style.notes" :more="existMore ? more : null"/>
|
||||||
</mk-ui>
|
</mk-ui>
|
||||||
@ -108,9 +105,6 @@ export default Vue.extend({
|
|||||||
border-radius 6px
|
border-radius 6px
|
||||||
overflow hidden
|
overflow hidden
|
||||||
|
|
||||||
.loading
|
|
||||||
padding 64px 0
|
|
||||||
|
|
||||||
.empty
|
.empty
|
||||||
display block
|
display block
|
||||||
margin 0 auto
|
margin 0 auto
|
||||||
|
@ -8,8 +8,6 @@
|
|||||||
<div>
|
<div>
|
||||||
<span class="username"><mk-acct :user="user" :detail="true" /></span>
|
<span class="username"><mk-acct :user="user" :detail="true" /></span>
|
||||||
<span v-if="user.isBot" title="%i18n:@is-bot%">%fa:robot%</span>
|
<span v-if="user.isBot" title="%i18n:@is-bot%">%fa:robot%</span>
|
||||||
<span class="location" v-if="user.host === null && user.profile.location">%fa:map-marker% {{ user.profile.location }}</span>
|
|
||||||
<span class="birthday" v-if="user.host === null && user.profile.birthday">%fa:birthday-cake% {{ user.profile.birthday.replace('-', '年').replace('-', '月') + '日' }} ({{ age }}歳)</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -18,6 +16,10 @@
|
|||||||
<div class="description">
|
<div class="description">
|
||||||
<misskey-flavored-markdown v-if="user.description" :text="user.description" :i="$store.state.i"/>
|
<misskey-flavored-markdown v-if="user.description" :text="user.description" :i="$store.state.i"/>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="info">
|
||||||
|
<span class="location" v-if="user.host === null && user.profile.location">%fa:map-marker% {{ user.profile.location }}</span>
|
||||||
|
<span class="birthday" v-if="user.host === null && user.profile.birthday">%fa:birthday-cake% {{ user.profile.birthday.replace('-', '年').replace('-', '月') + '日' }} ({{ age }}歳)</span>
|
||||||
|
</div>
|
||||||
<div class="status">
|
<div class="status">
|
||||||
<span class="notes-count"><b>{{ user.notesCount | number }}</b>%i18n:@posts%</span>
|
<span class="notes-count"><b>{{ user.notesCount | number }}</b>%i18n:@posts%</span>
|
||||||
<span class="following clickable" @click="showFollowing"><b>{{ user.followingCount | number }}</b>%i18n:@following%</span>
|
<span class="following clickable" @click="showFollowing"><b>{{ user.followingCount | number }}</b>%i18n:@following%</span>
|
||||||
@ -182,6 +184,14 @@ export default Vue.extend({
|
|||||||
padding 16px 16px 16px 154px
|
padding 16px 16px 16px 154px
|
||||||
color var(--text)
|
color var(--text)
|
||||||
|
|
||||||
|
> .info
|
||||||
|
margin-top 16px
|
||||||
|
padding-top 16px
|
||||||
|
border-top solid 1px var(--faceDivider)
|
||||||
|
|
||||||
|
> *
|
||||||
|
margin-right 16px
|
||||||
|
|
||||||
> .status
|
> .status
|
||||||
margin-top 16px
|
margin-top 16px
|
||||||
padding-top 16px
|
padding-top 16px
|
||||||
|
@ -5,9 +5,6 @@
|
|||||||
<span :data-active="mode == 'with-replies'" @click="mode = 'with-replies'">%fa:comments% %i18n:@with-replies%</span>
|
<span :data-active="mode == 'with-replies'" @click="mode = 'with-replies'">%fa:comments% %i18n:@with-replies%</span>
|
||||||
<span :data-active="mode == 'with-media'" @click="mode = 'with-media'">%fa:images% %i18n:@with-media%</span>
|
<span :data-active="mode == 'with-media'" @click="mode = 'with-media'">%fa:images% %i18n:@with-media%</span>
|
||||||
</header>
|
</header>
|
||||||
<div class="loading" v-if="fetching">
|
|
||||||
<mk-ellipsis-icon/>
|
|
||||||
</div>
|
|
||||||
<mk-notes ref="timeline" :more="existMore ? more : null">
|
<mk-notes ref="timeline" :more="existMore ? more : null">
|
||||||
<p class="empty" slot="empty">%fa:R comments%%i18n:@empty%</p>
|
<p class="empty" slot="empty">%fa:R comments%%i18n:@empty%</p>
|
||||||
</mk-notes>
|
</mk-notes>
|
||||||
@ -152,9 +149,6 @@ export default Vue.extend({
|
|||||||
&:hover
|
&:hover
|
||||||
color var(--desktopTimelineSrcHover)
|
color var(--desktopTimelineSrcHover)
|
||||||
|
|
||||||
> .loading
|
|
||||||
padding 64px 0
|
|
||||||
|
|
||||||
> .empty
|
> .empty
|
||||||
display block
|
display block
|
||||||
margin 0 auto
|
margin 0 auto
|
||||||
|
@ -13,7 +13,6 @@
|
|||||||
<router-link class="name" :to="_user | userPage" v-user-preview="_user.id">{{ _user | userName }}</router-link>
|
<router-link class="name" :to="_user | userPage" v-user-preview="_user.id">{{ _user | userName }}</router-link>
|
||||||
<p class="username">@{{ _user | acct }}</p>
|
<p class="username">@{{ _user | acct }}</p>
|
||||||
</div>
|
</div>
|
||||||
<mk-follow-button :user="_user"/>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<p class="empty" v-else>%i18n:@no-one%</p>
|
<p class="empty" v-else>%i18n:@no-one%</p>
|
||||||
|
@ -8,6 +8,7 @@ import VueRouter from 'vue-router';
|
|||||||
import * as TreeView from 'vue-json-tree-view';
|
import * as TreeView from 'vue-json-tree-view';
|
||||||
import VAnimateCss from 'v-animate-css';
|
import VAnimateCss from 'v-animate-css';
|
||||||
import VModal from 'vue-js-modal';
|
import VModal from 'vue-js-modal';
|
||||||
|
import VueSweetalert2 from 'vue-sweetalert2';
|
||||||
|
|
||||||
import VueHotkey from './common/hotkey';
|
import VueHotkey from './common/hotkey';
|
||||||
import App from './app.vue';
|
import App from './app.vue';
|
||||||
@ -26,6 +27,7 @@ Vue.use(TreeView);
|
|||||||
Vue.use(VAnimateCss);
|
Vue.use(VAnimateCss);
|
||||||
Vue.use(VModal);
|
Vue.use(VModal);
|
||||||
Vue.use(VueHotkey);
|
Vue.use(VueHotkey);
|
||||||
|
Vue.use(VueSweetalert2);
|
||||||
|
|
||||||
// Register global directives
|
// Register global directives
|
||||||
require('./common/views/directives');
|
require('./common/views/directives');
|
||||||
@ -122,11 +124,17 @@ export default (callback: (launch: (router: VueRouter, api?: (os: MiOS) => API)
|
|||||||
|
|
||||||
//#region shadow
|
//#region shadow
|
||||||
const shadow = '0 3px 8px rgba(0, 0, 0, 0.2)';
|
const shadow = '0 3px 8px rgba(0, 0, 0, 0.2)';
|
||||||
|
const shadowRight = '4px 0 4px rgba(0, 0, 0, 0.1)';
|
||||||
|
const shadowLeft = '-4px 0 4px rgba(0, 0, 0, 0.1)';
|
||||||
if (os.store.state.settings.useShadow) document.documentElement.style.setProperty('--shadow', shadow);
|
if (os.store.state.settings.useShadow) document.documentElement.style.setProperty('--shadow', shadow);
|
||||||
|
if (os.store.state.settings.useShadow) document.documentElement.style.setProperty('--shadowRight', shadowRight);
|
||||||
|
if (os.store.state.settings.useShadow) document.documentElement.style.setProperty('--shadowLeft', shadowLeft);
|
||||||
os.store.watch(s => {
|
os.store.watch(s => {
|
||||||
return s.settings.useShadow;
|
return s.settings.useShadow;
|
||||||
}, v => {
|
}, v => {
|
||||||
document.documentElement.style.setProperty('--shadow', v ? shadow : 'none');
|
document.documentElement.style.setProperty('--shadow', v ? shadow : 'none');
|
||||||
|
document.documentElement.style.setProperty('--shadowRight', v ? shadowRight : 'none');
|
||||||
|
document.documentElement.style.setProperty('--shadowLeft', v ? shadowLeft : 'none');
|
||||||
});
|
});
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
|
@ -212,7 +212,7 @@ export default class MiOS extends EventEmitter {
|
|||||||
const fetched = () => {
|
const fetched = () => {
|
||||||
this.emit('signedin');
|
this.emit('signedin');
|
||||||
|
|
||||||
this.stream = new Stream(this);
|
this.initStream();
|
||||||
|
|
||||||
// Finish init
|
// Finish init
|
||||||
callback();
|
callback();
|
||||||
@ -247,12 +247,103 @@ export default class MiOS extends EventEmitter {
|
|||||||
// Finish init
|
// Finish init
|
||||||
callback();
|
callback();
|
||||||
|
|
||||||
this.stream = new Stream(this);
|
this.initStream();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
private initStream() {
|
||||||
|
this.stream = new Stream(this);
|
||||||
|
|
||||||
|
if (this.store.getters.isSignedIn) {
|
||||||
|
const main = this.stream.useSharedConnection('main');
|
||||||
|
|
||||||
|
// 自分の情報が更新されたとき
|
||||||
|
main.on('meUpdated', i => {
|
||||||
|
this.store.dispatch('mergeMe', i);
|
||||||
|
});
|
||||||
|
|
||||||
|
main.on('readAllNotifications', () => {
|
||||||
|
this.store.dispatch('mergeMe', {
|
||||||
|
hasUnreadNotification: false
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
main.on('unreadNotification', () => {
|
||||||
|
this.store.dispatch('mergeMe', {
|
||||||
|
hasUnreadNotification: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
main.on('readAllMessagingMessages', () => {
|
||||||
|
this.store.dispatch('mergeMe', {
|
||||||
|
hasUnreadMessagingMessage: false
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
main.on('unreadMessagingMessage', () => {
|
||||||
|
this.store.dispatch('mergeMe', {
|
||||||
|
hasUnreadMessagingMessage: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
main.on('unreadMention', () => {
|
||||||
|
this.store.dispatch('mergeMe', {
|
||||||
|
hasUnreadMentions: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
main.on('readAllUnreadMentions', () => {
|
||||||
|
this.store.dispatch('mergeMe', {
|
||||||
|
hasUnreadMentions: false
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
main.on('unreadSpecifiedNote', () => {
|
||||||
|
this.store.dispatch('mergeMe', {
|
||||||
|
hasUnreadSpecifiedNotes: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
main.on('readAllUnreadSpecifiedNotes', () => {
|
||||||
|
this.store.dispatch('mergeMe', {
|
||||||
|
hasUnreadSpecifiedNotes: false
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
main.on('clientSettingUpdated', x => {
|
||||||
|
this.store.commit('settings/set', {
|
||||||
|
key: x.key,
|
||||||
|
value: x.value
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
main.on('homeUpdated', x => {
|
||||||
|
this.store.commit('settings/setHome', x);
|
||||||
|
});
|
||||||
|
|
||||||
|
main.on('mobileHomeUpdated', x => {
|
||||||
|
this.store.commit('settings/setMobileHome', x);
|
||||||
|
});
|
||||||
|
|
||||||
|
main.on('widgetUpdated', x => {
|
||||||
|
this.store.commit('settings/setWidget', {
|
||||||
|
id: x.id,
|
||||||
|
data: x.data
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// トークンが再生成されたとき
|
||||||
|
// このままではMisskeyが利用できないので強制的にサインアウトさせる
|
||||||
|
main.on('myTokenRegenerated', () => {
|
||||||
|
alert('%i18n:common.my-token-regenerated%');
|
||||||
|
this.signout();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register service worker
|
* Register service worker
|
||||||
*/
|
*/
|
||||||
@ -352,10 +443,10 @@ export default class MiOS extends EventEmitter {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const promise = new Promise((resolve, reject) => {
|
const promise = new Promise((resolve, reject) => {
|
||||||
const viaStream = this.stream && this.store.state.device.apiViaStream && !forceFetch;
|
const viaStream = this.stream && this.stream.state == 'connected' && this.store.state.device.apiViaStream && !forceFetch;
|
||||||
|
|
||||||
if (viaStream) {
|
if (viaStream) {
|
||||||
const id = Math.random().toString();
|
const id = Math.random().toString().substr(2, 8);
|
||||||
|
|
||||||
this.stream.once(`api:${id}`, res => {
|
this.stream.once(`api:${id}`, res => {
|
||||||
if (res == null || Object.keys(res).length == 0) {
|
if (res == null || Object.keys(res).length == 0) {
|
||||||
|
@ -18,6 +18,7 @@ export default (os) => (opts) => {
|
|||||||
}).$mount();
|
}).$mount();
|
||||||
vm.$once('cancel', recover);
|
vm.$once('cancel', recover);
|
||||||
vm.$once('posted', recover);
|
vm.$once('posted', recover);
|
||||||
|
if (o.cb) vm.$once('closed', o.cb);
|
||||||
document.body.appendChild(vm.$el);
|
document.body.appendChild(vm.$el);
|
||||||
(vm as any).focus();
|
(vm as any).focus();
|
||||||
};
|
};
|
||||||
|
@ -91,8 +91,6 @@ export default Vue.extend({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
|
|
||||||
|
|
||||||
.mk-dialog
|
.mk-dialog
|
||||||
> .bg
|
> .bg
|
||||||
display block
|
display block
|
||||||
|
@ -1,7 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="note" :class="{ renote: isRenote, smart: $store.state.device.postStyle == 'smart' }">
|
<div
|
||||||
<div class="reply-to" v-if="p.reply && (!$store.getters.isSignedIn || $store.state.settings.showReplyTarget)">
|
class="note"
|
||||||
<x-sub :note="p.reply"/>
|
v-show="appearNote.deletedAt == null"
|
||||||
|
:tabindex="appearNote.deletedAt == null ? '-1' : null"
|
||||||
|
:class="{ renote: isRenote, smart: $store.state.device.postStyle == 'smart' }"
|
||||||
|
v-hotkey="keymap"
|
||||||
|
>
|
||||||
|
<div class="reply-to" v-if="appearNote.reply && (!$store.getters.isSignedIn || $store.state.settings.showReplyTarget)">
|
||||||
|
<x-sub :note="appearNote.reply"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="renote" v-if="isRenote">
|
<div class="renote" v-if="isRenote">
|
||||||
<mk-avatar class="avatar" :user="note.user"/>
|
<mk-avatar class="avatar" :user="note.user"/>
|
||||||
@ -12,47 +18,45 @@
|
|||||||
<mk-time :time="note.createdAt"/>
|
<mk-time :time="note.createdAt"/>
|
||||||
</div>
|
</div>
|
||||||
<article>
|
<article>
|
||||||
<mk-avatar class="avatar" :user="p.user" v-if="$store.state.device.postStyle != 'smart'"/>
|
<mk-avatar class="avatar" :user="appearNote.user" v-if="$store.state.device.postStyle != 'smart'"/>
|
||||||
<div class="main">
|
<div class="main">
|
||||||
<mk-note-header class="header" :note="p" :mini="true"/>
|
<mk-note-header class="header" :note="appearNote" :mini="true"/>
|
||||||
<div class="body">
|
<div class="body">
|
||||||
<p v-if="p.cw != null" class="cw">
|
<p v-if="appearNote.cw != null" class="cw">
|
||||||
<span class="text" v-if="p.cw != ''">{{ p.cw }}</span>
|
<span class="text" v-if="appearNote.cw != ''">{{ appearNote.cw }}</span>
|
||||||
<mk-cw-button v-model="showContent"/>
|
<mk-cw-button v-model="showContent"/>
|
||||||
</p>
|
</p>
|
||||||
<div class="content" v-show="p.cw == null || showContent">
|
<div class="content" v-show="appearNote.cw == null || showContent">
|
||||||
<div class="text">
|
<div class="text">
|
||||||
<span v-if="p.isHidden" style="opacity: 0.5">(%i18n:@private%)</span>
|
<span v-if="appearNote.isHidden" style="opacity: 0.5">(%i18n:@private%)</span>
|
||||||
<span v-if="p.deletedAt" style="opacity: 0.5">(%i18n:@deleted%)</span>
|
<a class="reply" v-if="appearNote.reply">%fa:reply%</a>
|
||||||
<a class="reply" v-if="p.reply">%fa:reply%</a>
|
<misskey-flavored-markdown v-if="appearNote.text" :text="appearNote.text" :i="$store.state.i" :class="$style.text"/>
|
||||||
<misskey-flavored-markdown v-if="p.text" :text="p.text" :i="$store.state.i" :class="$style.text"/>
|
<a class="rp" v-if="appearNote.renote != null">RP:</a>
|
||||||
<a class="rp" v-if="p.renote != null">RP:</a>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="files" v-if="p.files.length > 0">
|
<div class="files" v-if="appearNote.files.length > 0">
|
||||||
<mk-media-list :media-list="p.files"/>
|
<mk-media-list :media-list="appearNote.files"/>
|
||||||
</div>
|
</div>
|
||||||
<mk-poll v-if="p.poll" :note="p" ref="pollViewer"/>
|
<mk-poll v-if="appearNote.poll" :note="appearNote" ref="pollViewer"/>
|
||||||
<mk-url-preview v-for="url in urls" :url="url" :key="url"/>
|
<mk-url-preview v-for="url in urls" :url="url" :key="url"/>
|
||||||
<a class="location" v-if="p.geo" :href="`https://maps.google.com/maps?q=${p.geo.coordinates[1]},${p.geo.coordinates[0]}`" target="_blank">%fa:map-marker-alt% %i18n:@location%</a>
|
<a class="location" v-if="appearNote.geo" :href="`https://maps.google.com/maps?q=${appearNote.geo.coordinates[1]},${appearNote.geo.coordinates[0]}`" target="_blank">%fa:map-marker-alt% %i18n:@location%</a>
|
||||||
<div class="map" v-if="p.geo" ref="map"></div>
|
<div class="renote" v-if="appearNote.renote"><mk-note-preview :note="appearNote.renote"/></div>
|
||||||
<div class="renote" v-if="p.renote"><mk-note-preview :note="p.renote"/></div>
|
|
||||||
</div>
|
</div>
|
||||||
<span class="app" v-if="p.app">via <b>{{ p.app.name }}</b></span>
|
<span class="app" v-if="appearNote.app">via <b>{{ appearNote.app.name }}</b></span>
|
||||||
</div>
|
</div>
|
||||||
<footer v-if="p.deletedAt == null">
|
<footer>
|
||||||
<mk-reactions-viewer :note="p" ref="reactionsViewer"/>
|
<mk-reactions-viewer :note="appearNote" ref="reactionsViewer"/>
|
||||||
<button @click="reply">
|
<button @click="reply()">
|
||||||
<template v-if="p.reply">%fa:reply-all%</template>
|
<template v-if="appearNote.reply">%fa:reply-all%</template>
|
||||||
<template v-else>%fa:reply%</template>
|
<template v-else>%fa:reply%</template>
|
||||||
<p class="count" v-if="p.repliesCount > 0">{{ p.repliesCount }}</p>
|
<p class="count" v-if="appearNote.repliesCount > 0">{{ appearNote.repliesCount }}</p>
|
||||||
</button>
|
</button>
|
||||||
<button @click="renote" title="Renote">
|
<button @click="renote()" title="Renote">
|
||||||
%fa:retweet%<p class="count" v-if="p.renoteCount > 0">{{ p.renoteCount }}</p>
|
%fa:retweet%<p class="count" v-if="appearNote.renoteCount > 0">{{ appearNote.renoteCount }}</p>
|
||||||
</button>
|
</button>
|
||||||
<button :class="{ reacted: p.myReaction != null }" @click="react" ref="reactButton">
|
<button :class="{ reacted: appearNote.myReaction != null }" @click="react()" ref="reactButton">
|
||||||
%fa:plus%<p class="count" v-if="p.reactions_count > 0">{{ p.reactions_count }}</p>
|
%fa:plus%<p class="count" v-if="appearNote.reactions_count > 0">{{ appearNote.reactions_count }}</p>
|
||||||
</button>
|
</button>
|
||||||
<button class="menu" @click="menu" ref="menuButton">
|
<button class="menu" @click="menu()" ref="menuButton">
|
||||||
%fa:ellipsis-h%
|
%fa:ellipsis-h%
|
||||||
</button>
|
</button>
|
||||||
</footer>
|
</footer>
|
||||||
@ -63,12 +67,9 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import parse from '../../../../../mfm/parse';
|
|
||||||
|
|
||||||
import MkNoteMenu from '../../../common/views/components/note-menu.vue';
|
|
||||||
import MkReactionPicker from '../../../common/views/components/reaction-picker.vue';
|
|
||||||
import XSub from './note.sub.vue';
|
import XSub from './note.sub.vue';
|
||||||
import { sum } from '../../../../../prelude/array';
|
import noteMixin from '../../../common/scripts/note-mixin';
|
||||||
import noteSubscriber from '../../../common/scripts/note-subscriber';
|
import noteSubscriber from '../../../common/scripts/note-subscriber';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
@ -76,74 +77,17 @@ export default Vue.extend({
|
|||||||
XSub
|
XSub
|
||||||
},
|
},
|
||||||
|
|
||||||
mixins: [noteSubscriber('note')],
|
mixins: [
|
||||||
|
noteMixin({
|
||||||
|
mobile: true
|
||||||
|
}),
|
||||||
|
noteSubscriber('note')
|
||||||
|
],
|
||||||
|
|
||||||
props: ['note'],
|
props: {
|
||||||
|
note: {
|
||||||
data() {
|
type: Object,
|
||||||
return {
|
required: true
|
||||||
showContent: false
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
isRenote(): boolean {
|
|
||||||
return (this.note.renote &&
|
|
||||||
this.note.text == null &&
|
|
||||||
this.note.fileIds.length == 0 &&
|
|
||||||
this.note.poll == null);
|
|
||||||
},
|
|
||||||
|
|
||||||
p(): any {
|
|
||||||
return this.isRenote ? this.note.renote : this.note;
|
|
||||||
},
|
|
||||||
|
|
||||||
reactionsCount(): number {
|
|
||||||
return this.p.reactionCounts
|
|
||||||
? sum(Object.values(this.p.reactionCounts))
|
|
||||||
: 0;
|
|
||||||
},
|
|
||||||
|
|
||||||
urls(): string[] {
|
|
||||||
if (this.p.text) {
|
|
||||||
const ast = parse(this.p.text);
|
|
||||||
return ast
|
|
||||||
.filter(t => (t.type == 'url' || t.type == 'link') && !t.silent)
|
|
||||||
.map(t => t.url);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
reply() {
|
|
||||||
(this as any).apis.post({
|
|
||||||
reply: this.p
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
renote() {
|
|
||||||
(this as any).apis.post({
|
|
||||||
renote: this.p
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
react() {
|
|
||||||
(this as any).os.new(MkReactionPicker, {
|
|
||||||
source: this.$refs.reactButton,
|
|
||||||
note: this.p,
|
|
||||||
compact: true,
|
|
||||||
big: true
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
menu() {
|
|
||||||
(this as any).os.new(MkNoteMenu, {
|
|
||||||
source: this.$refs.menuButton,
|
|
||||||
note: this.p,
|
|
||||||
compact: true
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -154,6 +98,20 @@ export default Vue.extend({
|
|||||||
font-size 12px
|
font-size 12px
|
||||||
border-bottom solid 1px var(--faceDivider)
|
border-bottom solid 1px var(--faceDivider)
|
||||||
|
|
||||||
|
&:focus
|
||||||
|
z-index 1
|
||||||
|
|
||||||
|
&:after
|
||||||
|
content ""
|
||||||
|
pointer-events none
|
||||||
|
position absolute
|
||||||
|
top 2px
|
||||||
|
right 2px
|
||||||
|
bottom 2px
|
||||||
|
left 2px
|
||||||
|
border 2px solid var(--primaryAlpha03)
|
||||||
|
border-radius 4px
|
||||||
|
|
||||||
&:last-of-type
|
&:last-of-type
|
||||||
border-bottom none
|
border-bottom none
|
||||||
|
|
||||||
|
@ -4,8 +4,10 @@
|
|||||||
|
|
||||||
<slot name="empty" v-if="notes.length == 0 && !fetching && requestInitPromise == null"></slot>
|
<slot name="empty" v-if="notes.length == 0 && !fetching && requestInitPromise == null"></slot>
|
||||||
|
|
||||||
<div class="init" v-if="fetching">
|
<div class="placeholder" v-if="fetching">
|
||||||
%fa:spinner .pulse%%i18n:common.loading%
|
<template v-for="i in 10">
|
||||||
|
<mk-note-skeleton :key="i"/>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="!fetching && requestInitPromise != null">
|
<div v-if="!fetching && requestInitPromise != null">
|
||||||
@ -251,13 +253,12 @@ export default Vue.extend({
|
|||||||
[data-fa]
|
[data-fa]
|
||||||
margin-right 8px
|
margin-right 8px
|
||||||
|
|
||||||
> .init
|
> .placeholder
|
||||||
padding 64px 0
|
padding 16px
|
||||||
text-align center
|
opacity 0.3
|
||||||
color #999
|
|
||||||
|
|
||||||
> [data-fa]
|
@media (min-width 500px)
|
||||||
margin-right 4px
|
padding 32px
|
||||||
|
|
||||||
> .empty
|
> .empty
|
||||||
margin 0 auto
|
margin 0 auto
|
||||||
|
@ -1,5 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="mk-notifications">
|
<div class="mk-notifications">
|
||||||
|
<div class="placeholder" v-if="fetching">
|
||||||
|
<template v-for="i in 10">
|
||||||
|
<mk-note-skeleton :key="i"/>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- トランジションを有効にするとなぜかメモリリークする -->
|
<!-- トランジションを有効にするとなぜかメモリリークする -->
|
||||||
<component :is="!$store.state.device.reduceMotion ? 'transition-group' : 'div'" name="mk-notifications" class="transition notifications">
|
<component :is="!$store.state.device.reduceMotion ? 'transition-group' : 'div'" name="mk-notifications" class="transition notifications">
|
||||||
<template v-for="(notification, i) in _notifications">
|
<template v-for="(notification, i) in _notifications">
|
||||||
@ -17,7 +23,6 @@
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<p class="empty" v-if="notifications.length == 0 && !fetching">%i18n:@empty%</p>
|
<p class="empty" v-if="notifications.length == 0 && !fetching">%i18n:@empty%</p>
|
||||||
<p class="fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:common.loading%<mk-ellipsis/></p>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -77,6 +82,8 @@ export default Vue.extend({
|
|||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
fetchMoreNotifications() {
|
fetchMoreNotifications() {
|
||||||
|
if (this.fetchingMoreNotifications) return;
|
||||||
|
|
||||||
this.fetchingMoreNotifications = true;
|
this.fetchingMoreNotifications = true;
|
||||||
|
|
||||||
const max = 30;
|
const max = 30;
|
||||||
@ -98,8 +105,7 @@ export default Vue.extend({
|
|||||||
|
|
||||||
onNotification(notification) {
|
onNotification(notification) {
|
||||||
// TODO: ユーザーが画面を見てないと思われるとき(ブラウザやタブがアクティブじゃないなど)は送信しない
|
// TODO: ユーザーが画面を見てないと思われるとき(ブラウザやタブがアクティブじゃないなど)は送信しない
|
||||||
this.connection.send({
|
(this as any).os.stream.send('readNotification', {
|
||||||
type: 'readNotification',
|
|
||||||
id: notification.id
|
id: notification.id
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -178,13 +184,11 @@ export default Vue.extend({
|
|||||||
text-align center
|
text-align center
|
||||||
color #aaa
|
color #aaa
|
||||||
|
|
||||||
> .fetching
|
> .placeholder
|
||||||
margin 0
|
|
||||||
padding 16px
|
padding 16px
|
||||||
text-align center
|
opacity 0.3
|
||||||
color #aaa
|
|
||||||
|
|
||||||
> [data-fa]
|
@media (min-width 500px)
|
||||||
margin-right 4px
|
padding 32px
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
@ -62,6 +62,7 @@ import { host } from '../../../config';
|
|||||||
import { erase, unique } from '../../../../../prelude/array';
|
import { erase, unique } from '../../../../../prelude/array';
|
||||||
import { length } from 'stringz';
|
import { length } from 'stringz';
|
||||||
import parseAcct from '../../../../../misc/acct/parse';
|
import parseAcct from '../../../../../misc/acct/parse';
|
||||||
|
import { toASCII } from 'punycode';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
components: {
|
components: {
|
||||||
@ -153,14 +154,14 @@ export default Vue.extend({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.reply && this.reply.user.host != null) {
|
if (this.reply && this.reply.user.host != null) {
|
||||||
this.text = `@${this.reply.user.username}@${this.reply.user.host} `;
|
this.text = `@${this.reply.user.username}@${toASCII(this.reply.user.host)} `;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.reply && this.reply.text != null) {
|
if (this.reply && this.reply.text != null) {
|
||||||
const ast = parse(this.reply.text);
|
const ast = parse(this.reply.text);
|
||||||
|
|
||||||
ast.filter(t => t.type == 'mention').forEach(x => {
|
ast.filter(t => t.type == 'mention').forEach(x => {
|
||||||
const mention = x.host ? `@${x.username}@${x.host}` : `@${x.username}`;
|
const mention = x.host ? `@${x.username}@${toASCII(x.host)}` : `@${x.username}`;
|
||||||
|
|
||||||
// 自分は除外
|
// 自分は除外
|
||||||
if (this.$store.state.i.username == x.username && x.host == null) return;
|
if (this.$store.state.i.username == x.username && x.host == null) return;
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
<li><router-link to="/" :data-active="$route.name == 'index'">%fa:home%%i18n:@timeline%%fa:angle-right%</router-link></li>
|
<li><router-link to="/" :data-active="$route.name == 'index'">%fa:home%%i18n:@timeline%%fa:angle-right%</router-link></li>
|
||||||
<li><router-link to="/i/notifications" :data-active="$route.name == 'notifications'">%fa:R bell%%i18n:@notifications%<template v-if="hasUnreadNotification">%fa:circle%</template>%fa:angle-right%</router-link></li>
|
<li><router-link to="/i/notifications" :data-active="$route.name == 'notifications'">%fa:R bell%%i18n:@notifications%<template v-if="hasUnreadNotification">%fa:circle%</template>%fa:angle-right%</router-link></li>
|
||||||
<li><router-link to="/i/messaging" :data-active="$route.name == 'messaging'">%fa:R comments%%i18n:@messaging%<template v-if="hasUnreadMessagingMessage">%fa:circle%</template>%fa:angle-right%</router-link></li>
|
<li><router-link to="/i/messaging" :data-active="$route.name == 'messaging'">%fa:R comments%%i18n:@messaging%<template v-if="hasUnreadMessagingMessage">%fa:circle%</template>%fa:angle-right%</router-link></li>
|
||||||
<li v-if="$store.getters.isSignedIn && $store.state.i.isLocked"><router-link to="/i/received-follow-requests" :data-active="$route.name == 'received-follow-requests'">%fa:R envelope%%i18n:@follow-requests%<template v-if="$store.getters.isSignedIn && $store.state.i.pendingReceivedFollowRequestsCount">%fa:circle%</template>%fa:angle-right%</router-link></li>
|
<li v-if="$store.getters.isSignedIn && ($store.state.i.isLocked || $store.state.i.carefulBot)"><router-link to="/i/received-follow-requests" :data-active="$route.name == 'received-follow-requests'">%fa:R envelope%%i18n:@follow-requests%<template v-if="$store.getters.isSignedIn && $store.state.i.pendingReceivedFollowRequestsCount">%fa:circle%</template>%fa:angle-right%</router-link></li>
|
||||||
<li><router-link to="/reversi" :data-active="$route.name == 'reversi'">%fa:gamepad%%i18n:@game%<template v-if="hasGameInvitation">%fa:circle%</template>%fa:angle-right%</router-link></li>
|
<li><router-link to="/reversi" :data-active="$route.name == 'reversi'">%fa:gamepad%%i18n:@game%<template v-if="hasGameInvitation">%fa:circle%</template>%fa:angle-right%</router-link></li>
|
||||||
</ul>
|
</ul>
|
||||||
<ul>
|
<ul>
|
||||||
|
@ -58,8 +58,7 @@ export default Vue.extend({
|
|||||||
methods: {
|
methods: {
|
||||||
onNotification(notification) {
|
onNotification(notification) {
|
||||||
// TODO: ユーザーが画面を見てないと思われるとき(ブラウザやタブがアクティブじゃないなど)は送信しない
|
// TODO: ユーザーが画面を見てないと思われるとき(ブラウザやタブがアクティブじゃないなど)は送信しない
|
||||||
this.connection.send({
|
(this as any).os.stream.send('readNotification', {
|
||||||
type: 'readNotification',
|
|
||||||
id: notification.id
|
id: notification.id
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -36,13 +36,15 @@ export default Vue.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
this.connection.close();
|
this.connection.dispose();
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
init() {
|
init() {
|
||||||
if (this.connection) this.connection.close();
|
if (this.connection) this.connection.dispose();
|
||||||
this.connection = new UserListStream((this as any).os, this.$store.state.i, this.list.id);
|
this.connection = (this as any).os.stream.connectToChannel('userList', {
|
||||||
|
listId: this.list.id
|
||||||
|
});
|
||||||
this.connection.on('note', this.onNote);
|
this.connection.on('note', this.onNote);
|
||||||
this.connection.on('userAdded', this.onUserAdded);
|
this.connection.on('userAdded', this.onUserAdded);
|
||||||
this.connection.on('userRemoved', this.onUserRemoved);
|
this.connection.on('userRemoved', this.onUserRemoved);
|
||||||
|
@ -8,7 +8,14 @@
|
|||||||
<x-profile/>
|
<x-profile/>
|
||||||
|
|
||||||
<ui-card>
|
<ui-card>
|
||||||
<div slot="title">%fa:palette% %i18n:@design%</div>
|
<div slot="title">%fa:palette% %i18n:@theme%</div>
|
||||||
|
<section>
|
||||||
|
<mk-theme/>
|
||||||
|
</section>
|
||||||
|
</ui-card>
|
||||||
|
|
||||||
|
<ui-card>
|
||||||
|
<div slot="title">%fa:poll-h% %i18n:@design%</div>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<ui-switch v-model="darkmode">%i18n:@dark-mode%</ui-switch>
|
<ui-switch v-model="darkmode">%i18n:@dark-mode%</ui-switch>
|
||||||
@ -23,13 +30,6 @@
|
|||||||
<ui-switch v-model="games_reversi_useContrastStones">%i18n:common.use-contrast-reversi-stones%</ui-switch>
|
<ui-switch v-model="games_reversi_useContrastStones">%i18n:common.use-contrast-reversi-stones%</ui-switch>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
|
||||||
<header>%i18n:@theme%</header>
|
|
||||||
<div>
|
|
||||||
<mk-theme/>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<header>%i18n:@timeline%</header>
|
<header>%i18n:@timeline%</header>
|
||||||
<div>
|
<div>
|
||||||
@ -54,7 +54,7 @@
|
|||||||
</ui-card>
|
</ui-card>
|
||||||
|
|
||||||
<ui-card>
|
<ui-card>
|
||||||
<div slot="title">%fa:cog% %i18n:@behavior%</div>
|
<div slot="title">%fa:sliders-h% %i18n:@behavior%</div>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<ui-switch v-model="fetchOnScroll">%i18n:@fetch-on-scroll%</ui-switch>
|
<ui-switch v-model="fetchOnScroll">%i18n:@fetch-on-scroll%</ui-switch>
|
||||||
|
@ -58,6 +58,7 @@
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<ui-switch v-model="isLocked" @change="save(false)">%i18n:@is-locked%</ui-switch>
|
<ui-switch v-model="isLocked" @change="save(false)">%i18n:@is-locked%</ui-switch>
|
||||||
|
<ui-switch v-model="carefulBot" @change="save(false)">%i18n:@careful-bot%</ui-switch>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</ui-card>
|
</ui-card>
|
||||||
@ -80,6 +81,7 @@ export default Vue.extend({
|
|||||||
bannerId: null,
|
bannerId: null,
|
||||||
isCat: false,
|
isCat: false,
|
||||||
isLocked: false,
|
isLocked: false,
|
||||||
|
carefulBot: false,
|
||||||
saving: false,
|
saving: false,
|
||||||
avatarUploading: false,
|
avatarUploading: false,
|
||||||
bannerUploading: false
|
bannerUploading: false
|
||||||
@ -103,6 +105,7 @@ export default Vue.extend({
|
|||||||
this.bannerId = this.$store.state.i.bannerId;
|
this.bannerId = this.$store.state.i.bannerId;
|
||||||
this.isCat = this.$store.state.i.isCat;
|
this.isCat = this.$store.state.i.isCat;
|
||||||
this.isLocked = this.$store.state.i.isLocked;
|
this.isLocked = this.$store.state.i.isLocked;
|
||||||
|
this.carefulBot = this.$store.state.i.carefulBot;
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
@ -161,7 +164,8 @@ export default Vue.extend({
|
|||||||
avatarId: this.avatarId,
|
avatarId: this.avatarId,
|
||||||
bannerId: this.bannerId,
|
bannerId: this.bannerId,
|
||||||
isCat: this.isCat,
|
isCat: this.isCat,
|
||||||
isLocked: this.isLocked
|
isLocked: this.isLocked,
|
||||||
|
carefulBot: this.carefulBot
|
||||||
}).then(i => {
|
}).then(i => {
|
||||||
this.saving = false;
|
this.saving = false;
|
||||||
this.$store.state.i.avatarId = i.avatarId;
|
this.$store.state.i.avatarId = i.avatarId;
|
||||||
@ -170,7 +174,10 @@ export default Vue.extend({
|
|||||||
this.$store.state.i.bannerUrl = i.bannerUrl;
|
this.$store.state.i.bannerUrl = i.bannerUrl;
|
||||||
|
|
||||||
if (notify) {
|
if (notify) {
|
||||||
alert('%i18n:@saved%');
|
this.$swal({
|
||||||
|
type: 'success',
|
||||||
|
text: '%i18n:@saved%'
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -56,6 +56,7 @@ const defaultDeviceSettings = {
|
|||||||
loadRawImages: false,
|
loadRawImages: false,
|
||||||
alwaysShowNsfw: false,
|
alwaysShowNsfw: false,
|
||||||
postStyle: 'standard',
|
postStyle: 'standard',
|
||||||
|
navbar: 'top',
|
||||||
mobileNotificationPosition: 'bottom'
|
mobileNotificationPosition: 'bottom'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
face: '$secondary',
|
face: '$secondary',
|
||||||
faceText: '#fff',
|
faceText: '#fff',
|
||||||
faceHeader: ':lighten<5<$secondary',
|
faceHeader: ':lighten<5<$secondary',
|
||||||
faceHeaderText: '#e3e5e8',
|
faceHeaderText: '$text',
|
||||||
faceDivider: 'rgba(0, 0, 0, 0.3)',
|
faceDivider: 'rgba(0, 0, 0, 0.3)',
|
||||||
faceTextButton: '$text',
|
faceTextButton: '$text',
|
||||||
faceTextButtonHover: ':lighten<10<$text',
|
faceTextButtonHover: ':lighten<10<$text',
|
||||||
@ -84,7 +84,8 @@
|
|||||||
|
|
||||||
reactionPickerButtonHoverBg: 'rgba(255, 255, 255, 0.18)',
|
reactionPickerButtonHoverBg: 'rgba(255, 255, 255, 0.18)',
|
||||||
|
|
||||||
reactionViewerBorder: 'rgba(255, 255, 255, 0.1)',
|
reactionViewerButtonBg: 'rgba(255, 255, 255, 0.1)',
|
||||||
|
reactionViewerButtonHoverBg: 'rgba(255, 255, 255, 0.2)',
|
||||||
|
|
||||||
pollEditorInputBg: 'rgba(0, 0, 0, 0.25)',
|
pollEditorInputBg: 'rgba(0, 0, 0, 0.25)',
|
||||||
|
|
||||||
|
@ -84,7 +84,8 @@
|
|||||||
|
|
||||||
reactionPickerButtonHoverBg: '#eee',
|
reactionPickerButtonHoverBg: '#eee',
|
||||||
|
|
||||||
reactionViewerBorder: 'rgba(0, 0, 0, 0.1)',
|
reactionViewerButtonBg: 'rgba(0, 0, 0, 0.05)',
|
||||||
|
reactionViewerButtonHoverBg: 'rgba(0, 0, 0, 0.1)',
|
||||||
|
|
||||||
pollEditorInputBg: '#fff',
|
pollEditorInputBg: '#fff',
|
||||||
|
|
||||||
|
@ -62,6 +62,8 @@ export type Source = {
|
|||||||
*/
|
*/
|
||||||
ghost?: string;
|
ghost?: string;
|
||||||
|
|
||||||
|
proxy?: string;
|
||||||
|
|
||||||
summalyProxy?: string;
|
summalyProxy?: string;
|
||||||
|
|
||||||
accesslog?: string;
|
accesslog?: string;
|
||||||
@ -93,9 +95,13 @@ export type Source = {
|
|||||||
private_key: string;
|
private_key: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
google_maps_api_key: string;
|
|
||||||
|
|
||||||
clusterLimit?: number;
|
clusterLimit?: number;
|
||||||
|
|
||||||
|
user_recommendation?: {
|
||||||
|
external: boolean;
|
||||||
|
engine: string;
|
||||||
|
timeout: number;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import * as redis from 'redis';
|
import * as redis from 'redis';
|
||||||
import config from '../config';
|
import config from '../config';
|
||||||
|
|
||||||
export default redis.createClient(
|
export default config.redis ? redis.createClient(
|
||||||
config.redis.port,
|
config.redis.port,
|
||||||
config.redis.host,
|
config.redis.host,
|
||||||
{
|
{
|
||||||
auth_pass: config.redis.pass
|
auth_pass: config.redis.pass
|
||||||
}
|
}
|
||||||
);
|
) : null;
|
||||||
|
@ -30,6 +30,8 @@
|
|||||||
<tr><td><kbd class="group"><kbd class="key">Ctrl</kbd> + <kbd class="key">→</kbd></kbd>, <kbd class="group"><kbd class="key">Ctrl</kbd> + <kbd class="key">Q</kbd></kbd></td><td>即刻Renoteする(フォームを開かずに)</td><td>-</td></tr>
|
<tr><td><kbd class="group"><kbd class="key">Ctrl</kbd> + <kbd class="key">→</kbd></kbd>, <kbd class="group"><kbd class="key">Ctrl</kbd> + <kbd class="key">Q</kbd></kbd></td><td>即刻Renoteする(フォームを開かずに)</td><td>-</td></tr>
|
||||||
<tr><td><kbd class="key">E</kbd>, <kbd class="key">A</kbd>, <kbd class="key">+</kbd></td><td>リアクションフォームを開く</td><td><b>E</b>mote, re<b>A</b>ction</td></tr>
|
<tr><td><kbd class="key">E</kbd>, <kbd class="key">A</kbd>, <kbd class="key">+</kbd></td><td>リアクションフォームを開く</td><td><b>E</b>mote, re<b>A</b>ction</td></tr>
|
||||||
<tr><td><kbd class="key">0</kbd>~<kbd class="key">9</kbd></td><td>数字に対応したリアクションをする(対応については後述)</td><td>-</td></tr>
|
<tr><td><kbd class="key">0</kbd>~<kbd class="key">9</kbd></td><td>数字に対応したリアクションをする(対応については後述)</td><td>-</td></tr>
|
||||||
|
<tr><td><kbd class="key">F</kbd>, <kbd class="key">B</kbd></td><td>お気に入りに登録</td><td><b>F</b>avorite, <b>B</b>ookmark</td></tr>
|
||||||
|
<tr><td><kbd class="key">Del</kbd>, <kbd class="group"><kbd class="key">Ctrl</kbd> + <kbd class="key">D</kbd></kbd></td><td>投稿を削除</td><td><b>D</b>elete</tr>
|
||||||
<tr><td><kbd class="key">M</kbd>, <kbd class="key">O</kbd></td><td>投稿に対するメニューを開く</td><td><b>M</b>ore, <b>O</b>ther</td></tr>
|
<tr><td><kbd class="key">M</kbd>, <kbd class="key">O</kbd></td><td>投稿に対するメニューを開く</td><td><b>M</b>ore, <b>O</b>ther</td></tr>
|
||||||
<tr><td><kbd class="key">S</kbd></td><td>CWで隠された部分を表示 or 隠す</td><td><b>S</b>how, <b>S</b>ee</td></tr>
|
<tr><td><kbd class="key">S</kbd></td><td>CWで隠された部分を表示 or 隠す</td><td><b>S</b>how, <b>S</b>ee</td></tr>
|
||||||
<tr><td><kbd class="key">Esc</kbd></td><td>フォーカスを外す</td><td>-</td></tr>
|
<tr><td><kbd class="key">Esc</kbd></td><td>フォーカスを外す</td><td>-</td></tr>
|
||||||
|
@ -111,6 +111,7 @@ async function workerMain() {
|
|||||||
*/
|
*/
|
||||||
async function init(): Promise<Config> {
|
async function init(): Promise<Config> {
|
||||||
Logger.info('Welcome to Misskey!');
|
Logger.info('Welcome to Misskey!');
|
||||||
|
Logger.info(`<<< Misskey v${pkg.version} >>>`);
|
||||||
|
|
||||||
new Logger('Deps').info(`Node.js ${process.version}`);
|
new Logger('Deps').info(`Node.js ${process.version}`);
|
||||||
MachineInfo.show();
|
MachineInfo.show();
|
||||||
|
@ -2,10 +2,12 @@
|
|||||||
* Mention
|
* Mention
|
||||||
*/
|
*/
|
||||||
import parseAcct from '../../../misc/acct/parse';
|
import parseAcct from '../../../misc/acct/parse';
|
||||||
|
import { toUnicode } from 'punycode';
|
||||||
|
|
||||||
export type TextElementMention = {
|
export type TextElementMention = {
|
||||||
type: 'mention'
|
type: 'mention'
|
||||||
content: string
|
content: string
|
||||||
|
canonical: string
|
||||||
username: string
|
username: string
|
||||||
host: string
|
host: string
|
||||||
};
|
};
|
||||||
@ -15,9 +17,11 @@ export default function(text: string) {
|
|||||||
if (!match) return null;
|
if (!match) return null;
|
||||||
const mention = match[0];
|
const mention = match[0];
|
||||||
const { username, host } = parseAcct(mention.substr(1));
|
const { username, host } = parseAcct(mention.substr(1));
|
||||||
|
const canonical = host != null ? `@${username}@${toUnicode(host)}` : mention;
|
||||||
return {
|
return {
|
||||||
type: 'mention',
|
type: 'mention',
|
||||||
content: mention,
|
content: mention,
|
||||||
|
canonical,
|
||||||
username,
|
username,
|
||||||
host
|
host
|
||||||
} as TextElementMention;
|
} as TextElementMention;
|
||||||
|
@ -17,7 +17,7 @@ const summarize = (note: any): string => {
|
|||||||
summary += note.text ? note.text : '';
|
summary += note.text ? note.text : '';
|
||||||
|
|
||||||
// ファイルが添付されているとき
|
// ファイルが添付されているとき
|
||||||
if (note.files.length != 0) {
|
if ((note.files || []).length != 0) {
|
||||||
summary += ` (${note.files.length}つのファイル)`;
|
summary += ` (${note.files.length}つのファイル)`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,6 +11,8 @@ import DriveFileThumbnail, { deleteDriveFileThumbnail } from './drive-file-thumb
|
|||||||
const DriveFile = monkDb.get<IDriveFile>('driveFiles.files');
|
const DriveFile = monkDb.get<IDriveFile>('driveFiles.files');
|
||||||
DriveFile.createIndex('md5');
|
DriveFile.createIndex('md5');
|
||||||
DriveFile.createIndex('metadata.uri');
|
DriveFile.createIndex('metadata.uri');
|
||||||
|
DriveFile.createIndex('metadata.userId');
|
||||||
|
DriveFile.createIndex('metadata.folderId');
|
||||||
export default DriveFile;
|
export default DriveFile;
|
||||||
|
|
||||||
export const DriveFileChunk = monkDb.get('driveFiles.chunks');
|
export const DriveFileChunk = monkDb.get('driveFiles.chunks');
|
||||||
@ -166,7 +168,7 @@ export const pack = (
|
|||||||
|
|
||||||
// (データベースの欠損などで)ファイルがデータベース上に見つからなかったとき
|
// (データベースの欠損などで)ファイルがデータベース上に見つからなかったとき
|
||||||
if (_file == null) {
|
if (_file == null) {
|
||||||
console.warn(`in packaging driveFile: driveFile not found on database: ${_file}`);
|
console.warn(`[DAMAGED DB] (missing) pkg: driveFile :: ${file}`);
|
||||||
return resolve(null);
|
return resolve(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ import db from '../db/mongodb';
|
|||||||
import DriveFile from './drive-file';
|
import DriveFile from './drive-file';
|
||||||
|
|
||||||
const DriveFolder = db.get<IDriveFolder>('driveFolders');
|
const DriveFolder = db.get<IDriveFolder>('driveFolders');
|
||||||
|
DriveFolder.createIndex('userId');
|
||||||
export default DriveFolder;
|
export default DriveFolder;
|
||||||
|
|
||||||
export type IDriveFolder = {
|
export type IDriveFolder = {
|
||||||
|
@ -75,11 +75,13 @@ export const pack = (
|
|||||||
delete _favorite._id;
|
delete _favorite._id;
|
||||||
|
|
||||||
// Populate note
|
// Populate note
|
||||||
_favorite.note = await packNote(_favorite.noteId, me);
|
_favorite.note = await packNote(_favorite.noteId, me, {
|
||||||
|
detail: true
|
||||||
|
});
|
||||||
|
|
||||||
// (データベースの不具合などで)投稿が見つからなかったら
|
// (データベースの不具合などで)投稿が見つからなかったら
|
||||||
if (_favorite.note == null) {
|
if (_favorite.note == null) {
|
||||||
console.warn(`in packaging favorite: note not found on database: ${_favorite.noteId}`);
|
console.warn(`[DAMAGED DB] (missing) pkg: favorite -> note :: ${_favorite.id} (note ${_favorite.noteId})`);
|
||||||
return resolve(null);
|
return resolve(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -281,9 +281,9 @@ export const pack = async (
|
|||||||
_note = deepcopy(note);
|
_note = deepcopy(note);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 投稿がデータベース上に見つからなかったとき
|
// (データベースの欠損などで)投稿がデータベース上に見つからなかったとき
|
||||||
if (_note == null) {
|
if (_note == null) {
|
||||||
console.warn(`note not found on database: ${note}`);
|
console.warn(`[DAMAGED DB] (missing) pkg: note :: ${note}`);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -358,8 +358,8 @@ export const pack = async (
|
|||||||
})(_note.poll);
|
})(_note.poll);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch my reaction
|
|
||||||
if (meId) {
|
if (meId) {
|
||||||
|
// Fetch my reaction
|
||||||
_note.myReaction = (async () => {
|
_note.myReaction = (async () => {
|
||||||
const reaction = await Reaction
|
const reaction = await Reaction
|
||||||
.findOne({
|
.findOne({
|
||||||
@ -374,18 +374,44 @@ export const pack = async (
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
// isFavorited
|
||||||
|
_note.isFavorited = (async () => {
|
||||||
|
const favorite = await Favorite
|
||||||
|
.count({
|
||||||
|
userId: meId,
|
||||||
|
noteId: id
|
||||||
|
}, {
|
||||||
|
limit: 1
|
||||||
|
});
|
||||||
|
|
||||||
|
return favorite === 1;
|
||||||
|
})();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// resolve promises in _note object
|
// resolve promises in _note object
|
||||||
_note = await rap(_note);
|
_note = await rap(_note);
|
||||||
|
|
||||||
// (データベースの欠損などで)ユーザーがデータベース上に見つからなかったとき
|
//#region (データベースの欠損などで)参照しているデータがデータベース上に見つからなかったとき
|
||||||
if (_note.user == null) {
|
if (_note.user == null) {
|
||||||
console.warn(`in packaging note: note user not found on database: note(${_note.id})`);
|
console.warn(`[DAMAGED DB] (missing) pkg: note -> user :: ${_note.id} (user ${_note.userId})`);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (opts.detail) {
|
||||||
|
if (_note.replyId != null && _note.reply == null) {
|
||||||
|
console.warn(`[DAMAGED DB] (missing) pkg: note -> reply :: ${_note.id} (reply ${_note.replyId})`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_note.renoteId != null && _note.renote == null) {
|
||||||
|
console.warn(`[DAMAGED DB] (missing) pkg: note -> renote :: ${_note.id} (renote ${_note.renoteId})`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//#endregion
|
||||||
|
|
||||||
if (_note.user.isCat && _note.text) {
|
if (_note.user.isCat && _note.text) {
|
||||||
_note.text = _note.text.replace(/な/g, 'にゃ').replace(/ナ/g, 'ニャ').replace(/ナ/g, 'ニャ');
|
_note.text = _note.text.replace(/な/g, 'にゃ').replace(/ナ/g, 'ニャ').replace(/ナ/g, 'ニャ');
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user