mirror of
https://github.com/pawelmalak/flame.git
synced 2025-07-19 03:29:37 +02:00
Merge 8ba281ac4e
into 3c347c854c
This commit is contained in:
commit
ea4a1fe2c2
17 changed files with 356 additions and 8 deletions
|
@ -12,7 +12,7 @@ import { Bookmark, Category } from '../../../interfaces';
|
||||||
// Other
|
// Other
|
||||||
import classes from './BookmarkCard.module.css';
|
import classes from './BookmarkCard.module.css';
|
||||||
import { Icon } from '../../UI';
|
import { Icon } from '../../UI';
|
||||||
import { iconParser, isImage, isSvg, isUrl, urlParser } from '../../../utility';
|
import { iconParser, isImage, isBase64Image, isSvg, isUrl, urlParser } from '../../../utility';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
category: Category;
|
category: Category;
|
||||||
|
@ -54,7 +54,18 @@ export const BookmarkCard = (props: Props): JSX.Element => {
|
||||||
if (bookmark.icon) {
|
if (bookmark.icon) {
|
||||||
const { icon, name } = bookmark;
|
const { icon, name } = bookmark;
|
||||||
|
|
||||||
if (isImage(icon)) {
|
if(isBase64Image(icon)){
|
||||||
|
iconEl = (
|
||||||
|
<div className={classes.BookmarkIcon}>
|
||||||
|
<img
|
||||||
|
src={icon}
|
||||||
|
alt={`${name} icon`}
|
||||||
|
className={classes.CustomIcon}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else if (isImage(icon)) {
|
||||||
const source = isUrl(icon) ? icon : `/uploads/${icon}`;
|
const source = isUrl(icon) ? icon : `/uploads/${icon}`;
|
||||||
|
|
||||||
iconEl = (
|
iconEl = (
|
||||||
|
|
|
@ -7,6 +7,8 @@ import { State } from '../../store/reducers';
|
||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
import { actionCreators } from '../../store';
|
import { actionCreators } from '../../store';
|
||||||
|
|
||||||
|
import { importBookmark } from '../../store/action-creators';
|
||||||
|
|
||||||
// Typescript
|
// Typescript
|
||||||
import { Category, Bookmark } from '../../interfaces';
|
import { Category, Bookmark } from '../../interfaces';
|
||||||
|
|
||||||
|
@ -21,6 +23,7 @@ import {
|
||||||
Spinner,
|
Spinner,
|
||||||
Modal,
|
Modal,
|
||||||
Message,
|
Message,
|
||||||
|
FileButton,
|
||||||
} from '../UI';
|
} from '../UI';
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
|
@ -49,6 +52,11 @@ export const Bookmarks = (props: Props): JSX.Element => {
|
||||||
const { getCategories, setEditCategory, setEditBookmark } =
|
const { getCategories, setEditCategory, setEditBookmark } =
|
||||||
bindActionCreators(actionCreators, dispatch);
|
bindActionCreators(actionCreators, dispatch);
|
||||||
|
|
||||||
|
const { importBookmark } = bindActionCreators(
|
||||||
|
actionCreators,
|
||||||
|
dispatch
|
||||||
|
);
|
||||||
|
|
||||||
// Load categories if array is empty
|
// Load categories if array is empty
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!categories.length) {
|
if (!categories.length) {
|
||||||
|
@ -128,6 +136,10 @@ export const Bookmarks = (props: Props): JSX.Element => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const importFromFile = async (file: File) => {
|
||||||
|
await importBookmark({file});
|
||||||
|
};
|
||||||
|
|
||||||
const finishEditing = () => {
|
const finishEditing = () => {
|
||||||
setShowTable(false);
|
setShowTable(false);
|
||||||
setEditCategory(null);
|
setEditCategory(null);
|
||||||
|
@ -169,6 +181,11 @@ export const Bookmarks = (props: Props): JSX.Element => {
|
||||||
handler={finishEditing}
|
handler={finishEditing}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
<FileButton
|
||||||
|
name="Import From File"
|
||||||
|
icon="mdiPlusBox"
|
||||||
|
handler={async (f) => await importFromFile(f)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
.ActionButton {
|
||||||
|
/* background-color: var(--color-accent); */
|
||||||
|
border: 1.5px solid var(--color-accent);
|
||||||
|
border-radius: 4px;
|
||||||
|
color: var(--color-primary);
|
||||||
|
padding: 5px 15px;
|
||||||
|
transition: all 0.3s;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
max-width: 250px;
|
||||||
|
margin-right: 10px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ActionButton:hover {
|
||||||
|
background-color: var(--color-accent);
|
||||||
|
color: var(--color-background);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ActionButtonIcon {
|
||||||
|
--size: 20px;
|
||||||
|
width: var(--size);
|
||||||
|
height: var(--size);
|
||||||
|
/* display: flex; */
|
||||||
|
}
|
||||||
|
|
||||||
|
.ActionButtonName {
|
||||||
|
display: flex;
|
||||||
|
}
|
64
client/src/components/UI/Buttons/FileButton/FileButton.tsx
Normal file
64
client/src/components/UI/Buttons/FileButton/FileButton.tsx
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
import { ChangeEvent, Fragment, useRef } from 'react';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
import classes from './FileButton.module.css';
|
||||||
|
import { Icon } from '../..';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
name: string;
|
||||||
|
icon: string;
|
||||||
|
link?: string;
|
||||||
|
handler?: (file: File) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FileButton = (props: Props): JSX.Element => {
|
||||||
|
const imageRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
|
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
let target = e.target as HTMLInputElement;
|
||||||
|
|
||||||
|
if (target?.files && target?.files[0]) {
|
||||||
|
if(props.handler) props.handler(target?.files[0]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const open = () => {
|
||||||
|
imageRef?.current?.click();
|
||||||
|
};
|
||||||
|
|
||||||
|
const body = (
|
||||||
|
<Fragment>
|
||||||
|
<div className={classes.ActionButtonIcon}>
|
||||||
|
<Icon icon={props.icon} />
|
||||||
|
</div>
|
||||||
|
<div className={classes.ActionButtonName}>{props.name}</div>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (props.link) {
|
||||||
|
return (
|
||||||
|
<Link to={props.link} tabIndex={0}>
|
||||||
|
{body}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
} else if (props.handler) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={classes.ActionButton}
|
||||||
|
onClick={open}
|
||||||
|
tabIndex={0}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
ref={imageRef}
|
||||||
|
type="file"
|
||||||
|
style={{ display: 'none' }}
|
||||||
|
accept=".html"
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
{body}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return <div className={classes.ActionButton}>{body}</div>;
|
||||||
|
}
|
||||||
|
};
|
|
@ -14,4 +14,5 @@ export * from './Forms/InputGroup/InputGroup';
|
||||||
export * from './Forms/ModalForm/ModalForm';
|
export * from './Forms/ModalForm/ModalForm';
|
||||||
export * from './Buttons/ActionButton/ActionButton';
|
export * from './Buttons/ActionButton/ActionButton';
|
||||||
export * from './Buttons/Button/Button';
|
export * from './Buttons/Button/Button';
|
||||||
|
export * from './Buttons/FileButton/FileButton';
|
||||||
export * from './Text/Message/Message';
|
export * from './Text/Message/Message';
|
||||||
|
|
|
@ -11,3 +11,7 @@ export interface NewBookmark {
|
||||||
export interface Bookmark extends Model, NewBookmark {
|
export interface Bookmark extends Model, NewBookmark {
|
||||||
orderId: number;
|
orderId: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface BookmarkImport {
|
||||||
|
file: File
|
||||||
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { ActionType } from '../action-types';
|
||||||
import {
|
import {
|
||||||
ApiResponse,
|
ApiResponse,
|
||||||
Bookmark,
|
Bookmark,
|
||||||
|
BookmarkImport,
|
||||||
Category,
|
Category,
|
||||||
Config,
|
Config,
|
||||||
NewBookmark,
|
NewBookmark,
|
||||||
|
@ -18,6 +19,7 @@ import {
|
||||||
DeleteBookmarkAction,
|
DeleteBookmarkAction,
|
||||||
DeleteCategoryAction,
|
DeleteCategoryAction,
|
||||||
GetCategoriesAction,
|
GetCategoriesAction,
|
||||||
|
ImportBookmarkAction,
|
||||||
PinCategoryAction,
|
PinCategoryAction,
|
||||||
ReorderBookmarksAction,
|
ReorderBookmarksAction,
|
||||||
ReorderCategoriesAction,
|
ReorderCategoriesAction,
|
||||||
|
@ -108,6 +110,55 @@ export const addBookmark =
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const importBookmark =
|
||||||
|
(formData: BookmarkImport) =>
|
||||||
|
async (dispatch: Dispatch<ImportBookmarkAction>) => {
|
||||||
|
try {
|
||||||
|
let formd = new FormData();
|
||||||
|
formd.append("file", formData.file);
|
||||||
|
|
||||||
|
const res = await axios.post<ApiResponse<Bookmark>>(
|
||||||
|
'/api/bookmarks/import',
|
||||||
|
formd,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
...applyAuth(),
|
||||||
|
'Content-Type': 'multipart/form-data',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
dispatch<any>({
|
||||||
|
type: ActionType.createNotification,
|
||||||
|
payload: {
|
||||||
|
title: 'Success',
|
||||||
|
message: `Bookmark file imported.`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: ActionType.importBookmark,
|
||||||
|
payload: res.data.data,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fetch bookmarks after import.
|
||||||
|
try {
|
||||||
|
const res = await axios.get<ApiResponse<Category[]>>('/api/categories', {
|
||||||
|
headers: applyAuth(),
|
||||||
|
});
|
||||||
|
|
||||||
|
dispatch<any>({
|
||||||
|
type: ActionType.getCategoriesSuccess,
|
||||||
|
payload: res.data.data,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const pinCategory =
|
export const pinCategory =
|
||||||
(category: Category) => async (dispatch: Dispatch<PinCategoryAction>) => {
|
(category: Category) => async (dispatch: Dispatch<PinCategoryAction>) => {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -47,6 +47,7 @@ export enum ActionType {
|
||||||
setEditBookmark = 'SET_EDIT_BOOKMARK',
|
setEditBookmark = 'SET_EDIT_BOOKMARK',
|
||||||
reorderBookmarks = 'REORDER_BOOKMARKS',
|
reorderBookmarks = 'REORDER_BOOKMARKS',
|
||||||
sortBookmarks = 'SORT_BOOKMARKS',
|
sortBookmarks = 'SORT_BOOKMARKS',
|
||||||
|
importBookmark = 'IMPORT_BOOKMARK',
|
||||||
// AUTH
|
// AUTH
|
||||||
login = 'LOGIN',
|
login = 'LOGIN',
|
||||||
logout = 'LOGOUT',
|
logout = 'LOGOUT',
|
||||||
|
|
|
@ -19,6 +19,11 @@ export interface AddBookmarkAction {
|
||||||
payload: Bookmark;
|
payload: Bookmark;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ImportBookmarkAction {
|
||||||
|
type: ActionType.importBookmark;
|
||||||
|
payload: Bookmark;
|
||||||
|
}
|
||||||
|
|
||||||
export interface PinCategoryAction {
|
export interface PinCategoryAction {
|
||||||
type: ActionType.pinCategory;
|
type: ActionType.pinCategory;
|
||||||
payload: Category;
|
payload: Category;
|
||||||
|
|
|
@ -18,6 +18,10 @@ export const isImage = (data: string): boolean => {
|
||||||
return regex.test(data);
|
return regex.test(data);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const isBase64Image = (data: string): boolean => {
|
||||||
|
return data.startsWith("data:image/")
|
||||||
|
};
|
||||||
|
|
||||||
export const isSvg = (data: string): boolean => {
|
export const isSvg = (data: string): boolean => {
|
||||||
const regex = /.(svg)$/i;
|
const regex = /.(svg)$/i;
|
||||||
|
|
||||||
|
|
22
controllers/bookmarks/importBookmark.js
Normal file
22
controllers/bookmarks/importBookmark.js
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
const asyncWrapper = require('../../middleware/asyncWrapper');
|
||||||
|
const ErrorResponse = require('../../utils/ErrorResponse');
|
||||||
|
const importBookmarkData = require('../../utils/importBookmark');
|
||||||
|
const Bookmark = require('../../models/Bookmark');
|
||||||
|
|
||||||
|
// @desc Import bookmarks from file.
|
||||||
|
// @route POST /api/bookmarks/import
|
||||||
|
// @access Public
|
||||||
|
const importBookmark = asyncWrapper(async (req, res, next) => {
|
||||||
|
importBookmarkData(req.file.path);
|
||||||
|
|
||||||
|
if(next){
|
||||||
|
next();
|
||||||
|
} else {
|
||||||
|
res.status(200).json({
|
||||||
|
success: true,
|
||||||
|
data: bookmark,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = importBookmark;
|
|
@ -3,6 +3,7 @@ module.exports = {
|
||||||
getAllBookmarks: require('./getAllBookmarks'),
|
getAllBookmarks: require('./getAllBookmarks'),
|
||||||
getSingleBookmark: require('./getSingleBookmark'),
|
getSingleBookmark: require('./getSingleBookmark'),
|
||||||
updateBookmark: require('./updateBookmark'),
|
updateBookmark: require('./updateBookmark'),
|
||||||
|
importBookmark: require('./importBookmark'),
|
||||||
deleteBookmark: require('./deleteBookmark'),
|
deleteBookmark: require('./deleteBookmark'),
|
||||||
reorderBookmarks: require('./reorderBookmarks'),
|
reorderBookmarks: require('./reorderBookmarks'),
|
||||||
};
|
};
|
||||||
|
|
|
@ -14,7 +14,15 @@ const storage = multer.diskStorage({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const supportedTypes = ['jpg', 'jpeg', 'png', 'svg', 'svg+xml', 'x-icon'];
|
const supportedTypes = [
|
||||||
|
'jpg',
|
||||||
|
'jpeg',
|
||||||
|
'png',
|
||||||
|
'svg',
|
||||||
|
'svg+xml',
|
||||||
|
'x-icon',
|
||||||
|
'html',
|
||||||
|
];
|
||||||
|
|
||||||
const fileFilter = (req, file, cb) => {
|
const fileFilter = (req, file, cb) => {
|
||||||
if (supportedTypes.includes(file.mimetype.split('/')[1])) {
|
if (supportedTypes.includes(file.mimetype.split('/')[1])) {
|
||||||
|
@ -26,4 +34,7 @@ const fileFilter = (req, file, cb) => {
|
||||||
|
|
||||||
const upload = multer({ storage, fileFilter });
|
const upload = multer({ storage, fileFilter });
|
||||||
|
|
||||||
module.exports = upload.single('icon');
|
module.exports = {
|
||||||
|
icon: upload.single('icon'),
|
||||||
|
bookmark: upload.single('file'),
|
||||||
|
};
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"jsonwebtoken": "^8.5.1",
|
"jsonwebtoken": "^8.5.1",
|
||||||
"multer": "^1.4.3",
|
"multer": "^1.4.3",
|
||||||
|
"node-bookmarks-parser": "^2.0.0",
|
||||||
"node-schedule": "^2.0.0",
|
"node-schedule": "^2.0.0",
|
||||||
"sequelize": "^6.9.0",
|
"sequelize": "^6.9.0",
|
||||||
"sqlite3": "^5.0.2",
|
"sqlite3": "^5.0.2",
|
||||||
|
|
|
@ -15,13 +15,13 @@ const {
|
||||||
|
|
||||||
router
|
router
|
||||||
.route('/')
|
.route('/')
|
||||||
.post(auth, requireAuth, upload, createApp)
|
.post(auth, requireAuth, upload.icon, createApp)
|
||||||
.get(auth, getAllApps);
|
.get(auth, getAllApps);
|
||||||
|
|
||||||
router
|
router
|
||||||
.route('/:id')
|
.route('/:id')
|
||||||
.get(auth, getSingleApp)
|
.get(auth, getSingleApp)
|
||||||
.put(auth, requireAuth, upload, updateApp)
|
.put(auth, requireAuth, upload.icon, updateApp)
|
||||||
.delete(auth, requireAuth, deleteApp);
|
.delete(auth, requireAuth, deleteApp);
|
||||||
|
|
||||||
router.route('/0/reorder').put(auth, requireAuth, reorderApps);
|
router.route('/0/reorder').put(auth, requireAuth, reorderApps);
|
||||||
|
|
|
@ -11,19 +11,26 @@ const {
|
||||||
updateBookmark,
|
updateBookmark,
|
||||||
deleteBookmark,
|
deleteBookmark,
|
||||||
reorderBookmarks,
|
reorderBookmarks,
|
||||||
|
importBookmark,
|
||||||
} = require('../controllers/bookmarks');
|
} = require('../controllers/bookmarks');
|
||||||
|
|
||||||
|
const { getAllCategories } = require('../controllers/categories');
|
||||||
|
|
||||||
router
|
router
|
||||||
.route('/')
|
.route('/')
|
||||||
.post(auth, requireAuth, upload, createBookmark)
|
.post(auth, requireAuth, upload.icon, createBookmark)
|
||||||
.get(auth, getAllBookmarks);
|
.get(auth, getAllBookmarks);
|
||||||
|
|
||||||
router
|
router
|
||||||
.route('/:id')
|
.route('/:id')
|
||||||
.get(auth, getSingleBookmark)
|
.get(auth, getSingleBookmark)
|
||||||
.put(auth, requireAuth, upload, updateBookmark)
|
.put(auth, requireAuth, upload.icon, updateBookmark)
|
||||||
.delete(auth, requireAuth, deleteBookmark);
|
.delete(auth, requireAuth, deleteBookmark);
|
||||||
|
|
||||||
|
router
|
||||||
|
.route('/import')
|
||||||
|
.post(auth, requireAuth, upload.bookmark, importBookmark, getAllCategories);
|
||||||
|
|
||||||
router.route('/0/reorder').put(auth, requireAuth, reorderBookmarks);
|
router.route('/0/reorder').put(auth, requireAuth, reorderBookmarks);
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|
117
utils/importBookmark.js
Normal file
117
utils/importBookmark.js
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
const parse = require('node-bookmarks-parser');
|
||||||
|
var sqlite3 = require('sqlite3');
|
||||||
|
const File = require('./File');
|
||||||
|
const databaseFilePath = 'data/db.sqlite';
|
||||||
|
|
||||||
|
let bookmarks = [];
|
||||||
|
|
||||||
|
function saveBookmarks() {
|
||||||
|
const db = new sqlite3.Database(databaseFilePath);
|
||||||
|
const importDate = new Date().toISOString();
|
||||||
|
|
||||||
|
db.serialize(() => {
|
||||||
|
db.all(`SELECT id, name FROM categories`, (err, data) => {
|
||||||
|
if (err) {
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const bookmark of bookmarks) {
|
||||||
|
// Found in db.
|
||||||
|
let stmt = db.prepare(
|
||||||
|
'INSERT INTO bookmarks (name, url, categoryId, icon, createdAt, updatedAt) VALUES(?,?,?,?,?,?)'
|
||||||
|
);
|
||||||
|
stmt.run(
|
||||||
|
bookmark.title,
|
||||||
|
bookmark.url,
|
||||||
|
data.find(
|
||||||
|
(r) => r.name.toLowerCase() === bookmark.category.toLowerCase()
|
||||||
|
)?.id,
|
||||||
|
bookmark.icon,
|
||||||
|
importDate,
|
||||||
|
importDate,
|
||||||
|
(err) => {
|
||||||
|
if (err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
stmt.finalize();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveCategories() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let db = new sqlite3.Database(databaseFilePath);
|
||||||
|
let uniqueCats = [...new Set(bookmarks.map((r) => r.category))];
|
||||||
|
const importDate = new Date().toString();
|
||||||
|
|
||||||
|
let tasks = {};
|
||||||
|
|
||||||
|
db.serialize(() => {
|
||||||
|
db.all(`SELECT * FROM categories`, (err, data) => {
|
||||||
|
if (err) {
|
||||||
|
console.error(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const newCaterory of uniqueCats) {
|
||||||
|
for (const category of data) {
|
||||||
|
if (category.name.toLowerCase() === newCaterory.toLowerCase()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let stmt = db.prepare(
|
||||||
|
'INSERT INTO categories (name, createdAt, updatedAt) VALUES(?,?,?)'
|
||||||
|
);
|
||||||
|
tasks[newCaterory] = false;
|
||||||
|
stmt.run(newCaterory, importDate, importDate, (err) => {
|
||||||
|
tasks[newCaterory] = true;
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
console.error(err);
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(Object.keys(tasks).every(function(k){ return tasks[k] })){
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
stmt.finalize();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function crawlBookmarks(bookmark, category) {
|
||||||
|
if (bookmark.type === 'bookmark') {
|
||||||
|
bookmarks.push({
|
||||||
|
title: bookmark.title,
|
||||||
|
url: bookmark.url,
|
||||||
|
icon: bookmark.icon,
|
||||||
|
category: category,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bookmark.type === 'folder') {
|
||||||
|
for (const child of bookmark.children) {
|
||||||
|
crawlBookmarks(child, bookmark.title);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = function importBookmark(path) {
|
||||||
|
const fileContent = new File(path).read();
|
||||||
|
const bookmarkData = parse(fileContent);
|
||||||
|
|
||||||
|
for (const bookmark of bookmarkData) {
|
||||||
|
crawlBookmarks(bookmark);
|
||||||
|
}
|
||||||
|
|
||||||
|
saveCategories()
|
||||||
|
.then((r) => {
|
||||||
|
saveBookmarks();
|
||||||
|
})
|
||||||
|
};
|
Loading…
Add table
Add a link
Reference in a new issue