class WTVAuthor { fs = require('fs'); path = require('path'); uuid = require('uuid'); ssid = null; minisrv_config = []; wtvshared = null; wtvclient = null; pageFileExt = ".page"; pagestore_dir = null; pageArr = []; blockArr = []; header = null; previewheader = null; titheader = null; tabstart = null; footerstart = null; webtvfooter = null; footerend = null; link = "#0000EE"; vlink = "#551A8B"; text = "#000000"; headcol = this.text listcol1 = null; listcol2 = null; stylemedia = []; headerimgL = null; headerimgLheight = null; headerimgLwidth = null; afterblock1 = null; debug = require('debug')('WTVAuthor') constructor(minisrv_config, wtvclient) { if (!minisrv_config) throw ("minisrv_config required"); if (!wtvclient) throw ("WTVClientSessionData required"); var WTVShared = require('./WTVShared.js')['WTVShared']; var WTVMime = require('./WTVMime.js'); this.minisrv_config = minisrv_config; this.wtvshared = new WTVShared(minisrv_config); this.wtvmime = new WTVMime(minisrv_config); this.wtvclient = wtvclient; this.ssid = wtvclient.ssid; this.pageArr = this.pageArr; this.blockArr = this.blockArr; } checkPageIntroSeen() { return (this.wtvclient.getSessionData("subscriber_page_intro_seen")) ? this.wtvclient.getSessionData("subscriber_page_intro_seen") : false; } setPageIntroSeen(seen) { this.wtvclient.setSessionData("subscriber_page_intro_seen", (seen) ? true : false); } pagestoreExists() { if (!this.isguest) { if (this.pagestore_dir === null) { // set pagestore directory local var so we don't call the function every time var userstore_dir = this.wtvclient.getUserStoreDirectory(); this.debug("pagestoreExists", "userstore_dir", userstore_dir) // PageStore var store_dir = "PageStore" + this.path.sep; this.pagestore_dir = userstore_dir + store_dir; } return this.fs.existsSync(this.pagestore_dir); } return true; } createPagestore() { if (this.pagestoreExists() === false) { try { if (!this.fs.existsSync(this.pagestore_dir)) this.fs.mkdirSync(this.pagestore_dir, { recursive: true }); return true; } catch { } } return false; } createPage(style) { this.pagestoreExists() var pagestorepath = this.pagestore_dir; // All this shit is to work around the part where I don't use UUIDs to store pages, which is bad var pages = this.fs.readdirSync(pagestorepath) if (pages.length == 0) { pagenum = 0; } else { var pagelen = pages.length; if (pagelen < 0) pagelen = 0; this.debug("createPage","pages",pages) var pagenums = []; for(let i = 0; i < pagelen; i++) { var toarr = pages[i].substr(0, pages[i].indexOf('.')); pagenums.push(parseInt(toarr)); } pagenums = pagenums.sort() this.debug("createPage", "pagenums", pagenums) var pagenum = parseInt(pagenums[pagelen - 1]); this.debug("createPage", "pagenum", pagenum) this.debug("createPage", "pagelen", pagelen) } if (pages.length == 0) { pagenum = 0 var pagefile = pagenum + this.pageFileExt; } else { var pagefile = (pagenum + 1) + this.pageFileExt; } var pagefileout = this.pagestore_dir + pagefile; // JSON data structure var pagedata = { "style": style, "title": "(Untitled)", "description": "(no description)", "pagebreaks": [], "showtitle": true, "inlist": true, "published": false, "publishdate": null, "publishname": null, "blocks": [] } if (this.fs.existsSync(pagefileout)) { console.error(" * ERROR: Page already exists (should never happen). Page lost."); return false; } // Encode page data into JSON var returnval = pages.length var result = this.fs.writeFileSync(pagefileout, JSON.stringify(pagedata)); if (returnval != 0) { var npages = this.fs.readdirSync(pagestorepath) var returnval = npages.length - 1; } return returnval; } loadPage(pagenum) { this.pagestoreExists() var page_file = this.listPages(); var page_data_raw = page_file[pagenum]; if (page_data_raw) { return page_data_raw; } return false; } setStyle(style, title, desc, state, docName) { // There's probably a better way to do this involving external files for each style, but no this.debug("setStyle", "this.wtvshared.makeSafeStringPath(style) (before load)", this.wtvshared.makeSafeStringPath(style)); var template_data_file = this.wtvshared.getTemplate("wtv-author", "styles/" + this.wtvshared.makeSafeStringPath(style) + ".js", true); if (template_data_file) { this.debug("setStyle", "template_data_file", template_data_file); const PBTemplate = require(template_data_file); var pbtemplate = new PBTemplate(this, title, desc, state, docName); var template_data = pbtemplate.get(); var self = this; Object.keys(template_data).forEach((k) => { self[k] = template_data[k]; }) this.debug("setStyle", "this.stylemedia", this.stylemedia); } } generateBlock(number, page, state) { var block; var pagedata = this.loadPage(page) var thisblock = pagedata.blocks[number] var type = thisblock.type var numofblocks = pagedata.blocks.length // Generate HTML for each type of block switch(type) { case "text": if (state == "editing") { block = `` } else { block = `
` } block += `
` block += "" + thisblock.title + "" if (thisblock.caption) block += "

" else block += ">" if (thisblock.style) block += "<" + thisblock.style + ">" block += thisblock.caption + "

" if (thisblock.style) block += "" block += "
" break; case "snapshot": if (state == "publishing") { block = this.generatePublishImageBlock(number, page) break; } else { block = `` if (state == "editing") { block += `` } else { block += `
` } block += `` block += `
` if (thisblock.title) block += `

${thisblock.title}

` block += `
` if (state == "editing" && thisblock.caption == null || state == "editing" && thisblock.caption.length == 0) block += "Choose this to change the image or add an optional caption." else if (thisblock.caption != null) block += `${thisblock.caption}` block += `

 

` break; } case "clipart": if (state == "publishing") { block = this.generatePublishImageBlock(number, page) break; } else { block = `

` if (state == "editing") block += `` else block += `
` if (thisblock.title) block += `` block += `` if (state == "editing") block += `

${thisblock.title}

` else block += "" block += `
` if (state == "editing" && thisblock.caption == null || state == "editing" && thisblock.caption.length == 0) block += "Choose this to change the image or add an optional caption." else if (thisblock.caption != null) block += `${thisblock.caption}` block += `

` break; } case "heading": block = `

` if (state == "editing") block += `` else block += `
` block += `` if (this.headerimgL) { block += `` } block += `
` if (thisblock.dividerBefore == "on") block += `


` block += `<${thisblock.size}>${thisblock.text}` if (thisblock.dividerAfter == "on") block += `


` block += `

` break; case "list": block = `
` if (state == "editing") block += `` else block += `
` block += `` for (let i = 0; i < thisblock.items.length; i++) { block += `` if (this.listcol1 != null) { if(i % 2 == 0) { block += `` } block += `

${thisblock.title}

` } else { if (this.listcol2 != null) { block += `` } else { block += `` } } } else { block += `` } block += `
  • ${thisblock.items[i]}
` break; case "link": block = `
` if (state == "editing") block += `` else block += `
` block += `` for (let i = 0; i < thisblock.items.length; i++) { block += `` if (this.listcol1 != null) { if(i % 2 == 0) { block += `` } block += `

${thisblock.title}

` } else { if (this.listcol2 != null) { block += `` } else { block += `` } } } else { block += `` } block += `
` break; default: block = "" break; } return block; } // Separate function for generating image blocks when publishing because there is no possibly better way to do this generatePublishImageBlock(number, page) { var block; var destDir = this.getPublishDir(); var pagedata = this.loadPage(page) var thisblock = pagedata.blocks[number] var type = thisblock.type var numofblocks = pagedata.blocks.length switch(type) { case "snapshot": block = `

` if (thisblock.title) block += `` block += `

${thisblock.title}

` block += `
` if (thisblock.caption != null) block += `${thisblock.caption}` block += `

` this.fs.writeFile(destDir + this.wtvclient.session_store.subscriber_username + '/' + pagedata.publishname + '/media/captureA' + number + '.jpg', new Buffer.from(thisblock.photo, 'base64'), err => { if (err) { console.error(err); } // file written successfully }); break; case "clipart": this.fs.mkdirSync(destDir + this.wtvclient.session_store.subscriber_username + '/' + pagedata.publishname + "/" + btoa(thisblock.photo).substr(0, btoa(thisblock.photo).lastIndexOf("/")), { recursive: true }) this.fs.copyFile('includes/ServiceVault/wtv-author/' + btoa(thisblock.photo), destDir + this.wtvclient.session_store.subscriber_username + '/' + pagedata.publishname + "/" + btoa(thisblock.photo), (err) => { if (err) throw err; }); block = `

` if (thisblock.title) block += `` block += `

${thisblock.title}

` if (thisblock.caption != null) block += `${thisblock.caption}` block += `

` break; } return block; } generatePage(state, pagenum, page) { var pagedata = this.loadPage(pagenum); var title // Should probably have a better way to know if the page has no title if (pagedata.title == "(Untitled)" && state == "editing") title = "Choose this to add a title to your document" else title = pagedata.title // Set the page style with too many paramaters this.setStyle(pagedata.style, title, pagedata.description, state, pagenum); var html = this.header if (page == 1) { if (pagedata.showtitle == true){ html += this.titheader } } html += this.tabstart // This generates blocks on separate pages in the most neat and optimized way possible (i think, i hate past jar) if (page != 1) { for (let i = pagedata.pagebreaks[page - 2]; i < (pagedata.pagebreaks[page - 1] || pagedata.blocks.length); i++) { var type = pagedata.blocks[i].type html += this.generateBlock(i, pagenum, state) } } else if (pagedata.pagebreaks.length != 0){ for (let i = 0; i < pagedata.pagebreaks[0]; i++) { var type = pagedata.blocks[i].type html += this.generateBlock(i, pagenum, state) if (this.afterblock1 && i == 0) { html += this.afterblock1 } } } else { for (let i = 0; i < pagedata.blocks.length; i++) { var type = pagedata.blocks[i].type html += this.generateBlock(i, pagenum, state) if (this.afterblock1 && i == 0) { html += this.afterblock1 } } } html += this.footerstart // Add the footer if we're not in edit mode if (state != "editing") if (page == 1 && pagedata.pagebreaks.length != 0) { html += `
Powered by ${this.minisrv_config.config.service_name}
next page
` } else if (page != 1 && pagedata.pagebreaks.length + 1 == parseInt(page)) { html += `
previous page
Powered by ${this.minisrv_config.config.service_name}

` } else if (page != 1 && pagedata.pagebreaks.length + 1 > parseInt(page)) { html += `
previous page
Powered by ${this.minisrv_config.config.service_name}
next page
` } else { html += `
Powered by ${this.minisrv_config.config.service_name}

` } html += this.footerend return html; } editPage(pagedata, pagenum, callPublish = true) { // just stolen from favorites lmao var pageout = new Object(); var pagepath = this.pagestore_dir; Object.assign(pageout, pagedata); var pagestorepath = this.pagestore_dir; var pages = this.fs.readdirSync(pagestorepath) var page = pages[pagenum] var result = this.fs.writeFileSync(pagepath + page, JSON.stringify(pageout)); if (pagedata.published == true && callPublish) { this.publishPage(pagenum, pagedata.inlist, false) } if (!result) return false; } editMetadata(title, description, showtitle, pagenum) { var pagedata = this.loadPage(pagenum); if (!pagedata) return false; if (showtitle == "true") var showtitle2 = false else var showtitle2 = true pagedata.title = title pagedata.description = description pagedata.showtitle = showtitle2 this.editPage(pagedata, pagenum); return true; } listPages() { // i don't remember why, but i'm pretty sure this function sucks var pagestore = this.pagestoreExists(); if (!pagestore) this.createPagestore(); var userstore_dir = this.wtvclient.getUserStoreDirectory(); // PageStore var store_dir = "PageStore" + this.path.sep; this.pagestore_dir = userstore_dir + store_dir; var pagestorepath = this.pagestore_dir; var self = this; self.pageArr = []; var files = this.fs.readdirSync(pagestorepath) this.debug("listPages","files",files) files.map(function (v) { // oh yeah it's because any non-JSON file in pagestore will throw an error and break everything var page_data_raw = null; var pagepath = pagestorepath + self.path.sep + v; if (self.fs.existsSync(pagepath)) page_data_raw = self.fs.readFileSync(pagepath); if (page_data_raw) { var page_data = JSON.parse(page_data_raw); self.pageArr.push(page_data); } }) return self.pageArr; } deleteBlock(pagenum, position) { var pagedata = this.loadPage(pagenum); if (!pagedata) return false; var block = pagedata.blocks[position] var blocks = pagedata.blocks blocks.splice(position, 1); this.editPage(pagedata, pagenum); if (block.type == "break") this.generateBreakList(pagenum) return true; } getPublishDomain() { if (this.minisrv_config.services['wtv-author'].public_domain) { return this.minisrv_config.services['wtv-author'].public_domain; } else { if (this.minisrv_config.services['wtv-author'].publish_mode == "service") { var target_service = this.minisrv_config.services[this.minisrv_config.services['wtv-author'].publish_dest]; if (target_service) { return target_service.host + ":" + target_service.port; } } else { return this.minisrv_config.services['wtv-author'].host + ":" + this.minisrv_config.services['wtv-author'].port; } } } getPublishDir() { var destDir = false; if (this.minisrv_config.services['wtv-author'].publish_mode == "service") { var target_service = this.minisrv_config.services[this.minisrv_config.services['wtv-author'].publish_dest]; if (target_service) { if (!target_service.pc_services) { console.error("Invalid service configuration: publish_dest is not a pc service."); return false; } if (!target_service.servicevault_dir) { target_service.servicevault_dir = this.minisrv_config.services['wtv-author'].publish_dest; } if (target_service.service_vaults) { destDir = target_service.service_vaults[0] + this.path.sep + target_service.servicevault_dir + this.path.sep; } else { destDir = minisrv_config.config.ServiceVaults[0] + this.path.sep + target_service.servicevault_dir + this.path.sep; } } } else if (this.minisrv_config.services['wtv-author'].publish_mode == "directory") { destDir = this.minisrv_config.services['wtv-author'].publish_dest; } else { console.error("Invalid service configuration: invalid publish_mode."); return false; } return destDir; } unpublishPage(pagenum) { var pagedata = this.loadPage(pagenum) var destDir = this.getPublishDir(); if (pagedata.published != true) { return "This page is not published." } var publishname = pagedata.publishname; if (this.fs.existsSync(destDir + this.wtvclient.session_store.subscriber_username + '/' + publishname)) { try { this.fs.rmdirSync(destDir + this.wtvclient.session_store.subscriber_username + '/' + publishname, { recursive: true }) pagedata.published = false; this.editPage(pagedata, pagenum, false); this.generatePageList() return true; } catch { } } return false; } publishPage(pagenum, listpublicly) { // this was done in a rush and probably also sucks // remember to increment the "hours wasted here" comment at the top of the file var pagedata = this.loadPage(pagenum) var destDir = this.getPublishDir(); var publishname = null; if (pagedata.published != true) { publishname = pagedata.title.substring(0, 50).replaceAll(" ", "").replace(/[^A-Za-z0-9]/g, "-"); pagedata.publishname = publishname; this.editPage(pagedata, pagenum); if (this.fs.existsSync(destDir + this.wtvclient.session_store.subscriber_username + '/' + publishname)) { return "You already have a published page with a name similar to this one. Try changing the name of this page and try again."; } } else { publishname = pagedata.publishname; } pagedata.publishname = publishname; this.fs.mkdirSync(destDir + this.wtvclient.session_store.subscriber_username + '/' + publishname, { recursive: true }) this.fs.mkdirSync(destDir + this.wtvclient.session_store.subscriber_username + '/' + publishname + "/clipart/styleMedia/", { recursive: true }) this.fs.mkdirSync(destDir + this.wtvclient.session_store.subscriber_username + '/' + publishname + "/media/", { recursive: true }) for (let i = 1; i < pagedata.pagebreaks.length + 2; i++) { var pagehtml = this.generatePage("publishing", pagenum, i) if (i == 1) var fileout = "index.html" else var fileout = "page" + i + ".html" this.fs.writeFile(destDir + this.wtvclient.session_store.subscriber_username + '/' + publishname + '/' + fileout, pagehtml, err => { if (err) { console.error(err); } // file written successfully }); } for (let i = 0; i < this.stylemedia.length; i++) { this.fs.mkdirSync(destDir + this.wtvclient.session_store.subscriber_username + '/' + publishname + this.stylemedia[i].substr(0, this.stylemedia[i].lastIndexOf("/")), { recursive: true }) this.fs.copyFile('includes/ServiceVault/wtv-author' + this.stylemedia[i], destDir + this.wtvclient.session_store.subscriber_username + '/' + publishname + this.stylemedia[i], (err) => { if (err) throw err; }); } var strftime = require('strftime'); pagedata.publishdate = strftime("%a, %b %d, %Y, %I:%M%P", new Date(new Date().toUTCString())) pagedata.published = true; pagedata.inlist = listpublicly; pagedata.publishname = publishname; this.editPage(pagedata, pagenum, false); this.generatePageList() return true; } generatePageList() { // this one's pretty ok i think, but it should have screenshots of each page var pagelist = this.listPages() var html = ` ${this.wtvclient.session_store.subscriber_name}
Pages of ${this.wtvclient.session_store.subscriber_name}
`; loop: for (let i = 0; i < pagelist.length; i++) { if (pagelist[i].published == true && pagelist[i].inlist == true) { html += `
${pagelist[i].title}
${pagelist[i].description}
` } else { continue loop; } } html += `
` this.fs.writeFile(this.getPublishDir() + this.wtvclient.session_store.subscriber_username + '/index.html', html, err => { if (err) { console.error(err); } // file written successfully }); } deletePage(pagenum) { // i hate fs operations var pagestore = this.pagestoreExists() var userstore_dir = this.wtvclient.getUserStoreDirectory(); this.debug("deletePage","userstore_dir",userstore_dir) // PageStore var store_dir = "PageStore" + this.path.sep; this.pagestore_dir = userstore_dir + store_dir; var pagestorepath = this.pagestore_dir; var page_file = this.fs.readdirSync(pagestorepath) var page_file_out = page_file[pagenum] this.unpublishPage(pagenum); this.fs.unlinkSync(this.pagestore_dir + page_file_out, { recursive: true }); } // these totally couldn't have been made into one function nah that's impossible createTextBlock(pagenum, title, caption, size, style, position) { var pagedata = this.loadPage(pagenum); if (!pagedata) return false; var newblock = { "type": "text", "title": title, "caption": caption, "size": size, "style": style } var blocks = pagedata.blocks blocks.splice(position, 0, newblock); this.editPage(pagedata, pagenum); return true; } editTextBlock(pagenum, title, caption, size, style, position, oldposition) { var pagedata = this.loadPage(pagenum); if (!pagedata) return false; var newblock = { "type": "text", "title": title, "caption": caption, "size": size, "style": style } pagedata.blocks[oldposition].title = title pagedata.blocks[oldposition].caption = caption pagedata.blocks[oldposition].size = size pagedata.blocks[oldposition].style = style if (oldposition != position) moveArrayKey(pagedata.blocks,oldposition,position); this.editPage(pagedata, pagenum); return true; } createPhotoBlock(pagenum, photo, type) { var pagedata = this.loadPage(pagenum); if (!pagedata) return false; var base64photo = new Buffer.from(photo).toString('base64') var newblock = { "type": type, "title": null, "caption": null, "photo": base64photo } var blocks = pagedata.blocks blocks.push(newblock); this.editPage(pagedata, pagenum); return true; } editPhotoBlock(pagenum, oldposition, position, photo, type, title, caption) { var pagedata = this.loadPage(pagenum); if (!pagedata) return false; var blocks = pagedata.blocks if (photo != null) { var base64photo = new Buffer.from(photo).toString('base64') blocks[oldposition].photo = base64photo } if (type != null) { blocks[oldposition].type = type } if (title != null) blocks[oldposition].title = title if (caption != null) blocks[oldposition].caption = caption if (oldposition != position) { moveArrayKey(blocks, oldposition,position); } this.editPage(pagedata, pagenum); return true; } createHeaderBlock(pagenum, text, size, dividerBefore, dividerAfter, position) { var pagedata = this.loadPage(pagenum); if (!pagedata) return false; var newblock = { "type": "heading", "text": text, "size": size, "dividerBefore": dividerBefore, "dividerAfter": dividerAfter } var blocks = pagedata.blocks blocks.splice(position, 0, newblock); this.editPage(pagedata, pagenum); return true; } editHeaderBlock(pagenum, text, size, dividerBefore, dividerAfter, position, oldposition) { var pagedata = this.loadPage(pagenum); if (!pagedata) return false; var newblock = { "type": "heading", "text": text, "size": size, "dividerBefore": dividerBefore, "dividerAfter": dividerAfter } pagedata.blocks[oldposition].text = text pagedata.blocks[oldposition].size = size pagedata.blocks[oldposition].dividerBefore = dividerBefore pagedata.blocks[oldposition].dividerAfter = dividerAfter if (oldposition != position) moveArrayKey(pagedata.blocks, oldposition,position); this.editPage(pagedata, pagenum); return true; } createListBlock(pagenum, title, items, position) { var pagedata = this.loadPage(pagenum); if (!pagedata) return false; var newblock = { "type": "list", "title": title, "items": items } var blocks = pagedata.blocks blocks.splice(position, 0, newblock); this.editPage(pagedata, pagenum); return true; } editListBlock(pagenum, title, items, position, oldposition) { var pagedata = this.loadPage(pagenum); if (!pagedata) return false; var newblock = { "type": "list", "title": title, "items": items } pagedata.blocks[oldposition].title = title pagedata.blocks[oldposition].items = items if (oldposition != position) moveArrayKey(pagedata.blocks,oldposition,position); this.editPage(pagedata, pagenum); return true; } createLinkBlock(pagenum, title, listItems, linkItems, position) { var pagedata = this.loadPage(pagenum); if (!pagedata) return false; var items = []; loop: for (let i = 0; i < linkItems.length; i++) { var url = linkItems[i] var name = listItems[i] if (url == "http://") { continue loop; } else { var subblock = { "name": name, "url": url } items.push(subblock) } } var newblock = { "type": "link", "title": title, "items": items } var blocks = pagedata.blocks blocks.splice(position, 0, newblock); this.editPage(pagedata, pagenum); return true; } editLinkBlock(pagenum, title, listItems, linkItems, position, oldposition) { var pagedata = this.loadPage(pagenum); if (!pagedata) return false; var items = []; loop: for (let i = 0; i < linkItems.length; i++) { var url = linkItems[i] var name = listItems[i] if (url == "http://") { continue loop; } else { var subblock = { "name": name, "url": url } items.push(subblock) } } pagedata.blocks[oldposition].title = title pagedata.blocks[oldposition].items = items if (oldposition != position) moveArrayKey(pagedata.blocks,oldposition,position); this.editPage(pagedata, pagenum); return true; } createBreakBlock(pagenum, position) { var pagedata = this.loadPage(pagenum); if (!pagedata) return false; var newblock = { "type": "break" } var blocks = pagedata.blocks blocks.splice(position, 0, newblock); this.editPage(pagedata, pagenum); this.generateBreakList(pagenum); return true; } editBreakBlock(pagenum, position, oldposition) { var pagedata = this.loadPage(pagenum); if (!pagedata) return false; if (oldposition != position) moveArrayKey(pagedata.blocks,oldposition,position); this.editPage(pagedata, pagenum); this.generateBreakList(pagenum); return true; } generateBreakList(pagenum) { var pagedata = this.loadPage(pagenum) var breaks = []; for (let i = 0; i < pagedata.blocks.length; i++) { var type = pagedata.blocks[i].type if (type == "break") breaks.push(i) } pagedata.pagebreaks = breaks; this.editPage(pagedata, pagenum); } getCommonFooter() { return `
Powered by ${this.minisrv_config.config.service_name}

`; } } module.exports = WTVAuthor;