Compare commits
31 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
bec48319ec | ||
![]() |
71a93b2b43 | ||
![]() |
6ed3f9e414 | ||
![]() |
dc8f592c1f | ||
![]() |
f66c31c771 | ||
![]() |
55e2ae1408 | ||
![]() |
19c72627fc | ||
![]() |
2a4c53c3a4 | ||
![]() |
1f2ebce8ed | ||
![]() |
fcea9dacb7 | ||
![]() |
908872f374 | ||
![]() |
f688ceafb8 | ||
![]() |
b47b5d6d8b | ||
![]() |
31ce3aa312 | ||
![]() |
5b22d92e99 | ||
![]() |
df148e25da | ||
![]() |
4b26df5c3a | ||
![]() |
f7d2457063 | ||
![]() |
6032d803aa | ||
![]() |
0de371db38 | ||
![]() |
56dd8c298b | ||
![]() |
3533257efe | ||
![]() |
dc2f08721d | ||
![]() |
66608a4131 | ||
![]() |
2fa90131eb | ||
![]() |
a51ed28db6 | ||
![]() |
5ec290663b | ||
![]() |
1374d6e34d | ||
![]() |
45ade17c58 | ||
![]() |
c753e26187 | ||
![]() |
577929eed1 |
@@ -158,6 +158,7 @@ common:
|
||||
hashtag: "ハッシュタグ"
|
||||
global: "グローバル"
|
||||
mentions: "あなた宛て"
|
||||
direct: "ダイレクト投稿"
|
||||
notifications: "通知"
|
||||
list: "リスト"
|
||||
swap-left: "左に移動"
|
||||
@@ -810,6 +811,7 @@ desktop/views/components/timeline.vue:
|
||||
hybrid: "ソーシャル"
|
||||
global: "グローバル"
|
||||
mentions: "あなた宛て"
|
||||
messages: "メッセージ"
|
||||
list: "リスト"
|
||||
hashtag: "ハッシュタグ"
|
||||
add-tag-timeline: "ハッシュタグを追加"
|
||||
@@ -1140,6 +1142,7 @@ mobile/views/pages/home.vue:
|
||||
hybrid: "ソーシャル"
|
||||
global: "グローバル"
|
||||
mentions: "あなた宛て"
|
||||
messages: "メッセージ"
|
||||
mobile/views/pages/tag.vue:
|
||||
no-posts-found: "ハッシュタグ「{}」が付けられた投稿は見つかりませんでした。"
|
||||
mobile/views/pages/welcome.vue:
|
||||
|
@@ -158,6 +158,7 @@ common:
|
||||
hashtag: "ハッシュタグ"
|
||||
global: "Global"
|
||||
mentions: "あなた宛て"
|
||||
direct: "ダイレクト投稿"
|
||||
notifications: "Mitteilungen"
|
||||
list: "Listen"
|
||||
swap-left: "Nach links"
|
||||
@@ -810,6 +811,7 @@ desktop/views/components/timeline.vue:
|
||||
hybrid: "ソーシャル"
|
||||
global: "Global"
|
||||
mentions: "あなた宛て"
|
||||
messages: "メッセージ"
|
||||
list: "Listen"
|
||||
hashtag: "ハッシュタグ"
|
||||
add-tag-timeline: "ハッシュタグを追加"
|
||||
@@ -1140,6 +1142,7 @@ mobile/views/pages/home.vue:
|
||||
hybrid: "ソーシャル"
|
||||
global: "グローバル"
|
||||
mentions: "あなた宛て"
|
||||
messages: "メッセージ"
|
||||
mobile/views/pages/tag.vue:
|
||||
no-posts-found: "ハッシュタグ「{}」が付けられた投稿は見つかりませんでした。"
|
||||
mobile/views/pages/welcome.vue:
|
||||
|
@@ -110,7 +110,7 @@ common:
|
||||
verified-user: "Verified account"
|
||||
disable-animated-mfm: "Disable animated texts in a post"
|
||||
always-show-nsfw: "常に閲覧注意のメディアを表示する"
|
||||
always-mark-nsfw: "常にメディアを閲覧注意として投稿"
|
||||
always-mark-nsfw: "Always post with a warning about media attachment"
|
||||
show-full-acct: "Do not omit the hostname from the username"
|
||||
reduce-motion: "Reduce motion in UI"
|
||||
this-setting-is-this-device-only: "Only for this device"
|
||||
@@ -158,6 +158,7 @@ common:
|
||||
hashtag: "Hashtag"
|
||||
global: "Global"
|
||||
mentions: "Mentions"
|
||||
direct: "ダイレクト投稿"
|
||||
notifications: "Notifications"
|
||||
list: "Lists"
|
||||
swap-left: "Move to the left"
|
||||
@@ -810,6 +811,7 @@ desktop/views/components/timeline.vue:
|
||||
hybrid: "Social"
|
||||
global: "Global"
|
||||
mentions: "Mentions"
|
||||
messages: "Messages"
|
||||
list: "Lists"
|
||||
hashtag: "Hashtag"
|
||||
add-tag-timeline: "Add hashtag tl"
|
||||
@@ -1140,6 +1142,7 @@ mobile/views/pages/home.vue:
|
||||
hybrid: "Social"
|
||||
global: "Global"
|
||||
mentions: "Mentions"
|
||||
messages: "Messages"
|
||||
mobile/views/pages/tag.vue:
|
||||
no-posts-found: "No posts \"{}\" found."
|
||||
mobile/views/pages/welcome.vue:
|
||||
|
@@ -158,6 +158,7 @@ common:
|
||||
hashtag: "ハッシュタグ"
|
||||
global: "Global"
|
||||
mentions: "あなた宛て"
|
||||
direct: "ダイレクト投稿"
|
||||
notifications: "Notificaciones"
|
||||
list: "Listado"
|
||||
swap-left: "Desplazar a la izq."
|
||||
@@ -810,6 +811,7 @@ desktop/views/components/timeline.vue:
|
||||
hybrid: "ソーシャル"
|
||||
global: "グローバル"
|
||||
mentions: "あなた宛て"
|
||||
messages: "メッセージ"
|
||||
list: "リスト"
|
||||
hashtag: "ハッシュタグ"
|
||||
add-tag-timeline: "ハッシュタグを追加"
|
||||
@@ -1140,6 +1142,7 @@ mobile/views/pages/home.vue:
|
||||
hybrid: "ソーシャル"
|
||||
global: "グローバル"
|
||||
mentions: "あなた宛て"
|
||||
messages: "メッセージ"
|
||||
mobile/views/pages/tag.vue:
|
||||
no-posts-found: "ハッシュタグ「{}」が付けられた投稿は見つかりませんでした。"
|
||||
mobile/views/pages/welcome.vue:
|
||||
|
@@ -158,6 +158,7 @@ common:
|
||||
hashtag: "ハッシュタグ"
|
||||
global: "Global"
|
||||
mentions: "Mentions"
|
||||
direct: "ダイレクト投稿"
|
||||
notifications: "Notifications"
|
||||
list: "Liste"
|
||||
swap-left: "Déplacer à gauche"
|
||||
@@ -810,6 +811,7 @@ desktop/views/components/timeline.vue:
|
||||
hybrid: "Social"
|
||||
global: "Global"
|
||||
mentions: "Mentions"
|
||||
messages: "メッセージ"
|
||||
list: "Listes"
|
||||
hashtag: "ハッシュタグ"
|
||||
add-tag-timeline: "ハッシュタグを追加"
|
||||
@@ -1140,6 +1142,7 @@ mobile/views/pages/home.vue:
|
||||
hybrid: "Social"
|
||||
global: "Global"
|
||||
mentions: "Mentions"
|
||||
messages: "メッセージ"
|
||||
mobile/views/pages/tag.vue:
|
||||
no-posts-found: "Pas de message avec un hashtag {} trouvé."
|
||||
mobile/views/pages/welcome.vue:
|
||||
|
@@ -158,6 +158,7 @@ common:
|
||||
hashtag: "ハッシュタグ"
|
||||
global: "グローバル"
|
||||
mentions: "あなた宛て"
|
||||
direct: "ダイレクト投稿"
|
||||
notifications: "通知"
|
||||
list: "リスト"
|
||||
swap-left: "左に移動"
|
||||
@@ -810,6 +811,7 @@ desktop/views/components/timeline.vue:
|
||||
hybrid: "ソーシャル"
|
||||
global: "グローバル"
|
||||
mentions: "あなた宛て"
|
||||
messages: "メッセージ"
|
||||
list: "リスト"
|
||||
hashtag: "ハッシュタグ"
|
||||
add-tag-timeline: "ハッシュタグを追加"
|
||||
@@ -1140,6 +1142,7 @@ mobile/views/pages/home.vue:
|
||||
hybrid: "ソーシャル"
|
||||
global: "グローバル"
|
||||
mentions: "あなた宛て"
|
||||
messages: "メッセージ"
|
||||
mobile/views/pages/tag.vue:
|
||||
no-posts-found: "ハッシュタグ「{}」が付けられた投稿は見つかりませんでした。"
|
||||
mobile/views/pages/welcome.vue:
|
||||
|
@@ -158,6 +158,7 @@ common:
|
||||
hashtag: "ハッシュタグ"
|
||||
global: "グローバル"
|
||||
mentions: "あなた宛て"
|
||||
direct: "ダイレクト投稿"
|
||||
notifications: "通知"
|
||||
list: "リスト"
|
||||
swap-left: "左に移動や!"
|
||||
@@ -810,6 +811,7 @@ desktop/views/components/timeline.vue:
|
||||
hybrid: "ソーシャル"
|
||||
global: "グローバル"
|
||||
mentions: "あなた宛て"
|
||||
messages: "メッセージ"
|
||||
list: "リスト"
|
||||
hashtag: "ハッシュタグ"
|
||||
add-tag-timeline: "ハッシュタグを追加"
|
||||
@@ -1140,6 +1142,7 @@ mobile/views/pages/home.vue:
|
||||
hybrid: "ソーシャル"
|
||||
global: "グローバル"
|
||||
mentions: "あなた宛て"
|
||||
messages: "メッセージ"
|
||||
mobile/views/pages/tag.vue:
|
||||
no-posts-found: "ハッシュタグ「{}」が付けられた投稿はあらへんで。"
|
||||
mobile/views/pages/welcome.vue:
|
||||
|
@@ -158,6 +158,7 @@ common:
|
||||
hashtag: "ハッシュタグ"
|
||||
global: "글로벌"
|
||||
mentions: "あなた宛て"
|
||||
direct: "ダイレクト投稿"
|
||||
notifications: "통지"
|
||||
list: "목록"
|
||||
swap-left: "左に移動"
|
||||
@@ -810,6 +811,7 @@ desktop/views/components/timeline.vue:
|
||||
hybrid: "ソーシャル"
|
||||
global: "グローバル"
|
||||
mentions: "あなた宛て"
|
||||
messages: "メッセージ"
|
||||
list: "リスト"
|
||||
hashtag: "ハッシュタグ"
|
||||
add-tag-timeline: "ハッシュタグを追加"
|
||||
@@ -1140,6 +1142,7 @@ mobile/views/pages/home.vue:
|
||||
hybrid: "ソーシャル"
|
||||
global: "グローバル"
|
||||
mentions: "あなた宛て"
|
||||
messages: "メッセージ"
|
||||
mobile/views/pages/tag.vue:
|
||||
no-posts-found: "ハッシュタグ「{}」が付けられた投稿は見つかりませんでした。"
|
||||
mobile/views/pages/welcome.vue:
|
||||
|
@@ -158,6 +158,7 @@ common:
|
||||
hashtag: "ハッシュタグ"
|
||||
global: "グローバル"
|
||||
mentions: "あなた宛て"
|
||||
direct: "ダイレクト投稿"
|
||||
notifications: "通知"
|
||||
list: "リスト"
|
||||
swap-left: "左に移動"
|
||||
@@ -810,6 +811,7 @@ desktop/views/components/timeline.vue:
|
||||
hybrid: "ソーシャル"
|
||||
global: "Algemeen"
|
||||
mentions: "あなた宛て"
|
||||
messages: "メッセージ"
|
||||
list: "Lijsten"
|
||||
hashtag: "ハッシュタグ"
|
||||
add-tag-timeline: "ハッシュタグを追加"
|
||||
@@ -1140,6 +1142,7 @@ mobile/views/pages/home.vue:
|
||||
hybrid: "ソーシャル"
|
||||
global: "グローバル"
|
||||
mentions: "あなた宛て"
|
||||
messages: "メッセージ"
|
||||
mobile/views/pages/tag.vue:
|
||||
no-posts-found: "ハッシュタグ「{}」が付けられた投稿は見つかりませんでした。"
|
||||
mobile/views/pages/welcome.vue:
|
||||
|
@@ -158,6 +158,7 @@ common:
|
||||
hashtag: "ハッシュタグ"
|
||||
global: "グローバル"
|
||||
mentions: "あなた宛て"
|
||||
direct: "ダイレクト投稿"
|
||||
notifications: "通知"
|
||||
list: "リスト"
|
||||
swap-left: "左に移動"
|
||||
@@ -810,6 +811,7 @@ desktop/views/components/timeline.vue:
|
||||
hybrid: "ソーシャル"
|
||||
global: "グローバル"
|
||||
mentions: "あなた宛て"
|
||||
messages: "メッセージ"
|
||||
list: "リスト"
|
||||
hashtag: "ハッシュタグ"
|
||||
add-tag-timeline: "ハッシュタグを追加"
|
||||
@@ -1140,6 +1142,7 @@ mobile/views/pages/home.vue:
|
||||
hybrid: "ソーシャル"
|
||||
global: "グローバル"
|
||||
mentions: "あなた宛て"
|
||||
messages: "メッセージ"
|
||||
mobile/views/pages/tag.vue:
|
||||
no-posts-found: "ハッシュタグ「{}」が付けられた投稿は見つかりませんでした。"
|
||||
mobile/views/pages/welcome.vue:
|
||||
|
@@ -158,6 +158,7 @@ common:
|
||||
hashtag: "ハッシュタグ"
|
||||
global: "Globalne"
|
||||
mentions: "あなた宛て"
|
||||
direct: "ダイレクト投稿"
|
||||
notifications: "Powiadomienia"
|
||||
list: "Listy"
|
||||
swap-left: "Przesuń w lewo"
|
||||
@@ -810,6 +811,7 @@ desktop/views/components/timeline.vue:
|
||||
hybrid: "Społeczność"
|
||||
global: "Globalne"
|
||||
mentions: "あなた宛て"
|
||||
messages: "メッセージ"
|
||||
list: "Listy"
|
||||
hashtag: "ハッシュタグ"
|
||||
add-tag-timeline: "ハッシュタグを追加"
|
||||
@@ -1140,6 +1142,7 @@ mobile/views/pages/home.vue:
|
||||
hybrid: "Społeczność"
|
||||
global: "Globalne"
|
||||
mentions: "あなた宛て"
|
||||
messages: "メッセージ"
|
||||
mobile/views/pages/tag.vue:
|
||||
no-posts-found: "Nie znaleziono wpisów zawierających „{}”."
|
||||
mobile/views/pages/welcome.vue:
|
||||
|
@@ -158,6 +158,7 @@ common:
|
||||
hashtag: "ハッシュタグ"
|
||||
global: "Global"
|
||||
mentions: "あなた宛て"
|
||||
direct: "ダイレクト投稿"
|
||||
notifications: "Notificações"
|
||||
list: "Listas"
|
||||
swap-left: "Mover para a esquerda"
|
||||
@@ -810,6 +811,7 @@ desktop/views/components/timeline.vue:
|
||||
hybrid: "ソーシャル"
|
||||
global: "グローバル"
|
||||
mentions: "あなた宛て"
|
||||
messages: "メッセージ"
|
||||
list: "リスト"
|
||||
hashtag: "ハッシュタグ"
|
||||
add-tag-timeline: "ハッシュタグを追加"
|
||||
@@ -1140,6 +1142,7 @@ mobile/views/pages/home.vue:
|
||||
hybrid: "ソーシャル"
|
||||
global: "グローバル"
|
||||
mentions: "あなた宛て"
|
||||
messages: "メッセージ"
|
||||
mobile/views/pages/tag.vue:
|
||||
no-posts-found: "ハッシュタグ「{}」が付けられた投稿は見つかりませんでした。"
|
||||
mobile/views/pages/welcome.vue:
|
||||
|
@@ -158,6 +158,7 @@ common:
|
||||
hashtag: "ハッシュタグ"
|
||||
global: "グローバル"
|
||||
mentions: "あなた宛て"
|
||||
direct: "ダイレクト投稿"
|
||||
notifications: "通知"
|
||||
list: "リスト"
|
||||
swap-left: "左に移動"
|
||||
@@ -810,6 +811,7 @@ desktop/views/components/timeline.vue:
|
||||
hybrid: "ソーシャル"
|
||||
global: "グローバル"
|
||||
mentions: "あなた宛て"
|
||||
messages: "メッセージ"
|
||||
list: "リスト"
|
||||
hashtag: "ハッシュタグ"
|
||||
add-tag-timeline: "ハッシュタグを追加"
|
||||
@@ -1140,6 +1142,7 @@ mobile/views/pages/home.vue:
|
||||
hybrid: "ソーシャル"
|
||||
global: "グローバル"
|
||||
mentions: "あなた宛て"
|
||||
messages: "メッセージ"
|
||||
mobile/views/pages/tag.vue:
|
||||
no-posts-found: "ハッシュタグ「{}」が付けられた投稿は見つかりませんでした。"
|
||||
mobile/views/pages/welcome.vue:
|
||||
|
@@ -158,6 +158,7 @@ common:
|
||||
hashtag: "ハッシュタグ"
|
||||
global: "グローバル"
|
||||
mentions: "あなた宛て"
|
||||
direct: "ダイレクト投稿"
|
||||
notifications: "通知"
|
||||
list: "リスト"
|
||||
swap-left: "左に移動"
|
||||
@@ -810,6 +811,7 @@ desktop/views/components/timeline.vue:
|
||||
hybrid: "ソーシャル"
|
||||
global: "グローバル"
|
||||
mentions: "あなた宛て"
|
||||
messages: "メッセージ"
|
||||
list: "リスト"
|
||||
hashtag: "ハッシュタグ"
|
||||
add-tag-timeline: "ハッシュタグを追加"
|
||||
@@ -1140,6 +1142,7 @@ mobile/views/pages/home.vue:
|
||||
hybrid: "ソーシャル"
|
||||
global: "グローバル"
|
||||
mentions: "あなた宛て"
|
||||
messages: "メッセージ"
|
||||
mobile/views/pages/tag.vue:
|
||||
no-posts-found: "ハッシュタグ「{}」が付けられた投稿は見つかりませんでした。"
|
||||
mobile/views/pages/welcome.vue:
|
||||
|
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "misskey",
|
||||
"author": "syuilo <i@syuilo.com>",
|
||||
"version": "8.46.0",
|
||||
"clientVersion": "1.0.9851",
|
||||
"version": "8.50.0",
|
||||
"clientVersion": "1.0.9883",
|
||||
"codename": "nighthike",
|
||||
"main": "./built/index.js",
|
||||
"private": true,
|
||||
@@ -60,7 +60,7 @@
|
||||
"@types/mocha": "5.2.3",
|
||||
"@types/mongodb": "3.1.7",
|
||||
"@types/ms": "0.7.30",
|
||||
"@types/node": "10.10.0",
|
||||
"@types/node": "10.10.1",
|
||||
"@types/portscanner": "2.1.0",
|
||||
"@types/pug": "2.0.4",
|
||||
"@types/qrcode": "1.2.0",
|
||||
|
106
src/client/app/common/hotkey.ts
Normal file
106
src/client/app/common/hotkey.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import keyCode from './keycode';
|
||||
import { concat } from '../../../prelude/array';
|
||||
|
||||
type pattern = {
|
||||
which: string[];
|
||||
ctrl?: boolean;
|
||||
shift?: boolean;
|
||||
alt?: boolean;
|
||||
};
|
||||
|
||||
type action = {
|
||||
patterns: pattern[];
|
||||
|
||||
callback: Function;
|
||||
};
|
||||
|
||||
const getKeyMap = keymap => Object.entries(keymap).map(([patterns, callback]): action => {
|
||||
const result = {
|
||||
patterns: [],
|
||||
callback: callback
|
||||
} as action;
|
||||
|
||||
result.patterns = patterns.split('|').map(part => {
|
||||
const pattern = {
|
||||
which: []
|
||||
} as pattern;
|
||||
|
||||
part.trim().split('+').forEach(key => {
|
||||
key = key.trim().toLowerCase();
|
||||
switch (key) {
|
||||
case 'ctrl': pattern.ctrl = true; break;
|
||||
case 'alt': pattern.alt = true; break;
|
||||
case 'shift': pattern.shift = true; break;
|
||||
default: pattern.which = keyCode(key).map(k => k.toLowerCase());
|
||||
}
|
||||
});
|
||||
|
||||
return pattern;
|
||||
});
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
const ignoreElemens = ['input', 'textarea'];
|
||||
|
||||
export default {
|
||||
install(Vue) {
|
||||
Vue.directive('hotkey', {
|
||||
bind(el, binding) {
|
||||
el._hotkey_global = binding.modifiers.global === true;
|
||||
|
||||
const actions = getKeyMap(binding.value);
|
||||
|
||||
// flatten
|
||||
const reservedKeys = concat(concat(actions.map(a => a.patterns.map(p => p.which))));
|
||||
|
||||
el.dataset.reservedKeys = reservedKeys.map(key => `'${key}'`).join(' ');
|
||||
|
||||
el._keyHandler = e => {
|
||||
const key = e.code.toLowerCase();
|
||||
|
||||
const targetReservedKeys = document.activeElement ? ((document.activeElement as any).dataset || {}).reservedKeys || '' : '';
|
||||
if (document.activeElement && ignoreElemens.some(el => document.activeElement.matches(el))) return;
|
||||
|
||||
for (const action of actions) {
|
||||
if (el._hotkey_global && targetReservedKeys.includes(`'${key}'`)) break;
|
||||
|
||||
const matched = action.patterns.some(pattern => {
|
||||
let matched = pattern.which.includes(key);
|
||||
if (pattern.ctrl && !e.ctrlKey) matched = false;
|
||||
if (pattern.shift && !e.shiftKey) matched = false;
|
||||
if (pattern.alt && !e.altKey) matched = false;
|
||||
|
||||
if (matched) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
action.callback(e);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
if (matched) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (el._hotkey_global) {
|
||||
document.addEventListener('keydown', el._keyHandler);
|
||||
} else {
|
||||
el.addEventListener('keydown', el._keyHandler);
|
||||
}
|
||||
},
|
||||
|
||||
unbind(el) {
|
||||
if (el._hotkey_global) {
|
||||
document.removeEventListener('keydown', el._keyHandler);
|
||||
} else {
|
||||
el.removeEventListener('keydown', el._keyHandler);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
33
src/client/app/common/keycode.ts
Normal file
33
src/client/app/common/keycode.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
export default (input: string): string[] => {
|
||||
if (Object.keys(aliases).some(a => a.toLowerCase() == input.toLowerCase())) {
|
||||
const codes = aliases[input];
|
||||
return Array.isArray(codes) ? codes : [codes];
|
||||
} else {
|
||||
return [input];
|
||||
}
|
||||
};
|
||||
|
||||
export const aliases = {
|
||||
'esc': 'Escape',
|
||||
'enter': ['Enter', 'NumpadEnter'],
|
||||
'up': 'ArrowUp',
|
||||
'down': 'ArrowDown',
|
||||
'left': 'ArrowLeft',
|
||||
'right': 'ArrowRight',
|
||||
'plus': ['NumpadAdd', 'Semicolon'],
|
||||
};
|
||||
|
||||
/*!
|
||||
* Programatically add the following
|
||||
*/
|
||||
|
||||
// lower case chars
|
||||
for (let i = 97; i < 123; i++) {
|
||||
const char = String.fromCharCode(i);
|
||||
aliases[char] = `Key${char.toUpperCase()}`;
|
||||
}
|
||||
|
||||
// numbers
|
||||
for (let i = 0; i < 10; i++) {
|
||||
aliases[i] = [`Numpad${i}`, `Digit${i}`];
|
||||
}
|
@@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<div class="mk-reaction-picker">
|
||||
<div class="mk-reaction-picker" v-hotkey.global="keymap">
|
||||
<div class="backdrop" ref="backdrop" @click="close"></div>
|
||||
<div class="popover" :class="{ compact, big }" ref="popover">
|
||||
<p v-if="!compact">{{ title }}</p>
|
||||
<div>
|
||||
<div ref="buttons" :class="{ showFocus }">
|
||||
<button @click="react('like')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="1" title="%i18n:common.reactions.like%"><mk-reaction-icon reaction='like'/></button>
|
||||
<button @click="react('love')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="2" title="%i18n:common.reactions.love%"><mk-reaction-icon reaction='love'/></button>
|
||||
<button @click="react('laugh')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="3" title="%i18n:common.reactions.laugh%"><mk-reaction-icon reaction='laugh'/></button>
|
||||
@@ -31,30 +31,84 @@ export default Vue.extend({
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
|
||||
source: {
|
||||
required: true
|
||||
},
|
||||
|
||||
compact: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
|
||||
cb: {
|
||||
required: false
|
||||
},
|
||||
|
||||
big: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
|
||||
showFocus: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
|
||||
animation: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
title: placeholder
|
||||
title: placeholder,
|
||||
focus: null
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
keymap(): any {
|
||||
return {
|
||||
'esc': this.close,
|
||||
'enter|space|plus': this.choose,
|
||||
'up': this.focusUp,
|
||||
'right': this.focusRight,
|
||||
'down': this.focusDown,
|
||||
'left': this.focusLeft,
|
||||
'1': () => this.react('like'),
|
||||
'2': () => this.react('love'),
|
||||
'3': () => this.react('laugh'),
|
||||
'4': () => this.react('hmm'),
|
||||
'5': () => this.react('surprise'),
|
||||
'6': () => this.react('congrats'),
|
||||
'7': () => this.react('angry'),
|
||||
'8': () => this.react('confused'),
|
||||
'9': () => this.react('rip'),
|
||||
'0': () => this.react('pudding'),
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
focus(i) {
|
||||
this.$refs.buttons.childNodes[i].focus();
|
||||
|
||||
if (this.showFocus) {
|
||||
this.title = this.$refs.buttons.childNodes[i].title;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.focus = 0;
|
||||
|
||||
const popover = this.$refs.popover as any;
|
||||
|
||||
const rect = this.source.getBoundingClientRect();
|
||||
@@ -76,7 +130,7 @@ export default Vue.extend({
|
||||
anime({
|
||||
targets: this.$refs.backdrop,
|
||||
opacity: 1,
|
||||
duration: 100,
|
||||
duration: this.animation ? 100 : 0,
|
||||
easing: 'linear'
|
||||
});
|
||||
|
||||
@@ -84,10 +138,11 @@ export default Vue.extend({
|
||||
targets: this.$refs.popover,
|
||||
opacity: 1,
|
||||
scale: [0.5, 1],
|
||||
duration: 500
|
||||
duration: this.animation ? 500 : 0
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
methods: {
|
||||
react(reaction) {
|
||||
(this as any).api('notes/reactions/create', {
|
||||
@@ -95,21 +150,25 @@ export default Vue.extend({
|
||||
reaction: reaction
|
||||
}).then(() => {
|
||||
if (this.cb) this.cb();
|
||||
this.$emit('closed');
|
||||
this.destroyDom();
|
||||
});
|
||||
},
|
||||
|
||||
onMouseover(e) {
|
||||
this.title = e.target.title;
|
||||
},
|
||||
|
||||
onMouseout(e) {
|
||||
this.title = placeholder;
|
||||
},
|
||||
|
||||
close() {
|
||||
(this.$refs.backdrop as any).style.pointerEvents = 'none';
|
||||
anime({
|
||||
targets: this.$refs.backdrop,
|
||||
opacity: 0,
|
||||
duration: 200,
|
||||
duration: this.animation ? 200 : 0,
|
||||
easing: 'linear'
|
||||
});
|
||||
|
||||
@@ -118,10 +177,33 @@ export default Vue.extend({
|
||||
targets: this.$refs.popover,
|
||||
opacity: 0,
|
||||
scale: 0.5,
|
||||
duration: 200,
|
||||
duration: this.animation ? 200 : 0,
|
||||
easing: 'easeInBack',
|
||||
complete: () => this.destroyDom()
|
||||
complete: () => {
|
||||
this.$emit('closed');
|
||||
this.destroyDom();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
focusUp() {
|
||||
this.focus = this.focus == 0 ? 9 : this.focus < 5 ? (this.focus + 4) : (this.focus - 5);
|
||||
},
|
||||
|
||||
focusDown() {
|
||||
this.focus = this.focus == 9 ? 0 : this.focus >= 5 ? (this.focus - 4) : (this.focus + 5);
|
||||
},
|
||||
|
||||
focusRight() {
|
||||
this.focus = this.focus == 9 ? 0 : (this.focus + 1);
|
||||
},
|
||||
|
||||
focusLeft() {
|
||||
this.focus = this.focus == 0 ? 9 : (this.focus - 1);
|
||||
},
|
||||
|
||||
choose() {
|
||||
this.$refs.buttons.childNodes[this.focus].click();
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -207,6 +289,21 @@ root(isDark)
|
||||
width 240px
|
||||
text-align center
|
||||
|
||||
&.showFocus
|
||||
> button:focus
|
||||
z-index 1
|
||||
|
||||
&:after
|
||||
content ""
|
||||
pointer-events none
|
||||
position absolute
|
||||
top 0
|
||||
right 0
|
||||
bottom 0
|
||||
left 0
|
||||
border 2px solid rgba($theme-color, 0.3)
|
||||
border-radius 4px
|
||||
|
||||
> button
|
||||
padding 0
|
||||
width 40px
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<mk-window ref="window" is-modal width="800px" height="500px" @closed="$destroy">
|
||||
<mk-window ref="window" is-modal width="800px" height="500px" @closed="destroyDom">
|
||||
<span slot="header">
|
||||
<span v-html="title" :class="$style.title"></span>
|
||||
<span :class="$style.count" v-if="multiple && files.length > 0">({{ files.length }}%i18n:@choose-file%)</span>
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<mk-window ref="window" is-modal width="800px" height="500px" @closed="$destroy">
|
||||
<mk-window ref="window" is-modal width="800px" height="500px" @closed="destroyDom">
|
||||
<span slot="header">
|
||||
<span v-html="title" :class="$style.title"></span>
|
||||
</span>
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<mk-window ref="window" @closed="$destroy" width="800px" height="500px" :popout-url="popout">
|
||||
<mk-window ref="window" @closed="destroyDom" width="800px" height="500px" :popout-url="popout">
|
||||
<template slot="header">
|
||||
<p v-if="usage" :class="$style.info"><b>{{ usage.toFixed(1) }}%</b> %i18n:@used%</p>
|
||||
<span :class="$style.title">%fa:cloud%%i18n:@drive%</span>
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<mk-window width="400px" height="550px" @closed="$destroy">
|
||||
<mk-window width="400px" height="550px" @closed="destroyDom">
|
||||
<span slot="header" :class="$style.header">
|
||||
<img :src="user.avatarUrl" alt=""/>{{ '%i18n:@followers%'.replace('{}', name) }}
|
||||
</span>
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<mk-window width="400px" height="550px" @closed="$destroy">
|
||||
<mk-window width="400px" height="550px" @closed="destroyDom">
|
||||
<span slot="header" :class="$style.header">
|
||||
<img :src="user.avatarUrl" alt=""/>{{ '%i18n:@following%'.replace('{}', name) }}
|
||||
</span>
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<mk-window ref="window" width="500px" height="560px" :popout-url="popout" @closed="$destroy">
|
||||
<mk-window ref="window" width="500px" height="560px" :popout-url="popout" @closed="destroyDom">
|
||||
<span slot="header" :class="$style.header">%fa:gamepad%%i18n:@game%</span>
|
||||
<mk-reversi :class="$style.content" @gamed="g => game = g"/>
|
||||
</mk-window>
|
||||
|
@@ -237,6 +237,10 @@ export default Vue.extend({
|
||||
|
||||
warp(date) {
|
||||
(this.$refs.tl as any).warp(date);
|
||||
},
|
||||
|
||||
focus() {
|
||||
(this.$refs.tl as any).focus();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<mk-window ref="window" is-modal width="500px" @before-close="beforeClose" @closed="$destroy">
|
||||
<mk-window ref="window" is-modal width="500px" @before-close="beforeClose" @closed="destroyDom">
|
||||
<span slot="header" :class="$style.header">
|
||||
%fa:i-cursor%{{ title }}
|
||||
</span>
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<mk-window ref="window" width="500px" height="560px" :popout-url="popout" @closed="$destroy">
|
||||
<mk-window ref="window" width="500px" height="560px" :popout-url="popout" @closed="destroyDom">
|
||||
<span slot="header" :class="$style.header">%fa:comments%%i18n:@title% {{ user | userName }}</span>
|
||||
<mk-messaging-room :user="user" :class="$style.content"/>
|
||||
</mk-window>
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<mk-window ref="window" width="500px" height="560px" @closed="$destroy">
|
||||
<mk-window ref="window" width="500px" height="560px" @closed="destroyDom">
|
||||
<span slot="header" :class="$style.header">%fa:comments%%i18n:@title%</span>
|
||||
<mk-messaging :class="$style.content" @navigate="navigate"/>
|
||||
</mk-window>
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="note" tabindex="-1" :title="title" @keydown="onKeydown">
|
||||
<div class="note" tabindex="-1" v-hotkey="keymap" :title="title">
|
||||
<div class="reply-to" v-if="p.reply && (!$store.getters.isSignedIn || $store.state.settings.showReplyTarget)">
|
||||
<x-sub :note="p.reply"/>
|
||||
</div>
|
||||
@@ -48,7 +48,7 @@
|
||||
<button class="renoteButton" @click="renote" title="%i18n:@renote%">
|
||||
%fa:retweet%<p class="count" v-if="p.renoteCount > 0">{{ p.renoteCount }}</p>
|
||||
</button>
|
||||
<button class="reactionButton" :class="{ reacted: p.myReaction != null }" @click="react" ref="reactButton" title="%i18n:@add-reaction%">
|
||||
<button class="reactionButton" :class="{ reacted: p.myReaction != null }" @click="react()" ref="reactButton" title="%i18n:@add-reaction%">
|
||||
%fa:plus%<p class="count" v-if="p.reactions_count > 0">{{ p.reactions_count }}</p>
|
||||
</button>
|
||||
<button @click="menu" ref="menuButton">
|
||||
@@ -111,6 +111,16 @@ export default Vue.extend({
|
||||
},
|
||||
|
||||
computed: {
|
||||
keymap(): any {
|
||||
return {
|
||||
'r|left': this.reply,
|
||||
'a|plus': () => this.react(true),
|
||||
'n|right': this.renote,
|
||||
'up|shift+tab': this.focusBefore,
|
||||
'down|tab': this.focusAfter,
|
||||
};
|
||||
},
|
||||
|
||||
isRenote(): boolean {
|
||||
return (this.note.renote &&
|
||||
this.note.text == null &&
|
||||
@@ -223,64 +233,46 @@ export default Vue.extend({
|
||||
reply() {
|
||||
(this as any).os.new(MkPostFormWindow, {
|
||||
reply: this.p
|
||||
});
|
||||
}).$once('closed', this.focus);
|
||||
},
|
||||
|
||||
renote() {
|
||||
(this as any).os.new(MkRenoteFormWindow, {
|
||||
note: this.p
|
||||
});
|
||||
}).$once('closed', this.focus);
|
||||
},
|
||||
|
||||
react() {
|
||||
react(viaKeyboard = false) {
|
||||
this.blur();
|
||||
(this as any).os.new(MkReactionPicker, {
|
||||
source: this.$refs.reactButton,
|
||||
note: this.p
|
||||
});
|
||||
note: this.p,
|
||||
showFocus: viaKeyboard,
|
||||
animation: !viaKeyboard
|
||||
}).$once('closed', this.focus);
|
||||
},
|
||||
|
||||
menu() {
|
||||
(this as any).os.new(MkNoteMenu, {
|
||||
source: this.$refs.menuButton,
|
||||
note: this.p
|
||||
});
|
||||
}).$once('closed', this.focus);
|
||||
},
|
||||
|
||||
onKeydown(e) {
|
||||
let shouldBeCancel = true;
|
||||
focus() {
|
||||
this.$el.focus();
|
||||
},
|
||||
|
||||
switch (true) {
|
||||
case e.which == 38: // [↑]
|
||||
case e.which == 74: // [j]
|
||||
case e.which == 9 && e.shiftKey: // [Shift] + [Tab]
|
||||
focus(this.$el, e => e.previousElementSibling);
|
||||
break;
|
||||
blur() {
|
||||
this.$el.blur();
|
||||
},
|
||||
|
||||
case e.which == 40: // [↓]
|
||||
case e.which == 75: // [k]
|
||||
case e.which == 9: // [Tab]
|
||||
focus(this.$el, e => e.nextElementSibling);
|
||||
break;
|
||||
focusBefore() {
|
||||
focus(this.$el, e => e.previousElementSibling);
|
||||
},
|
||||
|
||||
case e.which == 81: // [q]
|
||||
case e.which == 69: // [e]
|
||||
this.renote();
|
||||
break;
|
||||
|
||||
case e.which == 70: // [f]
|
||||
case e.which == 76: // [l]
|
||||
//this.like();
|
||||
break;
|
||||
|
||||
case e.which == 82: // [r]
|
||||
this.reply();
|
||||
break;
|
||||
|
||||
default:
|
||||
shouldBeCancel = false;
|
||||
}
|
||||
|
||||
if (shouldBeCancel) e.preventDefault();
|
||||
focusAfter() {
|
||||
focus(this.$el, e => e.nextElementSibling);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@@ -12,7 +12,7 @@
|
||||
<!-- トランジションを有効にするとなぜかメモリリークする -->
|
||||
<component :is="!$store.state.device.reduceMotion ? 'transition-group' : 'div'" name="mk-notes" class="notes transition" tag="div">
|
||||
<template v-for="(note, i) in _notes">
|
||||
<x-note :note="note" :key="note.id" @update:note="onNoteUpdated(i, $event)"/>
|
||||
<x-note :note="note" :key="note.id" @update:note="onNoteUpdated(i, $event)" ref="note"/>
|
||||
<p class="date" :key="note.id + '_date'" v-if="i != notes.length - 1 && note._date != _notes[i + 1]._date">
|
||||
<span>%fa:angle-up%{{ note._datetext }}</span>
|
||||
<span>%fa:angle-down%{{ _notes[i + 1]._datetext }}</span>
|
||||
@@ -89,7 +89,7 @@ export default Vue.extend({
|
||||
},
|
||||
|
||||
focus() {
|
||||
(this.$el as any).children[0].focus();
|
||||
(this.$refs.note as any)[0].focus();
|
||||
},
|
||||
|
||||
onNoteUpdated(i, note) {
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<mk-window class="mk-post-form-window" ref="window" is-modal @closed="$destroy">
|
||||
<mk-window class="mk-post-form-window" ref="window" is-modal @closed="onWindowClosed">
|
||||
<span slot="header" class="mk-post-form-window--header">
|
||||
<span class="icon" v-if="geo">%fa:map-marker-alt%</span>
|
||||
<span v-if="!reply">%i18n:@note%</span>
|
||||
@@ -53,6 +53,10 @@ export default Vue.extend({
|
||||
},
|
||||
onPosted() {
|
||||
(this.$refs.window as any).close();
|
||||
},
|
||||
onWindowClosed() {
|
||||
this.$emit('closed');
|
||||
this.destroyDom();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<mk-window ref="window" :is-modal="false" :can-close="false" width="500px" @closed="$destroy">
|
||||
<mk-window ref="window" :is-modal="false" :can-close="false" width="500px" @closed="destroyDom">
|
||||
<span slot="header">{{ title }}<mk-ellipsis/></span>
|
||||
<div :class="$style.body">
|
||||
<p :class="$style.init" v-if="isNaN(value)">%i18n:@waiting%<mk-ellipsis/></p>
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<mk-window ref="window" is-modal width="450px" height="500px" @closed="$destroy">
|
||||
<mk-window ref="window" is-modal width="450px" height="500px" @closed="destroyDom">
|
||||
<span slot="header">%fa:envelope R% %i18n:@title%</span>
|
||||
|
||||
<div class="slpqaxdoxhvglersgjukmvizkqbmbokc" :data-darkmode="$store.state.device.darkmode">
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<mk-window ref="window" is-modal @closed="$destroy">
|
||||
<mk-window ref="window" is-modal @closed="onWindowClosed">
|
||||
<span slot="header" :class="$style.header">%fa:retweet%%i18n:@title%</span>
|
||||
<mk-renote-form ref="form" :note="note" @posted="onPosted" @canceled="onCanceled"/>
|
||||
<mk-renote-form ref="form" :note="note" @posted="onPosted" @canceled="onCanceled" v-hotkey.global="keymap"/>
|
||||
</mk-window>
|
||||
</template>
|
||||
|
||||
@@ -10,25 +10,32 @@ import Vue from 'vue';
|
||||
|
||||
export default Vue.extend({
|
||||
props: ['note'],
|
||||
mounted() {
|
||||
document.addEventListener('keydown', this.onDocumentKeydown);
|
||||
},
|
||||
beforeDestroy() {
|
||||
document.removeEventListener('keydown', this.onDocumentKeydown);
|
||||
|
||||
computed: {
|
||||
keymap(): any {
|
||||
return {
|
||||
'esc': this.close,
|
||||
'ctrl+enter': this.post
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
onDocumentKeydown(e) {
|
||||
if (e.target.tagName != 'INPUT' && e.target.tagName != 'TEXTAREA') {
|
||||
if (e.which == 27) { // Esc
|
||||
(this.$refs.window as any).close();
|
||||
}
|
||||
}
|
||||
post() {
|
||||
(this.$refs.form as any).ok();
|
||||
},
|
||||
close() {
|
||||
(this.$refs.window as any).close();
|
||||
},
|
||||
onPosted() {
|
||||
(this.$refs.window as any).close();
|
||||
},
|
||||
onCanceled() {
|
||||
(this.$refs.window as any).close();
|
||||
},
|
||||
onWindowClosed() {
|
||||
this.$emit('closed');
|
||||
this.destroyDom();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<mk-window ref="window" is-modal width="700px" height="550px" @closed="$destroy">
|
||||
<mk-window ref="window" is-modal width="700px" height="550px" @closed="destroyDom">
|
||||
<span slot="header" :class="$style.header">%fa:cog%%i18n:@settings%</span>
|
||||
<mk-settings :initial-page="initialPage" @done="close"/>
|
||||
</mk-window>
|
||||
|
@@ -152,14 +152,11 @@ export default Vue.extend({
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', this.onKeydown);
|
||||
|
||||
this.fetch();
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
this.$emit('beforeDestroy');
|
||||
document.removeEventListener('keydown', this.onKeydown);
|
||||
},
|
||||
|
||||
methods: {
|
||||
@@ -212,14 +209,6 @@ export default Vue.extend({
|
||||
warp(date) {
|
||||
this.date = date;
|
||||
this.fetch();
|
||||
},
|
||||
|
||||
onKeydown(e) {
|
||||
if (e.target.tagName != 'INPUT' && e.target.tagName != 'TEXTAREA') {
|
||||
if (e.which == 84) { // t
|
||||
this.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@@ -92,6 +92,10 @@ export default Vue.extend({
|
||||
});
|
||||
},
|
||||
|
||||
focus() {
|
||||
(this.$refs.tl as any).focus();
|
||||
},
|
||||
|
||||
warp(date) {
|
||||
(this.$refs.tl as any).warp(date);
|
||||
},
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="mk-ui" :style="style">
|
||||
<div class="mk-ui" :style="style" v-hotkey.global="keymap">
|
||||
<x-header class="header" v-show="!zenMode"/>
|
||||
<div class="content">
|
||||
<slot></slot>
|
||||
@@ -16,11 +16,13 @@ export default Vue.extend({
|
||||
components: {
|
||||
XHeader
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
zenMode: false
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
style(): any {
|
||||
if (!this.$store.getters.isSignedIn || this.$store.state.i.wallpaperUrl == null) return {};
|
||||
@@ -28,27 +30,24 @@ export default Vue.extend({
|
||||
backgroundColor: this.$store.state.i.wallpaperColor && this.$store.state.i.wallpaperColor.length == 3 ? `rgb(${ this.$store.state.i.wallpaperColor.join(',') })` : null,
|
||||
backgroundImage: `url(${ this.$store.state.i.wallpaperUrl })`
|
||||
};
|
||||
},
|
||||
|
||||
keymap(): any {
|
||||
return {
|
||||
'p': this.post,
|
||||
'n': this.post,
|
||||
'z': this.toggleZenMode
|
||||
};
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
document.addEventListener('keydown', this.onKeydown);
|
||||
},
|
||||
beforeDestroy() {
|
||||
document.removeEventListener('keydown', this.onKeydown);
|
||||
},
|
||||
|
||||
methods: {
|
||||
onKeydown(e) {
|
||||
if (e.target.tagName == 'INPUT' || e.target.tagName == 'TEXTAREA') return;
|
||||
post() {
|
||||
(this as any).apis.post();
|
||||
},
|
||||
|
||||
if (e.which == 80 || e.which == 78) { // p or n
|
||||
e.preventDefault();
|
||||
(this as any).apis.post();
|
||||
}
|
||||
|
||||
if (e.which == 90) { // z
|
||||
e.preventDefault();
|
||||
this.zenMode = !this.zenMode;
|
||||
}
|
||||
toggleZenMode() {
|
||||
this.zenMode = !this.zenMode;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<mk-window ref="window" is-modal width="450px" height="500px" @closed="$destroy">
|
||||
<mk-window ref="window" is-modal width="450px" height="500px" @closed="destroyDom">
|
||||
<span slot="header">%fa:list% %i18n:@title%</span>
|
||||
|
||||
<div class="xkxvokkjlptzyewouewmceqcxhpgzprp" :data-darkmode="$store.state.device.darkmode">
|
||||
|
@@ -190,8 +190,8 @@ export default Vue.extend({
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
this.destroyDom();
|
||||
this.$emit('closed');
|
||||
this.destroyDom();
|
||||
}, 300);
|
||||
},
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<mk-ui>
|
||||
<mk-home :mode="mode" @loaded="loaded"/>
|
||||
<mk-home :mode="mode" @loaded="loaded" ref="home" v-hotkey.global="keymap"/>
|
||||
</mk-ui>
|
||||
</template>
|
||||
|
||||
@@ -15,6 +15,13 @@ export default Vue.extend({
|
||||
default: 'timeline'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
keymap(): any {
|
||||
return {
|
||||
't': this.focus
|
||||
};
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
document.title = (this as any).os.instanceName;
|
||||
|
||||
@@ -23,6 +30,9 @@ export default Vue.extend({
|
||||
methods: {
|
||||
loaded() {
|
||||
Progress.done();
|
||||
},
|
||||
focus() {
|
||||
this.$refs.home.focus();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@@ -6,7 +6,7 @@
|
||||
<main>
|
||||
<div class="main">
|
||||
<x-header :user="user"/>
|
||||
<mk-note-detail v-if="user.pinnedNote" :note="user.pinnedNote" :compact="true"/>
|
||||
<mk-note-detail v-for="n in user.pinnedNotes" :key="n.id" :note="n" :compact="true"/>
|
||||
<x-timeline class="timeline" ref="tl" :user="user"/>
|
||||
</div>
|
||||
<div class="side">
|
||||
@@ -28,7 +28,6 @@
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import parseAcct from '../../../../../../misc/acct/parse';
|
||||
import getUserName from '../../../../../../misc/get-user-name';
|
||||
import Progress from '../../../../common/scripts/loading';
|
||||
import XHeader from './user.header.vue';
|
||||
import XTimeline from './user.timeline.vue';
|
||||
|
@@ -8,6 +8,7 @@ import VueRouter from 'vue-router';
|
||||
import * as TreeView from 'vue-json-tree-view';
|
||||
import VAnimateCss from 'v-animate-css';
|
||||
import VModal from 'vue-js-modal';
|
||||
import VueHotkey from './common/hotkey';
|
||||
|
||||
import App from './app.vue';
|
||||
import checkForUpdate from './common/scripts/check-for-update';
|
||||
@@ -19,6 +20,7 @@ Vue.use(VueRouter);
|
||||
Vue.use(TreeView);
|
||||
Vue.use(VAnimateCss);
|
||||
Vue.use(VModal);
|
||||
Vue.use(VueHotkey);
|
||||
|
||||
// Register global directives
|
||||
require('./common/views/directives');
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="root home">
|
||||
<mk-note-detail v-if="user.pinnedNote" :note="user.pinnedNote" :compact="true"/>
|
||||
<mk-note-detail v-for="n in user.pinnedNotes" :key="n.id" :note="n" :compact="true"/>
|
||||
<section class="recent-notes">
|
||||
<h2>%fa:R comments%%i18n:@recent-notes%</h2>
|
||||
<div>
|
||||
|
@@ -101,15 +101,15 @@ props:
|
||||
ja-JP: "投稿の数"
|
||||
en-US: "The number of the notes of this user"
|
||||
|
||||
pinnedNote:
|
||||
type: "entity(Note)"
|
||||
pinnedNotes:
|
||||
type: "entity(Note)[]"
|
||||
optional: true
|
||||
desc:
|
||||
ja-JP: "ピン留めされた投稿"
|
||||
en-US: "The pinned note of this user"
|
||||
|
||||
pinnedNoteId:
|
||||
type: "id(Note)"
|
||||
pinnedNoteIds:
|
||||
type: "id(Note)[]"
|
||||
optional: true
|
||||
desc:
|
||||
ja-JP: "ピン留めされた投稿のID"
|
||||
|
@@ -35,6 +35,28 @@ User.createIndex('uri', { sparse: true, unique: true });
|
||||
|
||||
export default User;
|
||||
|
||||
// 後方互換性のため
|
||||
User.findOne({
|
||||
pinnedNoteId: { $exists: true }
|
||||
}).then(async x => {
|
||||
if (x == null) return;
|
||||
|
||||
const users = await User.find({
|
||||
pinnedNoteId: { $exists: true }
|
||||
});
|
||||
|
||||
users.forEach(u => {
|
||||
User.update({ _id: u._id }, {
|
||||
$set: {
|
||||
pinnedNoteIds: [(u as any).pinnedNoteId]
|
||||
},
|
||||
$unset: {
|
||||
pinnedNoteId: ''
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
type IUserBase = {
|
||||
_id: mongo.ObjectID;
|
||||
createdAt: Date;
|
||||
@@ -53,7 +75,7 @@ type IUserBase = {
|
||||
wallpaperUrl?: string;
|
||||
data: any;
|
||||
description: string;
|
||||
pinnedNoteId: mongo.ObjectID;
|
||||
pinnedNoteIds: mongo.ObjectID[];
|
||||
|
||||
/**
|
||||
* 凍結されているか否か
|
||||
@@ -464,11 +486,11 @@ export const pack = (
|
||||
}
|
||||
|
||||
if (opts.detail) {
|
||||
if (_user.pinnedNoteId) {
|
||||
// Populate pinned note
|
||||
_user.pinnedNote = packNote(_user.pinnedNoteId, meId, {
|
||||
if (_user.pinnedNoteIds) {
|
||||
// Populate pinned notes
|
||||
_user.pinnedNotes = Promise.all(_user.pinnedNoteIds.map((id: mongo.ObjectId) => packNote(id, meId, {
|
||||
detail: true
|
||||
});
|
||||
})));
|
||||
}
|
||||
|
||||
if (meId && !meId.equals(_user.id)) {
|
||||
|
@@ -21,9 +21,21 @@ export default async (params: any, user: ILocalUser) => new Promise(async (res,
|
||||
return rej('note not found');
|
||||
}
|
||||
|
||||
const pinnedNoteIds = user.pinnedNoteIds || [];
|
||||
|
||||
if (pinnedNoteIds.some(id => id.equals(note._id))) {
|
||||
return rej('already exists');
|
||||
}
|
||||
|
||||
pinnedNoteIds.unshift(note._id);
|
||||
|
||||
if (pinnedNoteIds.length > 5) {
|
||||
pinnedNoteIds.pop();
|
||||
}
|
||||
|
||||
await User.update(user._id, {
|
||||
$set: {
|
||||
pinnedNoteId: note._id
|
||||
pinnedNoteIds: pinnedNoteIds
|
||||
}
|
||||
});
|
||||
|
||||
|
Reference in New Issue
Block a user