-
-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
✨ Import existing bookmarks from the browser
- Loading branch information
Showing
11 changed files
with
423 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
Oops, something went wrong.