material-editor.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434
  1. let MaterialEditor = (function () {
  2. let module = {};
  3. let __CreateNewsButton = document.querySelector("#CreateNewsButton");
  4. if (__CreateNewsButton) {
  5. __CreateNewsButton.onclick = openModal;
  6. }
  7. let __CreateNoticeButton = document.querySelector("#CreateNoticeButton");
  8. if (__CreateNoticeButton) {
  9. __CreateNoticeButton.onclick = openModal;
  10. }
  11. let __CreateMaterialButton = document.querySelector("#CreateMaterialButton");
  12. if (__CreateMaterialButton) {
  13. __CreateMaterialButton.onclick = openModal;
  14. }
  15. let __CurrentCategory = undefined;
  16. function dataURIToBlob(dataURI) {
  17. const splitDataURI = dataURI.split(",");
  18. const byteString = splitDataURI[0].indexOf("base64") >= 0 ? atob(splitDataURI[1]) : decodeURI(splitDataURI[1]);
  19. const mimeString = splitDataURI[0].split(":")[1].split(";")[0];
  20. const ia = new Uint8Array(byteString.length);
  21. for (let i = 0; i < byteString.length; i++) ia[i] = byteString.charCodeAt(i);
  22. return new Blob([ia], { type: mimeString });
  23. }
  24. /**
  25. * Собрать все данные и отправить на БД
  26. *
  27. * @param {HTMLInputElement} caption
  28. * @param {HTMLInputElement} link
  29. * @param {HTMLDivElement} content
  30. * @param {HTMLDivElement} images
  31. * @param {HTMLInputElement} file
  32. */
  33. function create(caption, link, content, images, file) {
  34. if (!caption || !link || !content || !images) {
  35. Messenger.Show("Некоторые элементы не были найдены, невозможно создать новость!");
  36. return;
  37. }
  38. if (caption.value.length == 0) {
  39. Messenger.Show("Необходимо заполнить заголовок!");
  40. return;
  41. }
  42. if (link.value.length == 0) {
  43. link.value = Main.GenerateHash256();
  44. }
  45. let preview = {
  46. Valid: true,
  47. String: "",
  48. };
  49. var date = new Date();
  50. let m = ("00" + (date.getMonth() + 1)).slice(-2);
  51. let d = ("00" + date.getDate()).slice(-2);
  52. var imageFullpath = "image/" + __CurrentCategory + "/" + date.getUTCFullYear().toString() + "-" + m + "-" + d + "/";
  53. var thumbFullpath = "thumb/" + __CurrentCategory + "/" + date.getUTCFullYear().toString() + "-" + m + "-" + m + "/";
  54. let data = new FormData();
  55. let contentHtml = "";
  56. for (const c of content.blocks) {
  57. switch (c.type) {
  58. case "paragraph":
  59. contentHtml += "<p>" + c.data.text + "</p>";
  60. preview.String += c.data.text;
  61. break;
  62. case "header":
  63. contentHtml += "<h" + c.data.level + ">" + c.data.text + "/h" + c.data.level + ">";
  64. preview.String += c.data.text;
  65. break;
  66. case "list":
  67. if (c.data.style === "ordered") {
  68. contentHtml += "<ol>";
  69. for (const li of c.data.items) {
  70. contentHtml += "<li>" + li + "</li>";
  71. preview.String += li;
  72. }
  73. contentHtml += "</ol>";
  74. } else {
  75. contentHtml += "<ul>";
  76. for (const li of c.data.items) {
  77. contentHtml += "<li>" + li + "</li>";
  78. preview.String += li;
  79. }
  80. contentHtml += "</ul>";
  81. }
  82. break;
  83. case "delimiter":
  84. contentHtml += "<hr>";
  85. break;
  86. case "table":
  87. contentHtml += "<table>";
  88. for (const row of c.data.content) {
  89. contentHtml += "<tr>";
  90. for (const col of row) {
  91. contentHtml += "<td>" + col + "</td>";
  92. preview.String += col;
  93. }
  94. contentHtml += "</tr>";
  95. }
  96. contentHtml += "</table>";
  97. break;
  98. case "image":
  99. let filename = Main.GenerateHashDashed(2) + "." + c.data.url.slice(11, c.data.url.indexOf(";"));
  100. contentHtml +=
  101. '<a href="/' +
  102. imageFullpath +
  103. filename +
  104. '" data-fslightbox="' +
  105. link.value +
  106. '"><img class="material-image" src="/' +
  107. thumbFullpath +
  108. filename +
  109. '" /></a>';
  110. let div = document.createElement("div");
  111. div.innerHTML = c.data.caption;
  112. if (div.textContent.length > 0) {
  113. contentHtml += '<div class="material-image-caption">' + c.data.caption + "</div>";
  114. }
  115. data.append("Files", dataURIToBlob(c.data.url), filename);
  116. break;
  117. }
  118. }
  119. let clearTags = document.createElement("div");
  120. clearTags.innerHTML = preview.String;
  121. preview.String = clearTags.textContent;
  122. if (preview.String.length > 260) {
  123. preview.String = preview.String.slice(0, 260);
  124. }
  125. let selectedImage = {
  126. Valid: false,
  127. String: "",
  128. };
  129. if (__CurrentCategory != MATERIAL_CATEGORY.MATERIAL) {
  130. let selectedImageName = ";";
  131. let selectedInput = images.querySelector("input:checked");
  132. if (selectedInput) {
  133. let img = selectedInput.closest(".image").querySelector("img");
  134. if (img) {
  135. selectedImageName = img.alt;
  136. selectedImage.String = thumbFullpath + selectedImageName.toLowerCase();
  137. selectedImage.Valid = true;
  138. }
  139. }
  140. if (file.files.length > 0) {
  141. if (__CurrentCategory != MATERIAL_CATEGORY.NOTICE) {
  142. contentHtml += '<div class="news-images">';
  143. }
  144. for (const f of file.files) {
  145. let filename = f.name.toLowerCase();
  146. filename = Main.GenerateHashDashed(2) + filename.slice(filename.lastIndexOf("."));
  147. if (selectedImageName === f.name) {
  148. selectedImage.String = thumbFullpath + filename;
  149. }
  150. if (__CurrentCategory != MATERIAL_CATEGORY.NOTICE) {
  151. contentHtml +=
  152. '<a href="/' +
  153. imageFullpath +
  154. filename +
  155. '" data-fslightbox="' +
  156. link.value +
  157. '"><img src="/' +
  158. thumbFullpath +
  159. filename +
  160. '" alt="' +
  161. filename +
  162. '"></a>';
  163. }
  164. data.append("Files", f, filename);
  165. }
  166. if (__CurrentCategory != MATERIAL_CATEGORY.NOTICE) {
  167. contentHtml += "</div>";
  168. }
  169. }
  170. }
  171. data.set("Caption", caption.value);
  172. data.set("Link", link.value);
  173. data.set("Content", contentHtml);
  174. data.set("Source", JSON.stringify(content));
  175. data.set("PreviewValid", preview.Valid);
  176. data.set("PreviewString", preview.String);
  177. data.set("ImageValid", selectedImage.Valid);
  178. data.set("ImageString", selectedImage.String);
  179. data.set("ImagePath", imageFullpath);
  180. data.set("ThumbPath", thumbFullpath);
  181. let closeModal = this.closest(".modal").querySelector(".close");
  182. XHR.POST(
  183. function (result) {
  184. if (result && "Error" in result) {
  185. if (result.Error === null) {
  186. Messenger.Show(__CurrentCategory + " успешно добавлена");
  187. closeModal.click();
  188. } else {
  189. Messenger.Show("Возникла ошибка добавления новости." + result.Error);
  190. }
  191. }
  192. },
  193. "/material-insert/" + __CurrentCategory,
  194. data,
  195. null,
  196. true,
  197. true
  198. );
  199. }
  200. /**
  201. * Создать модальное окно для добавления новости
  202. */
  203. function openModal() {
  204. switch (this.dataset.category) {
  205. case MATERIAL_CATEGORY.MATERIAL:
  206. __CurrentCategory = MATERIAL_CATEGORY.MATERIAL;
  207. break;
  208. case MATERIAL_CATEGORY.NEWS:
  209. __CurrentCategory = MATERIAL_CATEGORY.NEWS;
  210. break;
  211. case MATERIAL_CATEGORY.NOTICE:
  212. __CurrentCategory = MATERIAL_CATEGORY.NOTICE;
  213. break;
  214. default:
  215. return;
  216. }
  217. let body = document.createElement("div"),
  218. inputCaption = document.createElement("input"),
  219. inputLink = document.createElement("input"),
  220. contentBlock = document.createElement("div"),
  221. previewBlock = document.createElement("div"),
  222. preview = document.createElement("div"),
  223. label = document.createElement("label"),
  224. file = document.createElement("input"),
  225. fileButton = document.createElement("div"),
  226. buttonSend = document.createElement("button");
  227. label.append(file, fileButton);
  228. label.className = "select-file";
  229. fileButton.className = "button";
  230. fileButton.textContent = "Выбрать изображениия";
  231. file.type = "file";
  232. if (__CurrentCategory === MATERIAL_CATEGORY.NOTICE) {
  233. file.multiple = false;
  234. } else {
  235. file.multiple = true;
  236. }
  237. file.accept = "image/*";
  238. file.onchange = changeImages;
  239. inputCaption.type = "text";
  240. inputCaption.placeholder = "Заголовок/название (обязательно)";
  241. inputLink.type = "text";
  242. inputLink.placeholder = "Ссылка на " + __CurrentCategory + " (не обязательно)";
  243. buttonSend.textContent = "Создать";
  244. // buttonSend.onclick = create.bind(buttonSend, inputCaption, inputLink, contentBlock, preview, file);
  245. preview.className = "preview-images";
  246. contentBlock.id = "editorjs";
  247. contentBlock.className = "content-editor";
  248. // contentBlock.append(createManipulator());
  249. if (__CurrentCategory !== MATERIAL_CATEGORY.MATERIAL) {
  250. body.append(inputCaption, inputLink, contentBlock, previewBlock, label, preview, buttonSend);
  251. } else {
  252. body.append(inputCaption, inputLink, contentBlock, previewBlock, buttonSend);
  253. }
  254. let tools = {
  255. header: {
  256. class: Header,
  257. inlineToolbar: true,
  258. config: {
  259. placeholder: "Заголовок",
  260. },
  261. shortcut: "CMD+SHIFT+H",
  262. },
  263. list: {
  264. class: List,
  265. inlineToolbar: true,
  266. shortcut: "CMD+SHIFT+L",
  267. },
  268. // quote: {
  269. // class: Quote,
  270. // inlineToolbar: true,
  271. // config: {
  272. // quotePlaceholder: "Введите цитату",
  273. // captionPlaceholder: "Автор цитаты",
  274. // },
  275. // shortcut: "CMD+SHIFT+O",
  276. // },
  277. marker: {
  278. class: Marker,
  279. shortcut: "CMD+SHIFT+M",
  280. },
  281. delimiter: Delimiter,
  282. inlineCode: {
  283. class: InlineCode,
  284. shortcut: "CMD+SHIFT+C",
  285. },
  286. linkTool: LinkTool,
  287. table: {
  288. class: Table,
  289. inlineToolbar: true,
  290. shortcut: "CMD+ALT+T",
  291. },
  292. onReady: function () {
  293. console.log("ready");
  294. },
  295. onChange: function () {
  296. console.log("changed");
  297. },
  298. };
  299. if (__CurrentCategory === MATERIAL_CATEGORY.MATERIAL) {
  300. tools.image = {
  301. class: SimpleImage,
  302. inlineCode: true,
  303. };
  304. tools.embed = {
  305. class: Embed,
  306. inlineToolbar: false,
  307. config: {
  308. services: {
  309. youtube: true,
  310. },
  311. },
  312. };
  313. }
  314. const editor = new EditorJS({
  315. holderOd: "editorjs",
  316. tools: tools,
  317. data: null,
  318. });
  319. buttonSend.addEventListener("click", function () {
  320. editor.save().then((savedData) => {
  321. create.call(buttonSend, inputCaption, inputLink, savedData, preview, file);
  322. });
  323. });
  324. Modal.Create("Создать " + __CurrentCategory, body);
  325. }
  326. /**
  327. * Функция выбора файлов изображения для новости
  328. */
  329. function changeImages() {
  330. let preview = document.querySelector(".preview-images");
  331. if (!preview) {
  332. console.warn("Элемент [.preview-images] не найжен");
  333. return;
  334. }
  335. for (const file of this.files) {
  336. /**
  337. * 1. Контейнер под изображение
  338. */
  339. let div = document.createElement("div");
  340. div.className = "image";
  341. /**
  342. * 2. Создать элемент для изображения
  343. */
  344. let img = document.createElement("img");
  345. img.src = window.URL.createObjectURL(file);
  346. img.alt = file.name;
  347. img.height = 100;
  348. /**
  349. * 3. Создать label
  350. */
  351. let label = document.createElement("label");
  352. /**
  353. * 4. Создать input + span
  354. */
  355. let input = document.createElement("input");
  356. input.type = "radio";
  357. input.dataset.image = file.name;
  358. input.name = "preview-images";
  359. let span = document.createElement("span");
  360. div.append(img, label);
  361. label.append(input, span);
  362. preview.append(div);
  363. }
  364. let firstImage = preview.querySelector("input");
  365. firstImage.click();
  366. }
  367. return module;
  368. })();