TWPUB Epub Plugin - Convert EPUB Files to TiddlyWiki Plugins
//u002f//u002a//nClass representing a twpub plugin//n//u002a//u002f//n//nconst {cleanStylesheet,cleanStyleAttribute} = require(/'//u002f//u002ftransform-stylesheets/'),//n//t{flattenTree} = require(/'//u002f//u002fflatten-tree/'),//n//t{hash} = require(/'//u002f//u002futils/');//n//nconst URL_PREFIX = /'https://u002f//u002fexample.com//u002f/';//n//nclass TwpubPlugin {//n//n//tconstructor (app,options) {//n//t//tthis.app = app;//n//t//tthis.epubReader = options.epubReader;//n//t//tthis.fields = {}; //u002f//u002f Fields of the plugin tiddler itself//n//t//tthis.tiddlers = {}; //u002f//u002f Payload tiddlers//n//t//tthis.errors = []; //u002f//u002f Array of conversion errors//n//t}//n//n//tlogError(message) {//n//t//tthis.errors.push(message);//n//t}//n//n//tconvertEpub() {//n//t//t//u002f//u002f Get the hash of the epub//n//t//tthis.hash = this.epubReader.epubHash.slice(0,16);//n//t//t//u002f//u002f Construct the title of the plugin//n//t//tthis.titlePrefix = /'$//u003a//u002fplugins//u002ftwpub//u002f/' + this.hash;//n//t//t//u002f//u002f For text chunks, make a map of href (including anchor) to title//n//t//tthis.createAnchorToTitleMapping();//n//t//t//u002f//u002f Convert the text, TOC, stylesheets and images into tiddlers//n//t//tthis.convertText();//n//t//tthis.convertToc();//n//t//tthis.convertStylesheets();//n//t//tthis.convertImages();//n//t//t//u002f//u002f Setup the fields of the plugin tiddler//n//t//tthis.fields.list = /'readme errors cover text/';//n//t//tthis.fields.version = /'v0.0.1/';//n//t//tthis.fields[/'plugin-type/'] = /'plugin/';//n//t//tthis.fields.type = /'application//u002fjson/';//n//t//tthis.fields[/'epub-title/'] = this.epubReader.getMetadataItem(/'dc:title/',/'(Untitled)/');//n//t//tthis.fields.name = /'TWPUB/';//n//t//tthis.fields.title = this.titlePrefix;//n//t//tthis.fields.description = this.fields[/'epub-title/'];//n//t//tthis.fields[/'converter-version/'] = this.app.version.toString();//n//t//tthis.fields[/'epub-creator/'] = this.epubReader.getMetadataItem(/'dc:creator/',/'(Unknown)/');//n//t//tthis.fields[/'conversion-errors/'] = this.errors.length.toString();//n//t//tthis.fields[/'count-chunks/'] = this.epubReader.chunks.length.toString();//n//t//tthis.fields[/'count-images/'] = Object.keys(this.epubReader.images).length.toString();//n//t//t//u002f//u002f Cover tab//n//t//tif(this.epubReader.hasMetadataItem(/'cover/')) {//n//t//t//tconst href = this.epubReader.getManifestItem(this.epubReader.getMetadataItem(/'cover/')).href;//n//t//t//tif(href) {//n//t//t//t//tthis.fields[/'cover-image/'] = this.titlePrefix + /'/images//u002f/' + this.epubReader.getManifestItem(this.epubReader.getMetadataItem(/'cover/')).href;//n//t//t//t//tthis.addTiddler({//n//t//t//t//t//ttitle: this.titlePrefix + /'/cover/',//n//t//t//t//t//ttype: /'text//u002fvnd.tiddlywiki/',//n//t//t//t//t//ttext: /'<$transclude tiddler=///'///' + this.titlePrefix + /'///' subtiddler=///'///' + this.fields[/'cover-image/'] + /'///'//u002f>/' //n//t//t//t//t});//n//t//t//t}//n//t//t}//n//t//t//u002f//u002f Readme tab//n//t//tthis.addTiddler({//n//t//t//ttitle: this.titlePrefix + /'/readme/',//n//t//t//ttype: /'text//u002fvnd.tiddlywiki/',//n//t//t//ttext: [/'epub-title/',//n//t//t//t//t//t/'epub-creator/',//n//t//t//t//t//t/'converter-version/',//n//t//t//t//t//t/'conversion-errors/',//n//t//t//t//t//t/'count-chunks/',//n//t//t//t//t//t/'count-images/'//n//t//t//t//t].filter(field => field in this.fields).map(field => |${field} |''${this.fields[field]}'' |).join(/'//n/')//n//t//t});//n//t//t//u002f//u002f Errors tab//n//t//tthis.addTiddler({//n//t//t//ttitle: this.titlePrefix + /'/errors/',//n//t//t//ttype: /'text//u002fvnd.tiddlywiki/',//n//t//t//ttext: this.epubReader.errors.concat(this.errors).map(message => /'# /' + message + /'//n/').join(/'//n/') || /'None/'//n//t//t});//n//t//t//u002f//u002f Full text tab//n//t//tthis.addTiddler({//n//t//t//ttitle: this.titlePrefix + /'/text/',//n//t//t//ttype: /'text//u002fvnd.tiddlywiki/',//n//t//t//ttext: //u005cdefine link-actions()//n<$action-sendmessage $message=///'tm-scroll///' selector={{{ [<navigateTo>escapecss[]addprefix[#]] }}}//u002f>//n//u005cend//n//n<$linkcatcher actions=<<link-actions>>>//n//n<div class=///'tc-table-of-contents///'>//n<<toc /'${this.titlePrefix}//u002ftoc/'>//n</div>//n//n<$list filter=///'[all[tiddlers+shadows]prefix[${this.titlePrefix}//u002ftext//u002f]sort[]]///'>//n<a id=<<currentTiddler>>>//n<$transclude mode=///'inline///'//u002f>//n</a>//n</$list>//n//n</$linkcatcher>//n//t//t});//n//t}//n//n//tcreateAnchorToTitleMapping() {//n//t//tthis.mapAnchorToTitle = Object.create(null);//n//t//tthis.epubReader.chunks.forEach((chunk,index) => {//n//t//t//tconst title = this.makeTextTiddlerTitle(index);//n//t//t//t//u002f//u002f If we've not seen the file before, add a mapping for the file itself, without an anchor ID//n//t//t//tif(!this.mapAnchorToTitle[chunk.href]) {//n//t//t//t//tthis.mapAnchorToTitle[chunk.href] = title;//n//t//t//t}//n//t//t//t//u002f//u002f Add mappings for each anchor ID//n//t//t//tchunk.anchorIds.forEach(anchorId => {//n//t//t//t//tif(!this.mapAnchorToTitle[chunk.href + /'#/' + anchorId]) {//n//t//t//t//t//tthis.mapAnchorToTitle[chunk.href + /'#/' + anchorId] = title; //n//t//t//t//t}//n//t//t//t});//n//t//t});//n//t}//n//n//tmakeTextTiddlerTitle(index) {//n//t//treturn this.titlePrefix + /'/text//u002f/' + (///'///' + index).padStart(9,/'0/');//n//t}//n//n//tconvertText() {//n//t//tthis.epubReader.chunks.forEach((chunk,index) => {//n//t//t//t//u002f//u002f Construct the title for this chunk//n//t//t//tconst title = this.makeTextTiddlerTitle(index);//n//t//t//t//u002f//u002f Collect the scoping classes to be wrapped around this chunk//n//t//t//tconst scopingClasses = chunk.stylesheetIds.map(id => this.makeStylesheetScopeClass(id));//n//t//t//t//u002f//u002f Process some elements and attributes to wikitext//n//t//t//tthis.processTextChunk(chunk);//n//t//t//t//u002f//u002f Flatten the nodes to text//n//t//t//tconst flatText = flattenTree(chunk.nodes);//n//t//t//t//u002f//u002f Add the tiddler//n//t//t//tthis.addTiddler({//n//t//t//t//trole: /'text/',//n//t//t//t//ttitle: title,//n//t//t//t//ttype: /'text//u002fvnd.twpub/',//n//t//t//t//ttext: /'<div class=///'///' + scopingClasses.join(/' /') + /'///'>/' + flatText + /'
Missing TOC link to /${target}/ from /${chunk.href}/``);//n//t//t//t//t//t//t//t//t//t}//n//t//t//t//t//t//t//t} else {//n//t//t//t//t//t//t//t//t//u002f//u002f It's an external link//n//t//t//t//t//t//t//t//tnode.attributes.rel = /'noopener noreferrer/';//n//t//t//t//t//t//t//t//tnode.attributes.target = /'_blank/';//n//t//t//t//t//t//t//t}//n//t//t//t//t//t//t}//n//t//t//t//t//t//tbreak;//n//t//t//t//t//t}//n//t//t//t//t}//n//t//t//t//tvisitNodes(node.childNodes);//n//t//t//t},//n//t//t//tvisitNodes = childNodes => {//n//t//t//t//tchildNodes = childNodes || [];//n//t//t//t//tfor(const childNode of childNodes) {//n//t//t//t//t//tvisitNode(childNode);//n//t//t//t//t}//n//t//t//t};//n//t//tvisitNodes(chunk.nodes);//n//t}//n//n//tconvertToc() {//n//t//tconst visitNodes = (nodes,tag) => {//n//t//t//tconst titles = [];//n//t//t//tnodes.forEach(node => {//n//t//t//t//ttitles.push(visitNode(node,tag));//n//t//t//t});//n//t//t//treturn titles;//n//t//t};//n//t//tconst visitNode = (node,tag) => {//n//t//t//tconst title = this.titlePrefix + /'/toc//u002f/' + node.id;//n//t//t//tconst childTitles = visitNodes(node.children,title);//n//t//t//tvar target = node.href;//n//t//t//tif(target.charAt(0) === /'//') {//n//t//t//t//ttarget = target.slice(1);//n//t//t//t}//n//t//t//tconst targetTitle = this.mapAnchorToTitle[target];//n//t//t//tif(!targetTitle) {//n//t//t//t//tconsole.log(/'Missing link to/',target)//n//t//t//t}//n//t//t//tthis.addTiddler({//n//t//t//t//ttitle: title,//n//t//t//t//tcaption: node.text,//n//t//t//t//ttags: tag,//n//t//t//t//ttarget: targetTitle,//n//t//t//t//tlist: stringifyList(childTitles),//n//t//t//t//trole: /'toc/'//n//t//t//t});//n//t//t//treturn title;//n//t//t};//n//t//tvisitNodes(this.epubReader.toc,this.titlePrefix + /'/toc/');//n//t}//n//n//tmakeStylesheetScopeClass(id) {//n//t//treturn /'twpub-/' + this.hash + /'-/' + id;//n//t}//n//n//tconvertStylesheets() {//n//t//tconst scopingClasses = Object.keys(this.epubReader.stylesheets).map(id => this.makeStylesheetScopeClass(id)),//n//t//t//tmakeSelectors = target => scopingClasses.map(className => /'./' + className + /' /' + target).join(/',/'),//n//t//t//tcleanText = [];//n//t//tcleanText.push(//n//t//t//t${makeSelectors(/'blockquote/')} {//n//t//t//t//tborder-color: initial;//n//t//t//t//tborder-style: initial;//n//t//t//t//tmargin: initial;//n//t//t//t//tpadding: initial;//n//t//t//t//tquotes: initial;//n//t//t//t}//n//t//t);//n//t//tfor(const id in this.epubReader.stylesheets) {//n//t//t//tcleanText.push(cleanStylesheet(this.epubReader.stylesheets[id],this.makeStylesheetScopeClass(id)));//n//t//t};//n//t//tthis.addTiddler({//n//t//t//trole: /'stylesheet/',//n//t//t//ttitle: this.titlePrefix + /'/stylesheets/',//n//t//t//ttype: /'text//u002fcss/',//n//t//t//ttags: /'$//u003a//u002ftags//u002fStylesheet/',//n//t//t//ttext: cleanText.join(/'//n/')//n//t//t});//n//t}//n//n//tconvertImages() {//n//t//tfor(const imagePath in this.epubReader.images) {//n//t//t//tconst imageInfo = this.epubReader.images[imagePath];//n//t//t//tthis.addTiddler({//n//t//t//t//trole: /'image/',//n//t//t//t//ttitle: this.titlePrefix + /'/images//u002f/' + imagePath,//n//t//t//t//ttype: imageInfo.type,//n//t//t//t//ttext: imageInfo.text//n//t//t//t});//n//t//t};//n//t}//n//n//taddTiddler(fields) {//n//t//tthis.tiddlers[fields.title] = fields;//n//t}//n//n//t//u002f//u002a//n//tGet the JSON of the entire plugin//n//t//u002a//u002f//n//tgetPluginText() {//n//t//tthis.fields.text = JSON.stringify({tiddlers: this.tiddlers},null,4)//n//t//t//u002f//u002f Replace /'</' with /'//u003c/' to avoid HTML parsing errors when the JSON is embedded in a script tag//n//t//treturn JSON.stringify(this.fields,null,4).replace(/</g,/'//u003c/');//n//t}//n//n}//n//nfunction stringifyList(value) {//n//tif(Array.isArray(value)) {//n//t//tconst result = new Array(value.length);//n//t//tfor(const t of value) {//n//t//t//tconst entry = value[t] || /'/';//n//t//t//tif(entry.indexOf(/' /') !== -1) {//n//t//t//t//tresult[t] = /'[[/' + entry + /']]/'//n//t//t//t} else {//n//t//t//t//tresult[t] = entry;//n//t//t//t}//n//t//t}//n//t//treturn result.join(/' /');//n//t} else {//n//t//treturn value || /'/';//n//t}//n};//n//nexports.TwpubPlugin = TwpubPlugin;