333 lines
9.8 KiB
Vue
333 lines
9.8 KiB
Vue
<template>
|
|
<div>
|
|
<!-- Page head goes here -->
|
|
<div class="px-4 py-5 sm:px-6">
|
|
<div
|
|
class="flex justify-between items-center flex-wrap sm:flex-nowrap"
|
|
>
|
|
<div class="">
|
|
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
|
{{ pageTitle }}
|
|
</h3>
|
|
<p class="mt-1 text-sm text-gray-500">
|
|
{{ pageDescription }}
|
|
</p>
|
|
</div>
|
|
<div class="flex-shrink-0">
|
|
<button
|
|
v-for="(headingAction, index) in headingActions"
|
|
:key="headingAction"
|
|
type="button"
|
|
:class="index > 0 ? 'ml-3' : ''"
|
|
class="btn btn-sm btn-primary"
|
|
@click="doHeadingAction(headingAction)"
|
|
>
|
|
{{ headingAction }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="max-w-7xl mx-auto sm:px-4 lg:px-4">
|
|
<!-- Content goes here -->
|
|
<input
|
|
id="title"
|
|
v-model="title"
|
|
type="text"
|
|
name="title"
|
|
placeholder="제목을 입력하세요."
|
|
class="mb-3 shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-gray-300 rounded-md"
|
|
/>
|
|
<quill-editor
|
|
v-model:content="content"
|
|
class="min-h-[30rem]"
|
|
:modules="modules"
|
|
:toolbar="toolbarOptions"
|
|
placeholder="본문을 입력하세요."
|
|
@ready="onEditorReady($event)"
|
|
/>
|
|
|
|
<base-attachment-ctl1
|
|
v-if="attachmentEnabled"
|
|
:attachments="attachments"
|
|
:read-only-flag="false"
|
|
:update-attachments="updateAttachments"
|
|
/>
|
|
|
|
<div class="mt-5 flex justify-between items-center flex-wrap">
|
|
<div class="ml-4 mt-4"></div>
|
|
<div class="ml-4 mt-4 flex-shrink-0">
|
|
<button
|
|
type="button"
|
|
class="inline-flex items-center rounded-md border border-transparent bg-red-600 px-3 py-2 text-sm font-medium leading-4 text-white shadow-sm hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2"
|
|
@click="doFooterAction('취소')"
|
|
>
|
|
{{ '취소' }}
|
|
</button>
|
|
|
|
<button
|
|
type="button"
|
|
class="ml-3 inline-flex items-center rounded-md border border-transparent bg-indigo-600 px-3 py-2 text-sm font-medium leading-4 text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
|
|
@click="doFooterAction('저장')"
|
|
>
|
|
{{ '저장' }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import BlotFormatter from 'quill-blot-formatter/dist/BlotFormatter';
|
|
|
|
definePageMeta({
|
|
// middleware: 'check-auth-admin',
|
|
});
|
|
|
|
const route = useRoute();
|
|
|
|
const bid = ref('');
|
|
let cid: string | string[] = '';
|
|
|
|
bid.value = route.params.boardId[0];
|
|
cid = route.params['_cid'];
|
|
|
|
const modules = {
|
|
name: 'blotFormatter',
|
|
module: BlotFormatter,
|
|
options: {},
|
|
};
|
|
|
|
const pageTitle = cid == undefined ? '광장 - 새글 작성' : '광장 - 글 수정';
|
|
const pageDescription =
|
|
cid == undefined ? '새로운 글을 작성 합니다.' : '내 글을 수정합니다.';
|
|
|
|
// 해당 페이지 우측 상단에 표시될 액션 버튼들
|
|
const headingActions = [];
|
|
|
|
const title = ref('');
|
|
const content = ref({ ops: [] });
|
|
const attachments = ref([]);
|
|
const status = ref(0);
|
|
|
|
function updateAttachments(newAttachments) {
|
|
console.log('newAttachments=', newAttachments);
|
|
attachments.value = newAttachments;
|
|
}
|
|
|
|
const toolbarOptions = [
|
|
['bold', 'italic', 'underline', 'strike'], // toggled buttons
|
|
['blockquote', 'code-block'],
|
|
|
|
// [{ header: 1 }, { header: 2 }], // custom button values
|
|
[{ list: 'ordered' }, { list: 'bullet' }],
|
|
[{ script: 'sub' }, { script: 'super' }], // superscript/subscript
|
|
[{ indent: '-1' }, { indent: '+1' }], // outdent/indent
|
|
// [{ direction: 'rtl' }], // text direction
|
|
|
|
// [{ size: ['small', false, 'large', 'huge'] }], // custom dropdown
|
|
[{ header: [1, 2, 3, 4, 5, 6, false] }],
|
|
|
|
[{ color: [] }, { background: [] }], // dropdown with defaults from theme
|
|
[{ font: [] }],
|
|
[{ align: [] }],
|
|
['link', 'video', 'image'],
|
|
['clean'], // remove formatting button
|
|
];
|
|
|
|
let quill = null;
|
|
|
|
function selectLocalImage() {
|
|
const input = document.createElement('input');
|
|
input.setAttribute('type', 'file');
|
|
input.click();
|
|
|
|
// Listen upload local image and save to server
|
|
input.onchange = () => {
|
|
const file = input.files[0];
|
|
|
|
// file type is only image.
|
|
if (/^image\//.test(file.type)) {
|
|
console.warn('upload images...');
|
|
saveToServer(file);
|
|
} else {
|
|
console.warn('You could only upload images.');
|
|
}
|
|
};
|
|
}
|
|
|
|
function selectLocalFiles() {
|
|
const input = document.createElement('input');
|
|
input.setAttribute('type', 'file');
|
|
input.click();
|
|
|
|
// Listen upload local image and save to server
|
|
input.onchange = () => {
|
|
console.log('we got file(s) : ', input.files);
|
|
};
|
|
}
|
|
|
|
async function saveToServer(file: File) {
|
|
const formData = new FormData();
|
|
|
|
formData.append('upload-file', file, file.name);
|
|
// save it
|
|
|
|
formData.append('target', 'just');
|
|
|
|
console.log('formData=', formData);
|
|
|
|
const responseJson = await _crossCtl.doUpload('just', formData);
|
|
|
|
console.log('responseJson=', responseJson);
|
|
|
|
if (responseJson['responseCode'] == 200) {
|
|
insertToEditor(
|
|
_crossCtl.config['API_BASE_URL'].replace('/api/', '') +
|
|
responseJson['files'][0]['localUrl']
|
|
);
|
|
} else {
|
|
}
|
|
}
|
|
function insertToEditor(url: string) {
|
|
// push image url to rich editor.
|
|
|
|
const range = quill.getSelection();
|
|
quill.insertEmbed(range.index, 'image', url);
|
|
}
|
|
|
|
function onEditorReady(e) {
|
|
console.log('onEditorReady() e = ', e);
|
|
|
|
quill = e;
|
|
|
|
// quill editor add image handler
|
|
|
|
e.getModule('toolbar').addHandler('image', () => {
|
|
selectLocalImage();
|
|
});
|
|
|
|
e.getModule('toolbar').addHandler('attachment', () => {
|
|
selectLocalFiles();
|
|
});
|
|
|
|
if (cid != undefined) {
|
|
loadContent();
|
|
}
|
|
}
|
|
|
|
const attachmentEnabled = ref(false);
|
|
|
|
async function loadContent() {
|
|
const responseJson = await _crossCtl.doComm('select', 'board', {
|
|
boardId: bid.value,
|
|
hero: cid,
|
|
});
|
|
|
|
if (responseJson['responseCode'] != 200) {
|
|
alert(responseJson['responseMessage']);
|
|
} else {
|
|
console.log('huk responseJson=', responseJson);
|
|
const tmpDatas = responseJson['data'];
|
|
console.log('huk tmpDatas=', tmpDatas);
|
|
if (tmpDatas.length == 1) {
|
|
attachmentEnabled.value =
|
|
responseJson['metaData']['attachmentEnabled'];
|
|
|
|
title.value = tmpDatas[0]['title'];
|
|
|
|
// content.value = tmpDatas[0]['content'];
|
|
quill.root.innerHTML = tmpDatas[0]['content'];
|
|
|
|
attachments.value = JSON.parse(tmpDatas[0]['attachments']);
|
|
status.value = tmpDatas[0]['status'];
|
|
|
|
// $dayjs(val).format('YY/MM/DD A h:mm:ss')
|
|
} else {
|
|
alert('bad count. count = ' + tmpDatas.length);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
const attachments = [
|
|
{ name: 'resume_front_end_developer.pdf', href: '#' },
|
|
{ name: 'coverletter_front_end_developer.pdf', href: '#' },
|
|
];
|
|
*/
|
|
|
|
const router = useRouter();
|
|
|
|
function doHeadingAction(tag) {
|
|
console.log('on doHeadingAction(), tag=', tag);
|
|
}
|
|
|
|
function doFooterAction(tag) {
|
|
console.log('on doFooterAction(), tag=', tag);
|
|
|
|
switch (tag) {
|
|
case '저장':
|
|
// console.log('quill.root.innerHTML=', quill.root.innerHTML);
|
|
// console.log('content=', content.value);
|
|
updateContent();
|
|
break;
|
|
|
|
case '취소':
|
|
// router.back();
|
|
navigateTo('/board/' + bid.value + '/list');
|
|
break;
|
|
}
|
|
}
|
|
|
|
async function updateContent() {
|
|
if (title.value.trim() == '') {
|
|
alert('제목을 입력해 주세요.');
|
|
return;
|
|
}
|
|
let emptyContent = true;
|
|
|
|
console.log('content.value = ', content.value);
|
|
console.log('quill.root.innerHTML = ', quill.root.innerHTML);
|
|
|
|
for (let i = 0; i < content.value['ops'].length; i++) {
|
|
console.log("content.value['ops'][i] = ", content.value['ops'][i]);
|
|
|
|
if (typeof content.value['ops'][i]['insert'] == 'string') {
|
|
if (content.value['ops'][i]['insert'].trim() != '') {
|
|
emptyContent = false;
|
|
break;
|
|
}
|
|
} else {
|
|
emptyContent = false;
|
|
break;
|
|
}
|
|
}
|
|
if (emptyContent == true) {
|
|
alert('본문을 입력해 주세요.');
|
|
return;
|
|
}
|
|
|
|
const responseJson = await _crossCtl.doComm(
|
|
cid == undefined ? 'insert' : 'update',
|
|
'board',
|
|
{
|
|
boardId: bid.value,
|
|
hero: cid,
|
|
title: title.value,
|
|
content: quill.root.innerHTML,
|
|
attachments: attachments.value,
|
|
status: status.value,
|
|
}
|
|
);
|
|
|
|
if (responseJson['responseCode'] != 200) {
|
|
alert(responseJson['responseMessage']);
|
|
} else {
|
|
// router.back();
|
|
navigateTo('/board/' + bid.value + '/list');
|
|
}
|
|
}
|
|
|
|
// refresh();
|
|
</script>
|