diff --git a/README.md b/README.md index 5a249016707a31fb91f963bc6fdc2135a181c29b..8c9ae30ab89b332396bc52416e043cad5e1c8c02 100644 --- a/README.md +++ b/README.md @@ -48,16 +48,17 @@ - 🍰 三叉树分类、结构清晰、分类清晰。 - 🍰 支持一个网站多个分类。 - 🍰 颜值与简约并存,不再是杀马特时代。 +- 🍰 完全开源,轻松定制化。 - 🍰 支持多种浏览模式,创新。 - 🍰 支持足迹记忆。 - 🍰 支持移动端浏览。 - 🍰 支持搜索查询。 - 🍰 支持自定义引擎搜索。 -- 🍰 完全开源,轻松定制化。 - 🍰 多款主题切换。 - 🍰 支持暗黑模式。 - 🍰 支持快捷键操作,一步到位。 - 🍰 支持后台管理, 无需部署。 +- 🍰 支持从Chrome书签导入 @@ -93,6 +94,14 @@ server { ``` +## 书签导入 +支持从 Chrome 书签导入,必须满足三级分类,否则会导入失败: + +![](https://raw.githubusercontent.com/xjh22222228/public/gh-pages/nav/import.png) + +浏览器打开 [chrome://bookmarks/](chrome://bookmarks/) 导出书签得到 html 文件, 接着从导航网站后台导入即可。 + + diff --git a/src/assets/img/bookmark.svg b/src/assets/img/bookmark.svg new file mode 100644 index 0000000000000000000000000000000000000000..17de6bb55698f41d6610fae4ec7337cf02228747 --- /dev/null +++ b/src/assets/img/bookmark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/utils/bookmark.ts b/src/utils/bookmark.ts new file mode 100644 index 0000000000000000000000000000000000000000..ac322ecb074664a08d1019c4d3f1630ee78cf995 --- /dev/null +++ b/src/utils/bookmark.ts @@ -0,0 +1,112 @@ +// Copyright @ 2018-2021 xiejiahe. All rights reserved. MIT license. +// See https://github.com/xjh22222228/nav + +import { INavProps } from '../types' +import { websiteList } from '../store' + +export function parseBookmark(htmlStr: string) { + const data: INavProps[] = [] + const importEl = document.createElement('div') + importEl.innerHTML = htmlStr + const roolDL = importEl.querySelector('dl dl') + + let ii = 0 + let jj = 0 + let kk = 0 + try { + // One Level + for (let i = 0; i < roolDL.childElementCount; i++) { + const iItem = roolDL.childNodes[i] as any + if (iItem && iItem.nodeName === 'DT') { + const titleEl = iItem.querySelector('h3') + if (!titleEl) continue + ii++ + const title = titleEl.textContent + const createdAt = new Date(titleEl.getAttribute('add_date') * 1000).toISOString() + data.push({ + title, + createdAt, + icon: null, + nav: [] + }) + + // Two Level + jj = 0 + const DL = iItem.querySelector('dl') + for (let j = 0; j < DL.childElementCount; j++) { + const jItem = DL.childNodes[j] + if (jItem && jItem.nodeName === 'DT') { + const titleEl = jItem.querySelector('h3') + if (!titleEl) continue + jj++ + const title = titleEl.textContent + const createdAt = new Date(titleEl.getAttribute('add_date') * 1000).toISOString() + data[ii - 1].nav.push({ + title, + createdAt, + icon: null, + nav: [] + }) + + // Three Level + kk = 0 + const DL3 = jItem.querySelector('dl') + for (let k = 0; k < DL3.childElementCount; k++) { + const kItem = DL3.childNodes[k] + if (kItem && kItem.nodeName === 'DT') { + const titleEl = kItem.querySelector('h3') + if (!titleEl) continue + kk++ + const title = titleEl.textContent + const createdAt = new Date(titleEl.getAttribute('add_date') * 1000).toISOString() + data[ii - 1].nav[jj - 1].nav.push({ + title, + createdAt, + nav: [], + icon: null + }) + + // Website Level + const DL3 = kItem.querySelector('dl') + for (let b = 0; b < DL3.childElementCount; b++) { + const kItem = DL3.childNodes[b] + if (kItem && kItem.nodeName === 'DT') { + const titleEl = kItem.querySelector('a') + if (!titleEl) continue + const title = titleEl.textContent + const createdAt = new Date(titleEl.getAttribute('add_date') * 1000).toISOString() + const icon = titleEl.getAttribute('icon') || null + const url = titleEl.getAttribute('href') + data[ii - 1].nav[jj - 1].nav[kk - 1].nav.push({ + name: title, + createdAt, + url, + desc: '', + urls: {}, + icon + }) + } + } + } + } + } + } + } + } + } catch (error) { + return error + } + + const mergeList = [...data, ...websiteList] + const newList = []; + + for (let i = 0; i < mergeList.length; i++) { + const item = mergeList[i] + const exists = newList.some(el => el.title === item.title) + if (!exists) { + newList.push(item) + } + } + + return newList +} diff --git a/src/view/admin/index.component.html b/src/view/admin/index.component.html index 3b4ed4855d8b8adc1a4ba30a7653cc3e3b20028b..febee110cde455091454050d9b242b16c0b3665d 100644 --- a/src/view/admin/index.component.html +++ b/src/view/admin/index.component.html @@ -16,11 +16,24 @@ + +
+ +
diff --git a/src/view/admin/index.component.scss b/src/view/admin/index.component.scss index a51eeb4fd37f627a2489c52cbc7939b214493e10..6f7d3c07880b39ba385a02a6d136bd84844aef9f 100644 --- a/src/view/admin/index.component.scss +++ b/src/view/admin/index.component.scss @@ -13,14 +13,30 @@ text-align: center; cursor: pointer; text-align: center; + + .logo { + width: 100px; + height: 100px; + border: 1px solid #f2f2f2; + border-radius: 50%; + cursor: pointer; + } } - .logo { - width: 100px; - height: 100px; - border: 1px solid #f2f2f2; - border-radius: 50%; + .book-wrapper { + position: absolute; + top: -45px; + left: 650px; + width: 180px; + text-align: center; cursor: pointer; + text-align: center; + + .logo { + width: 100px; + height: 100px; + cursor: pointer; + } } input[type="file"] { diff --git a/src/view/admin/index.component.ts b/src/view/admin/index.component.ts index 19968604a780d7095de61afa02312db337d496ae..ccdf0718f4da2abdcb570f0066b1d31844ef9d0d 100644 --- a/src/view/admin/index.component.ts +++ b/src/view/admin/index.component.ts @@ -15,6 +15,7 @@ import { updateFileContent } from '../../services' import { DB_PATH, LOGO_PATH } from '../../constants' import * as __tag from '../../../data/tag.json' import config from '../../../nav.config' +import { parseBookmark } from '../../utils/bookmark' const tagMap: ITagProp = (__tag as any).default const tagKeys = Object.keys(tagMap) @@ -70,7 +71,31 @@ export default class WebpComponent { }); } - onFileChange(e) { + onBookChange(e) { + const that = this + const { files } = e.target + if (files.length <= 0) return; + const file = files[0] + const fileReader = new FileReader() + fileReader.readAsText(file) + fileReader.onload = function() { + const html = this.result as string + const result = parseBookmark(html) + if (!Array.isArray(result)) { + that.notification.error( + `错误: 书签解析失败`, + `${result?.message ?? ''}` + ) + } else { + that.message.success('导入成功,2秒后刷新!') + that.websiteList = result + setWebsiteList(that.websiteList) + setTimeout(() => window.location.reload(), 2000) + } + } + } + + onLogoChange(e) { const that = this const { files } = e.target if (files.length <= 0) return;