You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

368 lines
10 KiB

// ==================================
// dom action helper
const domHelper = {
loadScript (url) {
var script = document.createElement('script')
script.type = 'text/javacript'
script.src = url
document.body.appendChild(script)
},
loadStyle (url) {
var sytle = document.createElement('link')
sytle.rel = 'stylesheet'
sytle.type = 'text/css'
sytle.href = url
sytle.media = 'screen'
var headobj = document.getElementsByTagName('head')[0]
headobj.appendChild(sytle)
}
}
// ==================================
// chrome bookmark api wrapper
const api = {
/**
* 获得整个书签树
*/
getTreeAsync () {
return new Promise(resolve => {
chrome.bookmarks.getTree(resolve)
})
},
/**
* 获得特定父书签组下的所有子书签和子书签组
* 返回的书签数组中是不包含children字段的即不包含子节点以下的节点
* @param {String} id 父书签组id
*/
getChildrenAsync (id) {
return new Promise(resolve => {
chrome.bookmarks.getChildren(id, resolve)
})
},
/**
* 获得特定书签组下的所有书签
* 返回的书签数组中包含children字段即包含子节点以下的节点
* @param {String} id 父书签组id
*/
getSubTreeAsync (id) {
return new Promise(resolve => {
chrome.bookmarks.getSubTree(id, resolve)
})
},
/**
* 删除指定id的书签
* @param {String} id 需要删除的书签的id
*/
removeAsync (id) {
return new Promise(resolve => {
chrome.bookmarks.remove(id, resolve)
})
},
/**
* 删除指定id的空书签组如果书签组下有子书签或子书签组删除将失败
* @param {String} id 需要删除的书签文件夹id
*/
removeTreeAsync (id) {
return new Promise(resolve => {
chrome.bookmarks.removeTree(id, resolve)
})
},
/**
* 创建一个书签
* @param {Object} bookmark
* @param {String} (optional) bookmark.parentId 父书签组如果不填则默认在**其他书签**一栏中
* @param {Number} (optional) bookmark.index
* @param {String} bookmark.title
* @param {String} (optional) bookmark.url 如果为NULL或者不填则代表一个书签组文件夹
*/
createAsync (bookmark) {
return new Promise(resolve => {
chrome.bookmarks.create(bookmark, resolve)
})
},
/**
* 获得浏览器书签的数组
*/
async getBookmarkList () {
function tree2List (tree) {
const cacheMap = {}
function add2Map (tree) {
for (const item of tree) {
// 给书签文件创建group属性
if (
parseInt(item.id) < 3 ||
typeof item.dateGroupModified === 'number'
) {
item.group = true
}
cacheMap[item.id] = item
if (Array.isArray(item.children)) {
add2Map(item.children)
}
}
}
add2Map(tree)
for (const key in cacheMap) {
const item = cacheMap[key]
if (typeof item.parentId === 'string') {
if (!Array.isArray(cacheMap[item.parentId].nodes)) {
cacheMap[item.parentId].nodes = []
}
cacheMap[item.parentId].nodes.push(item.id)
}
}
const list = []
for (const key in cacheMap) {
const item = cacheMap[key]
list.push({
id: item.id,
parentId: item.parentId,
title: item.title,
url: item.url,
index: item.index,
group: item.group,
nodes: item.nodes
})
}
return list
}
const localTree = await api.getTreeAsync()
return tree2List(localTree)
}
}
// ==================================
// backend server api wrapper
const serverApi = (function () {
const API_VERSION = 'v1'
let serverAddress = ''
const setServerAddress = (address) => {
serverAddress = address
}
const getServerAddress = () => {
return serverAddress
}
const getVersion = () => {
return new Promise((resolve, reject) => {
const xhttp = new XMLHttpRequest()
xhttp.onreadystatechange = function () {
if (this.readyState === 4 && this.status === 200) {
const result = JSON.parse(this.responseText)
resolve(result.data)
}
}
xhttp.open('GET', `${serverAddress}/${API_VERSION}/version`, true)
xhttp.send()
})
}
const uploadBookmarks = (data) => {
return new Promise((resolve, reject) => {
const xhttp = new XMLHttpRequest()
xhttp.open('POST', `${serverAddress}/${API_VERSION}/bookmark`, true)
xhttp.setRequestHeader('content-type', 'application/json')
xhttp.onreadystatechange = function () {
if (this.readyState === 4 && this.status === 200) {
const result = JSON.parse(this.responseText)
resolve(result.data)
}
}
// 将用户输入值序列化成字符串
xhttp.send(JSON.stringify(data))
})
}
const downloadBookmarks = () => {
return new Promise((resolve, reject) => {
const xhttp = new XMLHttpRequest()
xhttp.open('GET', `${serverAddress}/${API_VERSION}/bookmark`, true)
xhttp.onreadystatechange = function () {
if (this.readyState === 4 && this.status === 200) {
const result = JSON.parse(this.responseText)
resolve(result.data)
}
}
xhttp.send()
})
}
return {
setServerAddress,
getServerAddress,
getVersion,
uploadBookmarks,
downloadBookmarks
}
})()
// ==================================
// starter
const PROTO = 'http'
const SERVER_ADDRESS = '192.168.31.238'
const SERVER_PORT = '3000'
const SERVER_URL = `${PROTO}://${SERVER_ADDRESS}:${SERVER_PORT}`
/**
* 数组转map表
* @param {Object} bookmarkList
*/
function listToMap (bookmarkList) {
const map = {}
for (let i = 0; i < bookmarkList.length; i++) {
const bookmark = bookmarkList[i]
map[bookmark.id] = bookmark
}
function getDepth (bookmark, depth) {
if (parseInt(bookmark.id) === 0) {
return depth
} else {
return getDepth(map[bookmark.parentId], depth + 1)
}
}
const finalMap = {}
for (let i = 0; i < bookmarkList.length; i++) {
const bookmark = bookmarkList[i]
const finalDepth = getDepth(bookmark, 0)
if (!Array.isArray(finalMap[finalDepth])) {
finalMap[finalDepth] = []
}
finalMap[finalDepth].push(bookmark)
}
return finalMap
}
async function removeAllBookmarks () {
async function removeBookmark (item) {
// 不可删除节点
if (parseInt(item.id) <= 2) {
return
}
try {
if (Array.isArray(item.children)) {
await api.removeTreeAsync(item.id)
} else {
await api.removeAsync(item.id)
}
console.log(`remove bookmark "${item.title}" success`)
} catch (err) {
console.error(`remove bookmark "${item.title}" error`)
console.error(err)
}
}
async function dfsRemove (tree) {
if (!Array.isArray(tree)) {
throw new Error('tree should be an array')
}
for (const item of tree) {
console.log(`checking item ${item.title}`)
if (Array.isArray(item.children) && item.children.length > 0) {
// 根节点
await dfsRemove(item.children)
}
await removeBookmark(item)
}
}
const localTree = await api.getTreeAsync()
await dfsRemove(localTree)
}
/**
* 覆盖同步
*/
async function restoreByOverwrite () {
const remoteBookmarkArray = await serverApi.downloadBookmarks()
function getNewbookmarkId (array, parentId) {
for (const item of array) {
if (item.id === parentId) {
return item.newId
}
}
}
const map = listToMap(remoteBookmarkArray)
const maxDepth = Object.keys(map).length
for (let depth = 0; depth < maxDepth; depth++) {
const bookmarks = map[depth]
for (const bookmark of bookmarks) {
// 第0层应该自动过滤
if (parseInt(bookmark.id) < 3) {
bookmark.newId = bookmark.id
continue
}
try {
const newParentId = getNewbookmarkId(map[depth - 1], bookmark.parentId)
const newBookmark = await api.createAsync({
parentId: newParentId,
index: bookmark.index,
title: bookmark.title,
url: bookmark.url
})
bookmark.newId = newBookmark.id
} catch (error) {
console.error(`create bookmark ${bookmark.titel} error`)
console.error(error)
}
}
}
}
/**
* 合并同步
*/
async function restoreByMerge () {
const remoteBookmarks = await serverApi.downloadBookmarks()
const localBookmarks = await api.getBookmarkList()
console.log(remoteBookmarks)
console.log(localBookmarks)
}
const connectButton = document.getElementById('connect')
if (connectButton) {
connectButton.addEventListener('click', async () => {
const serverAddress = document.getElementById('server').value || SERVER_URL
serverApi.setServerAddress(serverAddress)
const version = await serverApi.getVersion()
document.getElementById('version').innerHTML = version
})
}
const uploadButton = document.getElementById('upload')
if (uploadButton) {
uploadButton.addEventListener('click', async () => {
const bookmarks = await api.getBookmarkList()
serverApi.uploadBookmarks({ bookmarks })
})
}
const overwriteSyncButton = document.getElementById('overwriteSync')
if (overwriteSyncButton) {
overwriteSyncButton.addEventListener('click', async () => {
return restoreByOverwrite()
})
}
const mergeSyncButton = document.getElementById('mergeSync')
if (mergeSyncButton) {
mergeSyncButton.addEventListener('click', async () => {
return restoreByMerge()
})
}
const removeButton = document.getElementById('removeAll')
if (removeButton) {
removeButton.addEventListener('click', async () => {
await removeAllBookmarks()
const bookmarkArray = await api.getBookmarkList()
if (Array.isArray(bookmarkArray) && bookmarkArray.length === 3) {
console.log('clear bookmarks success')
} else {
console.error('clear bookmarks error')
}
})
}
domHelper.loadScript('./js/test.js')
domHelper.loadStyle('./css/normalize.css')
domHelper.loadStyle('./css/style.css')