XMark: Content Building Tool Powered by Plugins
By Shijun Wang on 04/04/2019

XMark: Content Building Tool Powered by Plugins

Brought to you by Shijun Wang

XMark: Content Building Tool Powered by Plugins

Agenda

  • Motivation
  • Design
  • Implementation
  • Show Case
  • How to Write a Plugin?
  • Future Plans

Motivation

  • Mixing content with structure and presentation in source code makes it hard to change
  • Existing markdown tools are too generic(remark) or rely too much on developer skills(mdx)
  • We need a generic tool that can generic web content from markdown, must be flexible and extensible

XMark: means a powerful(x) tool based on markdown

Design

  • The core of xmark should only care about 2 things

    • Allow user to compose a processing pipeline with his selected plugins
    • Run the set of plugins against input and return the results
  • The input of the pipeline can be either markdown or hast

    • markdown is the source code
    • hast can be generated from any other tool, such as remark
  • The output of the pipeline can be either html or hast

    • html for direct page rendering
    • hast for rendering in react|vue or later processing
  • All structure transforming and style applying should be done in plugins

    • transform plugins only modifies the hast, the only format through the pipeline
    • theme plugins only return styles that can applied to certain structure

Side Note: hast: Hypertext Abstract Syntax Tree

Spec

interface Element <: Parent {
  type: "element"
  tagName: string
  properties: Properties?
  content: Root?
  children: [Element | Comment | Text]
}

Example

{
  "type": "element",
  "tagName": "a",
  "properties": {
    "href": "http://alpha.com",
    "className": ["bravo"],
    "download": true
  },
  "children": []
}

Implementation

function fromMarkdownToHast(markdown) {
  let hast = typeof markdown === 'string' ? toHAST(remark.parse(markdown)) : markdown;

  // Apply transformation
  plugins
    .filter(x => x.type === 'transform')
    .forEach(({ fn, config }) => {
      hast = fn(hast, config);
    });

  // Apply styling
  plugins
    .filter(x => x.type === 'theme')
    .forEach(({ fn, config }) => {
      hast.children.unshift(h('style.xmark-injected-style', { scoped: true }, fn(config)));
    });

  return hast;
}

function fromMarkdownToHtml(markdown) {
  return toHTML(fromMarkdownToHast(markdown));
}

Implementation (ctnd.)

function usePlugin(type, plugin, config) {
  const fn = typeof plugin === 'function' ? plugin : require.resolve(fn);
  plugins.push({ type, fn, config });
}

return {
  toHAST: fromMarkdownToHast,
  toHTML: fromMarkdownToHtml,

  useTransform(plugin, config) {
    usePlugin('transform', plugin, config || {});
    return this;
  },

  useTheme(plugin, config) {
    usePlugin('theme', plugin, config || {});
    return this;
  },
};

Show Case

Show Case: Wrap Headings

Plugins

Wrap Headings

wrap

Show Case: Class Extension

Plugins

Class Extension

extension

Show Case: Landing Page Generating

Plugins

Supported Sections

  • card-list
  • horizontal-annotation-list
  • image-background
  • image-column
  • image-feature-list
  • image-row
  • image-rows
  • step-guide
  • vertical-annotation-list

Show Case: Landing Page Generating: Pipeline

const XMark = require('@xmark/core');
const page = require('@xmark/transform-landing-page');
const wrap = require('@xmark/transform-wrap-headings');
const theme = require('@xmark/theme-landing-page');

const xmark = XMark({});
xmark.useTransform(wrap, {
  wrapperTag: 'section',
  enabledHeadings: ['h2', 'h4', 'h5', 'h6'],
});

xmark.useTransform(page, {
  selectors: {
    sectionImage: 'p:has(.gatsby-resp-image-wrapper)',
  },
});

xmark.useTheme(theme);
xmark.toHAST(`## title {.section .section--hero}`);

Show Case: Landing Page Generating: Syntax

---
title: 'Product Name'
path: '/learning/product'
layout: 'page'
pageType: 'product'
backgroundImage: './images/bg.jpg'
disableHeader: true
logoColor: 'blue'
---

## Hero section from top to bottom. {.section .section--hero}

A fully decentralized wallet that put you in control of everything - digital identity, tokens, profiles and more.

[Demo App](mailto:mining-partner@arcblock.io){.action-button}
[Install](mailto:mining-partner@arcblock.io){.action-button}

![](./images/hero.jpg)

Show Case: Presentation Generating

Plugins

Example

  • This presentation is generated by xmark with above plugins

Show Case: Presentation Generating: Pipeline

const XMark = require('@xmark/core');
const shower = require('@xmark/transform-shower');
const wrap = require('@xmark/transform-wrap-headings');
const ribbon = require('@xmark/theme-shower-ribbon');

const xmark = XMark({});

xmark.useTransform(wrap, {
  enabledHeadings: ['h2'],
  wrapperClasses: {
    wrapper: 'slide',
    modifier: x => `slide--level${x}`,
  },
});

xmark.useTransform(shower, {
  caption: false,
  progress: false,
  wrapper: false,
});

xmark.useTheme(ribbon);
xmark.toHAST(`## title {.slide}`);

Show Case: Presentation Generating: Syntax

---
title: 'XMark: Content Building Tool Powered by Plugins'
author: 'Shijun Wang'
date: '04/04/2019'
path: '/bbl/xmark'
layout: 'presentation'
---

## Agenda {.slide}

- Motivation
- Design
- Implementation
- Show Case
- How to Write a Plugin?
- Future Plans

How to Write a Plugin?

How to Write a Plugin: Transform?

const xmark = XMark({});
xmark.useTransform(hast => {
  hast.children[0].properties.className = ['plugin-added-class'];
  return hast;
});

expect(xmark.toHTML('# heading 1')).toEqual('<h1 class="plugin-added-class">heading 1</h1>');
expect(xmark.toHAST('# heading 1')).toEqual({
  children: [
    {
      children: [
        {
          type: 'text',
          value: 'heading 1',
        },
      ],
      properties: { className: ['plugin-added-class'] },
      tagName: 'h1',
      type: 'element',
    },
  ],
  type: 'root',
});

How to Write a Plugin: Theme?

const xmark = XMark({});
xmark.useTheme(() => 'h1 { color: red; }');

expect(xmark.toHTML('# heading 1')).toEqual(`
  <style class="xmark-injected-style" scoped>h1 { color: red; }</style>
  <h1>heading 1</h1>
`);

Future Plans

XMark

  • Write more plugins to make xmark usable when the content matters most
  • Provide @xmark/cli

Content

  • It's possible to make use xmark processing markdown snippets
  • Snippets can be reused across pages in www repo, then we can make it completely marketing driven
  • Improve shower plugin and presentation component, allow publish slide to web easily

TODO

  • TOC on sidebar
  • Accelerator repo use xmark

Thanks

Arcblock