Signed-off-by: Marcel Klehr <mklehr@gmx.net>
This commit is contained in:
Marcel Klehr 2023-12-09 10:10:39 +01:00
Родитель 8fc6ab688f
Коммит 08b4466271
5 изменённых файлов: 267 добавлений и 53 удалений

Просмотреть файл

@ -2,7 +2,7 @@
"manifest_version": 3,
"name": "floccus bookmarks sync",
"short_name": "floccus",
"version": "4.19.1",
"version": "5.0.0",
"description": "__MSG_DescriptionExtension__",
"icons": {
"48": "icons/logo.png",
@ -10,13 +10,6 @@
"128": "icons/logo_128.png"
},
"applications": {
"gecko": {
"id": "floccus@handmadeideas.org",
"strict_min_version": "57.0"
}
},
"default_locale": "en",
"permissions": ["alarms", "bookmarks", "storage", "unlimitedStorage", "tabs", "identity"],
@ -42,6 +35,6 @@
},
"background": {
"scripts": ["dist/js/background-script.js"]
"service_worker": "dist/js/background-script.js"
}
}

170
package-lock.json сгенерированный
Просмотреть файл

@ -24,6 +24,7 @@
"async-lock": "^1.2.8",
"async-parallel": "^1.2.3",
"batching-toposort": "^1.2.0",
"cheerio": "^1.0.0-rc.12",
"cordova-plugin-device": "^2.1.0",
"cordova-plugin-inappbrowser": "^5.0.0",
"core-js": "3.x",
@ -4188,8 +4189,7 @@
"node_modules/boolbase": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
"dev": true
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="
},
"node_modules/boolean": {
"version": "3.2.0",
@ -4626,6 +4626,42 @@
"node": "*"
}
},
"node_modules/cheerio": {
"version": "1.0.0-rc.12",
"resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz",
"integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==",
"dependencies": {
"cheerio-select": "^2.1.0",
"dom-serializer": "^2.0.0",
"domhandler": "^5.0.3",
"domutils": "^3.0.1",
"htmlparser2": "^8.0.1",
"parse5": "^7.0.0",
"parse5-htmlparser2-tree-adapter": "^7.0.0"
},
"engines": {
"node": ">= 6"
},
"funding": {
"url": "https://github.com/cheeriojs/cheerio?sponsor=1"
}
},
"node_modules/cheerio-select": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz",
"integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==",
"dependencies": {
"boolbase": "^1.0.0",
"css-select": "^5.1.0",
"css-what": "^6.1.0",
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3",
"domutils": "^3.0.1"
},
"funding": {
"url": "https://github.com/sponsors/fb55"
}
},
"node_modules/chokidar": {
"version": "2.1.8",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz",
@ -5288,6 +5324,32 @@
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
},
"node_modules/css-select": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz",
"integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==",
"dependencies": {
"boolbase": "^1.0.0",
"css-what": "^6.1.0",
"domhandler": "^5.0.2",
"domutils": "^3.0.1",
"nth-check": "^2.0.1"
},
"funding": {
"url": "https://github.com/sponsors/fb55"
}
},
"node_modules/css-what": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz",
"integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==",
"engines": {
"node": ">= 6"
},
"funding": {
"url": "https://github.com/sponsors/fb55"
}
},
"node_modules/cssesc": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
@ -5595,6 +5657,19 @@
"node": ">=6.0.0"
}
},
"node_modules/dom-serializer": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
"integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
"dependencies": {
"domelementtype": "^2.3.0",
"domhandler": "^5.0.2",
"entities": "^4.2.0"
},
"funding": {
"url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
}
},
"node_modules/domain-browser": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz",
@ -5605,6 +5680,44 @@
"npm": ">=1.2"
}
},
"node_modules/domelementtype": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
"integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/fb55"
}
]
},
"node_modules/domhandler": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
"integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
"dependencies": {
"domelementtype": "^2.3.0"
},
"engines": {
"node": ">= 4"
},
"funding": {
"url": "https://github.com/fb55/domhandler?sponsor=1"
}
},
"node_modules/domutils": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz",
"integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==",
"dependencies": {
"dom-serializer": "^2.0.0",
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3"
},
"funding": {
"url": "https://github.com/fb55/domutils?sponsor=1"
}
},
"node_modules/duplexify": {
"version": "3.7.1",
"resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz",
@ -5737,6 +5850,17 @@
"node": ">=8.6"
}
},
"node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/env-paths": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
@ -8391,6 +8515,24 @@
"integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==",
"dev": true
},
"node_modules/htmlparser2": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz",
"integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==",
"funding": [
"https://github.com/fb55/htmlparser2?sponsor=1",
{
"type": "github",
"url": "https://github.com/sponsors/fb55"
}
],
"dependencies": {
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3",
"domutils": "^3.0.1",
"entities": "^4.4.0"
}
},
"node_modules/http-cache-semantics": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz",
@ -10982,7 +11124,6 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
"integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
"dev": true,
"dependencies": {
"boolbase": "^1.0.0"
},
@ -11433,6 +11574,29 @@
"node": ">=0.10.0"
}
},
"node_modules/parse5": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz",
"integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==",
"dependencies": {
"entities": "^4.4.0"
},
"funding": {
"url": "https://github.com/inikulin/parse5?sponsor=1"
}
},
"node_modules/parse5-htmlparser2-tree-adapter": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz",
"integrity": "sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==",
"dependencies": {
"domhandler": "^5.0.2",
"parse5": "^7.0.0"
},
"funding": {
"url": "https://github.com/inikulin/parse5?sponsor=1"
}
},
"node_modules/pascalcase": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz",

Просмотреть файл

@ -85,6 +85,7 @@
"async-lock": "^1.2.8",
"async-parallel": "^1.2.3",
"batching-toposort": "^1.2.0",
"cheerio": "^1.0.0-rc.12",
"cordova-plugin-device": "^2.1.0",
"cordova-plugin-inappbrowser": "^5.0.0",
"core-js": "3.x",

Просмотреть файл

@ -465,7 +465,8 @@ function createXBEL(rootFolder, highestId) {
function createHTML(rootFolder, highestId) {
let output = `<!DOCTYPE NETSCAPE-Bookmark-file-1>
<html>`
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8">
<TITLE>Bookmarks</TITLE>`
output +=
'<!--- highestId :' +

Просмотреть файл

@ -1,9 +1,10 @@
import Serializer from '../interfaces/Serializer'
import { Bookmark, Folder, ItemLocation } from '../Tree'
import { Bookmark, Folder, ItemLocation, ItemType, TItem, TItemType } from '../Tree'
import { load } from 'cheerio'
class HtmlSerializer implements Serializer {
serialize(folder) {
return `<DL><p>${this._serializeFolder(folder, '')}</p></DL>`
return `<DL><p>\n${this._serializeFolder(folder, '')}</DL><p>\n`
}
_serializeFolder(folder, indent) {
@ -12,58 +13,112 @@ class HtmlSerializer implements Serializer {
if (child instanceof Bookmark) {
return (
`${indent}<DT>` +
`<A HREF="${child.url}" TAGS="${''}" id="${child.id}">${child.title}</A>`
`<A HREF="${child.url}" TAGS="${''}" ID="${child.id}">${child.title}</A>\n`
)
} else if (child instanceof Folder) {
const nextIndent = indent + ' '
return (
`${indent}<DT><h3 id="${child.id}">${child.title}</h3>\n` +
`${indent}<DL><p>${this._serializeFolder(
`${indent}<DT><H3 ID="${child.id}">${child.title}</H3>\n` +
`${indent}<DL><p>\n${this._serializeFolder(
child,
nextIndent
)}</p></DL>`
)}${indent}</DL><p>\n`
)
}
})
.join('\n')
.join('')
}
deserialize(html): Folder {
const parser = new DOMParser()
const document = parser.parseFromString(html, 'text/html')
const rootFolder = new Folder({id: '', title: '', location: ItemLocation.SERVER})
const dl = document.querySelector('dl')
const counter = {highestId: 1}
deserializeDL(dl, rootFolder, counter)
return rootFolder
}
}
function deserializeDL(dl:Element, parentFolder:Folder, counter:{highestId: number}) {
for (let element:Element = dl.querySelector('dt'); element; element = element.nextElementSibling) {
const child = element.firstElementChild
if (child instanceof HTMLHeadingElement) {
const folder = new Folder({
parentId: parentFolder.id,
title: child.textContent,
id: child.id ? parseInt(child.id) : counter.highestId++,
location: ItemLocation.SERVER
})
parentFolder.children.push(folder)
if (child.nextElementSibling instanceof HTMLDListElement) {
const dl = child.nextElementSibling
deserializeDL(dl, folder, counter)
}
} else if (child instanceof HTMLAnchorElement) {
parentFolder.children.push(new Bookmark({
parentId: parentFolder.id,
url: child.href,
title: child.textContent,
id: child.id ? parseInt(child.id) : counter.highestId++,
location: ItemLocation.SERVER
}))
const folders: Folder[] = parseByString(html)
if (folders.length === 1) {
folders[0].isRoot = true
return folders[0]
} else {
folders.forEach(f => f.parentId = '0')
return new Folder({id: '0', title: 'root', children: folders, location: ItemLocation.SERVER, isRoot: true})
}
}
}
export default new HtmlSerializer()
// The following code is based on https://github.com/hold-baby/bookmark-file-parser
// Copyright (c) 2019 hold-baby
// MIT License
export const getRootFolder = (body: cheerio.Cheerio) => {
const h3 = body.find('h3').first()
const isChrome = typeof h3.attr('personal_toolbar_folder') === 'string'
if (isChrome) {
return body.children('dl').first()
}
const isSafari = typeof h3.attr('folded') === 'string'
if (isSafari) {
return body
}
const isIE = typeof h3.attr('item_id') === 'string'
if (isIE) {
return body.children('dl').first()
}
const isFireFox = h3.text() === 'Mozilla Firefox'
if (isFireFox) {
return body.children('dl').first()
}
return body.children('dl').first()
}
export const parseByString = (content: string) => {
const $ = load(content, {
decodeEntities: false
})
const body = $('body')
const root: Folder[] = []
const rdt = getRootFolder(body).children('dt')
const parseNode = (node: cheerio.Cheerio, parentId) => {
const eq0 = node.children().eq(0)
const title = eq0.html() || ''
let type: TItemType = ItemType.BOOKMARK
let url = ''
let id = eq0.attr('id') || ''
let children: TItem[] = []
switch (eq0[0].name) {
case 'h3':
// folder
const dl = node.children('dl').first()
const dts = dl.children()
const ls = dts.toArray().map((ele) => {
if (ele.name !== 'dt') return null
return parseNode($(ele), id)
})
children = ls.filter((item) => item !== null) as TItem[]
return new Folder({id, title, parentId, children, location: ItemLocation.SERVER})
case 'a':
// site
url = eq0.attr('href') || ''
return new Bookmark({id, title, url, parentId, location: ItemLocation.SERVER})
}
throw new Error('Failed to parse')
}
rdt.each((_, item) => {
const node = $(item)
const child = parseNode(node)
root.push(child)
})
return root
}