kopia lustrzana https://codeberg.org/nmkj/audon
adding creation form
rodzic
636c1c3c18
commit
c4aa9cd2a8
|
@ -11,7 +11,7 @@
|
|||
"@vuelidate/core": "^2.0.0",
|
||||
"@vuelidate/validators": "^2.0.0",
|
||||
"axios": "^1.2.0",
|
||||
"lodash": "^4.17.21",
|
||||
"lodash-es": "^4.17.21",
|
||||
"masto": "^4.9.0",
|
||||
"pinia": "^2.0.26",
|
||||
"vue": "^3.2.45",
|
||||
|
@ -1713,7 +1713,13 @@
|
|||
"node_modules/lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/lodash-es": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
|
||||
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
|
||||
},
|
||||
"node_modules/lodash.merge": {
|
||||
"version": "4.6.2",
|
||||
|
@ -3898,7 +3904,13 @@
|
|||
"lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||
"dev": true
|
||||
},
|
||||
"lodash-es": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
|
||||
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
|
||||
},
|
||||
"lodash.merge": {
|
||||
"version": "4.6.2",
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
"@vuelidate/core": "^2.0.0",
|
||||
"@vuelidate/validators": "^2.0.0",
|
||||
"axios": "^1.2.0",
|
||||
"lodash": "^4.17.21",
|
||||
"lodash-es": "^4.17.21",
|
||||
"masto": "^4.9.0",
|
||||
"pinia": "^2.0.26",
|
||||
"vue": "^3.2.45",
|
||||
|
|
|
@ -1,22 +1,32 @@
|
|||
<script setup>
|
||||
import { RouterView } from 'vue-router'
|
||||
import { useMastodonStore } from "./stores/mastodon"
|
||||
|
||||
const donStore = useMastodonStore();
|
||||
donStore.fetchToken();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-container class="fill-height">
|
||||
<v-row align="center" justify="center" class="fill-height">
|
||||
<v-col id="appView">
|
||||
<v-responsive class="mx-auto" max-width="600px">
|
||||
<RouterView />
|
||||
</v-responsive>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
<v-app class="fill-height">
|
||||
<v-system-bar :height="40">
|
||||
<v-row>
|
||||
<v-col class="text-center">
|
||||
<h2>Audon</h2>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-system-bar>
|
||||
<v-main>
|
||||
<v-container class="fill-height">
|
||||
<v-row align="center" justify="center" class="fill-height">
|
||||
<v-col>
|
||||
<v-responsive class="mx-auto" max-width="600px">
|
||||
<RouterView />
|
||||
</v-responsive>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</v-main>
|
||||
</v-app>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
#app .v-application__wrap {
|
||||
min-height: 100%;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -6,3 +6,8 @@ export const validators = {
|
|||
/^([a-zA-Z0-9]{1}[a-zA-Z0-9-]{0,62})(\.[a-zA-Z0-9]{1}[a-zA-Z0-9-]{0,62})*?(\.[a-zA-Z]{1}[a-zA-Z0-9]{0,62})\.?$/
|
||||
),
|
||||
}
|
||||
|
||||
export function webfinger(user) {
|
||||
const url = new URL(user.url)
|
||||
return `${user.acct}@${url.host}`
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ const vuetify = createVuetify({
|
|||
defaultTheme: "dark",
|
||||
},
|
||||
icons: {
|
||||
defaultSet: "mdi",
|
||||
aliases,
|
||||
sets: {
|
||||
mdi,
|
||||
|
@ -26,6 +27,7 @@ const vuetify = createVuetify({
|
|||
});
|
||||
|
||||
axios.defaults.withCredentials = true;
|
||||
|
||||
// if audon server returns 401, display the login form
|
||||
axios.interceptors.response.use(undefined, (error) => {
|
||||
if (error.response?.status === 401) {
|
||||
|
@ -34,11 +36,22 @@ axios.interceptors.response.use(undefined, (error) => {
|
|||
}
|
||||
return Promise.reject(error);
|
||||
});
|
||||
|
||||
router.beforeEach(async (to) => {
|
||||
const donStore = useMastodonStore();
|
||||
if (!to.meta.noauth && !donStore.authorized) {
|
||||
try {
|
||||
await donStore.fetchToken();
|
||||
} catch (error) {
|
||||
if (error.response?.status === 401) {
|
||||
donStore.$reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
router.afterEach((to) => {
|
||||
const donStore = useMastodonStore();
|
||||
if (!to.meta.noauth && !donStore.authorized) {
|
||||
router.push({ name: "login" });
|
||||
router.push({ name: "login" }); // need to push in afterEach to get nonempty lastPath in LoginView.vue
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { createRouter, createWebHistory } from "vue-router";
|
||||
import HomeView from "../views/HomeView.vue";
|
||||
import LoginView from "../views/LoginView.vue";
|
||||
import CreateView from "../views/CreateView.vue";
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
|
@ -25,10 +26,14 @@ const router = createRouter({
|
|||
path: "/login",
|
||||
name: "login",
|
||||
meta: {
|
||||
noauth: true
|
||||
noauth: true,
|
||||
},
|
||||
component: LoginView,
|
||||
// component: () => import("../views/LoginView.vue"),
|
||||
},
|
||||
{
|
||||
path: "/create",
|
||||
name: "create",
|
||||
component: CreateView,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
|
|
@ -1,23 +1,47 @@
|
|||
import { defineStore } from "pinia";
|
||||
import axios from "axios";
|
||||
import { login } from "masto";
|
||||
import router from "../router";
|
||||
import { webfinger } from "../assets/utils";
|
||||
|
||||
export const useMastodonStore = defineStore("mastodon", {
|
||||
state() {
|
||||
return {
|
||||
authorized: false,
|
||||
oauth: null,
|
||||
authorized: false
|
||||
client: null,
|
||||
userinfo: null,
|
||||
};
|
||||
},
|
||||
getters: {
|
||||
myWebfinger() {
|
||||
if (this.userinfo !== null) {
|
||||
return webfinger(this.userinfo);
|
||||
}
|
||||
return "";
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
async fetchToken() {
|
||||
const resp = await axios.get("/api/token");
|
||||
this.oauth = resp.data;
|
||||
const client = await login({
|
||||
url: this.oauth.url,
|
||||
accessToken: this.oauth.token,
|
||||
});
|
||||
this.client = client;
|
||||
this.userinfo = await client.accounts.verifyCredentials();
|
||||
this.authorized = true;
|
||||
},
|
||||
async callMastodonAPI(caller, ...args) {
|
||||
try {
|
||||
const resp = await axios.get("/api/token");
|
||||
this.$state.oauth = resp.data;
|
||||
this.$state.authorized = true
|
||||
return await caller(...args);
|
||||
} catch (error) {
|
||||
if (error.response?.status !== 401) {
|
||||
alert(`Server is down: ${error.response.status}`);
|
||||
if (error.response?.status === 401) {
|
||||
this.$reset();
|
||||
router.push({ name: "login" });
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
|
@ -0,0 +1,159 @@
|
|||
<script>
|
||||
import { mdiArrowLeft, mdiMagnify, mdiClose } from "@mdi/js";
|
||||
import { debounce } from "lodash-es";
|
||||
import { login } from "masto";
|
||||
import { webfinger } from "../assets/utils";
|
||||
|
||||
export default {
|
||||
created() {
|
||||
this.cohostSearch = debounce(this.search, 1000);
|
||||
},
|
||||
unmounted() {
|
||||
this.cohostSearch.cancel();
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
mdiArrowLeft,
|
||||
mdiMagnify,
|
||||
mdiClose,
|
||||
title: "",
|
||||
description: "",
|
||||
cohosts: [
|
||||
{
|
||||
acct: "admin",
|
||||
displayName: "Test Name",
|
||||
avatar:
|
||||
"https://mstdn.nmkj.tk/system/accounts/avatars/109/453/041/233/122/320/original/a111d61c635f57ae.png",
|
||||
url: "https://mstdn.nmkj.tk/@admin",
|
||||
},
|
||||
],
|
||||
relationship: "everyone",
|
||||
relOptions: [
|
||||
{ title: "制限なし", value: "everyone" },
|
||||
{ title: "フォロー限定", value: "following" },
|
||||
{ title: "フォロワー限定", value: "follower" },
|
||||
{ title: "フォローまたはフォロワー限定", value: "knows" },
|
||||
{ title: "相互フォロー限定", value: "mutual" },
|
||||
],
|
||||
searchResult:
|
||||
{
|
||||
acct: "user1",
|
||||
displayName: "User 1",
|
||||
avatar:
|
||||
"https://media.songbird.cloud/accounts/avatars/109/374/604/338/855/643/original/23b2384b4bc8ccae.png",
|
||||
url: "https://mstdn.nmkj.tk/@user1",
|
||||
},
|
||||
searchQuery: "",
|
||||
isCandiadateLoading: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
async search(val) {
|
||||
if (!val) return;
|
||||
const webfinger = val.split("@");
|
||||
if (webfinger.length < 2) return;
|
||||
this.cohostSearch(webfinger);
|
||||
try {
|
||||
const url = new URL(`https://${webfinger[1]}`);
|
||||
this.isCandiadateLoading = true;
|
||||
const client = await login({ url: url.toString() });
|
||||
const user = await client.accounts.lookup({ acct: webfinger[0] });
|
||||
this.searchResult = [user];
|
||||
} catch {
|
||||
} finally {
|
||||
this.isCandiadateLoading = false;
|
||||
}
|
||||
},
|
||||
webfinger
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main>
|
||||
<div>
|
||||
<v-btn class="ma-2" variant="text" color="blue" :to="{ name: 'home' }">
|
||||
<v-icon start :icon="mdiArrowLeft"></v-icon>
|
||||
戻る
|
||||
</v-btn>
|
||||
<v-card>
|
||||
<v-card-title class="text-center">部屋を新規作成</v-card-title>
|
||||
<v-card-text>
|
||||
<v-form>
|
||||
<v-text-field v-model="title" label="タイトル"></v-text-field>
|
||||
<v-textarea
|
||||
auto-grow
|
||||
v-model="description"
|
||||
rows="2"
|
||||
label="説明"
|
||||
></v-textarea>
|
||||
<v-select
|
||||
:items="relOptions"
|
||||
label="入室制限"
|
||||
v-model="relationship"
|
||||
disabled
|
||||
:messages="['今後のアップデートで追加予定']"
|
||||
></v-select>
|
||||
<v-card class="my-2" variant="outlined">
|
||||
<v-card-title class="text-subtitle-1">共同ホスト</v-card-title>
|
||||
<v-card-text v-if="(cohosts.length > 0 || searchResult)">
|
||||
<div v-if="(cohosts.length > 0)">
|
||||
<v-list lines="two" variant="tonal">
|
||||
<v-list-item
|
||||
v-for="(cohost, index) in cohosts"
|
||||
:key="cohost.url"
|
||||
:title="cohost.displayName"
|
||||
rounded
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<v-avatar class="rounded">
|
||||
<v-img :src="cohost.avatar"></v-img>
|
||||
</v-avatar>
|
||||
</template>
|
||||
<template v-slot:subtitle>
|
||||
{{ webfinger(cohost) }}
|
||||
</template>
|
||||
<template v-slot:append>
|
||||
<v-btn variant="plain" size="small" :icon="mdiClose" @click="() => {cohosts.splice(index)}"></v-btn>
|
||||
</template>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</div>
|
||||
<div v-if="(searchResult)">
|
||||
<v-divider></v-divider>
|
||||
<v-list lines="two" variant="flat">
|
||||
<v-list-item :title="searchResult.displayName">
|
||||
<template v-slot:prepend>
|
||||
<v-avatar class="rounded">
|
||||
<v-img :src="searchResult.avatar"></v-img>
|
||||
</v-avatar>
|
||||
</template>
|
||||
<v-list-item-subtitle>
|
||||
{{ webfinger(searchResult) }}
|
||||
</v-list-item-subtitle>
|
||||
<v-list-item-action end>
|
||||
</v-list-item-action>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</div>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-text-field
|
||||
density="compact"
|
||||
v-model="searchQuery"
|
||||
:prepend-inner-icon="mdiMagnify"
|
||||
single-line
|
||||
hide-details
|
||||
clearable
|
||||
:loading="isCandiadateLoading"
|
||||
placeholder="user@mastodon.example"
|
||||
>
|
||||
</v-text-field>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-form>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</div>
|
||||
</main>
|
||||
</template>
|
|
@ -1,12 +1,45 @@
|
|||
<script>
|
||||
import { RouterLink } from 'vue-router';
|
||||
import { RouterLink } from "vue-router";
|
||||
import { useMastodonStore } from "../stores/mastodon";
|
||||
|
||||
export default {
|
||||
}
|
||||
setup() {
|
||||
return {
|
||||
donStore: useMastodonStore(),
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
query: ""
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main>
|
||||
<RouterLink to="/login" replace>Login</RouterLink>
|
||||
<div class="text-center my-10" >
|
||||
<v-avatar class="rounded" size="100">
|
||||
<v-img
|
||||
:src="donStore.userinfo?.avatar"
|
||||
:alt="donStore.userinfo?.displayName"
|
||||
>
|
||||
</v-img>
|
||||
</v-avatar>
|
||||
<h2 class="mt-5">
|
||||
{{ donStore.userinfo?.displayName }}
|
||||
</h2>
|
||||
<div>
|
||||
<a :href="donStore.userinfo?.url">{{ donStore.myWebfinger }}</a>
|
||||
</div>
|
||||
</div>
|
||||
<v-row class="text-center" justify="center">
|
||||
<!-- <v-col cols="12">
|
||||
<v-text-field v-mode="query"></v-text-field>
|
||||
</v-col> -->
|
||||
<v-col cols="12">
|
||||
<v-btn block :to="{name: 'create'}" color="indigo">部屋を作成</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</main>
|
||||
</template>
|
||||
|
|
|
@ -3,7 +3,7 @@ import { RouterLink } from "vue-router";
|
|||
import { useVuelidate } from "@vuelidate/core";
|
||||
import { required, helpers } from "@vuelidate/validators";
|
||||
import { validators } from "../assets/utils";
|
||||
import _ from "lodash/collection";
|
||||
import { map } from "lodash-es";
|
||||
import axios from "axios";
|
||||
|
||||
export default {
|
||||
|
@ -37,7 +37,7 @@ export default {
|
|||
computed: {
|
||||
serverErrors() {
|
||||
const errors = this.v$.server.$errors;
|
||||
const messages = _.map(errors, (e) => e.$message);
|
||||
const messages = map(errors, (e) => e.$message);
|
||||
if (this.serverErr !== "") {
|
||||
messages.push(this.serverErr);
|
||||
}
|
||||
|
@ -56,9 +56,8 @@ export default {
|
|||
server: this.server,
|
||||
});
|
||||
if (response.status === 201) {
|
||||
// this.$router.push(response.data)
|
||||
location.assign(response.data);
|
||||
this.serverErr = "";
|
||||
location.assign(response.data);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.response?.status === 404) {
|
||||
|
@ -78,7 +77,6 @@ export default {
|
|||
<v-alert v-if="$route.query.warn" type="warning" variant="text">
|
||||
<div>ログインが必要です</div>
|
||||
</v-alert>
|
||||
<h1>Audon</h1>
|
||||
<v-form ref="form" @submit.prevent="onSubmit" class="my-3" lazy-validation>
|
||||
<v-text-field
|
||||
v-model="server"
|
||||
|
|
Ładowanie…
Reference in New Issue