mirror of
				https://github.com/ether/etherpad-lite.git
				synced 2025-10-31 00:01:16 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			174 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			174 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| // Copyright Joyent, Inc. and other Node contributors.
 | |
| //
 | |
| // Permission is hereby granted, free of charge, to any person obtaining a
 | |
| // copy of this software and associated documentation files (the
 | |
| // "Software"), to deal in the Software without restriction, including
 | |
| // without limitation the rights to use, copy, modify, merge, publish,
 | |
| // distribute, sublicense, and/or sell copies of the Software, and to permit
 | |
| // persons to whom the Software is furnished to do so, subject to the
 | |
| // following conditions:
 | |
| //
 | |
| // The above copyright notice and this permission notice shall be included
 | |
| // in all copies or substantial portions of the Software.
 | |
| //
 | |
| // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 | |
| // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 | |
| // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
 | |
| // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 | |
| // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
 | |
| // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
 | |
| // USE OR OTHER DEALINGS IN THE SOFTWARE.
 | |
| 
 | |
| var fs = require('fs');
 | |
| var marked = require('marked');
 | |
| var path = require('path');
 | |
| 
 | |
| module.exports = toHTML;
 | |
| 
 | |
| function toHTML(input, filename, template, cb) {
 | |
|   var lexed = marked.lexer(input);
 | |
|   fs.readFile(template, 'utf8', function(er, template) {
 | |
|     if (er) return cb(er);
 | |
|     render(lexed, filename, template, cb);
 | |
|   });
 | |
| }
 | |
| 
 | |
| function render(lexed, filename, template, cb) {
 | |
|   // get the section
 | |
|   var section = getSection(lexed);
 | |
| 
 | |
|   filename = path.basename(filename, '.md');
 | |
| 
 | |
|   lexed = parseLists(lexed);
 | |
| 
 | |
|   // generate the table of contents.
 | |
|   // this mutates the lexed contents in-place.
 | |
|   buildToc(lexed, filename, function(er, toc) {
 | |
|     if (er) return cb(er);
 | |
| 
 | |
|     template = template.replace(/__FILENAME__/g, filename);
 | |
|     template = template.replace(/__SECTION__/g, section);
 | |
|     template = template.replace(/__TOC__/g, toc);
 | |
| 
 | |
|     // content has to be the last thing we do with
 | |
|     // the lexed tokens, because it's destructive.
 | |
|     content = marked.parser(lexed);
 | |
|     template = template.replace(/__CONTENT__/g, content);
 | |
| 
 | |
|     cb(null, template);
 | |
|   });
 | |
| }
 | |
| 
 | |
| 
 | |
| // just update the list item text in-place.
 | |
| // lists that come right after a heading are what we're after.
 | |
| function parseLists(input) {
 | |
|   var state = null;
 | |
|   var depth = 0;
 | |
|   var output = [];
 | |
|   output.links = input.links;
 | |
|   input.forEach(function(tok) {
 | |
|     if (state === null) {
 | |
|       if (tok.type === 'heading') {
 | |
|         state = 'AFTERHEADING';
 | |
|       }
 | |
|       output.push(tok);
 | |
|       return;
 | |
|     }
 | |
|     if (state === 'AFTERHEADING') {
 | |
|       if (tok.type === 'list_start') {
 | |
|         state = 'LIST';
 | |
|         if (depth === 0) {
 | |
|           output.push({ type:'html', text: '<div class="signature">' });
 | |
|         }
 | |
|         depth++;
 | |
|         output.push(tok);
 | |
|         return;
 | |
|       }
 | |
|       state = null;
 | |
|       output.push(tok);
 | |
|       return;
 | |
|     }
 | |
|     if (state === 'LIST') {
 | |
|       if (tok.type === 'list_start') {
 | |
|         depth++;
 | |
|         output.push(tok);
 | |
|         return;
 | |
|       }
 | |
|       if (tok.type === 'list_end') {
 | |
|         depth--;
 | |
|         if (depth === 0) {
 | |
|           state = null;
 | |
|           output.push({ type:'html', text: '</div>' });
 | |
|         }
 | |
|         output.push(tok);
 | |
|         return;
 | |
|       }
 | |
|       if (tok.text) {
 | |
|         tok.text = parseListItem(tok.text);
 | |
|       }
 | |
|     }
 | |
|     output.push(tok);
 | |
|   });
 | |
| 
 | |
|   return output;
 | |
| }
 | |
| 
 | |
| 
 | |
| function parseListItem(text) {
 | |
|   text = text.replace(/\{([^\}]+)\}/, '<span class="type">$1</span>');
 | |
|   //XXX maybe put more stuff here?
 | |
|   return text;
 | |
| }
 | |
| 
 | |
| 
 | |
| // section is just the first heading
 | |
| function getSection(lexed) {
 | |
|   var section = '';
 | |
|   for (var i = 0, l = lexed.length; i < l; i++) {
 | |
|     var tok = lexed[i];
 | |
|     if (tok.type === 'heading') return tok.text;
 | |
|   }
 | |
|   return '';
 | |
| }
 | |
| 
 | |
| 
 | |
| function buildToc(lexed, filename, cb) {
 | |
|   var indent = 0;
 | |
|   var toc = [];
 | |
|   var depth = 0;
 | |
|   lexed.forEach(function(tok) {
 | |
|     if (tok.type !== 'heading') return;
 | |
|     if (tok.depth - depth > 1) {
 | |
|       return cb(new Error('Inappropriate heading level\n' +
 | |
|                           JSON.stringify(tok)));
 | |
|     }
 | |
| 
 | |
|     depth = tok.depth;
 | |
|     var id = getId(filename + '_' + tok.text.trim());
 | |
|     toc.push(new Array((depth - 1) * 2 + 1).join(' ') +
 | |
|              '* <a href="#' + id + '">' +
 | |
|              tok.text + '</a>');
 | |
|     tok.text += '<span><a class="mark" href="#' + id + '" ' +
 | |
|                 'id="' + id + '">#</a></span>';
 | |
|   });
 | |
| 
 | |
|   toc = marked.parse(toc.join('\n'));
 | |
|   cb(null, toc);
 | |
| }
 | |
| 
 | |
| var idCounters = {};
 | |
| function getId(text) {
 | |
|   text = text.toLowerCase();
 | |
|   text = text.replace(/[^a-z0-9]+/g, '_');
 | |
|   text = text.replace(/^_+|_+$/, '');
 | |
|   text = text.replace(/^([^a-z])/, '_$1');
 | |
|   if (idCounters.hasOwnProperty(text)) {
 | |
|     text += '_' + (++idCounters[text]);
 | |
|   } else {
 | |
|     idCounters[text] = 0;
 | |
|   }
 | |
|   return text;
 | |
| }
 | |
| 
 |