UNPKG

globular-mvc

Version:

Generic template to create web-application that made use of globular as backend and materialize as css (wrap in web-component's)

1,317 lines (1,120 loc) 99.8 kB
import EditorJS from '@editorjs/editorjs'; import RawTool from '@editorjs/raw'; import Header from '@editorjs/header'; import Delimiter from '@editorjs/delimiter'; import Embed from '@editorjs/embed'; import Table from '@editorjs/table' import Quote from '@editorjs/quote' import SimpleImage from '@editorjs/simple-image' import NestedList from '@editorjs/nested-list'; import Checklist from '@editorjs/checklist'; import Paragraph from 'editorjs-paragraph-with-alignment' import CodeTool from '@editorjs/code' import Underline from '@editorjs/underline'; import DragDrop from 'editorjs-drag-drop'; import Undo from 'editorjs-undo'; import { File as File__ } from "../File"; // File object already exist in js and I need to use it... import { ApplicationView } from '../ApplicationView'; import { CreateBlogPostRequest, GetBlogPostsByAuthorsRequest, SaveBlogPostRequest, BlogPost, DeleteBlogPostRequest, AddEmojiRequest, Emoji, AddCommentRequest, Comment, GetBlogPostsRequest } from 'globular-web-client/blog/blog_pb'; import { Application } from '../Application'; import { generatePeerToken, Model, getUrl } from '../Model'; import * as edjsHTML from 'editorjs-html' import { Account } from '../Account'; import { v4 as uuidv4 } from "uuid"; import '@polymer/iron-icons/communication-icons' import '@polymer/iron-icons/editor-icons' import * as getUuidByString from 'uuid-by-string'; import { BlogPostInfo } from './Informations'; import { AppScrollEffectsBehavior } from '@polymer/app-layout/app-scroll-effects/app-scroll-effects-behavior'; import { Menu } from './Menu'; import { Carousel } from './Carousel' import { ImageGallery } from './Gallery' import "@polymer/paper-spinner/paper-spinner.js"; import { getAudioInfo, getVideoInfo } from './File'; import { SearchAudioCard, SearchVideoCard } from './Search'; import { randomUUID } from './utility'; import { playVideos } from './Video'; import { playAudio, playAudios } from './Audio'; const intervals = [ { label: 'year', seconds: 31536000 }, { label: 'month', seconds: 2592000 }, { label: 'day', seconds: 86400 }, { label: 'hour', seconds: 3600 }, { label: 'minute', seconds: 60 }, { label: 'second', seconds: 1 } ]; export function timeSince(date) { const seconds = Math.floor((Date.now() - date.getTime()) / 1000); const interval = intervals.find(i => i.seconds < seconds); const count = Math.floor(seconds / interval.seconds); return `${count} ${interval.label}${count !== 1 ? 's' : ''} ago`; } // Get the image default size... function getMeta(url, callback) { const img = new Image(); img.addEventListener("load", () => { callback({ width: img.naturalWidth, height: img.naturalHeight }); }); img.src = url; } // generate html from json data function jsonToHtml(data) { // So here I will get the plain html from the output json data. function filesParser(block) { let jsonStr = JSON.stringify(block.data.files) return `<globular-file-drop-zone files='${btoa(jsonStr)}'> </globular-file-drop-zone>`; } const edjsParser = edjsHTML({ files: filesParser }); let elements = edjsParser.parse(data); let html = "" elements.forEach(e => { html += e }); var div = document.createElement('div'); div.className = "blog-read-div" div.slot = "read-only-blog-content" div.innerHTML = html.trim(); // Now I will set image height. let images = div.querySelectorAll("img") images.forEach(img => { getMeta(img.src, meta => { if (meta.width < div.offsetWidth && meta.height < div.offsetHeight) { img.style.width = meta.width + "px" img.style.height = meta.height + "px" } }) }) return div } /** * Here I will create the image from the data url. * @param {*} url * @param {*} callback */ function getImageFile(url, callback) { var image = new Image(); image.crossOrigin = 'Anonymous'; image.onload = function () { var canvas = document.createElement('canvas'); var context = canvas.getContext('2d'); canvas.height = this.naturalHeight; canvas.width = this.naturalWidth; context.drawImage(this, 0, 0); var dataURL = canvas.toDataURL('image/jpeg'); let img = new Image() img.src = dataURL callback(img) }; image.src = url; } /** * Create a thumbnail from an url. The url can from from img.src... * @param {*} src The image url * @param {*} w The width of the thumnail * @param {*} callback The callback to be call */ export function createThumbmail(src, w, callback) { getImageFile(src, (img) => { if (img.width > w) { var oc = document.createElement('canvas'), octx = oc.getContext('2d'); oc.width = img.width; oc.height = img.height; octx.drawImage(img, 0, 0); if (img.width > img.height) { oc.height = (img.height / img.width) * w; oc.width = w; } else { oc.width = (img.width / img.height) * w; oc.height = w; } octx.drawImage(oc, 0, 0, oc.width, oc.height); octx.drawImage(img, 0, 0, oc.width, oc.height); callback(oc.toDataURL()); } else { callback(img.src); } }) } export function createThumbmailFromImage(img, w, callback) { createThumbmail(img.src, w, callback) } export function readBlogPost(domain, uuid, callback, errorCallback) { let globule = Model.getGlobule(domain) generatePeerToken(globule, token => { let rqst = new GetBlogPostsRequest rqst.setUuidsList([uuid]) let stream = globule.blogService.getBlogPosts(rqst, { domain: Model.domain, application: Model.application, address: Model.address, token: token }); let blogs = [] stream.on("data", (rsp) => { blogs.push(rsp.getBlogPost()) }); stream.on("status", (status) => { if (status.code == 0) { callback(blogs[0]) } else { callback([]) } }) }, errorCallback) } /** * Search Box */ export class BlogPostElement extends HTMLElement { // attributes. // Create the applicaiton view. constructor(blog, globule) { super() this.globule = globule if (!globule) { this.globule = Model.globular } this.blog = blog if (blog != undefined) { this.id = "_" + blog.getUuid() } // The close event listener. this.onclose = null // Set the shadow dom. this.attachShadow({ mode: 'open' }); // Innitialisation of the layout. this.shadowRoot.innerHTML = ` <style> #container { display: flex; justify-content: center; margin-bottom: 10px; margin-top: 10px; } .blog-post-editor-div{ display: flex; flex-direction: column; width: 800px; } .blog-post-title-div { display: flex; border-bottom: 1px solid var(--palette-action-disabled); padding: 4px; padding-left: 16px; align-items: center; } .blog-editor-title { flex-grow: 1; color: var(--palette-action-disabled); text-align: left; padding: 8px; } .blog-reader-title{ flex-grow: 1; text-align: left; margin-left: 16px; padding: 8px; } .blog-options-panel{ position: absolute; right: 50px; top: 40px; z-index: 100; background-color: var(--palette-background-paper); } .blog-options-panel .card-content{ min-width: 400px; padding: 0px 10px 0px 10px; display: flex; flex-direction: column; } .blog-actions{ display: flex; border-top: 1px solid var(--palette-action-disabled); align-items: center; } globular-string-list-setting { padding-left: 0px; padding-rigth: 0px; } .blog-post-reader-div{ position: relative; width: 800px; text-align: center; } #close-btn{ position: absolute; top: 0px; right: 0px; z-index: 100; } paper-card{ background-color: var(--palette-background-paper); color: var(--palette-text-primary); font-size: 1rem; border-left: 1px solid var(--palette-divider); border-right: 1px solid var(--palette-divider); border-top: 1px solid var(--palette-divider); } paper-radio-button { --paper-radio-button-checked-color: var(--palette-primary-main); --paper-radio-button-checked-ink-color: var(--palette-primary-main); --paper-radio-button-unchecked-color: var(--palette-action-disabled); --paper-radio-button-unchecked-ink-color: var(--palette-action-disabled); --paper-radio-button-label-color: var(--palette-action-disabled); } paper-radio-button[checked] { --paper-radio-button-label-color: var(--palette-text-accent); } @media (max-width: 500px) { #container { width: calc(100vw - 20px); } .blog-post-editor-div, .blog-post-reader-div { width: calc(100vw - 20px); position: relative; } .blog-post-title-div{ width: 100%; flex-direction: column; align-items: flex-start; padding: 0px; } .actions-div{ position: absolute; top: 0px; right: 0px; } .blog-actions{ flex-direction: column; } .blog-actions paper-radio-group{ align-self: flex-start; } .blog-actions div{ align-self: flex-end; } .blog-reader-title, .blog-read-div{ padding: 5px; margin: 0px; width: calc(100vw - 20px); } } </style> <div id="container"> <paper-card class="blog-post-editor-div"> <div class="blog-post-title-div"> <div style="display: flex; width: 32px; height: 32px; justify-content: center; align-items: center;position: relative;"> <iron-icon id="collapse-btn" icon="unfold-less" --iron-icon-fill-color:var(--palette-text-primary);"></iron-icon> <paper-ripple class="circle" recenters=""></paper-ripple> </div> <span id="blog-editor-title" class="blog-editor-title"> ${Application.account.name}, express yourself </span> <div class="actions-div" style="display: flex;"> <paper-icon-button name="publish-blog" title="save the blog" icon="icons:save"></paper-icon-button> <paper-icon-button name="blog-editor-delete-btn" title="delete blog" icon="icons:delete" ></paper-icon-button> <paper-icon-button id="exit-editor-btn" title="exit edit mode" icon="icons:exit-to-app"></paper-icon-button> <paper-icon-button icon="icons:more-vert" title="more options" id="blog-editor-menu-btn"></paper-icon-button> <paper-icon-button id="close-editor-btn" title="exit" icon="icons:close"></paper-icon-button> </div> </div> <iron-collapse opened = "[[opened]]" id="collapse-panel" style="display: flex; flex-direction: column;"> <paper-card id="blog-editor-options-panel" class="blog-options-panel" style="display: none;"> <div style="display: flex; flex-direction: column; margin: 5px;"> <globular-image-selector label="cover" url=""> </globular-image-selector> </div> <div class="card-content" style="background-color: transparent;"> <paper-input id="blog-title-input" label="title"></paper-input> <paper-input id="blog-subtitle-input" label="subtitle"></paper-input> <globular-string-list-setting id="keywords-list" name="keywords" description="keywords will be use by the search engine to retreive your blog."></globular-string-list-setting> </div> <div style="display: flex;"> <div class="blog-actions" style="background-color: transparent; flex-grow: 1;"> <paper-radio-group selected="draft" style="flex-grow: 1; text-align: left; font-size: 1rem;"> <paper-radio-button name="draft">draft</paper-radio-button> <paper-radio-button name="published">published</paper-radio-button> <paper-radio-button name="archived">archived</paper-radio-button> </paper-radio-group> </div> </div> </paper-card> <slot id="edit-blog-content" name="edit-blog-content"></slot> </iron-collapse> </paper-card> <paper-card class="blog-post-reader-div"> <div id="title" class="blog-post-title-div"> <div style="display: flex; flex-direction: column; padding-left: 5px;"> <div> <img id="blog-reader-author-blog-post" style="width: 32px; height: 32px; border-radius: 16px; display:none;"></img> <iron-icon id="blog-reader-author-icon" icon="account-circle" style="width: 34px; height: 34px; --iron-icon-fill-color:var(--palette-action-disabled); display: block;"></iron-icon> </div> <span id="blog-reader-author-id"></span> </div> <h2 class="blog-reader-title"></h2> <div class="actions-div" style="display: flex;"> <paper-icon-button id="blog-reader-edit-btn" icon="editor:mode-edit"></paper-icon-button> <paper-icon-button id="close-reader-btn" icon="icons:close"></paper-icon-button> </div> </div> <slot id="read-only-blog-content" name="read-only-blog-content"></slot> <slot name="blog-comments"></slot> </paper-card> </div> ` // The comments container... this.blogComments = new BlogComments(blog, null, globule) this.blogComments.slot = "blog-comments" this.appendChild(this.blogComments) if (blog != null) { if (blog.getStatus() == 0) { this.shadowRoot.querySelector("paper-radio-group").selected = "draft" } else if (blog.getStatus() == 1) { this.shadowRoot.querySelector("paper-radio-group").selected = "published" } else if (blog.getStatus() == 2) { this.shadowRoot.querySelector("paper-radio-group").selected = "archived" } if (blog.getThumbnail().length > 0) { this.shadowRoot.querySelector("globular-image-selector").setImageUrl(blog.getThumbnail()) } else { console.log("-------------> no image was set for thumbnail...") } this.shadowRoot.querySelector("globular-image-selector").ondelete = () => { blog.setThumbnail("") } this.shadowRoot.querySelector("globular-image-selector").onselectimage = (dataUrl) => { this.blog.setThumbnail(dataUrl) } } this.shadowRoot.querySelector("#close-reader-btn").onclick = () => { if (this.onclose != undefined) { this.onclose() } } this.shadowRoot.querySelector("#close-editor-btn").onclick = () => { if (this.onclose != undefined) { this.onclose() } } this.shadowRoot.querySelector("#exit-editor-btn").onclick = () => { this.read(() => { ApplicationView.displayMessage("exit edit mode", 3000) }) } this.collapse_btn = this.shadowRoot.querySelector("#collapse-btn") this.collapse_panel = this.shadowRoot.querySelector("#collapse-panel") this.collapse_btn.onclick = () => { if (!this.collapse_panel.opened) { this.collapse_btn.icon = "unfold-more" } else { this.collapse_btn.icon = "unfold-less" } this.collapse_panel.toggle(); } // publish the blog... let publishBtns = this.shadowRoot.querySelectorAll(`[name="publish-blog"]`) for (var i = 0; i < publishBtns.length; i++) { publishBtns[i].onclick = () => { this.publish() } } // Delete the blog... let deleteBtns = this.shadowRoot.querySelectorAll(`[name="blog-editor-delete-btn"]`) for (var i = 0; i < deleteBtns.length; i++) { deleteBtns[i].onclick = () => { let toast = ApplicationView.displayMessage( ` <style> #yes-no-blog-post-delete-box{ display: flex; flex-direction: column; } #yes-no-blog-post-delete-box globular-blog-post-card{ padding-bottom: 10px; } #yes-no-blog-post-delete-box div{ display: flex; padding-bottom: 10px; } </style> <div id="yes-no-blog-post-delete-box"> <div>Your about to delete blog post</div> <img style="max-height: 256px; object-fit: contain; width: 100%;" src="${this.blog.getThumbnail()}"></img> <span style="font-size: 1.1rem;">${this.blog.getTitle()}</span> <div>Is it what you want to do? </div> <div style="justify-content: flex-end;"> <paper-button raised id="yes-delete-blog-post">Yes</paper-button> <paper-button raised id="no-delete-blog-post">No</paper-button> </div> </div> `, 60 * 1000 // 60 sec... ); let yesBtn = document.querySelector("#yes-delete-blog-post") let noBtn = document.querySelector("#no-delete-blog-post") // On yes yesBtn.onclick = () => { let rqst = new DeleteBlogPostRequest rqst.setUuid(this.blog.getUuid()) let globule = this.globule rqst.setIndexpath(globule.config.DataPath + "/search/blogPosts") // Delete the blog... generatePeerToken(globule, token => { globule.blogService.deleteBlogPost(rqst, { domain: Model.domain, application: Model.application, address: Model.address, token: token }) .then(rsp => { Model.getGlobule(this.blog.getDomain()).eventHub.publish(this.blog.getUuid() + "_blog_delete_event", {}, false) Model.eventHub.publish("_blog_delete_event_", this.blog.getUuid(), true) ApplicationView.displayMessage( `<div style="display: flex; flex-direction: column;"> <span style="font-size: 1.1rem;">${this.blog.getTitle()}</span> <span>was deleted...</span> </div>`, 3000 ); }) .catch(e => ApplicationView.displayMessage(e, 3000)) }, err => ApplicationView.displayMessage(err, 3000)) toast.dismiss(); } noBtn.onclick = () => { toast.dismiss(); } } } // do not close the panel... this.shadowRoot.querySelector("#blog-editor-options-panel").onclick = (evt) => { evt.stopPropagation() } // Display the option panel. this.shadowRoot.querySelector("#blog-editor-menu-btn").onclick = (evt) => { evt.stopPropagation() let optionPanel = this.shadowRoot.querySelector("#blog-editor-options-panel") if (optionPanel.style.display == "") { optionPanel.style.display = "none"; } else { optionPanel.style.display = ""; optionPanel.style.top = optionPanel.parentNode.offsetHeigt / 2 + "px" } if (this.titleInput.value) { if (this.titleInput.value.length > 0) { this.titleSpan.innerHTML = this.titleInput.value; this.titleSpan.style.color = "var(--palette-text-primary)" } else { this.titleSpan.innerHTML = ` ${Application.account.name}, express yourself` this.titleSpan.style.color = "var(--palette-action-disabled)" } } else { this.titleSpan.innerHTML = ` ${Application.account.name}, express yourself` this.titleSpan.style.color = "var(--palette-action-disabled)" } } this.shadowRoot.querySelector("#container").onclick = () => { let optionPanel = this.shadowRoot.querySelector("#blog-editor-options-panel") optionPanel.style.display = "none"; } // The editor values. this.titleSpan = this.shadowRoot.querySelector(".blog-reader-title") this.titleInput = this.shadowRoot.querySelector("#blog-title-input") this.subtitleInput = this.shadowRoot.querySelector("#blog-subtitle-input") this.keywordsEditList = this.shadowRoot.querySelector("#keywords-list") // switch to edit mode... this.shadowRoot.querySelector("#blog-reader-edit-btn").onclick = () => { this.edit(() => { this.setAttribute("editable", "true") ApplicationView.displayMessage("you'r in edit mode, click save to exit...", 3000) }) } // set the blog. if (blog != undefined) { this.setBlog(blog) } } // Connection callback connectedCallback() { // If the blog editor is set to true... if (this.getAttribute("editable") != undefined) { if (this.getAttribute("editable") == "true") { this.edit(() => { }) } } } // Set the blog... setBlog(blog) { this.blog = blog; if (this.blog.getThumbnail().length > 0) { this.shadowRoot.querySelector("globular-image-selector").setImageUrl(blog.getThumbnail()) } else { console.log("no image was set to blog.") } this.shadowRoot.querySelector("globular-image-selector").ondelete = () => { blog.setThumbnail("") } this.shadowRoot.querySelector("globular-image-selector").onselectimage = (dataUrl) => { this.blog.setThumbnail(dataUrl) } this.blogComments.setBlog(blog) if (this.blog.getAuthor() != Application.account.getId() + "@" + Application.account.getDomain()) { let editBtn = this.shadowRoot.querySelector("#blog-reader-edit-btn") editBtn.parentNode.removeChild(editBtn) } if (this.updateListener == undefined) { Model.getGlobule(this.blog.getDomain()).eventHub.subscribe(this.blog.getUuid() + "_blog_updated_event", uuid => this.updateListener = uuid, evt => { readBlogPost(this.blog.getDomain(), this.blog.getUuid(), b => { this.blog = b let isEditable = this.getAttribute("editable") if (isEditable == undefined) { isEditable = false } else { if (this.getAttribute("editable") == "true") { isEditable = true } else { isEditable = false } } if (isEditable) { this.edit(() => { this.titleSpan.innerHTML = this.blog.getTitle() this.titleInput.value = this.blog.getTitle() this.subtitleInput.value = this.blog.getSubtitle() this.keywordsEditList.setValues(this.blog.getKeywordsList()) }) } else { this.read(() => { // set back values. this.titleSpan.innerHTML = this.blog.getTitle() this.titleInput.value = this.blog.getTitle() this.subtitleInput.value = this.blog.getSubtitle() this.keywordsEditList.setValues(this.blog.getKeywordsList()) }) } }, err => ApplicationView.displayMessage(err, 3000)) }, false, this) } if (this.deleteListener == undefined) { Model.getGlobule(this.blog.getDomain()).eventHub.subscribe(this.blog.getUuid() + "_blog_delete_event", uuid => this.deleteListener = uuid, evt => { // simplity remove it from it parent... if (this.parentNode) this.parentNode.removeChild(this) if (this.onclose) { this.onclose() } }, false, this) } // Here I will set the blog various information... let authorIdSpan = this.shadowRoot.querySelector("#blog-reader-author-id") authorIdSpan.innerHTML = blog.getAuthor() Account.getAccount(blog.getAuthor(), a => { let img = this.shadowRoot.querySelector("#blog-reader-author-blog-post") let ico = this.shadowRoot.querySelector("#blog-reader-author-icon") if (a.profilePicture_ != undefined) { img.src = a.profilePicture_ img.style.display = "block" ico.style.display = "none" } }, e => { console.log(e) }) this.shadowRoot.querySelector(".blog-reader-title").innerHTML = blog.getTitle() this.titleSpan.innerHTML = blog.getTitle() this.titleInput.value = blog.getTitle() this.subtitleInput.value = blog.getSubtitle() this.keywordsEditList.setValues(blog.getKeywordsList()) } getThumbnail(width, callback) { // Take the image from the editor... let dataUrl = this.shadowRoot.querySelector("globular-image-selector").getImageUrl() if(dataUrl.endsWith("/")){ dataUrl = "" // invalidate the given url... } if (dataUrl.length > 0) { callback(dataUrl) return } // Here I will create image let images = this.editorDiv.querySelectorAll("img") if (images.length > 0) { if (images.length == 1) { createThumbmailFromImage(images[0], width, dataUrl => { if(dataUrl.endsWith("/")){ dataUrl = "" // invalidate the given url... } this.shadowRoot.querySelector("globular-image-selector").setImageUrl(dataUrl) callback(dataUrl); }) } else { this.shadowRoot.querySelector("globular-image-selector").createMosaic(images, callback) } } else { let galleries = this.editorDiv.querySelectorAll("globular-image-gallery") if (galleries.length > 0) { // take the first images... let images = galleries[0].getImages() if (images.length == 1) { createThumbmailFromImage(images[0], width, dataUrl => { if(dataUrl.endsWith("/")){ dataUrl = "" // invalidate the given url... } this.shadowRoot.querySelector("globular-image-selector").setImageUrl(dataUrl) callback(dataUrl); console.log(dataUrl) }) }else{ this.shadowRoot.querySelector("globular-image-selector").createMosaic(images, callback) } } else { let embeddedVideos = this.editorDiv.querySelectorAll("globular-embedded-videos") if (embeddedVideos.length > 0) { // get all images from the first embeded video... let images = embeddedVideos[0].getImages() if (images.length == 1) { createThumbmailFromImage(images[0], width, dataUrl => { if(dataUrl.endsWith("/")){ dataUrl = "" // invalidate the given url... } this.shadowRoot.querySelector("globular-image-selector").setImageUrl(dataUrl) callback(dataUrl); }) } else { this.shadowRoot.querySelector("globular-image-selector").createMosaic(images, callback) } } else { let embeddedAudios = this.editorDiv.querySelectorAll("globular-embedded-audios") if (embeddedAudios.length > 0) { let images = embeddedAudios[0].getImages() // take the first images... if (images.length == 1) { createThumbmailFromImage(images[0], width, dataUrl => { if(dataUrl.endsWith("/")){ dataUrl = "" // invalidate the given url... } this.shadowRoot.querySelector("globular-image-selector").setImageUrl(dataUrl) callback(dataUrl); }) } else { this.shadowRoot.querySelector("globular-image-selector").createMosaic(images, callback) } } else { callback("") } } } } } /** * Display the blog in edit mode. * @param {*} callback */ edit(callback) { // Show the paper-card where the blog will be display this.shadowRoot.querySelector(".blog-post-editor-div").style.display = "" this.shadowRoot.querySelector(".blog-post-reader-div").style.display = "none" if (this.editorDiv != null) { if (this.blog != null) { if (this.blog.getTitle().length > 0) { this.shadowRoot.querySelector("#blog-editor-title").innerHTML = this.blog.getTitle() this.titleSpan.innerHTML = this.blog.getTitle() this.titleInput.value = this.blog.getTitle() this.subtitleInput.value = this.blog.getSubtitle() this.titleSpan.style.color = "var(--palette-text-primary)" this.shadowRoot.querySelector("#blog-editor-title").style.color = "var(--palette-text-primary)" } else { this.titleSpan.style.color = "var(--palette-action-disabled)" this.shadowRoot.querySelector("#blog-editor-title").style.color = "var(--palette-action-disabled)" this.shadowRoot.querySelector("#blog-editor-title").innerHTML = ` ${Application.account.name}, express yourself` } } callback(this.editorDiv) return } this.editorDiv = document.createElement("div") this.editorDiv.id = "_" + uuidv4() + "editorjs" this.editorDiv.slot = "edit-blog-content" this.editorDiv.style = "min-height: 230px;" this.appendChild(this.editorDiv) let data = {} if (this.blog != undefined) { data = JSON.parse(this.blog.getText()) if (this.blog.getTitle().length > 0) { this.shadowRoot.querySelector("#blog-editor-title").innerHTML = this.blog.getTitle() this.titleSpan.innerHTML = this.blog.getTitle() this.titleInput.value = this.blog.getTitle() this.subtitleInput.value = this.blog.getSubtitle() this.titleSpan.style.color = "var(--palette-text-primary)" this.shadowRoot.querySelector("#blog-editor-title").style.color = "var(--palette-text-primary)" } else { this.titleSpan.style.color = "var(--palette-action-disabled)" this.shadowRoot.querySelector("#blog-editor-title").style.color = "var(--palette-action-disabled)" this.shadowRoot.querySelector("#blog-editor-title").innerHTML = ` ${Application.account.name}, express yourself` } if (this.blog.getAuthor() != Application.account.getId() + "@" + Application.account.getDomain()) { ApplicationView.displayMessage("your not allowed to edit ", this.blog.getAuthor(), " post!", 3000) return } this.keywordsEditList.setValues(this.blog.getKeywordsList()) } const editor = this.editor = new EditorJS({ holder: this.editorDiv.id, autofocus: true, /** * Available Tools list. * Pass Tool's class or Settings object for each Tool you want to use * * linkTool: { class: LinkTool, config: { endpoint: 'http://localhost:8008/fetchUrl', // Your backend endpoint for url data fetching } }, */ tools: { header: Header, delimiter: Delimiter, quote: Quote, list: NestedList, checklist: { class: Checklist, inlineToolbar: true, }, table: Table, paragraph: { class: Paragraph, inlineToolbar: true, }, underline: Underline, code: CodeTool, raw: RawTool, embed: { class: Embed, inlineToolbar: false, config: { services: { youtube: true, coub: true, codepen: true, imgur: true, gfycat: true, twitchvideo: true, vimeo: true, vine: true, twitter: true, instagram: true, aparat: true, facebook: true, pinterest: true, } }, }, image: SimpleImage, files: FileDropZoneTool }, data: data, onReady: () => { new Undo({ editor }); new DragDrop(editor); } }); // Move the editor inside the this.editor.isReady .then(() => { /** Do anything you need after editor initialization */ this.editorDiv.querySelector(".codex-editor__redactor").style.paddingBottom = "0px"; /** done with the editor initialisation */ callback() }) .catch((reason) => { ApplicationView.displayMessage(`Editor.js initialization failed because of ${reason}`, 3000) }); } /** * Display the blog in read mode. * @param {*} callbak */ read(callback) { this.shadowRoot.querySelector(".blog-post-editor-div").style.display = "none" this.shadowRoot.querySelector(".blog-post-reader-div").style.display = "" // Here I will replace existing elements with new one... let elements = this.querySelectorAll(`[slot="read-only-blog-content"]`) for (var i = 0; i < elements.length; i++) { this.removeChild(elements[i]) } if (this.editor != null) { this.editor.save().then((outputData) => { let div = jsonToHtml(outputData) this.appendChild(div) callback() }) } else { let div = jsonToHtml(JSON.parse(this.blog.getText())) this.appendChild(div) callback() } } /** * Clear the content of blog read and writh editor */ clear() { this.blog = null this.removeChild(this.editorDiv); this.editorDiv = null; this.editor = null; this.titleInput.value = "" this.subtitleInput.value = "" this.titleSpan.innerHTML = ` ${Application.account.name}, express yourself` this.keywordsEditList.setValues([]) // reset the editor. this.edit(() => { }) } /** * Save the blog and publish it... */ publish() { this.editor.save().then((outputData) => { if (this.blog == null) { let globule = this.globule // see if the blog need domain... let rqst = new CreateBlogPostRequest rqst.setIndexpath(globule.config.DataPath + "/search/blogPosts") rqst.setAccountId(Application.account.getId() + "@" + Application.account.getDomain()) rqst.setText(JSON.stringify(outputData)); rqst.setLanguage(navigator.language.split("-")[0]) rqst.setTitle(this.titleInput.value) rqst.setSubtitle(this.subtitleInput.value) rqst.setKeywordsList(this.keywordsEditList.getValues()) // if the title is empty I will set it from the firt h1 found in the text... if (this.titleInput.value) if (this.titleInput.value.length == 0) { let h1 = this.editorDiv.querySelectorAll("h1") if (h1.length > 0) { rqst.setTitle(h1[0].innerHTML) } } // do the same with subtitle... if (this.subtitleInput.value) if (this.subtitleInput.value.length == 0) { let h2 = this.editorDiv.querySelectorAll("h2") if (h2.length > 0) { rqst.setSubtitle(h2[0].innerHTML) } } let createBlog = () => { generatePeerToken(globule, token => { globule.blogService.createBlogPost(rqst, { domain: Model.domain, application: Model.application, address: Model.address, token: token }) .then(rsp => { this.blog = rsp.getBlogPost(); ApplicationView.displayMessage("Your post is published!", 3000) // Publish the event Model.publish(Application.account.getId() + "@" + Application.account.getDomain() + "_publish_blog_event", this.blog.serializeBinary(), false) if (this.blog.getThumbnail().length > 0) { this.shadowRoot.querySelector("globular-image-selector").setImageUrl(this.blog.getThumbnail()) } }).catch(e => { ApplicationView.displayMessage(e, 3000) }) }, err => ApplicationView.displayMessage(err, 3000)) } if (this.blog) { if (this.blog.getThumbnail().length == 0) { this.getThumbnail(500, dataUrl => { rqst.setThumbnail(dataUrl); createBlog(); }) } else { createBlog(); } } else { this.getThumbnail(500, dataUrl => { rqst.setThumbnail(dataUrl); createBlog(); }) } } else { let rqst = new SaveBlogPostRequest let globule = this.globule rqst.setIndexpath(globule.config.DataPath + "/search/blogPosts") this.blog.setText(JSON.stringify(outputData)) this.blog.setLanguage(navigator.language.split("-")[0]) this.blog.setTitle(this.titleInput.value) this.blog.setSubtitle(this.subtitleInput.value) this.blog.setKeywordsList(this.keywordsEditList.getValues()) // Set the blogpost status... if (this.shadowRoot.querySelector("paper-radio-group").selected == "draft") { this.blog.setStatus(0) } else if (this.shadowRoot.querySelector("paper-radio-group").selected == "published") { this.blog.setStatus(1) } else if (this.shadowRoot.querySelector("paper-radio-group").selected == "archived") { this.blog.setStatus(2) } // set request parameters rqst.setUuid(this.blog.getUuid()) rqst.setBlogPost(this.blog) let saveBlog = () => { generatePeerToken(globule, token => { globule.blogService.saveBlogPost(rqst, { domain: Model.domain, application: Model.application, address: Model.address, token: token }) .then(rsp => { ApplicationView.displayMessage("Your post was updated!", 3000) // That function will update the blog... globule.eventHub.publish(this.blog.getUuid() + "_blog_updated_event", this.blog.getUuid(), false) if (this.blog.getThumbnail().length > 0) { this.shadowRoot.querySelector("globular-image-selector").setImageUrl(this.blog.getThumbnail()) } }).catch(e => { ApplicationView.displayMessage(e, 3000) }) }, err => ApplicationView.displayMessage(err, 3000)) } // set the thumbnail and save the blog... if (this.blog.getThumbnail().length == 0) { this.getThumbnail(500, dataUrl => { this.blog.setThumbnail(dataUrl); saveBlog() }) } else { saveBlog() } } }).catch((error) => { console.log('Saving failed: ', error) ApplicationView.displayMessage(`Saving failed: ${error}`, 3000) }); } } customElements.define('globular-blog-post', BlogPostElement) /** * Test if string is a valid json. * @param {*} str * @returns */ function isJson(str) { try { JSON.parse(str); } catch (e) { return false; } return true; } /** * Simple class to display a group of video files. (video must have video infos...) */ export class EmbeddedVideos extends HTMLElement { // attributes. // Create the applicaiton view. constructor() { super() // Set the shadow dom. this.attachShadow({ mode: 'open' }); } // The connection callback. connectedCallback() { // Innitialisation of the layout. this.shadowRoot.innerHTML = ` <style> #container{ background-color: var(--palette-background-paper); color: var(--palette-text-primary); display: flex; flex-direction: column; position: relative; } #videos{ display: flex; flex-wrap: wrap; } #play-all-btn { position:absolute; top: 0px; right: 0px; z-index: 100; } </style> <div id="container"> <paper-icon-button id="play-all-btn" title="play all video" icon="av:playlist-play"></paper-icon-button> <div id="videos"> <slot></slot> </div> </div> ` // Keep a reference to the videos. this.videos = [] this.playAllBtn = this.shadowRoot.querySelector("#play-all-btn") this.playAllBtn.onclick = (evt) => { playVideos(this.videos, "videos list") } } getVideo(index) { return this.videos[index] } removeVideo(video) { if (this.onremovevideo) { this.onremovevideo(video) } } setVideos(videos) { this.videos = videos this.innerHTML = "" if (videos.length > 4) { let carousel = new Carousel carousel.style.width = "100%" // put video info in the carousel. carousel.setItems(videos) // set the carousel... this.appendChild(carousel) } else { // Create video card's this.videos.forEach(video => { let card = new SearchVideoCard card.setVideo(video) card.onclose = () => { this.removeChild(card) this.removeVideo(video.file.path) } card.setEditable(this.editable) this.appendChild(card) }) } } getImages(){ let images = [] for (var i = 0; i < this.videos.length; i++) { let img = document.createElement("img") img.src = this.videos[i].getPoster().getContenturl() images.push(img) } return images; } // hide or show the edit button... setEditable(editable) { this.editable = editable; for (var i = 0; i < this.children.length; i++) { this.children[i].setEditable(editable) } } } customElements.define('globular-embedded-videos', EmbeddedVideos) /** * Simple class to display a group of audios files. */ export class EmbeddedAudios extends HTMLElement { // attributes. // Create the applicaiton view. constructor() { super() // Set the shadow dom. this.attachShadow({ mode: 'open' }); // Innitialisation of the layout. this.shadowRoot.innerHTML = ` <style> #container{ position: relative; background-color: var(--palette-background-paper); color: var(--palette-text-primary); display: flex; flex-direction: column; } #audios{ display: flex; flex-wrap: wrap; } #play-all-btn { position:absolute; top: 0px; right: 0px; z-index: 100; } </style> <div id="container"> <paper-icon-button id="play-all-btn" title="play all audio" icon="av:playlist-play"></paper-icon-button> <div id="audios"> <slot></slot> </div> </div> ` // Keep a reference to the audios. this.audi