diff --git a/lib/collectTags.js b/lib/collectTags.js
new file mode 100644
index 0000000000000000000000000000000000000000..9c6cc1545af5e4a60615064a84c520a4e61d60ce
--- /dev/null
+++ b/lib/collectTags.js
@@ -0,0 +1,57 @@
+import _ from 'lodash/fp'
+
+const collector = ({ pagination, warnings = {} }) => files => {
+  const { onlyOneArticle = false, sameURL = true } = warnings
+  const tags = {}
+  const tagList = []
+  let warned = false
+  files.forEach(file => {
+    if(file.metadata.tags)
+    for(const tag of file.metadata.tags) {
+      const tagname = _.flow(
+        _.deburr,
+        _.toLower,
+        _.replace(' ','-'),
+        _.replace(/[^a-zA-Z-0-9]+/g, ''),
+      )(tag)
+      if(!tags[tagname]) {
+        const obj = { deburr: tagname, tag, list: [] }
+        tags[tagname] = obj
+        tagList.push(obj)
+      }
+      tags[tagname].list.push(file)
+      const warn = () => { if(!warned) { console.log(); warned = true } }
+      if(sameURL && tags[tagname].tag !== tag) {
+        warn()
+        console.error(`WARNING: Name of tag "${tag}" is not same as "${tags[tagname].tag}" but they correspond to same url of "${tagname}"`)
+        console.error(`         In files ${file.metadata.filename} and ${tags[tagname].list[0].metadata.filename}`)
+      }
+      if(onlyOneArticle && tags[tagname].list.length < 2) {
+        warn()
+        console.error(`WARNING: Tag "${tag}" only has one article. Consider removing it or adding it more articles.`)
+        console.error(`         In file ${file.metadata.filename}`)
+      }
+    }
+  })
+  const tagPages = tagList.reduce((list, tag) => {
+    const chunks = _.chunk(pagination, tag.list)
+    const paginator = chunks.map((chunk, index) => '/tag/'+tag.deburr+(index?'/'+(index+1):''))
+    return list.concat(chunks.map(
+      (chunk, index) => ({
+        type: 'tag',
+        metadata: {
+          url: paginator[index],
+          name: tag.tag,
+          paginator,
+        },
+        content: chunk
+      })
+    ))
+  }, [])
+  return files.map(
+    article => ({ ...article, type: 'article' })
+  ).concat(tagPages)
+  //console.log(tagList)
+}
+
+export default collector
diff --git a/lib/index.js b/lib/index.js
index 00b10673d8672083010fe20550fb22fb429fe088..c1a6fe2b5114391a8c5b8529b73346fc1647d21e 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -4,16 +4,41 @@ import fs from 'fs'
 import {readFiles} from './readFiles'
 import parser from './parser'
 import markdownToJSON from './markdownToJSON'
+import collectTags from './collectTags'
 
+const buildStep = (desc, func) => f => {
+  const start = new Date();
+  const msg = `- ${desc}`
+  process.stdout.write(msg)
+  const ret = func(f)
+  process.stdout.write(_.repeat(30-msg.length,' '))
+  process.stdout.write(`${(new Date()-start)}ms\n`)
+  return ret
+}
+
+const map = (desc, func) => buildStep(desc, f => f.map(func))
+
+let start
 readFiles({
   filter: file => !/\.git$/.exec(file)
-})(change => console.log())
-.then(files => files.map(f => parser(f)))
-.then(files => files.filter(f => f))
-.then(files => files.map(f => ({ metadata: f.metadata, content: markdownToJSON(f.content) })))
-.then(files => files.map(f => {
-  console.log(_.repeat(80,'='))
-  console.log(f.metadata)
-  console.log(f.content)
-}))
+})(change => console.log(change))
+.then(f => (start = new Date()) && f)
+.then(map('Parsing files', f => parser(f)))
+.then(map('Parsing markdown', f => ({ metadata: f.metadata, content: markdownToJSON(f.content) })))
+.then(map('Rewriting URLs', f => ({
+  metadata: {
+    ...f.metadata,
+    url: '/clanek/' + f.metadata.filename.replace(/\.md$/,'')
+  },
+  content: f.content
+})))
+.then(buildStep('Collect tags', collectTags({
+  pagination: 6,
+  warnings: {
+    onlyOneArticle: false,
+    sameURL: false,
+  }
+})))
+//.then(f => console.log(f))
+.then(() => console.log(`                       Total: ${(new Date())-start}ms`))
 .catch(e => console.log(e))
diff --git a/lib/markdownToJSON/flatten.js b/lib/markdownToJSON/flatten.js
new file mode 100644
index 0000000000000000000000000000000000000000..0ba8ece1bf3330758f1b5bc1c96c14e4452b35a6
--- /dev/null
+++ b/lib/markdownToJSON/flatten.js
@@ -0,0 +1,67 @@
+import _ from 'lodash/fp'
+
+const join = (a, b) => {
+  const anmap = an => Object.assign({}, an, {from: an.from+a.text.length})
+  const anotations = { ...a.anotations }
+  for(const anotation in b.anotations) {
+    if(anotations[anotation]) { // merge
+      anotations[anotation] = [
+        ...a.anotations[anotation],
+        ...b.anotations[anotation].map(anmap)
+      ]
+    } else {
+      anotations[anotation] = b.anotations[anotation].map(anmap)
+    }
+  }
+  return {
+    text: a.text+b.text,
+    anotations
+  }
+}
+
+const mergeAnotations = (a, b) => {
+  throw new Error('Not implemented');
+}
+
+const toAnotatedText = (par) => {
+  if(!par) {
+    return { text: '', anotations: {} }
+  }
+
+  if(_.isString(par)) {
+    return { text: par, anotations: {} }
+  } else if(_.isArray(par)) {
+    return par.map(toAnotatedText).reduce(join)
+    return { text: 'TODO', anotations: {} }
+  } else { // object
+    const {name, opts} = par
+    const ant = toAnotatedText(par.children)
+    const anotation = { from: 0, length: ant.text.length, opts }
+    const ret = { text: ant.text, anotations: { ...ant.anotations }}
+    ret.anotations[name] = [
+      ...(ant.anotations[name] || []),
+      anotation,
+    ]
+    return ret
+  }
+  console.log(par)
+  throw new Error('Panic')
+}
+
+const mkmerger = merger => arg => {
+  for(const i in merger) {
+
+  }
+}
+
+export default a => {
+  //console.log(_.repeat(80,'='))
+  //console.log(a)
+  const ret = mkmerger({
+
+  })(
+    toAnotatedText(a.filter(o => o))
+  )
+  //console.log(JSON.stringify(ret, undefined, '  '))
+  return ret
+}
diff --git a/lib/markdownToJSON/index.js b/lib/markdownToJSON/index.js
index adcaa544fb91e4d9ca7c5a2cfb806912f148ac4a..c45e2826af68e8ee7ae5679c1aebb224b337e310 100644
--- a/lib/markdownToJSON/index.js
+++ b/lib/markdownToJSON/index.js
@@ -2,10 +2,12 @@ import _ from 'lodash/fp'
 
 import {toStr} from './renderer'
 import Parser from './parser'
+import flatten from './flatten'
 import kramed from 'kramed'
 
 const mtj = new Parser()
 export default _.flow(
   a => kramed.lexer(a),
-  a => mtj.parse(a)
+  a => mtj.parse(a),
+  flatten
 )
diff --git a/lib/markdownToJSON/renderer.js b/lib/markdownToJSON/renderer.js
index 927c8d987a00d5df75a2f11b33968e641aa19811..26e03a1f4f15a1b93c94cdd0e0ada139e17b7ff7 100644
--- a/lib/markdownToJSON/renderer.js
+++ b/lib/markdownToJSON/renderer.js
@@ -82,7 +82,7 @@ Renderer.prototype.listitem = simple('li')
 Renderer.prototype.paragraph = simple('p')
 Renderer.prototype.tablerow = simple('tr')
 
-Renderer.prototype.table = (header, body) => fmt('table', body, {header})
+Renderer.prototype.table = (header, body) => fmt('table', [fmt('thead', header), fmt('tbody', body)])
 
 Renderer.prototype.list = function(body, ordered) {
   var type = ordered ? 'ol' : 'ul';