diff --git a/src/web/app/auth/script.ts b/src/web/app/auth/script.ts new file mode 100644 index 0000000000000000000000000000000000000000..31c758ebc2b389c888a58077127a1a188c56be80 --- /dev/null +++ b/src/web/app/auth/script.ts @@ -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 }, + ]); +}); diff --git a/src/web/app/auth/views/index.vue b/src/web/app/auth/views/index.vue index 1e372c0bde7f54ffd28052fc699322fbc76f70ee..17e5cc610861409cd67e3123f0737316226f9ccd 100644 --- a/src/web/app/auth/views/index.vue +++ b/src/web/app/auth/views/index.vue @@ -42,10 +42,14 @@ export default Vue.extend({ return { state: null, session: null, - fetching: true, - token: window.location.href.split('/').pop() + fetching: true }; }, + computed: { + token(): string { + return this.$route.params.token; + } + }, mounted() { if (!this.$root.$data.os.isSignedIn) return; diff --git a/src/web/app/dev/script.ts b/src/web/app/dev/script.ts index bb434111954f1e05a028baf284b3cd316f21c8fa..757bfca490cd0b511748b4233f78899222247743 100644 --- a/src/web/app/dev/script.ts +++ b/src/web/app/dev/script.ts @@ -5,11 +5,25 @@ // Style import './style.styl'; -require('./tags'); 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(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 }, + ]); }); diff --git a/src/web/app/dev/tags/index.ts b/src/web/app/dev/tags/index.ts deleted file mode 100644 index 1e0c73697e374e01d2254ec1bc8d788a3af80711..0000000000000000000000000000000000000000 --- a/src/web/app/dev/tags/index.ts +++ /dev/null @@ -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'); diff --git a/src/web/app/dev/tags/new-app-form.tag b/src/web/app/dev/tags/new-app-form.tag deleted file mode 100644 index cf3c440079372553c6775dbb476f4b0cb480f67f..0000000000000000000000000000000000000000 --- a/src/web/app/dev/tags/new-app-form.tag +++ /dev/null @@ -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> diff --git a/src/web/app/dev/tags/pages/app.tag b/src/web/app/dev/tags/pages/app.tag deleted file mode 100644 index 982549ed2be71b933ca90d4c7ca050cc28cde90d..0000000000000000000000000000000000000000 --- a/src/web/app/dev/tags/pages/app.tag +++ /dev/null @@ -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> diff --git a/src/web/app/dev/tags/pages/apps.tag b/src/web/app/dev/tags/pages/apps.tag deleted file mode 100644 index 6ae6031e644dcd44ead1327ec45581ef25b9e1f2..0000000000000000000000000000000000000000 --- a/src/web/app/dev/tags/pages/apps.tag +++ /dev/null @@ -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> diff --git a/src/web/app/dev/tags/pages/index.tag b/src/web/app/dev/tags/pages/index.tag deleted file mode 100644 index ca270b3774134d92e1316199ad905e9b616d8dcd..0000000000000000000000000000000000000000 --- a/src/web/app/dev/tags/pages/index.tag +++ /dev/null @@ -1,6 +0,0 @@ -<mk-index><a href="/apps">アプリ</a> - <style lang="stylus" scoped> - :scope - display block - </style> -</mk-index> diff --git a/src/web/app/dev/tags/pages/new-app.tag b/src/web/app/dev/tags/pages/new-app.tag deleted file mode 100644 index 26185f278bec5a91edae2ed8e5eb71da8e4daf32..0000000000000000000000000000000000000000 --- a/src/web/app/dev/tags/pages/new-app.tag +++ /dev/null @@ -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> diff --git a/src/web/app/dev/views/app.vue b/src/web/app/dev/views/app.vue new file mode 100644 index 0000000000000000000000000000000000000000..9eddabbec22560f8b6df069db7dd6099c924da29 --- /dev/null +++ b/src/web/app/dev/views/app.vue @@ -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> diff --git a/src/web/app/dev/views/apps.vue b/src/web/app/dev/views/apps.vue new file mode 100644 index 0000000000000000000000000000000000000000..e8adbea2dac3d171d9b4a6c45c60b54e71d598cb --- /dev/null +++ b/src/web/app/dev/views/apps.vue @@ -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> diff --git a/src/web/app/dev/views/index.vue b/src/web/app/dev/views/index.vue new file mode 100644 index 0000000000000000000000000000000000000000..a8429a56ddc10df3413ded4d86768cde5c3720ec --- /dev/null +++ b/src/web/app/dev/views/index.vue @@ -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> diff --git a/src/web/app/dev/views/new-app.vue b/src/web/app/dev/views/new-app.vue new file mode 100644 index 0000000000000000000000000000000000000000..f2e5659a4e03f668de280a23cfd33ba20ccfd91c --- /dev/null +++ b/src/web/app/dev/views/new-app.vue @@ -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> diff --git a/src/web/app/init.ts b/src/web/app/init.ts index 5c0b4c2d39acbebf81b8055a3abc1a9e52c8bf96..2e90d62d799951b8ce818ffe0bc93c9b13b9be9b 100644 --- a/src/web/app/init.ts +++ b/src/web/app/init.ts @@ -77,7 +77,7 @@ if (localStorage.getItem('should-refresh') == 'true') { } // 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); os.init(() => { diff --git a/webpack/webpack.config.ts b/webpack/webpack.config.ts index 69e05dc8c66c26cf50b03fc37bc5de2e724681d0..775ecbd34848641970354fbba857a8e0562d0965 100644 --- a/webpack/webpack.config.ts +++ b/webpack/webpack.config.ts @@ -38,8 +38,8 @@ module.exports = Object.keys(langs).map(lang => { //ch: './src/web/app/ch/script.ts', //stats: './src/web/app/stats/script.ts', //status: './src/web/app/status/script.ts', - //dev: './src/web/app/dev/script.ts', - //auth: './src/web/app/auth/script.ts', + dev: './src/web/app/dev/script.ts', + auth: './src/web/app/auth/script.ts', sw: './src/web/app/sw.js' };