Фильтрация содержимого Markdown в Jade / Node.js - PullRequest
2 голосов
/ 06 февраля 2012

Уважаемые хакеры,

Я хочу отфильтровать строку, полную Markdown, в шаблоне Jade.У меня есть Markdown в переменной.

Jade просто интерполирует переменную внутри Markdown:

This:

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, но это не сильно поможет, если вы хотите написать content в Markdown.

Я знаю, что могурешить эту проблему с помощью программирования , но я чувствую, что, должно быть, что-то упустил.В конце концов, хранение фрагментов содержимого Markdown в базе данных и вставка полученных фрагментов HTML в шаблоны кажется наиболее очевидным вариантом использования фильтра :markdown.

Существует ли «нормальный» способ сделать этов нефрите?

Большое спасибо заранее за ожидаемое просветление.

Ответы [ 2 ]

4 голосов
/ 06 февраля 2012

Я думаю, что ответ больше программирования, но я покажу вам, что я делаю. Я использую пользовательское промежуточное ПО, которое позволяет мне комбинировать произвольные процессы преобразования, прежде чем я доберусь до окончательного вывода HTML-документа. Так, например, в моем модуле middleware.js есть следующие фильтры, которые я объясню по очереди.

Так что простые представления просто используют обычный нефрит с его различными фильтрами для уценки, javascript, coffeescript. Некоторые представления, например сообщение в блоге, требуют более сложной цепочки промежуточного программного обеспечения, которая выглядит следующим образом.

Сначала, основываясь на запросе, я устанавливаю файл, содержащий основное содержимое для этого ответа, и устанавливаю его как свойство на res.viewPath. Это может быть необработанный файл фрагмента HTML или файл уценки. Затем я отправляю ответ через серию преобразований промежуточного программного обеспечения. Я использую res.html и res.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);
  });
};

Этот файл преобразует файл уценки в 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);
  });
};

У меня есть под-макет, который идет в рамках моего основного макета, но вокруг каждого поста в блоге. Так что я заверну пост в блоге здесь. (Отдельный код, который не показан, создает объект res.post из файла метаданных json).

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 позволяет мне возвращать HTML-код без дополнительного тега <script> для нашего jquery в памяти, добавленного jsdom.

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);
  });
};

Итак, вот собственное преобразование, которое я делаю. Это может заменить готовый тег DSL, такой как <flickrshow href="http://flickr.com/example"/>, на настоящий действительный HTML, который в противном случае был бы большим неприятным шаблоном <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, я хотел бы услышать мысли и предложения об этом подходе!

2 голосов
/ 02 мая 2014

Джейд говорит, что передача переменных в фильтры не поддерживается . Я не мог получить ответ Питера Лайонса, поэтому я использовал это:

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

Тогда в нефрите:

!= md(<markdown string>)

Быстро и грязно. Вероятно, неоптимальный, потому что он выполняет преобразование каждый раз без кэширования результата (я думаю), но по крайней мере это работает.

(Edit)

Вы также можете использовать отмеченные для рендеринга уценки в браузере, разгрузки некоторой работы с вашего сервера и ускорения загрузки.

...