提交 118c40cc 编写于 作者: X xjh22222228

feat: Bookmark import

上级 9b2773b5
...@@ -48,16 +48,17 @@ ...@@ -48,16 +48,17 @@
- 🍰 三叉树分类、结构清晰、分类清晰。 - 🍰 三叉树分类、结构清晰、分类清晰。
- 🍰 支持一个网站多个分类。 - 🍰 支持一个网站多个分类。
- 🍰 颜值与简约并存,不再是杀马特时代。 - 🍰 颜值与简约并存,不再是杀马特时代。
- 🍰 完全开源,轻松定制化。
- 🍰 支持多种浏览模式,创新。 - 🍰 支持多种浏览模式,创新。
- 🍰 支持足迹记忆。 - 🍰 支持足迹记忆。
- 🍰 支持移动端浏览。 - 🍰 支持移动端浏览。
- 🍰 支持搜索查询。 - 🍰 支持搜索查询。
- 🍰 支持自定义引擎搜索。 - 🍰 支持自定义引擎搜索。
- 🍰 完全开源,轻松定制化。
- 🍰 多款主题切换。 - 🍰 多款主题切换。
- 🍰 支持暗黑模式。 - 🍰 支持暗黑模式。
- 🍰 支持快捷键操作,一步到位。 - 🍰 支持快捷键操作,一步到位。
- 🍰 支持后台管理, 无需部署。 - 🍰 支持后台管理, 无需部署。
- 🍰 支持从Chrome书签导入
...@@ -93,6 +94,14 @@ server { ...@@ -93,6 +94,14 @@ server {
``` ```
## 书签导入
支持从 Chrome 书签导入,必须满足三级分类,否则会导入失败:
![](https://raw.githubusercontent.com/xjh22222228/public/gh-pages/nav/import.png)
浏览器打开 [chrome://bookmarks/](chrome://bookmarks/) 导出书签得到 html 文件, 接着从导航网站后台导入即可。
......
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1612860706297" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4885" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M823.7 79.9c13.8 0 26.8 5.4 36.6 15.2 9.8 9.8 15.2 22.8 15.2 36.6v733.8c0 13.8-5.4 26.8-15.2 36.6-9.8 9.8-22.8 15.2-36.6 15.2H200.3c-13.8 0-26.8-5.4-36.6-15.2-9.8-9.8-15.2-22.8-15.2-36.6V131.8c0-13.8 5.4-26.8 15.2-36.6 9.8-9.8 22.8-15.2 36.6-15.2h623.4" fill="#FFB36E" p-id="4886"></path><path d="M618.5 79.9H200.3c-13.8 0-26.8 5.4-36.6 15.2-9.8 9.8-15.2 22.8-15.2 36.6L142.6 820c0 5 92.1-22.8 93.5-18.1l382.4-722z" fill="#FFBB88" p-id="4887"></path><path d="M891.5 667.3c-0.1-4.3-3.7-7.8-8-7.8s-7.9 3.5-8 7.8c0 23.1-9.1 44.8-25.5 61.2-16.4 16.4-38.2 25.5-61.2 25.5H235.2c-56.5 0-102.7 46.2-102.7 102.7s46.2 102.7 102.7 102.7h553.6c56.5 0 102.7-46.2 102.7-102.7V667.4v-0.1z m-15.9 189.5c-0.1 0-0.1 0 0 0 0 23.1-9.1 44.8-25.5 61.2-16.4 16.4-38.2 25.5-61.2 25.5H235.2c-23.1 0-44.8-9.1-61.2-25.5-16.4-16.4-25.5-38.2-25.5-61.2s9.1-44.8 25.5-61.2c16.4-16.4 38.2-25.5 61.2-25.5h553.6c36.4 0 68.5-19.2 86.7-47.9v134.3c0 0.1 0 0.2 0.1 0.3z" fill="#581166" p-id="4888"></path><path d="M823.7 79.9c13.8 0 26.8 5.4 36.6 15.2 9.8 9.8 15.2 22.8 15.2 36.6v733.8c0 13.8-5.4 26.8-15.2 36.6-9.8 9.8-22.8 15.2-36.6 15.2H200.3c-13.8 0-26.8-5.4-36.6-15.2-9.8-9.8-15.2-22.8-15.2-36.6V131.8c0-13.8 5.4-26.8 15.2-36.6 9.8-9.8 22.8-15.2 36.6-15.2h623.4m0-16.1H200.3c-37.3 0-67.9 30.5-67.9 67.9v733.8c0 37.3 30.5 67.9 67.9 67.9h623.3c37.3 0 45.6-44.1 67.9-76.7v-725c0-37.3-30.5-67.9-67.8-67.9z" fill="#581166" p-id="4889"></path><path d="M875.6 856.8c-0.1 0-0.1 0 0 0 0 23.1-9.1 44.8-25.5 61.2-16.4 16.4-38.2 25.5-61.2 25.5H235.2c-23.1 0-44.8-9.1-61.2-25.5-16.4-16.4-25.5-38.2-25.5-61.2s9.1-44.8 25.5-61.2c16.4-16.4 38.2-25.5 61.2-25.5h553.6c36.4 0 68.5-19.2 86.7-47.9v134.3c0 0.1 0 0.2 0.1 0.3z" fill="#FFEBDE" p-id="4890"></path><path d="M883.6 722.2c-4.4 0-8 3.6-8 8v35c0 23.1-9.1 44.8-25.5 61.2-16.4 16.4-38.2 25.5-61.2 25.5H252.7c-4.4 0-8 3.6-8 8s3.6 8 8 8H792c53.5-1.6 97.1-44.6 99.6-97.9 0.1-0.5 0.1-0.9 0.1-1.4v-38.4c-0.1-4.4-3.7-8-8.1-8z" fill="#581166" p-id="4891"></path><path d="M445.2 867.9v81.6l-53.8-42.1-8.6-6.7-8.6 6.7-53.8 42.1v-81.6h124.8m14.7-16H305.7v128.7l77.1-60.3 77.1 60.3V851.9z" fill="#581166" p-id="4892"></path><path d="M445.2 867.9v81.5l-53.9-42.1-8.5-6.6-8.6 6.6-53.8 42.1v-81.5h124.8" fill="#FF5855" p-id="4893"></path><path d="M719.6 79.9V241H689V79.9h30.6m16-16H673V257h62.6V63.9z" fill="#581166" p-id="4894"></path><path d="M719.6 79.9V241h-30.7V79.9h30.7" fill="#FFD18D" p-id="4895"></path><path d="M719.6 368.3V754H689V368.3h30.6m16-16H673V770h62.6V352.3z" fill="#581166" p-id="4896"></path><path d="M719.6 368.3v385.8h-30.7V368.3h30.7" fill="#FFD18D" p-id="4897"></path><path d="M704.2 273.3m-103 0a103 103 0 1 0 206 0 103 103 0 1 0-206 0Z" fill="#FF5855" p-id="4898"></path><path d="M704.2 186.3c48 0 87 39 87 87s-39 87-87 87-87-39-87-87 39.1-87 87-87m0-16c-56.9 0-103 46.1-103 103s46.1 103 103 103 103-46.1 103-103c0.1-56.9-46.1-103-103-103z" fill="#581166" p-id="4899"></path><path d="M704.2 273.3m-42.3 0a42.3 42.3 0 1 0 84.6 0 42.3 42.3 0 1 0-84.6 0Z" fill="#FFFFFF" p-id="4900"></path><path d="M704.2 247c14.5 0 26.3 11.8 26.3 26.3s-11.8 26.3-26.3 26.3-26.3-11.8-26.3-26.3 11.8-26.3 26.3-26.3m0-16c-23.4 0-42.3 19-42.3 42.3 0 23.4 19 42.3 42.3 42.3 23.4 0 42.3-19 42.3-42.3 0.1-23.4-18.9-42.3-42.3-42.3z" fill="#581166" p-id="4901"></path></svg>
\ No newline at end of file
// 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
}
...@@ -16,11 +16,24 @@ ...@@ -16,11 +16,24 @@
<input <input
type="file" type="file"
name="file" name="file"
(change)="onFileChange($event)" (change)="onLogoChange($event)"
accept="image/png" accept="image/png"
> >
</label> </label>
</div> </div>
<div class="book-wrapper">
<label id="file">
<img src="assets/img/bookmark.svg" alt="" class="logo">
<p style="white-space: nowrap; margin-top: 5px;">从Chrome书签导入</p>
<input
type="file"
name="file"
(change)="onBookChange($event)"
accept="text/html"
>
</label>
</div>
</div> </div>
<nz-tabset *ngIf="isLogin" [nzSelectedIndex]="tabActive" (nzSelectedIndexChange)="onTabChange($event)"> <nz-tabset *ngIf="isLogin" [nzSelectedIndex]="tabActive" (nzSelectedIndexChange)="onTabChange($event)">
......
...@@ -13,14 +13,30 @@ ...@@ -13,14 +13,30 @@
text-align: center; text-align: center;
cursor: pointer; cursor: pointer;
text-align: center; text-align: center;
.logo {
width: 100px;
height: 100px;
border: 1px solid #f2f2f2;
border-radius: 50%;
cursor: pointer;
}
} }
.logo { .book-wrapper {
width: 100px; position: absolute;
height: 100px; top: -45px;
border: 1px solid #f2f2f2; left: 650px;
border-radius: 50%; width: 180px;
text-align: center;
cursor: pointer; cursor: pointer;
text-align: center;
.logo {
width: 100px;
height: 100px;
cursor: pointer;
}
} }
input[type="file"] { input[type="file"] {
......
...@@ -15,6 +15,7 @@ import { updateFileContent } from '../../services' ...@@ -15,6 +15,7 @@ import { updateFileContent } from '../../services'
import { DB_PATH, LOGO_PATH } from '../../constants' import { DB_PATH, LOGO_PATH } from '../../constants'
import * as __tag from '../../../data/tag.json' import * as __tag from '../../../data/tag.json'
import config from '../../../nav.config' import config from '../../../nav.config'
import { parseBookmark } from '../../utils/bookmark'
const tagMap: ITagProp = (__tag as any).default const tagMap: ITagProp = (__tag as any).default
const tagKeys = Object.keys(tagMap) const tagKeys = Object.keys(tagMap)
...@@ -70,7 +71,31 @@ export default class WebpComponent { ...@@ -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 that = this
const { files } = e.target const { files } = e.target
if (files.length <= 0) return; if (files.length <= 0) return;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册