Merge branch 'feat/wiki' into 'main'

Feat/wiki

See merge request jjimenez/safekat!581
This commit is contained in:
Alvaro
2025-03-02 09:25:35 +00:00
54 changed files with 14061 additions and 7 deletions

View File

@ -0,0 +1,41 @@
import Ajax from "../components/ajax.js";
class TranslationHelper {
constructor() {
this.locale = "es"
this.lang = {}
}
async get_translations(translationFile) {
return new Promise(async (resolve,reject) => {
this.locale = $("meta[name='locale']").attr("content");
this.translationFile = translationFile
const ajax = new Ajax('/translate/getTranslation',
{
locale: this.locale,
translationFile: this.translationFile
},
null,
(response) => {
this.lang = JSON.parse(response)
resolve(this.lang)
},
(error) => {
reject(error)
}
);
ajax.post()
})
}
get_lang(key) {
if (key in this.lang) {
return this.lang[key]
}else{
return key
}
}
}
export default TranslationHelper;

View File

@ -19,10 +19,10 @@ export const initAutonumeric = () => {
new AutoNumeric(this, {
digitGroupSeparator: ".",
decimalCharacter: ",",
allowDecimalPadding : 'floats',
allowDecimalPadding: 'floats',
decimalPlaces: 2,
unformatOnSubmit: true,
});
}
})
@ -32,10 +32,10 @@ export const initAutonumeric = () => {
new AutoNumeric(this, {
digitGroupSeparator: ".",
decimalCharacter: ",",
allowDecimalPadding : 'floats',
allowDecimalPadding: 'floats',
decimalPlaces: $(this).data('decimal-places'),
unformatOnSubmit: true,
});
}
})
@ -49,7 +49,7 @@ export const initAutonumeric = () => {
// allowDecimalPadding : 'floats',
// decimalPlaces: 2,
// unformatOnSubmit: true,
// });
// }
// })
@ -85,4 +85,26 @@ export const initAutonumeric = () => {
// });
// }
// })
}
export const getTablerIconClassName = () => {
const classNames = [];
for (const sheet of document.styleSheets) {
try {
if (sheet.cssRules) {
for (const rule of sheet.cssRules) {
if (rule.selectorText && rule.selectorText.startsWith(".ti-")) {
let className = rule.selectorText.split(" ")[0].replace(".", "");
className = className.split("::")[0]
className = className.split(":")[0]
classNames.push(className);
}
}
}
} catch (e) {
console.warn("Cannot access stylesheet:", sheet.href);
}
}
return classNames.reverse();
}

View File

@ -1,6 +1,5 @@
export const alertConfirmationDelete = (title, type = "primary") => {
return Swal.fire({
title: '¿Está seguro?',
@ -18,7 +17,23 @@ export const alertConfirmationDelete = (title, type = "primary") => {
buttonsStyling: false
})
}
export const alertConfirmAction = (title, type = "primary") => {
return Swal.fire({
title: '¿Está seguro?',
text: title,
icon: 'info',
showCancelButton: true,
confirmButtonColor: '#3085d6',
cancelButtonColor: '#d33',
confirmButtonText: 'Sí',
cancelButtonText: 'Cancelar',
customClass: {
confirmButton: 'btn btn-success me-1',
cancelButton: 'btn btn-label-secondary'
},
buttonsStyling: false
})
}
export const alertSuccessMessage = (title, type = "primary") => {
return Swal.fire({
showCancelButton: false,
@ -63,4 +78,52 @@ export const toastPresupuestoSummary = (value, target = 'body') => {
timerProgressBar: false,
stopKeydownPropagation: false,
})
}
export const alertSuccess = (value, target = 'body') => {
return Swal.mixin({
toast: true,
position: 'bottom-end',
html: `<span class="text-sm-left text-wrap">${value}</span>`,
customClass: {
popup: 'bg-success text-white',
},
icon : 'success',
iconColor: 'white',
target: target,
showConfirmButton: false,
timer: 2000,
timerProgressBar: true,
})
}
export const alertError = (value, target = 'body') => {
return Swal.mixin({
toast: true,
position: 'bottom-end',
html: `<span class="text-sm-left text-wrap">${value}</span>`,
customClass: {
popup: 'bg-danger text-white',
},
icon : 'error',
iconColor: 'white',
target: target,
showConfirmButton: false,
timer: 2000,
timerProgressBar: true,
})
}
export const alertWarning = (value, target = 'body') => {
return Swal.mixin({
toast: true,
position: 'bottom-end',
html: `<span class="text-sm-left text-wrap">${value}</span>`,
customClass: {
popup: 'bg-warning text-white',
},
icon : 'warning',
iconColor: 'white',
target: target,
showConfirmButton: false,
timer: 2000,
timerProgressBar: true,
})
}

View File

@ -0,0 +1,255 @@
import Ajax from '../ajax.js'
import { es } from './lang/es.js';
import '../../../../../themes/vuexy/js/editorjs/toc.js';
import '../../../../../themes/vuexy/js/editorjs/list.js';
import '../../../../../themes/vuexy/js/editorjs/tune.js';
import '../../../../../themes/vuexy/js/editorjs/table.js';
import '../../../../../../themes/vuexy/js/editorjs/image.js';
import '../../../../../../themes/vuexy/js/editorjs/alert.js';
import '../../../../../../themes/vuexy/js/editorjs/header.js';
import '../../../../../../themes/vuexy/js/editorjs/drag-drop.js';
import EditorJS from '../../../../../themes/vuexy/js/editorjs.mjs';
import TranslationHelper from '../../common/TranslationHelper.js';
import { alertConfirmAction, alertError, alertSuccess, alertWarning } from '../alerts/sweetAlert.js';
import WikiSectionForm from '../forms/WikiSectionForm.js'
class WikiEditor extends TranslationHelper{
constructor() {
super();
this.sectionId = $("#wiki-section-id").val();
this.wikiFormSection = new WikiSectionForm()
this.wikiFormSection.setId(this.sectionId)
}
async initEditor()
{
this.wikiFormSection.init()
await this.get_translations("Wiki")
this.editor = new EditorJS({
holder: 'editorjs',
i18n: this.locale == "es" ? es : null,
autofocus: true,
readOnly: true,
tunes: {
alignment: {
class: AlignmentBlockTune,
},
},
tools: {
toc: TOC,
header: {
class: Header,
tunes: ['alignment'],
config: {
placeholder: "",
levels: [1, 2, 3, 4],
defaultLevel: 1
},
},
nestedchecklist: {
class: editorjsNestedChecklist,
config: {
maxLevel: 1,
}
},
alert: {
class: Alert,
inlineToolbar: true,
tunes: ['alignment'],
config: {
alertTypes: ['primary', 'secondary', 'info', 'success', 'warning', 'danger', 'light', 'dark'],
defaultType: 'primary',
messagePlaceholder: 'Introduzca texto',
},
},
image: {
class: ImageTool,
config: {
features: {
border: true,
caption: 'optional',
stretch: false
},
endpoints: {
byFile: `/wiki/file/upload/${this.sectionId}`, // Your backend file uploader endpoint
byUrl: 'fetchUrl', // Your endpoint that provides uploading by Url
}
}
},
table: {
class: Table,
tunes: ['alignment'],
inlineToolbar: true,
config: {
rows: 2,
cols: 3,
maxRows: 5,
maxCols: 5,
},
},
alignment: {
class: AlignmentBlockTune,
config: {
default: "left",
blocks: {
header: 'left',
list: 'left'
}
}
},
},
})
}
async initViewOnly()
{
try {
await this.initEditor()
await this.editor.isReady;
this.handleGetDataPublished();
} catch (reason) {
console.log(`Editor.js initialization failed because of ${reason}`)
}
}
async init() {
try {
await this.initEditor()
await this.editor.isReady;
new DragDrop(this.editor);
/** Do anything you need after editor initialization */
$('#save-editor').on('click', () => {
this.editor.save().then(outputData => {
alertConfirmAction(this.get_lang('save_content')).then(result => {
if (result.isConfirmed) {
this.handleSaveContent(outputData)
}
});
})
})
$('#release-editor').on('click', () => {
this.editor.save().then(outputData => {
alertConfirmAction('Publicar contenido').then(result => {
if (result.isConfirmed) {
this.handlePublishContent(outputData)
}
console.log(result)
});
}).catch((error) => {
alertError(this.get_lang('need_editor_to_save')).fire()
})
})
$('#preview-editor').on('click', () => {
this.editor.readOnly.toggle()
$('#edit-editor').removeClass('d-none')
$('#preview-editor').addClass('d-none')
$('#release-editor').attr('disabled', 'disabled')
$('#save-editor').attr('disabled', 'disabled')
})
$('#edit-editor').on('click', () => {
$('#edit-editor').addClass('d-none')
$('#preview-editor').removeClass('d-none')
$('#release-editor').attr('disabled', null)
$('#save-editor').attr('disabled', null)
this.editor.readOnly.toggle()
})
this.handleGetData();
} catch (reason) {
console.log(`Editor.js initialization failed because of ${reason}`)
}
}
handleGetData() {
const ajax = new Ajax(`/wiki/section/${this.sectionId}`,
null,
null,
this.handleGetDataSuccess.bind(this),
this.handleGetDataError.bind(this))
ajax.get()
}
handleGetDataSuccess(response) {
if (response.data.contents?.editor_data) {
alertSuccess(response.message).fire()
this.renderContent(response.data.contents.editor_data)
} else {
alertWarning('No hay contenido').fire()
}
}
handleGetDataError(error) {
console.log(error)
}
handleGetDataPublished() {
const ajax = new Ajax(`/wiki/section/${this.sectionId}`,
null,
null,
this.handleGetDataPublishedSuccess.bind(this),
this.handleGetDataPublishedError.bind(this))
ajax.get()
}
handleGetDataPublishedSuccess(response) {
if (response.data.contents?.published_data) {
alertSuccess(response.message).fire()
this.renderContent(response.data.contents.published_data)
} else {
alertWarning(this.get_lang('no_content')).fire()
}
}
handleGetDataPublishedError(error) {
alertError(error.error).fire()
}
renderContent(content) {
try {
let parsedContent = JSON.parse(content)
this.editor.render(parsedContent)
this.centerImages()
} catch (error) {
console.log(error)
}
}
handleSaveContent(data) {
const ajax = new Ajax(`/wiki/save/${this.sectionId}`,
data,
null,
this.handleSaveContentSuccess.bind(this),
this.handleSaveContentError.bind(this))
ajax.post()
}
handleSaveContentSuccess(response) {
this.handleGetData()
}
handleSaveContentError(response) { }
handlePublishContent(data) {
const ajax = new Ajax(`/wiki/publish/${this.sectionId}`,
data,
null,
this.handleSaveContentSuccess.bind(this),
this.handleSaveContentError.bind(this))
ajax.post()
}
handleSaveContentSuccess(response) {
this.handleGetData()
}
handleSaveContentError(response) { }
centerImages(){
setInterval(() => {
$(".image-tool img").css('margin','0 auto')
$(".image-tool__caption").css('margin','0 auto').css('border','none').css('text-align','center').css('box-shadow','none')
},500)
}
}
export default WikiEditor

View File

@ -0,0 +1,65 @@
export const dataExample = {
"time": 1739862579806,
"blocks": [
{
"id": "uynEMELQjD",
"type": "header",
"data": {
"text": "Presupuesto",
"level": 1
}
},
{
"id": "5lBp4qELvp",
"type": "header",
"data": {
"text": "Introducción",
"level": 4
}
},
{
"id": "cf8xhN8Zo_",
"type": "paragraph",
"data": {
"text": "\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam lacinia\n diam ut tincidunt ornare. Suspendisse bibendum, velit ut mollis \nplacerat, tellus ante iaculis mi, ut luctus erat ligula sed est. Integer\n aliquam mauris eu diam tristique viverra. Proin id sem nisi. Praesent \ngravida tortor ac aliquam faucibus. Curabitur faucibus, magna vel \nconsectetur luctus, arcu diam iaculis lectus, posuere vehicula nisl \nnulla eget nibh.<b> Praesent tincidunt</b> enim condimentum interdum sodales. \nCurabitur eu diam sed mauris dignissim vestibulum eu quis justo. Nam \nornare odio id tincidunt aliquam. Quisque quis elit quis sem blandit \nimperdiet euismod non quam. Mauris maximus elit eu elementum consequat. \nSed nec cursus nibh. Nullam accumsan enim in tortor semper, in dapibus \nquam sagittis. In volutpat nisl libero, et pharetra dui luctus vitae. \nProin sit amet ornare mi. Sed vulputate ligula at elit posuere, non \nultrices risus ornare.\n"
}
},
{
"id": "2SnovwX73t",
"type": "header",
"data": {
"text": "Configuracion",
"level": 4
}
},
{
"id": "Sz_mcM8_BZ",
"type": "paragraph",
"data": {
"text": "\nFusce varius, erat vitae egestas elementum, ante turpis tincidunt erat, \nsit amet scelerisque massa nisl vel mauris. Cras eu condimentum magna, \nac finibus orci. Donec convallis sapien nulla, ac porta massa elementum \nsed. Nulla elit urna, ornare id quam ac, luctus commodo sem. Nam a \nsemper lectus. Sed eget ex varius, pretium ante ac, ultricies est. Sed \nut est leo. In velit lorem, vestibulum id sodales eu, vulputate et \nlectus. Mauris elementum lectus consequat cursus sollicitudin. Proin in \nsagittis turpis. Pellentesque ut pulvinar mi. Nullam nec purus vel enim \nconvallis rutrum. Pellentesque sem elit, fringilla quis accumsan quis, \nconvallis eu est. Nam malesuada nulla volutpat dui lobortis, vitae \nauctor turpis faucibus.\n"
}
},
{
"id": "Alu9XBLH6v",
"type": "paragraph",
"data": {
"text": "\nMaecenas sit amet iaculis diam. Aliquam sed feugiat neque. Proin et \npellentesque mi, et elementum risus. Sed volutpat, nibh quis finibus \nvenenatis, augue magna mollis est, vel efficitur nulla ligula et urna. \nVestibulum dictum molestie orci, vel pretium sapien fringilla eget. \nPellentesque fringilla facilisis congue. Proin bibendum dui non nisl \nornare rhoncus. Nam sit amet eros est. Integer luctus molestie lacus. \nPraesent elementum condimentum bibendum. Donec lacus nibh, sagittis in \ntempor nec, lacinia lacinia erat. Mauris pulvinar erat non nulla \npharetra dignissim. Quisque commodo dolor a neque lacinia porttitor. \nCras rhoncus ligula nibh.\n"
}
},
{
"id": "DLkw7hyvB1",
"type": "paragraph",
"data": {
"text": "\nPellentesque condimentum ullamcorper faucibus. Quisque vel enim et urna \nvenenatis faucibus at suscipit ipsum. Suspendisse nec nisi sit amet \ntortor suscipit volutpat ac eget mauris. Curabitur arcu arcu, vehicula \nin malesuada vitae, dapibus sit amet massa. Vivamus nec ex porttitor, \nvehicula nunc et, euismod velit. Maecenas dolor tortor, cursus quis \npurus ut, vulputate malesuada nunc. Morbi hendrerit auctor nisi. In \nfaucibus nibh sed quam suscipit lacinia. Cras placerat ornare lorem \nhendrerit blandit. Lorem ipsum dolor sit amet, consectetur adipiscing \nelit. Aliquam porttitor nisl ut arcu pretium, a sodales est iaculis. \nCras commodo sit amet tortor a rutrum. Mauris tristique magna nibh, quis\n bibendum tortor feugiat eget.\n"
}
},
{
"id": "3Ph1J1DR_5",
"type": "paragraph",
"data": {
"text": "\nPraesent iaculis tellus in quam rutrum ultrices. Phasellus ultricies \nlacus in neque volutpat iaculis. Aliquam at egestas tellus, vitae \nsollicitudin sapien. Sed commodo tellus ut ligula elementum iaculis. \nCurabitur sodales consequat dui, et tincidunt nunc vehicula vitae. \nSuspendisse aliquam justo a ante pellentesque ultrices ut in turpis. \nClass aptent taciti sociosqu ad litora torquent per conubia nostra, per \ninceptos himenaeos. Mauris scelerisque mattis tortor, in molestie neque \nbibendum a. Curabitur ut tempor erat. Donec aliquam scelerisque ipsum, \nbibendum placerat ante efficitur at. Sed malesuada, eros ut posuere \nvenenatis, leo est sagittis mi, at pretium augue tortor id ligula. Ut \negestas eget sapien a rhoncus. Ut lectus arcu, consequat posuere ipsum \nin, mollis iaculis augue.\n"
}
}
],
"version": "2.31.0-rc.8"
}

View File

@ -0,0 +1,155 @@
export const es = {
messages: {
/**
* Other below: translation of different UI components of the editor.js core
*/
ui: {
"blockTunes": {
"toggler": {
"Click to tune": "Click para editar",
"or drag to move": "o arrastra para editar",
"Filter" : "Filtrar"
},
"filter" : {
"Filter" : "Filtrar"
}
},
"inlineToolbar": {
"converter": {
"Convert to": "Convertir a",
"Filter" : "Filtrar"
},
},
"toolbar": {
"toolbox": {
"Add": "Añadir",
"Filter" : "Filtrar"
},
}
},
/**
* Section for translation Tool Names: both block and inline tools
*/
toolNames: {
"Text": "Texto",
"Heading": "Encabezado",
"List": "Lista",
"Unordered List": "Lista",
"Ordered List" : "Enumeración",
"Warning": "Advertencia",
"Checklist": "Checklist",
"Quote": "Cita",
"Code": "Codigo",
"Nested Checklist": "Lista anidada",
"Delimiter": "Delimitador",
"Raw HTML": "Raw HTML",
"Table": "Tabla",
"Link": "Enlace",
"Marker": "Subrayar",
"Bold": "Negrita",
"Italic": "Cursiva",
"InlineCode": "Código",
"Image" : "Imagen",
"Alert" : "Alerta",
"Convert to" : "Convertir a",
"TOC" : "Tabla de contenidos"
},
/**
* Section for passing translations to the external tools classes
*/
tools: {
/**
* Each subsection is the i18n dictionary that will be passed to the corresponded plugin
* The name of a plugin should be equal the name you specify in the 'tool' section for that plugin
*/
"warning": { // <-- 'Warning' tool will accept this dictionary section
"Title": "Titulo",
"Message": "Mensaje",
},
"list" : {
"Start with" : "Empezar con",
"Unordered": "Sin orden",
"Ordered" : "Ordenada",
"Counter type" : "Contador",
"Convert to" : "Convertir a",
},
"toc" : {
'Refresh' : "Actualizar",
},
"table" : {
"Add column to left" : "Añadir columna a la izquierda",
"Add column to right" : "Añadir columna a la derecha",
"Delete column" : "Eliminar columna",
"Delete row" : "Eliminar fila",
"Without headings" : "Sin encabezados",
"With headings" : "Con encabezados",
"Stretch" : "Ampliar",
"Add row above" : "Añadir fila arriba",
"Add row below" : "Añadir fila abajo",
},
/**
* Link is the internal Inline Tool
*/
"link": {
"Add a link": "Añadir un enlace"
},
/**
* The "stub" is an internal block tool, used to fit blocks that does not have the corresponded plugin
*/
"stub": {
'The block can not be displayed correctly.': 'El bloque no puede ser mostrado correctamente'
},
"alert" : {
"Primary" : "Color primario",
"Secondary" : "Color secundario",
"Info" : "Info",
"Success" : "Éxito",
"Warning" : "Advertencia",
"Danger" : "Peligro",
"Light" : "Ligero",
"Left" : "Izquierda",
"Right" :"Derecha",
"Dark" : "Oscuro",
"Center" : "Centrar"
},
"image": {
"With border" : "Con borde",
"Stretch image" : "Estirar imagen",
"With background" : "Añadir fondo",
"Select an Image" : "Seleccione una imagen",
"With caption" : "Con título"
}
},
/**
* Section allows to translate Block Tunes
*/
blockTunes: {
/**
* Each subsection is the i18n dictionary that will be passed to the corresponded Block Tune plugin
* The name of a plugin should be equal the name you specify in the 'tunes' section for that plugin
*
* Also, there are few internal block tunes: "delete", "moveUp" and "moveDown"
*/
"delete": {
"Delete": "Eliminar",
"Click to delete" : "Click para eliminar"
},
"moveUp": {
"Move up": "Mover hacia arriba"
},
"moveDown": {
"Move down": "Mover hacia abajo"
}
},
}
}

View File

@ -0,0 +1,168 @@
import Ajax from "../ajax.js";
import { alertConfirmAction, alertConfirmationDelete, alertError, alertSuccess } from "../alerts/sweetAlert.js";
import Modal from "../modal.js"
import { getTablerIconClassName } from "../../common/common.js";
class WikiSectionForm {
constructor() {
this.item = $("#formSection")
this.btnNew = $("#submit-new-section")
this.btnUpdate = $("#submit-update-section")
this.btnDelete = $("#delete-section")
this.name = this.item.find('#section-name')
this.icon = this.item.find('#section-icon')
this.roles = this.item.find('#section-roles').select2({
dropdownParent: this.item.parent()
})
this.newSectionBtn = $("#new-section")
this.editSectionBtn = $("#edit-section")
this.modalSection = $("#modalSection")
this.modal = new Modal(this.modalSection)
this.icons = getTablerIconClassName().map((e, index) => {
return { 'text': e, 'id': `ti ${e}` }
})
}
renderIcon(option) {
var icon = `<i class="ti ${option.text}"></i> ${option.text}`;
return icon;
}
init() {
this.icon.wrap('<div class="position-relative"></div>').select2({
data: this.icons,
dropdownParent: this.icon.parent(),
templateResult: this.renderIcon,
templateSelection: this.renderIcon,
escapeMarkup: function (m) { return m; }
})
this.icon.on('change', () => {
console.log(this.icon.val())
})
this.btnNew.on('click', this.post.bind(this))
this.btnUpdate.on('click', this.update.bind(this))
this.btnDelete.on('click',this.deleteConfirm.bind(this))
this.newSectionBtn.on('click', () => {
this.initPost()
})
this.editSectionBtn.on('click', () => {
this.initUpdate()
})
}
setId(id) {
this.sectionId = id
}
initPost() {
this.showPost()
}
initUpdate() {
this.showUpdate()
}
getFormData() {
return {
name: this.name.val(),
icon: this.icon.val(),
roles: this.roles.val()
}
}
post() {
const ajax = new Ajax('/wiki/section',
this.getFormData(),
null,
this.success.bind(this),
this.error.bind(this)
)
ajax.post()
}
update() {
const ajax = new Ajax('/wiki/update/section',
{
wiki_section_id: this.sectionId,
...this.getFormData()
},
null,
this.success.bind(this),
this.error.bind(this)
)
ajax.post()
}
deleteConfirm() {
alertConfirmAction('Eliminar sección').then(result => {
if (result.isConfirmed) {
this.delete()
}
console.log(result)
});
}
delete() {
const ajax = new Ajax('/wiki/section/' + this.sectionId,
null,
null,
this.success.bind(this),
this.error.bind(this)
)
ajax.delete()
}
success(response) {
alertSuccess(response.message).fire()
this.modal.toggle()
}
error(error) {
alertError(error?.message).fire()
}
showPost() {
this.empty()
this.btnNew.removeClass('d-none')
this.btnUpdate.addClass('d-none')
$("#modalTitleNew").removeClass('d-none')
$("#modalTitleEdit").addClass('d-none')
this.modal.toggle()
}
async showUpdate() {
this.empty()
const sectionData = await this.handleShowSection()
if (sectionData) {
console.log(sectionData)
this.name.val(sectionData.data.name)
this.icon.val(sectionData.data.icon).trigger('change')
this.roles.val(sectionData.data.roles_array).trigger('change')
}
this.btnUpdate.removeClass('d-none')
this.btnNew.addClass('d-none')
$("#modalTitleNew").addClass('d-none')
$("#modalTitleEdit").removeClass('d-none')
this.modal.toggle()
}
handleShowSection() {
return new Promise((resolve, reject) => {
const ajax = new Ajax('/wiki/section/' + this.sectionId,
null,
null,
(response) => {
resolve(response)
},
(error) => {
alertError(error.message).fire()
resolve(false)
}
)
ajax.get()
})
}
empty() {
this.name.val(null)
this.icon.val("").trigger('change')
this.roles.val("").trigger('change')
}
}
export default WikiSectionForm

View File

@ -0,0 +1,37 @@
import "../../../../../themes/vuexy/vendor/libs/sortablejs/sortable.js"
class ListSortable{
constructor(listItem,config = {}) {
this.list = Sortable.create($(listItem)[0], {
animation: 150,
group: 'menuSorting',
handle: '.drag-handle',
direction : 'vertical',
...config,
});
}
getArray(){
const list = this.list.toArray()
return list
}
onChange(callback)
{
this.list.option('onChange',callback)
}
onMove(callback)
{
this.list.option('onMove',callback)
}
onEnd(callback)
{
this.list.option('onEnd',callback)
}
}
export default ListSortable

View File

@ -9,6 +9,7 @@ class Modal{
show(){
this.item.modal('show');
}
}
export default Modal;

View File

@ -0,0 +1,13 @@
import WikiEditor from "../../components/editorjs/WikiEditor.js"
import Ajax from "../../components/ajax.js"
$(async () => {
try {
const editor = new WikiEditor()
await editor.init()
} catch (error) {
}
})

View File

@ -0,0 +1,25 @@
import Ajax from "../../components/ajax.js"
import { alertError, alertSuccess } from "../../components/alerts/sweetAlert.js"
import ListSortable from "../../components/listSortable.js"
$(() => {
const menuSectionList = new ListSortable('#menu-inner-list')
menuSectionList.onEnd(updateWikiSectionOrder)
})
const updateWikiSectionOrder = (evt) =>
{
let list = new ListSortable('#menu-inner-list')
const ajax = new Ajax('/wiki/section/update/order',
{
orders : list.getArray()
},
null,
(response) => alertSuccess(response.message).fire(),
(error) => alertError(error.message).fire(),
)
ajax.post();
}

View File

@ -0,0 +1,13 @@
import WikiEditor from "../../components/editorjs/WikiEditor.js"
$(async() => {
try {
const editor = new WikiEditor()
await editor.initViewOnly()
} catch (error) {
}
})