Skip to content

Commit

Permalink
✨ Import existing bookmarks from the browser
Browse files Browse the repository at this point in the history
  • Loading branch information
BetaHuhn committed Sep 4, 2021
1 parent 315bce7 commit 07b8f13
Show file tree
Hide file tree
Showing 11 changed files with 423 additions and 2 deletions.
10 changes: 10 additions & 0 deletions src/background.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ chrome.contextMenus.onClicked.addListener(({ menuItemId, linkUrl }) => {
chrome.tabs.create({
url: `${ detaInstance }?addUrl=${ linkUrl }`
})
} else if (menuItemId === 'import-bookmarks') {
chrome.tabs.create({
url: chrome.runtime.getURL('./import/index.html')
})
} else if (menuItemId === 'open-settings') {
chrome.runtime.openOptionsPage()
}
Expand Down Expand Up @@ -70,6 +74,12 @@ chrome.contextMenus.create({
contexts: [ 'browser_action' ]
})

chrome.contextMenus.create({
title: 'Import existing bookmarks',
id: 'import-bookmarks',
contexts: [ 'browser_action' ]
})

chrome.contextMenus.create({
title: 'Save link to WebCrate',
id: 'save-link',
Expand Down
204 changes: 204 additions & 0 deletions src/import/App.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
<template>
<div class="settings">
<Logo />
<div v-if="state === 'load'">
<div class="wrapper">
<h1>Import existing bookmarks</h1>
<p>Here are all bookmarks which where found in this browser. Since WebCrate doesn't support nested crates your folder structure may have been flattened. A new crate will be created for each of your folders.</p>
<p>You can select all or choose individual bookmarks you want to import into your WebCrate. You can also select a folder to import all of its bookmarks.</p>
<div class="actions">
<button class="primary-button" @click="importSelected">Import selected <span v-if="selected.length > 0">({{ selected.length }})</span></button>
<div class="all">
<input id="all" type="checkbox" v-model="selectAll">
<label for="all">Select all</label>
</div>
</div>
</div>
<div class="list">
<BookmarkFolder v-for="(links, folder) in bookmarks" :key="folder" :title="folder" :links="links" :selectAll="selectAll" @select="select" @deselect="deselect" />
</div>
</div>
<div v-else-if="state === 'error'" class="wrapper">
<h1>Error occurred</h1>
<p>{{ error }}</p>
</div>
<div v-else-if="selected && state === 'loading'" class="wrapper">
<h1>Importing {{ selected.length }} bookmarks, may take a while...</h1>
</div>
<div v-else-if="state === 'success'" class="wrapper">
<h1>Successfully imported {{ response.length }} bookmarks!</h1>
<div class="actions">
<a :href="this.detaInstance" target="_blank">
<button class="primary-button">View them</button>
</a>
<button class="button" @click="reload">Import more</button>
</div>
</div>
</div>
</template>

<script>
import axios from 'axios'
import '../main.scss'
import Logo from './components/Logo.vue'
import BookmarkFolder from './components/BookmarkFolder.vue'
export default {
data() {
return {
state: 'load',
detaInstance: undefined,
saveText: 'Save Settings',
error: undefined,
closeAfterUpdate: false,
bookmarks: undefined,
selectAll: false,
selected: []
}
},
methods: {
restore(result) {
this.detaInstance = result.detaInstance
},
getBookmarks() {
chrome.bookmarks.getTree(treeNode => {
this.bookmarks = this.flattenBookmarkTree(treeNode[0].children)
})
},
flattenBookmarkTree(bookmarks) {
const data = {}
const loop = (items, title) => {
items.forEach((item) => {
if (item.children) {
loop(item.children, item.title)
return
}
if (!data[title]) {
data[title] = [ item ]
return
}
data[title].push(item)
})
}
loop(bookmarks, 'unfiled')
return data
},
async importSelected() {
try {
this.state = 'loading'
const res = await axios.post(`${ this.detaInstance }api/link/bulk`, this.selected)
// Check if we need to login by checking if we got redirected to the login page
if (res.request.responseURL.includes('deta.space/login')) {
this.state = 'login'
return
}
this.response = res.data.data
this.state = 'success'
} catch (err) {
// Assume it's a login error (we can't specifically check for that)
if (err.message === 'Network Error') {
this.state = 'login'
return
}
this.error = err.message || 'Unknown error occurred!'
this.state = 'error'
console.error(err)
}
},
select(e) {
this.selected.push(e)
},
deselect(e) {
this.$delete(this.selected, this.selected.findIndex(item => item.id === e))
},
reload() {
location.reload()
}
},
components: {
Logo,
BookmarkFolder
},
created() {
chrome.storage.local.get((items) => {
// detaInstance will be undefined on installation
if (!items.detaInstance) {
this.closeAfterUpdate = true
return
}
this.detaInstance = items.detaInstance
})
this.getBookmarks()
}
}
</script>

<style lang="scss">
html,
body {
margin: 0;
width: 100%;
height: 100%;
font-family: Inter UI, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
color: var(--text);
background: var(--background);
}
.settings {
padding: 5rem 0;
width: 95%;
max-width: 1000px;
margin: auto;
}
.wrapper {
margin-top: 3rem;
background: var(--background-2nd);
padding: 2rem;
border-radius: var(--border-radius);
}
.list {
margin-top: 3rem;
}
h1 {
margin-bottom: 1rem;
font-size: 1.3rem;
}
h2 {
font-size: 1rem;
}
a {
color: var(--accent);
&:hover {
text-decoration: underline;
}
}
label {
font-size: 1rem;
}
.actions {
display: flex;
align-items: center;
& button {
margin-right: 1rem;
}
}
</style>
71 changes: 71 additions & 0 deletions src/import/components/BookmarkFolder.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<template>
<div class="bookmark-folder">
<div class="title" :class="selected && 'selected'">
<input type="checkbox" v-model="selected">
<h2>{{ title }}</h2>
</div>
<hr>
<BookmarkItem v-for="link in links" :key="link.id" v-bind="link" :folder="title" :folderSelected="selected" @select="select" @deselect="deselect" />
</div>
</template>

<script>
import BookmarkItem from './BookmarkItem.vue'
export default {
name: 'BookmarkFolder',
data() {
return {
selected: false
}
},
props: ['title', 'links', 'selectAll'],
components: {
BookmarkItem
},
methods: {
select(e) {
this.$emit('select', e)
},
deselect(e) {
this.$emit('deselect', e)
}
},
watch: {
selectAll(newVal) {
this.selected = newVal
}
}
}
</script>

<style lang="scss" scoped>
.bookmark-folder {
background: var(--background-2nd);
border-radius: var(--border-radius);
margin-bottom: 1rem;
}
.title {
display: flex;
align-items: center;
border: 3px solid transparent;
border-radius: var(--border-radius);
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
padding: 0.8rem 1rem;
&.selected {
background: var(--grey);
}
& input {
margin-right: 1rem;
}
& h2 {
margin: 0;
}
}
</style>
66 changes: 66 additions & 0 deletions src/import/components/BookmarkItem.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<template>
<div class="bookmark-item" :class="selected && 'selected'" @click.stop="selected = !selected">
<input type="checkbox" v-model="selected">
<div class="content">
<p>{{ title }}</p>
<a :href="url">{{ url }}</a>
</div>
</div>
</template>

<script>
import BookmarkItem from './BookmarkItem.vue'
export default {
name: 'BookmarkItem',
data() {
return {
selected: false
}
},
props: ['title', 'id', 'dateAdded', 'url', 'folderSelected', 'folder'],
components: {
BookmarkItem
},
watch: {
folderSelected(newVal) {
this.selected = newVal
},
selected(newVal) {
if (newVal) {
this.$emit('select', { title: this.title, url: this.url, crate: this.folder, id: this.id })
return
}
this.$emit('deselect', this.id)
}
}
}
</script>

<style lang="scss" scoped>
.bookmark-item {
padding: 0.8rem 1rem;
display: flex;
align-items: center;
& .content {
overflow: hidden;
margin-left: 1rem;
p {
margin: 0;
font-weight: 500;
}
}
&.selected {
background: var(--grey);
}
&:last-child {
border-bottom-right-radius: var(--border-radius);
border-bottom-left-radius: var(--border-radius);
}
}
</style>
Loading

0 comments on commit 07b8f13

Please sign in to comment.