✌️
This commit is contained in:
		
							parent
							
								
									ea2ddf3a4d
								
							
						
					
					
						commit
						dbc421cb57
					
				
					 15 changed files with 272 additions and 377 deletions
				
			
		
							
								
								
									
										25
									
								
								src/web/app/auth/script.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/web/app/auth/script.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,25 @@
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Authorize Form
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Style
 | 
				
			||||||
 | 
					import './style.styl';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import init from '../init';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import Index from './views/index.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * init
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					init(async (launch) => {
 | 
				
			||||||
 | 
						document.title = 'Misskey | アプリの連携';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Launch the app
 | 
				
			||||||
 | 
						const [app] = launch();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Routing
 | 
				
			||||||
 | 
						app.$router.addRoutes([
 | 
				
			||||||
 | 
							{ path: '/:token', component: Index },
 | 
				
			||||||
 | 
						]);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
| 
						 | 
					@ -42,10 +42,14 @@ export default Vue.extend({
 | 
				
			||||||
		return {
 | 
							return {
 | 
				
			||||||
			state: null,
 | 
								state: null,
 | 
				
			||||||
			session: null,
 | 
								session: null,
 | 
				
			||||||
			fetching: true,
 | 
								fetching: true
 | 
				
			||||||
			token: window.location.href.split('/').pop()
 | 
					 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
						computed: {
 | 
				
			||||||
 | 
							token(): string {
 | 
				
			||||||
 | 
								return this.$route.params.token;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
	mounted() {
 | 
						mounted() {
 | 
				
			||||||
		if (!this.$root.$data.os.isSignedIn) return;
 | 
							if (!this.$root.$data.os.isSignedIn) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,11 +5,25 @@
 | 
				
			||||||
// Style
 | 
					// Style
 | 
				
			||||||
import './style.styl';
 | 
					import './style.styl';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
require('./tags');
 | 
					 | 
				
			||||||
import init from '../init';
 | 
					import init from '../init';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import Index from './views/index.vue';
 | 
				
			||||||
 | 
					import Apps from './views/apps.vue';
 | 
				
			||||||
 | 
					import AppNew from './views/new-app.vue';
 | 
				
			||||||
 | 
					import App from './views/app.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * init
 | 
					 * init
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
init(() => {
 | 
					init(launch => {
 | 
				
			||||||
 | 
						// Launch the app
 | 
				
			||||||
 | 
						const [app] = launch();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Routing
 | 
				
			||||||
 | 
						app.$router.addRoutes([
 | 
				
			||||||
 | 
							{ path: '/', component: Index },
 | 
				
			||||||
 | 
							{ path: '/app', component: Apps },
 | 
				
			||||||
 | 
							{ path: '/app/new', component: AppNew },
 | 
				
			||||||
 | 
							{ path: '/app/:id', component: App },
 | 
				
			||||||
 | 
						]);
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +0,0 @@
 | 
				
			||||||
require('./pages/index.tag');
 | 
					 | 
				
			||||||
require('./pages/apps.tag');
 | 
					 | 
				
			||||||
require('./pages/app.tag');
 | 
					 | 
				
			||||||
require('./pages/new-app.tag');
 | 
					 | 
				
			||||||
require('./new-app-form.tag');
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,252 +0,0 @@
 | 
				
			||||||
<mk-new-app-form>
 | 
					 | 
				
			||||||
	<form onsubmit={ onsubmit } autocomplete="off">
 | 
					 | 
				
			||||||
		<section class="name">
 | 
					 | 
				
			||||||
			<label>
 | 
					 | 
				
			||||||
				<p class="caption">アプリケーション名</p>
 | 
					 | 
				
			||||||
				<input ref="name" type="text" placeholder="ex) Misskey for iOS" autocomplete="off" required="required"/>
 | 
					 | 
				
			||||||
			</label>
 | 
					 | 
				
			||||||
		</section>
 | 
					 | 
				
			||||||
		<section class="nid">
 | 
					 | 
				
			||||||
			<label>
 | 
					 | 
				
			||||||
				<p class="caption">Named ID</p>
 | 
					 | 
				
			||||||
				<input ref="nid" type="text" pattern="^[a-zA-Z0-9-]{3,30}$" placeholder="ex) misskey-for-ios" autocomplete="off" required="required" onkeyup={ onChangeNid }/>
 | 
					 | 
				
			||||||
				<p class="info" v-if="nidState == 'wait'" style="color:#999">%fa:spinner .pulse .fw%確認しています...</p>
 | 
					 | 
				
			||||||
				<p class="info" v-if="nidState == 'ok'" style="color:#3CB7B5">%fa:fw check%利用できます</p>
 | 
					 | 
				
			||||||
				<p class="info" v-if="nidState == 'unavailable'" style="color:#FF1161">%fa:fw exclamation-triangle%既に利用されています</p>
 | 
					 | 
				
			||||||
				<p class="info" v-if="nidState == 'error'" style="color:#FF1161">%fa:fw exclamation-triangle%通信エラー</p>
 | 
					 | 
				
			||||||
				<p class="info" v-if="nidState == 'invalid-format'" style="color:#FF1161">%fa:fw exclamation-triangle%a~z、A~Z、0~9、-(ハイフン)が使えます</p>
 | 
					 | 
				
			||||||
				<p class="info" v-if="nidState == 'min-range'" style="color:#FF1161">%fa:fw exclamation-triangle%3文字以上でお願いします!</p>
 | 
					 | 
				
			||||||
				<p class="info" v-if="nidState == 'max-range'" style="color:#FF1161">%fa:fw exclamation-triangle%30文字以内でお願いします</p>
 | 
					 | 
				
			||||||
			</label>
 | 
					 | 
				
			||||||
		</section>
 | 
					 | 
				
			||||||
		<section class="description">
 | 
					 | 
				
			||||||
			<label>
 | 
					 | 
				
			||||||
				<p class="caption">アプリの概要</p>
 | 
					 | 
				
			||||||
				<textarea ref="description" placeholder="ex) Misskey iOSクライアント。" autocomplete="off" required="required"></textarea>
 | 
					 | 
				
			||||||
			</label>
 | 
					 | 
				
			||||||
		</section>
 | 
					 | 
				
			||||||
		<section class="callback">
 | 
					 | 
				
			||||||
			<label>
 | 
					 | 
				
			||||||
				<p class="caption">コールバックURL (オプション)</p>
 | 
					 | 
				
			||||||
				<input ref="cb" type="url" placeholder="ex) https://your.app.example.com/callback.php" autocomplete="off"/>
 | 
					 | 
				
			||||||
			</label>
 | 
					 | 
				
			||||||
		</section>
 | 
					 | 
				
			||||||
		<section class="permission">
 | 
					 | 
				
			||||||
			<p class="caption">権限</p>
 | 
					 | 
				
			||||||
			<div ref="permission">
 | 
					 | 
				
			||||||
				<label>
 | 
					 | 
				
			||||||
					<input type="checkbox" value="account-read"/>
 | 
					 | 
				
			||||||
					<p>アカウントの情報を見る。</p>
 | 
					 | 
				
			||||||
				</label>
 | 
					 | 
				
			||||||
				<label>
 | 
					 | 
				
			||||||
					<input type="checkbox" value="account-write"/>
 | 
					 | 
				
			||||||
					<p>アカウントの情報を操作する。</p>
 | 
					 | 
				
			||||||
				</label>
 | 
					 | 
				
			||||||
				<label>
 | 
					 | 
				
			||||||
					<input type="checkbox" value="post-write"/>
 | 
					 | 
				
			||||||
					<p>投稿する。</p>
 | 
					 | 
				
			||||||
				</label>
 | 
					 | 
				
			||||||
				<label>
 | 
					 | 
				
			||||||
					<input type="checkbox" value="reaction-write"/>
 | 
					 | 
				
			||||||
					<p>リアクションしたりリアクションをキャンセルする。</p>
 | 
					 | 
				
			||||||
				</label>
 | 
					 | 
				
			||||||
				<label>
 | 
					 | 
				
			||||||
					<input type="checkbox" value="following-write"/>
 | 
					 | 
				
			||||||
					<p>フォローしたりフォロー解除する。</p>
 | 
					 | 
				
			||||||
				</label>
 | 
					 | 
				
			||||||
				<label>
 | 
					 | 
				
			||||||
					<input type="checkbox" value="drive-read"/>
 | 
					 | 
				
			||||||
					<p>ドライブを見る。</p>
 | 
					 | 
				
			||||||
				</label>
 | 
					 | 
				
			||||||
				<label>
 | 
					 | 
				
			||||||
					<input type="checkbox" value="drive-write"/>
 | 
					 | 
				
			||||||
					<p>ドライブを操作する。</p>
 | 
					 | 
				
			||||||
				</label>
 | 
					 | 
				
			||||||
				<label>
 | 
					 | 
				
			||||||
					<input type="checkbox" value="notification-read"/>
 | 
					 | 
				
			||||||
					<p>通知を見る。</p>
 | 
					 | 
				
			||||||
				</label>
 | 
					 | 
				
			||||||
				<label>
 | 
					 | 
				
			||||||
					<input type="checkbox" value="notification-write"/>
 | 
					 | 
				
			||||||
					<p>通知を操作する。</p>
 | 
					 | 
				
			||||||
				</label>
 | 
					 | 
				
			||||||
			</div>
 | 
					 | 
				
			||||||
			<p>%fa:exclamation-triangle%アプリ作成後も変更できますが、新たな権限を付与する場合、その時点で関連付けられているユーザーキーはすべて無効になります。</p>
 | 
					 | 
				
			||||||
		</section>
 | 
					 | 
				
			||||||
		<button @click="onsubmit">アプリ作成</button>
 | 
					 | 
				
			||||||
	</form>
 | 
					 | 
				
			||||||
	<style lang="stylus" scoped>
 | 
					 | 
				
			||||||
		:scope
 | 
					 | 
				
			||||||
			display block
 | 
					 | 
				
			||||||
			overflow hidden
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			> form
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				section
 | 
					 | 
				
			||||||
					display block
 | 
					 | 
				
			||||||
					margin 16px 0
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
					.caption
 | 
					 | 
				
			||||||
						margin 0 0 4px 0
 | 
					 | 
				
			||||||
						color #616161
 | 
					 | 
				
			||||||
						font-size 0.95em
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
						> [data-fa]
 | 
					 | 
				
			||||||
							margin-right 0.25em
 | 
					 | 
				
			||||||
							color #96adac
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
					.info
 | 
					 | 
				
			||||||
						display block
 | 
					 | 
				
			||||||
						margin 4px 0
 | 
					 | 
				
			||||||
						font-size 0.8em
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
						> [data-fa]
 | 
					 | 
				
			||||||
							margin-right 0.3em
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				section.permission
 | 
					 | 
				
			||||||
					div
 | 
					 | 
				
			||||||
						padding 8px 0
 | 
					 | 
				
			||||||
						max-height 160px
 | 
					 | 
				
			||||||
						overflow auto
 | 
					 | 
				
			||||||
						background #fff
 | 
					 | 
				
			||||||
						border solid 1px #cecece
 | 
					 | 
				
			||||||
						border-radius 4px
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
					label
 | 
					 | 
				
			||||||
						display block
 | 
					 | 
				
			||||||
						padding 0 12px
 | 
					 | 
				
			||||||
						line-height 32px
 | 
					 | 
				
			||||||
						cursor pointer
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
						&:hover
 | 
					 | 
				
			||||||
							> p
 | 
					 | 
				
			||||||
								color #999
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
							[type='checkbox']:checked + p
 | 
					 | 
				
			||||||
								color #000
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
						[type='checkbox']
 | 
					 | 
				
			||||||
							margin-right 4px
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
						[type='checkbox']:checked + p
 | 
					 | 
				
			||||||
							color #111
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
						> p
 | 
					 | 
				
			||||||
							display inline
 | 
					 | 
				
			||||||
							color #aaa
 | 
					 | 
				
			||||||
							user-select none
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
					> p:last-child
 | 
					 | 
				
			||||||
						margin 6px
 | 
					 | 
				
			||||||
						font-size 0.8em
 | 
					 | 
				
			||||||
						color #999
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
						> [data-fa]
 | 
					 | 
				
			||||||
							margin-right 4px
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				[type=text]
 | 
					 | 
				
			||||||
				[type=url]
 | 
					 | 
				
			||||||
				textarea
 | 
					 | 
				
			||||||
					user-select text
 | 
					 | 
				
			||||||
					display inline-block
 | 
					 | 
				
			||||||
					cursor auto
 | 
					 | 
				
			||||||
					padding 8px 12px
 | 
					 | 
				
			||||||
					margin 0
 | 
					 | 
				
			||||||
					width 100%
 | 
					 | 
				
			||||||
					font-size 1em
 | 
					 | 
				
			||||||
					color #333
 | 
					 | 
				
			||||||
					background #fff
 | 
					 | 
				
			||||||
					outline none
 | 
					 | 
				
			||||||
					border solid 1px #cecece
 | 
					 | 
				
			||||||
					border-radius 4px
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
					&:hover
 | 
					 | 
				
			||||||
						border-color #bbb
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
					&:focus
 | 
					 | 
				
			||||||
						border-color $theme-color
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
					&:disabled
 | 
					 | 
				
			||||||
						opacity 0.5
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				> button
 | 
					 | 
				
			||||||
					margin 20px 0 32px 0
 | 
					 | 
				
			||||||
					width 100%
 | 
					 | 
				
			||||||
					font-size 1em
 | 
					 | 
				
			||||||
					color #111
 | 
					 | 
				
			||||||
					border-radius 3px
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	</style>
 | 
					 | 
				
			||||||
	<script lang="typescript">
 | 
					 | 
				
			||||||
		this.mixin('api');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		this.nidState = null;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		this.onChangeNid = () => {
 | 
					 | 
				
			||||||
			const nid = this.$refs.nid.value;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			if (nid == '') {
 | 
					 | 
				
			||||||
				this.update({
 | 
					 | 
				
			||||||
					nidState: null
 | 
					 | 
				
			||||||
				});
 | 
					 | 
				
			||||||
				return;
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			const err =
 | 
					 | 
				
			||||||
				!nid.match(/^[a-zA-Z0-9\-]+$/) ? 'invalid-format' :
 | 
					 | 
				
			||||||
				nid.length < 3                 ? 'min-range' :
 | 
					 | 
				
			||||||
				nid.length > 30                ? 'max-range' :
 | 
					 | 
				
			||||||
				null;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			if (err) {
 | 
					 | 
				
			||||||
				this.update({
 | 
					 | 
				
			||||||
					nidState: err
 | 
					 | 
				
			||||||
				});
 | 
					 | 
				
			||||||
				return;
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			this.update({
 | 
					 | 
				
			||||||
				nidState: 'wait'
 | 
					 | 
				
			||||||
			});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			this.$root.$data.os.api('app/name_id/available', {
 | 
					 | 
				
			||||||
				name_id: nid
 | 
					 | 
				
			||||||
			}).then(result => {
 | 
					 | 
				
			||||||
				this.update({
 | 
					 | 
				
			||||||
					nidState: result.available ? 'ok' : 'unavailable'
 | 
					 | 
				
			||||||
				});
 | 
					 | 
				
			||||||
			}).catch(err => {
 | 
					 | 
				
			||||||
				this.update({
 | 
					 | 
				
			||||||
					nidState: 'error'
 | 
					 | 
				
			||||||
				});
 | 
					 | 
				
			||||||
			});
 | 
					 | 
				
			||||||
		};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		this.onsubmit = () => {
 | 
					 | 
				
			||||||
			const name = this.$refs.name.value;
 | 
					 | 
				
			||||||
			const nid = this.$refs.nid.value;
 | 
					 | 
				
			||||||
			const description = this.$refs.description.value;
 | 
					 | 
				
			||||||
			const cb = this.$refs.cb.value;
 | 
					 | 
				
			||||||
			const permission = [];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			this.$refs.permission.querySelectorAll('input').forEach(el => {
 | 
					 | 
				
			||||||
				if (el.checked) permission.push(el.value);
 | 
					 | 
				
			||||||
			});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			const locker = document.body.appendChild(document.createElement('mk-locker'));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			this.$root.$data.os.api('app/create', {
 | 
					 | 
				
			||||||
				name: name,
 | 
					 | 
				
			||||||
				name_id: nid,
 | 
					 | 
				
			||||||
				description: description,
 | 
					 | 
				
			||||||
				callback_url: cb,
 | 
					 | 
				
			||||||
				permission: permission
 | 
					 | 
				
			||||||
			}).then(() => {
 | 
					 | 
				
			||||||
				location.href = '/apps';
 | 
					 | 
				
			||||||
			}).catch(() => {
 | 
					 | 
				
			||||||
				alert('アプリの作成に失敗しました。再度お試しください。');
 | 
					 | 
				
			||||||
				locker.parentNode.removeChild(locker);
 | 
					 | 
				
			||||||
			});
 | 
					 | 
				
			||||||
		};
 | 
					 | 
				
			||||||
	</script>
 | 
					 | 
				
			||||||
</mk-new-app-form>
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,32 +0,0 @@
 | 
				
			||||||
<mk-app-page>
 | 
					 | 
				
			||||||
	<p v-if="fetching">読み込み中</p>
 | 
					 | 
				
			||||||
	<main v-if="!fetching">
 | 
					 | 
				
			||||||
		<header>
 | 
					 | 
				
			||||||
			<h1>{ app.name }</h1>
 | 
					 | 
				
			||||||
		</header>
 | 
					 | 
				
			||||||
		<div class="body">
 | 
					 | 
				
			||||||
			<p>App Secret</p>
 | 
					 | 
				
			||||||
			<input value={ app.secret } readonly="readonly"/>
 | 
					 | 
				
			||||||
		</div>
 | 
					 | 
				
			||||||
	</main>
 | 
					 | 
				
			||||||
	<style lang="stylus" scoped>
 | 
					 | 
				
			||||||
		:scope
 | 
					 | 
				
			||||||
			display block
 | 
					 | 
				
			||||||
	</style>
 | 
					 | 
				
			||||||
	<script lang="typescript">
 | 
					 | 
				
			||||||
		this.mixin('api');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		this.fetching = true;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		this.on('mount', () => {
 | 
					 | 
				
			||||||
			this.$root.$data.os.api('app/show', {
 | 
					 | 
				
			||||||
				app_id: this.opts.app
 | 
					 | 
				
			||||||
			}).then(app => {
 | 
					 | 
				
			||||||
				this.update({
 | 
					 | 
				
			||||||
					fetching: false,
 | 
					 | 
				
			||||||
					app: app
 | 
					 | 
				
			||||||
				});
 | 
					 | 
				
			||||||
			});
 | 
					 | 
				
			||||||
		});
 | 
					 | 
				
			||||||
	</script>
 | 
					 | 
				
			||||||
</mk-app-page>
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,33 +0,0 @@
 | 
				
			||||||
<mk-apps-page>
 | 
					 | 
				
			||||||
	<h1>アプリを管理</h1><a href="/app/new">アプリ作成</a>
 | 
					 | 
				
			||||||
	<div class="apps">
 | 
					 | 
				
			||||||
		<p v-if="fetching">読み込み中</p>
 | 
					 | 
				
			||||||
		<template v-if="!fetching">
 | 
					 | 
				
			||||||
			<p v-if="apps.length == 0">アプリなし</p>
 | 
					 | 
				
			||||||
			<ul v-if="apps.length > 0">
 | 
					 | 
				
			||||||
				<li each={ app in apps }><a href={ '/app/' + app.id }>
 | 
					 | 
				
			||||||
						<p class="name">{ app.name }</p></a></li>
 | 
					 | 
				
			||||||
			</ul>
 | 
					 | 
				
			||||||
		</template>
 | 
					 | 
				
			||||||
	</div>
 | 
					 | 
				
			||||||
	<style lang="stylus" scoped>
 | 
					 | 
				
			||||||
		:scope
 | 
					 | 
				
			||||||
			display block
 | 
					 | 
				
			||||||
	</style>
 | 
					 | 
				
			||||||
	<script lang="typescript">
 | 
					 | 
				
			||||||
		this.mixin('api');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		this.fetching = true;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		this.on('mount', () => {
 | 
					 | 
				
			||||||
			this.$root.$data.os.api('my/apps').then(apps => {
 | 
					 | 
				
			||||||
				this.fetching = false
 | 
					 | 
				
			||||||
				this.apps = apps
 | 
					 | 
				
			||||||
				this.update({
 | 
					 | 
				
			||||||
					fetching: false,
 | 
					 | 
				
			||||||
					apps: apps
 | 
					 | 
				
			||||||
				});
 | 
					 | 
				
			||||||
			});
 | 
					 | 
				
			||||||
		});
 | 
					 | 
				
			||||||
	</script>
 | 
					 | 
				
			||||||
</mk-apps-page>
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,6 +0,0 @@
 | 
				
			||||||
<mk-index><a href="/apps">アプリ</a>
 | 
					 | 
				
			||||||
	<style lang="stylus" scoped>
 | 
					 | 
				
			||||||
		:scope
 | 
					 | 
				
			||||||
			display block
 | 
					 | 
				
			||||||
	</style>
 | 
					 | 
				
			||||||
</mk-index>
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,42 +0,0 @@
 | 
				
			||||||
<mk-new-app-page>
 | 
					 | 
				
			||||||
	<main>
 | 
					 | 
				
			||||||
		<header>
 | 
					 | 
				
			||||||
			<h1>新しいアプリを作成</h1>
 | 
					 | 
				
			||||||
			<p>MisskeyのAPIを利用したアプリケーションを作成できます。</p>
 | 
					 | 
				
			||||||
		</header>
 | 
					 | 
				
			||||||
		<mk-new-app-form/>
 | 
					 | 
				
			||||||
	</main>
 | 
					 | 
				
			||||||
	<style lang="stylus" scoped>
 | 
					 | 
				
			||||||
		:scope
 | 
					 | 
				
			||||||
			display block
 | 
					 | 
				
			||||||
			padding 64px 0
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			> main
 | 
					 | 
				
			||||||
				width 100%
 | 
					 | 
				
			||||||
				max-width 700px
 | 
					 | 
				
			||||||
				margin 0 auto
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				> header
 | 
					 | 
				
			||||||
					margin 0 0 16px 0
 | 
					 | 
				
			||||||
					padding 0 0 16px 0
 | 
					 | 
				
			||||||
					border-bottom solid 1px #282827
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
					> h1
 | 
					 | 
				
			||||||
						margin 0 0 12px 0
 | 
					 | 
				
			||||||
						padding 0
 | 
					 | 
				
			||||||
						line-height 32px
 | 
					 | 
				
			||||||
						font-size 32px
 | 
					 | 
				
			||||||
						font-weight normal
 | 
					 | 
				
			||||||
						color #000
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
					> p
 | 
					 | 
				
			||||||
						margin 0
 | 
					 | 
				
			||||||
						line-height 16px
 | 
					 | 
				
			||||||
						color #9a9894
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	</style>
 | 
					 | 
				
			||||||
</mk-new-app-page>
 | 
					 | 
				
			||||||
							
								
								
									
										43
									
								
								src/web/app/dev/views/app.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								src/web/app/dev/views/app.vue
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,43 @@
 | 
				
			||||||
 | 
					<template>
 | 
				
			||||||
 | 
					<div>
 | 
				
			||||||
 | 
						<p v-if="fetching">読み込み中</p>
 | 
				
			||||||
 | 
						<main v-if="!fetching">
 | 
				
			||||||
 | 
							<header>
 | 
				
			||||||
 | 
								<h1>{{ app.name }}</h1>
 | 
				
			||||||
 | 
							</header>
 | 
				
			||||||
 | 
							<div class="body">
 | 
				
			||||||
 | 
								<p>App Secret</p>
 | 
				
			||||||
 | 
								<input :value="app.secret" readonly/>
 | 
				
			||||||
 | 
							</div>
 | 
				
			||||||
 | 
						</main>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts">
 | 
				
			||||||
 | 
					import Vue from 'vue';
 | 
				
			||||||
 | 
					export default Vue.extend({
 | 
				
			||||||
 | 
						data() {
 | 
				
			||||||
 | 
							return {
 | 
				
			||||||
 | 
								fetching: true,
 | 
				
			||||||
 | 
								app: null
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						watch: {
 | 
				
			||||||
 | 
							$route: 'fetch'
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						mounted() {
 | 
				
			||||||
 | 
							this.fetch();
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						methods: {
 | 
				
			||||||
 | 
							fetch() {
 | 
				
			||||||
 | 
								this.fetching = true;
 | 
				
			||||||
 | 
								(this as any).api('app/show', {
 | 
				
			||||||
 | 
									app_id: this.$route.params.id
 | 
				
			||||||
 | 
								}).then(app => {
 | 
				
			||||||
 | 
									this.app = app;
 | 
				
			||||||
 | 
									this.fetching = false;
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
							
								
								
									
										24
									
								
								src/web/app/dev/views/apps.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								src/web/app/dev/views/apps.vue
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,24 @@
 | 
				
			||||||
 | 
					<template>
 | 
				
			||||||
 | 
					<div>
 | 
				
			||||||
 | 
						<h1>アプリを管理</h1>
 | 
				
			||||||
 | 
						<router-link to="/app/new">アプリ作成</router-link>
 | 
				
			||||||
 | 
						<div class="apps">
 | 
				
			||||||
 | 
							<p v-if="fetching">読み込み中</p>
 | 
				
			||||||
 | 
							<template v-if="!fetching">
 | 
				
			||||||
 | 
								<p v-if="apps.length == 0">アプリなし</p>
 | 
				
			||||||
 | 
								<ul v-else>
 | 
				
			||||||
 | 
									<li v-for="app in apps" :key="app.id">
 | 
				
			||||||
 | 
										<router-link :to="`/app/${app.id}`">
 | 
				
			||||||
 | 
											<p class="name">{{ app.name }}</p>
 | 
				
			||||||
 | 
										</router-link>
 | 
				
			||||||
 | 
									</li>
 | 
				
			||||||
 | 
								</ul>
 | 
				
			||||||
 | 
							</template>
 | 
				
			||||||
 | 
						</div>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts">
 | 
				
			||||||
 | 
					import Vue from 'vue';
 | 
				
			||||||
 | 
					export default Vue.extend();
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
							
								
								
									
										10
									
								
								src/web/app/dev/views/index.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/web/app/dev/views/index.vue
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,10 @@
 | 
				
			||||||
 | 
					<template>
 | 
				
			||||||
 | 
					<div>
 | 
				
			||||||
 | 
						<router-link to="/app">アプリ</router-link>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts">
 | 
				
			||||||
 | 
					import Vue from 'vue';
 | 
				
			||||||
 | 
					export default Vue.extend();
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
							
								
								
									
										145
									
								
								src/web/app/dev/views/new-app.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										145
									
								
								src/web/app/dev/views/new-app.vue
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,145 @@
 | 
				
			||||||
 | 
					<template>
 | 
				
			||||||
 | 
					<div>
 | 
				
			||||||
 | 
						<form @submit="onSubmit" autocomplete="off">
 | 
				
			||||||
 | 
							<section class="name">
 | 
				
			||||||
 | 
								<label>
 | 
				
			||||||
 | 
									<p class="caption">アプリケーション名</p>
 | 
				
			||||||
 | 
									<input v-model="name" type="text" placeholder="ex) Misskey for iOS" autocomplete="off" required/>
 | 
				
			||||||
 | 
								</label>
 | 
				
			||||||
 | 
							</section>
 | 
				
			||||||
 | 
							<section class="nid">
 | 
				
			||||||
 | 
								<label>
 | 
				
			||||||
 | 
									<p class="caption">Named ID</p>
 | 
				
			||||||
 | 
									<input v-model="nid" type="text" pattern="^[a-zA-Z0-9-]{3,30}$" placeholder="ex) misskey-for-ios" autocomplete="off" required/>
 | 
				
			||||||
 | 
									<p class="info" v-if="nidState == 'wait'" style="color:#999">%fa:spinner .pulse .fw%確認しています...</p>
 | 
				
			||||||
 | 
									<p class="info" v-if="nidState == 'ok'" style="color:#3CB7B5">%fa:fw check%利用できます</p>
 | 
				
			||||||
 | 
									<p class="info" v-if="nidState == 'unavailable'" style="color:#FF1161">%fa:fw exclamation-triangle%既に利用されています</p>
 | 
				
			||||||
 | 
									<p class="info" v-if="nidState == 'error'" style="color:#FF1161">%fa:fw exclamation-triangle%通信エラー</p>
 | 
				
			||||||
 | 
									<p class="info" v-if="nidState == 'invalid-format'" style="color:#FF1161">%fa:fw exclamation-triangle%a~z、A~Z、0~9、-(ハイフン)が使えます</p>
 | 
				
			||||||
 | 
									<p class="info" v-if="nidState == 'min-range'" style="color:#FF1161">%fa:fw exclamation-triangle%3文字以上でお願いします!</p>
 | 
				
			||||||
 | 
									<p class="info" v-if="nidState == 'max-range'" style="color:#FF1161">%fa:fw exclamation-triangle%30文字以内でお願いします</p>
 | 
				
			||||||
 | 
								</label>
 | 
				
			||||||
 | 
							</section>
 | 
				
			||||||
 | 
							<section class="description">
 | 
				
			||||||
 | 
								<label>
 | 
				
			||||||
 | 
									<p class="caption">アプリの概要</p>
 | 
				
			||||||
 | 
									<textarea v-model="description" placeholder="ex) Misskey iOSクライアント。" autocomplete="off" required></textarea>
 | 
				
			||||||
 | 
								</label>
 | 
				
			||||||
 | 
							</section>
 | 
				
			||||||
 | 
							<section class="callback">
 | 
				
			||||||
 | 
								<label>
 | 
				
			||||||
 | 
									<p class="caption">コールバックURL (オプション)</p>
 | 
				
			||||||
 | 
									<input v-model="cb" type="url" placeholder="ex) https://your.app.example.com/callback.php" autocomplete="off"/>
 | 
				
			||||||
 | 
								</label>
 | 
				
			||||||
 | 
							</section>
 | 
				
			||||||
 | 
							<section class="permission">
 | 
				
			||||||
 | 
								<p class="caption">権限</p>
 | 
				
			||||||
 | 
								<div ref="permission">
 | 
				
			||||||
 | 
									<label>
 | 
				
			||||||
 | 
										<input type="checkbox" value="account-read"/>
 | 
				
			||||||
 | 
										<p>アカウントの情報を見る。</p>
 | 
				
			||||||
 | 
									</label>
 | 
				
			||||||
 | 
									<label>
 | 
				
			||||||
 | 
										<input type="checkbox" value="account-write"/>
 | 
				
			||||||
 | 
										<p>アカウントの情報を操作する。</p>
 | 
				
			||||||
 | 
									</label>
 | 
				
			||||||
 | 
									<label>
 | 
				
			||||||
 | 
										<input type="checkbox" value="post-write"/>
 | 
				
			||||||
 | 
										<p>投稿する。</p>
 | 
				
			||||||
 | 
									</label>
 | 
				
			||||||
 | 
									<label>
 | 
				
			||||||
 | 
										<input type="checkbox" value="reaction-write"/>
 | 
				
			||||||
 | 
										<p>リアクションしたりリアクションをキャンセルする。</p>
 | 
				
			||||||
 | 
									</label>
 | 
				
			||||||
 | 
									<label>
 | 
				
			||||||
 | 
										<input type="checkbox" value="following-write"/>
 | 
				
			||||||
 | 
										<p>フォローしたりフォロー解除する。</p>
 | 
				
			||||||
 | 
									</label>
 | 
				
			||||||
 | 
									<label>
 | 
				
			||||||
 | 
										<input type="checkbox" value="drive-read"/>
 | 
				
			||||||
 | 
										<p>ドライブを見る。</p>
 | 
				
			||||||
 | 
									</label>
 | 
				
			||||||
 | 
									<label>
 | 
				
			||||||
 | 
										<input type="checkbox" value="drive-write"/>
 | 
				
			||||||
 | 
										<p>ドライブを操作する。</p>
 | 
				
			||||||
 | 
									</label>
 | 
				
			||||||
 | 
									<label>
 | 
				
			||||||
 | 
										<input type="checkbox" value="notification-read"/>
 | 
				
			||||||
 | 
										<p>通知を見る。</p>
 | 
				
			||||||
 | 
									</label>
 | 
				
			||||||
 | 
									<label>
 | 
				
			||||||
 | 
										<input type="checkbox" value="notification-write"/>
 | 
				
			||||||
 | 
										<p>通知を操作する。</p>
 | 
				
			||||||
 | 
									</label>
 | 
				
			||||||
 | 
								</div>
 | 
				
			||||||
 | 
								<p>%fa:exclamation-triangle%アプリ作成後も変更できますが、新たな権限を付与する場合、その時点で関連付けられているユーザーキーはすべて無効になります。</p>
 | 
				
			||||||
 | 
							</section>
 | 
				
			||||||
 | 
							<button type="submit">アプリ作成</button>
 | 
				
			||||||
 | 
						</form>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts">
 | 
				
			||||||
 | 
					import Vue from 'vue';
 | 
				
			||||||
 | 
					export default Vue.extend({
 | 
				
			||||||
 | 
						data() {
 | 
				
			||||||
 | 
							return {
 | 
				
			||||||
 | 
								name: '',
 | 
				
			||||||
 | 
								nid: '',
 | 
				
			||||||
 | 
								description: '',
 | 
				
			||||||
 | 
								cb: '',
 | 
				
			||||||
 | 
								nidState: null
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						watch: {
 | 
				
			||||||
 | 
							nid() {
 | 
				
			||||||
 | 
								if (this.nid == null || this.nid == '') {
 | 
				
			||||||
 | 
									this.nidState = null;
 | 
				
			||||||
 | 
									return;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								const err =
 | 
				
			||||||
 | 
									!this.nid.match(/^[a-zA-Z0-9\-]+$/) ? 'invalid-format' :
 | 
				
			||||||
 | 
									this.nid.length < 3                 ? 'min-range' :
 | 
				
			||||||
 | 
									this.nid.length > 30                ? 'max-range' :
 | 
				
			||||||
 | 
									null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (err) {
 | 
				
			||||||
 | 
									this.nidState = err;
 | 
				
			||||||
 | 
									return;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								this.nidState = 'wait';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								(this as any).api('app/name_id/available', {
 | 
				
			||||||
 | 
									name_id: this.nid
 | 
				
			||||||
 | 
								}).then(result => {
 | 
				
			||||||
 | 
									this.nidState = result.available ? 'ok' : 'unavailable';
 | 
				
			||||||
 | 
								}).catch(err => {
 | 
				
			||||||
 | 
									this.nidState = 'error';
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						methods: {
 | 
				
			||||||
 | 
							onSubmit() {
 | 
				
			||||||
 | 
								const permission = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								(this.$refs.permission as any).querySelectorAll('input').forEach(el => {
 | 
				
			||||||
 | 
									if (el.checked) permission.push(el.value);
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								(this as any).api('app/create', {
 | 
				
			||||||
 | 
									name: this.name,
 | 
				
			||||||
 | 
									name_id: this.nid,
 | 
				
			||||||
 | 
									description: this.description,
 | 
				
			||||||
 | 
									callback_url: this.cb,
 | 
				
			||||||
 | 
									permission: permission
 | 
				
			||||||
 | 
								}).then(() => {
 | 
				
			||||||
 | 
									location.href = '/apps';
 | 
				
			||||||
 | 
								}).catch(() => {
 | 
				
			||||||
 | 
									alert('アプリの作成に失敗しました。再度お試しください。');
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
| 
						 | 
					@ -77,7 +77,7 @@ if (localStorage.getItem('should-refresh') == 'true') {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// MiOSを初期化してコールバックする
 | 
					// MiOSを初期化してコールバックする
 | 
				
			||||||
export default (callback: (launch: (api: (os: MiOS) => API) => [Vue, MiOS]) => void, sw = false) => {
 | 
					export default (callback: (launch: (api?: (os: MiOS) => API) => [Vue, MiOS]) => void, sw = false) => {
 | 
				
			||||||
	const os = new MiOS(sw);
 | 
						const os = new MiOS(sw);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	os.init(() => {
 | 
						os.init(() => {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -38,8 +38,8 @@ module.exports = Object.keys(langs).map(lang => {
 | 
				
			||||||
		//ch: './src/web/app/ch/script.ts',
 | 
							//ch: './src/web/app/ch/script.ts',
 | 
				
			||||||
		//stats: './src/web/app/stats/script.ts',
 | 
							//stats: './src/web/app/stats/script.ts',
 | 
				
			||||||
		//status: './src/web/app/status/script.ts',
 | 
							//status: './src/web/app/status/script.ts',
 | 
				
			||||||
		//dev: './src/web/app/dev/script.ts',
 | 
							dev: './src/web/app/dev/script.ts',
 | 
				
			||||||
		//auth: './src/web/app/auth/script.ts',
 | 
							auth: './src/web/app/auth/script.ts',
 | 
				
			||||||
		sw: './src/web/app/sw.js'
 | 
							sw: './src/web/app/sw.js'
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue