Compare commits

...

258 Commits

Author SHA1 Message Date
5e529eb605 afsjpikoafsfa 2022-09-18 23:37:32 +09:00
890bef35eb
12.119.0-simkey-v10 2022-09-18 17:26:31 +09:00
8aa3787ffc Merge branch 'sim-dev/emojiimport' into develop 2022-09-18 17:26:07 +09:00
2b05de0204
cleanup code 2022-09-18 17:25:42 +09:00
6cc945b53c
12.119.0-simkey-v9 2022-09-18 16:42:24 +09:00
c60db40175 Merge branch 'sim-dev/emojiimport' into develop 2022-09-18 16:42:01 +09:00
fbc1ec67cf
remove debug log 2022-09-18 16:41:37 +09:00
f66626e6e6 Merge branch 'sim-dev/emojiimport' into develop 2022-09-18 16:40:33 +09:00
c7b6e0cbf9
fix copy 2022-09-18 16:39:35 +09:00
0a83ec26c8
12.119.0-simkey-v8 2022-09-18 16:07:01 +09:00
bd5712581a
update i18n 2022-09-18 16:06:37 +09:00
677f6bd6e1
i18n 2022-09-18 15:56:48 +09:00
ea6f538109
delete debug logs 2022-09-18 15:54:25 +09:00
3a540dd1f6
iwpo 2022-09-18 15:47:09 +09:00
2be42c0e4b
wuip 2022-09-18 15:44:12 +09:00
38e8d2cdb5
wip 2022-09-18 15:39:58 +09:00
3d8c8af0e8
wip 2022-09-18 15:37:19 +09:00
d06e9e7e0c
wqip 2022-09-18 15:34:08 +09:00
8bc3406c3c
wip 2022-09-18 15:29:04 +09:00
f817979d22
wip 2022-09-18 15:25:33 +09:00
123fedb5c4
wip 2022-09-18 15:20:48 +09:00
d01fde57ae
wip 2022-09-18 15:16:49 +09:00
657959dd30
wip 2022-09-18 14:49:56 +09:00
ac50efc5d1
wip 2022-09-18 14:46:06 +09:00
1e17b7db6b
wip 2022-09-18 14:17:55 +09:00
e0cea2facc
wip 2022-09-18 14:06:47 +09:00
167a7201c2
qip 2022-09-18 01:48:21 +09:00
581738f264
wip 2022-09-18 01:44:42 +09:00
cb18ce16a0
wip 2022-09-18 01:34:33 +09:00
356d28928a
wip: feat: reaction import contextmenu 2022-09-18 01:20:12 +09:00
3bd7f3d301
fix: edit eslint rule 2022-09-17 01:47:16 +09:00
fa7c8024d6 Merge branch 'sim-dev/ytplayer' into develop 2022-09-17 01:43:26 +09:00
5bff0cb41f
fix: delete debug output 2022-09-17 01:39:59 +09:00
6de468573a
fix: tailing comma 2022-09-17 01:11:26 +09:00
6fb825afdc
12.119.0-simkey-v7 2022-09-17 00:32:07 +09:00
f15e9b1e4e Merge branch 'sim-dev/ytplayer' into develop 2022-09-17 00:31:25 +09:00
f97146be98
fix: disable at mobile and creanup code 2022-09-17 00:07:58 +09:00
09ff9f76d0
bump version to 12.119.0-simkey-v6 2022-09-16 20:19:51 +09:00
95ec171479
fix: improve design 2022-09-16 20:18:57 +09:00
177ee5645e
fix: player fill window 2022-09-16 19:37:13 +09:00
98a768c28f
wip: feat: Youtube Player Window 2022-09-14 01:05:07 +09:00
a1c97e6700 Merge branch 'sim-dev/misete' into develop 2022-09-13 21:13:28 +09:00
41c0505043
12.119.0-simkey-v5 2022-09-13 21:12:35 +09:00
ade3b8e3fb
feat: add ifiltergenerator powered by misetehoshii 2022-09-13 20:54:57 +09:00
7817bfac2e
bump version to 12.119.0-simkey-v4 2022-09-12 02:42:14 +09:00
b59e0cda52
fix: cant reaction at note detailed 2022-09-12 02:22:57 +09:00
c366a9aa48
bump version to 12.119.0-simkey-v3 2022-09-12 01:39:20 +09:00
ThinaticSystem
a6287d4436 fix tab index 2022-09-12 01:38:42 +09:00
99a028facf
fix: i18n 2022-09-12 01:38:39 +09:00
ThinaticSystem
8f00846e44 style 2022-09-12 01:26:22 +09:00
ThinaticSystem
d8a4837df6 fix emojiPicker 2022-09-12 01:26:22 +09:00
ThinaticSystem
070d91e16e fix type 2022-09-12 01:26:22 +09:00
ThinaticSystem
18f778a335 毎回withRenoteをfalseに 2022-09-12 01:26:22 +09:00
ThinaticSystem
9c48c97047 fix 2022-09-12 01:26:22 +09:00
ThinaticSystem
3a7fbf9406 うごいた 2022-09-12 01:23:47 +09:00
c56f8fd953
feat: pakuru 2022-09-12 01:23:19 +09:00
9f6e6ac3c8
fix: remove comment 2022-09-12 01:23:10 +09:00
35659b6a6a
bump version to 12.119.0-simkey-v2 2022-09-11 23:07:32 +09:00
542349855e
fix: improve emojigen 2022-09-11 23:03:27 +09:00
b73d50db29 モデレーターがRelaysを操作できない問題を修正 2022-09-11 22:29:22 +09:00
d20b109d61 モデレーターがコントロールパネル開いたとき、Settingsが表示されないように 2022-09-11 22:29:11 +09:00
72983ff9e1
fix: url 2022-09-10 23:13:02 +09:00
b1ad672df8
fix: aaaaaaaaa 2022-09-10 22:47:33 +09:00
38ae0c4c1e
Merge remote-tracking branch 'refs/remotes/misskey-dev/master'
Conflicts:
	package.json
	packages/client/src/components/MkButton.vue
	packages/client/src/components/ui/button.vue
	packages/client/src/pages/timeline.tutorial.vue
2022-09-10 21:24:53 +09:00
bf781707c5
fix: old deck ui sidebar 2022-09-10 19:40:09 +09:00
6214d65ff5
fix: gaba limit 2022-09-10 19:39:47 +09:00
2d4dfc5137
fix: emojigen filename 2022-09-04 04:07:50 +09:00
2ea8e811d5
fix: emojigen 2022-09-04 03:47:00 +09:00
a151a14632
12.118.1-simkey-v2 2022-08-24 17:21:12 +09:00
d93c88e179
fix: popup blur 2022-08-24 17:20:14 +09:00
62cfc4ce13
fix: backend note test (文字数オーバー) 2022-08-12 04:31:09 +09:00
15a8ec6e45
fix: welcome page iikanji TM 2022-08-12 03:45:50 +09:00
8bc0b8e53d Merge tag '12.118.1' into develop 2022-08-08 17:00:45 +09:00
049be7caab Merge tag '12.117.1' into develop 2022-07-19 17:25:25 +09:00
0ed40b920b 12.116.1-simkey-v2 2022-07-17 18:35:03 +09:00
3b28e7029c fix: restore reload button 2022-07-17 18:34:12 +09:00
781dcdef53 Merge tag '12.116.1' into develop 2022-07-17 15:40:44 +09:00
42a7eccb36 feat: restore old deck ui 2 2022-07-17 15:39:14 +09:00
38a53b95ec feat: restore old deck ui 2022-07-17 06:49:25 +09:00
8d1f90038b
fix: emojigen.vue 2022-07-17 00:57:43 +09:00
65bb111a90 Merge tag '12.116.0' into develop 2022-07-17 00:06:29 +09:00
ed9cd9a770 Merge remote-tracking branch 'misskey-dev/develop' into develop 2022-07-13 22:14:40 +09:00
eb8ab56f46 Merge remote-tracking branch 'misskey-dev/develop' into develop 2022-07-09 18:56:47 +09:00
6dbd94ef09 Merge tag '12.112.1' into develop 2022-07-08 23:44:00 +09:00
57d53d5bc6 fix visitor UI 2022-06-18 18:24:20 +09:00
da16dfa7c9
update fix NoGrass Light Theme 2022-06-18 17:40:14 +09:00
fa3dee9222 いっぱい変えた 2022-06-17 01:46:10 +09:00
5f10f76488 update locale engine 2022-06-17 00:02:46 +09:00
c78de87c7a update package.json 2022-06-16 19:05:04 +09:00
263ac2d462 彩度場ーとコンテキストメニューなおした 2022-06-16 18:58:00 +09:00
adf755f0b2 いっぱいアップデート 2022-06-16 18:41:04 +09:00
de633f0117 update themes 2022-06-16 04:50:25 +09:00
e47495502d
update package.json style.scss 2022-06-16 00:53:06 +09:00
794c2c05a3
update package.json style.scss 2022-06-16 00:33:06 +09:00
baddc3b6c7
update package.json style.scss 2022-06-16 00:24:20 +09:00
3c140b2a62
update package.json style.scss 2022-06-15 23:36:43 +09:00
b72d8e0d58
update package.json style.scss 2022-06-13 23:31:06 +09:00
c1e56a00e0
update package.json init.ts 2022-06-13 23:23:46 +09:00
8c857daee0
update package.json init.ts style.scss 2022-06-13 23:17:09 +09:00
65ca1dd106
update package.json store.ts 2022-06-13 22:38:48 +09:00
b236cfebd3
update package.json 2022-06-13 22:11:34 +09:00
f5cf56862d
feat: panel blur 2022-06-13 22:09:44 +09:00
1e45e9c1a1 Merge remote-tracking branch 'misskey-dev/master' into develop 2022-06-13 16:31:41 +09:00
e475790710 Merge remote-tracking branch 'misskey-dev/master' into develop 2022-06-11 20:36:00 +09:00
b708b18004
fix: edit welcome animation2 2022-05-24 22:39:43 +09:00
14cb8262c1
fix: edit welcome animation 2022-05-24 22:27:17 +09:00
11127ae87a Merge branch 'sim-dev-emoji-gene' into develop 2022-05-24 21:49:28 +09:00
8db8fc605f
fix emojigen 40(stretchが逆) 2022-05-15 23:26:48 +09:00
4c71d05f51
wip emojigen 39(fix link) 2022-05-08 01:06:44 +09:00
da8cb8fc73
wip emojigen 38(fix link) 2022-05-08 00:44:45 +09:00
aecc01fd8a
wip emojigen 37(fix link) 2022-05-07 22:58:46 +09:00
ffeb109afd
wip emojigen 36(add preview color, fix link) 2022-05-07 22:46:27 +09:00
042d5f8c2d
wip emojigen 35(change fa version, fix link) 2022-05-07 21:32:36 +09:00
39f2ccf8a3
wip emojigen 34 2022-05-07 20:15:03 +09:00
55f0e23a74
emojigen wip33 2022-05-07 12:26:45 +09:00
b4edc40fa8
emojigen wip32 2022-05-07 12:04:56 +09:00
562e4fd743
emojigen wip31 2022-05-07 03:43:29 +09:00
db7abd17fb
emojigen wip30 2022-05-06 22:42:04 +09:00
fb1781ca53
emojigen wip29 2022-05-06 18:37:20 +09:00
17af085bef
emojigen wip28 2022-05-06 17:34:41 +09:00
5d23cf8c58
emojigen wip27 2022-05-06 04:43:32 +09:00
e051b7719d
emojigen wip26 2022-05-06 04:30:48 +09:00
522e148146
emojigen wip25 2022-05-06 04:19:23 +09:00
04c7dcd201
emojigen wip23 2022-05-06 03:43:04 +09:00
744310dc4e
emojigen wip22 2022-05-06 00:13:12 +09:00
7d62342477
Adzukiさんが変更 2022-05-06 00:11:11 +09:00
025dc3d6f9
emojigen wip21 2022-05-05 22:59:27 +09:00
98f412a172
emojigen wip20 2022-05-05 22:24:05 +09:00
970f373cae
emojigen wip19(あほすぎる) 2022-05-05 21:40:11 +09:00
c8331b5822
emojigen wip18 2022-05-05 20:43:39 +09:00
a8337b0964
emojigen wip17 2022-05-05 20:28:46 +09:00
85cc716781
Adzukiさんが変更 2022-05-05 20:00:00 +09:00
14990a1bd1
emojigen wip16 2022-05-05 19:59:11 +09:00
6b9f0d800e
emojigen wip15 2022-05-05 19:14:26 +09:00
3e96cc2b6b
emojigen wip14 2022-05-05 18:08:09 +09:00
5dad50767e
emojigen wip13 2022-05-05 17:02:11 +09:00
b54e63f835
emojigen wip12 2022-05-05 16:55:34 +09:00
d59a072de7
emojigen wip11 2022-05-05 16:31:44 +09:00
5e3554d693
emojigen wip10 2022-05-05 00:38:03 +09:00
8b3e7bf04c
emojigen wip9 2022-05-04 23:45:12 +09:00
15a1f1e585
emojigen wip8 2022-05-04 23:38:11 +09:00
8db5ec2919
emojigen wip7 2022-05-04 23:34:25 +09:00
c7e1bdf701
emojigen wip6 2022-05-04 23:30:27 +09:00
4d1b6b1c24
emojigen wip5 2022-05-04 23:24:17 +09:00
7a2c54cbd9
emojigen wip4 2022-05-04 23:09:30 +09:00
3a896e220a
emojigen wip3 2022-05-04 22:52:35 +09:00
3499ba5456
emojigen wip2 2022-05-04 20:49:47 +09:00
こけっち
641a565a86
wip set query object 2022-05-03 17:18:15 +09:00
300a84f3c4
emojigen wip 2022-05-03 16:51:26 +09:00
545671818a Merge branch 'develop' 2022-04-25 18:08:54 +09:00
22b647d97d Merge remote-tracking branch 'misskey-dev/master' into develop 2022-04-25 18:08:05 +09:00
22312ce983 Merge branch 'develop' 2022-04-11 23:17:30 +09:00
49b21efb60 Merge remote-tracking branch 'misskey-dev/master' into develop 2022-04-11 23:17:10 +09:00
dca6d23415 Merge branch 'develop' 2022-04-03 21:25:59 +09:00
7156bfb01c Merge remote-tracking branch 'misskey-dev/master' into develop 2022-04-03 21:25:44 +09:00
d4e9082941 Merge branch 'develop' 2022-04-02 17:05:00 +09:00
a2242261db Merge remote-tracking branch 'misskey-dev/master' into develop 2022-04-02 17:04:46 +09:00
9cf6561a95 Merge branch 'develop' 2022-04-02 16:07:03 +09:00
a989e904cc Merge branch '2022-wtf' into develop 2022-04-02 16:06:26 +09:00
7774465274 Merge branch 'develop' 2022-04-02 15:58:45 +09:00
b2299e1344 Merge remote-tracking branch 'misskey-dev/master' into develop 2022-04-02 15:57:56 +09:00
af0dfb2362
wtf??? 2022-04-01 03:39:17 +09:00
ad74fbc48f
wtf????aaaa 2022-04-01 03:29:45 +09:00
d0775da7d2
wtf????aaa 2022-04-01 03:23:01 +09:00
633994ecb6
wtf????aa 2022-04-01 02:46:07 +09:00
54923b7ac9
wtf????aa 2022-04-01 02:21:30 +09:00
48e132a2c7
wtf????a 2022-04-01 02:03:24 +09:00
7663170fae
wtf????a 2022-04-01 01:59:08 +09:00
cebb66cc36
wtf???? 2022-04-01 00:47:56 +09:00
f6dda604dc
fix: update package.json select-file-enc.ts 2022-03-30 16:05:35 +09:00
d10143544d
fix: update package.json 2022-03-30 16:04:20 +09:00
40e3c75c13
fix: update select-file-enc.ts 2022-03-30 15:59:08 +09:00
a2150b1269
fix: update l-simkey.json5 2022-03-29 03:08:09 +09:00
19579ed0f1
fix: add l-simkey.json5 2022-03-29 02:54:17 +09:00
78396baf82
fix: update package.json 2022-03-29 02:34:31 +09:00
982896f7be
fix: update default store.ts 2022-03-29 02:31:29 +09:00
1c9b842983
fix: max note text length to 8192 2022-03-23 11:36:10 +09:00
こけっち
0cf950bb35
Update test.yml 2022-03-21 19:38:59 +09:00
ef4f49778e Merge branch 'develop' 2022-03-21 19:29:41 +09:00
721c47a74b
fix: reject mp4 2022-03-21 19:28:22 +09:00
11cbbeb795 Merge branch 'develop' 2022-03-13 16:22:27 +09:00
こけっち
6bc893ca44
Merge pull request #27 from sim1222/sim-dev-comv-mov-1
feat: Add convert video container button
2022-03-13 16:15:56 +09:00
9c34f96cdd
feat: Add convert video container button lang 2022-03-13 16:14:57 +09:00
74a80b3405
feat: Add convert video container button 2022-03-13 16:11:08 +09:00
84da9d4cd4
fix: wip 2022-03-13 14:37:55 +09:00
b48f76aad1
fix: update package.json 2022-03-13 14:14:44 +09:00
78b9550eca
fix: debug 2022-03-13 14:09:44 +09:00
8fc770d371 Merge branch 'develop' 2022-03-12 19:38:26 +09:00
4aed68ec96 Merge tag '12.108.1-simkey' into develop 2022-03-12 19:38:14 +09:00
90c89c3083
fix: debug 2022-03-12 02:24:23 +09:00
c63d7dc898
fix: wippppppppppppppppppppppaaafaafawfwafwaafw 2022-03-12 02:19:56 +09:00
74e695a320
fix: wipppppppppppppppppppppp 2022-03-12 02:11:15 +09:00
f269ceaee9
fix: wip 2022-03-12 01:30:08 +09:00
57a106a192
fix: wip 2022-03-12 01:23:49 +09:00
5563d6bdeb
fix: wip 2022-03-11 22:48:50 +09:00
ab59a5b659
fix: dont export select-file-enc.ts 2022-03-11 22:45:03 +09:00
a81f8b8275
feat: Add video convert button 2022-03-11 22:34:38 +09:00
こけっち
c99c4b73f4
Create select-file-enc.ts 2022-03-11 22:05:08 +09:00
こけっち
995d8f2f5d
Update post-form.vue 2022-03-11 22:03:36 +09:00
6bb22007d8 Merge branch 'develop' 2022-03-09 23:09:49 +09:00
a4a0d33348 Merge tag '12.108.0-simkey' into develop 2022-03-09 23:09:14 +09:00
7d1f3cd1e8 Merge branch 'develop' of github.com:sim1222/misskey into develop 2022-02-21 19:08:08 +09:00
dc13627a68 add gitignore 2022-02-21 19:08:04 +09:00
b96e886a61 Merge branch 'develop' 2022-02-17 01:19:43 +09:00
7024f6651f Merge remote-tracking branch 'origin/sim-dev-reload-button-2' into develop 2022-02-17 01:19:30 +09:00
4f9aca0aa3 add reload button 2022-02-17 01:18:37 +09:00
88a2606884 Merge branch 'develop' 2022-02-12 17:39:33 +09:00
d6b15add1b Merge remote-tracking branch 'misskey-dev/master' into develop 2022-02-12 17:39:23 +09:00
39307694b5 Merge branch 'develop' 2022-02-11 22:20:20 +09:00
9b7be06eef Merge remote-tracking branch 'misskey-dev/develop' into develop 2022-02-11 22:20:04 +09:00
5afb3a59cd Merge branch 'develop' 2022-02-11 19:25:04 +09:00
73d66fc439 Merge tag '12.106.0-simkey' into develop 2022-02-11 19:24:42 +09:00
12214c473a Merge branch 'develop' 2022-02-09 22:01:03 +09:00
8e173a6d8e Merge tag '12.105.0' into develop 2022-02-09 22:00:47 +09:00
9d0c1a8fea Merge branch 'develop' 2022-02-09 17:17:20 +09:00
aee5193ef6 Merge branch 'master' into develop 2022-02-09 17:16:54 +09:00
2892a31142
Merge remote-tracking branch 'origin/develop' into develop 2022-02-09 17:14:50 +09:00
90bb0cec37
fix: Nya Translation from ja-JP 2022-02-09 17:14:30 +09:00
こけっち
19775507fc
Merge pull request #26 from sim1222/develop
Merge Develop 12.104.0
2022-02-09 16:40:32 +09:00
9d4f621833 fix conflct 2022-02-09 16:39:15 +09:00
こけっち
2e095b4863
Merge pull request #24 from sim1222/develop
Merge Develop 12.103.1
2022-02-03 10:48:14 +09:00
こけっち
5d9b9eeaad
Merge branch 'master' into develop 2022-02-03 10:48:04 +09:00
98f5cb6cd2 git push origin developMerge branch 'misskey-dev-develop' into develop 2022-02-03 01:43:09 +00:00
b88c4d6d34 fix comf 2022-02-03 01:42:58 +00:00
6238ba965b wip add button 2022-01-30 19:42:54 +09:00
こけっち
8489c3ca7b
Merge pull request #22 from sim1222/v12.102.1
Merge develop V12.102.1
2022-01-29 05:34:12 +09:00
こけっち
33871f3cb8
Merge branch 'master' into v12.102.1 2022-01-29 05:34:03 +09:00
5eb758fa28 update package.json 2022-01-29 04:17:15 +09:00
こけっち
56d8f7f257
Merge pull request #19 from sim1222/develop
Merge Develop v12.102.0
2022-01-27 00:53:29 +09:00
74c3e6d483 Merge branch 'misskey-dev-develop' into develop 2022-01-27 00:52:19 +09:00
ead62a538f fix confrict 2022-01-27 00:52:09 +09:00
こけっち
72058b30c2
Merge branch 'misskey-dev:develop' into develop 2022-01-26 20:09:42 +09:00
こけっち
22102639a8
Merge pull request #16 from sim1222/develop
Change Color
2022-01-13 03:11:14 +09:00
こけっち
ee4289076f Change Color 2022-01-13 03:10:42 +09:00
こけっち
be011d3985
Merge pull request #15 from sim1222/develop
change color
2022-01-09 21:43:44 +09:00
こけっち
6ef86df368 change color 2022-01-09 21:43:12 +09:00
こけっち
34e099984f
Merge pull request #14 from sim1222/develop
Fix ja-NY Language
2022-01-04 19:50:23 +09:00
こけっち
5a1c6be944 Fix ja-NY Language 2022-01-04 19:33:41 +09:00
こけっち
25991cfdc1
Merge pull request #11 from sim1222/develop
Merge Develop 12.101.1
2021-12-29 18:15:50 +09:00
b1a526aeea fix conflict 2021-12-29 18:15:03 +09:00
こけっち
f31ef70ce7
Merge pull request #9 from sim1222/develop
Merge develop 12.101.0
2021-12-29 15:12:02 +09:00
09a751b992 git push origin developMerge branch 'misskey-dev-develop' into develop 2021-12-29 15:08:42 +09:00
a41e4ed4fd fix conflict 2021-12-29 15:08:31 +09:00
こけっち
b77f7f5041
Merge pull request #7 from sim1222/develop
change repo url
2021-12-28 16:16:19 +09:00
こけっち
7adcf99865 change repo url 2021-12-28 16:15:51 +09:00
こけっち
cc2d5480f3
Merge pull request #6 from sim1222/develop
wip
2021-12-28 03:16:18 +09:00
こけっち
fdd129fb83 wip 2021-12-28 03:15:56 +09:00
こけっち
8dd3fef0e2
Merge pull request #5 from sim1222/develop
fix wip
2021-12-28 03:04:25 +09:00
こけっち
f1c66f09e1 fix wip 2021-12-28 03:03:58 +09:00
こけっち
6f328f2ccf
Merge pull request #4 from sim1222/develop
fix typo
2021-12-28 02:55:19 +09:00
こけっち
70b3d598c6 fix typo 2021-12-28 02:54:53 +09:00
こけっち
668c403ece
Merge pull request #3 from sim1222/develop
fix NYA JP
2021-12-28 02:49:24 +09:00
こけっち
c873f17080 fix NYA JP 2021-12-28 02:48:55 +09:00
こけっち
3f6691bd5e
Merge pull request #2 from sim1222/develop
vsddgvsgdsgsdsg
2021-12-28 02:27:13 +09:00
こけっち
260ece9881
Update package.json 2021-12-28 02:26:10 +09:00
こけっち
9a4c04fe79
Merge pull request #1 from sim1222/sim-dev-ja-nya
Sim dev ja nya
2021-12-28 02:25:13 +09:00
こけっち
24896d4a36
Merge branch 'misskey-dev:develop' into sim-dev-ja-nya 2021-12-28 02:17:22 +09:00
こけっち
8aea52e9b4 てぬきにゃ翻訳 2021-12-28 02:05:40 +09:00
73 changed files with 5784 additions and 73 deletions

2
.gitignore vendored
View File

@ -45,3 +45,5 @@ ormconfig.json
*.blend3 *.blend3
*.blend4 *.blend4
*.blend5 *.blend5
start.sh

View File

@ -23,6 +23,7 @@ const languages = [
'fr-FR', 'fr-FR',
'id-ID', 'id-ID',
'it-IT', 'it-IT',
'ja-NY',
'ja-JP', 'ja-JP',
'ja-KS', 'ja-KS',
'kab-KAB', 'kab-KAB',
@ -56,10 +57,12 @@ module.exports = Object.entries(locales)
.reduce((a, [k ,v]) => (a[k] = (() => { .reduce((a, [k ,v]) => (a[k] = (() => {
const [lang] = k.split('-'); const [lang] = k.split('-');
switch (k) { switch (k) {
case 'ja-JP': return v; case 'ja-NY': return merge(locales['ja-JP'], v);
case 'ja-JP':
case 'ja-KS': case 'ja-KS':
case 'en-US': return merge(locales['ja-JP'], v); case 'en-US': return merge(locales['ja-JP'], v);
default: return merge( default: return merge(
locales['ja-NY'],
locales['ja-JP'], locales['ja-JP'],
locales['en-US'], locales['en-US'],
locales[`${lang}-${primaries[lang]}`] || {}, locales[`${lang}-${primaries[lang]}`] || {},

View File

@ -107,6 +107,7 @@ clickToShow: "クリックして表示"
sensitive: "閲覧注意" sensitive: "閲覧注意"
add: "追加" add: "追加"
reaction: "リアクション" reaction: "リアクション"
reactWithRenote: "ついでにRenoteする"
reactionSetting: "ピッカーに表示するリアクション" reactionSetting: "ピッカーに表示するリアクション"
reactionSettingDescription2: "ドラッグして並び替え、クリックして削除、+を押して追加します。" reactionSettingDescription2: "ドラッグして並び替え、クリックして削除、+を押して追加します。"
rememberNoteVisibility: "公開範囲を記憶する" rememberNoteVisibility: "公開範囲を記憶する"
@ -224,6 +225,7 @@ currentPassword: "現在のパスワード"
newPassword: "新しいパスワード" newPassword: "新しいパスワード"
newPasswordRetype: "新しいパスワード(再入力)" newPasswordRetype: "新しいパスワード(再入力)"
attachFile: "ファイルを添付" attachFile: "ファイルを添付"
attachVideoFile: "MOVファイルを添付"
more: "もっと!" more: "もっと!"
featured: "ハイライト" featured: "ハイライト"
usernameOrUserId: "ユーザー名かユーザーID" usernameOrUserId: "ユーザー名かユーザーID"
@ -893,6 +895,10 @@ navbar: "ナビゲーションバー"
shuffle: "シャッフル" shuffle: "シャッフル"
account: "アカウント" account: "アカウント"
move: "移動" move: "移動"
deckOld: "旧デッキ"
pakuruConfirm: "パクりますか?"
pakuru: "パクる"
duplicateEmoji: "同じ名前の絵文字が存在します。インポートしますか?"
_sensitiveMediaDetection: _sensitiveMediaDetection:
description: "機械学習を使って自動でセンシティブなメディアを検出し、モデレーションに役立てることができます。サーバーの負荷が少し増えます。" description: "機械学習を使って自動でセンシティブなメディアを検出し、モデレーションに役立てることができます。サーバーの負荷が少し増えます。"
@ -1230,6 +1236,10 @@ _tutorial:
step7_1: "これで、Misskeyの基本的な使い方の説明は終わりました。お疲れ様でした。" step7_1: "これで、Misskeyの基本的な使い方の説明は終わりました。お疲れ様でした。"
step7_2: "もっとMisskeyについて知りたいときは、{help}を見てみてください。" step7_2: "もっとMisskeyについて知りたいときは、{help}を見てみてください。"
step7_3: "では、Misskeyをお楽しみください🚀" step7_3: "では、Misskeyをお楽しみください🚀"
step8_1: "さいごに"
step8_2: "このインスタンスではガラス風デザインを標準で採用しています。"
step8_3: "端末によってはパフォーマンスが低下する場合があります。"
step8_4: "その場合は設定よりブラーを使用しないように設定テーマをNoGlassにしてください。"
_2fa: _2fa:
alreadyRegistered: "既に設定は完了しています。" alreadyRegistered: "既に設定は完了しています。"

1811
locales/ja-NY.yml Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +1,10 @@
{ {
"name": "misskey", "name": "misskey",
"version": "12.119.0", "version": "12.119.0-simkey-v10111",
"codename": "indigo", "codename": "indigo",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/misskey-dev/misskey.git" "url": "https://github.com/sim1222/misskey.git"
}, },
"private": true, "private": true,
"scripts": { "scripts": {

View File

@ -1,4 +1,4 @@
export const MAX_NOTE_TEXT_LENGTH = 3000; export const MAX_NOTE_TEXT_LENGTH = 8192;
export const USER_ONLINE_THRESHOLD = 1000 * 60 * 10; // 10min export const USER_ONLINE_THRESHOLD = 1000 * 60 * 10; // 10min
export const USER_ACTIVE_THRESHOLD = 1000 * 60 * 60 * 24 * 3; // 3days export const USER_ACTIVE_THRESHOLD = 1000 * 60 * 60 * 24 * 3; // 3days

View File

@ -13,7 +13,7 @@ export const meta = {
limit: { limit: {
duration: 60000, duration: 60000,
max: 15, max: 100,
}, },
kind: 'read:notifications', kind: 'read:notifications',

View File

@ -34,6 +34,9 @@
if (lang == null || !supportedLangs.includes(lang)) { if (lang == null || !supportedLangs.includes(lang)) {
if (supportedLangs.includes(navigator.language)) { if (supportedLangs.includes(navigator.language)) {
lang = navigator.language; lang = navigator.language;
if (lang === 'ja-JP') {
lang = 'ja-NY';
}
} else { } else {
lang = supportedLangs.find(x => x.split('-')[0] === navigator.language); lang = supportedLangs.find(x => x.split('-')[0] === navigator.language);

View File

@ -4,7 +4,7 @@
"start_url": "/", "start_url": "/",
"display": "standalone", "display": "standalone",
"background_color": "#313a42", "background_color": "#313a42",
"theme_color": "#86b300", "theme_color": "#a7dc4e",
"icons": [ "icons": [
{ {
"src": "/static-assets/icons/192.png", "src": "/static-assets/icons/192.png",

View File

@ -138,7 +138,7 @@ describe('Note', () => {
it('文字数ぎりぎりで怒られない', async(async () => { it('文字数ぎりぎりで怒られない', async(async () => {
const post = { const post = {
text: '!'.repeat(3000), text: '!'.repeat(8192),
}; };
const res = await request('/notes/create', post, alice); const res = await request('/notes/create', post, alice);
assert.strictEqual(res.status, 200); assert.strictEqual(res.status, 200);
@ -146,7 +146,7 @@ describe('Note', () => {
it('文字数オーバーで怒られる', async(async () => { it('文字数オーバーで怒られる', async(async () => {
const post = { const post = {
text: '!'.repeat(3001), text: '!'.repeat(8193),
}; };
const res = await request('/notes/create', post, alice); const res = await request('/notes/create', post, alice);
assert.strictEqual(res.status, 400); assert.strictEqual(res.status, 400);

View File

@ -37,6 +37,7 @@
"katex": "0.15.6", "katex": "0.15.6",
"matter-js": "0.18.0", "matter-js": "0.18.0",
"mfm-js": "0.23.0", "mfm-js": "0.23.0",
"misetehoshii": "https://github.com/melt-adzuki/misetehoshii",
"misskey-js": "0.0.14", "misskey-js": "0.0.14",
"photoswipe": "5.3.2", "photoswipe": "5.3.2",
"prismjs": "1.29.0", "prismjs": "1.29.0",

View File

@ -71,15 +71,18 @@ function onMousedown(evt: Event) {
<style lang="scss" scoped> <style lang="scss" scoped>
.nvlagfpb { .nvlagfpb {
position: absolute; position: absolute;
backdrop-filter: var(--blur, blur(8px));
-webkit-backdrop-filter: var(--blur, blur(8px));
} }
.fade-enter-active, .fade-leave-active { .fade-enter-active, .fade-leave-active {
transition: opacity 0.5s cubic-bezier(0.16, 1, 0.3, 1), transform 0.5s cubic-bezier(0.16, 1, 0.3, 1); transition: opacity 0.5s cubic-bezier(0.16, 1, 0.3, 1), height 0.2s ease-out, transform 0.5s cubic-bezier(0.16, 1, 0.3, 1);
transform-origin: left top; transform-origin: left top;
} }
.fade-enter-from, .fade-leave-to { .fade-enter-from, .fade-leave-to {
opacity: 0; opacity: 0;
transform: scale(0.9); transform: scale(0.9);
height: 1px;
} }
</style> </style>

View File

@ -74,6 +74,7 @@
<button class="_button tab" :class="{ active: tab === 'unicode' }" @click="tab = 'unicode'"><i class="fas fa-leaf fa-fw"></i></button> <button class="_button tab" :class="{ active: tab === 'unicode' }" @click="tab = 'unicode'"><i class="fas fa-leaf fa-fw"></i></button>
<button class="_button tab" :class="{ active: tab === 'tags' }" @click="tab = 'tags'"><i class="fas fa-hashtag fa-fw"></i></button> <button class="_button tab" :class="{ active: tab === 'tags' }" @click="tab = 'tags'"><i class="fas fa-hashtag fa-fw"></i></button>
</div> </div>
<MkSwitch v-if="props.asReactionPicker" v-model="withRenote" class="withRenote">{{ i18n.ts.reactWithRenote }}</MkSwitch>
</div> </div>
</template> </template>
@ -90,6 +91,7 @@ import { deviceKind } from '@/scripts/device-kind';
import { emojiCategories, instance } from '@/instance'; import { emojiCategories, instance } from '@/instance';
import { i18n } from '@/i18n'; import { i18n } from '@/i18n';
import { defaultStore } from '@/store'; import { defaultStore } from '@/store';
import MkSwitch from '@/components/form/switch.vue';
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{
showPinned?: boolean; showPinned?: boolean;
@ -101,10 +103,11 @@ const props = withDefaults(defineProps<{
}); });
const emit = defineEmits<{ const emit = defineEmits<{
(ev: 'chosen', v: string): void; (ev: 'chosen', v: { reaction: string, withRenote: boolean } | string): void;
}>(); }>();
const search = ref<HTMLInputElement>(); const search = ref<HTMLInputElement>();
const withRenote = ref<boolean>(true);
const emojis = ref<HTMLDivElement>(); const emojis = ref<HTMLDivElement>();
const { const {
@ -278,6 +281,7 @@ function focus() {
function reset() { function reset() {
if (emojis.value) emojis.value.scrollTop = 0; if (emojis.value) emojis.value.scrollTop = 0;
q.value = ''; q.value = '';
withRenote.value = false; // Renotefalse
} }
function getKey(emoji: string | Misskey.entities.CustomEmoji | UnicodeEmojiDef): string { function getKey(emoji: string | Misskey.entities.CustomEmoji | UnicodeEmojiDef): string {
@ -294,7 +298,7 @@ function chosen(emoji: any, ev?: MouseEvent) {
} }
const key = getKey(emoji); const key = getKey(emoji);
emit('chosen', key); emit('chosen', (props.asReactionPicker) ? { reaction: key, withRenote: withRenote.value } : key);
// 使 // 使
if (!pinned.value.includes(key)) { if (!pinned.value.includes(key)) {
@ -452,6 +456,10 @@ defineExpose({
} }
} }
> .withRenote {
padding: 12px;
}
> .tabs { > .tabs {
display: flex; display: flex;
display: none; display: none;

View File

@ -51,8 +51,8 @@ const emit = defineEmits<{
const modal = ref<InstanceType<typeof MkModal>>(); const modal = ref<InstanceType<typeof MkModal>>();
const picker = ref<InstanceType<typeof MkEmojiPicker>>(); const picker = ref<InstanceType<typeof MkEmojiPicker>>();
function chosen(emoji: any) { function chosen(results: { reaction: string, withRenote: boolean }) {
emit('done', emoji); emit('done', results);
modal.value?.close(); modal.value?.close();
} }

View File

@ -237,7 +237,7 @@ onMounted(() => {
fixed = (type === 'drawer') || (getFixedContainer(props.src) != null); fixed = (type === 'drawer') || (getFixedContainer(props.src) != null);
await nextTick(); await nextTick();
align(); align();
}, { immediate: true }); }, { immediate: true });
@ -374,6 +374,8 @@ defineExpose({
&.popup { &.popup {
> .content { > .content {
position: absolute; position: absolute;
backdrop-filter: var(--blur, blur(8px));
-webkit-backdrop-filter: var(--blur, blur(8px));
&.fixed { &.fixed {
position: fixed; position: fixed;

View File

@ -207,11 +207,17 @@ function reply(viaKeyboard = false): void {
function react(viaKeyboard = false): void { function react(viaKeyboard = false): void {
pleaseLogin(); pleaseLogin();
blur(); blur();
reactionPicker.show(reactButton.value, reaction => { reactionPicker.show(reactButton.value, results => {
os.api('notes/reactions/create', { os.api('notes/reactions/create', {
noteId: appearNote.id, noteId: appearNote.id,
reaction: reaction, reaction: results.reaction,
}); });
if (results.withRenote) {
os.api('notes/create', {
renoteId: appearNote.id,
isRenote: true,
});
}
}, () => { }, () => {
focus(); focus();
}); });

View File

@ -215,7 +215,7 @@ function react(viaKeyboard = false): void {
reactionPicker.show(reactButton.value, reaction => { reactionPicker.show(reactButton.value, reaction => {
os.api('notes/reactions/create', { os.api('notes/reactions/create', {
noteId: appearNote.id, noteId: appearNote.id,
reaction: reaction, reaction: reaction.reaction,
}); });
}, () => { }, () => {
focus(); focus();

View File

@ -141,4 +141,7 @@ defineExpose({
min-height: 100%; min-height: 100%;
background: var(--bg); background: var(--bg);
} }
.yrolvcoq:has(.fill) {
height: 100%;
}
</style> </style>

View File

@ -27,6 +27,8 @@ let modal = $ref<InstanceType<typeof MkModal>>();
<style lang="scss" scoped> <style lang="scss" scoped>
.sfhdhdhq { .sfhdhdhq {
// backdrop-filter: var(--blur, blur(8px));
// -webkit-backdrop-filter: var(--blur, blur(8px));
&.drawer { &.drawer {
border-radius: 24px; border-radius: 24px;
border-bottom-right-radius: 0; border-bottom-right-radius: 0;

View File

@ -48,6 +48,7 @@
<XNotePreview v-if="showPreview" class="preview" :text="text"/> <XNotePreview v-if="showPreview" class="preview" :text="text"/>
<footer> <footer>
<button v-tooltip="i18n.ts.attachFile" class="_button" @click="chooseFileFrom"><i class="fas fa-photo-video"></i></button> <button v-tooltip="i18n.ts.attachFile" class="_button" @click="chooseFileFrom"><i class="fas fa-photo-video"></i></button>
<button v-tooltip="i18n.ts.attachVideoFile" class="_button" @click="chooseFileFromEnc"><i class="far fa-file-video"></i></button>
<button v-tooltip="i18n.ts.poll" class="_button" :class="{ active: poll }" @click="togglePoll"><i class="fas fa-poll-h"></i></button> <button v-tooltip="i18n.ts.poll" class="_button" :class="{ active: poll }" @click="togglePoll"><i class="fas fa-poll-h"></i></button>
<button v-tooltip="i18n.ts.useCw" class="_button" :class="{ active: useCw }" @click="useCw = !useCw"><i class="fas fa-eye-slash"></i></button> <button v-tooltip="i18n.ts.useCw" class="_button" :class="{ active: useCw }" @click="useCw = !useCw"><i class="fas fa-eye-slash"></i></button>
<button v-tooltip="i18n.ts.mention" class="_button" @click="insertMention"><i class="fas fa-at"></i></button> <button v-tooltip="i18n.ts.mention" class="_button" @click="insertMention"><i class="fas fa-at"></i></button>
@ -83,6 +84,7 @@ import { Autocomplete } from '@/scripts/autocomplete';
import * as os from '@/os'; import * as os from '@/os';
import { stream } from '@/stream'; import { stream } from '@/stream';
import { selectFiles } from '@/scripts/select-file'; import { selectFiles } from '@/scripts/select-file';
import {selectFileEnc, selectFilesEnc} from '@/scripts/select-file-enc';
import { defaultStore, notePostInterruptors, postFormActions } from '@/store'; import { defaultStore, notePostInterruptors, postFormActions } from '@/store';
import MkInfo from '@/components/MkInfo.vue'; import MkInfo from '@/components/MkInfo.vue';
import { i18n } from '@/i18n'; import { i18n } from '@/i18n';
@ -356,6 +358,12 @@ function chooseFileFrom(ev) {
}); });
} }
function chooseFileFromEnc(ev) {
selectFileEnc(ev.currentTarget ?? ev.target, i18n.ts.attachVideoFile).then(files_ => {
files.push(files_);
});
}
function detachFile(id) { function detachFile(id) {
files = files.filter(x => x.id !== id); files = files.filter(x => x.id !== id);
} }
@ -482,9 +490,9 @@ function onDragover(ev) {
switch (ev.dataTransfer.effectAllowed) { switch (ev.dataTransfer.effectAllowed) {
case 'all': case 'all':
case 'uninitialized': case 'uninitialized':
case 'copy': case 'copy':
case 'copyLink': case 'copyLink':
case 'copyMove': case 'copyMove':
ev.dataTransfer.dropEffect = 'copy'; ev.dataTransfer.dropEffect = 'copy';
break; break;
case 'linkMove': case 'linkMove':
@ -761,7 +769,7 @@ onMounted(() => {
margin-left: 0 !important; margin-left: 0 !important;
} }
} }
> .local-only { > .local-only {
margin: 0 0 0 12px; margin: 0 0 0 12px;
opacity: 0.7; opacity: 0.7;
@ -902,6 +910,7 @@ onMounted(() => {
max-width: 100%; max-width: 100%;
min-width: 100%; min-width: 100%;
min-height: 90px; min-height: 90px;
height: 250px;
&.withCw { &.withCw {
padding-top: 8px; padding-top: 8px;

View File

@ -6,6 +6,7 @@
class="hkzvhatu _button" class="hkzvhatu _button"
:class="{ reacted: note.myReaction == reaction, canToggle }" :class="{ reacted: note.myReaction == reaction, canToggle }"
@click="toggleReaction()" @click="toggleReaction()"
@contextmenu.stop="onContextmenu"
> >
<XReactionIcon class="icon" :reaction="reaction" :custom-emojis="note.emojis"/> <XReactionIcon class="icon" :reaction="reaction" :custom-emojis="note.emojis"/>
<span class="count">{{ count }}</span> <span class="count">{{ count }}</span>
@ -13,13 +14,14 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, onMounted, ref, watch } from 'vue'; import { computed, defineAsyncComponent, onMounted, ref, watch } from 'vue';
import * as misskey from 'misskey-js'; import * as misskey from 'misskey-js';
import XDetails from '@/components/MkReactionsViewer.details.vue'; import XDetails from '@/components/MkReactionsViewer.details.vue';
import XReactionIcon from '@/components/MkReactionIcon.vue'; import XReactionIcon from '@/components/MkReactionIcon.vue';
import * as os from '@/os'; import * as os from '@/os';
import { useTooltip } from '@/scripts/use-tooltip'; import { useTooltip } from '@/scripts/use-tooltip';
import { $i } from '@/account'; import { $i } from '@/account';
import { openReactionImportMenu } from '@/scripts/reactionImportMenu';
const props = defineProps<{ const props = defineProps<{
reaction: string; reaction: string;
@ -88,6 +90,11 @@ useTooltip(buttonRef, async (showing) => {
targetElement: buttonRef.value, targetElement: buttonRef.value,
}, {}, 'closed'); }, {}, 'closed');
}, 100); }, 100);
const onContextmenu = (e: MouseEvent) => {
e.preventDefault();
openReactionImportMenu(e, props.reaction);
};
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -1,5 +1,6 @@
<template> <template>
<form class="qlvuhzng _formRoot" autocomplete="new-password" @submit.prevent="onSubmit"> <form class="qlvuhzng _formRoot" autocomplete="new-password" @submit.prevent="onSubmit">
<div class="_formBlock">過去にこのインスタンスを訪れたことがある場合は<a href="/flush" target="_blank" class="_link">Local Storageを削除</a>してください</div>
<MkInput v-if="instance.disableRegistration" v-model="invitationCode" class="_formBlock" type="text" :spellcheck="false" required> <MkInput v-if="instance.disableRegistration" v-model="invitationCode" class="_formBlock" type="text" :spellcheck="false" required>
<template #label>{{ i18n.ts.invitationCode }}</template> <template #label>{{ i18n.ts.invitationCode }}</template>
<template #prefix><i class="fas fa-key"></i></template> <template #prefix><i class="fas fa-key"></i></template>

View File

@ -10,7 +10,7 @@
<transition :name="$store.state.animation ? 'zoom' : ''" mode="out-in"> <transition :name="$store.state.animation ? 'zoom' : ''" mode="out-in">
<component :is="self ? 'MkA' : 'a'" v-if="!fetching" class="link" :class="{ compact }" :[attr]="self ? url.substr(local.length) : url" rel="nofollow noopener" :target="target" :title="url"> <component :is="self ? 'MkA' : 'a'" v-if="!fetching" class="link" :class="{ compact }" :[attr]="self ? url.substr(local.length) : url" rel="nofollow noopener" :target="target" :title="url">
<div v-if="thumbnail" class="thumbnail" :style="`background-image: url('${thumbnail}')`"> <div v-if="thumbnail" class="thumbnail" :style="`background-image: url('${thumbnail}')`">
<button v-if="!playerEnabled && player.url" class="_button" :title="i18n.ts.enablePlayer" @click.prevent="playerEnabled = true"><i class="fas fa-play-circle"></i></button> <button v-if="!playerEnabled && player.url" class="_button" :title="i18n.ts.enablePlayer" @click.prevent="isMobile? playerEnabled = true : openPlayer()"><i class="fas fa-play-circle"></i></button>
</div> </div>
<article> <article>
<header> <header>
@ -36,6 +36,8 @@
import { onMounted, onUnmounted } from 'vue'; import { onMounted, onUnmounted } from 'vue';
import { url as local, lang } from '@/config'; import { url as local, lang } from '@/config';
import { i18n } from '@/i18n'; import { i18n } from '@/i18n';
import * as os from '@/os';
import { deviceKind } from '@/scripts/device-kind';
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{
url: string; url: string;
@ -46,6 +48,9 @@ const props = withDefaults(defineProps<{
compact: false, compact: false,
}); });
const MOBILE_THRESHOLD = 500;
const isMobile = $ref(deviceKind === 'smartphone' || window.innerWidth <= MOBILE_THRESHOLD);
const self = props.url.startsWith(local); const self = props.url.startsWith(local);
const attr = self ? 'to' : 'href'; const attr = self ? 'to' : 'href';
const target = self ? null : '_blank'; const target = self ? null : '_blank';
@ -103,6 +108,10 @@ function adjustTweetHeight(message: any) {
if (height) tweetHeight = height; if (height) tweetHeight = height;
} }
const openPlayer = () => {
os.pageWindow(`/ytplayer/${encodeURIComponent(requestUrl.href)}`);
};
(window as any).addEventListener('message', adjustTweetHeight); (window as any).addEventListener('message', adjustTweetHeight);
onUnmounted(() => { onUnmounted(() => {

View File

@ -83,11 +83,12 @@ onMounted(() => {
<style lang="scss" scoped> <style lang="scss" scoped>
.popup-enter-active, .popup-leave-active { .popup-enter-active, .popup-leave-active {
transition: opacity 0.3s, transform 0.3s !important; transition: opacity 0.3s, height 0.2s, transform 0.3s !important;
} }
.popup-enter-from, .popup-leave-to { .popup-enter-from, .popup-leave-to {
opacity: 0; opacity: 0;
transform: scale(0.9); transform: scale(0.9);
height: 0;
} }
.fxxzrfni { .fxxzrfni {

View File

@ -413,6 +413,8 @@ defineExpose({
position: fixed; position: fixed;
top: 0; top: 0;
left: 0; left: 0;
backdrop-filter: var(--blur, blur(8px));
-webkit-backdrop-filter: var(--blur, blur(8px));
> .body { > .body {
overflow: clip; overflow: clip;

View File

@ -29,6 +29,9 @@
</div> </div>
</template> </template>
<div class="buttons right"> <div class="buttons right">
<button v-tooltip.noDelay="i18n.ts.reload" class="_button button" onclick="location.reload();">
<i class="fa-solid fa-arrow-rotate-right"></i>
</button>
<template v-for="action in actions"> <template v-for="action in actions">
<button v-tooltip.noDelay="action.text" class="_button button" :class="{ highlighted: action.highlighted }" @click.stop="action.handler" @touchstart="preventDrag"><i :class="action.icon"></i></button> <button v-tooltip.noDelay="action.text" class="_button button" :class="{ highlighted: action.highlighted }" @click.stop="action.handler" @touchstart="preventDrag"><i :class="action.icon"></i></button>
</template> </template>

View File

@ -172,6 +172,7 @@ import { getAccountFromId } from '@/scripts/get-account-from-id';
window.location.search === '?zen' ? defineAsyncComponent(() => import('@/ui/zen.vue')) : window.location.search === '?zen' ? defineAsyncComponent(() => import('@/ui/zen.vue')) :
!$i ? defineAsyncComponent(() => import('@/ui/visitor.vue')) : !$i ? defineAsyncComponent(() => import('@/ui/visitor.vue')) :
ui === 'deck' ? defineAsyncComponent(() => import('@/ui/deck.vue')) : ui === 'deck' ? defineAsyncComponent(() => import('@/ui/deck.vue')) :
ui === 'deckold' ? defineAsyncComponent(() => import('@/ui/deckold.vue')) :
ui === 'classic' ? defineAsyncComponent(() => import('@/ui/classic.vue')) : ui === 'classic' ? defineAsyncComponent(() => import('@/ui/classic.vue')) :
defineAsyncComponent(() => import('@/ui/universal.vue')), defineAsyncComponent(() => import('@/ui/universal.vue')),
); );
@ -337,9 +338,13 @@ import { getAccountFromId } from '@/scripts/get-account-from-id';
's': search, 's': search,
}; };
if ($i) { if (!$i) {
// only add post shortcuts if logged in localStorage.setItem('wallpaper', 'https://simkey.net/files/c2f30819-64f7-42df-91b6-0e7fb122a413');
hotkeys['p|n'] = post; }
if ($i) {
// only add post shortcuts if logged in
hotkeys['p|n'] = post;
if ($i.isDeleted) { if ($i.isDeleted) {
alert({ alert({

View File

@ -115,6 +115,13 @@ export const navbarItemDef = reactive({
localStorage.setItem('ui', 'deck'); localStorage.setItem('ui', 'deck');
unisonReload(); unisonReload();
}, },
}, {
text: i18n.ts.deckOld,
active: ui === 'deckold',
action: () => {
localStorage.setItem('ui', 'deckold');
unisonReload();
},
}, { }, {
text: i18n.ts.classic, text: i18n.ts.classic,
active: ui === 'classic', active: ui === 'classic',

View File

@ -19,7 +19,7 @@
</div> </div>
<FormSection> <FormSection>
<div class="_formLinks"> <div class="_formLinks">
<FormLink to="https://github.com/misskey-dev/misskey" external> <FormLink to="https://github.com/sim1222/misskey" external>
<template #icon><i class="fas fa-code"></i></template> <template #icon><i class="fas fa-code"></i></template>
{{ i18n.ts._aboutMisskey.source }} {{ i18n.ts._aboutMisskey.source }}
<template #suffix>GitHub</template> <template #suffix>GitHub</template>

View File

@ -0,0 +1,380 @@
<template>
<MkStickyContainer>
<template #header><XHeader v-model:tab="tab" :tabs="headerTabs" /></template>
<MkSpacer :content-max="700" :margin-min="16" :margin-max="32">
<div class="cwepdizn _formRoot">
<div v-if="tab === 'string'" class="local">
<FormSection>
<template #label>{{ $ts.preview }}</template>
<p><img :src="previewUrl" class="img" :alt="emojiName" /></p>
</FormSection>
<FormButton primary class="_formBlock" @click="uploadEmoji('url')">{{ $ts.emojiApproval }}</FormButton>
<FormSection>
<template #label>{{ $ts.settings }}</template>
<FormInput v-model="emojiName" class="_formBlock">
<template #label>{{ $ts.emojiName }}</template>
</FormInput>
<FormTextarea v-model="text" class="_formBlock">
<template #label>{{ $ts.text }}</template>
</FormTextarea>
<FormRadios v-model="emojiAlign" class="_formBlock">
<template #label>{{ $ts.emojiAlign }}</template>
<option value="left"><i class="fas fa-align-left"/></option>
<option value="center"><i class="fas fa-align-center"></i></option>
<option value="right"><i class="fas fa-align-right"/></option>
</FormRadios>
<FormFolder :default-open="false" class="_formBlock">
<template #label>{{ $ts.emojiSizeSetting }}</template>
<FormSection>
<FormSwitch v-model="emojiSizeFixed" class="_formBlock">
<template #label>{{ $ts.emojiSizeFixed }}</template>
</FormSwitch>
<FormSwitch v-model="emojiStretch" class="_formBlock">
<template #label>{{ $ts.emojiStretch }}</template>
</FormSwitch>
</FormSection>
</FormFolder>
<FormFolder :default-open="false" class="_formBlock">
<template #label>{{ $ts._pages.font }}</template>
<FormRadios v-model="font" class="_formBlock">
<option value="notosans-mono-bold">Noto Sans Mono CJK JP Bold</option>
<option value="mplus-1p-black">M+ 1p black</option>
<option value="rounded-x-mplus-1p-black">Rounded M+ 1p black</option>
<option value="ipamjm">IPAmj明朝</option>
<option value="aoyagireisyoshimo">青柳隷書しも</option>
<option value="LinLibertine_RBah">LinLibertine Bold</option>
</FormRadios>
</FormFolder>
<FormFolder :default-open="false" class="_formBlock">
<template #label>{{ $ts.emojiColor }}</template>
<FormSection>
<div class="cwepdizn-colors">
<div class="row">
<button v-for="color in accentColors" :key="color" class="color rounded _button" @click="setAccentColor(color)">
<div class="preview" :style="{ background: color }"></div>
</button>
</div>
</div>
<FormInput v-model="emojiColor" class="_formBlock" :style="{ color: '#' + emojiColor }">
<template #prefix><i class="fas fa-palette"></i></template>
<template #label @click="colorPick()">{{ $ts.emojiColor }}</template>
<template #caption>#RRGGBB</template>
</FormInput>
<FormButton @click="colorPick()">{{ $ts.colorPicker }}</FormButton>
</FormSection>
</FormFolder>
</FormSection>
</div>
<div v-else-if="tab === 'misetehoshii'" class="remote">
<FormSection>
<template #label>{{ $ts.preview }}</template>
<canvas class="preview__content" ref="canvas" width="64" height="30" />
</FormSection>
<FormButton primary class="_formBlock" @click="uploadEmoji('')">{{ $ts.emojiApproval }}</FormButton>
<FormSection>
<template #label>{{ $ts.settings }}</template>
<FormInput v-model="emojiName" class="_formBlock">
<template #label>{{ $ts.emojiName }}</template>
</FormInput>
<FormTextarea v-model="text" value="見せてほしい" class="_formBlock">
<template #label>{{ $ts.text }}</template>
</FormTextarea>
<FormRadios v-model="backColor" class="_formBlock">
<template #label>{{ $ts._theme.color }}</template>
<option v-for="c in backColors" v-bind:value="c">{{c}}</option>
</FormRadios>
</FormSection>
<FormSection>
<template #label>見せてほしいメーカー</template>
<div>Powered by <MkLink url="https://github.com/melt-adzuki/misetehoshii">misetehoshii</MkLink>
</div>
</FormSection>
</div>
</div>
</MkSpacer>
</MkStickyContainer>
</template>
<script lang="ts" setup>
import { v4 as uuid } from 'uuid';
import { computed, defineAsyncComponent, ref, watch } from 'vue';
import * as os from '@/os';
import { i18n } from '@/i18n';
import { defaultStore } from '@/store';
import { stream } from '@/stream';
import { uploadFile } from '@/scripts/upload';
import XHeader from './_header_.vue';
import MkTab from '@/components/MkTab.vue';
import MkLink from '@/components/MkLink.vue';
import FormSection from '@/components/form/section.vue';
import FormInput from '@/components/form/input.vue';
import FormSwitch from '@/components/form/switch.vue';
import FormButton from '@/components/MkButton.vue';
import FormRadios from '@/components/form/radios.vue';
import FormTextarea from '@/components/form/textarea.vue';
import FormFolder from '@/components/form/folder.vue';
import { definePageMetadata } from '@/scripts/page-metadata';
import { buttonImages } from "misetehoshii/src/assets";
import { draw } from "misetehoshii/src/canvas"
definePageMetadata(computed(() => ({
title: i18n.ts.emojiGen,
icon: 'fas fa-kiss-wink-heart',
})));
const tab = ref('string');
const headerTabs = $computed(() => [{
key: 'string',
title: 'もじもじ',
}, {
key: 'misetehoshii',
title: '見せてほしい',
}]);
const font = ref('rounded-x-mplus-1p-black');
const text = ref('');
const emojiName = ref('');
const emojiAlign = ref('center');
const emojiSizeFixed = ref(false);
const emojiStretch = ref(false);
const emojiColor = ref('90ee90');
const previewUrl = ref('');
const accentColors = [
'#ff00ff',
'#ff0080',
'#D55353',
'#ff8080',
'#ff8040',
'#ffff80',
'#80ff80',
'#90ee90',
'#80ffff',
'#0080ff',
'#8080ff',
];
const backColor = ref('blue')
const canvas = ref<HTMLCanvasElement>()
const buttonImage = computed(() => buttonImages[backColor.value])
const backColors = [
'blue',
'peacockGreen',
'green',
'yellow',
'red',
'pink',
'disabled',
];
const updateCanvas = (): void => {
const context = canvas.value!.getContext("2d")!
draw({ context, text: text.value, button: buttonImage.value })
}
watch([font, text, emojiAlign, emojiSizeFixed, emojiStretch, emojiColor, backColor], () => {
preview();
updateCanvas();
});
const colorPick = (): void => {
const input = document.createElement('input') as HTMLInputElement;
input.type = 'color';
input.value = `#${emojiColor.value}`;
input.addEventListener('input', () => {
emojiColor.value = input.value.replace('#', '');
});
(window as any).__misskey_input_ref__ = input;
input.click();
};
const makeUrl = (): string => {
const API_URL = 'https://emoji-gen.ninja/emoji';
const query = {
text: encodeURI(text.value),
color: emojiColor.value + 'FF',
back_color: '00000000',
font: font.value,
size_fixed: !emojiSizeFixed.value ? 'true' : 'false',
align: 'center',
stretch: !emojiStretch.value ? 'true' : 'false',
public_fg: 'false',
locale: 'ja',
};
return API_URL + '?' + Object.entries(query).map(([key, value]) => `${key}=${value}`).join('&');
};
const preview = (): void => {
previewUrl.value = makeUrl();
};
const setAccentColor = (color) => {
emojiColor.value = color.replace('#', '');
};
const uploadFileFromUrlWithId = (url: string) => new Promise<string>(async resolve => {
const marker = uuid();
//
const connection = stream.useChannel('main');
connection.on('urlUploadFinished', async response => {
if (response.marker === marker) {
resolve(response.file.id);
connection.dispose();
}
});
//
await os.api('drive/files/upload-from-url', {
url,
folderId: defaultStore.state.uploadFolder,
marker,
});
});
const getEmojiObject = emojiId => new Promise<Record<string, any> | null>(async resolve => {
const sinceId = await os.api('admin/emoji/list', {
limit: 1,
untilId: emojiId.id,
});
if (!sinceId || !sinceId[0] || !sinceId[0].id) {
resolve(null);
return;
}
const id = await os.api('admin/emoji/list', {
limit: 1,
sinceId: sinceId[0].id,
});
if (!id || !id[0]) {
resolve(null);
return;
}
resolve(id[0]);
});
const uploadEmoji = async (type: string) => {
if (type == 'url') {
const emojiUrl = makeUrl();
const fileId = await uploadFileFromUrlWithId(emojiUrl);
//
await os.api('drive/files/update', {
fileId,
name: emojiName.value + '.png',
});
const emojiId = await os.api('admin/emoji/add', { fileId });
const emoji = await getEmojiObject(emojiId);
if (!emoji) {
os.alert({
type: 'error',
text: i18n.ts.failedToUploadEmoji,
});
}
os.popup(defineAsyncComponent(() => import('./emoji-edit-dialog.vue')), { emoji });
} else {
const canvasImg = new Promise<Blob>(resolve => {
canvas.value!.toBlob(blob => resolve(blob?.slice(0, blob.size, 'image/png') as Blob));
})
const file = new File([await canvasImg], emojiName.value + '.png', { type: 'image/png' });
const fileId = (await uploadFile(file, defaultStore.state.uploadFolder, undefined, true)).id
//
await os.api('drive/files/update', {
fileId,
name: emojiName.value + '.png',
});
const emojiId = await os.api('admin/emoji/add', { fileId });
const emoji = await getEmojiObject(emojiId);
if (!emoji) {
os.alert({
type: 'error',
text: i18n.ts.failedToUploadEmoji,
});
}
os.popup(defineAsyncComponent(() => import('./emoji-edit-dialog.vue')), { emoji });
}
};
</script>
<style lang="scss" scoped>
.preview-img {
height: 128px;
}
.cwepdizn {
::v-deep(.cwepdizn-colors) {
text-align: center;
> .row {
> .color {
display: inline-block;
position: relative;
width: 64px;
height: 64px;
border-radius: 8px;
> .preview {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: auto;
width: 42px;
height: 42px;
border-radius: 4px;
box-shadow: 0 2px 4px rgb(0 0 0 / 30%);
transition: transform 0.15s ease;
}
&:hover {
> .preview {
transform: scale(1.1);
}
}
&.active {
box-shadow: 0 0 0 2px var(--divider) inset;
}
&.rounded {
border-radius: 999px;
> .preview {
border-radius: 999px;
}
}
&.char {
line-height: 42px;
}
}
}
}
}
</style>

View File

@ -301,13 +301,13 @@ definePageMetadata(computed(() => ({
.empty { .empty {
margin: var(--margin); margin: var(--margin);
} }
.ldhfsamy { .ldhfsamy {
display: grid; display: grid;
grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
grid-gap: 12px; grid-gap: 12px;
margin: var(--margin) 0; margin: var(--margin) 0;
> .emoji { > .emoji {
display: flex; display: flex;
align-items: center; align-items: center;

View File

@ -1,6 +1,6 @@
<template> <template>
<div ref="el" class="hiyeyicy" :class="{ wide: !narrow }"> <div ref="el" class="hiyeyicy" :class="{ wide: !narrow }">
<div v-if="!narrow || currentPage?.route.name == null" class="nav"> <div v-if="!narrow || currentPage?.route.name == null" class="nav">
<MkSpacer :content-max="700" :margin-min="16"> <MkSpacer :content-max="700" :margin-min="16">
<div class="lxpfedzu"> <div class="lxpfedzu">
<div class="banner"> <div class="banner">
@ -24,6 +24,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { defineAsyncComponent, inject, nextTick, onMounted, onUnmounted, provide, watch } from 'vue'; import { defineAsyncComponent, inject, nextTick, onMounted, onUnmounted, provide, watch } from 'vue';
import { $i } from '../../account';
import { i18n } from '@/i18n'; import { i18n } from '@/i18n';
import MkSuperMenu from '@/components/MkSuperMenu.vue'; import MkSuperMenu from '@/components/MkSuperMenu.vue';
import MkInfo from '@/components/MkInfo.vue'; import MkInfo from '@/components/MkInfo.vue';
@ -101,6 +102,11 @@ const menuDef = $computed(() => [{
text: i18n.ts.customEmojis, text: i18n.ts.customEmojis,
to: '/admin/emojis', to: '/admin/emojis',
active: currentPage?.route.name === 'emojis', active: currentPage?.route.name === 'emojis',
}, {
icon: 'fas fa-laugh',
text: i18n.ts.emojiGen,
to: '/admin/emojigen',
active: currentPage?.route.name === 'emojigen',
}, { }, {
icon: 'fas fa-globe', icon: 'fas fa-globe',
text: i18n.ts.federation, text: i18n.ts.federation,
@ -134,52 +140,52 @@ const menuDef = $computed(() => [{
}], }],
}, { }, {
title: i18n.ts.settings, title: i18n.ts.settings,
items: [{ items: [ ...($i?.isAdmin ? [{
icon: 'fas fa-cog', icon: 'fas fa-cog',
text: i18n.ts.general, text: i18n.ts.general,
to: '/admin/settings', to: '/admin/settings',
active: currentPage?.route.name === 'settings', active: currentPage?.route.name === 'settings',
}, { }] : []), ...($i?.isAdmin ? [{
icon: 'fas fa-envelope', icon: 'fas fa-envelope',
text: i18n.ts.emailServer, text: i18n.ts.emailServer,
to: '/admin/email-settings', to: '/admin/email-settings',
active: currentPage?.route.name === 'email-settings', active: currentPage?.route.name === 'email-settings',
}, { }] : []), ...($i?.isAdmin ? [{
icon: 'fas fa-cloud', icon: 'fas fa-cloud',
text: i18n.ts.objectStorage, text: i18n.ts.objectStorage,
to: '/admin/object-storage', to: '/admin/object-storage',
active: currentPage?.route.name === 'object-storage', active: currentPage?.route.name === 'object-storage',
}, { }] : []), ...($i?.isAdmin ? [{
icon: 'fas fa-lock', icon: 'fas fa-lock',
text: i18n.ts.security, text: i18n.ts.security,
to: '/admin/security', to: '/admin/security',
active: currentPage?.route.name === 'security', active: currentPage?.route.name === 'security',
}, { }] : []), {
icon: 'fas fa-globe', icon: 'fas fa-globe',
text: i18n.ts.relays, text: i18n.ts.relays,
to: '/admin/relays', to: '/admin/relays',
active: currentPage?.route.name === 'relays', active: currentPage?.route.name === 'relays',
}, { }, ...($i?.isAdmin ? [{
icon: 'fas fa-share-alt', icon: 'fas fa-share-alt',
text: i18n.ts.integration, text: i18n.ts.integration,
to: '/admin/integrations', to: '/admin/integrations',
active: currentPage?.route.name === 'integrations', active: currentPage?.route.name === 'integrations',
}, { }] : []), ...($i?.isAdmin ? [{
icon: 'fas fa-ban', icon: 'fas fa-ban',
text: i18n.ts.instanceBlocking, text: i18n.ts.instanceBlocking,
to: '/admin/instance-block', to: '/admin/instance-block',
active: currentPage?.route.name === 'instance-block', active: currentPage?.route.name === 'instance-block',
}, { }] : []), ...($i?.isAdmin ? [{
icon: 'fas fa-ghost', icon: 'fas fa-ghost',
text: i18n.ts.proxyAccount, text: i18n.ts.proxyAccount,
to: '/admin/proxy-account', to: '/admin/proxy-account',
active: currentPage?.route.name === 'proxy-account', active: currentPage?.route.name === 'proxy-account',
}, { }] : []), ...($i?.isAdmin ? [{
icon: 'fas fa-cogs', icon: 'fas fa-cogs',
text: i18n.ts.other, text: i18n.ts.other,
to: '/admin/other-settings', to: '/admin/other-settings',
active: currentPage?.route.name === 'other-settings', active: currentPage?.route.name === 'other-settings',
}], }] : [])],
}, { }, {
title: i18n.ts.info, title: i18n.ts.info,
items: [{ items: [{

View File

@ -48,33 +48,139 @@
</I18n> </I18n>
<div>{{ i18n.ts._tutorial.step7_3 }}</div> <div>{{ i18n.ts._tutorial.step7_3 }}</div>
</div> </div>
<div v-else-if="tutorial === 7" class="_content">
<div>{{ $ts._tutorial.step8_1 }}</div>
<div>{{ $ts._tutorial.step8_2 }}</div>
<div>{{ $ts._tutorial.step8_3 }}</div>
<div>{{ $ts._tutorial.step8_4 }}</div>
<FormSwitch v-model="useBlurEffect" class="_formBlock">{{ i18n.ts.useBlurEffect }}</FormSwitch>
<FormSwitch v-model="useBlurEffectForModal" class="_formBlock">{{ i18n.ts.useBlurEffectForModal }}</FormSwitch>
<template v-if="darkMode">
<FormSelect v-model="darkThemeId" class="_formBlock">
<template #label>{{ $ts.themeForDarkMode }}</template>
<template #prefix><i class="fas fa-moon"></i></template>
<optgroup :label="$ts.darkThemes">
<option v-for="x in darkThemes" :key="x.id" :value="x.id">{{ x.name }}</option>
</optgroup>
<optgroup :label="$ts.lightThemes">
<option v-for="x in lightThemes" :key="x.id" :value="x.id">{{ x.name }}</option>
</optgroup>
</FormSelect>
<FormSelect v-model="lightThemeId" class="_formBlock">
<template #label>{{ $ts.themeForLightMode }}</template>
<template #prefix><i class="fas fa-sun"></i></template>
<optgroup :label="$ts.lightThemes">
<option v-for="x in lightThemes" :key="x.id" :value="x.id">{{ x.name }}</option>
</optgroup>
<optgroup :label="$ts.darkThemes">
<option v-for="x in darkThemes" :key="x.id" :value="x.id">{{ x.name }}</option>
</optgroup>
</FormSelect>
</template>
<template v-else>
<FormSelect v-model="lightThemeId" class="_formBlock">
<template #label>{{ $ts.themeForLightMode }}</template>
<template #prefix><i class="fas fa-sun"></i></template>
<optgroup :label="$ts.lightThemes">
<option v-for="x in lightThemes" :key="x.id" :value="x.id">{{ x.name }}</option>
</optgroup>
<optgroup :label="$ts.darkThemes">
<option v-for="x in darkThemes" :key="x.id" :value="x.id">{{ x.name }}</option>
</optgroup>
</FormSelect>
<FormSelect v-model="darkThemeId" class="_formBlock">
<template #label>{{ $ts.themeForDarkMode }}</template>
<template #prefix><i class="fas fa-moon"></i></template>
<optgroup :label="$ts.darkThemes">
<option v-for="x in darkThemes" :key="x.id" :value="x.id">{{ x.name }}</option>
</optgroup>
<optgroup :label="$ts.lightThemes">
<option v-for="x in lightThemes" :key="x.id" :value="x.id">{{ x.name }}</option>
</optgroup>
</FormSelect>
</template>
</div>
<div class="_footer navigation"> <div class="_footer navigation">
<div class="step"> <div class="step">
<button class="arrow _button" :disabled="tutorial === 0" @click="tutorial--"> <button class="arrow _button" :disabled="tutorial === 0" @click="tutorial--">
<i class="fas fa-chevron-left"></i> <i class="fas fa-chevron-left"></i>
</button> </button>
<span>{{ tutorial + 1 }} / 7</span> <span>{{ tutorial + 1 }} / 8</span>
<button class="arrow _button" :disabled="tutorial === 6" @click="tutorial++"> <button class="arrow _button" :disabled="tutorial === 7" @click="tutorial++">
<i class="fas fa-chevron-right"></i> <i class="fas fa-chevron-right"></i>
</button> </button>
</div> </div>
<MkButton v-if="tutorial === 6" class="ok" primary @click="tutorial = -1"><i class="fas fa-check"></i> {{ i18n.ts.gotIt }}</MkButton> <MkButton v-if="tutorial === 7" class="ok" primary @click="tutorial = -1"><i class="fas fa-check"></i> {{ i18n.ts.gotIt }}</MkButton>
<MkButton v-else class="ok" primary @click="tutorial++"><i class="fas fa-check"></i> {{ i18n.ts.next }}</MkButton> <MkButton v-else class="ok" primary @click="tutorial++"><i class="fas fa-check"></i> {{ i18n.ts.next }}</MkButton>
</div> </div>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed } from 'vue'; import { computed, onActivated, ref, watch } from 'vue';
import MkButton from '@/components/MkButton.vue'; import MkButton from '@/components/MkButton.vue';
import { defaultStore } from '@/store';
import { i18n } from '@/i18n'; import { i18n } from '@/i18n';
import { defaultStore, ColdDeviceStorage } from '@/store';
import FormSwitch from '@/components/form/switch.vue';
import FormSelect from '@/components/form/select.vue';
import { fetchThemes, getThemes } from '@/theme-store';
import { getBuiltinThemesRef } from '@/scripts/theme';
import { uniqueBy } from '@/scripts/array';
import { isDeviceDarkmode } from '@/scripts/is-device-darkmode';
const tutorial = computed({ const tutorial = computed({
get() { return defaultStore.reactiveState.tutorial.value || 0; }, get() { return defaultStore.reactiveState.tutorial.value || 0; },
set(value) { defaultStore.set('tutorial', value); }, set(value) { defaultStore.set('tutorial', value); },
}); });
const useBlurEffectForModal = computed(defaultStore.makeGetterSetter('useBlurEffectForModal'));
const useBlurEffect = computed(defaultStore.makeGetterSetter('useBlurEffect'));
const installedThemes = ref(getThemes());
const builtinThemes = getBuiltinThemesRef();
const instanceThemes = [];
const themes = computed(() => uniqueBy([...instanceThemes, ...builtinThemes.value, ...installedThemes.value], theme => theme.id));
const darkThemes = computed(() => themes.value.filter(t => t.base === 'dark' || t.kind === 'dark'));
const lightThemes = computed(() => themes.value.filter(t => t.base === 'light' || t.kind === 'light'));
const darkTheme = ColdDeviceStorage.ref('darkTheme');
const darkThemeId = computed({
get() {
return darkTheme.value.id;
},
set(id) {
ColdDeviceStorage.set('darkTheme', themes.value.find(x => x.id === id));
}
});
const lightTheme = ColdDeviceStorage.ref('lightTheme');
const lightThemeId = computed({
get() {
return lightTheme.value.id;
},
set(id) {
ColdDeviceStorage.set('lightTheme', themes.value.find(x => x.id === id));
}
});
const darkMode = computed(defaultStore.makeGetterSetter('darkMode'));
const syncDeviceDarkMode = computed(ColdDeviceStorage.makeGetterSetter('syncDeviceDarkMode'));
watch(syncDeviceDarkMode, () => {
if (syncDeviceDarkMode.value) {
defaultStore.set('darkMode', isDeviceDarkmode());
}
});
onActivated(() => {
fetchThemes().then(() => {
installedThemes.value = getThemes();
});
});
fetchThemes().then(() => {
installedThemes.value = getThemes();
});
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -175,7 +175,7 @@ definePageMetadata(computed(() => ({
} }
> .tl { > .tl {
background: var(--bg); // background: var(--bg);
border-radius: var(--radius); border-radius: var(--radius);
overflow: clip; overflow: clip;
} }

View File

@ -209,20 +209,34 @@ function showMenu(ev) {
> .main { > .main {
position: relative; position: relative;
width: min(480px, 100%); width: min(480px, 100%);
margin: auto auto auto 128px;
background: var(--panel); background: var(--panel);
border-radius: var(--radius); border-radius: var(--radius);
box-shadow: 0 12px 32px rgb(0 0 0 / 25%); box-shadow: 0 12px 32px rgb(0 0 0 / 25%);
margin: 0 0 auto 0;
animation: moveX 5s linear 0s infinite alternate, moveY 6.4s linear 0s infinite alternate;
-webkit-backdrop-filter: var(--blur, blur(15px));
backdrop-filter: var(--blur, blur(15px));
@keyframes moveX {
from { left: 0; } to { left: calc(100vw - 500px); }
}
@keyframes moveY {
from { top: 0; } to { top: calc(100vh - 510px); }
}
@media (max-width: 1200px) { @media (max-width: 1200px) {
margin: auto; margin: 0 0 auto 0;
} }
> .icon { > .icon {
width: 85px; width: 85px;
margin-top: -47px; margin-top: -47px;
border-radius: 100%; border-radius: 20%;
vertical-align: bottom; vertical-align: bottom;
background: var(--panel);
box-shadow: 0 12px 32px rgb(0 0 0 / 25%);
} }
> .menu { > .menu {
@ -306,4 +320,8 @@ function showMenu(ev) {
border-radius: 999px; border-radius: 999px;
} }
} }
._panel {
background: var(--panel);
}
</style> </style>

View File

@ -179,8 +179,21 @@ export default defineComponent({
color: #fff; color: #fff;
font-size: 1.1em; font-size: 1.1em;
margin: 0 0 auto 0;
animation: moveX 5s linear 0s infinite alternate, moveY 6.4s linear 0s infinite alternate;
-webkit-backdrop-filter: var(--blur, blur(15px));
backdrop-filter: var(--blur, blur(15px));
@keyframes moveX {
from { left: 0; } to { left: calc(100vw - 500px); }
}
@keyframes moveY {
from { top: 0; } to { top: calc(100vh - 510px); }
}
@media (max-width: 1200px) { @media (max-width: 1200px) {
margin: auto; margin: 0 0 auto 0;
} }
> h1 { > h1 {
@ -234,4 +247,9 @@ export default defineComponent({
} }
} }
} }
._panel {
background: var(--panel);
}
</style> </style>

View File

@ -2,7 +2,7 @@
<div v-if="meta" class="rsqzvsbo"> <div v-if="meta" class="rsqzvsbo">
<div class="top"> <div class="top">
<MkFeaturedPhotos class="bg"/> <MkFeaturedPhotos class="bg"/>
<div class="fade"></div> <!-- <div class="fade"></div> -->
<div class="emojis"> <div class="emojis">
<MkEmoji :normal="true" :no-style="true" emoji="👍"/> <MkEmoji :normal="true" :no-style="true" emoji="👍"/>
<MkEmoji :normal="true" :no-style="true" emoji="❤"/> <MkEmoji :normal="true" :no-style="true" emoji="❤"/>
@ -25,7 +25,7 @@
<div class="desc" v-html="meta.description || $ts.headlineMisskey"></div> <div class="desc" v-html="meta.description || $ts.headlineMisskey"></div>
</div> </div>
<div class="action"> <div class="action">
<MkButton inline gradate @click="signup()">{{ $ts.signup }}</MkButton> <MkButton inline gradate class="signup" @click="signup()">{{ $ts.signup }}</MkButton>
<MkButton inline @click="signin()">{{ $ts.login }}</MkButton> <MkButton inline @click="signin()">{{ $ts.login }}</MkButton>
</div> </div>
<div v-if="onlineUsersCount && stats" class="status"> <div v-if="onlineUsersCount && stats" class="status">
@ -191,7 +191,19 @@ export default defineComponent({
> .main { > .main {
position: relative; position: relative;
width: min(460px, 100%); width: min(460px, 100%);
margin: auto;
margin: 0 0 auto 0;
animation: moveX 5s linear 0s infinite alternate, moveY 6.4s linear 0s infinite alternate;
-webkit-backdrop-filter: var(--blur, blur(15px));
backdrop-filter: var(--blur, blur(15px));
@keyframes moveX {
from { left: 0; } to { left: calc(100vw - 500px); }
}
@keyframes moveY {
from { top: 0; } to { top: calc(100vh - 510px); }
}
> .misskey { > .misskey {
width: 150px; width: 150px;
@ -303,4 +315,9 @@ export default defineComponent({
} }
} }
} }
._panel {
background: var(--panel);
}
</style> </style>

View File

@ -88,6 +88,9 @@ export default defineComponent({
margin: 0 0 0 auto; margin: 0 0 0 auto;
max-width: max-content; max-width: max-content;
border-radius: 16px; border-radius: 16px;
-webkit-backdrop-filter: var(--blur, blur(15px));
backdrop-filter: var(--blur, blur(15px));
background: var(--panel);
> .richcontent { > .richcontent {
min-width: 250px; min-width: 250px;

View File

@ -0,0 +1,83 @@
<template>
<div class="poamfof fill">
<transition :name="$store.state.animation ? 'fade' : ''" mode="out-in">
<div v-if="player.url" class="player">
<iframe v-if="!fetching" :src="player.url + (player.url.match(/\?/) ? '&autoplay=1&auto_play=1' : '?autoplay=1&auto_play=1')" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen/>
</div>
</transition>
<MkLoading v-if="fetching" />
<MkError v-else-if="!player.url" @retry="ytFetch()" />
</div>
</template>
<script lang="ts" setup>
import { definePageMetadata } from '@/scripts/page-metadata';
import { computed } from 'vue';
import { lang } from '@/config';
const props = defineProps<{
url: string;
}>();
const requestUrl = new URL(props.url);
let fetching = $ref(true);
let title = $ref<string | null>(null);
let player = $ref({
url: null,
width: null,
height: null,
});
const requestLang = (lang || 'ja-JP').replace('ja-KS', 'ja-JP');
const ytFetch = () => {
fetching = true;
fetch(`/url?url=${encodeURIComponent(requestUrl.href)}&lang=${requestLang}`).then(res => {
res.json().then(info => {
if (info.url == null) return;
title = info.title;
fetching = false;
player = info.player;
});
});
};
ytFetch();
definePageMetadata(computed(() => props.url ? {
title: title?.toString() || 'Youtube Player',
path: `/notes/${props.url}`,
icon: 'fa-brands fa-youtube'
} : null));
</script>
<style lang="scss">
.poamfof {
position: relative;
overflow: hidden;
min-height: 64px;
.player {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
height: calc(100% - 10px);
width: calc(100% - 10px);
padding: 5px;
iframe {
width: 100%;
height: 100%;
border-radius: 0 0 var(--radius) var(--radius);
}
}
}
.fill {
height: 100%;
}
</style>

View File

@ -316,6 +316,10 @@ export const routes = [{
path: '/emojis', path: '/emojis',
name: 'emojis', name: 'emojis',
component: page(() => import('./pages/admin/emojis.vue')), component: page(() => import('./pages/admin/emojis.vue')),
}, {
path: '/emojigen',
name: 'emojigen',
component: page(() => import('./pages/admin/emojigen.vue')),
}, { }, {
path: '/queue', path: '/queue',
name: 'queue', name: 'queue',
@ -445,6 +449,9 @@ export const routes = [{
path: '/timeline/antenna/:antennaId', path: '/timeline/antenna/:antennaId',
component: page(() => import('./pages/antenna-timeline.vue')), component: page(() => import('./pages/antenna-timeline.vue')),
loginRequired: true, loginRequired: true,
}, {
path: '/ytplayer/:url',
component: page(() => import('./pages/ytplayer.vue')),
}, { }, {
name: 'index', name: 'index',
path: '/', path: '/',

View File

@ -26,6 +26,22 @@ export function getNoteMenu(props: {
const appearNote = isRenote ? props.note.renote as misskey.entities.Note : props.note; const appearNote = isRenote ? props.note.renote as misskey.entities.Note : props.note;
function pakuru(): void {
os.confirm({
type: 'question',
text: i18n.ts.pakuruConfirm,
}).then(({ canceled }) => {
if (canceled) return;
const postData = {
text: appearNote.text,
cw: appearNote.cw ? appearNote.cw || '' : undefined,
localOnly: appearNote.localOnly,
visibility: appearNote.visibility,
}
os.api('notes/create', postData, undefined);
});
}
function del(): void { function del(): void {
os.confirm({ os.confirm({
type: 'warning', type: 'warning',
@ -205,6 +221,11 @@ export function getNoteMenu(props: {
action: unclip, action: unclip,
}, null] : [] }, null] : []
), ),
{
icon: 'fas fa-copy',
text: i18n.ts.pakuru,
action: pakuru,
},
{ {
icon: 'fas fa-copy', icon: 'fas fa-copy',
text: i18n.ts.copyContent, text: i18n.ts.copyContent,

View File

@ -4,7 +4,7 @@ import { popup } from '@/os';
class ReactionPicker { class ReactionPicker {
private src: Ref<HTMLElement | null> = ref(null); private src: Ref<HTMLElement | null> = ref(null);
private manualShowing = ref(false); private manualShowing = ref(false);
private onChosen?: (reaction: string) => void; private onChosen?: ({ reaction: string, withRenote: boolean }) => void;
private onClosed?: () => void; private onClosed?: () => void;
constructor() { constructor() {
@ -17,8 +17,8 @@ class ReactionPicker {
asReactionPicker: true, asReactionPicker: true,
manualShowing: this.manualShowing manualShowing: this.manualShowing
}, { }, {
done: reaction => { done: results => {
this.onChosen!(reaction); this.onChosen!({ reaction: results.reaction, withRenote: results.withRenote });
}, },
close: () => { close: () => {
this.manualShowing.value = false; this.manualShowing.value = false;

View File

@ -0,0 +1,114 @@
import { defineAsyncComponent } from 'vue';
import { $i } from '@/account';
import { i18n } from '@/i18n';
import * as os from '@/os';
import copyToClipboard from '@/scripts/copy-to-clipboard';
import { MenuItem } from '@/types/menu';
export async function openReactionImportMenu(ev: MouseEvent, reaction: string): Promise<void> {
if (!reaction) return;
const host = reaction.match(/(?<=@).*\.*(?=:)/g)?.[0];
const name = reaction.match(/(?<=:).*(?=@.*\.*(?=:))/g)?.[0];
const isLocal = (host === null || host === '.');
const isCustom = reaction.startsWith(':');
const getEmojiObject = (emojiId): Promise<Record<string, any> | null> => new Promise<Record<string, any> | null>(async resolve => {
const sinceId = await os.api('admin/emoji/list', {
limit: 1,
untilId: emojiId.id,
});
if (!sinceId || !sinceId[0] || !sinceId[0].id) {
resolve(null);
return;
}
const id = await os.api('admin/emoji/list', {
limit: 1,
sinceId: sinceId[0].id,
});
if (!id || !id[0]) {
resolve(null);
return;
}
resolve(id[0]);
});
const getEmojiId = async (): Promise<string | null> => {
if (isLocal) return null;
if (!host || !name) return null;
const resList: Record<string, any>[] = await os.api('admin/emoji/list-remote', {
host,
query: name,
limit: 100,
});
const emojiId = await resList.find(emoji => emoji.name === name && emoji.host === host)?.id;
return emojiId;
};
const importEmoji = async (): Promise<void> => {
const emojiId = await getEmojiId();
if (!await emojiId) return;
os.api('admin/emoji/copy', {
emojiId: emojiId,
}).then(async emoji => os.popup(defineAsyncComponent(() => import('@/pages/admin/emoji-edit-dialog.vue')), {
emoji: await getEmojiObject(emoji),
}));
};
const menuItems: MenuItem[] = [{
type: 'label',
text: reaction,
}, {
type: 'button',
icon: 'fas fa-copy',
text: i18n.ts.copy,
action: (): void => {
copyToClipboard(isCustom ? `:${name}:` : reaction);
},
}];
const emojiId = await getEmojiId() ? await getEmojiId() : reaction;
if (
isCustom &&
emojiId &&
($i?.isAdmin || $i?.isModerator) &&
!isLocal
) {
menuItems.push({
type: 'button',
icon: 'fas fa-download',
text: i18n.ts.import,
action: async () => {
const duplication: boolean = await os.api('meta').then(meta => {
const emojis = meta.emojis;
return emojis.some((emoji) => {
return (emoji.name === name);
});
});
if (await duplication) {
os.confirm({
type: 'warning',
text: i18n.ts.duplicateEmoji,
}).then(res => {
if (res.canceled) return;
importEmoji();
});
} else {
importEmoji();
}
},
});
}
os.contextMenu(menuItems, ev);
}

View File

@ -0,0 +1,41 @@
import { ref } from 'vue';
import { DriveFile } from 'misskey-js/built/entities';
import { defaultStore } from '@/store';
import { uploadFile } from '@/scripts/upload';
function select(src: any, label: string | null, multiple: boolean): Promise<DriveFile | DriveFile[]> {
return new Promise((res, rej) => {
const keepOriginal = ref(defaultStore.state.keepOriginalUploading);
const chooseFileFromPc = () => {
const input = document.createElement('input');
input.type = 'file';
input.accept = 'video/mp4';
input.multiple = multiple;
input.onchange = () => {
const promises = Array.from(input.files).map(file => uploadFile(file, defaultStore.state.uploadFolder, undefined, keepOriginal.value));
Promise.all(promises).then(driveFiles => {
res(multiple ? driveFiles : driveFiles[0]);
}).catch(err => {
// アップロードのエラーは uploadFile 内でハンドリングされているためアラートダイアログを出したりはしてはいけない
});
// 一応廃棄
(window as any).__misskey_input_ref__ = null;
};
// https://qiita.com/fukasawah/items/b9dc732d95d99551013d
// iOS Safari で正常に動かす為のおまじない
(window as any).__misskey_input_ref__ = input;
input.click();
};
chooseFileFromPc();
});
}
export function selectFileEnc(src: any, label: string | null = null): Promise<DriveFile> {
return select(src, label, false) as Promise<DriveFile>;
}

View File

@ -6,6 +6,7 @@ import { i18n } from '@/i18n';
import { defaultStore } from '@/store'; import { defaultStore } from '@/store';
import { uploadFile } from '@/scripts/upload'; import { uploadFile } from '@/scripts/upload';
function select(src: any, label: string | null, multiple: boolean): Promise<DriveFile | DriveFile[]> { function select(src: any, label: string | null, multiple: boolean): Promise<DriveFile | DriveFile[]> {
return new Promise((res, rej) => { return new Promise((res, rej) => {
const keepOriginal = ref(defaultStore.state.keepOriginalUploading); const keepOriginal = ref(defaultStore.state.keepOriginalUploading);

View File

@ -18,6 +18,8 @@ export const themeProps = Object.keys(lightTheme.props).filter(key => !key.start
export const getBuiltinThemes = () => Promise.all( export const getBuiltinThemes = () => Promise.all(
[ [
'l-simkey',
'l-simkey-noglass',
'l-light', 'l-light',
'l-coffee', 'l-coffee',
'l-apricot', 'l-apricot',
@ -27,6 +29,8 @@ export const getBuiltinThemes = () => Promise.all(
'l-sushi', 'l-sushi',
'l-u0', 'l-u0',
'd-simkey',
'd-simkey-noglass',
'd-dark', 'd-dark',
'd-persimmon', 'd-persimmon',
'd-astro', 'd-astro',

View File

@ -25,7 +25,7 @@ export const defaultStore = markRaw(new Storage('base', {
}, },
rememberNoteVisibility: { rememberNoteVisibility: {
where: 'account', where: 'account',
default: false, default: true
}, },
defaultNoteVisibility: { defaultNoteVisibility: {
where: 'account', where: 'account',
@ -157,7 +157,7 @@ export const defaultStore = markRaw(new Storage('base', {
}, },
disableDrawer: { disableDrawer: {
where: 'device', where: 'device',
default: false, default: true
}, },
useBlurEffectForModal: { useBlurEffectForModal: {
where: 'device', where: 'device',
@ -177,7 +177,7 @@ export const defaultStore = markRaw(new Storage('base', {
}, },
useReactionPickerForContextMenu: { useReactionPickerForContextMenu: {
where: 'device', where: 'device',
default: false, default: true
}, },
showGapBetweenNotesInTimeline: { showGapBetweenNotesInTimeline: {
where: 'device', where: 'device',
@ -197,15 +197,15 @@ export const defaultStore = markRaw(new Storage('base', {
}, },
reactionPickerWidth: { reactionPickerWidth: {
where: 'device', where: 'device',
default: 1, default: 3
}, },
reactionPickerHeight: { reactionPickerHeight: {
where: 'device', where: 'device',
default: 2, default: 4
}, },
reactionPickerUseDrawerForMobile: { reactionPickerUseDrawerForMobile: {
where: 'device', where: 'device',
default: true, default: false,
}, },
recentlyUsedEmojis: { recentlyUsedEmojis: {
where: 'device', where: 'device',
@ -269,8 +269,8 @@ type Plugin = {
/** /**
* () * ()
*/ */
import lightTheme from '@/themes/l-light.json5'; import lightTheme from '@/themes/l-simkey.json5';
import darkTheme from '@/themes/d-green-lime.json5'; import darkTheme from '@/themes/d-simkey.json5';
export class ColdDeviceStorage { export class ColdDeviceStorage {
public static default = { public static default = {

View File

@ -17,7 +17,7 @@
::selection { ::selection {
color: #fff; color: #fff;
background-color: var(--accent); background-color: var(--accent);
} }
html { html {
touch-action: manipulation; touch-action: manipulation;
@ -250,9 +250,11 @@ hr {
} }
._panel { ._panel {
background: var(--panel); // background: var(--panel);
border-radius: var(--radius); border-radius: var(--radius);
overflow: clip; overflow: clip;
backdrop-filter: var(--blur, blur(8px));
-webkit-backdrop-filter: var(--blur, blur(8px));
} }
._block { ._block {
@ -334,6 +336,8 @@ hr {
background: var(--popup); background: var(--popup);
border-radius: var(--radius); border-radius: var(--radius);
contain: content; contain: content;
-webkit-backdrop-filter: var(--blur, blur(15px));
backdrop-filter: var(--blur, blur(15px));
} }
// TODO: 廃止 // TODO: 廃止
@ -341,11 +345,11 @@ hr {
._section:not(:empty) { ._section:not(:empty) {
box-sizing: border-box; box-sizing: border-box;
padding: var(--root-margin, 32px); padding: var(--root-margin, 32px);
@media (max-width: 500px) { @media (max-width: 500px) {
--root-margin: 10px; --root-margin: 10px;
} }
& + ._section:not(:empty) { & + ._section:not(:empty) {
border-top: solid 0.5px var(--divider); border-top: solid 0.5px var(--divider);
} }

View File

@ -0,0 +1,78 @@
{
id: '971cb22e-c9e6-4a7d-92f9-87cc0ddda561',
base: 'dark',
name: 'simkey Astro Dark-NoGlass',
author: 'sim1222',
props: {
bg: 'rgba(35, 33, 37, 1)',
fg: '#efdab9',
cwBg: '#687390',
cwFg: '#393f4f',
link: '#78b0a0',
warn: '#ecb637',
badge: '#31b1ce',
error: '#ec4137',
focus: ':alpha<0.3<@accent',
navBg: '@panel',
navFg: '@fg',
panel: 'rgba(42, 39, 43, 1)',
accent: '#81c08b',
header: ':alpha<0.7<@bg',
infoBg: '#253142',
infoFg: '#fff',
renote: '#659CC8',
shadow: 'rgba(0, 0, 0, 0.3)',
divider: 'rgba(255, 255, 255, 0.1)',
hashtag: '#ff9156',
mention: '#ffd152',
modalBg: 'rgba(0, 0, 0, 0.5)',
success: '#86b300',
buttonBg: 'rgba(255, 255, 255, 0.05)',
acrylicBg: ':alpha<0.5<@bg',
cwHoverBg: '#707b97',
indicator: '@accent',
mentionMe: '#fb5d38',
messageBg: '@bg',
navActive: '@accent',
infoWarnBg: '#42321c',
infoWarnFg: '#ffbd3e',
navHoverFg: ':lighten<17<@fg',
dateLabelFg: '@fg',
inputBorder: 'rgba(255, 255, 255, 0.1)',
inputBorderHover: 'rgba(255, 255, 255, 0.2)',
panelBorder: '" solid 1px var(--divider)',
accentDarken: ':darken<10<@accent',
acrylicPanel: ':alpha<0.5<@panel',
navIndicator: '@accent',
accentLighten: ':lighten<10<@accent',
buttonHoverBg: 'rgba(255, 255, 255, 0.1)',
buttonGradateA: '@accent',
buttonGradateB: ':hue<-20<@accent',
driveFolderBg: ':alpha<0.3<@accent',
fgHighlighted: ':lighten<3<@fg',
panelHeaderBg: ':lighten<3<@panel',
panelHeaderFg: '@fg',
htmlThemeColor: '@bg',
panelHighlight: ':lighten<3<@panel',
listItemHoverBg: 'rgba(255, 255, 255, 0.03)',
scrollbarHandle: 'rgba(255, 255, 255, 0.2)',
wallpaperOverlay: 'rgba(0, 0, 0, 0.5)',
panelHeaderDivider: 'rgba(0, 0, 0, 0)',
scrollbarHandleHover: 'rgba(255, 255, 255, 0.4)',
X2: ':darken<2<@panel',
X3: 'rgba(255, 255, 255, 0.05)',
X4: 'rgba(255, 255, 255, 0.1)',
X5: 'rgba(255, 255, 255, 0.05)',
X6: 'rgba(255, 255, 255, 0.15)',
X7: 'rgba(255, 255, 255, 0.05)',
X8: ':lighten<5<@accent',
X9: ':darken<5<@accent',
X10: ':alpha<0.4<@accent',
X11: 'rgba(0, 0, 0, 0.3)',
X12: 'rgba(255, 255, 255, 0.1)',
X13: 'rgba(255, 255, 255, 0.15)',
X14: ':alpha<0.5<@navBg',
X15: ':alpha<0<@panel',
X16: ':alpha<0.7<@panel',
},
}

View File

@ -0,0 +1,78 @@
{
id: '1c9f3491-d0d3-4da8-a542-a2d0358d23bc',
base: 'dark',
name: 'simkey Astro Dark',
author: 'sim1222',
props: {
bg: 'rgba(35, 33, 37, 0.5)',
fg: '#efdab9',
cwBg: '#687390',
cwFg: '#393f4f',
link: '#78b0a0',
warn: '#ecb637',
badge: '#31b1ce',
error: '#ec4137',
focus: ':alpha<0.3<@accent',
navBg: '@panel',
navFg: '@fg',
panel: 'rgba(42, 39, 43, 0.5)',
accent: '#81c08b',
header: ':alpha<0.7<@bg',
infoBg: '#253142',
infoFg: '#fff',
renote: '#659CC8',
shadow: 'rgba(0, 0, 0, 0.3)',
divider: 'rgba(255, 255, 255, 0.1)',
hashtag: '#ff9156',
mention: '#ffd152',
modalBg: 'rgba(0, 0, 0, 0.5)',
success: '#86b300',
buttonBg: 'rgba(255, 255, 255, 0.05)',
acrylicBg: ':alpha<0.5<@bg',
cwHoverBg: '#707b97',
indicator: '@accent',
mentionMe: '#fb5d38',
messageBg: '@bg',
navActive: '@accent',
infoWarnBg: '#42321c',
infoWarnFg: '#ffbd3e',
navHoverFg: ':lighten<17<@fg',
dateLabelFg: '@fg',
inputBorder: 'rgba(255, 255, 255, 0.1)',
inputBorderHover: 'rgba(255, 255, 255, 0.2)',
panelBorder: '" solid 1px var(--divider)',
accentDarken: ':darken<10<@accent',
acrylicPanel: ':alpha<0.5<@panel',
navIndicator: '@accent',
accentLighten: ':lighten<10<@accent',
buttonHoverBg: 'rgba(255, 255, 255, 0.1)',
buttonGradateA: '@accent',
buttonGradateB: ':hue<-20<@accent',
driveFolderBg: ':alpha<0.3<@accent',
fgHighlighted: ':lighten<3<@fg',
panelHeaderBg: ':lighten<3<@panel',
panelHeaderFg: '@fg',
htmlThemeColor: '@bg',
panelHighlight: ':lighten<3<@panel',
listItemHoverBg: 'rgba(255, 255, 255, 0.03)',
scrollbarHandle: 'rgba(255, 255, 255, 0.2)',
wallpaperOverlay: 'rgba(0, 0, 0, 0.5)',
panelHeaderDivider: 'rgba(0, 0, 0, 0)',
scrollbarHandleHover: 'rgba(255, 255, 255, 0.4)',
X2: ':darken<2<@panel',
X3: 'rgba(255, 255, 255, 0.05)',
X4: 'rgba(255, 255, 255, 0.1)',
X5: 'rgba(255, 255, 255, 0.05)',
X6: 'rgba(255, 255, 255, 0.15)',
X7: 'rgba(255, 255, 255, 0.05)',
X8: ':lighten<5<@accent',
X9: ':darken<5<@accent',
X10: ':alpha<0.4<@accent',
X11: 'rgba(0, 0, 0, 0.3)',
X12: 'rgba(255, 255, 255, 0.1)',
X13: 'rgba(255, 255, 255, 0.15)',
X14: ':alpha<0.5<@navBg',
X15: ':alpha<0<@panel',
X16: ':alpha<0.7<@panel',
},
}

View File

@ -0,0 +1,50 @@
{
id: '2626d839-8579-4b44-80a4-39af123ff74a',
name: 'simkey Vivid Light-NoGlass',
author: 'sim1222',
base: 'light',
props: {
bg: 'rgba(250, 250, 250, 1)',
fg: '#444',
cwBg: '#b1b9c1',
cwFg: '#fff',
link: '#ff9400',
panel: 'rgba(255, 255, 255, 1)',
accent: '#81c08b',
header: ':alpha<0.7<@panel',
infoBg: '#e5f5ff',
infoFg: '#72818a',
renote: '#659CC8',
shadow: 'rgba(0, 0, 0, 0.1)',
divider: 'rgba(0, 0, 0, 0.08)',
hashtag: '#92d400',
mention: '@accent',
modalBg: 'rgba(0, 0, 0, 0.3)',
buttonBg: 'rgba(0, 0, 0, 0.05)',
cwHoverBg: '#bbc4ce',
mentionMe: '@mention',
infoWarnBg: '#fff0db',
infoWarnFg: '#8f6e31',
navHoverFg: ':darken<17<@fg',
inputBorder: 'rgba(0, 0, 0, 0.1)',
inputBorderHover: 'rgba(0, 0, 0, 0.2)',
buttonHoverBg: 'rgba(0, 0, 0, 0.1)',
fgHighlighted: ':darken<3<@fg',
fgTransparent: ':alpha<0.5<@fg',
panelHighlight: ':darken<3<@panel',
listItemHoverBg: 'rgba(0, 0, 0, 0.03)',
scrollbarHandle: 'rgba(0, 0, 0, 0.2)',
wallpaperOverlay: 'rgba(255, 255, 255, 0.5)',
fgTransparentWeak: ':alpha<0.75<@fg',
panelHeaderDivider: '@divider',
scrollbarHandleHover: 'rgba(0, 0, 0, 0.4)',
X3: 'rgba(0, 0, 0, 0.05)',
X4: 'rgba(0, 0, 0, 0.1)',
X5: 'rgba(0, 0, 0, 0.05)',
X6: 'rgba(0, 0, 0, 0.25)',
X7: 'rgba(0, 0, 0, 0.05)',
X11: 'rgba(0, 0, 0, 0.1)',
X12: 'rgba(0, 0, 0, 0.1)',
X13: 'rgba(0, 0, 0, 0.15)',
},
}

View File

@ -0,0 +1,50 @@
{
id: '80b5d38c-c281-403b-9fc5-3bb4f1dc19b8',
name: 'simkey Vivid Light',
author: 'sim1222',
base: 'light',
props: {
bg: 'rgba(250, 250, 250, 0.7)',
fg: '#444',
cwBg: '#b1b9c1',
cwFg: '#fff',
link: '#ff9400',
panel: 'rgba(255, 255, 255, 0.6)',
accent: '#81c08b',
header: ':alpha<0.7<@panel',
infoBg: '#e5f5ff',
infoFg: '#72818a',
renote: '#659CC8',
shadow: 'rgba(0, 0, 0, 0.1)',
divider: 'rgba(0, 0, 0, 0.08)',
hashtag: '#92d400',
mention: '@accent',
modalBg: 'rgba(0, 0, 0, 0.3)',
buttonBg: 'rgba(0, 0, 0, 0.05)',
cwHoverBg: '#bbc4ce',
mentionMe: '@mention',
infoWarnBg: '#fff0db',
infoWarnFg: '#8f6e31',
navHoverFg: ':darken<17<@fg',
inputBorder: 'rgba(0, 0, 0, 0.1)',
inputBorderHover: 'rgba(0, 0, 0, 0.2)',
buttonHoverBg: 'rgba(0, 0, 0, 0.1)',
fgHighlighted: ':darken<3<@fg',
fgTransparent: ':alpha<0.5<@fg',
panelHighlight: ':darken<3<@panel',
listItemHoverBg: 'rgba(0, 0, 0, 0.03)',
scrollbarHandle: 'rgba(0, 0, 0, 0.2)',
wallpaperOverlay: 'rgba(255, 255, 255, 0.5)',
fgTransparentWeak: ':alpha<0.75<@fg',
panelHeaderDivider: '@divider',
scrollbarHandleHover: 'rgba(0, 0, 0, 0.4)',
X3: 'rgba(0, 0, 0, 0.05)',
X4: 'rgba(0, 0, 0, 0.1)',
X5: 'rgba(0, 0, 0, 0.05)',
X6: 'rgba(0, 0, 0, 0.25)',
X7: 'rgba(0, 0, 0, 0.05)',
X11: 'rgba(0, 0, 0, 0.1)',
X12: 'rgba(0, 0, 0, 0.1)',
X13: 'rgba(0, 0, 0, 0.15)',
},
}

View File

@ -0,0 +1,123 @@
<template>
<component :is="popup.component"
v-for="popup in popups"
:key="popup.id"
v-bind="popup.props"
v-on="popup.events"
/>
<XUpload v-if="uploads.length > 0"/>
<XStreamIndicator/>
<div v-if="pendingApiRequestsCount > 0" id="wait"></div>
<div v-if="dev" id="devTicker"><span>DEV BUILD</span></div>
</template>
<script lang="ts">
import { defineAsyncComponent, defineComponent } from 'vue';
import { popup, popups, pendingApiRequestsCount } from '@/os';
import { uploads } from '@/scripts/upload';
import * as sound from '@/scripts/sound';
import { $i } from '@/account';
import { swInject } from './sw-inject';
import { stream } from '@/stream';
export default defineComponent({
components: {
XStreamIndicator: defineAsyncComponent(() => import('./stream-indicator.vue')),
XUpload: defineAsyncComponent(() => import('./upload.vue')),
},
setup() {
const onNotification = notification => {
if ($i.mutingNotificationTypes.includes(notification.type)) return;
if (document.visibilityState === 'visible') {
stream.send('readNotification', {
id: notification.id
});
popup(defineAsyncComponent(() => import('@/components/MkNotificationToast.vue')), {
notification
}, {}, 'closed');
}
sound.play('notification');
};
if ($i) {
const connection = stream.useChannel('main', null, 'UI');
connection.on('notification', onNotification);
//#region Listen message from SW
if ('serviceWorker' in navigator) {
swInject();
}
}
return {
uploads,
popups,
pendingApiRequestsCount,
dev: _DEV_,
};
},
});
</script>
<style lang="scss">
@keyframes dev-ticker-blink {
0% { opacity: 1; }
50% { opacity: 0; }
100% { opacity: 1; }
}
@keyframes progress-spinner {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
#wait {
display: block;
position: fixed;
z-index: 4000000;
top: 15px;
right: 15px;
&:before {
content: "";
display: block;
width: 18px;
height: 18px;
box-sizing: border-box;
border: solid 2px transparent;
border-top-color: var(--accent);
border-left-color: var(--accent);
border-radius: 50%;
animation: progress-spinner 400ms linear infinite;
}
}
#devTicker {
position: fixed;
top: 0;
left: 0;
z-index: 2147483647;
color: #ff0;
background: rgba(0, 0, 0, 0.5);
padding: 4px 5px;
font-size: 14px;
pointer-events: none;
user-select: none;
> span {
animation: dev-ticker-blink 2s infinite;
}
}
</style>

View File

@ -0,0 +1,286 @@
<template>
<div class="kmwsukvl">
<div class="body">
<div class="top">
<div class="banner" :style="{ backgroundImage: `url(${ $instance.bannerUrl })` }"></div>
<button v-click-anime class="item _button instance" @click="openInstanceMenu">
<img :src="$instance.iconUrl || $instance.faviconUrl || '/favicon.ico'" alt="" class="icon"/>
</button>
</div>
<div class="middle">
<MkA v-click-anime class="item index" active-class="active" to="/" exact>
<i class="icon fas fa-home fa-fw"></i><span class="text">{{ $ts.timeline }}</span>
</MkA>
<template v-for="item in menu">
<div v-if="item === '-'" class="divider"></div>
<component :is="navbarItemDef[item].to ? 'MkA' : 'button'" v-else-if="navbarItemDef[item] && (navbarItemDef[item].show !== false)" v-click-anime class="item _button" :class="[item, { active: navbarItemDef[item].active }]" active-class="active" :to="navbarItemDef[item].to" v-on="navbarItemDef[item].action ? { click: navbarItemDef[item].action } : {}">
<i class="icon fa-fw" :class="navbarItemDef[item].icon"></i><span class="text">{{ $ts[navbarItemDef[item].title] }}</span>
<span v-if="navbarItemDef[item].indicated" class="indicator"><i class="icon fas fa-circle"></i></span>
</component>
</template>
<div class="divider"></div>
<MkA v-if="$i.isAdmin || $i.isModerator" v-click-anime class="item" active-class="active" to="/admin">
<i class="icon fas fa-door-open fa-fw"></i><span class="text">{{ $ts.controlPanel }}</span>
</MkA>
<button v-click-anime class="item _button" @click="more">
<i class="icon fa fa-ellipsis-h fa-fw"></i><span class="text">{{ $ts.more }}</span>
<span v-if="otherMenuItemIndicated" class="indicator"><i class="icon fas fa-circle"></i></span>
</button>
<MkA v-click-anime class="item" active-class="active" to="/settings">
<i class="icon fas fa-cog fa-fw"></i><span class="text">{{ $ts.settings }}</span>
</MkA>
</div>
<div class="bottom">
<button class="item _button post" data-cy-open-post-form @click="os.post">
<i class="icon fas fa-pencil-alt fa-fw"></i><span class="text">{{ $ts.note }}</span>
</button>
<button v-click-anime class="item _button account" @click="openAccountMenu">
<MkAvatar :user="$i" class="avatar"/><MkAcct class="text" :user="$i"/>
</button>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { computed, defineAsyncComponent, defineComponent, ref, toRef, watch } from 'vue';
import { host } from '@/config';
import { search } from '@/scripts/search';
import * as os from '@/os';
import { navbarItemDef } from '@/navbar';
import { openAccountMenu as openAccountMenu_ } from '@/account';
import { defaultStore } from '@/store';
import { instance } from '@/instance';
import { i18n } from '@/i18n';
const menu = toRef(defaultStore.state, 'menu');
const otherMenuItemIndicated = computed(() => {
for (const def in navbarItemDef) {
if (menu.value.includes(def)) continue;
if (navbarItemDef[def].indicated) return true;
}
return false;
});
function openAccountMenu(ev: MouseEvent) {
openAccountMenu_({
withExtraOperation: true,
}, ev);
}
function openInstanceMenu(ev: MouseEvent) {
os.popupMenu([{
text: instance.name ?? host,
type: 'label',
}, {
type: 'link',
text: i18n.ts.instanceInfo,
icon: 'fas fa-info-circle',
to: '/about',
}, {
type: 'link',
text: i18n.ts.customEmojis,
icon: 'fas fa-laugh',
to: '/about#emojis',
}, {
type: 'link',
text: i18n.ts.federation,
icon: 'fas fa-globe',
to: '/about#federation',
}], ev.currentTarget ?? ev.target, {
align: 'left',
});
}
function more() {
os.popup(defineAsyncComponent(() => import('@/components/MkLaunchPad.vue')), {}, {
}, 'closed');
}
</script>
<style lang="scss" scoped>
.kmwsukvl {
backdrop-filter: var(--blur, blur(8px));
-webkit-backdrop-filter: var(--blur, blur(8px));
> .body {
display: flex;
flex-direction: column;
> .top {
position: sticky;
top: 0;
z-index: 1;
padding: 20px 0;
background: var(--X14);
-webkit-backdrop-filter: var(--blur, blur(8px));
backdrop-filter: var(--blur, blur(8px));
> .banner {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-size: cover;
background-position: center center;
-webkit-mask-image: linear-gradient(0deg, rgba(0,0,0,0) 15%, rgba(0,0,0,0.75) 100%);
mask-image: linear-gradient(0deg, rgba(0,0,0,0) 15%, rgba(0,0,0,0.75) 100%);
}
> .instance {
position: relative;
display: block;
text-align: center;
width: 100%;
> .icon {
display: inline-block;
width: 38px;
aspect-ratio: 1;
}
}
}
> .bottom {
position: sticky;
bottom: 0;
padding: 20px 0;
background: var(--X14);
-webkit-backdrop-filter: var(--blur, blur(8px));
backdrop-filter: var(--blur, blur(8px));
> .post {
position: relative;
display: block;
width: 100%;
height: 40px;
color: var(--fgOnAccent);
font-weight: bold;
text-align: left;
&:before {
content: "";
display: block;
width: calc(100% - 38px);
height: 100%;
margin: auto;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border-radius: 999px;
background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB));
}
&:hover, &.active {
&:before {
background: var(--accentLighten);
}
}
> .icon {
position: relative;
margin-left: 30px;
margin-right: 8px;
width: 32px;
}
> .text {
position: relative;
}
}
> .account {
position: relative;
display: flex;
align-items: center;
padding-left: 30px;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
width: 100%;
text-align: left;
box-sizing: border-box;
margin-top: 16px;
> .avatar {
position: relative;
width: 32px;
aspect-ratio: 1;
margin-right: 8px;
}
}
}
> .middle {
flex: 1;
> .divider {
margin: 16px 16px;
border-top: solid 0.5px var(--divider);
}
> .item {
position: relative;
display: block;
padding-left: 24px;
line-height: 2.85rem;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
width: 100%;
text-align: left;
box-sizing: border-box;
color: var(--navFg);
> .icon {
position: relative;
width: 32px;
margin-right: 8px;
}
> .indicator {
position: absolute;
top: 0;
left: 20px;
color: var(--navIndicator);
font-size: 8px;
animation: blink 1s infinite;
}
> .text {
position: relative;
font-size: 0.9em;
}
&:hover {
text-decoration: none;
color: var(--navHoverFg);
}
&.active {
color: var(--navActive);
}
&:hover, &.active {
&:before {
content: "";
display: block;
width: calc(100% - 24px);
height: 100%;
margin: auto;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border-radius: 999px;
background: var(--accentedBg);
}
}
}
}
}
}
</style>

View File

@ -0,0 +1,527 @@
<template>
<div class="mvcprjjd" :class="{ iconOnly }">
<div class="body">
<div class="top">
<div class="banner" :style="{ backgroundImage: `url(${ $instance.bannerUrl })` }"></div>
<button v-click-anime v-tooltip.noDelay.right="$instance.name ?? i18n.ts.instance" class="item _button instance" @click="openInstanceMenu">
<img :src="$instance.iconUrl || $instance.faviconUrl || '/favicon.ico'" alt="" class="icon"/>
</button>
</div>
<div class="middle">
<MkA v-click-anime v-tooltip.noDelay.right="i18n.ts.timeline" class="item index" active-class="active" to="/" exact>
<i class="icon fas fa-home fa-fw"></i><span class="text">{{ i18n.ts.timeline }}</span>
</MkA>
<template v-for="item in menu">
<div v-if="item === '-'" class="divider"></div>
<component
:is="navbarItemDef[item].to ? 'MkA' : 'button'"
v-else-if="navbarItemDef[item] && (navbarItemDef[item].show !== false)"
v-click-anime
v-tooltip.noDelay.right="i18n.ts[navbarItemDef[item].title]"
class="item _button"
:class="[item, { active: navbarItemDef[item].active }]"
active-class="active"
:to="navbarItemDef[item].to"
v-on="navbarItemDef[item].action ? { click: navbarItemDef[item].action } : {}"
>
<i class="icon fa-fw" :class="navbarItemDef[item].icon"></i><span class="text">{{ i18n.ts[navbarItemDef[item].title] }}</span>
<span v-if="navbarItemDef[item].indicated" class="indicator"><i class="icon fas fa-circle"></i></span>
</component>
</template>
<div class="divider"></div>
<MkA v-if="$i.isAdmin || $i.isModerator" v-click-anime v-tooltip.noDelay.right="i18n.ts.controlPanel" class="item" active-class="active" to="/admin">
<i class="icon fas fa-door-open fa-fw"></i><span class="text">{{ i18n.ts.controlPanel }}</span>
</MkA>
<button v-click-anime class="item _button" @click="more">
<i class="icon fa fa-ellipsis-h fa-fw"></i><span class="text">{{ i18n.ts.more }}</span>
<span v-if="otherMenuItemIndicated" class="indicator"><i class="icon fas fa-circle"></i></span>
</button>
<MkA v-click-anime v-tooltip.noDelay.right="i18n.ts.settings" class="item" active-class="active" to="/settings">
<i class="icon fas fa-cog fa-fw"></i><span class="text">{{ i18n.ts.settings }}</span>
</MkA>
</div>
<div class="bottom">
<button v-tooltip.noDelay.right="i18n.ts.note" class="item _button post" data-cy-open-post-form @click="os.post">
<i class="icon fas fa-pencil-alt fa-fw"></i><span class="text">{{ i18n.ts.note }}</span>
</button>
<button v-click-anime v-tooltip.noDelay.right="`${i18n.ts.account}: @${$i.username}`" class="item _button account" @click="openAccountMenu">
<MkAvatar :user="$i" class="avatar"/><MkAcct class="text" :user="$i"/>
</button>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { computed, defineAsyncComponent, ref, watch } from 'vue';
import * as os from '@/os';
import { navbarItemDef } from '@/navbar';
import { $i, openAccountMenu as openAccountMenu_ } from '@/account';
import { defaultStore } from '@/store';
import { i18n } from '@/i18n';
import { instance } from '@/instance';
import { host } from '@/config';
const iconOnly = ref(false);
const menu = computed(() => defaultStore.state.menu);
const otherMenuItemIndicated = computed(() => {
for (const def in navbarItemDef) {
if (menu.value.includes(def)) continue;
if (navbarItemDef[def].indicated) return true;
}
return false;
});
const calcViewState = () => {
iconOnly.value = (window.innerWidth <= 1279) || (defaultStore.state.menuDisplay === 'sideIcon');
};
calcViewState();
window.addEventListener('resize', calcViewState);
watch(defaultStore.reactiveState.menuDisplay, () => {
calcViewState();
});
function openAccountMenu(ev: MouseEvent) {
openAccountMenu_({
withExtraOperation: true,
}, ev);
}
function openInstanceMenu(ev: MouseEvent) {
os.popupMenu([{
text: instance.name ?? host,
type: 'label',
}, {
type: 'link',
text: i18n.ts.instanceInfo,
icon: 'fas fa-info-circle',
to: '/about',
}, {
type: 'link',
text: i18n.ts.customEmojis,
icon: 'fas fa-laugh',
to: '/about#emojis',
}, {
type: 'link',
text: i18n.ts.federation,
icon: 'fas fa-globe',
to: '/about#federation',
}, null, {
type: 'parent',
text: i18n.ts.help,
icon: 'fas fa-question-circle',
children: [{
type: 'link',
to: '/mfm-cheat-sheet',
text: i18n.ts._mfm.cheatSheet,
icon: 'fas fa-code',
}, {
type: 'link',
to: '/scratchpad',
text: i18n.ts.scratchpad,
icon: 'fas fa-terminal',
}, {
type: 'link',
to: '/api-console',
text: 'API Console',
icon: 'fas fa-terminal',
}, null, {
text: i18n.ts.document,
icon: 'fas fa-question-circle',
action: () => {
window.open('https://misskey-hub.net/help.html', '_blank');
},
}],
}, {
type: 'link',
text: i18n.ts.aboutMisskey,
to: '/about-misskey',
}], ev.currentTarget ?? ev.target, {
align: 'left',
});
}
function more(ev: MouseEvent) {
os.popup(defineAsyncComponent(() => import('@/components/MkLaunchPad.vue')), {
src: ev.currentTarget ?? ev.target,
}, {
}, 'closed');
}
</script>
<style lang="scss" scoped>
.mvcprjjd {
$ui-font-size: 1em; // TODO:
$nav-width: 250px;
$nav-icon-only-width: 86px;
$avatar-size: 32px;
$avatar-margin: 8px;
flex: 0 0 $nav-width;
width: $nav-width;
box-sizing: border-box;
> .body {
position: fixed;
top: 0;
left: 0;
z-index: 1001;
width: $nav-icon-only-width;
// 100vh ... https://css-tricks.com/the-trick-to-viewport-units-on-mobile/
height: calc(var(--vh, 1vh) * 100);
box-sizing: border-box;
overflow: auto;
overflow-x: clip;
background: var(--navBg);
contain: strict;
display: flex;
flex-direction: column;
backdrop-filter: var(--blur, blur(8px));
-webkit-backdrop-filter: var(--blur, blur(8px));
}
&:not(.iconOnly) {
> .body {
width: $nav-width;
> .top {
position: sticky;
top: 0;
z-index: 1;
padding: 20px 0;
background: var(--X14);
-webkit-backdrop-filter: var(--blur, blur(8px));
backdrop-filter: var(--blur, blur(8px));
> .banner {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-size: cover;
background-position: center center;
-webkit-mask-image: linear-gradient(0deg, rgba(0,0,0,0) 15%, rgba(0,0,0,0.75) 100%);
mask-image: linear-gradient(0deg, rgba(0,0,0,0) 15%, rgba(0,0,0,0.75) 100%);
}
> .instance {
position: relative;
display: block;
text-align: center;
width: 100%;
> .icon {
display: inline-block;
width: 38px;
aspect-ratio: 1;
}
}
}
> .bottom {
position: sticky;
bottom: 0;
padding: 20px 0;
background: var(--X14);
-webkit-backdrop-filter: var(--blur, blur(8px));
backdrop-filter: var(--blur, blur(8px));
> .post {
position: relative;
display: block;
width: 100%;
height: 40px;
color: var(--fgOnAccent);
font-weight: bold;
text-align: left;
&:before {
content: "";
display: block;
width: calc(100% - 38px);
height: 100%;
margin: auto;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border-radius: 999px;
background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB));
}
&:hover, &.active {
&:before {
background: var(--accentLighten);
}
}
> .icon {
position: relative;
margin-left: 30px;
margin-right: 8px;
width: 32px;
}
> .text {
position: relative;
}
}
> .account {
position: relative;
display: flex;
align-items: center;
padding-left: 30px;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
width: 100%;
text-align: left;
box-sizing: border-box;
margin-top: 16px;
> .avatar {
position: relative;
width: 32px;
aspect-ratio: 1;
margin-right: 8px;
}
}
}
> .middle {
flex: 1;
> .divider {
margin: 16px 16px;
border-top: solid 0.5px var(--divider);
}
> .item {
position: relative;
display: block;
padding-left: 30px;
line-height: 2.85rem;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
width: 100%;
text-align: left;
box-sizing: border-box;
color: var(--navFg);
> .icon {
position: relative;
width: 32px;
margin-right: 8px;
}
> .indicator {
position: absolute;
top: 0;
left: 20px;
color: var(--navIndicator);
font-size: 8px;
animation: blink 1s infinite;
}
> .text {
position: relative;
font-size: 0.9em;
}
&:hover {
text-decoration: none;
color: var(--navHoverFg);
}
&.active {
color: var(--navActive);
}
&:hover, &.active {
color: var(--accent);
&:before {
content: "";
display: block;
width: calc(100% - 34px);
height: 100%;
margin: auto;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border-radius: 999px;
background: var(--accentedBg);
}
}
}
}
}
}
&.iconOnly {
flex: 0 0 $nav-icon-only-width;
width: $nav-icon-only-width;
> .body {
width: $nav-icon-only-width;
> .top {
position: sticky;
top: 0;
z-index: 1;
padding: 20px 0;
background: var(--X14);
-webkit-backdrop-filter: var(--blur, blur(8px));
backdrop-filter: var(--blur, blur(8px));
> .instance {
display: block;
text-align: center;
width: 100%;
> .icon {
display: inline-block;
width: 30px;
aspect-ratio: 1;
}
}
}
> .bottom {
position: sticky;
bottom: 0;
padding: 20px 0;
background: var(--X14);
-webkit-backdrop-filter: var(--blur, blur(8px));
backdrop-filter: var(--blur, blur(8px));
> .post {
display: block;
position: relative;
width: 100%;
height: 52px;
margin-bottom: 16px;
text-align: center;
&:before {
content: "";
display: block;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: auto;
width: 52px;
aspect-ratio: 1/1;
border-radius: 100%;
background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB));
}
&:hover, &.active {
&:before {
background: var(--accentLighten);
}
}
> .icon {
position: relative;
color: var(--fgOnAccent);
}
> .text {
display: none;
}
}
> .account {
display: block;
text-align: center;
width: 100%;
> .avatar {
display: inline-block;
width: 38px;
aspect-ratio: 1;
}
> .text {
display: none;
}
}
}
> .middle {
flex: 1;
> .divider {
margin: 8px auto;
width: calc(100% - 32px);
border-top: solid 0.5px var(--divider);
}
> .item {
display: block;
position: relative;
padding: 18px 0;
width: 100%;
text-align: center;
> .icon {
display: block;
margin: 0 auto;
opacity: 0.7;
}
> .text {
display: none;
}
> .indicator {
position: absolute;
top: 6px;
left: 24px;
color: var(--navIndicator);
font-size: 8px;
animation: blink 1s infinite;
}
&:hover, &.active {
text-decoration: none;
color: var(--accent);
&:before {
content: "";
display: block;
height: 100%;
aspect-ratio: 1;
margin: auto;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border-radius: 999px;
background: var(--accentedBg);
}
> .icon, > .text {
opacity: 1;
}
}
}
}
}
}
}
</style>

View File

@ -0,0 +1,60 @@
<template>
<div v-if="hasDisconnected && $store.state.serverDisconnectedBehavior === 'quiet'" class="nsbbhtug" @click="resetDisconnected">
<div>{{ $ts.disconnectedFromServer }}</div>
<div class="command">
<button class="_textButton" @click="reload">{{ $ts.reload }}</button>
<button class="_textButton">{{ $ts.doNothing }}</button>
</div>
</div>
</template>
<script lang="ts" setup>
import { onUnmounted } from 'vue';
import { stream } from '@/stream';
let hasDisconnected = $ref(false);
function onDisconnected() {
hasDisconnected = true;
}
function resetDisconnected() {
hasDisconnected = false;
}
function reload() {
location.reload();
}
stream.on('_disconnected_', onDisconnected);
onUnmounted(() => {
stream.off('_disconnected_', onDisconnected);
});
</script>
<style lang="scss" scoped>
.nsbbhtug {
position: fixed;
z-index: 16385;
bottom: 8px;
right: 8px;
margin: 0;
padding: 6px 12px;
font-size: 0.9em;
color: #fff;
background: #000;
opacity: 0.8;
border-radius: 4px;
max-width: 320px;
> .command {
display: flex;
justify-content: space-around;
> button {
padding: 0.7em;
}
}
}
</style>

View File

@ -0,0 +1,35 @@
import { inject } from 'vue';
import { post } from '@/os';
import { $i, login } from '@/account';
import { defaultStore } from '@/store';
import { getAccountFromId } from '@/scripts/get-account-from-id';
import { mainRouter } from '@/router';
export function swInject() {
navigator.serviceWorker.addEventListener('message', ev => {
if (_DEV_) {
console.log('sw msg', ev.data);
}
if (ev.data.type !== 'order') return;
if (ev.data.loginId !== $i?.id) {
return getAccountFromId(ev.data.loginId).then(account => {
if (!account) return;
return login(account.token, ev.data.url);
});
}
switch (ev.data.order) {
case 'post':
return post(ev.data.options);
case 'push':
if (mainRouter.currentRoute.value.path === ev.data.url) {
return window.scroll({ top: 0, behavior: 'smooth' });
}
return mainRouter.push(ev.data.url);
default:
return;
}
});
}

View File

@ -0,0 +1,128 @@
<template>
<div class="mk-uploader _acrylic" :style="{ zIndex }">
<ol v-if="uploads.length > 0">
<li v-for="ctx in uploads" :key="ctx.id">
<div class="img" :style="{ backgroundImage: `url(${ ctx.img })` }"></div>
<div class="top">
<p class="name"><i class="fas fa-spinner fa-pulse"></i>{{ ctx.name }}</p>
<p class="status">
<span v-if="ctx.progressValue === undefined" class="initing">{{ $ts.waiting }}<MkEllipsis/></span>
<span v-if="ctx.progressValue !== undefined" class="kb">{{ String(Math.floor(ctx.progressValue / 1024)).replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1,') }}<i>KB</i> / {{ String(Math.floor(ctx.progressMax / 1024)).replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1,') }}<i>KB</i></span>
<span v-if="ctx.progressValue !== undefined" class="percentage">{{ Math.floor((ctx.progressValue / ctx.progressMax) * 100) }}</span>
</p>
</div>
<progress :value="ctx.progressValue || 0" :max="ctx.progressMax || 0" :class="{ initing: ctx.progressValue === undefined, waiting: ctx.progressValue !== undefined && ctx.progressValue === ctx.progressMax }"></progress>
</li>
</ol>
</div>
</template>
<script lang="ts" setup>
import { } from 'vue';
import * as os from '@/os';
import { uploads } from '@/scripts/upload';
const zIndex = os.claimZIndex('high');
</script>
<style lang="scss" scoped>
.mk-uploader {
position: fixed;
right: 16px;
width: 260px;
top: 32px;
padding: 16px 20px;
pointer-events: none;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
border-radius: 8px;
}
.mk-uploader:empty {
display: none;
}
.mk-uploader > ol {
display: block;
margin: 0;
padding: 0;
list-style: none;
}
.mk-uploader > ol > li {
display: grid;
margin: 8px 0 0 0;
padding: 0;
height: 36px;
width: 100%;
border-top: solid 8px transparent;
grid-template-columns: 36px calc(100% - 44px);
grid-template-rows: 1fr 8px;
column-gap: 8px;
box-sizing: content-box;
}
.mk-uploader > ol > li:first-child {
margin: 0;
box-shadow: none;
border-top: none;
}
.mk-uploader > ol > li > .img {
display: block;
background-size: cover;
background-position: center center;
grid-column: 1/2;
grid-row: 1/3;
}
.mk-uploader > ol > li > .top {
display: flex;
grid-column: 2/3;
grid-row: 1/2;
}
.mk-uploader > ol > li > .top > .name {
display: block;
padding: 0 8px 0 0;
margin: 0;
font-size: 0.8em;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
flex-shrink: 1;
}
.mk-uploader > ol > li > .top > .name > i {
margin-right: 4px;
}
.mk-uploader > ol > li > .top > .status {
display: block;
margin: 0 0 0 auto;
padding: 0;
font-size: 0.8em;
flex-shrink: 0;
}
.mk-uploader > ol > li > .top > .status > .initing {
}
.mk-uploader > ol > li > .top > .status > .kb {
}
.mk-uploader > ol > li > .top > .status > .percentage {
display: inline-block;
width: 48px;
text-align: right;
}
.mk-uploader > ol > li > .top > .status > .percentage:after {
content: '%';
}
.mk-uploader > ol > li > progress {
display: block;
background: transparent;
border: none;
border-radius: 4px;
overflow: hidden;
grid-column: 2/3;
grid-row: 2/3;
z-index: 2;
width: 100%;
height: 8px;
}
.mk-uploader > ol > li > progress::-webkit-progress-value {
background: var(--accent);
}
.mk-uploader > ol > li > progress::-webkit-progress-bar {
//background: var(--accentAlpha01);
background: transparent;
}
</style>

View File

@ -0,0 +1,305 @@
<template>
<div class="mk-deck" :class="[{ isMobile }, `${deckStore.reactiveState.columnAlign.value}`]" :style="{ '--deckMargin': '12' + 'px' }"
@contextmenu.self.prevent="onContextmenu"
>
<XSidebar v-if="!isMobile"/>
<template v-for="ids in layout">
<!-- sectionを利用しているのはdeck.vue側でcolumnに対してfirst-of-typeを効かせるため -->
<section
v-if="ids.length > 1"
class="folder column"
:style="columns.filter(c => ids.includes(c.id)).some(c => c.flexible) ? { flex: 1, minWidth: '350px' } : { width: Math.max(...columns.filter(c => ids.includes(c.id)).map(c => c.width)) + 'px' }"
>
<DeckColumnCore v-for="id in ids" :ref="id" :key="id" :column="columns.find(c => c.id === id)" :is-stacked="true" @parent-focus="moveFocus(id, $event)"/>
</section>
<DeckColumnCore
v-else
:ref="ids[0]"
:key="ids[0]"
class="column"
:column="columns.find(c => c.id === ids[0])"
:is-stacked="false"
:style="columns.find(c => c.id === ids[0])!.flexible ? { flex: 1, minWidth: '350px' } : { width: columns.find(c => c.id === ids[0])!.width + 'px' }"
@parent-focus="moveFocus(ids[0], $event)"
/>
</template>
<div v-if="isMobile" class="buttons">
<button class="button nav _button" @click="drawerMenuShowing = true"><i class="fas fa-bars"></i><span v-if="menuIndicated" class="indicator"><i class="fas fa-circle"></i></span></button>
<button class="button home _button" @click="mainRouter.push('/')"><i class="fas fa-home"></i></button>
<button class="button notifications _button" @click="mainRouter.push('/my/notifications')"><i class="fas fa-bell"></i><span v-if="$i?.hasUnreadNotification" class="indicator"><i class="fas fa-circle"></i></span></button>
<button class="button post _button" @click="os.post()"><i class="fas fa-pencil-alt"></i></button>
</div>
<transition :name="$store.state.animation ? 'menu-back' : ''">
<div
v-if="drawerMenuShowing"
class="menu-back _modalBg"
@click="drawerMenuShowing = false"
@touchstart.passive="drawerMenuShowing = false"
></div>
</transition>
<transition :name="$store.state.animation ? 'menu' : ''">
<XDrawerMenu v-if="drawerMenuShowing" class="menu"/>
</transition>
<XCommon/>
</div>
</template>
<script lang="ts" setup>
import { computed, defineAsyncComponent, onMounted, provide, ref, watch } from 'vue';
import { v4 as uuid } from 'uuid';
import XCommon from './_common_old/common.vue';
import { deckStore, addColumn as addColumnToStore, loadDeck, getProfiles, deleteProfile as deleteProfile_ } from './deckold/deck-store';
import DeckColumnCore from '@/ui/deckold/column-core.vue';
import XSidebar from '@/ui/_common_old/sidebar.vue';
import XDrawerMenu from '@/ui/_common_old/sidebar-for-mobile.vue';
import MkButton from '@/components/ui/button.vue';
import { getScrollContainer } from '@/scripts/scroll';
import * as os from '@/os';
import { navbarItemDef } from '@/navbar';
import { $i } from '@/account';
import { i18n } from '@/i18n';
import { mainRouter } from '@/router';
import { unisonReload } from '@/scripts/unison-reload';
mainRouter.navHook = (path): boolean => {
const noMainColumn = !deckStore.state.columns.some(x => x.type === 'main');
if (deckStore.state.navWindow || noMainColumn) {
os.pageWindow(path);
return true;
}
return false;
};
const isMobile = ref(window.innerWidth <= 500);
window.addEventListener('resize', () => {
isMobile.value = window.innerWidth <= 500;
});
const drawerMenuShowing = ref(false);
const route = 'TODO';
watch(route, () => {
drawerMenuShowing.value = false;
});
const columns = deckStore.reactiveState.columns;
const layout = deckStore.reactiveState.layout;
const menuIndicated = computed(() => {
if ($i == null) return false;
for (const def in navbarItemDef) {
if (navbarItemDef[def].indicated) return true;
}
return false;
});
const addColumn = async (ev) => {
const columns = [
'main',
'widgets',
'notifications',
'tl',
'antenna',
'list',
'mentions',
'direct',
];
const { canceled, result: column } = await os.select({
title: i18n.ts._deck.addColumn,
items: columns.map(column => ({
value: column, text: i18n.t('_deck._columns.' + column)
}))
});
if (canceled) return;
addColumnToStore({
type: column,
id: uuid(),
name: i18n.t('_deck._columns.' + column),
width: 330,
});
};
const onContextmenu = (ev) => {
os.contextMenu([{
text: i18n.ts._deck.addColumn,
action: addColumn,
}], ev);
};
provide('shouldSpacerMin', true);
if (deckStore.state.navWindow) {
provide('navHook', (url) => {
os.pageWindow(url);
});
}
document.documentElement.style.overflowY = 'hidden';
document.documentElement.style.scrollBehavior = 'auto';
window.addEventListener('wheel', (ev) => {
if (getScrollContainer(ev.target as HTMLElement) == null && ev.deltaX === 0) {
document.documentElement.scrollLeft += ev.deltaY;
}
});
loadDeck();
function moveFocus(id: string, direction: 'up' | 'down' | 'left' | 'right') {
// TODO??
}
</script>
<style lang="scss" scoped>
.menu-enter-active,
.menu-leave-active {
opacity: 1;
transform: translateX(0);
transition: transform 300ms cubic-bezier(0.23, 1, 0.32, 1), opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
}
.menu-enter-from,
.menu-leave-active {
opacity: 0;
transform: translateX(-240px);
}
.menu-back-enter-active,
.menu-back-leave-active {
opacity: 1;
transition: opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
}
.menu-back-enter-from,
.menu-back-leave-active {
opacity: 0;
}
.mk-deck {
$nav-hide-threshold: 650px; // TODO:
// TODO:
--margin: var(--marginHalf);
display: flex;
// 100vh ... https://css-tricks.com/the-trick-to-viewport-units-on-mobile/
height: calc(var(--vh, 1vh) * 100);
box-sizing: border-box;
flex: 1;
padding: var(--deckMargin);
&.center {
> .column:first-of-type {
margin-left: auto;
}
> .column:last-of-type {
margin-right: auto;
}
}
&.isMobile {
padding-bottom: 100px;
}
> .column {
flex-shrink: 0;
margin-right: var(--deckMargin);
&.folder {
display: flex;
flex-direction: column;
> *:not(:last-child) {
margin-bottom: var(--deckMargin);
}
}
}
> .buttons {
position: fixed;
z-index: 1000;
bottom: 0;
left: 0;
padding: 16px;
display: flex;
width: 100%;
box-sizing: border-box;
> .button {
position: relative;
flex: 1;
padding: 0;
margin: auto;
height: 64px;
border-radius: 8px;
background: var(--panel);
color: var(--fg);
&:not(:last-child) {
margin-right: 12px;
}
@media (max-width: 400px) {
height: 60px;
&:not(:last-child) {
margin-right: 8px;
}
}
&:hover {
background: var(--X2);
}
> .indicator {
position: absolute;
top: 0;
left: 0;
color: var(--indicator);
font-size: 16px;
animation: blink 1s infinite;
}
&:first-child {
margin-left: 0;
}
&:last-child {
margin-right: 0;
}
> * {
font-size: 20px;
}
&:disabled {
cursor: default;
> * {
opacity: 0.5;
}
}
}
}
> .menu-back {
z-index: 1001;
}
> .menu {
position: fixed;
top: 0;
left: 0;
z-index: 1001;
// 100vh ... https://css-tricks.com/the-trick-to-viewport-units-on-mobile/
height: calc(var(--vh, 1vh) * 100);
width: 240px;
box-sizing: border-box;
overflow: auto;
overscroll-behavior: contain;
background: var(--bg);
}
}
</style>

View File

@ -0,0 +1,63 @@
<template>
<XColumn :func="{ handler: setAntenna, title: $ts.selectAntenna }" :column="column" :is-stacked="isStacked" @parent-focus="$event => emit('parent-focus', $event)">
<template #header>
<i class="fas fa-satellite"></i><span style="margin-left: 8px;">{{ column.name }}</span>
</template>
<XTimeline v-if="column.antennaId" ref="timeline" src="antenna" :antenna="column.antennaId" @after="() => emit('loaded')"/>
</XColumn>
</template>
<script lang="ts" setup>
import { onMounted } from 'vue';
import XColumn from './column.vue';
import XTimeline from '@/components/MkTimeline.vue';
import * as os from '@/os';
import { updateColumn, Column } from './deck-store';
import { i18n } from '@/i18n';
const props = defineProps<{
column: Column;
isStacked: boolean;
}>();
const emit = defineEmits<{
(ev: 'loaded'): void;
(ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void;
}>();
let timeline = $ref<InstanceType<typeof XTimeline>>();
onMounted(() => {
if (props.column.antennaId == null) {
setAntenna();
}
});
async function setAntenna() {
const antennas = await os.api('antennas/list');
const { canceled, result: antenna } = await os.select({
title: i18n.ts.selectAntenna,
items: antennas.map(x => ({
value: x, text: x.name
})),
default: props.column.antennaId
});
if (canceled) return;
updateColumn(props.column.id, {
antennaId: antenna.id
});
}
/*
function focus() {
timeline.focus();
}
defineExpose({
focus,
});
*/
</script>
<style lang="scss" scoped>
</style>

View File

@ -0,0 +1,44 @@
<template>
<!-- TODO: リファクタの余地がありそう -->
<div v-if="!column">たぶん見えちゃいけないやつ</div>
<XMainColumn v-else-if="column.type === 'main'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/>
<XWidgetsColumn v-else-if="column.type === 'widgets'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/>
<XNotificationsColumn v-else-if="column.type === 'notifications'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/>
<XTlColumn v-else-if="column.type === 'tl'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/>
<XListColumn v-else-if="column.type === 'list'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/>
<XAntennaColumn v-else-if="column.type === 'antenna'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/>
<XMentionsColumn v-else-if="column.type === 'mentions'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/>
<XDirectColumn v-else-if="column.type === 'direct'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/>
</template>
<script lang="ts" setup>
import { } from 'vue';
import XMainColumn from './main-column.vue';
import XTlColumn from './tl-column.vue';
import XAntennaColumn from './antenna-column.vue';
import XListColumn from './list-column.vue';
import XNotificationsColumn from './notifications-column.vue';
import XWidgetsColumn from './widgets-column.vue';
import XMentionsColumn from './mentions-column.vue';
import XDirectColumn from './direct-column.vue';
import { Column } from './deck-store';
defineProps<{
column?: Column;
isStacked: boolean;
}>();
const emit = defineEmits<{
(ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void;
}>();
/*
export default defineComponent({
methods: {
focus() {
this.$children[0].focus();
}
}
});
*/
</script>

View File

@ -0,0 +1,393 @@
<template>
<!-- sectionを利用しているのはdeck.vue側でcolumnに対してfirst-of-typeを効かせるため -->
<section v-hotkey="keymap" class="dnpfarvg _panel _narrow_"
:class="{ paged: isMainColumn, naked, active, isStacked, draghover, dragging, dropready }"
:style="{ '--deckColumnHeaderHeight': deckStore.reactiveState.columnHeaderHeight.value + 'px' }"
@dragover.prevent.stop="onDragover"
@dragleave="onDragleave"
@drop.prevent.stop="onDrop"
>
<header :class="{ indicated }"
draggable="true"
@click="goTop"
@dragstart="onDragstart"
@dragend="onDragend"
@contextmenu.prevent.stop="onContextmenu"
>
<button v-if="isStacked && !isMainColumn" class="toggleActive _button" @click="toggleActive">
<template v-if="active"><i class="fas fa-angle-up"></i></template>
<template v-else><i class="fas fa-angle-down"></i></template>
</button>
<div class="action">
<slot name="action"></slot>
</div>
<span class="header"><slot name="header"></slot></span>
<button v-tooltip="i18n.ts.settings" class="menu _button" @click.stop="showSettingsMenu"><i class="fas fa-ellipsis"></i></button>
</header>
<div v-show="active" ref="body">
<slot></slot>
</div>
</section>
</template>
<script lang="ts">
export type DeckFunc = {
title: string;
handler: (payload: MouseEvent) => void;
icon?: string;
};
</script>
<script lang="ts" setup>
import { onBeforeUnmount, onMounted, provide, watch } from 'vue';
import { updateColumn, swapLeftColumn, swapRightColumn, swapUpColumn, swapDownColumn, stackLeftColumn, popRightColumn, removeColumn, swapColumn, Column , deckStore } from './deck-store';
import * as os from '@/os';
import { i18n } from '@/i18n';
provide('shouldHeaderThin', true);
provide('shouldOmitHeaderTitle', true);
const props = withDefaults(defineProps<{
column: Column;
isStacked?: boolean;
func?: DeckFunc | null;
naked?: boolean;
indicated?: boolean;
}>(), {
isStacked: false,
func: null,
naked: false,
indicated: false,
});
const emit = defineEmits<{
(ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void;
(ev: 'change-active-state', v: boolean): void;
}>();
let body = $ref<HTMLDivElement>();
let dragging = $ref(false);
watch($$(dragging), v => os.deckGlobalEvents.emit(v ? 'column.dragStart' : 'column.dragEnd'));
let draghover = $ref(false);
let dropready = $ref(false);
const isMainColumn = $computed(() => props.column.type === 'main');
const active = $computed(() => props.column.active !== false);
watch($$(active), v => emit('change-active-state', v));
const keymap = $computed(() => ({
'shift+up': () => emit('parent-focus', 'up'),
'shift+down': () => emit('parent-focus', 'down'),
'shift+left': () => emit('parent-focus', 'left'),
'shift+right': () => emit('parent-focus', 'right'),
}));
onMounted(() => {
os.deckGlobalEvents.on('column.dragStart', onOtherDragStart);
os.deckGlobalEvents.on('column.dragEnd', onOtherDragEnd);
});
onBeforeUnmount(() => {
os.deckGlobalEvents.off('column.dragStart', onOtherDragStart);
os.deckGlobalEvents.off('column.dragEnd', onOtherDragEnd);
});
function onOtherDragStart() {
dropready = true;
}
function onOtherDragEnd() {
dropready = false;
}
function toggleActive() {
if (!props.isStacked) return;
updateColumn(props.column.id, {
active: !props.column.active,
});
}
function getMenu() {
const items = [{
icon: 'fas fa-pencil-alt',
text: i18n.ts.edit,
action: async () => {
const { canceled, result } = await os.form(props.column.name, {
name: {
type: 'string',
label: i18n.ts.name,
default: props.column.name,
},
width: {
type: 'number',
label: i18n.ts.width,
default: props.column.width,
},
flexible: {
type: 'boolean',
label: i18n.ts.flexible,
default: props.column.flexible,
},
});
if (canceled) return;
updateColumn(props.column.id, result);
},
}, null, {
icon: 'fas fa-arrow-left',
text: i18n.ts._deck.swapLeft,
action: () => {
swapLeftColumn(props.column.id);
},
}, {
icon: 'fas fa-arrow-right',
text: i18n.ts._deck.swapRight,
action: () => {
swapRightColumn(props.column.id);
},
}, props.isStacked ? {
icon: 'fas fa-arrow-up',
text: i18n.ts._deck.swapUp,
action: () => {
swapUpColumn(props.column.id);
},
} : undefined, props.isStacked ? {
icon: 'fas fa-arrow-down',
text: i18n.ts._deck.swapDown,
action: () => {
swapDownColumn(props.column.id);
},
} : undefined, null, {
icon: 'fas fa-window-restore',
text: i18n.ts._deck.stackLeft,
action: () => {
stackLeftColumn(props.column.id);
},
}, props.isStacked ? {
icon: 'fas fa-window-maximize',
text: i18n.ts._deck.popRight,
action: () => {
popRightColumn(props.column.id);
},
} : undefined, null, {
icon: 'fas fa-trash-alt',
text: i18n.ts.remove,
danger: true,
action: () => {
removeColumn(props.column.id);
},
}];
if (props.func) {
items.unshift(null);
items.unshift({
icon: props.func.icon,
text: props.func.title,
action: props.func.handler,
});
}
return items;
}
function showSettingsMenu(ev: MouseEvent) {
os.popupMenu(getMenu(), ev.currentTarget ?? ev.target);
}
function onContextmenu(ev: MouseEvent) {
os.contextMenu(getMenu(), ev);
}
function goTop() {
body.scrollTo({
top: 0,
behavior: 'smooth',
});
}
function onDragstart(ev) {
ev.dataTransfer.effectAllowed = 'move';
ev.dataTransfer.setData(_DATA_TRANSFER_DECK_COLUMN_, props.column.id);
// ChromeDragstartDOM(=)Drag
// SEE: https://stackoverflow.com/questions/19639969/html5-dragend-event-firing-immediately
window.setTimeout(() => {
dragging = true;
}, 10);
}
function onDragend(ev) {
dragging = false;
}
function onDragover(ev) {
//
if (dragging) {
//
ev.dataTransfer.dropEffect = 'none';
} else {
const isDeckColumn = ev.dataTransfer.types[0] === _DATA_TRANSFER_DECK_COLUMN_;
ev.dataTransfer.dropEffect = isDeckColumn ? 'move' : 'none';
if (isDeckColumn) draghover = true;
}
}
function onDragleave() {
draghover = false;
}
function onDrop(ev) {
draghover = false;
os.deckGlobalEvents.emit('column.dragEnd');
const id = ev.dataTransfer.getData(_DATA_TRANSFER_DECK_COLUMN_);
if (id != null && id !== '') {
swapColumn(props.column.id, id);
}
}
</script>
<style lang="scss" scoped>
.dnpfarvg {
--root-margin: 10px;
--deckColumnHeaderHeight: 42px;
height: 100%;
overflow: hidden;
contain: content;
box-shadow: 0 0 8px 0 var(--shadow);
&.draghover {
box-shadow: 0 0 0 2px var(--focus);
&:after {
content: "";
display: block;
position: absolute;
z-index: 1000;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: var(--focus);
}
}
&.dragging {
box-shadow: 0 0 0 2px var(--focus);
}
&.dropready {
* {
pointer-events: none;
}
}
&:not(.active) {
flex-basis: var(--deckColumnHeaderHeight);
min-height: var(--deckColumnHeaderHeight);
> header.indicated {
box-shadow: 4px 0px var(--accent) inset;
}
}
&.naked {
background: var(--acrylicBg) !important;
-webkit-backdrop-filter: var(--blur, blur(10px));
backdrop-filter: var(--blur, blur(10px));
> header {
background: transparent;
box-shadow: none;
> button {
color: var(--fg);
}
}
}
&.paged {
background: var(--bg) !important;
}
> header {
position: relative;
display: flex;
z-index: 2;
line-height: var(--deckColumnHeaderHeight);
height: var(--deckColumnHeaderHeight);
padding: 0 16px;
font-size: 0.9em;
color: var(--panelHeaderFg);
background: var(--panelHeaderBg);
box-shadow: 0 1px 0 0 var(--panelHeaderDivider);
cursor: pointer;
&, * {
user-select: none;
}
&.indicated {
box-shadow: 0 3px 0 0 var(--accent);
}
> .header {
display: inline-block;
align-items: center;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
> span:only-of-type {
width: 100%;
}
> .toggleActive,
> .action > ::v-deep(*),
> .menu {
z-index: 1;
width: var(--deckColumnHeaderHeight);
line-height: var(--deckColumnHeaderHeight);
color: var(--faceTextButton);
&:hover {
color: var(--faceTextButtonHover);
}
&:active {
color: var(--faceTextButtonActive);
}
}
> .toggleActive, > .action {
margin-left: -16px;
}
> .action {
z-index: 1;
}
> .action:empty {
display: none;
}
> .menu {
margin-left: auto;
margin-right: -16px;
}
}
> div {
height: calc(100% - var(--deckColumnHeaderHeight));
overflow-y: auto;
overflow-x: hidden; // Safari does not supports clip
overflow-x: clip;
-webkit-overflow-scrolling: touch;
box-sizing: border-box;
}
}
</style>

View File

@ -0,0 +1,317 @@
import { throttle } from 'throttle-debounce';
import { markRaw } from 'vue';
import { notificationTypes } from 'misskey-js';
import { Storage } from '../../pizzax';
import { i18n } from '@/i18n';
import { api } from '@/os';
type ColumnWidget = {
name: string;
id: string;
data: Record<string, any>;
};
export type Column = {
id: string;
type: 'main' | 'widgets' | 'notifications' | 'tl' | 'antenna' | 'list' | 'mentions' | 'direct';
name: string | null;
width: number;
widgets?: ColumnWidget[];
active?: boolean;
flexible?: boolean;
antennaId?: string;
listId?: string;
includingTypes?: typeof notificationTypes[number][];
tl?: 'home' | 'local' | 'social' | 'global';
};
function copy<T>(x: T): T {
return JSON.parse(JSON.stringify(x));
}
export const deckStore = markRaw(new Storage('deck', {
profile: {
where: 'deviceAccount',
default: 'default',
},
columns: {
where: 'deviceAccount',
default: [] as Column[],
},
layout: {
where: 'deviceAccount',
default: [] as Column['id'][][],
},
columnAlign: {
where: 'deviceAccount',
default: 'left' as 'left' | 'right' | 'center',
},
alwaysShowMainColumn: {
where: 'deviceAccount',
default: true,
},
navWindow: {
where: 'deviceAccount',
default: true,
},
columnMargin: {
where: 'deviceAccount',
default: 12,
},
columnHeaderHeight: {
where: 'deviceAccount',
default: 42,
},
}));
export const loadDeck = async () => {
let deck;
try {
deck = await api('i/registry/get', {
scope: ['client', 'deck', 'profiles'],
key: deckStore.state.profile,
});
} catch (err) {
if (err.code === 'NO_SUCH_KEY') {
// 後方互換性のため
if (deckStore.state.profile === 'default') {
saveDeck();
return;
}
deckStore.set('columns', [{
id: 'a',
type: 'main',
name: i18n.ts._deck._columns.main,
width: 350,
}, {
id: 'b',
type: 'notifications',
name: i18n.ts._deck._columns.notifications,
width: 330,
}]);
deckStore.set('layout', [['a'], ['b']]);
return;
}
throw err;
}
deckStore.set('columns', deck.columns);
deckStore.set('layout', deck.layout);
};
// TODO: deckがloadされていない状態でsaveすると意図せず上書きが発生するので対策する
export const saveDeck = throttle(1000, () => {
api('i/registry/set', {
scope: ['client', 'deck', 'profiles'],
key: deckStore.state.profile,
value: {
columns: deckStore.reactiveState.columns.value,
layout: deckStore.reactiveState.layout.value,
},
});
});
export async function getProfiles(): Promise<string[]> {
return await api('i/registry/keys', {
scope: ['client', 'deck', 'profiles'],
});
}
export async function deleteProfile(key: string): Promise<void> {
return await api('i/registry/remove', {
scope: ['client', 'deck', 'profiles'],
key: key,
});
}
export function addColumn(column: Column) {
if (column.name === undefined) column.name = null;
deckStore.push('columns', column);
deckStore.push('layout', [column.id]);
saveDeck();
}
export function removeColumn(id: Column['id']) {
deckStore.set('columns', deckStore.state.columns.filter(c => c.id !== id));
deckStore.set('layout', deckStore.state.layout
.map(ids => ids.filter(_id => _id !== id))
.filter(ids => ids.length > 0));
saveDeck();
}
export function swapColumn(a: Column['id'], b: Column['id']) {
const aX = deckStore.state.layout.findIndex(ids => ids.indexOf(a) !== -1);
const aY = deckStore.state.layout[aX].findIndex(id => id === a);
const bX = deckStore.state.layout.findIndex(ids => ids.indexOf(b) !== -1);
const bY = deckStore.state.layout[bX].findIndex(id => id === b);
const layout = copy(deckStore.state.layout);
layout[aX][aY] = b;
layout[bX][bY] = a;
deckStore.set('layout', layout);
saveDeck();
}
export function swapLeftColumn(id: Column['id']) {
const layout = copy(deckStore.state.layout);
deckStore.state.layout.some((ids, i) => {
if (ids.includes(id)) {
const left = deckStore.state.layout[i - 1];
if (left) {
layout[i - 1] = deckStore.state.layout[i];
layout[i] = left;
deckStore.set('layout', layout);
}
return true;
}
});
saveDeck();
}
export function swapRightColumn(id: Column['id']) {
const layout = copy(deckStore.state.layout);
deckStore.state.layout.some((ids, i) => {
if (ids.includes(id)) {
const right = deckStore.state.layout[i + 1];
if (right) {
layout[i + 1] = deckStore.state.layout[i];
layout[i] = right;
deckStore.set('layout', layout);
}
return true;
}
});
saveDeck();
}
export function swapUpColumn(id: Column['id']) {
const layout = copy(deckStore.state.layout);
const idsIndex = deckStore.state.layout.findIndex(ids => ids.includes(id));
const ids = copy(deckStore.state.layout[idsIndex]);
ids.some((x, i) => {
if (x === id) {
const up = ids[i - 1];
if (up) {
ids[i - 1] = id;
ids[i] = up;
layout[idsIndex] = ids;
deckStore.set('layout', layout);
}
return true;
}
});
saveDeck();
}
export function swapDownColumn(id: Column['id']) {
const layout = copy(deckStore.state.layout);
const idsIndex = deckStore.state.layout.findIndex(ids => ids.includes(id));
const ids = copy(deckStore.state.layout[idsIndex]);
ids.some((x, i) => {
if (x === id) {
const down = ids[i + 1];
if (down) {
ids[i + 1] = id;
ids[i] = down;
layout[idsIndex] = ids;
deckStore.set('layout', layout);
}
return true;
}
});
saveDeck();
}
export function stackLeftColumn(id: Column['id']) {
let layout = copy(deckStore.state.layout);
const i = deckStore.state.layout.findIndex(ids => ids.includes(id));
layout = layout.map(ids => ids.filter(_id => _id !== id));
layout[i - 1].push(id);
layout = layout.filter(ids => ids.length > 0);
deckStore.set('layout', layout);
saveDeck();
}
export function popRightColumn(id: Column['id']) {
let layout = copy(deckStore.state.layout);
const i = deckStore.state.layout.findIndex(ids => ids.includes(id));
const affected = layout[i];
layout = layout.map(ids => ids.filter(_id => _id !== id));
layout.splice(i + 1, 0, [id]);
layout = layout.filter(ids => ids.length > 0);
deckStore.set('layout', layout);
const columns = copy(deckStore.state.columns);
for (const column of columns) {
if (affected.includes(column.id)) {
column.active = true;
}
}
deckStore.set('columns', columns);
saveDeck();
}
export function addColumnWidget(id: Column['id'], widget: ColumnWidget) {
const columns = copy(deckStore.state.columns);
const columnIndex = deckStore.state.columns.findIndex(c => c.id === id);
const column = copy(deckStore.state.columns[columnIndex]);
if (column == null) return;
if (column.widgets == null) column.widgets = [];
column.widgets.unshift(widget);
columns[columnIndex] = column;
deckStore.set('columns', columns);
saveDeck();
}
export function removeColumnWidget(id: Column['id'], widget: ColumnWidget) {
const columns = copy(deckStore.state.columns);
const columnIndex = deckStore.state.columns.findIndex(c => c.id === id);
const column = copy(deckStore.state.columns[columnIndex]);
if (column == null) return;
column.widgets = column.widgets.filter(w => w.id !== widget.id);
columns[columnIndex] = column;
deckStore.set('columns', columns);
saveDeck();
}
export function setColumnWidgets(id: Column['id'], widgets: ColumnWidget[]) {
const columns = copy(deckStore.state.columns);
const columnIndex = deckStore.state.columns.findIndex(c => c.id === id);
const column = copy(deckStore.state.columns[columnIndex]);
if (column == null) return;
column.widgets = widgets;
columns[columnIndex] = column;
deckStore.set('columns', columns);
saveDeck();
}
export function updateColumnWidget(id: Column['id'], widgetId: string, widgetData: any) {
const columns = copy(deckStore.state.columns);
const columnIndex = deckStore.state.columns.findIndex(c => c.id === id);
const column = copy(deckStore.state.columns[columnIndex]);
if (column == null) return;
column.widgets = column.widgets.map(w => w.id === widgetId ? {
...w,
data: widgetData,
} : w);
columns[columnIndex] = column;
deckStore.set('columns', columns);
saveDeck();
}
export function updateColumn(id: Column['id'], column: Partial<Column>) {
const columns = copy(deckStore.state.columns);
const columnIndex = deckStore.state.columns.findIndex(c => c.id === id);
const currentColumn = copy(deckStore.state.columns[columnIndex]);
if (currentColumn == null) return;
for (const [k, v] of Object.entries(column)) {
currentColumn[k] = v;
}
columns[columnIndex] = currentColumn;
deckStore.set('columns', columns);
saveDeck();
}

View File

@ -0,0 +1,31 @@
<template>
<XColumn :column="column" :is-stacked="isStacked" @parent-focus="$event => emit('parent-focus', $event)">
<template #header><i class="fas fa-envelope" style="margin-right: 8px;"></i>{{ column.name }}</template>
<XNotes :pagination="pagination"/>
</XColumn>
</template>
<script lang="ts" setup>
import { } from 'vue';
import XColumn from './column.vue';
import XNotes from '@/components/MkNotes.vue';
import { Column } from './deck-store';
defineProps<{
column: Column;
isStacked: boolean;
}>();
const emit = defineEmits<{
(ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void;
}>();
const pagination = {
endpoint: 'notes/mentions' as const,
limit: 10,
params: {
visibility: 'specified'
},
};
</script>

View File

@ -0,0 +1,66 @@
<template>
<XColumn :func="{ handler: setList, title: $ts.selectList }" :column="column" :is-stacked="isStacked" @parent-focus="$event => emit('parent-focus', $event)">
<template #header>
<i class="fas fa-list-ul"></i><span style="margin-left: 8px;">{{ column.name }}</span>
</template>
<XTimeline v-if="column.listId" ref="timeline" src="list" :list="column.listId" @after="() => emit('loaded')"/>
</XColumn>
</template>
<script lang="ts" setup>
import { } from 'vue';
import XColumn from './column.vue';
import XTimeline from '@/components/MkTimeline.vue';
import * as os from '@/os';
import { updateColumn, Column } from './deck-store';
import { i18n } from '@/i18n';
const props = defineProps<{
column: Column;
isStacked: boolean;
}>();
const emit = defineEmits<{
(ev: 'loaded'): void;
(ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void;
}>();
let timeline = $ref<InstanceType<typeof XTimeline>>();
if (props.column.listId == null) {
setList();
}
async function setList() {
const lists = await os.api('users/lists/list');
const { canceled, result: list } = await os.select({
title: i18n.ts.selectList,
items: lists.map(x => ({
value: x, text: x.name
})),
default: props.column.listId
});
if (canceled) return;
updateColumn(props.column.id, {
listId: list.id
});
}
/*
function focus() {
timeline.focus();
}
export default defineComponent({
watch: {
mediaOnly() {
(this.$refs.timeline as any).reload();
}
}
});
*/
</script>
<style lang="scss" scoped>
</style>

View File

@ -0,0 +1,68 @@
<template>
<XColumn v-if="deckStore.state.alwaysShowMainColumn || mainRouter.currentRoute.value.name !== 'index'" :column="column" :is-stacked="isStacked" @parent-focus="$event => emit('parent-focus', $event)">
<template #header>
<template v-if="pageMetadata?.value">
<i :class="pageMetadata?.value.icon"></i>
{{ pageMetadata?.value.title }}
</template>
</template>
<RouterView @contextmenu.stop="onContextmenu"/>
</XColumn>
</template>
<script lang="ts" setup>
import { ComputedRef, provide } from 'vue';
import XColumn from './column.vue';
import { deckStore, Column } from '@/ui/deck/deck-store';
import * as os from '@/os';
import { i18n } from '@/i18n';
import { mainRouter } from '@/router';
import { PageMetadata, provideMetadataReceiver, setPageMetadata } from '@/scripts/page-metadata';
defineProps<{
column: Column;
isStacked: boolean;
}>();
const emit = defineEmits<{
(ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void;
}>();
let pageMetadata = $ref<null | ComputedRef<PageMetadata>>();
provide('router', mainRouter);
provideMetadataReceiver((info) => {
pageMetadata = info;
});
/*
function back() {
history.back();
}
*/
function onContextmenu(ev: MouseEvent) {
if (!ev.target) return;
const isLink = (el: HTMLElement) => {
if (el.tagName === 'A') return true;
if (el.parentElement) {
return isLink(el.parentElement);
}
};
if (isLink(ev.target as HTMLElement)) return;
if (['INPUT', 'TEXTAREA', 'IMG', 'VIDEO', 'CANVAS'].includes((ev.target as HTMLElement).tagName) || (ev.target as HTMLElement).attributes['contenteditable']) return;
if (window.getSelection()?.toString() !== '') return;
const path = mainRouter.currentRoute.value.path;
os.contextMenu([{
type: 'label',
text: path,
}, {
icon: 'fas fa-window-maximize',
text: i18n.ts.openInWindow,
action: () => {
os.pageWindow(path);
},
}], ev);
}
</script>

View File

@ -0,0 +1,28 @@
<template>
<XColumn :column="column" :is-stacked="isStacked" @parent-focus="$event => emit('parent-focus', $event)">
<template #header><i class="fas fa-at" style="margin-right: 8px;"></i>{{ column.name }}</template>
<XNotes :pagination="pagination"/>
</XColumn>
</template>
<script lang="ts" setup>
import { } from 'vue';
import XColumn from './column.vue';
import XNotes from '@/components/MkNotes.vue';
import { Column } from './deck-store';
defineProps<{
column: Column;
isStacked: boolean;
}>();
const emit = defineEmits<{
(ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void;
}>();
const pagination = {
endpoint: 'notes/mentions' as const,
limit: 10,
};
</script>

View File

@ -0,0 +1,38 @@
<template>
<XColumn :column="column" :is-stacked="isStacked" :func="{ handler: func, title: $ts.notificationSetting }" @parent-focus="$event => emit('parent-focus', $event)">
<template #header><i class="fas fa-bell" style="margin-right: 8px;"></i>{{ column.name }}</template>
<XNotifications :include-types="column.includingTypes"/>
</XColumn>
</template>
<script lang="ts" setup>
import { defineAsyncComponent } from 'vue';
import XColumn from './column.vue';
import XNotifications from '@/components/MkNotifications.vue';
import * as os from '@/os';
import { updateColumn } from './deck-store';
import { Column } from './deck-store';
const props = defineProps<{
column: Column;
isStacked: boolean;
}>();
const emit = defineEmits<{
(ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void;
}>();
function func() {
os.popup(defineAsyncComponent(() => import('@/components/MkNotificationSettingWindow.vue')), {
includingTypes: props.column.includingTypes,
}, {
done: async (res) => {
const { includingTypes } = res;
updateColumn(props.column.id, {
includingTypes: includingTypes
});
},
}, 'closed');
}
</script>

View File

@ -0,0 +1,129 @@
<template>
<XColumn :func="{ handler: setType, title: $ts.timeline }" :column="column" :is-stacked="isStacked" :indicated="indicated" @change-active-state="onChangeActiveState" @parent-focus="$event => emit('parent-focus', $event)">
<template #header>
<i v-if="column.tl === 'home'" class="fas fa-home"></i>
<i v-else-if="column.tl === 'local'" class="fas fa-comments"></i>
<i v-else-if="column.tl === 'social'" class="fas fa-share-alt"></i>
<i v-else-if="column.tl === 'global'" class="fas fa-globe"></i>
<span style="margin-left: 8px;">{{ column.name }}</span>
</template>
<div v-if="disabled" class="iwaalbte">
<p>
<i class="fas fa-minus-circle"></i>
{{ $t('disabled-timeline.title') }}
</p>
<p class="desc">{{ $t('disabled-timeline.description') }}</p>
</div>
<XTimeline v-else-if="column.tl" ref="timeline" :key="column.tl" :src="column.tl" @after="() => emit('loaded')" @queue="queueUpdated" @note="onNote"/>
</XColumn>
</template>
<script lang="ts" setup>
import { onMounted } from 'vue';
import XColumn from './column.vue';
import XTimeline from '@/components/MkTimeline.vue';
import * as os from '@/os';
import { removeColumn, updateColumn, Column } from './deck-store';
import { $i } from '@/account';
import { instance } from '@/instance';
import { i18n } from '@/i18n';
const props = defineProps<{
column: Column;
isStacked: boolean;
}>();
const emit = defineEmits<{
(ev: 'loaded'): void;
(ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void;
}>();
let disabled = $ref(false);
let indicated = $ref(false);
let columnActive = $ref(true);
onMounted(() => {
if (props.column.tl == null) {
setType();
} else if ($i) {
disabled = !$i.isModerator && !$i.isAdmin && (
instance.disableLocalTimeline && ['local', 'social'].includes(props.column.tl) ||
instance.disableGlobalTimeline && ['global'].includes(props.column.tl));
}
});
async function setType() {
const { canceled, result: src } = await os.select({
title: i18n.ts.timeline,
items: [{
value: 'home' as const, text: i18n.ts._timelines.home
}, {
value: 'local' as const, text: i18n.ts._timelines.local
}, {
value: 'social' as const, text: i18n.ts._timelines.social
}, {
value: 'global' as const, text: i18n.ts._timelines.global
}],
});
if (canceled) {
if (props.column.tl == null) {
removeColumn(props.column.id);
}
return;
}
updateColumn(props.column.id, {
tl: src
});
}
function queueUpdated(q) {
if (columnActive) {
indicated = q !== 0;
}
}
function onNote() {
if (!columnActive) {
indicated = true;
}
}
function onChangeActiveState(state) {
columnActive = state;
if (columnActive) {
indicated = false;
}
}
/*
export default defineComponent({
watch: {
mediaOnly() {
(this.$refs.timeline as any).reload();
}
},
methods: {
focus() {
(this.$refs.timeline as any).focus();
}
}
});
*/
</script>
<style lang="scss" scoped>
.iwaalbte {
text-align: center;
> p {
margin: 16px;
&.desc {
font-size: 14px;
}
}
}
</style>

View File

@ -0,0 +1,63 @@
<template>
<XColumn :func="{ handler: func, title: $ts.editWidgets }" :naked="true" :column="column" :is-stacked="isStacked" @parent-focus="$event => emit('parent-focus', $event)">
<template #header><i class="fas fa-window-maximize" style="margin-right: 8px;"></i>{{ column.name }}</template>
<div class="wtdtxvec">
<div v-if="!(column.widgets && column.widgets.length > 0) && !edit" class="intro">{{ i18n.ts._deck.widgetsIntroduction }}</div>
<XWidgets :edit="edit" :widgets="column.widgets" @add-widget="addWidget" @remove-widget="removeWidget" @update-widget="updateWidget" @update-widgets="updateWidgets" @exit="edit = false"/>
</div>
</XColumn>
</template>
<script lang="ts" setup>
import { } from 'vue';
import XColumn from './column.vue';
import { addColumnWidget, Column, removeColumnWidget, setColumnWidgets, updateColumnWidget } from './deck-store';
import XWidgets from '@/components/MkWidgets.vue';
import { i18n } from '@/i18n';
const props = defineProps<{
column: Column;
isStacked: boolean;
}>();
const emit = defineEmits<{
(ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void;
}>();
let edit = $ref(false);
function addWidget(widget) {
addColumnWidget(props.column.id, widget);
}
function removeWidget(widget) {
removeColumnWidget(props.column.id, widget);
}
function updateWidget({ id, data }) {
updateColumnWidget(props.column.id, id, data);
}
function updateWidgets(widgets) {
setColumnWidgets(props.column.id, widgets);
}
function func() {
edit = !edit;
}
</script>
<style lang="scss" scoped>
.wtdtxvec {
--margin: 8px;
--panelBorder: none;
padding: 0 var(--margin);
> .intro {
padding: 16px;
text-align: center;
}
}
</style>

View File

@ -19,7 +19,7 @@
</div> </div>
</div> </div>
<div class="main"> <div class="main" :class="{ 'noroot': $route.path === '/' }">
<div ref="contents" class="contents" :class="{ wallpaper }"> <div ref="contents" class="contents" :class="{ wallpaper }">
<header v-show="mainRouter.currentRoute?.name !== 'index'" ref="header" class="header"> <header v-show="mainRouter.currentRoute?.name !== 'index'" ref="header" class="header">
<XHeader :info="pageInfo"/> <XHeader :info="pageInfo"/>
@ -130,6 +130,16 @@ export default defineComponent({
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.noroot {
background: rgba(0, 0, 0, 0.6);
&.transparent {
-webkit-backdrop-filter: var(--blur, blur(12px));
backdrop-filter: var(--blur, blur(12px));
}
}
.mk-app { .mk-app {
min-height: 100vh; min-height: 100vh;

View File

@ -6,7 +6,7 @@
<XKanban class="kanban" full/> <XKanban class="kanban" full/>
</div> </div>
<div class="main"> <div class="main" :class="{ 'noroot': !root }">
<XKanban v-if="narrow && !root" class="banner" :powered-by="root"/> <XKanban v-if="narrow && !root" class="banner" :powered-by="root"/>
<div class="contents"> <div class="contents">
@ -127,6 +127,14 @@ defineExpose({
</style> </style>
<style lang="scss" scoped> <style lang="scss" scoped>
.noroot {
background: rgba(0, 0, 0, 0.6);
&.transparent {
-webkit-backdrop-filter: var(--blur, blur(12px));
backdrop-filter: var(--blur, blur(12px));
}
}
.tray-enter-active, .tray-enter-active,
.tray-leave-active { .tray-leave-active {
opacity: 1; opacity: 1;

View File

@ -1226,6 +1226,11 @@ delayed-stream@~1.0.0:
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk=
destyle.css@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/destyle.css/-/destyle.css-3.0.2.tgz#80c331db91f02337040b62dc534ab4f3dc075546"
integrity sha512-OUzuAlridP01YP0seHxx+FgwhdXF07Ls9NxnJpR8EI7ADioswoR7VKkBd/s1hNw/G4ILasLSA0k2Kw1M57r1kA==
detect-node@2.1.0, detect-node@^2.1.0: detect-node@2.1.0, detect-node@^2.1.0:
version "2.1.0" version "2.1.0"
resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1"
@ -2616,6 +2621,13 @@ minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6:
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
"misetehoshii@https://github.com/melt-adzuki/misetehoshii":
version "0.0.0"
resolved "https://github.com/melt-adzuki/misetehoshii#d00f51529e390808f92172000f960b4f1314f37c"
dependencies:
destyle.css "^3.0.2"
vue "^3.2.37"
misskey-js@0.0.14: misskey-js@0.0.14:
version "0.0.14" version "0.0.14"
resolved "https://registry.yarnpkg.com/misskey-js/-/misskey-js-0.0.14.tgz#1a616bdfbe81c6ee6900219eaf425bb5c714dd4d" resolved "https://registry.yarnpkg.com/misskey-js/-/misskey-js-0.0.14.tgz#1a616bdfbe81c6ee6900219eaf425bb5c714dd4d"
@ -3621,7 +3633,7 @@ vue-prism-editor@2.0.0-alpha.2:
resolved "https://registry.yarnpkg.com/vue-prism-editor/-/vue-prism-editor-2.0.0-alpha.2.tgz#aa53a88efaaed628027cbb282c2b1d37fc7c5c69" resolved "https://registry.yarnpkg.com/vue-prism-editor/-/vue-prism-editor-2.0.0-alpha.2.tgz#aa53a88efaaed628027cbb282c2b1d37fc7c5c69"
integrity sha512-Gu42ba9nosrE+gJpnAEuEkDMqG9zSUysIR8SdXUw8MQKDjBnnNR9lHC18uOr/ICz7yrA/5c7jHJr9lpElODC7w== integrity sha512-Gu42ba9nosrE+gJpnAEuEkDMqG9zSUysIR8SdXUw8MQKDjBnnNR9lHC18uOr/ICz7yrA/5c7jHJr9lpElODC7w==
vue@3.2.39: vue@3.2.39, vue@^3.2.37:
version "3.2.39" version "3.2.39"
resolved "https://registry.yarnpkg.com/vue/-/vue-3.2.39.tgz#de071c56c4c32c41cbd54e55f11404295c0dd62d" resolved "https://registry.yarnpkg.com/vue/-/vue-3.2.39.tgz#de071c56c4c32c41cbd54e55f11404295c0dd62d"
integrity sha512-tRkguhRTw9NmIPXhzk21YFBqXHT2t+6C6wPOgQ50fcFVWnPdetmRqbmySRHznrYjX2E47u0cGlKGcxKZJ38R/g== integrity sha512-tRkguhRTw9NmIPXhzk21YFBqXHT2t+6C6wPOgQ50fcFVWnPdetmRqbmySRHznrYjX2E47u0cGlKGcxKZJ38R/g==