在Jade / Node.js中过滤Markdown内容

时间:2012-02-06 12:05:48

标签: node.js pug

尊敬的黑客,

我希望在Jade模板中过滤一个充满Markdown的字符串。 我在变量中有Markdown。

Jade在Markdown中插入变量就好了:

此:

var jade = require('jade');

var jade_string = [
    ':markdown',
    '    ## This is markdown!',
    '    * First',
    '    * #{var2}',
    '    * Third'
].join('\n');

var fn = jade.compile( jade_string, { pretty: true } );

console.log( fn( { var1: "First!", var2: "Second!" } ) );

想到这个:

<h2>This is markdown!</h2>

<ul>
<li>First</li>
<li>Second!</li>
<li>Third</li>
</ul>

然而,我所拥有的是变量中的实际完整Markdown。 这个:

var jade = require('jade');

var jade_string = [
    'div.markedup',
    '    :markdown',
    '        \\#{var2}'
].join('\n');

var fn = jade.compile( jade_string, { pretty: true } );

var markdown = [
    '## I am marked!',
    '* One',
    '* Two'
].join('\n');

console.log( fn( { var1: "First!", var2: markdown } ) );

仅提供此:

<div class="markedup"><p>## I am marked!
* One
* Two</p>
</div>

因此,我认为Jade在执行任何变量之前过滤了块 插值,然后在结果HTML中插入变量。这是 如果您想在Markdown中编写模板,那很好,但并不多 如果您想在Markdown中编写内容,请提供帮助。

我知道我可以通过更多编程解决这个问题,但我觉得我 必须遗漏一些东西。毕竟,持有Markdown内容的片段 数据库并将生成的HTML片段填充到模板中似乎 :markdown过滤器最明显的用例。

在Jade中有“正常”的方法吗?

非常感谢未来的启蒙。

2 个答案:

答案 0 :(得分:4)

我认为答案更多是编程,但我会告诉你我的工作。我使用自定义中间件,让我在进入最终的HTML文档输出之前结合任意变换过程。所以,例如,我在middleware.js模块中有以下过滤器,我将依次解释。

如此简单的视图只需使用普通的玉石及其各种过滤器来降价,javascript,coffeescript。一些观点,例如博客文章,需要更复杂的中间件链,就像这样。

首先,根据请求,我建立了包含此响应的核心内容的文件,并将其设置为res.viewPath上的属性。这可以是原始HTML片段文件或markdown文件。然后我通过一系列中间件转换发送响应。我使用res.htmlres.dom来存储响应的中间表示。

这个只存储原始HTML(只是一个没有头部或布局的文档正文片段)。

html = function(req, res, next) {
  if (!/\.html$/.test(res.viewPath)) return next();
  return fs.readFile(res.viewPath, "utf8", function(error, htmlText) {
    res.html = htmlText;
    return next(error);
  });
};

这个将markdown文件转换为HTML(使用markdown-js模块)。

markdownToHTML = function(req, res, next) {
  if (!/\.md$/.test(res.viewPath)) return next();
  return fs.readFile(res.viewPath, "utf8", function(error, markdownText) {
    res.html = markdown(markdownText);
    return next(error);
  });
};

我有一个子布局,在我的主布局中,但围绕每个博客文章。所以我在这里的子布局中包装博客文章。 (未显示的单独代码从json元数据文件生成res.post对象。)

blogArticle = function(req, res, next) {
  var footerPath, post;
  post = res.post;
  footerPath = path.join(__dirname, "..", "templates", "blog_layout.jade");
  return fs.readFile(footerPath, "utf8", function(error, jadeText) {
    var footerFunc;
    if (error) return next(error);
    footerFunc = jade.compile(jadeText);
    res.html = footerFunc({
      post: post,
      body: res.html
    });
    return next();
  });
};

现在我将布局包裹在主要内容HTML中。请注意,我可以在此处设置页面标题之类的内容,或者等到稍后,因为我可以在此之后通过jsdom操作响应。我做body: res.html || ""所以我可以渲染一个空的布局,如果更方便的话,可以在以后插入正文。

exports.layout = function(req, res, next) {
  var layoutPath;
  layoutPath = path.join(__dirname, "..", "templates", "layout.jade");
  return fs.readFile(layoutPath, "utf8", function(error, jadeText) {
    var layoutFunc, locals;
    layoutFunc = jade.compile(jadeText, {
      filename: layoutPath
    });
    locals = {
      config: config,
      title: "",
      body: res.html || ""
    };
    res.html = layoutFunc(locals);
    return next(error);
  });
};

这里有真正强大的东西。我将HTML字符串转换为jsdom文档对象模型,该模型允许在服务器端进行基于jQuery的转换。下面的toMarkup函数只允许我在没有jsdom添加的内存中jquery的额外<script>标记的情况下返回HTML。

exports.domify = function(req, res, next) {
  return jsdom.env(res.html, [jqueryPath], function(error, dom) {
    if (error) return next(error);
    res.dom = dom;
    dom.toMarkup = function() {
      this.window.$("script").last().remove();
      return this.window.document.doctype + this.window.document.innerHTML;
    };
    return next(error);
  });
};

所以这是我做的自定义转换。这可以用真正有效的HTML替换像<flickrshow href="http://flickr.com/example"/>这样的伪造的DSL标签,否则这将是一个非常讨厌的<object>样板,我将不得不在每篇博文中复制,如果flickr改变了样板文件他们使用的标记,在许多单独的博客帖子降价文件中修复它将是一种维护痛苦。他们当前使用的样板文件位于flickrshowTemplate变量中,并包含一些小胡子占位符{URLs}

exports.flickr = function(req, res, next) {
  var $ = res.dom.window.$;
  $("flickrshow").each(function(index, elem) {
    var $elem, URLs;
    $elem = $(elem);
    URLs = $elem.attr("href");
    return $elem.replaceWith(flickrshowTemplate.replace(/\{URLs\}/g, URLs));
  });
  return next();
};

同样嵌入YouTube视频。 <youtube href="http://youtube.com/example"/>

exports.youtube = function(req, res, next) {
  var $ = res.dom.window.$;
  $("youtube").each(function(index, elem) {
    var $elem, URL;
    $elem = $(elem);
    URL = $elem.attr("href");
    return $elem.replaceWith(youtubeTemplate.replace(/\{URL\}/, URL));
  });
  return next();
};

现在我可以根据需要更改标题,添加/删除javascripts或样式表等。这里我在已经渲染布局后设置标题。

postTitle = function(req, res, next) {
  var $;
  $ = res.dom.window.$;
  $("title").text(res.post.title + " | Peter Lyons");
  return next();
};

好的,是时候回到最终的HTML了。

exports.undomify = function(req, res, next) {
  res.html = res.dom.toMarkup();
  return next();
};

现在我们发货了!

exports.send = function(req, res) {
  return res.send(res.html);
};

为了将它们整合在一起并明确使用它,我们做

postMiddleware = [
  loadPost,
  html,
  markdownToHTML,
  blogArticle,
  layout,
  domify,
  postTitle,
  flickr,
  youtube,
  undomify,
  send
]

app.get("/your/uri", postMiddleware);

简明?不。清洁?我认同。灵活?非常。非常快?可能并不快,因为我认为jsdom是你可以做的更重要的事情之一,但我使用它作为静态站点生成器,所以速度是无关紧要的。当然,在中间件链的开头和结尾添加另一个函数将最终的HTML写入静态文件并直接提供它,如果它比相应的降价页面主体内容文件更新,那将是微不足道的。 Stackoverflowers,我很想听听有关这种方法的想法和建议!

答案 1 :(得分:2)

Jade人说将变量传递给过滤器is not supported。我无法理解Peter Lyons的回答,所以我用了这个:

marked = require 'marked'
marked.setOptions
  <my options>
app.locals.md = marked

然后在玉:

!= md(<markdown string>)

快速而肮脏。可能不是最理想的,因为它每次运行转换而不缓存结果(我认为),但至少它是有效的。

<强>(编辑)

您还可以在浏览器中使用标记来渲染markdown,从服务器卸载一些工作并加快加载速度。