memi-board 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -28
- package/dist/index.d.ts +2 -8
- package/dist/index.js +162 -814
- package/dist/index.js.map +1 -1
- package/dist/module.d.ts +4 -0
- package/dist/module.mjs +62 -0
- package/dist/module.mjs.map +1 -0
- package/dist/runtime/components/Attachments.vue +173 -0
- package/dist/runtime/components/CommentForm.vue +83 -0
- package/dist/runtime/components/CommentList.vue +64 -0
- package/dist/runtime/components/Detail.vue +156 -0
- package/dist/runtime/components/Editor.vue +168 -0
- package/dist/runtime/components/List.vue +121 -0
- package/dist/runtime/components/SignIn.vue +146 -0
- package/dist/runtime/components/VersionHistory.vue +36 -0
- package/package.json +35 -9
- package/dist/components/Attachments.vue.d.ts +0 -17
- package/dist/components/CommentForm.vue.d.ts +0 -5
- package/dist/components/CommentList.vue.d.ts +0 -5
- package/dist/components/Detail.vue.d.ts +0 -11
- package/dist/components/Editor.vue.d.ts +0 -12
- package/dist/components/List.vue.d.ts +0 -14
- package/dist/components/SignIn.vue.d.ts +0 -2
- package/dist/components/VersionHistory.vue.d.ts +0 -2
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref, computed } from 'vue'
|
|
3
|
+
import { useMemiBoardConfig } from 'memi-board'
|
|
4
|
+
import { useMemiBoardAuth } from 'memi-board'
|
|
5
|
+
|
|
6
|
+
const config = useMemiBoardConfig()
|
|
7
|
+
const providers = computed(() => config.auth?.providers ?? ['google', 'apple'])
|
|
8
|
+
const hasOAuthProvider = computed(() => providers.value.includes('google') || providers.value.includes('apple'))
|
|
9
|
+
|
|
10
|
+
const { signInWithGoogle, signInWithApple, signInWithEmail, signUpWithEmail } = useMemiBoardAuth()
|
|
11
|
+
|
|
12
|
+
const mode = ref<'signin' | 'signup'>('signin')
|
|
13
|
+
const email = ref('')
|
|
14
|
+
const password = ref('')
|
|
15
|
+
const displayName = ref('')
|
|
16
|
+
const loading = ref(false)
|
|
17
|
+
const error = ref('')
|
|
18
|
+
|
|
19
|
+
async function handleGoogle() {
|
|
20
|
+
loading.value = true
|
|
21
|
+
error.value = ''
|
|
22
|
+
try {
|
|
23
|
+
await signInWithGoogle()
|
|
24
|
+
}
|
|
25
|
+
catch (e) {
|
|
26
|
+
error.value = (e as Error).message
|
|
27
|
+
}
|
|
28
|
+
finally {
|
|
29
|
+
loading.value = false
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function handleApple() {
|
|
34
|
+
loading.value = true
|
|
35
|
+
error.value = ''
|
|
36
|
+
try {
|
|
37
|
+
await signInWithApple()
|
|
38
|
+
}
|
|
39
|
+
catch (e) {
|
|
40
|
+
error.value = (e as Error).message
|
|
41
|
+
}
|
|
42
|
+
finally {
|
|
43
|
+
loading.value = false
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function handleEmailSubmit() {
|
|
48
|
+
loading.value = true
|
|
49
|
+
error.value = ''
|
|
50
|
+
try {
|
|
51
|
+
if (mode.value === 'signin') {
|
|
52
|
+
await signInWithEmail(email.value, password.value)
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
await signUpWithEmail(email.value, password.value, displayName.value || undefined)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
catch (e) {
|
|
59
|
+
error.value = (e as Error).message
|
|
60
|
+
}
|
|
61
|
+
finally {
|
|
62
|
+
loading.value = false
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
</script>
|
|
66
|
+
|
|
67
|
+
<template>
|
|
68
|
+
<div class="flex flex-col gap-4 max-w-sm">
|
|
69
|
+
<UButton
|
|
70
|
+
v-if="providers.includes('google')"
|
|
71
|
+
icon="i-simple-icons-google"
|
|
72
|
+
color="neutral"
|
|
73
|
+
variant="outline"
|
|
74
|
+
label="Google로 로그인"
|
|
75
|
+
block
|
|
76
|
+
:loading="loading"
|
|
77
|
+
@click="handleGoogle"
|
|
78
|
+
/>
|
|
79
|
+
|
|
80
|
+
<UButton
|
|
81
|
+
v-if="providers.includes('apple')"
|
|
82
|
+
icon="i-simple-icons-apple"
|
|
83
|
+
color="neutral"
|
|
84
|
+
variant="outline"
|
|
85
|
+
label="Apple로 로그인"
|
|
86
|
+
block
|
|
87
|
+
:loading="loading"
|
|
88
|
+
@click="handleApple"
|
|
89
|
+
/>
|
|
90
|
+
|
|
91
|
+
<template v-if="providers.includes('emailPassword')">
|
|
92
|
+
<div
|
|
93
|
+
v-if="hasOAuthProvider"
|
|
94
|
+
class="flex items-center gap-2 text-xs text-muted"
|
|
95
|
+
>
|
|
96
|
+
<div class="flex-1 border-t border-default" />
|
|
97
|
+
또는
|
|
98
|
+
<div class="flex-1 border-t border-default" />
|
|
99
|
+
</div>
|
|
100
|
+
|
|
101
|
+
<form
|
|
102
|
+
class="flex flex-col gap-3"
|
|
103
|
+
@submit.prevent="handleEmailSubmit"
|
|
104
|
+
>
|
|
105
|
+
<UInput
|
|
106
|
+
v-if="mode === 'signup'"
|
|
107
|
+
v-model="displayName"
|
|
108
|
+
placeholder="이름"
|
|
109
|
+
/>
|
|
110
|
+
<UInput
|
|
111
|
+
v-model="email"
|
|
112
|
+
type="email"
|
|
113
|
+
placeholder="이메일"
|
|
114
|
+
required
|
|
115
|
+
/>
|
|
116
|
+
<UInput
|
|
117
|
+
v-model="password"
|
|
118
|
+
type="password"
|
|
119
|
+
placeholder="비밀번호"
|
|
120
|
+
required
|
|
121
|
+
/>
|
|
122
|
+
<UButton
|
|
123
|
+
type="submit"
|
|
124
|
+
block
|
|
125
|
+
:loading="loading"
|
|
126
|
+
:label="mode === 'signin' ? '로그인' : '회원가입'"
|
|
127
|
+
/>
|
|
128
|
+
</form>
|
|
129
|
+
|
|
130
|
+
<UButton
|
|
131
|
+
variant="link"
|
|
132
|
+
size="sm"
|
|
133
|
+
color="neutral"
|
|
134
|
+
:label="mode === 'signin' ? '계정이 없으신가요? 회원가입' : '이미 계정이 있으신가요? 로그인'"
|
|
135
|
+
@click="mode = mode === 'signin' ? 'signup' : 'signin'"
|
|
136
|
+
/>
|
|
137
|
+
</template>
|
|
138
|
+
|
|
139
|
+
<p
|
|
140
|
+
v-if="error"
|
|
141
|
+
class="text-sm text-error"
|
|
142
|
+
>
|
|
143
|
+
{{ error }}
|
|
144
|
+
</p>
|
|
145
|
+
</div>
|
|
146
|
+
</template>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { versionHistory } from 'memi-board'
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<template>
|
|
6
|
+
<div class="flex flex-col gap-6">
|
|
7
|
+
<div
|
|
8
|
+
v-for="entry in versionHistory"
|
|
9
|
+
:key="entry.version"
|
|
10
|
+
class="flex flex-col gap-2"
|
|
11
|
+
>
|
|
12
|
+
<div class="flex items-center gap-2">
|
|
13
|
+
<UBadge
|
|
14
|
+
:label="`v${entry.version}`"
|
|
15
|
+
color="primary"
|
|
16
|
+
variant="subtle"
|
|
17
|
+
/>
|
|
18
|
+
<span class="text-sm text-muted">{{ entry.date }}</span>
|
|
19
|
+
</div>
|
|
20
|
+
<ul class="list-disc list-inside text-sm leading-relaxed space-y-1">
|
|
21
|
+
<li
|
|
22
|
+
v-for="item in entry.highlights"
|
|
23
|
+
:key="item"
|
|
24
|
+
>
|
|
25
|
+
{{ item }}
|
|
26
|
+
</li>
|
|
27
|
+
</ul>
|
|
28
|
+
<p
|
|
29
|
+
v-if="entry.note"
|
|
30
|
+
class="text-xs text-muted"
|
|
31
|
+
>
|
|
32
|
+
{{ entry.note }}
|
|
33
|
+
</p>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
</template>
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "memi-board",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "Firebase
|
|
5
|
+
"description": "Firebase 게시판 — core composable + thin Nuxt module(UI SFC). Nuxt 4 + Nuxt UI 호스트에 붙임",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "fkkmemi",
|
|
8
8
|
"repository": {
|
|
@@ -13,8 +13,19 @@
|
|
|
13
13
|
"url": "https://github.com/fkkmemi/memi-board/issues"
|
|
14
14
|
},
|
|
15
15
|
"homepage": "https://github.com/fkkmemi/memi-board#readme",
|
|
16
|
-
"keywords": [
|
|
17
|
-
|
|
16
|
+
"keywords": [
|
|
17
|
+
"vue",
|
|
18
|
+
"nuxt",
|
|
19
|
+
"firebase",
|
|
20
|
+
"firestore",
|
|
21
|
+
"board",
|
|
22
|
+
"community",
|
|
23
|
+
"nuxt-ui"
|
|
24
|
+
],
|
|
25
|
+
"sideEffects": [
|
|
26
|
+
"**/*.vue",
|
|
27
|
+
"**/*.css"
|
|
28
|
+
],
|
|
18
29
|
"publishConfig": {
|
|
19
30
|
"access": "public"
|
|
20
31
|
},
|
|
@@ -22,6 +33,10 @@
|
|
|
22
33
|
".": {
|
|
23
34
|
"types": "./dist/index.d.ts",
|
|
24
35
|
"import": "./dist/index.js"
|
|
36
|
+
},
|
|
37
|
+
"./nuxt": {
|
|
38
|
+
"types": "./dist/module.d.ts",
|
|
39
|
+
"import": "./dist/module.mjs"
|
|
25
40
|
}
|
|
26
41
|
},
|
|
27
42
|
"main": "./dist/index.js",
|
|
@@ -31,23 +46,31 @@
|
|
|
31
46
|
],
|
|
32
47
|
"scripts": {
|
|
33
48
|
"prepack": "pnpm build",
|
|
34
|
-
"build": "
|
|
49
|
+
"build": "node scripts/build.mjs",
|
|
35
50
|
"build:watch": "vite build --watch",
|
|
36
|
-
"dev": "nuxi dev playground",
|
|
37
|
-
"dev:build": "nuxi build playground",
|
|
38
|
-
"dev:prepare": "nuxi prepare playground",
|
|
51
|
+
"dev": "pnpm build && nuxi dev playground",
|
|
52
|
+
"dev:build": "pnpm build && nuxi build playground",
|
|
53
|
+
"dev:prepare": "pnpm build && nuxi prepare playground",
|
|
39
54
|
"typecheck": "vue-tsc --noEmit"
|
|
40
55
|
},
|
|
41
56
|
"peerDependencies": {
|
|
42
57
|
"@nuxt/ui": "^4.0.0",
|
|
43
58
|
"firebase": "^12.0.0",
|
|
59
|
+
"nuxt": ">=3.0.0 || >=4.0.0",
|
|
44
60
|
"vue": "^3.5.0",
|
|
45
61
|
"vue-router": "^4.0.0 || ^5.0.0",
|
|
46
62
|
"vuefire": "^3.2.3"
|
|
47
63
|
},
|
|
64
|
+
"peerDependenciesMeta": {
|
|
65
|
+
"nuxt": {
|
|
66
|
+
"optional": true
|
|
67
|
+
},
|
|
68
|
+
"@nuxt/ui": {
|
|
69
|
+
"optional": true
|
|
70
|
+
}
|
|
71
|
+
},
|
|
48
72
|
"devDependencies": {
|
|
49
73
|
"@nuxt/ui": "^4.9.0",
|
|
50
|
-
"@vitejs/plugin-vue": "^6.0.8",
|
|
51
74
|
"firebase": "^12.15.0",
|
|
52
75
|
"nuxt": "^4.5.1",
|
|
53
76
|
"nuxt-vuefire": "^1.1.2",
|
|
@@ -67,5 +90,8 @@
|
|
|
67
90
|
"protobufjs",
|
|
68
91
|
"vue-demi"
|
|
69
92
|
]
|
|
93
|
+
},
|
|
94
|
+
"dependencies": {
|
|
95
|
+
"@nuxt/kit": "^4.5.1"
|
|
70
96
|
}
|
|
71
97
|
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { Attachment } from '../types';
|
|
2
|
-
type __VLS_Props = {
|
|
3
|
-
modelValue: Attachment[];
|
|
4
|
-
/** 새 글 작성 중이면 아직 slug가 없으므로 임시 네임스페이스를 넘긴다. */
|
|
5
|
-
postId: string;
|
|
6
|
-
editable?: boolean;
|
|
7
|
-
maxFiles?: number;
|
|
8
|
-
};
|
|
9
|
-
declare const _default: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
|
|
10
|
-
"update:modelValue": (attachments: Attachment[]) => any;
|
|
11
|
-
}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
12
|
-
"onUpdate:modelValue"?: ((attachments: Attachment[]) => any) | undefined;
|
|
13
|
-
}>, {
|
|
14
|
-
editable: boolean;
|
|
15
|
-
maxFiles: number;
|
|
16
|
-
}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
|
|
17
|
-
export default _default;
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
type __VLS_Props = {
|
|
2
|
-
postId: string;
|
|
3
|
-
};
|
|
4
|
-
declare const _default: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
|
|
5
|
-
export default _default;
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
type __VLS_Props = {
|
|
2
|
-
postId: string;
|
|
3
|
-
};
|
|
4
|
-
declare const _default: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
|
|
5
|
-
export default _default;
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
type __VLS_Props = {
|
|
2
|
-
postId: string;
|
|
3
|
-
};
|
|
4
|
-
declare const _default: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
|
|
5
|
-
deleted: () => any;
|
|
6
|
-
edit: (postId: string) => any;
|
|
7
|
-
}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
8
|
-
onDeleted?: (() => any) | undefined;
|
|
9
|
-
onEdit?: ((postId: string) => any) | undefined;
|
|
10
|
-
}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
|
|
11
|
-
export default _default;
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
type __VLS_Props = {
|
|
2
|
-
/** 지정하면 수정 모드, 지정하지 않으면 새 글 작성 모드. */
|
|
3
|
-
postId?: string;
|
|
4
|
-
};
|
|
5
|
-
declare const _default: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
|
|
6
|
-
saved: (id: string) => any;
|
|
7
|
-
cancel: () => any;
|
|
8
|
-
}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
9
|
-
onSaved?: ((id: string) => any) | undefined;
|
|
10
|
-
onCancel?: (() => any) | undefined;
|
|
11
|
-
}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
|
|
12
|
-
export default _default;
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { PostModel } from '../types';
|
|
2
|
-
type __VLS_Props = {
|
|
3
|
-
pageSize?: number;
|
|
4
|
-
/** 지정하면 각 글이 `${linkBase}/${id}`로 이동하는 링크가 된다. 지정하지 않으면 select 이벤트만 emit. */
|
|
5
|
-
linkBase?: string;
|
|
6
|
-
};
|
|
7
|
-
declare const _default: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
|
|
8
|
-
select: (post: PostModel) => any;
|
|
9
|
-
}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
10
|
-
onSelect?: ((post: PostModel) => any) | undefined;
|
|
11
|
-
}>, {
|
|
12
|
-
pageSize: number;
|
|
13
|
-
}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
|
|
14
|
-
export default _default;
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
declare const _default: import('vue').DefineComponent<{}, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {}, any>;
|
|
2
|
-
export default _default;
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
declare const _default: import('vue').DefineComponent<{}, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {}, any>;
|
|
2
|
-
export default _default;
|