Merge pull request 'main' (#5) from RemixDev/deemix-webui:main into main
Reviewed-on: https://codeberg.org/m3troux/deemix-webui/pulls/5
This commit is contained in:
commit
9a5168d994
36
README.md
36
README.md
@ -4,36 +4,34 @@ This is just the WebUI for deemix, it should be used with deemix-pyweb or someth
|
|||||||
|
|
||||||
## What's left to do?
|
## What's left to do?
|
||||||
|
|
||||||
- Use Vue as much as possible
|
- [ ] Use Vue app-wide
|
||||||
- First step: rewrite the app in Single File Components way ✅
|
- First step: rewrite the app in Single File Components way ✅
|
||||||
- Second step: Implement custom contextmenu ⚒
|
- Second step: Implement routing for the whole app using Vue Router ⚒
|
||||||
- Implemented a first version
|
- Third step: Remove jQuery
|
||||||
- Need heavy testing and more features
|
- [ ] Implement custom contextmenu ⚒
|
||||||
- Third step: Implement routing for the whole app using Vue Router
|
- Copy and paste functions ✅
|
||||||
- Fourth step: Remove jQuery
|
- Copy Link where possible ✅
|
||||||
- Make i18n async (https://kazupon.github.io/vue-i18n/guide/lazy-loading.html)
|
- Download Quality ✅
|
||||||
|
- Copy Image URL where possible ✅
|
||||||
|
- Resolve problem when positioning out of window (e.g. clicking on the bottom of the window)
|
||||||
|
- [ ] Make i18n async (https://kazupon.github.io/vue-i18n/guide/lazy-loading.html)
|
||||||
- Use ES2020 async imports, if possible
|
- Use ES2020 async imports, if possible
|
||||||
- Make the UI look coherent
|
- [ ] Make the UI look coherent
|
||||||
- Style buttons
|
- Style buttons
|
||||||
- Style text inputs
|
- Style text inputs
|
||||||
- Style checkboxes
|
- Style checkboxes
|
||||||
- Search tab
|
- [ ] Search tab
|
||||||
- Better placeholer before search
|
- Better placeholer before search
|
||||||
- Link Analyzer
|
- [ ] Link Analyzer
|
||||||
- Better placeholer before analyzing and error feedback
|
- Better placeholer before analyzing and error feedback
|
||||||
- Settings tab
|
- [ ] Settings tab
|
||||||
- Maybe tabbing the section for easy navigation
|
- Maybe tabbing the section for easy navigation
|
||||||
- Could use a carousel, but it's not worth adding a new dep
|
- Could use a carousel, but it's not worth adding a new dep
|
||||||
- Variable selector near template inputs
|
- Variable selector near template inputs
|
||||||
- Add Custom Context menu to objects
|
- [ ] Block selection where it's not needed (keep only titles artists albums labels and useful data)
|
||||||
- Copy Link where possible
|
|
||||||
- Copy Image URL where possible
|
|
||||||
- Context menu for pywebview (Context menu is blocked in pywebview)
|
|
||||||
- Copy and paste functions
|
|
||||||
- Block selection where it's not needed (keep only titles artists albums labels and useful data)
|
|
||||||
- There's a SASS mixin for this. Need to use it in the proper classes
|
- There's a SASS mixin for this. Need to use it in the proper classes
|
||||||
- Better feedback for socket.io possible errors
|
- [ ] Better feedback for socket.io possible errors
|
||||||
- Remove images size limit and add warning if > 1200
|
- [ ] Remove images size limit and add warning if > 1200
|
||||||
- ?
|
- ?
|
||||||
|
|
||||||
# License
|
# License
|
||||||
|
File diff suppressed because one or more lines are too long
@ -33,6 +33,7 @@ function initClient() {
|
|||||||
window.clientMode = true
|
window.clientMode = true
|
||||||
document.querySelector(`#open_downloads_folder`).classList.remove('hide')
|
document.querySelector(`#open_downloads_folder`).classList.remove('hide')
|
||||||
document.querySelector(`#select_downloads_folder`).classList.remove('hide')
|
document.querySelector(`#select_downloads_folder`).classList.remove('hide')
|
||||||
|
document.querySelector(`#settings_btn_applogin`).classList.remove('hide')
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', startApp)
|
document.addEventListener('DOMContentLoaded', startApp)
|
||||||
|
@ -8,14 +8,7 @@
|
|||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<h1>{{ title }}</h1>
|
<h1>{{ title }}</h1>
|
||||||
<div
|
<div role="button" aria-label="download" @click.stop="addToQueue" :data-link="link" class="fab right">
|
||||||
role="button"
|
|
||||||
aria-label="download"
|
|
||||||
@contextmenu.prevent="openQualityModal"
|
|
||||||
@click.stop="addToQueue"
|
|
||||||
:data-link="link"
|
|
||||||
class="fab right"
|
|
||||||
>
|
|
||||||
<i class="material-icons" :title="$t('globals.download_hint')">get_app</i>
|
<i class="material-icons" :title="$t('globals.download_hint')">get_app</i>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
@ -64,18 +57,13 @@
|
|||||||
explicit
|
explicit
|
||||||
</i>
|
</i>
|
||||||
{{ release.title }}
|
{{ release.title }}
|
||||||
<i v-if="checkNewRelease(release.release_date)" class="material-icons" style="color:#FF7300;">
|
<i v-if="checkNewRelease(release.release_date)" class="material-icons" style="color: #ff7300;">
|
||||||
fiber_new
|
fiber_new
|
||||||
</i>
|
</i>
|
||||||
</td>
|
</td>
|
||||||
<td>{{ release.release_date }}</td>
|
<td>{{ release.release_date }}</td>
|
||||||
<td>{{ release.nb_song }}</td>
|
<td>{{ release.nb_song }}</td>
|
||||||
<td
|
<td @click.stop="addToQueue" :data-link="release.link" class="clickable">
|
||||||
@click.stop="addToQueue"
|
|
||||||
@contextmenu.prevent="openQualityModal"
|
|
||||||
:data-link="release.link"
|
|
||||||
class="clickable"
|
|
||||||
>
|
|
||||||
<i class="material-icons" :title="$t('globals.download_hint')">
|
<i class="material-icons" :title="$t('globals.download_hint')">
|
||||||
file_download
|
file_download
|
||||||
</i>
|
</i>
|
||||||
@ -129,9 +117,6 @@ export default {
|
|||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
Downloads.sendAddToQueue(e.currentTarget.dataset.link)
|
Downloads.sendAddToQueue(e.currentTarget.dataset.link)
|
||||||
},
|
},
|
||||||
openQualityModal(e) {
|
|
||||||
this.$root.$emit('QualityModal:open', e.currentTarget.dataset.link)
|
|
||||||
},
|
|
||||||
sortBy(key) {
|
sortBy(key) {
|
||||||
if (key == this.sortKey) {
|
if (key == this.sortKey) {
|
||||||
this.sortOrder = this.sortOrder == 'asc' ? 'desc' : 'asc'
|
this.sortOrder = this.sortOrder == 'asc' ? 'desc' : 'asc'
|
||||||
@ -184,11 +169,15 @@ export default {
|
|||||||
showTable() {
|
showTable() {
|
||||||
if (this.body) {
|
if (this.body) {
|
||||||
if (this.sortKey == 'nb_song')
|
if (this.sortKey == 'nb_song')
|
||||||
return orderBy(this.body[this.currentTab], function (o) { return new Number(o.nb_song); }, this.sortOrder)
|
return orderBy(
|
||||||
else
|
this.body[this.currentTab],
|
||||||
return orderBy(this.body[this.currentTab], this.sortKey, this.sortOrder)
|
function (o) {
|
||||||
}
|
return new Number(o.nb_song)
|
||||||
else return []
|
},
|
||||||
|
this.sortOrder
|
||||||
|
)
|
||||||
|
else return orderBy(this.body[this.currentTab], this.sortKey, this.sortOrder)
|
||||||
|
} else return []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
@ -36,11 +36,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div v-else id="charts_table">
|
<div v-else id="charts_table">
|
||||||
<button @click="changeCountry">{{ $t('charts.changeCountry') }}</button>
|
<button @click="changeCountry">{{ $t('charts.changeCountry') }}</button>
|
||||||
<button
|
<button @click.stop="addToQueue" :data-link="'https://www.deezer.com/playlist/' + id">
|
||||||
@contextmenu.prevent="openQualityModal"
|
|
||||||
@click.stop="addToQueue"
|
|
||||||
:data-link="'https://www.deezer.com/playlist/' + id"
|
|
||||||
>
|
|
||||||
{{ $t('charts.download') }}
|
{{ $t('charts.download') }}
|
||||||
</button>
|
</button>
|
||||||
<table class="table table--charts">
|
<table class="table table--charts">
|
||||||
@ -72,9 +68,7 @@
|
|||||||
<td class="table__cell--large breakline">
|
<td class="table__cell--large breakline">
|
||||||
{{
|
{{
|
||||||
track.title +
|
track.title +
|
||||||
(track.title_version && track.title.indexOf(track.title_version) == -1
|
(track.title_version && track.title.indexOf(track.title_version) == -1 ? ' ' + track.title_version : '')
|
||||||
? ' ' + track.title_version
|
|
||||||
: '')
|
|
||||||
}}
|
}}
|
||||||
</td>
|
</td>
|
||||||
<td
|
<td
|
||||||
@ -96,7 +90,6 @@
|
|||||||
</td>
|
</td>
|
||||||
<td
|
<td
|
||||||
class="table__cell--download"
|
class="table__cell--download"
|
||||||
@contextmenu.prevent="openQualityModal"
|
|
||||||
@click.stop="addToQueue"
|
@click.stop="addToQueue"
|
||||||
:data-link="track.link"
|
:data-link="track.link"
|
||||||
role="button"
|
role="button"
|
||||||
@ -146,9 +139,6 @@ export default {
|
|||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
Downloads.sendAddToQueue(e.currentTarget.dataset.link)
|
Downloads.sendAddToQueue(e.currentTarget.dataset.link)
|
||||||
},
|
},
|
||||||
openQualityModal(e) {
|
|
||||||
this.$root.$emit('QualityModal:open', e.currentTarget.dataset.link)
|
|
||||||
},
|
|
||||||
getTrackList(event) {
|
getTrackList(event) {
|
||||||
document.getElementById('content').scrollTo(0, 0)
|
document.getElementById('content').scrollTo(0, 0)
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<div class="context-menu" v-show="menuOpen" ref="contextMenu" :style="{ top: yPos, left: xPos }">
|
<div class="context-menu" v-show="menuOpen" ref="contextMenu" :style="{ top: yPos, left: xPos }">
|
||||||
<button
|
<button
|
||||||
class="menu-option"
|
class="menu-option"
|
||||||
v-for="option of options"
|
v-for="option of sortedOptions"
|
||||||
:key="option.label"
|
:key="option.label"
|
||||||
v-show="option.show"
|
v-show="option.show"
|
||||||
@click.prevent="option.action"
|
@click.prevent="option.action"
|
||||||
@ -13,118 +13,185 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import Downloads from '@/utils/downloads'
|
||||||
|
import downloadQualities from '@js/qualities'
|
||||||
|
import { generatePath } from '@/utils/utils'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data: () => ({
|
data() {
|
||||||
|
return {
|
||||||
menuOpen: false,
|
menuOpen: false,
|
||||||
xPos: 0,
|
xPos: 0,
|
||||||
yPos: 0,
|
yPos: 0,
|
||||||
currentHref: '',
|
deezerHref: '',
|
||||||
options: [
|
generalHref: '',
|
||||||
{
|
imgSrc: ''
|
||||||
label: 'Cut',
|
}
|
||||||
show: true,
|
},
|
||||||
|
computed: {
|
||||||
|
options() {
|
||||||
|
// In the action property:
|
||||||
// Use arrow functions to keep the Vue instance in 'this'
|
// Use arrow functions to keep the Vue instance in 'this'
|
||||||
// Use normal functions to keep the object instance in 'this'
|
// Use normal functions to keep the object instance in 'this'
|
||||||
|
const options = {
|
||||||
|
cut: {
|
||||||
|
label: this.$t('globals.cut'),
|
||||||
|
show: true,
|
||||||
|
position: 1,
|
||||||
action: () => {
|
action: () => {
|
||||||
document.execCommand('Cut')
|
document.execCommand('Cut')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
copy: {
|
||||||
label: 'Copy',
|
label: this.$t('globals.copy'),
|
||||||
show: true,
|
show: true,
|
||||||
|
position: 2,
|
||||||
action: () => {
|
action: () => {
|
||||||
document.execCommand('Copy')
|
document.execCommand('Copy')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
copyLink: {
|
||||||
label: 'Copy Link',
|
label: this.$t('globals.copyLink'),
|
||||||
show: false,
|
show: false,
|
||||||
action: null
|
position: 3,
|
||||||
|
action: () => {
|
||||||
|
navigator.clipboard.writeText(this.generalHref).catch(err => {
|
||||||
|
console.error('Link copying failed', err)
|
||||||
|
})
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
copyImageLink: {
|
||||||
label: 'Copy Deezer Link',
|
label: this.$t('globals.copyImageLink'),
|
||||||
show: false,
|
show: false,
|
||||||
action: null
|
position: 4,
|
||||||
|
action: () => {
|
||||||
|
navigator.clipboard.writeText(this.imgSrc).catch(err => {
|
||||||
|
console.error('Image copying failed', err)
|
||||||
|
})
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
copyDeezerLink: {
|
||||||
label: 'Paste',
|
label: this.$t('globals.copyDeezerLink'),
|
||||||
|
show: false,
|
||||||
|
position: 5,
|
||||||
|
action: () => {
|
||||||
|
navigator.clipboard.writeText(this.generalHref).catch(err => {
|
||||||
|
console.error('Deezer link copying failed', err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
paste: {
|
||||||
|
label: this.$t('globals.paste'),
|
||||||
show: true,
|
show: true,
|
||||||
|
position: 6,
|
||||||
action: () => {
|
action: () => {
|
||||||
navigator.clipboard.readText().then(text => {
|
navigator.clipboard.readText().then(text => {
|
||||||
document.execCommand('insertText', undefined, text)
|
document.execCommand('insertText', undefined, text)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
}),
|
|
||||||
|
let nextValuePosition = Object.values(options).length + 1
|
||||||
|
|
||||||
|
downloadQualities.forEach((quality, index) => {
|
||||||
|
options[quality.objName] = {
|
||||||
|
label: `${this.$t('globals.download', [quality.label])}`,
|
||||||
|
show: false,
|
||||||
|
position: nextValuePosition + index,
|
||||||
|
action: this.tryToDownloadTrack.bind(null, quality.value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return options
|
||||||
|
},
|
||||||
|
// This computed property is used for rendering the options in the wanted order
|
||||||
|
// while keeping the options computed property an Object to make the properties
|
||||||
|
// accessible via property name (es this.options.copyLink)
|
||||||
|
sortedOptions() {
|
||||||
|
return Object.values(this.options).sort((first, second) => {
|
||||||
|
return first.position < second.position ? -1 : 1
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
document.body.addEventListener('contextmenu', this.showMenu)
|
document.body.addEventListener('contextmenu', this.showMenu)
|
||||||
|
document.body.addEventListener('click', this.hideMenu)
|
||||||
document.body.addEventListener('click', () => {
|
|
||||||
// Finish all operations before closing (may be not necessary)
|
|
||||||
this.$nextTick().then(() => {
|
|
||||||
this.menuOpen = false
|
|
||||||
|
|
||||||
this.options[2].show = false
|
|
||||||
this.options[3].show = false
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
showMenu(contextMenuEvent) {
|
showMenu(contextMenuEvent) {
|
||||||
contextMenuEvent.preventDefault()
|
contextMenuEvent.preventDefault()
|
||||||
|
|
||||||
const {
|
const { pageX, pageY, target: elementClicked } = contextMenuEvent
|
||||||
pageX,
|
|
||||||
pageY,
|
const path = generatePath(elementClicked)
|
||||||
path,
|
|
||||||
path: [elementClicked]
|
|
||||||
} = contextMenuEvent
|
|
||||||
|
|
||||||
this.positionMenu(pageX, pageY)
|
this.positionMenu(pageX, pageY)
|
||||||
|
|
||||||
// Show 'Copy Link' option
|
// Show 'Copy Link' option
|
||||||
if (elementClicked.matches('a')) {
|
if (elementClicked.matches('a')) {
|
||||||
this.showCopyLink(elementClicked.href)
|
this.generalHref = elementClicked.href
|
||||||
|
this.options.copyLink.show = true
|
||||||
}
|
}
|
||||||
|
|
||||||
let link = null
|
// Show 'Copy Image Link' option
|
||||||
|
if (elementClicked.matches('img')) {
|
||||||
|
this.imgSrc = elementClicked.src
|
||||||
|
this.options.copyImageLink.show = true
|
||||||
|
}
|
||||||
|
|
||||||
|
let deezerLink = null
|
||||||
|
|
||||||
for (let i = 0; i < path.length; i++) {
|
for (let i = 0; i < path.length; i++) {
|
||||||
if (path[i] == document) break
|
if (path[i] == document) break
|
||||||
|
|
||||||
if (path[i].matches('[data-link]')) {
|
if (path[i].matches('[data-link]')) {
|
||||||
link = path[i].dataset.link
|
deezerLink = path[i].dataset.link
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show 'Copy Deezer Link' option
|
// Show 'Copy Deezer Link' option
|
||||||
if (link) {
|
if (deezerLink) {
|
||||||
this.showCopyDeezerLink(link)
|
this.deezerHref = deezerLink
|
||||||
|
this.showDeezerOptions()
|
||||||
}
|
}
|
||||||
|
|
||||||
this.menuOpen = true
|
this.menuOpen = true
|
||||||
},
|
},
|
||||||
|
hideMenu() {
|
||||||
|
if (!this.menuOpen) return
|
||||||
|
|
||||||
|
// Finish all operations before closing (may be not necessary)
|
||||||
|
this.$nextTick()
|
||||||
|
.then(() => {
|
||||||
|
this.menuOpen = false
|
||||||
|
|
||||||
|
this.options.copyLink.show = false
|
||||||
|
this.options.copyDeezerLink.show = false
|
||||||
|
this.options.copyImageLink.show = false
|
||||||
|
|
||||||
|
downloadQualities.forEach(quality => {
|
||||||
|
this.options[quality.objName].show = false
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error(err)
|
||||||
|
})
|
||||||
|
},
|
||||||
positionMenu(newX, newY) {
|
positionMenu(newX, newY) {
|
||||||
this.xPos = `${newX}px`
|
this.xPos = `${newX}px`
|
||||||
this.yPos = `${newY}px`
|
this.yPos = `${newY}px`
|
||||||
},
|
},
|
||||||
showCopyLink(href) {
|
showDeezerOptions() {
|
||||||
this.options[2].show = true
|
this.options.copyDeezerLink.show = true
|
||||||
this.options[2].action = () => {
|
|
||||||
navigator.clipboard.writeText(href).catch(err => {
|
downloadQualities.forEach(quality => {
|
||||||
console.error('Link copying failed', err)
|
this.options[quality.objName].show = true
|
||||||
})
|
})
|
||||||
}
|
|
||||||
},
|
},
|
||||||
showCopyDeezerLink(link) {
|
tryToDownloadTrack(qualityValue) {
|
||||||
this.options[3].show = true
|
Downloads.sendAddToQueue(this.deezerHref, qualityValue)
|
||||||
this.options[3].action = () => {
|
|
||||||
navigator.clipboard.writeText(link).catch(err => {
|
|
||||||
console.error('Download link copying failed', err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -164,6 +231,10 @@ export default {
|
|||||||
background: var(--table-highlight);
|
background: var(--table-highlight);
|
||||||
filter: brightness(150%);
|
filter: brightness(150%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__text {
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resetting buttons only for this component (because the style is scoped)
|
// Resetting buttons only for this component (because the style is scoped)
|
||||||
|
@ -38,7 +38,6 @@
|
|||||||
<div
|
<div
|
||||||
role="button"
|
role="button"
|
||||||
aria-label="download"
|
aria-label="download"
|
||||||
@contextmenu.prevent="openQualityModal"
|
|
||||||
@click.stop="addToQueue"
|
@click.stop="addToQueue"
|
||||||
:data-link="release.link"
|
:data-link="release.link"
|
||||||
class="download_overlay"
|
class="download_overlay"
|
||||||
@ -48,9 +47,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<p class="primary-text">{{ release.title }}</p>
|
<p class="primary-text">{{ release.title }}</p>
|
||||||
<p class="secondary-text">
|
<p class="secondary-text">
|
||||||
{{
|
{{ `${$t('globals.by', [release.creator.name])} - ${$tc('globals.listTabs.trackN', release.nb_tracks)}` }}
|
||||||
`${$t('globals.by', [release.creator.name])} - ${$tc('globals.listTabs.trackN', release.nb_tracks)}`
|
|
||||||
}}
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@ -64,7 +61,6 @@
|
|||||||
<div
|
<div
|
||||||
role="button"
|
role="button"
|
||||||
aria-label="download"
|
aria-label="download"
|
||||||
@contextmenu.prevent="openQualityModal"
|
|
||||||
@click.stop="addToQueue"
|
@click.stop="addToQueue"
|
||||||
:data-link="release.link"
|
:data-link="release.link"
|
||||||
class="download_overlay"
|
class="download_overlay"
|
||||||
@ -74,12 +70,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<p class="primary-text">{{ release.title }}</p>
|
<p class="primary-text">{{ release.title }}</p>
|
||||||
<p class="secondary-text">
|
<p class="secondary-text">
|
||||||
{{
|
{{ `${$t('globals.by', [release.creator.name])} - ${$tc('globals.listTabs.trackN', release.nb_tracks)}` }}
|
||||||
`${$t('globals.by', [release.creator.name])} - ${$tc(
|
|
||||||
'globals.listTabs.trackN',
|
|
||||||
release.nb_tracks
|
|
||||||
)}`
|
|
||||||
}}
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -95,7 +86,6 @@
|
|||||||
<div
|
<div
|
||||||
role="button"
|
role="button"
|
||||||
aria-label="download"
|
aria-label="download"
|
||||||
@contextmenu.prevent="openQualityModal"
|
|
||||||
@click.stop="addToQueue"
|
@click.stop="addToQueue"
|
||||||
:data-link="release.link"
|
:data-link="release.link"
|
||||||
class="download_overlay"
|
class="download_overlay"
|
||||||
@ -119,7 +109,6 @@
|
|||||||
<div
|
<div
|
||||||
role="button"
|
role="button"
|
||||||
aria-label="download"
|
aria-label="download"
|
||||||
@contextmenu.prevent="openQualityModal"
|
|
||||||
@click.stop="addToQueue"
|
@click.stop="addToQueue"
|
||||||
:data-link="release.link"
|
:data-link="release.link"
|
||||||
class="download_overlay"
|
class="download_overlay"
|
||||||
@ -185,7 +174,6 @@
|
|||||||
</td>
|
</td>
|
||||||
<td
|
<td
|
||||||
class="table__cell--download clickable"
|
class="table__cell--download clickable"
|
||||||
@contextmenu.prevent="openQualityModal"
|
|
||||||
@click.stop="addToQueue"
|
@click.stop="addToQueue"
|
||||||
:data-link="track.link"
|
:data-link="track.link"
|
||||||
role="button"
|
role="button"
|
||||||
@ -267,9 +255,6 @@ export default {
|
|||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
Downloads.sendAddToQueue(e.currentTarget.dataset.link)
|
Downloads.sendAddToQueue(e.currentTarget.dataset.link)
|
||||||
},
|
},
|
||||||
openQualityModal(e) {
|
|
||||||
this.$root.$emit('QualityModal:open', e.currentTarget.dataset.link)
|
|
||||||
},
|
|
||||||
updated_userSpotifyPlaylists(data) {
|
updated_userSpotifyPlaylists(data) {
|
||||||
this.spotifyPlaylists = data
|
this.spotifyPlaylists = data
|
||||||
},
|
},
|
||||||
|
@ -20,7 +20,6 @@
|
|||||||
<div
|
<div
|
||||||
role="button"
|
role="button"
|
||||||
aria-label="download"
|
aria-label="download"
|
||||||
@contextmenu.prevent="openQualityModal"
|
|
||||||
@click.stop="addToQueue"
|
@click.stop="addToQueue"
|
||||||
:data-link="release.link"
|
:data-link="release.link"
|
||||||
class="download_overlay"
|
class="download_overlay"
|
||||||
@ -50,7 +49,6 @@
|
|||||||
<div
|
<div
|
||||||
role="button"
|
role="button"
|
||||||
aria-label="download"
|
aria-label="download"
|
||||||
@contextmenu.prevent="openQualityModal"
|
|
||||||
@click.stop="addToQueue"
|
@click.stop="addToQueue"
|
||||||
:data-link="release.link"
|
:data-link="release.link"
|
||||||
class="download_overlay"
|
class="download_overlay"
|
||||||
@ -89,9 +87,6 @@ export default {
|
|||||||
addToQueue(e) {
|
addToQueue(e) {
|
||||||
Downloads.sendAddToQueue(e.currentTarget.dataset.link)
|
Downloads.sendAddToQueue(e.currentTarget.dataset.link)
|
||||||
},
|
},
|
||||||
openQualityModal(e) {
|
|
||||||
this.$root.$emit('QualityModal:open', e.currentTarget.dataset.link)
|
|
||||||
},
|
|
||||||
initHome(data) {
|
initHome(data) {
|
||||||
const {
|
const {
|
||||||
playlists: { data: playlistData },
|
playlists: { data: playlistData },
|
||||||
|
@ -9,8 +9,12 @@
|
|||||||
<li class="section-tabs__tab search_tablinks" id="search_all_tab">{{ $t('globals.listTabs.all') }}</li>
|
<li class="section-tabs__tab search_tablinks" id="search_all_tab">{{ $t('globals.listTabs.all') }}</li>
|
||||||
<li class="section-tabs__tab search_tablinks" id="search_track_tab">{{ $tc('globals.listTabs.track', 2) }}</li>
|
<li class="section-tabs__tab search_tablinks" id="search_track_tab">{{ $tc('globals.listTabs.track', 2) }}</li>
|
||||||
<li class="section-tabs__tab search_tablinks" id="search_album_tab">{{ $tc('globals.listTabs.album', 2) }}</li>
|
<li class="section-tabs__tab search_tablinks" id="search_album_tab">{{ $tc('globals.listTabs.album', 2) }}</li>
|
||||||
<li class="section-tabs__tab search_tablinks" id="search_artist_tab">{{ $tc('globals.listTabs.artist', 2) }}</li>
|
<li class="section-tabs__tab search_tablinks" id="search_artist_tab">
|
||||||
<li class="section-tabs__tab search_tablinks" id="search_playlist_tab">{{ $tc('globals.listTabs.playlist', 2) }}</li>
|
{{ $tc('globals.listTabs.artist', 2) }}
|
||||||
|
</li>
|
||||||
|
<li class="section-tabs__tab search_tablinks" id="search_playlist_tab">
|
||||||
|
{{ $tc('globals.listTabs.playlist', 2) }}
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div id="search_tab_content">
|
<div id="search_tab_content">
|
||||||
<!-- ### Main Search Tab ### -->
|
<!-- ### Main Search Tab ### -->
|
||||||
@ -46,7 +50,6 @@
|
|||||||
<div
|
<div
|
||||||
role="button"
|
role="button"
|
||||||
aria-label="download"
|
aria-label="download"
|
||||||
@contextmenu.prevent="openQualityModal"
|
|
||||||
@click.stop="addToQueue"
|
@click.stop="addToQueue"
|
||||||
:data-link="results.allTab.TOP_RESULT[0].link"
|
:data-link="results.allTab.TOP_RESULT[0].link"
|
||||||
class="download_overlay"
|
class="download_overlay"
|
||||||
@ -65,9 +68,7 @@
|
|||||||
$tc('globals.listTabs.trackN', results.allTab.TOP_RESULT[0].nb_song)
|
$tc('globals.listTabs.trackN', results.allTab.TOP_RESULT[0].nb_song)
|
||||||
}}
|
}}
|
||||||
</p>
|
</p>
|
||||||
<span class="tag">{{
|
<span class="tag">{{ $tc(`globals.listTabs.${results.allTab.TOP_RESULT[0].type}`, 1) }}</span>
|
||||||
$tc(`globals.listTabs.${results.allTab.TOP_RESULT[0].type}`, 1)
|
|
||||||
}}</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="section == 'TRACK'">
|
<div v-else-if="section == 'TRACK'">
|
||||||
@ -113,7 +114,6 @@
|
|||||||
</td>
|
</td>
|
||||||
<td
|
<td
|
||||||
class="table__cell--download table__cell--center clickable"
|
class="table__cell--download table__cell--center clickable"
|
||||||
@contextmenu.prevent="openQualityModal"
|
|
||||||
@click.stop="addToQueue"
|
@click.stop="addToQueue"
|
||||||
:data-link="'https://www.deezer.com/track/' + track.SNG_ID"
|
:data-link="'https://www.deezer.com/track/' + track.SNG_ID"
|
||||||
role="button"
|
role="button"
|
||||||
@ -147,7 +147,6 @@
|
|||||||
<div
|
<div
|
||||||
role="button"
|
role="button"
|
||||||
aria-label="download"
|
aria-label="download"
|
||||||
@contextmenu.prevent="openQualityModal"
|
|
||||||
@click.stop="addToQueue"
|
@click.stop="addToQueue"
|
||||||
:data-link="'https://deezer.com/artist/' + release.ART_ID"
|
:data-link="'https://deezer.com/artist/' + release.ART_ID"
|
||||||
class="download_overlay"
|
class="download_overlay"
|
||||||
@ -179,7 +178,6 @@
|
|||||||
<div
|
<div
|
||||||
role="button"
|
role="button"
|
||||||
aria-label="download"
|
aria-label="download"
|
||||||
@contextmenu.prevent="openQualityModal"
|
|
||||||
@click.stop="addToQueue"
|
@click.stop="addToQueue"
|
||||||
:data-link="'https://deezer.com/album/' + release.ALB_ID"
|
:data-link="'https://deezer.com/album/' + release.ALB_ID"
|
||||||
class="download_overlay"
|
class="download_overlay"
|
||||||
@ -195,7 +193,9 @@
|
|||||||
>
|
>
|
||||||
{{ release.ALB_TITLE }}
|
{{ release.ALB_TITLE }}
|
||||||
</p>
|
</p>
|
||||||
<p class="secondary-text">{{ release.ART_NAME + ' - ' + $tc('globals.listTabs.trackN', release.NUMBER_TRACK) }}</p>
|
<p class="secondary-text">
|
||||||
|
{{ release.ART_NAME + ' - ' + $tc('globals.listTabs.trackN', release.NUMBER_TRACK) }}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="section == 'PLAYLIST'" class="release_grid firstrow_only">
|
<div v-else-if="section == 'PLAYLIST'" class="release_grid firstrow_only">
|
||||||
@ -220,7 +220,6 @@
|
|||||||
<div
|
<div
|
||||||
role="button"
|
role="button"
|
||||||
aria-label="download"
|
aria-label="download"
|
||||||
@contextmenu.prevent="openQualityModal"
|
|
||||||
@click.stop="addToQueue"
|
@click.stop="addToQueue"
|
||||||
:data-link="'https://deezer.com/playlist/' + release.PLAYLIST_ID"
|
:data-link="'https://deezer.com/playlist/' + release.PLAYLIST_ID"
|
||||||
class="download_overlay"
|
class="download_overlay"
|
||||||
@ -317,7 +316,6 @@
|
|||||||
</td>
|
</td>
|
||||||
<td
|
<td
|
||||||
class="table__cell--download table__cell--center clickable"
|
class="table__cell--download table__cell--center clickable"
|
||||||
@contextmenu.prevent="openQualityModal"
|
|
||||||
@click.stop="addToQueue"
|
@click.stop="addToQueue"
|
||||||
:data-link="track.link"
|
:data-link="track.link"
|
||||||
role="button"
|
role="button"
|
||||||
@ -349,7 +347,6 @@
|
|||||||
<div
|
<div
|
||||||
role="button"
|
role="button"
|
||||||
aria-label="download"
|
aria-label="download"
|
||||||
@contextmenu.prevent="openQualityModal"
|
|
||||||
@click.stop="addToQueue"
|
@click.stop="addToQueue"
|
||||||
:data-link="release.link"
|
:data-link="release.link"
|
||||||
class="download_overlay"
|
class="download_overlay"
|
||||||
@ -361,7 +358,11 @@
|
|||||||
<i v-if="release.explicit_lyrics" class="material-icons explicit_icon">explicit</i>
|
<i v-if="release.explicit_lyrics" class="material-icons explicit_icon">explicit</i>
|
||||||
{{ release.title }}
|
{{ release.title }}
|
||||||
</p>
|
</p>
|
||||||
<p class="secondary-text">{{ $t('globals.by', [release.artist.name]) + ' - ' + $tc('globals.listTabs.trackN', release.nb_tracks) }}</p>
|
<p class="secondary-text">
|
||||||
|
{{
|
||||||
|
$t('globals.by', [release.artist.name]) + ' - ' + $tc('globals.listTabs.trackN', release.nb_tracks)
|
||||||
|
}}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -383,7 +384,6 @@
|
|||||||
<div
|
<div
|
||||||
role="button"
|
role="button"
|
||||||
aria-label="download"
|
aria-label="download"
|
||||||
@contextmenu.prevent="openQualityModal"
|
|
||||||
@click.stop="addToQueue"
|
@click.stop="addToQueue"
|
||||||
:data-link="release.link"
|
:data-link="release.link"
|
||||||
class="download_overlay"
|
class="download_overlay"
|
||||||
@ -414,7 +414,6 @@
|
|||||||
<div
|
<div
|
||||||
role="button"
|
role="button"
|
||||||
aria-label="download"
|
aria-label="download"
|
||||||
@contextmenu.prevent="openQualityModal"
|
|
||||||
@click.stop="addToQueue"
|
@click.stop="addToQueue"
|
||||||
:data-link="release.link"
|
:data-link="release.link"
|
||||||
class="download_overlay"
|
class="download_overlay"
|
||||||
@ -424,9 +423,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<p class="primary-text">{{ release.title }}</p>
|
<p class="primary-text">{{ release.title }}</p>
|
||||||
<p class="secondary-text">
|
<p class="secondary-text">
|
||||||
{{
|
{{ `${$t('globals.by', [release.user.name])} - ${$tc('globals.listTabs.trackN', release.nb_tracks)}` }}
|
||||||
`${$t('globals.by', [release.user.name])} - ${$tc('globals.listTabs.trackN', release.nb_tracks)}`
|
|
||||||
}}
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -613,9 +610,6 @@ export default {
|
|||||||
addToQueue(e) {
|
addToQueue(e) {
|
||||||
Downloads.sendAddToQueue(e.currentTarget.dataset.link)
|
Downloads.sendAddToQueue(e.currentTarget.dataset.link)
|
||||||
},
|
},
|
||||||
openQualityModal(e) {
|
|
||||||
this.$root.$emit('QualityModal:open', e.currentTarget.dataset.link)
|
|
||||||
},
|
|
||||||
numberWithDots: Utils.numberWithDots,
|
numberWithDots: Utils.numberWithDots,
|
||||||
convertDuration: Utils.convertDuration,
|
convertDuration: Utils.convertDuration,
|
||||||
search(type) {
|
search(type) {
|
||||||
|
@ -53,7 +53,6 @@ export default {
|
|||||||
}),
|
}),
|
||||||
mounted() {
|
mounted() {
|
||||||
this.$root.$on('QualityModal:open', this.openModal)
|
this.$root.$on('QualityModal:open', this.openModal)
|
||||||
|
|
||||||
this.$refs.modal.addEventListener('webkitAnimationEnd', this.handleAnimationEnd)
|
this.$refs.modal.addEventListener('webkitAnimationEnd', this.handleAnimationEnd)
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -26,6 +26,9 @@
|
|||||||
<a href="https://codeberg.org/RemixDev/deemix/wiki/Getting-your-own-ARL" target="_blank">
|
<a href="https://codeberg.org/RemixDev/deemix/wiki/Getting-your-own-ARL" target="_blank">
|
||||||
{{ $t('settings.login.arl.question') }}
|
{{ $t('settings.login.arl.question') }}
|
||||||
</a>
|
</a>
|
||||||
|
<a id="settings_btn_applogin" class="hide" href="#" @click="applogin">
|
||||||
|
Automated login
|
||||||
|
</a>
|
||||||
<button id="settings_btn_updateArl" @click="login" style="width: 100%;">
|
<button id="settings_btn_updateArl" @click="login" style="width: 100%;">
|
||||||
{{ $t('settings.login.arl.update') }}
|
{{ $t('settings.login.arl.update') }}
|
||||||
</button>
|
</button>
|
||||||
@ -678,6 +681,7 @@ export default {
|
|||||||
socket.on('accountChanged', this.accountChanged)
|
socket.on('accountChanged', this.accountChanged)
|
||||||
socket.on('familyAccounts', this.initAccounts)
|
socket.on('familyAccounts', this.initAccounts)
|
||||||
socket.on('downloadFolderSelected', this.downloadFolderSelected)
|
socket.on('downloadFolderSelected', this.downloadFolderSelected)
|
||||||
|
socket.on('applogin_arl', this.setArl)
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
revertSettings() {
|
revertSettings() {
|
||||||
@ -742,6 +746,14 @@ export default {
|
|||||||
socket.emit('login', arl, true, this.accountNum)
|
socket.emit('login', arl, true, this.accountNum)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
applogin(e) {
|
||||||
|
e.preventDefault()
|
||||||
|
if (window.clientMode) socket.emit('applogin')
|
||||||
|
},
|
||||||
|
setArl(arl) {
|
||||||
|
this.$refs.loginInput.value = arl
|
||||||
|
this.login()
|
||||||
|
},
|
||||||
changeAccount() {
|
changeAccount() {
|
||||||
socket.emit('changeAccount', this.accountNum)
|
socket.emit('changeAccount', this.accountNum)
|
||||||
},
|
},
|
||||||
|
@ -133,15 +133,10 @@
|
|||||||
</table>
|
</table>
|
||||||
<span v-if="label" style="opacity: 0.4; margin-top: 8px; display: inline-block; font-size: 13px;">{{ label }}</span>
|
<span v-if="label" style="opacity: 0.4; margin-top: 8px; display: inline-block; font-size: 13px;">{{ label }}</span>
|
||||||
<footer>
|
<footer>
|
||||||
<button @contextmenu.prevent="openQualityModal" @click.stop="addToQueue" :data-link="link">
|
<button @click.stop="addToQueue" :data-link="link">
|
||||||
{{ `${$t('globals.download', [$tc(`globals.listTabs.${type}`, 1)])}` }}
|
{{ `${$t('globals.download', [$tc(`globals.listTabs.${type}`, 1)])}` }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button class="with_icon" @click.stop="addToQueue" :data-link="selectedLinks()">
|
||||||
class="with_icon"
|
|
||||||
@contextmenu.prevent="openQualityModal"
|
|
||||||
@click.stop="addToQueue"
|
|
||||||
:data-link="selectedLinks()"
|
|
||||||
>
|
|
||||||
{{ $t('tracklist.downloadSelection') }}<i class="material-icons">file_download</i>
|
{{ $t('tracklist.downloadSelection') }}<i class="material-icons">file_download</i>
|
||||||
</button>
|
</button>
|
||||||
<button class="back-button">{{ $t('globals.back') }}</button>
|
<button class="back-button">{{ $t('globals.back') }}</button>
|
||||||
@ -189,9 +184,6 @@ export default {
|
|||||||
addToQueue(e) {
|
addToQueue(e) {
|
||||||
Downloads.sendAddToQueue(e.currentTarget.dataset.link)
|
Downloads.sendAddToQueue(e.currentTarget.dataset.link)
|
||||||
},
|
},
|
||||||
openQualityModal(e) {
|
|
||||||
this.$root.$emit('QualityModal:open', e.currentTarget.dataset.link)
|
|
||||||
},
|
|
||||||
toggleAll(e) {
|
toggleAll(e) {
|
||||||
this.body.forEach(item => {
|
this.body.forEach(item => {
|
||||||
if (item.type == 'track') {
|
if (item.type == 'track') {
|
||||||
|
32
src/js/qualities.js
Normal file
32
src/js/qualities.js
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
export default [
|
||||||
|
{
|
||||||
|
objName: 'flac',
|
||||||
|
label: 'FLAC',
|
||||||
|
value: 9
|
||||||
|
},
|
||||||
|
{
|
||||||
|
objName: '320kbps',
|
||||||
|
label: 'MP3 320kbps',
|
||||||
|
value: 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
objName: '128kbps',
|
||||||
|
label: 'MP3 128kbps',
|
||||||
|
value: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
objName: 'realityAudioHQ',
|
||||||
|
label: '360 Reality Audio [HQ]',
|
||||||
|
value: 15
|
||||||
|
},
|
||||||
|
{
|
||||||
|
objName: 'realityAudioMQ',
|
||||||
|
label: '360 Reality Audio [MQ]',
|
||||||
|
value: 14
|
||||||
|
},
|
||||||
|
{
|
||||||
|
objName: 'realityAudioLQ',
|
||||||
|
label: '360 Reality Audio [LQ]',
|
||||||
|
value: 13
|
||||||
|
}
|
||||||
|
]
|
@ -211,7 +211,9 @@ const de = {
|
|||||||
title: 'Soll ich die Dateien überchreiben?',
|
title: 'Soll ich die Dateien überchreiben?',
|
||||||
y: 'Ja überschreibe die Dateien',
|
y: 'Ja überschreibe die Dateien',
|
||||||
n: 'Nein überschreibe die Dateien nicht',
|
n: 'Nein überschreibe die Dateien nicht',
|
||||||
t: 'Überschreibe nur die Tags'
|
t: 'Überschreibe nur die Tags',
|
||||||
|
b: 'Nein, behalte beide Dateien und füge der Kopie eine Nummer hinzu'
|
||||||
|
|
||||||
},
|
},
|
||||||
fallbackBitrate: 'Falls gewünschte Bitrate nicht verfügbar, auf niedrigere Bitrate zurückgreifen',
|
fallbackBitrate: 'Falls gewünschte Bitrate nicht verfügbar, auf niedrigere Bitrate zurückgreifen',
|
||||||
fallbackSearch: 'Zur Suche zurückkehren, wenn der Song nicht verfügbar ist',
|
fallbackSearch: 'Zur Suche zurückkehren, wenn der Song nicht verfügbar ist',
|
||||||
|
@ -11,6 +11,12 @@ const en = {
|
|||||||
toggle_download_tab_hint: 'Expand/Collapse',
|
toggle_download_tab_hint: 'Expand/Collapse',
|
||||||
clean_queue_hint: 'Clear Finished',
|
clean_queue_hint: 'Clear Finished',
|
||||||
cancel_queue_hint: 'Cancel All',
|
cancel_queue_hint: 'Cancel All',
|
||||||
|
cut: 'cut',
|
||||||
|
copy: 'copy',
|
||||||
|
copyLink: 'copy link',
|
||||||
|
copyImageLink: 'copy image link',
|
||||||
|
copyDeezerLink: 'copy deezer link',
|
||||||
|
paste: 'paste',
|
||||||
listTabs: {
|
listTabs: {
|
||||||
empty: '',
|
empty: '',
|
||||||
all: 'all',
|
all: 'all',
|
||||||
|
@ -11,6 +11,12 @@ const it = {
|
|||||||
toggle_download_tab_hint: 'Espandi/Riduci',
|
toggle_download_tab_hint: 'Espandi/Riduci',
|
||||||
clean_queue_hint: 'Pulisci Lista',
|
clean_queue_hint: 'Pulisci Lista',
|
||||||
cancel_queue_hint: 'Cancella tutti i download',
|
cancel_queue_hint: 'Cancella tutti i download',
|
||||||
|
cut: 'taglia',
|
||||||
|
copy: 'copia',
|
||||||
|
copyLink: 'copia link',
|
||||||
|
copyImageLink: 'copia link immagine',
|
||||||
|
copyDeezerLink: 'copia link deezer',
|
||||||
|
paste: 'incolla',
|
||||||
listTabs: {
|
listTabs: {
|
||||||
all: 'tutto',
|
all: 'tutto',
|
||||||
top_result: 'miglior risultato',
|
top_result: 'miglior risultato',
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { socket } from '@/utils/socket'
|
import { socket } from '@/utils/socket'
|
||||||
|
|
||||||
function sendAddToQueue(url, bitrate = null) {
|
function sendAddToQueue(url, bitrate = null) {
|
||||||
if (url != '') {
|
if (!url) return
|
||||||
socket.emit('addToQueue', { url: url, bitrate: bitrate }, () => {})
|
|
||||||
}
|
socket.emit('addToQueue', { url, bitrate }, () => {})
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -1,3 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* Climbs the DOM until the root is reached, storing every node passed.
|
||||||
|
* @param {HTMLElement} el
|
||||||
|
* @return {Array} Contains all the nodes between el and the root
|
||||||
|
*/
|
||||||
|
export function generatePath(el) {
|
||||||
|
if (!el) {
|
||||||
|
throw new Error('No element passed to the generatePath function!')
|
||||||
|
}
|
||||||
|
|
||||||
|
let path = [el]
|
||||||
|
|
||||||
|
while ((el = el.parentNode) && el !== document) {
|
||||||
|
path.push(el)
|
||||||
|
}
|
||||||
|
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
export function isValidURL(text) {
|
export function isValidURL(text) {
|
||||||
let lowerCaseText = text.toLowerCase()
|
let lowerCaseText = text.toLowerCase()
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user