@esershnr/artalk-sidebar 1.0.3
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 +16 -0
- package/auto-imports.d.ts +76 -0
- package/components.d.ts +31 -0
- package/env.d.ts +2 -0
- package/index.html +15 -0
- package/package.json +32 -0
- package/public/favicon.png +0 -0
- package/public/robots.txt +2 -0
- package/src/App.vue +89 -0
- package/src/artalk.ts +82 -0
- package/src/assets/favicon.png +0 -0
- package/src/assets/icon-darkmode-off.svg +1 -0
- package/src/assets/icon-darkmode-on.svg +1 -0
- package/src/assets/icon-eye-off.svg +1 -0
- package/src/assets/icon-eye-on.svg +1 -0
- package/src/assets/nav-icon-comments.svg +1 -0
- package/src/assets/nav-icon-pages.svg +1 -0
- package/src/assets/nav-icon-search.svg +1 -0
- package/src/assets/nav-icon-settings.svg +1 -0
- package/src/assets/nav-icon-sites.svg +1 -0
- package/src/assets/nav-icon-transfer.svg +1 -0
- package/src/assets/nav-icon-users.svg +1 -0
- package/src/components/AppHeader.vue +235 -0
- package/src/components/AppNavigation.vue +11 -0
- package/src/components/AppNavigationDesktop.vue +176 -0
- package/src/components/AppNavigationMenu.ts +152 -0
- package/src/components/AppNavigationMobile.vue +187 -0
- package/src/components/AppNavigationSearch.vue +137 -0
- package/src/components/FileUploader.vue +149 -0
- package/src/components/ItemTextEditor.vue +130 -0
- package/src/components/LoadingLayer.vue +37 -0
- package/src/components/LogTerminal.vue +89 -0
- package/src/components/PageEditor.vue +171 -0
- package/src/components/Pagination.vue +253 -0
- package/src/components/PreferenceArr.vue +105 -0
- package/src/components/PreferenceGrp.vue +153 -0
- package/src/components/PreferenceItem.vue +159 -0
- package/src/components/SiteCreate.vue +96 -0
- package/src/components/SiteEditor.vue +138 -0
- package/src/components/SiteSwitcher.vue +184 -0
- package/src/components/UserEditor.vue +229 -0
- package/src/global.ts +62 -0
- package/src/hooks/MobileWidth.ts +27 -0
- package/src/i18n/fr.ts +103 -0
- package/src/i18n/ja.ts +100 -0
- package/src/i18n/ko.ts +99 -0
- package/src/i18n/ru.ts +102 -0
- package/src/i18n/tr.ts +102 -0
- package/src/i18n/zh-CN.ts +97 -0
- package/src/i18n/zh-TW.ts +97 -0
- package/src/i18n-en.ts +99 -0
- package/src/i18n.ts +37 -0
- package/src/lib/promise-polyfill.ts +9 -0
- package/src/lib/settings-option.ts +186 -0
- package/src/lib/settings-sensitive.ts +44 -0
- package/src/lib/settings.ts +94 -0
- package/src/main.ts +65 -0
- package/src/pages/comments.vue +110 -0
- package/src/pages/index.vue +33 -0
- package/src/pages/login.vue +245 -0
- package/src/pages/pages.vue +309 -0
- package/src/pages/settings.vue +181 -0
- package/src/pages/sites.vue +353 -0
- package/src/pages/transfer.vue +204 -0
- package/src/pages/users.vue +271 -0
- package/src/stores/nav.ts +114 -0
- package/src/stores/user.ts +48 -0
- package/src/style/_extends.scss +100 -0
- package/src/style/_variables.scss +18 -0
- package/src/style.scss +245 -0
- package/src/vue-i18n.d.ts +7 -0
- package/tsconfig.json +40 -0
- package/tsconfig.node.json +11 -0
- package/typed-router.d.ts +30 -0
- package/vite.config.ts +71 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { useUserStore } from '../stores/user'
|
|
3
|
+
|
|
4
|
+
const { t } = useI18n()
|
|
5
|
+
const user = useUserStore()
|
|
6
|
+
|
|
7
|
+
const props = defineProps<{
|
|
8
|
+
apiUrl: string
|
|
9
|
+
}>()
|
|
10
|
+
|
|
11
|
+
const emit = defineEmits<{
|
|
12
|
+
/** Upload done event */
|
|
13
|
+
(evt: 'done', filename: string): void
|
|
14
|
+
}>()
|
|
15
|
+
|
|
16
|
+
const { apiUrl } = toRefs(props)
|
|
17
|
+
|
|
18
|
+
let xhr: XMLHttpRequest | null = null
|
|
19
|
+
const fileInputEl = ref<HTMLInputElement | null>(null)
|
|
20
|
+
const remoteFilename = ref('')
|
|
21
|
+
const isUploading = ref(false)
|
|
22
|
+
const progress = ref(0)
|
|
23
|
+
const isDone = ref(false)
|
|
24
|
+
|
|
25
|
+
function reset() {
|
|
26
|
+
remoteFilename.value = ''
|
|
27
|
+
isUploading.value = false
|
|
28
|
+
progress.value = 0
|
|
29
|
+
isDone.value = false
|
|
30
|
+
if (fileInputEl.value) fileInputEl.value.value = ''
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function startUploadFile(file: File) {
|
|
34
|
+
remoteFilename.value = ''
|
|
35
|
+
|
|
36
|
+
xhr = new XMLHttpRequest()
|
|
37
|
+
|
|
38
|
+
// Progress bar
|
|
39
|
+
xhr.upload.addEventListener('progress', (evt) => {
|
|
40
|
+
if (evt.loaded === evt.total) {
|
|
41
|
+
// Upload done
|
|
42
|
+
progress.value = 100
|
|
43
|
+
return
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const fileSize = file.size
|
|
47
|
+
if (evt.loaded <= fileSize) {
|
|
48
|
+
// Uploading
|
|
49
|
+
progress.value = Math.round((evt.loaded / fileSize) * 100)
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
// Create form data for upload
|
|
54
|
+
const formData = new FormData()
|
|
55
|
+
formData.append('file', file)
|
|
56
|
+
formData.append('token', user.token)
|
|
57
|
+
|
|
58
|
+
// Start upload
|
|
59
|
+
xhr.open('post', apiUrl.value)
|
|
60
|
+
xhr.send(formData)
|
|
61
|
+
|
|
62
|
+
// Update finished event
|
|
63
|
+
xhr.onload = () => {
|
|
64
|
+
const setErr = (msg: string): void => {
|
|
65
|
+
reset()
|
|
66
|
+
isUploading.value = false
|
|
67
|
+
alert(`File upload failed: ${msg}`)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (!xhr) {
|
|
71
|
+
setErr('xhr instance is null')
|
|
72
|
+
return
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const ok = xhr.status >= 200 && xhr.status <= 299
|
|
76
|
+
if (!ok) {
|
|
77
|
+
setErr(`Response HTTP Code: ${xhr.status}, Body: ${xhr.response}`)
|
|
78
|
+
return
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
let json: any
|
|
82
|
+
try {
|
|
83
|
+
json = JSON.parse(xhr.response)
|
|
84
|
+
} catch (err) {
|
|
85
|
+
console.error(err)
|
|
86
|
+
setErr(`JSON parse error: ${err}`)
|
|
87
|
+
return
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (!json.filename) {
|
|
91
|
+
setErr(`Response filename is empty: ${xhr.response}`)
|
|
92
|
+
return
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
isDone.value = true
|
|
96
|
+
remoteFilename.value = json.filename
|
|
97
|
+
isUploading.value = false
|
|
98
|
+
emit('done', remoteFilename.value)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function onFileInputChange() {
|
|
103
|
+
const files = fileInputEl.value?.files
|
|
104
|
+
if (!files || files.length === 0) return
|
|
105
|
+
|
|
106
|
+
isUploading.value = true
|
|
107
|
+
setTimeout(async () => {
|
|
108
|
+
await startUploadFile(files[0])
|
|
109
|
+
isUploading.value = false
|
|
110
|
+
}, 80)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function abortUpload() {
|
|
114
|
+
xhr?.abort()
|
|
115
|
+
reset()
|
|
116
|
+
isUploading.value = false
|
|
117
|
+
}
|
|
118
|
+
</script>
|
|
119
|
+
|
|
120
|
+
<template>
|
|
121
|
+
<div class="atk-file-upload-group">
|
|
122
|
+
<div v-show="!isUploading" class="atk-file-input-wrap atk-fade-in">
|
|
123
|
+
<input
|
|
124
|
+
ref="fileInputEl"
|
|
125
|
+
type="file"
|
|
126
|
+
name="AtkDataFile"
|
|
127
|
+
accept=".artrans"
|
|
128
|
+
@change="onFileInputChange()"
|
|
129
|
+
/>
|
|
130
|
+
<div class="atk-desc">
|
|
131
|
+
<slot v-if="!isDone" name="tip"></slot>
|
|
132
|
+
<slot v-if="isDone" name="done-msg"></slot>
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
<div v-show="isUploading" class="atk-uploading-wrap atk-fade-in">
|
|
136
|
+
<div class="atk-progress">
|
|
137
|
+
<div class="atk-bar" :style="{ width: `${progress}%` }"></div>
|
|
138
|
+
</div>
|
|
139
|
+
<div class="atk-status">
|
|
140
|
+
{{ t('uploading') }}
|
|
141
|
+
<span class="atk-curt">{{ progress }}%</span>
|
|
142
|
+
...
|
|
143
|
+
<span class="atk-abort" @click="abortUpload()">{{ t('cancel') }}</span>
|
|
144
|
+
</div>
|
|
145
|
+
</div>
|
|
146
|
+
</div>
|
|
147
|
+
</template>
|
|
148
|
+
|
|
149
|
+
<style scoped lang="scss"></style>
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
const props = defineProps<{
|
|
3
|
+
initValue?: string
|
|
4
|
+
placeholder?: string
|
|
5
|
+
validator?: (value: string) => boolean
|
|
6
|
+
}>()
|
|
7
|
+
|
|
8
|
+
const emit = defineEmits<{
|
|
9
|
+
(evt: 'yes', value: string): boolean | void | Promise<boolean | void>
|
|
10
|
+
(evt: 'no', value: string): boolean | void | Promise<boolean | void>
|
|
11
|
+
(evt: 'close'): void
|
|
12
|
+
}>()
|
|
13
|
+
|
|
14
|
+
const inputEl = ref<HTMLInputElement | null>(null)
|
|
15
|
+
const inputVal = ref('')
|
|
16
|
+
const inputInvalid = ref(false)
|
|
17
|
+
|
|
18
|
+
const { t } = useI18n()
|
|
19
|
+
|
|
20
|
+
onMounted(() => {
|
|
21
|
+
inputVal.value = props.initValue || ''
|
|
22
|
+
window.setTimeout(() => inputEl.value?.focus(), 80)
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
function onInput() {
|
|
26
|
+
// Input value validator
|
|
27
|
+
if (props.validator) {
|
|
28
|
+
inputInvalid.value = props.validator(inputVal.value)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function onKeyUp(evt: KeyboardEvent) {
|
|
33
|
+
if (evt.key === 'Enter' || evt.keyCode === 13) {
|
|
34
|
+
// Press Enter to submit
|
|
35
|
+
evt.preventDefault()
|
|
36
|
+
submit('yes')
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function submit(type: 'yes' | 'no') {
|
|
41
|
+
if (type == 'yes' && inputInvalid.value) return
|
|
42
|
+
|
|
43
|
+
let isContinue: any = undefined
|
|
44
|
+
|
|
45
|
+
const callback = emit(type as any, inputVal.value)
|
|
46
|
+
if (callback instanceof (async () => {}).constructor) {
|
|
47
|
+
isContinue = await callback
|
|
48
|
+
} else {
|
|
49
|
+
isContinue = callback
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (isContinue === undefined || isContinue === true) {
|
|
53
|
+
emit('close')
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
</script>
|
|
57
|
+
|
|
58
|
+
<template>
|
|
59
|
+
<div class="atk-item-text-editor-layer">
|
|
60
|
+
<div class="atk-edit-form">
|
|
61
|
+
<input
|
|
62
|
+
ref="inputEl"
|
|
63
|
+
v-model="inputVal"
|
|
64
|
+
class="atk-main-input"
|
|
65
|
+
type="text"
|
|
66
|
+
:placeholder="props.placeholder || t('inputHint')"
|
|
67
|
+
autocomplete="off"
|
|
68
|
+
:class="{ 'atk-invalid': inputInvalid }"
|
|
69
|
+
@input="onInput()"
|
|
70
|
+
@keyup="onKeyUp"
|
|
71
|
+
/>
|
|
72
|
+
</div>
|
|
73
|
+
<div class="atk-actions">
|
|
74
|
+
<div
|
|
75
|
+
class="atk-item atk-yes-btn"
|
|
76
|
+
:class="{ 'atk-disabled': inputInvalid }"
|
|
77
|
+
@click="submit('yes')"
|
|
78
|
+
>
|
|
79
|
+
<i class="atk-icon atk-icon-yes"></i>
|
|
80
|
+
</div>
|
|
81
|
+
<div class="atk-item atk-no-btn" @click="submit('no')">
|
|
82
|
+
<i class="atk-icon atk-icon-no"></i>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
</template>
|
|
87
|
+
|
|
88
|
+
<style scoped lang="scss">
|
|
89
|
+
.atk-item-text-editor-layer {
|
|
90
|
+
z-index: 999;
|
|
91
|
+
background: var(--at-color-bg);
|
|
92
|
+
position: absolute;
|
|
93
|
+
left: 0;
|
|
94
|
+
top: 0;
|
|
95
|
+
width: 100%;
|
|
96
|
+
height: 100%;
|
|
97
|
+
display: flex;
|
|
98
|
+
flex-direction: row;
|
|
99
|
+
align-items: center;
|
|
100
|
+
|
|
101
|
+
.atk-edit-form {
|
|
102
|
+
flex: auto;
|
|
103
|
+
padding-left: 20px;
|
|
104
|
+
|
|
105
|
+
input {
|
|
106
|
+
font-size: 17px;
|
|
107
|
+
width: 100%;
|
|
108
|
+
padding: 3px 5px;
|
|
109
|
+
border: 0;
|
|
110
|
+
border-bottom: 1px solid var(--at-color-border);
|
|
111
|
+
outline: none;
|
|
112
|
+
background: transparent;
|
|
113
|
+
|
|
114
|
+
&.atk-invalid {
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
&:focus {
|
|
118
|
+
border-bottom-color: var(--at-color-main);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.atk-actions {
|
|
124
|
+
@extend .atk-list-btn-actions;
|
|
125
|
+
|
|
126
|
+
.atk-yes-btn.atk-disabled {
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
</style>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
const props = defineProps<{
|
|
3
|
+
transparentBg?: boolean
|
|
4
|
+
timeout?: number
|
|
5
|
+
}>()
|
|
6
|
+
|
|
7
|
+
const showSpinner = ref(false)
|
|
8
|
+
let timer: number | undefined
|
|
9
|
+
|
|
10
|
+
onMounted(() => {
|
|
11
|
+
// spinner delay to prevent flash
|
|
12
|
+
// (no need to show spinner if loading is fast)
|
|
13
|
+
timer = window.setTimeout(() => {
|
|
14
|
+
showSpinner.value = true
|
|
15
|
+
timer = undefined
|
|
16
|
+
}, props.timeout || 700)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
onUnmounted(() => {
|
|
20
|
+
window.clearTimeout(timer)
|
|
21
|
+
})
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<template>
|
|
25
|
+
<div
|
|
26
|
+
class="atk-loading atk-fade-in"
|
|
27
|
+
:style="{ background: props.transparentBg ? 'transparent' : undefined }"
|
|
28
|
+
>
|
|
29
|
+
<div v-if="showSpinner" class="atk-loading-spinner">
|
|
30
|
+
<svg viewBox="25 25 50 50">
|
|
31
|
+
<circle cx="50" cy="50" r="20" fill="none" stroke-width="2" stroke-miterlimit="10"></circle>
|
|
32
|
+
</svg>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
</template>
|
|
36
|
+
|
|
37
|
+
<style scoped lang="scss"></style>
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { useNavStore } from '@/stores/nav'
|
|
3
|
+
|
|
4
|
+
const props = defineProps<{
|
|
5
|
+
apiUrl: string
|
|
6
|
+
reqParams: { [k: string]: string }
|
|
7
|
+
}>()
|
|
8
|
+
|
|
9
|
+
const emit = defineEmits<{
|
|
10
|
+
(evt: 'back'): void
|
|
11
|
+
}>()
|
|
12
|
+
|
|
13
|
+
const { t } = useI18n()
|
|
14
|
+
|
|
15
|
+
const logWrapEl = ref<HTMLElement | null>(null)
|
|
16
|
+
|
|
17
|
+
onMounted(() => {
|
|
18
|
+
// Create iframe element
|
|
19
|
+
const frameName = `f_${+new Date()}`
|
|
20
|
+
const $frame = document.createElement('iframe')
|
|
21
|
+
$frame.className = 'atk-iframe'
|
|
22
|
+
$frame.name = frameName
|
|
23
|
+
logWrapEl.value!.append($frame)
|
|
24
|
+
|
|
25
|
+
// on iframe done
|
|
26
|
+
$frame.onload = () => {
|
|
27
|
+
useNavStore().refreshSites()
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Crate temporary form for submitting and load iframe page
|
|
31
|
+
const $formTmp = document.createElement('form')
|
|
32
|
+
$formTmp.style.display = 'none'
|
|
33
|
+
$formTmp.setAttribute('method', 'post')
|
|
34
|
+
$formTmp.setAttribute('action', props.apiUrl)
|
|
35
|
+
$formTmp.setAttribute('target', frameName)
|
|
36
|
+
|
|
37
|
+
Object.entries(props.reqParams).forEach(([key, val]) => {
|
|
38
|
+
const $inputTmp = document.createElement('input')
|
|
39
|
+
$inputTmp.setAttribute('type', 'hidden')
|
|
40
|
+
$inputTmp.setAttribute('name', key)
|
|
41
|
+
$inputTmp.value = val
|
|
42
|
+
$formTmp.appendChild($inputTmp)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
logWrapEl.value!.append($formTmp)
|
|
46
|
+
$formTmp.submit()
|
|
47
|
+
$formTmp.remove()
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
function back() {
|
|
51
|
+
emit('back')
|
|
52
|
+
}
|
|
53
|
+
</script>
|
|
54
|
+
|
|
55
|
+
<template>
|
|
56
|
+
<div class="atk-log-wrap">
|
|
57
|
+
<div class="atk-log-back-btn" @click="back()">{{ t('back') }}</div>
|
|
58
|
+
<div ref="logWrapEl" class="atk-log"></div>
|
|
59
|
+
</div>
|
|
60
|
+
</template>
|
|
61
|
+
|
|
62
|
+
<style scoped lang="scss">
|
|
63
|
+
.atk-log-wrap {
|
|
64
|
+
margin-bottom: -40px;
|
|
65
|
+
|
|
66
|
+
.atk-log-back-btn {
|
|
67
|
+
display: inline-block;
|
|
68
|
+
padding: 5px 33px;
|
|
69
|
+
cursor: pointer;
|
|
70
|
+
user-select: none;
|
|
71
|
+
border-right: 1px solid var(--at-color-border);
|
|
72
|
+
border-left: 1px solid transparent;
|
|
73
|
+
&:hover {
|
|
74
|
+
background: var(--at-color-bg-grey);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.atk-log {
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.atk-iframe {
|
|
82
|
+
width: 100%;
|
|
83
|
+
height: calc(100vh - 150px);
|
|
84
|
+
border: 0;
|
|
85
|
+
background: var(--at-color-bg-grey);
|
|
86
|
+
border: 3px solid #eee;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
</style>
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { ArtalkType } from '@esershnr/artalk'
|
|
3
|
+
import { artalk } from '../global'
|
|
4
|
+
|
|
5
|
+
const props = defineProps<{
|
|
6
|
+
page: ArtalkType.PageData
|
|
7
|
+
}>()
|
|
8
|
+
|
|
9
|
+
const emit = defineEmits<{
|
|
10
|
+
(evt: 'close'): void
|
|
11
|
+
(evt: 'update', page: ArtalkType.PageData): void
|
|
12
|
+
(evt: 'remove', id: number): void
|
|
13
|
+
}>()
|
|
14
|
+
|
|
15
|
+
const { page } = toRefs(props)
|
|
16
|
+
const editFieldKey = ref<keyof ArtalkType.PageData | null>(null)
|
|
17
|
+
const editFieldVal = computed(() =>
|
|
18
|
+
String(editFieldKey.value ? page.value[editFieldKey.value!] || '' : ''),
|
|
19
|
+
)
|
|
20
|
+
const isLoading = ref(false)
|
|
21
|
+
const { t } = useI18n()
|
|
22
|
+
|
|
23
|
+
function editTitle() {
|
|
24
|
+
editFieldKey.value = 'title'
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function editKey() {
|
|
28
|
+
editFieldKey.value = 'key'
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function editAdminOnly() {
|
|
32
|
+
isLoading.value = true
|
|
33
|
+
let p: ArtalkType.PageData
|
|
34
|
+
try {
|
|
35
|
+
p = (
|
|
36
|
+
await artalk!.ctx.getApi().pages.updatePage(page.value.id, {
|
|
37
|
+
...page.value,
|
|
38
|
+
admin_only: !page.value.admin_only,
|
|
39
|
+
})
|
|
40
|
+
).data
|
|
41
|
+
} catch (err: any) {
|
|
42
|
+
alert(err.message)
|
|
43
|
+
console.error(err)
|
|
44
|
+
return
|
|
45
|
+
} finally {
|
|
46
|
+
isLoading.value = false
|
|
47
|
+
}
|
|
48
|
+
emit('update', p)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function sync() {
|
|
52
|
+
isLoading.value = true
|
|
53
|
+
let p: ArtalkType.PageData
|
|
54
|
+
try {
|
|
55
|
+
p = (await artalk!.ctx.getApi().pages.fetchPage(page.value.id)).data
|
|
56
|
+
} catch (err: any) {
|
|
57
|
+
alert(err.message)
|
|
58
|
+
console.error(err)
|
|
59
|
+
return
|
|
60
|
+
} finally {
|
|
61
|
+
isLoading.value = false
|
|
62
|
+
}
|
|
63
|
+
emit('update', p)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function del() {
|
|
67
|
+
const del = async () => {
|
|
68
|
+
isLoading.value = true
|
|
69
|
+
try {
|
|
70
|
+
await artalk!.ctx.getApi().pages.deletePage(page.value.id)
|
|
71
|
+
} catch (err: any) {
|
|
72
|
+
alert(err.message)
|
|
73
|
+
console.error(err)
|
|
74
|
+
return
|
|
75
|
+
} finally {
|
|
76
|
+
isLoading.value = false
|
|
77
|
+
}
|
|
78
|
+
emit('remove', page.value.id)
|
|
79
|
+
}
|
|
80
|
+
if (window.confirm(t('pageDeleteConfirm', { title: page.value.title || page.value.key }))) del()
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function close() {
|
|
84
|
+
emit('close')
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function onFieldEditorYes(val: string) {
|
|
88
|
+
if (editFieldVal.value !== val) {
|
|
89
|
+
isLoading.value = true
|
|
90
|
+
let p: ArtalkType.PageData
|
|
91
|
+
try {
|
|
92
|
+
p = (
|
|
93
|
+
await artalk!.ctx.getApi().pages.updatePage(page.value.id, {
|
|
94
|
+
...page.value,
|
|
95
|
+
[editFieldKey.value as any]: val,
|
|
96
|
+
})
|
|
97
|
+
).data
|
|
98
|
+
} catch (err: any) {
|
|
99
|
+
alert(err.message)
|
|
100
|
+
console.error(err)
|
|
101
|
+
return false
|
|
102
|
+
} finally {
|
|
103
|
+
isLoading.value = false
|
|
104
|
+
}
|
|
105
|
+
emit('update', p)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
editFieldKey.value = null
|
|
109
|
+
close()
|
|
110
|
+
return true
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function onFiledEditorNo() {
|
|
114
|
+
editFieldKey.value = null
|
|
115
|
+
}
|
|
116
|
+
</script>
|
|
117
|
+
|
|
118
|
+
<template>
|
|
119
|
+
<div class="atk-page-edit-layer">
|
|
120
|
+
<div class="atk-page-main-actions">
|
|
121
|
+
<div class="atk-item atk-title-edit-btn" @click="editTitle()">
|
|
122
|
+
{{ t('editTitle') }}
|
|
123
|
+
</div>
|
|
124
|
+
<div class="atk-item atk-key-edit-btn" @click="editKey()">
|
|
125
|
+
{{ t('switchKey') }}
|
|
126
|
+
</div>
|
|
127
|
+
<div
|
|
128
|
+
class="atk-item atk-admin-only-btn"
|
|
129
|
+
:class="!page.admin_only ? 'atk-green' : 'atk-yellow'"
|
|
130
|
+
@click="editAdminOnly()"
|
|
131
|
+
>
|
|
132
|
+
{{ !page.admin_only ? t('commentAllowAll') : t('commentOnlyAdmin') }}
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
<div class="atk-page-actions">
|
|
136
|
+
<div class="atk-item atk-sync-btn" @click="sync()">
|
|
137
|
+
<i class="atk-icon atk-icon-sync"></i>
|
|
138
|
+
</div>
|
|
139
|
+
<div class="atk-item atk-del-btn" @click="del()">
|
|
140
|
+
<i class="atk-icon atk-icon-del"></i>
|
|
141
|
+
</div>
|
|
142
|
+
<div class="atk-item atk-close-btn" @click="close()">
|
|
143
|
+
<i class="atk-icon atk-icon-close"></i>
|
|
144
|
+
</div>
|
|
145
|
+
</div>
|
|
146
|
+
<LoadingLayer v-if="isLoading" style="z-index: 1000" />
|
|
147
|
+
<ItemTextEditor
|
|
148
|
+
v-if="!!editFieldKey"
|
|
149
|
+
:init-value="editFieldVal"
|
|
150
|
+
@yes="onFieldEditorYes"
|
|
151
|
+
@no="onFiledEditorNo"
|
|
152
|
+
/>
|
|
153
|
+
</div>
|
|
154
|
+
</template>
|
|
155
|
+
|
|
156
|
+
<style scoped lang="scss">
|
|
157
|
+
.atk-page-edit-layer {
|
|
158
|
+
z-index: 9;
|
|
159
|
+
background: var(--at-color-bg);
|
|
160
|
+
position: absolute;
|
|
161
|
+
width: 100%;
|
|
162
|
+
height: 100%;
|
|
163
|
+
display: flex;
|
|
164
|
+
flex-direction: row;
|
|
165
|
+
align-items: center;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.atk-page-main-actions {
|
|
169
|
+
@extend .atk-list-text-actions;
|
|
170
|
+
}
|
|
171
|
+
</style>
|