Merge branch 'develop'
This commit is contained in:
		
						commit
						7b44727b23
					
				
					 39 changed files with 556 additions and 260 deletions
				
			
		
							
								
								
									
										16
									
								
								CHANGELOG.md
									
										
									
									
									
								
							
							
						
						
									
										16
									
								
								CHANGELOG.md
									
										
									
									
									
								
							| 
						 | 
					@ -5,6 +5,22 @@ If you encounter any problems with updating, please try the following:
 | 
				
			||||||
1. `npm run clean` or `npm run cleanall`
 | 
					1. `npm run clean` or `npm run cleanall`
 | 
				
			||||||
2. Retry update (Don't forget `npm i`)
 | 
					2. Retry update (Don't forget `npm i`)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					11.2.0 (2019/04/18)
 | 
				
			||||||
 | 
					-------------------
 | 
				
			||||||
 | 
					### Improvements
 | 
				
			||||||
 | 
					* 検索で日付(日時)を入力するとタイムラインをその時点まで遡るように
 | 
				
			||||||
 | 
					* APIコンソールでエンドポイントをサジェストするように
 | 
				
			||||||
 | 
					* モバイル版でドライブのメニューを使いやすく
 | 
				
			||||||
 | 
					* サイレンス時に確認を表示するように
 | 
				
			||||||
 | 
					* ユーザーメニューでブロックなどの操作を行う時に確認するように
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Fixes
 | 
				
			||||||
 | 
					* アプリケーション連携画面でパーミッションが表示されない問題を修正
 | 
				
			||||||
 | 
					* アンケートウィジットでもMFMを使用するように
 | 
				
			||||||
 | 
					* フォローしてないユーザーのホーム投稿がSTLに流れてくる問題を修正
 | 
				
			||||||
 | 
					* モバイル版でウィジェットを設定できない問題を修正
 | 
				
			||||||
 | 
					* スプラッシュがクリックに反応するように
 | 
				
			||||||
 | 
					
 | 
				
			||||||
11.1.6 (2019/04/18)
 | 
					11.1.6 (2019/04/18)
 | 
				
			||||||
-------------------
 | 
					-------------------
 | 
				
			||||||
### Fixes
 | 
					### Fixes
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -35,6 +35,7 @@ common:
 | 
				
			||||||
  signup: "新規登録"
 | 
					  signup: "新規登録"
 | 
				
			||||||
  signout: "ログアウト"
 | 
					  signout: "ログアウト"
 | 
				
			||||||
  reload-to-apply-the-setting: "この設定を反映するにはページをリロードする必要があります。今すぐリロードしますか?"
 | 
					  reload-to-apply-the-setting: "この設定を反映するにはページをリロードする必要があります。今すぐリロードしますか?"
 | 
				
			||||||
 | 
					  fetching-as-ap-object: "連合に照会中"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  got-it: "わかった"
 | 
					  got-it: "わかった"
 | 
				
			||||||
  customization-tips:
 | 
					  customization-tips:
 | 
				
			||||||
| 
						 | 
					@ -527,8 +528,12 @@ common/views/components/user-menu.vue:
 | 
				
			||||||
  mention: "メンション"
 | 
					  mention: "メンション"
 | 
				
			||||||
  mute: "ミュート"
 | 
					  mute: "ミュート"
 | 
				
			||||||
  unmute: "ミュート解除"
 | 
					  unmute: "ミュート解除"
 | 
				
			||||||
 | 
					  mute-confirm: "このユーザーをミュートしますか?"
 | 
				
			||||||
 | 
					  unmute-confirm: "このユーザーをミュート解除しますか?"
 | 
				
			||||||
  block: "ブロック"
 | 
					  block: "ブロック"
 | 
				
			||||||
  unblock: "ブロック解除"
 | 
					  unblock: "ブロック解除"
 | 
				
			||||||
 | 
					  block-confirm: "このユーザーをブロックしますか?"
 | 
				
			||||||
 | 
					  unblock-confirm: "このユーザーをブロック解除しますか?"
 | 
				
			||||||
  push-to-list: "リストに追加"
 | 
					  push-to-list: "リストに追加"
 | 
				
			||||||
  select-list: "リストを選択してください"
 | 
					  select-list: "リストを選択してください"
 | 
				
			||||||
  report-abuse: "スパムを報告"
 | 
					  report-abuse: "スパムを報告"
 | 
				
			||||||
| 
						 | 
					@ -536,8 +541,12 @@ common/views/components/user-menu.vue:
 | 
				
			||||||
  report-abuse-reported: "管理者に報告されました。ご協力ありがとうございました。"
 | 
					  report-abuse-reported: "管理者に報告されました。ご協力ありがとうございました。"
 | 
				
			||||||
  silence: "サイレンス"
 | 
					  silence: "サイレンス"
 | 
				
			||||||
  unsilence: "サイレンス解除"
 | 
					  unsilence: "サイレンス解除"
 | 
				
			||||||
 | 
					  silence-confirm: "このユーザーをサイレンスしますか?"
 | 
				
			||||||
 | 
					  unsilence-confirm: "このユーザーをサイレンス解除しますか?"
 | 
				
			||||||
  suspend: "凍結"
 | 
					  suspend: "凍結"
 | 
				
			||||||
  unsuspend: "凍結解除"
 | 
					  unsuspend: "凍結解除"
 | 
				
			||||||
 | 
					  suspend-confirm: "このユーザーを凍結しますか?"
 | 
				
			||||||
 | 
					  unsuspend-confirm: "このユーザーを凍結解除しますか?"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
common/views/components/poll.vue:
 | 
					common/views/components/poll.vue:
 | 
				
			||||||
  vote-to: "「{}」に投票する"
 | 
					  vote-to: "「{}」に投票する"
 | 
				
			||||||
| 
						 | 
					@ -739,6 +748,10 @@ common/views/components/user-list-editor.vue:
 | 
				
			||||||
  delete-are-you-sure: "リスト「$1」を削除しますか?"
 | 
					  delete-are-you-sure: "リスト「$1」を削除しますか?"
 | 
				
			||||||
  deleted: "削除しました"
 | 
					  deleted: "削除しました"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					common/views/components/user-lists.vue:
 | 
				
			||||||
 | 
					  create-list: "リストを作成"
 | 
				
			||||||
 | 
					  list-name: "リスト名"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
common/views/widgets/broadcast.vue:
 | 
					common/views/widgets/broadcast.vue:
 | 
				
			||||||
  fetching: "確認中"
 | 
					  fetching: "確認中"
 | 
				
			||||||
  no-broadcasts: "お知らせはありません"
 | 
					  no-broadcasts: "お知らせはありません"
 | 
				
			||||||
| 
						 | 
					@ -1145,8 +1158,6 @@ desktop/views/components/received-follow-requests-window.vue:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
desktop/views/components/user-lists-window.vue:
 | 
					desktop/views/components/user-lists-window.vue:
 | 
				
			||||||
  title: "リスト"
 | 
					  title: "リスト"
 | 
				
			||||||
  create-list: "リストを作成"
 | 
					 | 
				
			||||||
  list-name: "リスト名"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
desktop/views/components/user-preview.vue:
 | 
					desktop/views/components/user-preview.vue:
 | 
				
			||||||
  notes: "投稿"
 | 
					  notes: "投稿"
 | 
				
			||||||
| 
						 | 
					@ -1336,7 +1347,9 @@ admin/views/users.vue:
 | 
				
			||||||
  unsuspend-confirm: "凍結を解除しますか?"
 | 
					  unsuspend-confirm: "凍結を解除しますか?"
 | 
				
			||||||
  unsuspended: "凍結を解除しました"
 | 
					  unsuspended: "凍結を解除しました"
 | 
				
			||||||
  make-silence: "サイレンス"
 | 
					  make-silence: "サイレンス"
 | 
				
			||||||
 | 
					  silence-confirm: "サイレンスしますか?"
 | 
				
			||||||
  unmake-silence: "サイレンスの解除"
 | 
					  unmake-silence: "サイレンスの解除"
 | 
				
			||||||
 | 
					  unsilence-confirm: "サイレンスを解除しますか?"
 | 
				
			||||||
  verify: "公式アカウントにする"
 | 
					  verify: "公式アカウントにする"
 | 
				
			||||||
  verify-confirm: "公式アカウントにしますか?"
 | 
					  verify-confirm: "公式アカウントにしますか?"
 | 
				
			||||||
  verified: "公式アカウントにしました"
 | 
					  verified: "公式アカウントにしました"
 | 
				
			||||||
| 
						 | 
					@ -1573,12 +1586,11 @@ mobile/views/components/drive.vue:
 | 
				
			||||||
  file-count: "ファイル"
 | 
					  file-count: "ファイル"
 | 
				
			||||||
  nothing-in-drive: "ドライブには何もありません"
 | 
					  nothing-in-drive: "ドライブには何もありません"
 | 
				
			||||||
  folder-is-empty: "このフォルダは空です"
 | 
					  folder-is-empty: "このフォルダは空です"
 | 
				
			||||||
  prompt: "何をしますか?(数字を入力してください): <1 → ファイルをアップロード | 2 → ファイルをURLでアップロード | 3 → フォルダ作成 | 4 → このフォルダ名を変更 | 5 → このフォルダを移動 | 6 → このフォルダを削除>"
 | 
					 | 
				
			||||||
  deletion-alert: "ごめんなさい!フォルダの削除は未実装です...。"
 | 
					 | 
				
			||||||
  folder-name: "フォルダー名"
 | 
					  folder-name: "フォルダー名"
 | 
				
			||||||
  here-is-root: "現在いる場所はルートで、フォルダではありません。"
 | 
					  here-is-root: "現在いる場所はルートで、フォルダではありません。"
 | 
				
			||||||
  url-prompt: "アップロードしたいファイルのURL"
 | 
					  url-prompt: "アップロードしたいファイルのURL"
 | 
				
			||||||
  uploading: "アップロードをリクエストしました。アップロードが完了するまで時間がかかる場合があります。"
 | 
					  uploading: "アップロードをリクエストしました。アップロードが完了するまで時間がかかる場合があります。"
 | 
				
			||||||
 | 
					  folder-name-cannot-empty: "フォルダ名を空白にすることはできません。"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
mobile/views/components/drive-file-chooser.vue:
 | 
					mobile/views/components/drive-file-chooser.vue:
 | 
				
			||||||
  select-file: "ファイルを選択"
 | 
					  select-file: "ファイルを選択"
 | 
				
			||||||
| 
						 | 
					@ -1668,9 +1680,17 @@ mobile/views/components/ui.nav.vue:
 | 
				
			||||||
  admin: "管理"
 | 
					  admin: "管理"
 | 
				
			||||||
  about: "Misskeyについて"
 | 
					  about: "Misskeyについて"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					mobile/views/pages/drive.vue:
 | 
				
			||||||
 | 
					  contextmenu:
 | 
				
			||||||
 | 
					    upload: "ファイルをアップロード"
 | 
				
			||||||
 | 
					    url-upload: "ファイルをURLでアップロード"
 | 
				
			||||||
 | 
					    create-folder: "フォルダーを作成"
 | 
				
			||||||
 | 
					    rename-folder: "フォルダー名を変更"
 | 
				
			||||||
 | 
					    move-folder: "このフォルダを移動"
 | 
				
			||||||
 | 
					    delete-folder: "このフォルダを削除"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
mobile/views/pages/user-lists.vue:
 | 
					mobile/views/pages/user-lists.vue:
 | 
				
			||||||
  title: "リスト"
 | 
					  title: "リスト"
 | 
				
			||||||
  enter-list-name: "リスト名を入力してください"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
mobile/views/pages/signup.vue:
 | 
					mobile/views/pages/signup.vue:
 | 
				
			||||||
  lets-start: "📦 始めましょう"
 | 
					  lets-start: "📦 始めましょう"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,7 @@
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	"name": "misskey",
 | 
						"name": "misskey",
 | 
				
			||||||
	"author": "syuilo <i@syuilo.com>",
 | 
						"author": "syuilo <i@syuilo.com>",
 | 
				
			||||||
	"version": "11.1.6",
 | 
						"version": "11.2.0",
 | 
				
			||||||
	"codename": "daybreak",
 | 
						"codename": "daybreak",
 | 
				
			||||||
	"repository": {
 | 
						"repository": {
 | 
				
			||||||
		"type": "git",
 | 
							"type": "git",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -232,6 +232,8 @@ export default Vue.extend({
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		async silenceUser() {
 | 
							async silenceUser() {
 | 
				
			||||||
 | 
								if (!await this.getConfirmed(this.$t('silence-confirm'))) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			const process = async () => {
 | 
								const process = async () => {
 | 
				
			||||||
				await this.$root.api('admin/silence-user', { userId: this.user.id });
 | 
									await this.$root.api('admin/silence-user', { userId: this.user.id });
 | 
				
			||||||
				this.$root.dialog({
 | 
									this.$root.dialog({
 | 
				
			||||||
| 
						 | 
					@ -251,6 +253,8 @@ export default Vue.extend({
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		async unsilenceUser() {
 | 
							async unsilenceUser() {
 | 
				
			||||||
 | 
								if (!await this.getConfirmed(this.$t('unsilence-confirm'))) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			const process = async () => {
 | 
								const process = async () => {
 | 
				
			||||||
				await this.$root.api('admin/unsilence-user', { userId: this.user.id });
 | 
									await this.$root.api('admin/unsilence-user', { userId: this.user.id });
 | 
				
			||||||
				this.$root.dialog({
 | 
									this.$root.dialog({
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										64
									
								
								src/client/app/common/scripts/search.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								src/client/app/common/scripts/search.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,64 @@
 | 
				
			||||||
 | 
					import { faHistory } from '@fortawesome/free-solid-svg-icons';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function search(v: any, q: string) {
 | 
				
			||||||
 | 
						q = q.trim();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (q.startsWith('@')) {
 | 
				
			||||||
 | 
							v.$router.push(`/${q}`);
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (q.startsWith('#')) {
 | 
				
			||||||
 | 
							v.$router.push(`/tags/${encodeURIComponent(q.substr(1))}`);
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// like 2018/03/12
 | 
				
			||||||
 | 
						if (/^[0-9]{4}\/[0-9]{2}\/[0-9]{2}/.test(q.replace(/-/g, '/'))) {
 | 
				
			||||||
 | 
							const date = new Date(q.replace(/-/g, '/'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 日付しか指定されてない場合、例えば 2018/03/12 ならユーザーは
 | 
				
			||||||
 | 
							// 2018/03/12 のコンテンツを「含む」結果になることを期待するはずなので
 | 
				
			||||||
 | 
							// 23時間59分進める(そのままだと 2018/03/12 00:00:00 「まで」の
 | 
				
			||||||
 | 
							// 結果になってしまい、2018/03/12 のコンテンツは含まれない)
 | 
				
			||||||
 | 
							if (q.replace(/-/g, '/').match(/^[0-9]{4}\/[0-9]{2}\/[0-9]{2}$/)) {
 | 
				
			||||||
 | 
								date.setHours(23, 59, 59, 999);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							v.$root.$emit('warp', date);
 | 
				
			||||||
 | 
							v.$root.dialog({
 | 
				
			||||||
 | 
								icon: faHistory,
 | 
				
			||||||
 | 
								splash: true,
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (q.startsWith('https://')) {
 | 
				
			||||||
 | 
							const dialog = v.$root.dialog({
 | 
				
			||||||
 | 
								type: 'waiting',
 | 
				
			||||||
 | 
								text: v.$t('@.fetching-as-ap-object'),
 | 
				
			||||||
 | 
								showOkButton: false,
 | 
				
			||||||
 | 
								showCancelButton: false,
 | 
				
			||||||
 | 
								cancelableByBgClick: false
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							try {
 | 
				
			||||||
 | 
								const res = await v.$root.api('ap/show', {
 | 
				
			||||||
 | 
									uri: q
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
								dialog.close();
 | 
				
			||||||
 | 
								if (res.type == 'User') {
 | 
				
			||||||
 | 
									v.$router.push(`/@${res.object.username}@${res.object.host}`);
 | 
				
			||||||
 | 
								} else if (res.type == 'Note') {
 | 
				
			||||||
 | 
									v.$router.push(`/notes/${res.object.id}`);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							} catch (e) {
 | 
				
			||||||
 | 
								dialog.close();
 | 
				
			||||||
 | 
								// TODO: Show error
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						v.$router.push(`/search?q=${encodeURIComponent(q)}`);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -6,7 +6,17 @@
 | 
				
			||||||
			<mk-signin/>
 | 
								<mk-signin/>
 | 
				
			||||||
		</template>
 | 
							</template>
 | 
				
			||||||
		<template v-else>
 | 
							<template v-else>
 | 
				
			||||||
			<div class="icon" v-if="!input && !select && !user" :class="type"><fa :icon="icon"/></div>
 | 
								<div class="icon" v-if="icon">
 | 
				
			||||||
 | 
									<fa :icon="icon"/>
 | 
				
			||||||
 | 
								</div>
 | 
				
			||||||
 | 
								<div class="icon" v-else-if="!input && !select && !user" :class="type">
 | 
				
			||||||
 | 
									<fa icon="check" v-if="type === 'success'"/>
 | 
				
			||||||
 | 
									<fa :icon="faTimesCircle" v-if="type === 'error'"/>
 | 
				
			||||||
 | 
									<fa icon="exclamation-triangle" v-if="type === 'warning'"/>
 | 
				
			||||||
 | 
									<fa icon="info-circle" v-if="type === 'info'"/>
 | 
				
			||||||
 | 
									<fa :icon="faQuestionCircle" v-if="type === 'question'"/>
 | 
				
			||||||
 | 
									<fa icon="spinner" pulse v-if="type === 'waiting'"/>
 | 
				
			||||||
 | 
								</div>
 | 
				
			||||||
			<header v-if="title" v-html="title"></header>
 | 
								<header v-if="title" v-html="title"></header>
 | 
				
			||||||
			<div class="body" v-if="text" v-html="text"></div>
 | 
								<div class="body" v-if="text" v-html="text"></div>
 | 
				
			||||||
			<ui-input v-if="input" v-model="inputValue" autofocus :type="input.type || 'text'" :placeholder="input.placeholder" @keydown="onInputKeydown"></ui-input>
 | 
								<ui-input v-if="input" v-model="inputValue" autofocus :type="input.type || 'text'" :placeholder="input.placeholder" @keydown="onInputKeydown"></ui-input>
 | 
				
			||||||
| 
						 | 
					@ -14,8 +24,8 @@
 | 
				
			||||||
			<ui-select v-if="select" v-model="selectedValue" autofocus>
 | 
								<ui-select v-if="select" v-model="selectedValue" autofocus>
 | 
				
			||||||
				<option v-for="item in select.items" :value="item.value">{{ item.text }}</option>
 | 
									<option v-for="item in select.items" :value="item.value">{{ item.text }}</option>
 | 
				
			||||||
			</ui-select>
 | 
								</ui-select>
 | 
				
			||||||
			<ui-horizon-group no-grow class="buttons fit-bottom" v-if="!splash">
 | 
								<ui-horizon-group no-grow class="buttons fit-bottom" v-if="!splash && (showOkButton || showCancelButton)">
 | 
				
			||||||
				<ui-button @click="ok" primary :autofocus="!input && !select && !user">{{ (showCancelButton || input || select || user) ? $t('@.ok') : $t('@.got-it') }}</ui-button>
 | 
									<ui-button @click="ok" v-if="showOkButton" primary :autofocus="!input && !select && !user">{{ (showCancelButton || input || select || user) ? $t('@.ok') : $t('@.got-it') }}</ui-button>
 | 
				
			||||||
				<ui-button @click="cancel" v-if="showCancelButton || input || select || user">{{ $t('@.cancel') }}</ui-button>
 | 
									<ui-button @click="cancel" v-if="showCancelButton || input || select || user">{{ $t('@.cancel') }}</ui-button>
 | 
				
			||||||
			</ui-horizon-group>
 | 
								</ui-horizon-group>
 | 
				
			||||||
		</template>
 | 
							</template>
 | 
				
			||||||
| 
						 | 
					@ -55,10 +65,21 @@ export default Vue.extend({
 | 
				
			||||||
		user: {
 | 
							user: {
 | 
				
			||||||
			required: false
 | 
								required: false
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
							icon: {
 | 
				
			||||||
 | 
								required: false
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							showOkButton: {
 | 
				
			||||||
 | 
								type: Boolean,
 | 
				
			||||||
 | 
								default: true
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
		showCancelButton: {
 | 
							showCancelButton: {
 | 
				
			||||||
			type: Boolean,
 | 
								type: Boolean,
 | 
				
			||||||
			default: false
 | 
								default: false
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
							cancelableByBgClick: {
 | 
				
			||||||
 | 
								type: Boolean,
 | 
				
			||||||
 | 
								default: true
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
		splash: {
 | 
							splash: {
 | 
				
			||||||
			type: Boolean,
 | 
								type: Boolean,
 | 
				
			||||||
			default: false
 | 
								default: false
 | 
				
			||||||
| 
						 | 
					@ -69,22 +90,11 @@ export default Vue.extend({
 | 
				
			||||||
		return {
 | 
							return {
 | 
				
			||||||
			inputValue: this.input && this.input.default ? this.input.default : null,
 | 
								inputValue: this.input && this.input.default ? this.input.default : null,
 | 
				
			||||||
			userInputValue: null,
 | 
								userInputValue: null,
 | 
				
			||||||
			selectedValue: null
 | 
								selectedValue: null,
 | 
				
			||||||
 | 
								faTimesCircle, faQuestionCircle
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	computed: {
 | 
					 | 
				
			||||||
		icon(): any {
 | 
					 | 
				
			||||||
			switch (this.type) {
 | 
					 | 
				
			||||||
				case 'success': return 'check';
 | 
					 | 
				
			||||||
				case 'error': return faTimesCircle;
 | 
					 | 
				
			||||||
				case 'warning': return 'exclamation-triangle';
 | 
					 | 
				
			||||||
				case 'info': return 'info-circle';
 | 
					 | 
				
			||||||
				case 'question': return faQuestionCircle;
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	mounted() {
 | 
						mounted() {
 | 
				
			||||||
		this.$nextTick(() => {
 | 
							this.$nextTick(() => {
 | 
				
			||||||
			(this.$refs.bg as any).style.pointerEvents = 'auto';
 | 
								(this.$refs.bg as any).style.pointerEvents = 'auto';
 | 
				
			||||||
| 
						 | 
					@ -113,6 +123,8 @@ export default Vue.extend({
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	methods: {
 | 
						methods: {
 | 
				
			||||||
		async ok() {
 | 
							async ok() {
 | 
				
			||||||
 | 
								if (!this.showOkButton) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if (this.user) {
 | 
								if (this.user) {
 | 
				
			||||||
				const user = await this.$root.api('users/show', parseAcct(this.userInputValue));
 | 
									const user = await this.$root.api('users/show', parseAcct(this.userInputValue));
 | 
				
			||||||
				if (user) {
 | 
									if (user) {
 | 
				
			||||||
| 
						 | 
					@ -156,7 +168,9 @@ export default Vue.extend({
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		onBgClick() {
 | 
							onBgClick() {
 | 
				
			||||||
			this.cancel();
 | 
								if (this.cancelableByBgClick) {
 | 
				
			||||||
 | 
									this.cancel();
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		onInputKeydown(e) {
 | 
							onInputKeydown(e) {
 | 
				
			||||||
| 
						 | 
					@ -183,9 +197,6 @@ export default Vue.extend({
 | 
				
			||||||
	height 100%
 | 
						height 100%
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	&.splash
 | 
						&.splash
 | 
				
			||||||
		&, *
 | 
					 | 
				
			||||||
			pointer-events none !important
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		> .main
 | 
							> .main
 | 
				
			||||||
			min-width 0
 | 
								min-width 0
 | 
				
			||||||
			width initial
 | 
								width initial
 | 
				
			||||||
| 
						 | 
					@ -243,7 +254,7 @@ export default Vue.extend({
 | 
				
			||||||
				margin-top 8px
 | 
									margin-top 8px
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		> .body
 | 
							> .body
 | 
				
			||||||
			margin 16px 0
 | 
								margin 16px 0 0 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		> .buttons
 | 
							> .buttons
 | 
				
			||||||
			margin-top 16px
 | 
								margin-top 16px
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,7 +6,7 @@
 | 
				
			||||||
	<div class="body">
 | 
						<div class="body">
 | 
				
			||||||
		<p class="init" v-if="init"><fa icon="spinner .spin"/>{{ $t('@.loading') }}</p>
 | 
							<p class="init" v-if="init"><fa icon="spinner .spin"/>{{ $t('@.loading') }}</p>
 | 
				
			||||||
		<p class="empty" v-if="!init && messages.length == 0"><fa icon="info-circle"/>{{ $t('empty') }}</p>
 | 
							<p class="empty" v-if="!init && messages.length == 0"><fa icon="info-circle"/>{{ $t('empty') }}</p>
 | 
				
			||||||
		<p class="no-history" v-if="!init && messages.length > 0 && !existMoreMessages"><fa icon="flag"/>{{ $t('no-history') }}</p>
 | 
							<p class="no-history" v-if="!init && messages.length > 0 && !existMoreMessages"><fa :icon="faFlag"/>{{ $t('no-history') }}</p>
 | 
				
			||||||
		<button class="more" :class="{ fetching: fetchingMoreMessages }" v-if="existMoreMessages" @click="fetchMoreMessages" :disabled="fetchingMoreMessages">
 | 
							<button class="more" :class="{ fetching: fetchingMoreMessages }" v-if="existMoreMessages" @click="fetchMoreMessages" :disabled="fetchingMoreMessages">
 | 
				
			||||||
			<template v-if="fetchingMoreMessages"><fa icon="spinner" pulse fixed-width/></template>{{ fetchingMoreMessages ? $t('@.loading') : $t('@.load-more') }}
 | 
								<template v-if="fetchingMoreMessages"><fa icon="spinner" pulse fixed-width/></template>{{ fetchingMoreMessages ? $t('@.loading') : $t('@.load-more') }}
 | 
				
			||||||
		</button>
 | 
							</button>
 | 
				
			||||||
| 
						 | 
					@ -35,6 +35,7 @@ import XMessage from './messaging-room.message.vue';
 | 
				
			||||||
import XForm from './messaging-room.form.vue';
 | 
					import XForm from './messaging-room.form.vue';
 | 
				
			||||||
import { url } from '../../../config';
 | 
					import { url } from '../../../config';
 | 
				
			||||||
import { faArrowCircleDown } from '@fortawesome/free-solid-svg-icons';
 | 
					import { faArrowCircleDown } from '@fortawesome/free-solid-svg-icons';
 | 
				
			||||||
 | 
					import { faFlag } from '@fortawesome/free-regular-svg-icons';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
	i18n: i18n('common/views/components/messaging-room.vue'),
 | 
						i18n: i18n('common/views/components/messaging-room.vue'),
 | 
				
			||||||
| 
						 | 
					@ -54,7 +55,7 @@ export default Vue.extend({
 | 
				
			||||||
			connection: null,
 | 
								connection: null,
 | 
				
			||||||
			showIndicator: false,
 | 
								showIndicator: false,
 | 
				
			||||||
			timer: null,
 | 
								timer: null,
 | 
				
			||||||
			faArrowCircleDown
 | 
								faArrowCircleDown, faFlag
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,7 +14,7 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<section>
 | 
						<section>
 | 
				
			||||||
		<header><fa icon="terminal"/> {{ $t('console.title') }}</header>
 | 
							<header><fa icon="terminal"/> {{ $t('console.title') }}</header>
 | 
				
			||||||
		<ui-input v-model="endpoint">
 | 
							<ui-input v-model="endpoint" :datalist="endpoints">
 | 
				
			||||||
			<span>{{ $t('console.endpoint') }}</span>
 | 
								<span>{{ $t('console.endpoint') }}</span>
 | 
				
			||||||
		</ui-input>
 | 
							</ui-input>
 | 
				
			||||||
		<ui-textarea v-model="body">
 | 
							<ui-textarea v-model="body">
 | 
				
			||||||
| 
						 | 
					@ -39,15 +39,23 @@ import * as JSON5 from 'json5';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
	i18n: i18n('common/views/components/api-settings.vue'),
 | 
						i18n: i18n('common/views/components/api-settings.vue'),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	data() {
 | 
						data() {
 | 
				
			||||||
		return {
 | 
							return {
 | 
				
			||||||
			endpoint: '',
 | 
								endpoint: '',
 | 
				
			||||||
			body: '{}',
 | 
								body: '{}',
 | 
				
			||||||
			res: null,
 | 
								res: null,
 | 
				
			||||||
			sending: false
 | 
								sending: false,
 | 
				
			||||||
 | 
								endpoints: []
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						created() {
 | 
				
			||||||
 | 
							this.$root.api('endpoints').then(endpoints => {
 | 
				
			||||||
 | 
								this.endpoints = endpoints;
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	methods: {
 | 
						methods: {
 | 
				
			||||||
		regenerateToken() {
 | 
							regenerateToken() {
 | 
				
			||||||
			this.$root.dialog({
 | 
								this.$root.dialog({
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -23,6 +23,7 @@
 | 
				
			||||||
				@focus="focused = true"
 | 
									@focus="focused = true"
 | 
				
			||||||
				@blur="focused = false"
 | 
									@blur="focused = false"
 | 
				
			||||||
				@keydown="$emit('keydown', $event)"
 | 
									@keydown="$emit('keydown', $event)"
 | 
				
			||||||
 | 
									:list="id"
 | 
				
			||||||
			>
 | 
								>
 | 
				
			||||||
			<input v-else ref="input"
 | 
								<input v-else ref="input"
 | 
				
			||||||
				:type="type"
 | 
									:type="type"
 | 
				
			||||||
| 
						 | 
					@ -37,7 +38,11 @@
 | 
				
			||||||
				@focus="focused = true"
 | 
									@focus="focused = true"
 | 
				
			||||||
				@blur="focused = false"
 | 
									@blur="focused = false"
 | 
				
			||||||
				@keydown="$emit('keydown', $event)"
 | 
									@keydown="$emit('keydown', $event)"
 | 
				
			||||||
 | 
									:list="id"
 | 
				
			||||||
			>
 | 
								>
 | 
				
			||||||
 | 
								<datalist :id="id" v-if="datalist">
 | 
				
			||||||
 | 
									<option v-for="data in datalist" :value="data"/>
 | 
				
			||||||
 | 
								</datalist>
 | 
				
			||||||
		</template>
 | 
							</template>
 | 
				
			||||||
		<template v-else>
 | 
							<template v-else>
 | 
				
			||||||
			<input ref="input"
 | 
								<input ref="input"
 | 
				
			||||||
| 
						 | 
					@ -130,6 +135,10 @@ export default Vue.extend({
 | 
				
			||||||
			required: false,
 | 
								required: false,
 | 
				
			||||||
			default: false
 | 
								default: false
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
							datalist: {
 | 
				
			||||||
 | 
								type: Array,
 | 
				
			||||||
 | 
								required: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
		inline: {
 | 
							inline: {
 | 
				
			||||||
			type: Boolean,
 | 
								type: Boolean,
 | 
				
			||||||
			required: false,
 | 
								required: false,
 | 
				
			||||||
| 
						 | 
					@ -147,7 +156,8 @@ export default Vue.extend({
 | 
				
			||||||
		return {
 | 
							return {
 | 
				
			||||||
			v: this.value,
 | 
								v: this.value,
 | 
				
			||||||
			focused: false,
 | 
								focused: false,
 | 
				
			||||||
			passwordStrength: ''
 | 
								passwordStrength: '',
 | 
				
			||||||
 | 
								id: Math.random().toString()
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	computed: {
 | 
						computed: {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										95
									
								
								src/client/app/common/views/components/user-lists.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								src/client/app/common/views/components/user-lists.vue
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,95 @@
 | 
				
			||||||
 | 
					<template>
 | 
				
			||||||
 | 
					<div class="xkxvokkjlptzyewouewmceqcxhpgzprp">
 | 
				
			||||||
 | 
						<button class="ui" @click="add">{{ $t('create-list') }}</button>
 | 
				
			||||||
 | 
						<a v-for="list in lists" :key="list.id" @click="choice(list)">{{ list.name }}</a>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts">
 | 
				
			||||||
 | 
					import Vue from 'vue';
 | 
				
			||||||
 | 
					import i18n from '../../../i18n';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default Vue.extend({
 | 
				
			||||||
 | 
						i18n: i18n('common/views/components/user-lists.vue'),
 | 
				
			||||||
 | 
						data() {
 | 
				
			||||||
 | 
							return {
 | 
				
			||||||
 | 
								fetching: true,
 | 
				
			||||||
 | 
								lists: []
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						mounted() {
 | 
				
			||||||
 | 
							this.$root.api('users/lists/list').then(lists => {
 | 
				
			||||||
 | 
								this.fetching = false;
 | 
				
			||||||
 | 
								this.lists = lists;
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						methods: {
 | 
				
			||||||
 | 
							add() {
 | 
				
			||||||
 | 
								this.$root.dialog({
 | 
				
			||||||
 | 
									title: this.$t('list-name'),
 | 
				
			||||||
 | 
									input: true
 | 
				
			||||||
 | 
								}).then(async ({ canceled, result: title }) => {
 | 
				
			||||||
 | 
									if (canceled) return;
 | 
				
			||||||
 | 
									const list = await this.$root.api('users/lists/create', {
 | 
				
			||||||
 | 
										title
 | 
				
			||||||
 | 
									});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									this.lists.push(list)
 | 
				
			||||||
 | 
									this.$emit('choosen', list);
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							choice(list) {
 | 
				
			||||||
 | 
								this.$emit('choosen', list);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style lang="stylus" scoped>
 | 
				
			||||||
 | 
					.xkxvokkjlptzyewouewmceqcxhpgzprp
 | 
				
			||||||
 | 
						padding 16px
 | 
				
			||||||
 | 
						background: var(--bg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						> button
 | 
				
			||||||
 | 
							display block
 | 
				
			||||||
 | 
							margin-bottom 16px
 | 
				
			||||||
 | 
							color var(--primaryForeground)
 | 
				
			||||||
 | 
							background var(--primary)
 | 
				
			||||||
 | 
							width 100%
 | 
				
			||||||
 | 
							border-radius 38px
 | 
				
			||||||
 | 
							user-select none
 | 
				
			||||||
 | 
							cursor pointer
 | 
				
			||||||
 | 
							padding 0 16px
 | 
				
			||||||
 | 
							min-width 100px
 | 
				
			||||||
 | 
							line-height 38px
 | 
				
			||||||
 | 
							font-size 14px
 | 
				
			||||||
 | 
							font-weight 700
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							&:hover
 | 
				
			||||||
 | 
								background var(--primaryLighten10)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							&:active
 | 
				
			||||||
 | 
								background var(--primaryDarken10)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						a
 | 
				
			||||||
 | 
							display block
 | 
				
			||||||
 | 
							margin 8px 0
 | 
				
			||||||
 | 
							padding 8px
 | 
				
			||||||
 | 
							color var(--text)
 | 
				
			||||||
 | 
							background var(--face)
 | 
				
			||||||
 | 
							box-shadow 0 2px 16px var(--reversiListItemShadow)
 | 
				
			||||||
 | 
							border-radius 6px
 | 
				
			||||||
 | 
							cursor pointer
 | 
				
			||||||
 | 
							line-height 32px
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							*
 | 
				
			||||||
 | 
								pointer-events none
 | 
				
			||||||
 | 
								user-select none
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							&:hover
 | 
				
			||||||
 | 
								box-shadow 0 0 0 100px inset rgba(0, 0, 0, 0.05)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							&:active
 | 
				
			||||||
 | 
								box-shadow 0 0 0 100px inset rgba(0, 0, 0, 0.1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
| 
						 | 
					@ -89,8 +89,10 @@ export default Vue.extend({
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		toggleMute() {
 | 
							async toggleMute() {
 | 
				
			||||||
			if (this.user.isMuted) {
 | 
								if (this.user.isMuted) {
 | 
				
			||||||
 | 
									if (!await this.getConfirmed(this.$t('unmute-confirm'))) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				this.$root.api('mute/delete', {
 | 
									this.$root.api('mute/delete', {
 | 
				
			||||||
					userId: this.user.id
 | 
										userId: this.user.id
 | 
				
			||||||
				}).then(() => {
 | 
									}).then(() => {
 | 
				
			||||||
| 
						 | 
					@ -102,6 +104,8 @@ export default Vue.extend({
 | 
				
			||||||
					});
 | 
										});
 | 
				
			||||||
				});
 | 
									});
 | 
				
			||||||
			} else {
 | 
								} else {
 | 
				
			||||||
 | 
									if (!await this.getConfirmed(this.$t('mute-confirm'))) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				this.$root.api('mute/create', {
 | 
									this.$root.api('mute/create', {
 | 
				
			||||||
					userId: this.user.id
 | 
										userId: this.user.id
 | 
				
			||||||
				}).then(() => {
 | 
									}).then(() => {
 | 
				
			||||||
| 
						 | 
					@ -115,8 +119,10 @@ export default Vue.extend({
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		toggleBlock() {
 | 
							async toggleBlock() {
 | 
				
			||||||
			if (this.user.isBlocking) {
 | 
								if (this.user.isBlocking) {
 | 
				
			||||||
 | 
									if (!await this.getConfirmed(this.$t('unblock-confirm'))) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				this.$root.api('blocking/delete', {
 | 
									this.$root.api('blocking/delete', {
 | 
				
			||||||
					userId: this.user.id
 | 
										userId: this.user.id
 | 
				
			||||||
				}).then(() => {
 | 
									}).then(() => {
 | 
				
			||||||
| 
						 | 
					@ -128,6 +134,8 @@ export default Vue.extend({
 | 
				
			||||||
					});
 | 
										});
 | 
				
			||||||
				});
 | 
									});
 | 
				
			||||||
			} else {
 | 
								} else {
 | 
				
			||||||
 | 
									if (!await this.getConfirmed(this.$t('block-confirm'))) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				this.$root.api('blocking/create', {
 | 
									this.$root.api('blocking/create', {
 | 
				
			||||||
					userId: this.user.id
 | 
										userId: this.user.id
 | 
				
			||||||
				}).then(() => {
 | 
									}).then(() => {
 | 
				
			||||||
| 
						 | 
					@ -164,7 +172,9 @@ export default Vue.extend({
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		toggleSilence() {
 | 
							async toggleSilence() {
 | 
				
			||||||
 | 
								if (!await this.getConfirmed(this.$t(this.user.isSilenced ? 'unsilence-confirm' : 'silence-confirm'))) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			this.$root.api(this.user.isSilenced ? 'admin/unsilence-user' : 'admin/silence-user', {
 | 
								this.$root.api(this.user.isSilenced ? 'admin/unsilence-user' : 'admin/silence-user', {
 | 
				
			||||||
				userId: this.user.id
 | 
									userId: this.user.id
 | 
				
			||||||
			}).then(() => {
 | 
								}).then(() => {
 | 
				
			||||||
| 
						 | 
					@ -181,7 +191,9 @@ export default Vue.extend({
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		toggleSuspend() {
 | 
							async toggleSuspend() {
 | 
				
			||||||
 | 
								if (!await this.getConfirmed(this.$t(this.user.isSuspended ? 'unsuspend-confirm' : 'suspend-confirm'))) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			this.$root.api(this.user.isSuspended ? 'admin/unsuspend-user' : 'admin/suspend-user', {
 | 
								this.$root.api(this.user.isSuspended ? 'admin/unsuspend-user' : 'admin/suspend-user', {
 | 
				
			||||||
				userId: this.user.id
 | 
									userId: this.user.id
 | 
				
			||||||
			}).then(() => {
 | 
								}).then(() => {
 | 
				
			||||||
| 
						 | 
					@ -196,7 +208,18 @@ export default Vue.extend({
 | 
				
			||||||
					text: e
 | 
										text: e
 | 
				
			||||||
				});
 | 
									});
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
		}
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							async getConfirmed(text: string): Promise<Boolean> {
 | 
				
			||||||
 | 
								const confirm = await this.$root.dialog({
 | 
				
			||||||
 | 
									type: 'warning',
 | 
				
			||||||
 | 
									showCancelButton: true,
 | 
				
			||||||
 | 
									title: 'confirm',
 | 
				
			||||||
 | 
									text,
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								return !confirm.canceled;
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -123,7 +123,7 @@ export default Vue.extend({
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		fetchMore() {
 | 
							fetchMore() {
 | 
				
			||||||
			if (!this.more || this.moreFetching) return;
 | 
								if (!this.more || this.moreFetching || this.notes.length === 0) return;
 | 
				
			||||||
			this.moreFetching = true;
 | 
								this.moreFetching = true;
 | 
				
			||||||
			this.makePromise(this.notes[this.notes.length - 1].id).then(x => {
 | 
								this.makePromise(this.notes[this.notes.length - 1].id).then(x => {
 | 
				
			||||||
				this.notes = this.notes.concat(x.notes);
 | 
									this.notes = this.notes.concat(x.notes);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -90,9 +90,8 @@
 | 
				
			||||||
import Vue from 'vue';
 | 
					import Vue from 'vue';
 | 
				
			||||||
import i18n from '../../../i18n';
 | 
					import i18n from '../../../i18n';
 | 
				
			||||||
import MkUserListsWindow from './user-lists-window.vue';
 | 
					import MkUserListsWindow from './user-lists-window.vue';
 | 
				
			||||||
import MkUserListWindow from './user-list-window.vue';
 | 
					 | 
				
			||||||
import MkFollowRequestsWindow from './received-follow-requests-window.vue';
 | 
					import MkFollowRequestsWindow from './received-follow-requests-window.vue';
 | 
				
			||||||
import MkSettingsWindow from './settings-window.vue';
 | 
					// import MkSettingsWindow from './settings-window.vue';
 | 
				
			||||||
import MkDriveWindow from './drive-window.vue';
 | 
					import MkDriveWindow from './drive-window.vue';
 | 
				
			||||||
import contains from '../../../common/scripts/contains';
 | 
					import contains from '../../../common/scripts/contains';
 | 
				
			||||||
import { faHome, faColumns } from '@fortawesome/free-solid-svg-icons';
 | 
					import { faHome, faColumns } from '@fortawesome/free-solid-svg-icons';
 | 
				
			||||||
| 
						 | 
					@ -143,12 +142,7 @@ export default Vue.extend({
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		list() {
 | 
							list() {
 | 
				
			||||||
			this.close();
 | 
								this.close();
 | 
				
			||||||
			const w = this.$root.new(MkUserListsWindow);
 | 
								this.$root.new(MkUserListsWindow);
 | 
				
			||||||
			w.$once('choosen', list => {
 | 
					 | 
				
			||||||
				this.$root.new(MkUserListWindow, {
 | 
					 | 
				
			||||||
					list
 | 
					 | 
				
			||||||
				});
 | 
					 | 
				
			||||||
			});
 | 
					 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		followRequests() {
 | 
							followRequests() {
 | 
				
			||||||
			this.close();
 | 
								this.close();
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,6 +9,7 @@
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
import Vue from 'vue';
 | 
					import Vue from 'vue';
 | 
				
			||||||
import i18n from '../../../i18n';
 | 
					import i18n from '../../../i18n';
 | 
				
			||||||
 | 
					import { search } from '../../../common/scripts/search';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
	i18n: i18n('desktop/views/components/ui.header.search.vue'),
 | 
						i18n: i18n('desktop/views/components/ui.header.search.vue'),
 | 
				
			||||||
| 
						 | 
					@ -22,29 +23,11 @@ export default Vue.extend({
 | 
				
			||||||
		async onSubmit() {
 | 
							async onSubmit() {
 | 
				
			||||||
			if (this.wait) return;
 | 
								if (this.wait) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			const q = this.q.trim();
 | 
								this.wait = true;
 | 
				
			||||||
			if (q.startsWith('@')) {
 | 
								search(this, this.q).finally(() => {
 | 
				
			||||||
				this.$router.push(`/${q}`);
 | 
					 | 
				
			||||||
			} else if (q.startsWith('#')) {
 | 
					 | 
				
			||||||
				this.$router.push(`/tags/${encodeURIComponent(q.substr(1))}`);
 | 
					 | 
				
			||||||
			} else if (q.startsWith('https://')) {
 | 
					 | 
				
			||||||
				this.wait = true;
 | 
					 | 
				
			||||||
				try {
 | 
					 | 
				
			||||||
					const res = await this.$root.api('ap/show', {
 | 
					 | 
				
			||||||
						uri: q
 | 
					 | 
				
			||||||
					});
 | 
					 | 
				
			||||||
					if (res.type == 'User') {
 | 
					 | 
				
			||||||
						this.$router.push(`/@${res.object.username}@${res.object.host}`);
 | 
					 | 
				
			||||||
					} else if (res.type == 'Note') {
 | 
					 | 
				
			||||||
						this.$router.push(`/notes/${res.object.id}`);
 | 
					 | 
				
			||||||
					}
 | 
					 | 
				
			||||||
				} catch (e) {
 | 
					 | 
				
			||||||
					// TODO
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
				this.wait = false;
 | 
									this.wait = false;
 | 
				
			||||||
			} else {
 | 
									this.q = '';
 | 
				
			||||||
				this.$router.push(`/search?q=${encodeURIComponent(q)}`);
 | 
								});
 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -148,10 +148,7 @@ export default Vue.extend({
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		list() {
 | 
							list() {
 | 
				
			||||||
			const w = this.$root.new(MkUserListsWindow);
 | 
								this.$root.new(MkUserListsWindow);
 | 
				
			||||||
			w.$once('choosen', list => {
 | 
					 | 
				
			||||||
				this.$router.push(`i/lists/${ list.id }`);
 | 
					 | 
				
			||||||
			});
 | 
					 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		followRequests() {
 | 
							followRequests() {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -18,10 +18,12 @@ export default Vue.extend({
 | 
				
			||||||
	data() {
 | 
						data() {
 | 
				
			||||||
		return {
 | 
							return {
 | 
				
			||||||
			connection: null,
 | 
								connection: null,
 | 
				
			||||||
 | 
								date: null,
 | 
				
			||||||
			makePromise: cursor => this.$root.api('notes/user-list-timeline', {
 | 
								makePromise: cursor => this.$root.api('notes/user-list-timeline', {
 | 
				
			||||||
				listId: this.list.id,
 | 
									listId: this.list.id,
 | 
				
			||||||
				limit: fetchLimit + 1,
 | 
									limit: fetchLimit + 1,
 | 
				
			||||||
				untilId: cursor ? cursor : undefined,
 | 
									untilId: cursor ? cursor : undefined,
 | 
				
			||||||
 | 
									untilDate: cursor ? undefined : (this.date ? this.date.getTime() : undefined),
 | 
				
			||||||
				includeMyRenotes: this.$store.state.settings.showMyRenotes,
 | 
									includeMyRenotes: this.$store.state.settings.showMyRenotes,
 | 
				
			||||||
				includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
 | 
									includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
 | 
				
			||||||
				includeLocalRenotes: this.$store.state.settings.showLocalRenotes
 | 
									includeLocalRenotes: this.$store.state.settings.showLocalRenotes
 | 
				
			||||||
| 
						 | 
					@ -46,6 +48,10 @@ export default Vue.extend({
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	mounted() {
 | 
						mounted() {
 | 
				
			||||||
		this.init();
 | 
							this.init();
 | 
				
			||||||
 | 
							this.$root.$on('warp', this.warp);
 | 
				
			||||||
 | 
							this.$once('hook:beforeDestroy', () => {
 | 
				
			||||||
 | 
								this.$root.$off('warp', this.warp);
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	beforeDestroy() {
 | 
						beforeDestroy() {
 | 
				
			||||||
		this.connection.dispose();
 | 
							this.connection.dispose();
 | 
				
			||||||
| 
						 | 
					@ -68,6 +74,10 @@ export default Vue.extend({
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		onUserRemoved() {
 | 
							onUserRemoved() {
 | 
				
			||||||
			(this.$refs.timeline as any).reload();
 | 
								(this.$refs.timeline as any).reload();
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							warp(date) {
 | 
				
			||||||
 | 
								this.date = date;
 | 
				
			||||||
 | 
								(this.$refs.timeline as any).reload();
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,85 +1,36 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
<mk-window ref="window" width="450px" height="500px" @closed="destroyDom">
 | 
					<mk-window ref="window" width="450px" height="500px" @closed="destroyDom">
 | 
				
			||||||
	<template #header><fa icon="list"/> {{ $t('title') }}</template>
 | 
						<template #header><fa icon="list"/> {{ $t('title') }}</template>
 | 
				
			||||||
 | 
						<x-lists :class="$style.content" @choosen="choosen"/>
 | 
				
			||||||
	<div class="xkxvokkjlptzyewouewmceqcxhpgzprp">
 | 
					 | 
				
			||||||
		<button class="ui" @click="add">{{ $t('create-list') }}</button>
 | 
					 | 
				
			||||||
		<a v-for="list in lists" :key="list.id" @click="choice(list)">{{ list.name }}</a>
 | 
					 | 
				
			||||||
	</div>
 | 
					 | 
				
			||||||
</mk-window>
 | 
					</mk-window>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
import Vue from 'vue';
 | 
					import Vue from 'vue';
 | 
				
			||||||
import i18n from '../../../i18n';
 | 
					import i18n from '../../../i18n';
 | 
				
			||||||
 | 
					import MkUserListWindow from './user-list-window.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
	i18n: i18n('desktop/views/components/user-lists-window.vue'),
 | 
						i18n: i18n('desktop/views/components/user-lists-window.vue'),
 | 
				
			||||||
	data() {
 | 
						components: {
 | 
				
			||||||
		return {
 | 
							XLists: () => import('../../../common/views/components/user-lists.vue').then(m => m.default)
 | 
				
			||||||
			fetching: true,
 | 
					 | 
				
			||||||
			lists: []
 | 
					 | 
				
			||||||
		};
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	mounted() {
 | 
					 | 
				
			||||||
		this.$root.api('users/lists/list').then(lists => {
 | 
					 | 
				
			||||||
			this.fetching = false;
 | 
					 | 
				
			||||||
			this.lists = lists;
 | 
					 | 
				
			||||||
		});
 | 
					 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	methods: {
 | 
						methods: {
 | 
				
			||||||
		add() {
 | 
					 | 
				
			||||||
			this.$root.dialog({
 | 
					 | 
				
			||||||
				title: this.$t('list-name'),
 | 
					 | 
				
			||||||
				input: true
 | 
					 | 
				
			||||||
			}).then(async ({ canceled, result: title }) => {
 | 
					 | 
				
			||||||
				if (canceled) return;
 | 
					 | 
				
			||||||
				const list = await this.$root.api('users/lists/create', {
 | 
					 | 
				
			||||||
					title
 | 
					 | 
				
			||||||
				});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				this.$emit('choosen', list);
 | 
					 | 
				
			||||||
			});
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		choice(list) {
 | 
					 | 
				
			||||||
			this.$emit('choosen', list);
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		close() {
 | 
							close() {
 | 
				
			||||||
			(this as any).$refs.window.close();
 | 
								(this as any).$refs.window.close();
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							choosen(list) {
 | 
				
			||||||
 | 
								this.$root.new(MkUserListWindow, {
 | 
				
			||||||
 | 
									list
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style lang="stylus" scoped>
 | 
					<style lang="stylus" module>
 | 
				
			||||||
.xkxvokkjlptzyewouewmceqcxhpgzprp
 | 
					.content
 | 
				
			||||||
	padding 16px
 | 
						height 100%
 | 
				
			||||||
 | 
						overflow auto
 | 
				
			||||||
	> button
 | 
					 | 
				
			||||||
		display block
 | 
					 | 
				
			||||||
		margin-bottom 16px
 | 
					 | 
				
			||||||
		color var(--primaryForeground)
 | 
					 | 
				
			||||||
		background var(--primary)
 | 
					 | 
				
			||||||
		width 100%
 | 
					 | 
				
			||||||
		border-radius 38px
 | 
					 | 
				
			||||||
		user-select none
 | 
					 | 
				
			||||||
		cursor pointer
 | 
					 | 
				
			||||||
		padding 0 16px
 | 
					 | 
				
			||||||
		min-width 100px
 | 
					 | 
				
			||||||
		line-height 38px
 | 
					 | 
				
			||||||
		font-size 14px
 | 
					 | 
				
			||||||
		font-weight 700
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		&:hover
 | 
					 | 
				
			||||||
			background var(--primaryLighten10)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		&:active
 | 
					 | 
				
			||||||
			background var(--primaryDarken10)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	> a
 | 
					 | 
				
			||||||
		display block
 | 
					 | 
				
			||||||
		padding 16px
 | 
					 | 
				
			||||||
		border solid 1px var(--faceDivider)
 | 
					 | 
				
			||||||
		border-radius 4px
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
</style>
 | 
					</style>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -53,6 +53,12 @@ export default Vue.extend({
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	created() {
 | 
						created() {
 | 
				
			||||||
 | 
							this.$root.$on('warp', this.warp);
 | 
				
			||||||
 | 
							this.$once('hook:beforeDestroy', () => {
 | 
				
			||||||
 | 
								this.$root.$off('warp', this.warp);
 | 
				
			||||||
 | 
								this.connection.dispose();
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		const prepend = note => {
 | 
							const prepend = note => {
 | 
				
			||||||
			(this.$refs.timeline as any).prepend(note);
 | 
								(this.$refs.timeline as any).prepend(note);
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
| 
						 | 
					@ -124,13 +130,14 @@ export default Vue.extend({
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	beforeDestroy() {
 | 
					 | 
				
			||||||
		this.connection.dispose();
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	methods: {
 | 
						methods: {
 | 
				
			||||||
		focus() {
 | 
							focus() {
 | 
				
			||||||
			(this.$refs.timeline as any).focus();
 | 
								(this.$refs.timeline as any).focus();
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							warp(date) {
 | 
				
			||||||
 | 
								this.date = date;
 | 
				
			||||||
 | 
								(this.$refs.timeline as any).reload();
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -36,7 +36,8 @@ export default Vue.extend({
 | 
				
			||||||
				includeReplies: this.mode == 'with-replies',
 | 
									includeReplies: this.mode == 'with-replies',
 | 
				
			||||||
				includeMyRenotes: this.mode != 'my-posts',
 | 
									includeMyRenotes: this.mode != 'my-posts',
 | 
				
			||||||
				withFiles: this.mode == 'with-media',
 | 
									withFiles: this.mode == 'with-media',
 | 
				
			||||||
				untilDate: cursor ? cursor : new Date().getTime() + 1000 * 86400 * 365
 | 
									untilDate: cursor ? undefined : (this.date ? this.date.getTime() : undefined),
 | 
				
			||||||
 | 
									untilId: cursor ? cursor : undefined
 | 
				
			||||||
			}).then(notes => {
 | 
								}).then(notes => {
 | 
				
			||||||
				if (notes.length == fetchLimit + 1) {
 | 
									if (notes.length == fetchLimit + 1) {
 | 
				
			||||||
					notes.pop();
 | 
										notes.pop();
 | 
				
			||||||
| 
						 | 
					@ -62,10 +63,11 @@ export default Vue.extend({
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	mounted() {
 | 
						mounted() {
 | 
				
			||||||
		document.addEventListener('keydown', this.onDocumentKeydown);
 | 
							document.addEventListener('keydown', this.onDocumentKeydown);
 | 
				
			||||||
	},
 | 
							this.$root.$on('warp', this.warp);
 | 
				
			||||||
 | 
							this.$once('hook:beforeDestroy', () => {
 | 
				
			||||||
	beforeDestroy() {
 | 
								this.$root.$off('warp', this.warp);
 | 
				
			||||||
		document.removeEventListener('keydown', this.onDocumentKeydown);
 | 
								document.removeEventListener('keydown', this.onDocumentKeydown);
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	methods: {
 | 
						methods: {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,7 +11,9 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		<div class="mkw-polls--body">
 | 
							<div class="mkw-polls--body">
 | 
				
			||||||
			<div class="poll" v-if="!fetching && poll != null">
 | 
								<div class="poll" v-if="!fetching && poll != null">
 | 
				
			||||||
				<p v-if="poll.text"><router-link :to="poll | notePage">{{ poll.text }}</router-link></p>
 | 
									<p v-if="poll.text"><router-link :to="poll | notePage">
 | 
				
			||||||
 | 
										<mfm :text="poll.text" :author="poll.user" :custom-emojis="poll.emojis"/>
 | 
				
			||||||
 | 
									</router-link></p>
 | 
				
			||||||
				<p v-if="!poll.text"><router-link :to="poll | notePage"><fa icon="link"/></router-link></p>
 | 
									<p v-if="!poll.text"><router-link :to="poll | notePage"><fa icon="link"/></router-link></p>
 | 
				
			||||||
				<mk-poll :note="poll"/>
 | 
									<mk-poll :note="poll"/>
 | 
				
			||||||
			</div>
 | 
								</div>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,7 +14,7 @@ export default define({
 | 
				
			||||||
}).extend({
 | 
					}).extend({
 | 
				
			||||||
	methods: {
 | 
						methods: {
 | 
				
			||||||
		chosen(date) {
 | 
							chosen(date) {
 | 
				
			||||||
			this.$emit('chosen', date);
 | 
								this.$root.$emit('warp', date);
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		func() {
 | 
							func() {
 | 
				
			||||||
			if (this.props.design == 5) {
 | 
								if (this.props.design == 5) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -458,10 +458,14 @@ export default (callback: (launch: (router: VueRouter) => [Vue, MiOS], os: MiOS)
 | 
				
			||||||
					},
 | 
										},
 | 
				
			||||||
					dialog(opts) {
 | 
										dialog(opts) {
 | 
				
			||||||
						const vm = this.new(Dialog, opts);
 | 
											const vm = this.new(Dialog, opts);
 | 
				
			||||||
						return new Promise((res) => {
 | 
											const p: any = new Promise((res) => {
 | 
				
			||||||
							vm.$once('ok', result => res({ canceled: false, result }));
 | 
												vm.$once('ok', result => res({ canceled: false, result }));
 | 
				
			||||||
							vm.$once('cancel', () => res({ canceled: true }));
 | 
												vm.$once('cancel', () => res({ canceled: true }));
 | 
				
			||||||
						});
 | 
											});
 | 
				
			||||||
 | 
											p.close = () => {
 | 
				
			||||||
 | 
												vm.close();
 | 
				
			||||||
 | 
											};
 | 
				
			||||||
 | 
											return p;
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
				router,
 | 
									router,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -379,43 +379,30 @@ export default Vue.extend({
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		openContextMenu() {
 | 
					 | 
				
			||||||
			const fn = window.prompt(this.$t('prompt'));
 | 
					 | 
				
			||||||
			if (fn == null || fn == '') return;
 | 
					 | 
				
			||||||
			switch (fn) {
 | 
					 | 
				
			||||||
				case '1':
 | 
					 | 
				
			||||||
					this.selectLocalFile();
 | 
					 | 
				
			||||||
					break;
 | 
					 | 
				
			||||||
				case '2':
 | 
					 | 
				
			||||||
					this.urlUpload();
 | 
					 | 
				
			||||||
					break;
 | 
					 | 
				
			||||||
				case '3':
 | 
					 | 
				
			||||||
					this.createFolder();
 | 
					 | 
				
			||||||
					break;
 | 
					 | 
				
			||||||
				case '4':
 | 
					 | 
				
			||||||
					this.renameFolder();
 | 
					 | 
				
			||||||
					break;
 | 
					 | 
				
			||||||
				case '5':
 | 
					 | 
				
			||||||
					this.moveFolder();
 | 
					 | 
				
			||||||
					break;
 | 
					 | 
				
			||||||
				case '6':
 | 
					 | 
				
			||||||
					this.deleteFolder();
 | 
					 | 
				
			||||||
					break;
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		selectLocalFile() {
 | 
							selectLocalFile() {
 | 
				
			||||||
			(this.$refs.file as any).click();
 | 
								(this.$refs.file as any).click();
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		createFolder() {
 | 
							createFolder() {
 | 
				
			||||||
			const name = window.prompt(this.$t('folder-name'));
 | 
								this.$root.dialog({
 | 
				
			||||||
			if (name == null || name == '') return;
 | 
									title: this.$t('folder-name')
 | 
				
			||||||
			this.$root.api('drive/folders/create', {
 | 
									input: {
 | 
				
			||||||
				name: name,
 | 
										default: this.folder.name
 | 
				
			||||||
				parentId: this.folder ? this.folder.id : undefined
 | 
									}
 | 
				
			||||||
			}).then(folder => {
 | 
								}).then(({ result: name }) => {
 | 
				
			||||||
				this.addFolder(folder, true);
 | 
									if (!name) {
 | 
				
			||||||
 | 
										this.$root.dialog({
 | 
				
			||||||
 | 
											type: 'error',
 | 
				
			||||||
 | 
											text: this.$t('folder-name-cannot-empty')
 | 
				
			||||||
 | 
										});
 | 
				
			||||||
 | 
										return;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									this.$root.api('drive/folders/create', {
 | 
				
			||||||
 | 
										name: name,
 | 
				
			||||||
 | 
										parentId: this.folder ? this.folder.id : undefined
 | 
				
			||||||
 | 
									}).then(folder => {
 | 
				
			||||||
 | 
										this.addFolder(folder, true);
 | 
				
			||||||
 | 
									});
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -427,13 +414,25 @@ export default Vue.extend({
 | 
				
			||||||
				});
 | 
									});
 | 
				
			||||||
				return;
 | 
									return;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			const name = window.prompt(this.$t('folder-name'), this.folder.name);
 | 
								this.$root.dialog({
 | 
				
			||||||
			if (name == null || name == '') return;
 | 
									title: this.$t('folder-name')
 | 
				
			||||||
			this.$root.api('drive/folders/update', {
 | 
									input: {
 | 
				
			||||||
				name: name,
 | 
										default: this.folder.name
 | 
				
			||||||
				folderId: this.folder.id
 | 
									}
 | 
				
			||||||
			}).then(folder => {
 | 
								}).then(({ result: name }) => {
 | 
				
			||||||
				this.cd(folder);
 | 
									if (!name) {
 | 
				
			||||||
 | 
										this.$root.dialog({
 | 
				
			||||||
 | 
											type: 'error',
 | 
				
			||||||
 | 
											text: this.$t('cannot-empty')
 | 
				
			||||||
 | 
										});
 | 
				
			||||||
 | 
										return;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									this.$root.api('drive/folders/update', {
 | 
				
			||||||
 | 
										name: name,
 | 
				
			||||||
 | 
										folderId: this.folder.id
 | 
				
			||||||
 | 
									}).then(folder => {
 | 
				
			||||||
 | 
										this.cd(folder);
 | 
				
			||||||
 | 
									});
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -124,7 +124,7 @@ export default Vue.extend({
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		fetchMore() {
 | 
							fetchMore() {
 | 
				
			||||||
			if (!this.more || this.moreFetching) return;
 | 
								if (!this.more || this.moreFetching || this.notes.length === 0) return;
 | 
				
			||||||
			this.moreFetching = true;
 | 
								this.moreFetching = true;
 | 
				
			||||||
			this.makePromise(this.notes[this.notes.length - 1].id).then(x => {
 | 
								this.makePromise(this.notes[this.notes.length - 1].id).then(x => {
 | 
				
			||||||
				this.notes = this.notes.concat(x.notes);
 | 
									this.notes = this.notes.concat(x.notes);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -66,6 +66,7 @@ import i18n from '../../../i18n';
 | 
				
			||||||
import { lang } from '../../../config';
 | 
					import { lang } from '../../../config';
 | 
				
			||||||
import { faNewspaper, faHashtag, faHome, faColumns } from '@fortawesome/free-solid-svg-icons';
 | 
					import { faNewspaper, faHashtag, faHome, faColumns } from '@fortawesome/free-solid-svg-icons';
 | 
				
			||||||
import { faMoon, faSun } from '@fortawesome/free-regular-svg-icons';
 | 
					import { faMoon, faSun } from '@fortawesome/free-regular-svg-icons';
 | 
				
			||||||
 | 
					import { search } from '../../../common/scripts/search';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
	i18n: i18n('mobile/views/components/ui.nav.vue'),
 | 
						i18n: i18n('mobile/views/components/ui.nav.vue'),
 | 
				
			||||||
| 
						 | 
					@ -133,29 +134,10 @@ export default Vue.extend({
 | 
				
			||||||
			}).then(async ({ canceled, result: query }) => {
 | 
								}).then(async ({ canceled, result: query }) => {
 | 
				
			||||||
				if (canceled) return;
 | 
									if (canceled) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				const q = query.trim();
 | 
									this.searching = true;
 | 
				
			||||||
				if (q.startsWith('@')) {
 | 
									search(this, query).finally(() => {
 | 
				
			||||||
					this.$router.push(`/${q}`);
 | 
					 | 
				
			||||||
				} else if (q.startsWith('#')) {
 | 
					 | 
				
			||||||
					this.$router.push(`/tags/${encodeURIComponent(q.substr(1))}`);
 | 
					 | 
				
			||||||
				} else if (q.startsWith('https://')) {
 | 
					 | 
				
			||||||
					this.searching = true;
 | 
					 | 
				
			||||||
					try {
 | 
					 | 
				
			||||||
						const res = await this.$root.api('ap/show', {
 | 
					 | 
				
			||||||
							uri: q
 | 
					 | 
				
			||||||
						});
 | 
					 | 
				
			||||||
						if (res.type == 'User') {
 | 
					 | 
				
			||||||
							this.$router.push(`/@${res.object.username}@${res.object.host}`);
 | 
					 | 
				
			||||||
						} else if (res.type == 'Note') {
 | 
					 | 
				
			||||||
							this.$router.push(`/notes/${res.object.id}`);
 | 
					 | 
				
			||||||
						}
 | 
					 | 
				
			||||||
					} catch (e) {
 | 
					 | 
				
			||||||
						// TODO
 | 
					 | 
				
			||||||
					}
 | 
					 | 
				
			||||||
					this.searching = false;
 | 
										this.searching = false;
 | 
				
			||||||
				} else {
 | 
									});
 | 
				
			||||||
					this.$router.push(`/search?q=${encodeURIComponent(q)}`);
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,10 +15,12 @@ export default Vue.extend({
 | 
				
			||||||
	data() {
 | 
						data() {
 | 
				
			||||||
		return {
 | 
							return {
 | 
				
			||||||
			connection: null,
 | 
								connection: null,
 | 
				
			||||||
 | 
								date: null,
 | 
				
			||||||
			makePromise: cursor => this.$root.api('notes/user-list-timeline', {
 | 
								makePromise: cursor => this.$root.api('notes/user-list-timeline', {
 | 
				
			||||||
				listId: this.list.id,
 | 
									listId: this.list.id,
 | 
				
			||||||
				limit: fetchLimit + 1,
 | 
									limit: fetchLimit + 1,
 | 
				
			||||||
				untilId: cursor ? cursor : undefined,
 | 
									untilId: cursor ? cursor : undefined,
 | 
				
			||||||
 | 
									untilDate: cursor ? undefined : (this.date ? this.date.getTime() : undefined),
 | 
				
			||||||
				includeMyRenotes: this.$store.state.settings.showMyRenotes,
 | 
									includeMyRenotes: this.$store.state.settings.showMyRenotes,
 | 
				
			||||||
				includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
 | 
									includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
 | 
				
			||||||
				includeLocalRenotes: this.$store.state.settings.showLocalRenotes
 | 
									includeLocalRenotes: this.$store.state.settings.showLocalRenotes
 | 
				
			||||||
| 
						 | 
					@ -45,6 +47,11 @@ export default Vue.extend({
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	mounted() {
 | 
						mounted() {
 | 
				
			||||||
		this.init();
 | 
							this.init();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							this.$root.$on('warp', this.warp);
 | 
				
			||||||
 | 
							this.$once('hook:beforeDestroy', () => {
 | 
				
			||||||
 | 
								this.$root.$off('warp', this.warp);
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	beforeDestroy() {
 | 
						beforeDestroy() {
 | 
				
			||||||
| 
						 | 
					@ -73,6 +80,11 @@ export default Vue.extend({
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		onUserRemoved() {
 | 
							onUserRemoved() {
 | 
				
			||||||
			(this.$refs.timeline as any).reload();
 | 
								(this.$refs.timeline as any).reload();
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							warp(date) {
 | 
				
			||||||
 | 
								this.date = date;
 | 
				
			||||||
 | 
								(this.$refs.timeline as any).reload();
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -17,11 +17,13 @@ export default Vue.extend({
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	data() {
 | 
						data() {
 | 
				
			||||||
		return {
 | 
							return {
 | 
				
			||||||
 | 
								date: null,
 | 
				
			||||||
			makePromise: cursor => this.$root.api('users/notes', {
 | 
								makePromise: cursor => this.$root.api('users/notes', {
 | 
				
			||||||
				userId: this.user.id,
 | 
									userId: this.user.id,
 | 
				
			||||||
				limit: fetchLimit + 1,
 | 
									limit: fetchLimit + 1,
 | 
				
			||||||
				withFiles: this.withMedia,
 | 
									withFiles: this.withMedia,
 | 
				
			||||||
				untilDate: cursor ? cursor : new Date().getTime() + 1000 * 86400 * 365
 | 
									untilDate: cursor ? undefined : (this.date ? this.date.getTime() : undefined),
 | 
				
			||||||
 | 
									untilId: cursor ? cursor : undefined
 | 
				
			||||||
			}).then(notes => {
 | 
								}).then(notes => {
 | 
				
			||||||
				if (notes.length == fetchLimit + 1) {
 | 
									if (notes.length == fetchLimit + 1) {
 | 
				
			||||||
					notes.pop();
 | 
										notes.pop();
 | 
				
			||||||
| 
						 | 
					@ -37,6 +39,20 @@ export default Vue.extend({
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			})
 | 
								})
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						created() {
 | 
				
			||||||
 | 
							this.$root.$on('warp', this.warp);
 | 
				
			||||||
 | 
							this.$once('hook:beforeDestroy', () => {
 | 
				
			||||||
 | 
								this.$root.$off('warp', this.warp);
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						methods: {
 | 
				
			||||||
 | 
							warp(date) {
 | 
				
			||||||
 | 
								this.date = date;
 | 
				
			||||||
 | 
								(this.$refs.timeline as any).reload();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,7 +5,7 @@
 | 
				
			||||||
		<template v-if="file"><mk-file-type-icon data-icon :type="file.type" style="margin-right:4px;"/>{{ file.name }}</template>
 | 
							<template v-if="file"><mk-file-type-icon data-icon :type="file.type" style="margin-right:4px;"/>{{ file.name }}</template>
 | 
				
			||||||
		<template v-if="!folder && !file"><span style="margin-right:4px;"><fa icon="cloud"/></span>{{ $t('@.drive') }}</template>
 | 
							<template v-if="!folder && !file"><span style="margin-right:4px;"><fa icon="cloud"/></span>{{ $t('@.drive') }}</template>
 | 
				
			||||||
	</template>
 | 
						</template>
 | 
				
			||||||
	<template #func><button @click="fn"><fa icon="ellipsis-h"/></button></template>
 | 
						<template #func v-if="folder || (!folder && !file)"><button @click="openContextMenu" ref="contextSource"><fa icon="ellipsis-h"/></button></template>
 | 
				
			||||||
	<x-drive
 | 
						<x-drive
 | 
				
			||||||
		ref="browser"
 | 
							ref="browser"
 | 
				
			||||||
		:init-folder="initFolder"
 | 
							:init-folder="initFolder"
 | 
				
			||||||
| 
						 | 
					@ -26,9 +26,12 @@
 | 
				
			||||||
import Vue from 'vue';
 | 
					import Vue from 'vue';
 | 
				
			||||||
import i18n from '../../../i18n';
 | 
					import i18n from '../../../i18n';
 | 
				
			||||||
import Progress from '../../../common/scripts/loading';
 | 
					import Progress from '../../../common/scripts/loading';
 | 
				
			||||||
 | 
					import XMenu from '../../../common/views/components/menu.vue';
 | 
				
			||||||
 | 
					import { faTrashAlt } from '@fortawesome/free-regular-svg-icons';
 | 
				
			||||||
 | 
					import { faCloudUploadAlt } from '@fortawesome/free-solid-svg-icons';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
	i18n: i18n(),
 | 
						i18n: i18n('mobile/views/pages/drive.vue'),
 | 
				
			||||||
	components: {
 | 
						components: {
 | 
				
			||||||
		XDrive: () => import('../components/drive.vue').then(m => m.default),
 | 
							XDrive: () => import('../components/drive.vue').then(m => m.default),
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
| 
						 | 
					@ -63,9 +66,6 @@ export default Vue.extend({
 | 
				
			||||||
				(this.$refs as any).browser.goRoot(true);
 | 
									(this.$refs as any).browser.goRoot(true);
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		fn() {
 | 
					 | 
				
			||||||
			(this.$refs as any).browser.openContextMenu();
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		onMoveRoot(silent) {
 | 
							onMoveRoot(silent) {
 | 
				
			||||||
			const title = `${this.$root.instanceName} Drive`;
 | 
								const title = `${this.$root.instanceName} Drive`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -104,6 +104,42 @@ export default Vue.extend({
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			this.file = file;
 | 
								this.file = file;
 | 
				
			||||||
			this.folder = null;
 | 
								this.folder = null;
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							openContextMenu() {
 | 
				
			||||||
 | 
								this.$root.new(XMenu, {
 | 
				
			||||||
 | 
									items: [{
 | 
				
			||||||
 | 
										type: 'item',
 | 
				
			||||||
 | 
										text: this.$t('contextmenu.upload'),
 | 
				
			||||||
 | 
										icon: 'upload',
 | 
				
			||||||
 | 
										action: this.$refs.browser.selectLocalFile
 | 
				
			||||||
 | 
									}, {
 | 
				
			||||||
 | 
										type: 'item',
 | 
				
			||||||
 | 
										text: this.$t('contextmenu.url-upload'),
 | 
				
			||||||
 | 
										icon: faCloudUploadAlt,
 | 
				
			||||||
 | 
										action: this.$refs.browser.urlUpload
 | 
				
			||||||
 | 
									}, {
 | 
				
			||||||
 | 
										type: 'item',
 | 
				
			||||||
 | 
										text: this.$t('contextmenu.create-folder'),
 | 
				
			||||||
 | 
										icon: ['far', 'folder'],
 | 
				
			||||||
 | 
										action: this.$refs.browser.createFolder
 | 
				
			||||||
 | 
									}, ...(this.folder ? [{
 | 
				
			||||||
 | 
										type: 'item',
 | 
				
			||||||
 | 
										text: this.$t('contextmenu.rename-folder'),
 | 
				
			||||||
 | 
										icon: 'i-cursor',
 | 
				
			||||||
 | 
										action: this.$refs.browser.renameFolder
 | 
				
			||||||
 | 
									}, {
 | 
				
			||||||
 | 
										type: 'item',
 | 
				
			||||||
 | 
										text: this.$t('contextmenu.move-folder'),
 | 
				
			||||||
 | 
										icon: ['far', 'folder-open'],
 | 
				
			||||||
 | 
										action: this.$refs.browser.moveFolder
 | 
				
			||||||
 | 
									}, {
 | 
				
			||||||
 | 
										type: 'item',
 | 
				
			||||||
 | 
										text: this.$t('contextmenu.delete-folder'),
 | 
				
			||||||
 | 
										icon: faTrashAlt,
 | 
				
			||||||
 | 
										action: this.$refs.browser.deleteFolder
 | 
				
			||||||
 | 
									}] : [])],
 | 
				
			||||||
 | 
									source: this.$refs.contextSource,
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -54,6 +54,12 @@ export default Vue.extend({
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	created() {
 | 
						created() {
 | 
				
			||||||
 | 
							this.$root.$on('warp', this.warp);
 | 
				
			||||||
 | 
							this.$once('hook:beforeDestroy', () => {
 | 
				
			||||||
 | 
								this.$root.$off('warp', this.warp);
 | 
				
			||||||
 | 
								this.connection.dispose();
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		const prepend = note => {
 | 
							const prepend = note => {
 | 
				
			||||||
			(this.$refs.timeline as any).prepend(note);
 | 
								(this.$refs.timeline as any).prepend(note);
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
| 
						 | 
					@ -125,10 +131,6 @@ export default Vue.extend({
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	beforeDestroy() {
 | 
					 | 
				
			||||||
		this.connection.dispose();
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	methods: {
 | 
						methods: {
 | 
				
			||||||
		focus() {
 | 
							focus() {
 | 
				
			||||||
			(this.$refs.timeline as any).focus();
 | 
								(this.$refs.timeline as any).focus();
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,20 +1,15 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
<mk-ui>
 | 
					<mk-ui>
 | 
				
			||||||
	<template #header><fa icon="list"/>{{ $t('title') }}</template>
 | 
						<template #header><fa icon="list"/>{{ $t('title') }}</template>
 | 
				
			||||||
	<template #func><button @click="fn"><fa icon="plus"/></button></template>
 | 
						<template #func><button @click="$refs.lists.add()"><fa icon="plus"/></button></template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<main>
 | 
						<x-lists ref="lists" @choosen="choosen"/>
 | 
				
			||||||
		<ul>
 | 
					 | 
				
			||||||
			<li v-for="list in lists" :key="list.id"><router-link :to="`/i/lists/${list.id}`">{{ list.name }}</router-link></li>
 | 
					 | 
				
			||||||
		</ul>
 | 
					 | 
				
			||||||
	</main>
 | 
					 | 
				
			||||||
</mk-ui>
 | 
					</mk-ui>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
import Vue from 'vue';
 | 
					import Vue from 'vue';
 | 
				
			||||||
import i18n from '../../../i18n';
 | 
					import i18n from '../../../i18n';
 | 
				
			||||||
import Progress from '../../../common/scripts/loading';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
	i18n: i18n('mobile/views/pages/user-lists.vue'),
 | 
						i18n: i18n('mobile/views/pages/user-lists.vue'),
 | 
				
			||||||
| 
						 | 
					@ -24,31 +19,16 @@ export default Vue.extend({
 | 
				
			||||||
			lists: []
 | 
								lists: []
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
						components: {
 | 
				
			||||||
 | 
							XLists: () => import('../../../common/views/components/user-lists.vue').then(m => m.default)
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
	mounted() {
 | 
						mounted() {
 | 
				
			||||||
		document.title = this.$t('title');
 | 
							document.title = this.$t('title');
 | 
				
			||||||
 | 
					 | 
				
			||||||
		Progress.start();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		this.$root.api('users/lists/list').then(lists => {
 | 
					 | 
				
			||||||
			this.fetching = false;
 | 
					 | 
				
			||||||
			this.lists = lists;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			Progress.done();
 | 
					 | 
				
			||||||
		});
 | 
					 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	methods: {
 | 
						methods: {
 | 
				
			||||||
		fn() {
 | 
							choosen(list) {
 | 
				
			||||||
			this.$root.dialog({
 | 
								if (!list) return;
 | 
				
			||||||
				title: this.$t('enter-list-name'),
 | 
								this.$router.push(`/i/lists/${list.id}`);
 | 
				
			||||||
				input: true
 | 
					 | 
				
			||||||
			}).then(async ({ canceled, result: title }) => {
 | 
					 | 
				
			||||||
				if (canceled) return;
 | 
					 | 
				
			||||||
				const list = await this.$root.api('users/lists/create', {
 | 
					 | 
				
			||||||
					title
 | 
					 | 
				
			||||||
				});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				this.$router.push(`/i/lists/${list.id}`);
 | 
					 | 
				
			||||||
			});
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -72,13 +72,13 @@ export default Vue.extend({
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	computed: {
 | 
						computed: {
 | 
				
			||||||
		widgets(): any[] {
 | 
							widgets(): any[] {
 | 
				
			||||||
			return this.$store.state.settings.mobileHome;
 | 
								return this.$store.state.device.mobileHome;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	created() {
 | 
						created() {
 | 
				
			||||||
		if (this.widgets.length == 0) {
 | 
							if (this.widgets.length == 0) {
 | 
				
			||||||
			this.widgets = [{
 | 
								this.$store.commit('device/setMobileHome', [{
 | 
				
			||||||
				name: 'calendar',
 | 
									name: 'calendar',
 | 
				
			||||||
				id: 'a', data: {}
 | 
									id: 'a', data: {}
 | 
				
			||||||
			}, {
 | 
								}, {
 | 
				
			||||||
| 
						 | 
					@ -96,8 +96,7 @@ export default Vue.extend({
 | 
				
			||||||
			}, {
 | 
								}, {
 | 
				
			||||||
				name: 'version',
 | 
									name: 'version',
 | 
				
			||||||
				id: 'g', data: {}
 | 
									id: 'g', data: {}
 | 
				
			||||||
			}];
 | 
								}]);
 | 
				
			||||||
			this.saveHome();
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -123,7 +122,7 @@ export default Vue.extend({
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		addWidget() {
 | 
							addWidget() {
 | 
				
			||||||
			this.$store.commit('settings/addMobileHomeWidget', {
 | 
								this.$store.commit('device/addMobileHomeWidget', {
 | 
				
			||||||
				name: this.widgetAdderSelected,
 | 
									name: this.widgetAdderSelected,
 | 
				
			||||||
				id: uuid(),
 | 
									id: uuid(),
 | 
				
			||||||
				data: {}
 | 
									data: {}
 | 
				
			||||||
| 
						 | 
					@ -131,11 +130,11 @@ export default Vue.extend({
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		removeWidget(widget) {
 | 
							removeWidget(widget) {
 | 
				
			||||||
			this.$store.commit('settings/removeMobileHomeWidget', widget);
 | 
								this.$store.commit('device/removeMobileHomeWidget', widget);
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		saveHome() {
 | 
							saveHome() {
 | 
				
			||||||
			this.$store.commit('settings/setMobileHome', this.widgets);
 | 
								this.$store.commit('device/setMobileHome', this.widgets);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -358,7 +358,7 @@ export default (os: MiOS) => new Vuex.Store({
 | 
				
			||||||
					ctx.commit('set', x);
 | 
										ctx.commit('set', x);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					if (ctx.rootGetters.isSignedIn) {
 | 
										if (ctx.rootGetters.isSignedIn) {
 | 
				
			||||||
						os.api('i/update_client_setting', {
 | 
											os.api('i/update-client-setting', {
 | 
				
			||||||
							name: x.key,
 | 
												name: x.key,
 | 
				
			||||||
							value: x.value
 | 
												value: x.value
 | 
				
			||||||
						});
 | 
											});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -26,6 +26,7 @@ export class AppRepository extends Repository<App> {
 | 
				
			||||||
			id: app.id,
 | 
								id: app.id,
 | 
				
			||||||
			name: app.name,
 | 
								name: app.name,
 | 
				
			||||||
			callbackUrl: app.callbackUrl,
 | 
								callbackUrl: app.callbackUrl,
 | 
				
			||||||
 | 
								permission: app.permission,
 | 
				
			||||||
			...(opts.includeSecret ? { secret: app.secret } : {}),
 | 
								...(opts.includeSecret ? { secret: app.secret } : {}),
 | 
				
			||||||
			...(me ? {
 | 
								...(me ? {
 | 
				
			||||||
				isAuthorized: await AccessTokens.count({
 | 
									isAuthorized: await AccessTokens.count({
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										15
									
								
								src/server/api/endpoints/endpoints.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/server/api/endpoints/endpoints.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,15 @@
 | 
				
			||||||
 | 
					import define from '../define';
 | 
				
			||||||
 | 
					import endpoints from '../endpoints';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const meta = {
 | 
				
			||||||
 | 
						requireCredential: false,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						tags: ['meta'],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						params: {
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default define(meta, async () => {
 | 
				
			||||||
 | 
						return endpoints.map(x => x.name);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
| 
						 | 
					@ -17,6 +17,8 @@ export const meta = {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	tags: ['notes'],
 | 
						tags: ['notes'],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						requireCredential: true,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	params: {
 | 
						params: {
 | 
				
			||||||
		limit: {
 | 
							limit: {
 | 
				
			||||||
			validator: $.optional.num.range(1, 100),
 | 
								validator: $.optional.num.range(1, 100),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -142,7 +142,7 @@ export default define(meta, async (ps, me) => {
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	//#region Construct query
 | 
						//#region Construct query
 | 
				
			||||||
	const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId)
 | 
						const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
 | 
				
			||||||
		.andWhere('note.userId = :userId', { userId: user.id })
 | 
							.andWhere('note.userId = :userId', { userId: user.id })
 | 
				
			||||||
		.leftJoinAndSelect('note.user', 'user');
 | 
							.leftJoinAndSelect('note.user', 'user');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -20,11 +20,11 @@ export default class extends Channel {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@autobind
 | 
						@autobind
 | 
				
			||||||
	private async onNote(note: any) {
 | 
						private async onNote(note: any) {
 | 
				
			||||||
		// 自分自身の投稿 または その投稿のユーザーをフォローしている または ローカルの投稿 の場合だけ
 | 
							// 自分自身の投稿 または その投稿のユーザーをフォローしている または 全体公開のローカルの投稿 の場合だけ
 | 
				
			||||||
		if (!(
 | 
							if (!(
 | 
				
			||||||
			this.user!.id === note.userId ||
 | 
								this.user!.id === note.userId ||
 | 
				
			||||||
			this.following.includes(note.userId) ||
 | 
								this.following.includes(note.userId) ||
 | 
				
			||||||
			note.user.host == null
 | 
								(note.user.host == null && note.visibility === 'public')
 | 
				
			||||||
		)) return;
 | 
							)) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (['followers', 'specified'].includes(note.visibility)) {
 | 
							if (['followers', 'specified'].includes(note.visibility)) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -32,7 +32,7 @@ describe('Streaming', () => {
 | 
				
			||||||
		p.on('message', message => {
 | 
							p.on('message', message => {
 | 
				
			||||||
			if (message === 'ok') {
 | 
								if (message === 'ok') {
 | 
				
			||||||
				(p.channel as any).onread = () => {};
 | 
									(p.channel as any).onread = () => {};
 | 
				
			||||||
				initDb(true).then(async connection => {
 | 
									initDb(true).then(async (connection: any) => {
 | 
				
			||||||
					Followings = connection.getRepository(Following);
 | 
										Followings = connection.getRepository(Following);
 | 
				
			||||||
					done();
 | 
										done();
 | 
				
			||||||
				});
 | 
									});
 | 
				
			||||||
| 
						 | 
					@ -44,7 +44,7 @@ describe('Streaming', () => {
 | 
				
			||||||
		p.kill();
 | 
							p.kill();
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const follow = async (follower, followee) => {
 | 
						const follow = async (follower: any, followee: any) => {
 | 
				
			||||||
		await Followings.save({
 | 
							await Followings.save({
 | 
				
			||||||
			id: 'a',
 | 
								id: 'a',
 | 
				
			||||||
			createdAt: new Date(),
 | 
								createdAt: new Date(),
 | 
				
			||||||
| 
						 | 
					@ -484,6 +484,56 @@ describe('Streaming', () => {
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
		}));
 | 
							}));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							it('フォローしているユーザーのホーム投稿が流れる', () => new Promise(async done => {
 | 
				
			||||||
 | 
								const alice = await signup({ username: 'alice' });
 | 
				
			||||||
 | 
								const bob = await signup({ username: 'bob' });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Alice が Bob をフォロー
 | 
				
			||||||
 | 
								await request('/following/create', {
 | 
				
			||||||
 | 
									userId: bob.id
 | 
				
			||||||
 | 
								}, alice);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								const ws = await connectStream(alice, 'hybridTimeline', ({ type, body }) => {
 | 
				
			||||||
 | 
									if (type == 'note') {
 | 
				
			||||||
 | 
										assert.deepStrictEqual(body.userId, bob.id);
 | 
				
			||||||
 | 
										assert.deepStrictEqual(body.text, 'foo');
 | 
				
			||||||
 | 
										ws.close();
 | 
				
			||||||
 | 
										done();
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// ホーム投稿
 | 
				
			||||||
 | 
								post(bob, {
 | 
				
			||||||
 | 
									text: 'foo',
 | 
				
			||||||
 | 
									visibility: 'home'
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
							}));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							it('フォローしていないローカルユーザーのホーム投稿は流れない', () => new Promise(async done => {
 | 
				
			||||||
 | 
								const alice = await signup({ username: 'alice' });
 | 
				
			||||||
 | 
								const bob = await signup({ username: 'bob' });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								let fired = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								const ws = await connectStream(alice, 'hybridTimeline', ({ type, body }) => {
 | 
				
			||||||
 | 
									if (type == 'note') {
 | 
				
			||||||
 | 
										fired = true;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// ホーム投稿
 | 
				
			||||||
 | 
								post(bob, {
 | 
				
			||||||
 | 
									text: 'foo',
 | 
				
			||||||
 | 
									visibility: 'home'
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								setTimeout(() => {
 | 
				
			||||||
 | 
									assert.strictEqual(fired, false);
 | 
				
			||||||
 | 
									ws.close();
 | 
				
			||||||
 | 
									done();
 | 
				
			||||||
 | 
								}, 3000);
 | 
				
			||||||
 | 
							}));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		it('フォローしていないローカルユーザーのフォロワー宛て投稿は流れない', () => new Promise(async done => {
 | 
							it('フォローしていないローカルユーザーのフォロワー宛て投稿は流れない', () => new Promise(async done => {
 | 
				
			||||||
			const alice = await signup({ username: 'alice' });
 | 
								const alice = await signup({ username: 'alice' });
 | 
				
			||||||
			const bob = await signup({ username: 'bob' });
 | 
								const bob = await signup({ username: 'bob' });
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -76,7 +76,7 @@ export const uploadFile = (user: any, path?: string): Promise<any> => new Promis
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function connectStream(user: any, channel: string, listener: any, params?: any): Promise<WebSocket> {
 | 
					export function connectStream(user: any, channel: string, listener: (message: Record<string, any>) => any, params?: any): Promise<WebSocket> {
 | 
				
			||||||
	return new Promise((res, rej) => {
 | 
						return new Promise((res, rej) => {
 | 
				
			||||||
		const ws = new WebSocket(`ws://localhost/streaming?i=${user.token}`);
 | 
							const ws = new WebSocket(`ws://localhost/streaming?i=${user.token}`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue