|
|
@@ -0,0 +1,241 @@
|
|
|
+<link rel="stylesheet" href="~/lib/ckeditor/browser/ckeditor5.css" asp-append-version="true" />
|
|
|
+
|
|
|
+<script type="module">
|
|
|
+ import {
|
|
|
+ ClassicEditor, Essentials, Paragraph, Bold, Italic, Underline, Strikethrough, Code, Subscript, Superscript, RemoveFormat, List, TodoList,
|
|
|
+ Indent, Heading, Font, Highlight, Alignment, Link, Image, ImageToolbar, ImageCaption, ImageStyle, ImageResize, ImageUpload, MediaEmbed, CodeBlock,
|
|
|
+ HtmlEmbed, SpecialCharacters, HorizontalLine, PageBreak, SourceEditing, FindAndReplace, SelectAll, BlockQuote, Table, TableToolbar, TextPartLanguage
|
|
|
+ } from "/lib/ckeditor/browser/ckeditor5.js";
|
|
|
+
|
|
|
+ class CustomImageAttributesPlugin {
|
|
|
+ constructor(editor) {
|
|
|
+ this.editor = editor;
|
|
|
+ }
|
|
|
+
|
|
|
+ init() {
|
|
|
+ const editor = this.editor;
|
|
|
+ const schema = editor.model.schema;
|
|
|
+
|
|
|
+ // data-* 속성을 추가
|
|
|
+ const attributes = ['data-name', 'data-extension', 'data-size', 'data-type', 'data-width', 'data-height'];
|
|
|
+ schema.extend('imageBlock', {allowAttributes: attributes});
|
|
|
+ schema.extend('imageInline', {allowAttributes: attributes});
|
|
|
+
|
|
|
+ editor.plugins.get('ImageUploadEditing').on('uploadComplete', (evt, { data, imageElement }) => {
|
|
|
+ editor.model.change((writer) => {
|
|
|
+ writer.setAttribute('data-name', data.name, imageElement);
|
|
|
+ writer.setAttribute('data-extension', data.extension, imageElement);
|
|
|
+ writer.setAttribute('data-size', data.size, imageElement);
|
|
|
+ writer.setAttribute('data-type', data.type, imageElement);
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ attributes.forEach(attr => {
|
|
|
+ editor.conversion.for('upcast').attributeToAttribute({ model: attr, view: attr });
|
|
|
+ });
|
|
|
+
|
|
|
+ attributes.forEach(attr => {
|
|
|
+ editor.conversion.for('downcast').attributeToAttribute({ model: attr, view: attr });
|
|
|
+ editor.conversion.for('editingDowncast').attributeToAttribute({ model: attr, view: attr });
|
|
|
+ });
|
|
|
+
|
|
|
+ const updateDataAttribute = (evt, data, conversionApi) => {
|
|
|
+ const viewWriter = conversionApi.writer;
|
|
|
+ const viewElement = conversionApi.mapper.toViewElement(data.item);
|
|
|
+
|
|
|
+ if (viewElement) {
|
|
|
+ const imgElement = viewElement.getChild(0);
|
|
|
+ if (imgElement && imgElement.is('element', 'img')) {
|
|
|
+ viewWriter.setAttribute(data.attributeKey, data.attributeNewValue, imgElement);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ attributes.forEach(attribute => {
|
|
|
+ editor.conversion.for('downcast').add(dispatcher => {
|
|
|
+ dispatcher.on(`attribute:${attribute}:imageBlock`, (evt, data, conversionApi) => {
|
|
|
+ updateDataAttribute(evt, data, conversionApi);
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ editor.conversion.for('editingDowncast').add(dispatcher => {
|
|
|
+ dispatcher.on(`attribute:${attribute}:imageInline`, (evt, data, conversionApi) => {
|
|
|
+ updateDataAttribute(evt, data, conversionApi);
|
|
|
+ });
|
|
|
+ });
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ class CustomUploadAdapter {
|
|
|
+ constructor(loader) {
|
|
|
+ this.loader = loader;
|
|
|
+ }
|
|
|
+
|
|
|
+ upload() {
|
|
|
+ return this.loader.file.then(file => {
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+ const reader = new FileReader();
|
|
|
+
|
|
|
+ reader.onload = () => {
|
|
|
+ resolve({
|
|
|
+ default: reader.result,
|
|
|
+ name: file.name,
|
|
|
+ extension: file.name.split('.').pop(),
|
|
|
+ size: file.size,
|
|
|
+ type: file.type
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ reader.onerror = error => reject(error);
|
|
|
+ reader.readAsDataURL(file);
|
|
|
+ });
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ abort() {
|
|
|
+ console.log('업로드가 취소되었습니다.');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // CKEditor 저장, 전역 변수로 해서 다른 곳에서 접근할 수 있도록 함
|
|
|
+ window.editorInstances = window.editorInstances || new Map();
|
|
|
+
|
|
|
+ // CKEditor 초기화
|
|
|
+ window.initEditor = async function(domID, config = {})
|
|
|
+ {
|
|
|
+ const el = document.getElementById(domID);
|
|
|
+ if (!el || window.editorInstances.has(domID)) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ return ClassicEditor
|
|
|
+ .create(el, {
|
|
|
+ licenseKey: 'GPL',
|
|
|
+ extraPlugins: [CustomUploadAdapter, CustomImageAttributesPlugin],
|
|
|
+ plugins: [
|
|
|
+ Essentials, Paragraph, Bold, Italic, Underline, Strikethrough, Code,
|
|
|
+ Subscript, Superscript, RemoveFormat, List, TodoList, Indent, Heading,
|
|
|
+ Font, Highlight, Alignment, Link, Image, ImageToolbar, ImageCaption,
|
|
|
+ ImageStyle, ImageResize, ImageUpload, MediaEmbed, CodeBlock, HtmlEmbed,
|
|
|
+ SpecialCharacters, HorizontalLine, PageBreak, SourceEditing,
|
|
|
+ FindAndReplace, SelectAll, BlockQuote, Table, TableToolbar, TextPartLanguage
|
|
|
+ ],
|
|
|
+ toolbar: {
|
|
|
+ items: [
|
|
|
+ 'findAndReplace', 'selectAll', '|',
|
|
|
+ 'heading', '|',
|
|
|
+ 'bold', 'italic', 'strikethrough', 'underline', 'code', 'subscript', 'superscript', 'removeFormat', '|',
|
|
|
+ 'bulletedList', 'numberedList', 'todoList', '|',
|
|
|
+ 'outdent', 'indent', '|',
|
|
|
+ 'undo', 'redo', '|',
|
|
|
+ 'fontSize', 'fontFamily', 'fontColor', 'fontBackgroundColor', 'highlight', '|',
|
|
|
+ 'alignment', '|',
|
|
|
+ 'link', 'insertImage', 'blockQuote', 'insertTable', 'mediaEmbed', 'codeBlock', 'htmlEmbed', '|',
|
|
|
+ 'specialCharacters', 'horizontalLine', 'pageBreak', '|',
|
|
|
+ 'textPartLanguage', '|',
|
|
|
+ 'sourceEditing'
|
|
|
+ ],
|
|
|
+ shouldNotGroupWhenFull: true
|
|
|
+ },
|
|
|
+ list: {
|
|
|
+ properties: {
|
|
|
+ styles: true,
|
|
|
+ startIndex: true,
|
|
|
+ reversed: true
|
|
|
+ }
|
|
|
+ },
|
|
|
+ heading: {
|
|
|
+ options: [
|
|
|
+ { model: 'paragraph', title: 'Paragraph', class: 'ck-heading_paragraph' },
|
|
|
+ { model: 'heading1', view: 'h1', title: 'Heading 1', class: 'ck-heading_heading1' },
|
|
|
+ { model: 'heading2', view: 'h2', title: 'Heading 2', class: 'ck-heading_heading2' },
|
|
|
+ { model: 'heading3', view: 'h3', title: 'Heading 3', class: 'ck-heading_heading3' },
|
|
|
+ { model: 'heading4', view: 'h4', title: 'Heading 4', class: 'ck-heading_heading4' },
|
|
|
+ { model: 'heading5', view: 'h5', title: 'Heading 5', class: 'ck-heading_heading5' },
|
|
|
+ { model: 'heading6', view: 'h6', title: 'Heading 6', class: 'ck-heading_heading6' }
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ placeholder: '내용을 입력해주세요.',
|
|
|
+ fontFamily: {
|
|
|
+ options: [
|
|
|
+ 'default',
|
|
|
+ 'Arial, Helvetica, sans-serif',
|
|
|
+ 'Courier New, Courier, monospace',
|
|
|
+ 'Georgia, serif',
|
|
|
+ 'Lucida Sans Unicode, Lucida Grande, sans-serif',
|
|
|
+ 'Tahoma, Geneva, sans-serif',
|
|
|
+ 'Times New Roman, Times, serif',
|
|
|
+ 'Trebuchet MS, Helvetica, sans-serif',
|
|
|
+ 'Verdana, Geneva, sans-serif'
|
|
|
+ ],
|
|
|
+ supportAllValues: true
|
|
|
+ },
|
|
|
+ fontSize: {
|
|
|
+ options: [10, 12, 14, 'default', 18, 20, 22],
|
|
|
+ supportAllValues: true
|
|
|
+ },
|
|
|
+ htmlSupport: {
|
|
|
+ allow: [
|
|
|
+ {
|
|
|
+ name: /.*/,
|
|
|
+ attributes: true,
|
|
|
+ classes: true,
|
|
|
+ styles: true
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ htmlEmbed: {
|
|
|
+ showPreviews: true
|
|
|
+ },
|
|
|
+ link: {
|
|
|
+ decorators: {
|
|
|
+ addTargetToExternalLinks: true,
|
|
|
+ defaultProtocol: 'https://',
|
|
|
+ toggleDownloadable: {
|
|
|
+ mode: 'manual',
|
|
|
+ label: 'Downloadable',
|
|
|
+ attributes: {
|
|
|
+ download: 'file'
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ image: {
|
|
|
+ toolbar: [
|
|
|
+ 'imageStyle:inline', 'imageStyle:block', 'imageStyle:side', '|',
|
|
|
+ 'toggleImageCaption', 'imageTextAlternative'
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ table: {
|
|
|
+ contentToolbar: ['tableColumn', 'tableRow', 'mergeTableCells']
|
|
|
+ },
|
|
|
+ simpleUpload: {
|
|
|
+ uploadUrl: null
|
|
|
+ },
|
|
|
+ ...config
|
|
|
+ })
|
|
|
+ .then(editor => {
|
|
|
+ editor.plugins.get('FileRepository').createUploadAdapter = loader => new CustomUploadAdapter(loader);
|
|
|
+
|
|
|
+ // 에디터 저장
|
|
|
+ window.editorInstances.set(domID, editor);
|
|
|
+ })
|
|
|
+ .catch(error => {
|
|
|
+ console.error('Error initializing CKEditor:', error);
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ // 에디터 제거
|
|
|
+ window.destroyEditor = async function(domID) {
|
|
|
+ const editor = window.editorInstances.get(domID);
|
|
|
+ if (editor) {
|
|
|
+ await editor.destroy();
|
|
|
+ window.editorInstances.delete(domID);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ document.querySelectorAll('.ck-editor').forEach(e => {
|
|
|
+ initEditor(e.id);
|
|
|
+ });
|
|
|
+</script>
|