小工具:Chrome书签文件转Markdown文件

需求:

闲来无事,想把自己收藏的一些书签按目录来转成md文件。

于是乎就有了下面这个小工具:
bookmark2md

先上源码:

// 配置文件
config.js

/**
 * Created by OXOYO on 2017/10/16.
 */

exports = module.exports = {
    // 书签文件
    bookmarksFile: './bookmarks.html',
    // 生成目录
    mdFilePath: './md/',
    // 排除目录
    unlessPath: ['OXO', 'OYO']
}

// 核心文件
bookmark2md.js


/**
 * Created by OXOYO on 2017/10/13.
 */
const fs = require('fs')
const cheerio = require('cheerio')
const config = require('./config')
// console.log('config', config)

const html = fs.readFileSync(config.bookmarksFile)
const $ = cheerio.load(html)
// 当前时间
const timeNow = (new Date()).getTime()

// 目录名称对象
const dirNameObj = {}
// 分割符
const separator = '=>'

// 写文件
const writeFile = function (fileName, content, filePath) {
    filePath = filePath ? filePath : './md/'
    // 写入文件
    fs.writeFile(filePath + fileName, content, function (err) {
        if (err) {
            return console.error(err)
        } else {
            console.log(fileName + ' created success!')
        }
    })
}
// 日期格式化
const formatDate = (time, fmt = 'yyyy-MM-dd hh:mm') => {
    // 10位时间戳格式化
    let timeStr = time + ''
    if (timeStr.length < 13) {
        time = time * (Math.pow(10, 13 - timeStr.length))
    }
    time = parseInt(time)
    if (isNaN(time)) {
        return ''
    }
    let date = new Date(time)
    let padLeftZero = (str) => {
        return ('00' + str).substr(str.length)
    }
    let doFormatDate = (date, fmt) => {
        if (/(y+)/.test(fmt)) {
            fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length))
        }
        let obj = {
            'M+': date.getMonth() + 1,
            'd+': date.getDate(),
            'h+': date.getHours(),
            'm+': date.getMinutes(),
            's+': date.getSeconds()
        }
        for (let k in obj) {
            if (new RegExp(`(${k})`).test(fmt)) {
                let str = obj[k] + ''
                fmt = fmt.replace(RegExp.$1, RegExp.$1.length === 1 ? str : padLeftZero(str))
            }
        }
        return fmt
    }

    return doFormatDate(date, fmt)
}

// 遍历 a 标签
$('a').each(function (i, el) {
    let node = $(el)
    let nodeText = node.text()
    let nodeHref = node.attr('href')
    let nodeCreateTime = formatDate(node.attr('add_date'))
    let nodeUpdateTime = formatDate(node.attr('last_modified'))
    // 查找父目录
    let parentDT = node.parents('dt')
    let parentDirPathArr = []
    let parentDirPathStr = ''
    parentDT.each(function (j, parent) {
        let firstChild = $(parent).children().first()
        if (firstChild.is('h3')) {
            let dirName = firstChild.text()
            if (firstChild.attr('personal_toolbar_folder')) {
                dirName = timeNow
            }
            parentDirPathArr.push(dirName)
        }
    })
    parentDirPathStr = parentDirPathArr.reverse().join(separator)
    // console.log(parentDirPathStr)
    // 初始化当前目录路径
    if (!dirNameObj[parentDirPathStr]) {
        dirNameObj[parentDirPathStr] = []
    }
    dirNameObj[parentDirPathStr].push({
        text: nodeText,
        href: nodeHref,
        create_time: nodeCreateTime,
        update_time: nodeUpdateTime
    })
})

setTimeout(function () {
    let fileObj = {}
    Object.keys(dirNameObj).map(function (key) {
        let flag = false
        for (let path of config.unlessPath) {
            if (key.toLowerCase().includes(path.toLowerCase())) {
                flag = true
                break
            }
        }
        if (!flag) {
            // console.log('key', key)
            let keys = key.split(separator)
            // 递归处理
            function handle (index, elem, keys, len, obj) {
                if (!obj[elem]) {
                    obj[elem] = {
                        dir: elem,
                        child: {},
                        list: []
                    }
                }
                if (index < len - 1) {
                    let i = index + 1
                    handle(i, keys[i], keys, keys.length, obj[elem].child)
                } else if (index = len - 1) {
                    obj[elem].list =  dirNameObj[keys.join(separator)]
                }
            }
            handle(0, keys[0], keys, keys.length, fileObj)
        }
    })
    // 处理fileObj
    let childFileObj = fileObj[timeNow]['child']
    let fileContentArr = []
    Object.keys(childFileObj).map(function (key) {
        if (key) {
            let fileName = key + '.md'
            let fileContent = ''
            // 递归循环
            let count = 1
            let getSize = function (count) {
                let arr = new Array(count > 6 ? 6 : count).fill('#')
                return arr.join('') + ' '
            }
            let handle = function (obj, count) {
                if (obj['dir']) {
                    fileContent += getSize(count) + obj['dir'] + '\n\n'
                }
                // 判断list是否为空
                if (obj.list && obj.list.length) {
                    for (let [i, item] of obj.list.entries()) {
                        let createTime = item.create_time ? item.create_time + ' ' : ''
                        fileContent += createTime + '[' + item.text + '](' + item.href + ')' + '\n\n'
                    }
                }
                // 判断是否存在子节点
                if (obj.child && Object.keys(obj.child).length) {
                    for (let key of Object.keys(obj.child)) {
                        handle(obj.child[key], count + 1)
                    }
                }
            }
            handle(childFileObj[key], count)
            fileContentArr.push(fileContent)
            // 创建 md 文件
            writeFile(fileName, fileContent, config.mdFilePath)
        }
    })
    // 处理timeNow一层
    if (fileObj[timeNow].list && fileObj[timeNow].list.length) {
        let obj = fileObj[timeNow]
        let fileContent = ''
        for (let [i, item] of obj.list.entries()) {
            let createTime = item.create_time ? item.create_time + ' ' : ''
            fileContent += createTime + '[' + item.text + '](' + item.href + ')' + '\n\n'
        }
        fileContentArr.push(fileContent)
    }
    // 生成README.md
    writeFile('README.md', fileContentArr.join(''), config.mdFilePath)
}, 3000)

预览

前端书签整理:F2E-Tutorial-Collect
图片描述

用法

1.clone

    git clone https://github.com/OXOYO/bookmark2md
    cd bookmark2md
    npm i
    

2.导出Chrome书签,并替换 bookmark2md/bookmarks.html 文件
3.npm run start

半只绵羊 一首诗

82 声望
5 粉丝
0 条评论
推荐阅读
Happy New Year!
2020 = 1024 + 996 Happy New Year!

OXOYO阅读 1.1k

JavaScript有用的代码片段和trick
平时工作过程中可以用到的实用代码集棉。判断对象否为空 {代码...} 浮点数取整 {代码...} 注意:前三种方法只适用于32个位整数,对于负数的处理上和Math.floor是不同的。 {代码...} 生成6位数字验证码 {代码...} ...

jenemy46阅读 5.9k评论 12

从零搭建 Node.js 企业级 Web 服务器(十五):总结与展望
总结截止到本章 “从零搭建 Node.js 企业级 Web 服务器” 主题共计 16 章内容就更新完毕了,回顾第零章曾写道:搭建一个 Node.js 企业级 Web 服务器并非难事,只是必须做好几个关键事项这几件必须做好的关键事项就...

乌柏木66阅读 6.1k评论 16

再也不学AJAX了!(二)使用AJAX ① XMLHttpRequest
「再也不学 AJAX 了」是一个以 AJAX 为主题的系列文章,希望读者通过阅读本系列文章,能够对 AJAX 技术有更加深入的认识和理解,从此能够再也不用专门学习 AJAX。本篇文章为该系列的第二篇,最近更新于 2023 年 1...

libinfs39阅读 6.3k评论 12

封面图
从零搭建 Node.js 企业级 Web 服务器(一):接口与分层
分层规范从本章起,正式进入企业级 Web 服务器核心内容。通常,一块完整的业务逻辑是由视图层、控制层、服务层、模型层共同定义与实现的,如下图:从上至下,抽象层次逐渐加深。从下至上,业务细节逐渐清晰。视图...

乌柏木43阅读 7.3k评论 6

CSS 绘制一只思否猫
欢迎关注我的公众号:前端侦探练习 CSS 有一个比较有趣的方式,就是发挥想象,绘制各式各样的图案,比如来绘制一只思否猫?思否猫,SegmentFault 思否的吉祥物,是一只独一无二、特立独行、热爱自由的(&gt;^ω^&lt...

XboxYan42阅读 2.9k评论 14

封面图
从零搭建 Node.js 企业级 Web 服务器(二):校验
校验就是对输入条件的约束,避免无效的输入引起异常。Web 系统的用户输入主要为编辑与提交各类表单,一方面校验要做在编辑表单字段与提交的时候,另一方面接收表单的接口也要做足校验行为,通过前后端共同控制输...

乌柏木33阅读 6.1k评论 9

半只绵羊 一首诗

82 声望
5 粉丝
宣传栏