mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2025-06-20 20:05:44 +02:00
Update collection/playlist play button to show pause when playing #1394, update collections to play local item if available
This commit is contained in:
parent
c79ecbb92e
commit
0520cbd538
33 changed files with 84 additions and 50 deletions
|
@ -6,7 +6,7 @@
|
|||
</div>
|
||||
<div class="book-table-content h-full px-2 flex items-center">
|
||||
<div class="max-w-full">
|
||||
<p class="truncate block text-sm">{{ bookTitle }}</p>
|
||||
<p class="truncate block text-sm">{{ bookTitle }} <span v-if="localLibraryItem" class="material-icons text-success text-base align-text-bottom">download_done</span></p>
|
||||
<p class="truncate block text-fg-muted text-xs">{{ bookAuthor }}</p>
|
||||
<p v-if="media.duration" class="text-xxs text-fg-muted">{{ bookDuration }}</p>
|
||||
</div>
|
||||
|
@ -39,6 +39,9 @@ export default {
|
|||
libraryItemId() {
|
||||
return this.book.id
|
||||
},
|
||||
localLibraryItem() {
|
||||
return this.book.localLibraryItem
|
||||
},
|
||||
media() {
|
||||
return this.book.media || {}
|
||||
},
|
||||
|
@ -73,18 +76,34 @@ export default {
|
|||
showPlayBtn() {
|
||||
return !this.isMissing && !this.isInvalid && this.tracks.length
|
||||
},
|
||||
isStreaming() {
|
||||
playerIsStartingPlayback() {
|
||||
// Play has been pressed and waiting for native play response
|
||||
return this.$store.state.playerIsStartingPlayback
|
||||
},
|
||||
isOpenInPlayer() {
|
||||
if (this.localLibraryItem && this.$store.getters['getIsMediaStreaming'](this.localLibraryItem.id)) return true
|
||||
return this.$store.getters['getIsMediaStreaming'](this.libraryItemId)
|
||||
},
|
||||
streamIsPlaying() {
|
||||
return this.$store.state.playerIsPlaying && this.isStreaming
|
||||
return this.$store.state.playerIsPlaying && this.isOpenInPlayer
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async playClick() {
|
||||
if (this.playerIsStartingPlayback) return
|
||||
await this.$hapticsImpact()
|
||||
|
||||
if (this.streamIsPlaying) {
|
||||
this.$eventBus.$emit('pause-item')
|
||||
return
|
||||
}
|
||||
|
||||
this.$store.commit('setPlayerIsStartingPlayback', this.libraryItemId)
|
||||
if (this.localLibraryItem) {
|
||||
this.$eventBus.$emit('play-item', {
|
||||
libraryItemId: this.localLibraryItem.id,
|
||||
serverLibraryItemId: this.libraryItemId
|
||||
})
|
||||
} else {
|
||||
this.$eventBus.$emit('play-item', {
|
||||
libraryItemId: this.libraryItemId
|
||||
|
|
|
@ -113,12 +113,12 @@ export default {
|
|||
showPlayBtn() {
|
||||
return !this.isMissing && !this.isInvalid && (this.tracks.length || this.episode)
|
||||
},
|
||||
isStreaming() {
|
||||
isOpenInPlayer() {
|
||||
if (this.localLibraryItem && this.localEpisode && this.$store.getters['getIsMediaStreaming'](this.localLibraryItem.id, this.localEpisode.id)) return true
|
||||
return this.$store.getters['getIsMediaStreaming'](this.libraryItem.id, this.episodeId)
|
||||
},
|
||||
streamIsPlaying() {
|
||||
return this.$store.state.playerIsPlaying && this.isStreaming
|
||||
return this.$store.state.playerIsPlaying && this.isOpenInPlayer
|
||||
},
|
||||
playerIsStartingPlayback() {
|
||||
// Play has been pressed and waiting for native play response
|
||||
|
|
|
@ -12,9 +12,9 @@
|
|||
{{ collectionName }}
|
||||
</h1>
|
||||
<div class="flex-grow" />
|
||||
<ui-btn v-if="showPlayButton" :disabled="streaming" color="success" :padding-x="4" small :loading="playerIsStartingForThisMedia" class="flex items-center justify-center h-9 mr-2 w-24" @click="clickPlay">
|
||||
<span v-show="!streaming" class="material-icons -ml-2 pr-1 text-white">play_arrow</span>
|
||||
{{ streaming ? $strings.ButtonPlaying : $strings.ButtonPlay }}
|
||||
<ui-btn v-if="showPlayButton" color="success" :padding-x="4" :loading="playerIsStartingForThisMedia" small class="flex items-center justify-center mx-1 w-24" @click="playClick">
|
||||
<span class="material-icons">{{ playerIsPlaying ? 'pause' : 'play_arrow' }}</span>
|
||||
<span class="px-1 text-sm">{{ playerIsPlaying ? $strings.ButtonPause : $strings.ButtonPlay }}</span>
|
||||
</ui-btn>
|
||||
</div>
|
||||
|
||||
|
@ -47,6 +47,18 @@ export default {
|
|||
return redirect('/bookshelf')
|
||||
}
|
||||
|
||||
// Lookup matching local items and attach to collection items
|
||||
if (collection.books.length) {
|
||||
const localLibraryItems = (await app.$db.getLocalLibraryItems('book')) || []
|
||||
if (localLibraryItems.length) {
|
||||
collection.books.forEach((collectionItem) => {
|
||||
const matchingLocalLibraryItem = localLibraryItems.find((lli) => lli.libraryItemId === collectionItem.id)
|
||||
if (!matchingLocalLibraryItem) return
|
||||
collectionItem.localLibraryItem = matchingLocalLibraryItem
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
collection
|
||||
}
|
||||
|
@ -70,13 +82,19 @@ export default {
|
|||
description() {
|
||||
return this.collection.description || ''
|
||||
},
|
||||
playableBooks() {
|
||||
playableItems() {
|
||||
return this.bookItems.filter((book) => {
|
||||
return !book.isMissing && !book.isInvalid && book.media.tracks.length
|
||||
})
|
||||
},
|
||||
streaming() {
|
||||
return !!this.playableBooks.find((b) => this.$store.getters['getIsMediaStreaming'](b.id))
|
||||
playerIsPlaying() {
|
||||
return this.$store.state.playerIsPlaying && this.isOpenInPlayer
|
||||
},
|
||||
isOpenInPlayer() {
|
||||
return !!this.playableItems.find((i) => {
|
||||
if (i.localLibraryItem && this.$store.getters['getIsMediaStreaming'](i.localLibraryItem.id)) return true
|
||||
return this.$store.getters['getIsMediaStreaming'](i.id)
|
||||
})
|
||||
},
|
||||
playerIsStartingPlayback() {
|
||||
// Play has been pressed and waiting for native play response
|
||||
|
@ -88,21 +106,34 @@ export default {
|
|||
return mediaId === this.mediaIdStartingPlayback
|
||||
},
|
||||
showPlayButton() {
|
||||
return this.playableBooks.length
|
||||
return this.playableItems.length
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
clickPlay() {
|
||||
async playClick() {
|
||||
if (this.playerIsStartingPlayback) return
|
||||
await this.$hapticsImpact()
|
||||
|
||||
var nextBookNotRead = this.playableBooks.find((pb) => {
|
||||
var prog = this.$store.getters['user/getUserMediaProgress'](pb.id)
|
||||
if (this.playerIsPlaying) {
|
||||
this.$eventBus.$emit('pause-item')
|
||||
} else {
|
||||
this.playNextItem()
|
||||
}
|
||||
},
|
||||
playNextItem() {
|
||||
const nextBookNotRead = this.playableItems.find((pb) => {
|
||||
const prog = this.$store.getters['user/getUserMediaProgress'](pb.id)
|
||||
return !prog?.isFinished
|
||||
})
|
||||
if (nextBookNotRead) {
|
||||
this.mediaIdStartingPlayback = nextBookNotRead.id
|
||||
this.$store.commit('setPlayerIsStartingPlayback', nextBookNotRead.id)
|
||||
this.$eventBus.$emit('play-item', { libraryItemId: nextBookNotRead.id })
|
||||
|
||||
if (nextBookNotRead.localLibraryItem) {
|
||||
this.$eventBus.$emit('play-item', { libraryItemId: nextBookNotRead.localLibraryItem.id, serverLibraryItemId: nextBookNotRead.id })
|
||||
} else {
|
||||
this.$eventBus.$emit('play-item', { libraryItemId: nextBookNotRead.id })
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -10,9 +10,9 @@
|
|||
{{ playlistName }}
|
||||
</h1>
|
||||
<div class="flex-grow" />
|
||||
<ui-btn v-if="showPlayButton" :disabled="streaming" color="success" :padding-x="4" :loading="playerIsStartingForThisMedia" small class="flex items-center justify-center text-center h-9 mr-2 w-24" @click="clickPlay">
|
||||
<span v-show="!streaming" class="material-icons -ml-2 pr-1 text-white">play_arrow</span>
|
||||
{{ streaming ? $strings.ButtonPlaying : $strings.ButtonPlay }}
|
||||
<ui-btn v-if="showPlayButton" color="success" :padding-x="4" :loading="playerIsStartingForThisMedia" small class="flex items-center justify-center mx-1 w-24" @click="playClick">
|
||||
<span class="material-icons">{{ playerIsPlaying ? 'pause' : 'play_arrow' }}</span>
|
||||
<span class="px-1 text-sm">{{ playerIsPlaying ? $strings.ButtonPause : $strings.ButtonPlay }}</span>
|
||||
</ui-btn>
|
||||
</div>
|
||||
|
||||
|
@ -101,7 +101,10 @@ export default {
|
|||
return libraryItem.media.tracks.length
|
||||
})
|
||||
},
|
||||
streaming() {
|
||||
playerIsPlaying() {
|
||||
return this.$store.state.playerIsPlaying && this.isOpenInPlayer
|
||||
},
|
||||
isOpenInPlayer() {
|
||||
return !!this.playableItems.find((i) => {
|
||||
if (i.localLibraryItem && this.$store.getters['getIsMediaStreaming'](i.localLibraryItem.id, i.localEpisode?.id)) return true
|
||||
return this.$store.getters['getIsMediaStreaming'](i.libraryItemId, i.episodeId)
|
||||
|
@ -126,7 +129,17 @@ export default {
|
|||
this.selectedEpisode = playlistItem.episode
|
||||
this.showMoreMenu = true
|
||||
},
|
||||
clickPlay() {
|
||||
async playClick() {
|
||||
if (this.playerIsStartingPlayback) return
|
||||
await this.$hapticsImpact()
|
||||
|
||||
if (this.playerIsPlaying) {
|
||||
this.$eventBus.$emit('pause-item')
|
||||
} else {
|
||||
this.playNextItem()
|
||||
}
|
||||
},
|
||||
playNextItem() {
|
||||
const nextItem = this.playableItems.find((i) => {
|
||||
const prog = this.$store.getters['user/getUserMediaProgress'](i.libraryItemId, i.episodeId)
|
||||
return !prog?.isFinished
|
||||
|
|
|
@ -34,7 +34,6 @@
|
|||
"ButtonPause": "تَوَقَّف",
|
||||
"ButtonPlay": "تشغيل",
|
||||
"ButtonPlayEpisode": "شغل الحلقة",
|
||||
"ButtonPlaying": "مشغل الآن",
|
||||
"ButtonPlaylists": "قوائم التشغيل",
|
||||
"ButtonRead": "اقرأ",
|
||||
"ButtonReadLess": "قلص",
|
||||
|
|
|
@ -29,7 +29,6 @@
|
|||
"ButtonNextEpisode": "Наступны эпізод",
|
||||
"ButtonOpenFeed": "Адкрыць стужку",
|
||||
"ButtonPause": "Паўза",
|
||||
"ButtonPlaying": "Прайграваецца",
|
||||
"ButtonPlaylists": "Плэйлісты",
|
||||
"ButtonRemoveFromServer": "Выдаліць з сервера",
|
||||
"ButtonSave": "Захаваць",
|
||||
|
|
|
@ -34,7 +34,6 @@
|
|||
"ButtonPause": "বিরতি",
|
||||
"ButtonPlay": "বাজান",
|
||||
"ButtonPlayEpisode": "পর্বটি চালান",
|
||||
"ButtonPlaying": "বাজছে",
|
||||
"ButtonPlaylists": "প্লেলিস্ট",
|
||||
"ButtonRead": "পড়ুন",
|
||||
"ButtonReadLess": "কম পড়ুন",
|
||||
|
|
|
@ -34,7 +34,6 @@
|
|||
"ButtonPause": "Pausa",
|
||||
"ButtonPlay": "Reproduir",
|
||||
"ButtonPlayEpisode": "Reproduir episodi",
|
||||
"ButtonPlaying": "Reproduint",
|
||||
"ButtonPlaylists": "Llistes de reproducció",
|
||||
"ButtonRead": "Llegir",
|
||||
"ButtonReadLess": "Llig menys",
|
||||
|
|
|
@ -35,7 +35,6 @@
|
|||
"ButtonPause": "Pozastavit",
|
||||
"ButtonPlay": "Přehrát",
|
||||
"ButtonPlayEpisode": "Přehrát epizodu",
|
||||
"ButtonPlaying": "Hraje",
|
||||
"ButtonPlaylists": "Seznamy skladeb",
|
||||
"ButtonRead": "Číst",
|
||||
"ButtonReadLess": "Číst méně",
|
||||
|
|
|
@ -35,7 +35,6 @@
|
|||
"ButtonPause": "Pause",
|
||||
"ButtonPlay": "Afspil",
|
||||
"ButtonPlayEpisode": "Afspil Afsnit",
|
||||
"ButtonPlaying": "Afspiller",
|
||||
"ButtonPlaylists": "Afspilningslister",
|
||||
"ButtonRead": "Læs",
|
||||
"ButtonReadLess": "Se mindre",
|
||||
|
|
|
@ -35,7 +35,6 @@
|
|||
"ButtonPause": "Pausieren",
|
||||
"ButtonPlay": "Abspielen",
|
||||
"ButtonPlayEpisode": "Episode abspielen",
|
||||
"ButtonPlaying": "Spielt",
|
||||
"ButtonPlaylists": "Wiedergabelisten",
|
||||
"ButtonRead": "Lesen",
|
||||
"ButtonReadLess": "weniger Anzeigen",
|
||||
|
|
|
@ -35,7 +35,6 @@
|
|||
"ButtonPause": "Pause",
|
||||
"ButtonPlay": "Play",
|
||||
"ButtonPlayEpisode": "Play Episode",
|
||||
"ButtonPlaying": "Playing",
|
||||
"ButtonPlaylists": "Playlists",
|
||||
"ButtonRead": "Read",
|
||||
"ButtonReadLess": "Read less",
|
||||
|
|
|
@ -35,7 +35,6 @@
|
|||
"ButtonPause": "Pausar",
|
||||
"ButtonPlay": "Reproducir",
|
||||
"ButtonPlayEpisode": "Reproducir episodio",
|
||||
"ButtonPlaying": "Reproduciendo",
|
||||
"ButtonPlaylists": "Listas de reproducción",
|
||||
"ButtonRead": "Leer",
|
||||
"ButtonReadLess": "Leer menos",
|
||||
|
|
|
@ -34,7 +34,6 @@
|
|||
"ButtonPause": "Pysäytä",
|
||||
"ButtonPlay": "Toista",
|
||||
"ButtonPlayEpisode": "Toista jakso",
|
||||
"ButtonPlaying": "Toistetaan",
|
||||
"ButtonPlaylists": "Soittolistat",
|
||||
"ButtonRead": "Lue",
|
||||
"ButtonReadLess": "Lue vähemmän",
|
||||
|
|
|
@ -35,7 +35,6 @@
|
|||
"ButtonPause": "Pause",
|
||||
"ButtonPlay": "Lire",
|
||||
"ButtonPlayEpisode": "Lire l’épisode",
|
||||
"ButtonPlaying": "En lecture",
|
||||
"ButtonPlaylists": "Listes de lecture",
|
||||
"ButtonRead": "Lire",
|
||||
"ButtonReadLess": "Lire moins",
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
"ButtonLibrary": "પુસ્તકાલય",
|
||||
"ButtonOpenFeed": "ફીડ ખોલો",
|
||||
"ButtonPlay": "ચલાવો",
|
||||
"ButtonPlaying": "ચલાવી રહ્યું છે",
|
||||
"ButtonPlaylists": "પ્લેલિસ્ટ",
|
||||
"ButtonRead": "વાંચો",
|
||||
"ButtonRemove": "કાઢી નાખો",
|
||||
|
|
|
@ -27,7 +27,6 @@
|
|||
"ButtonLibrary": "ספרייה",
|
||||
"ButtonPlay": "נגן",
|
||||
"ButtonPlayEpisode": "נגן פרק",
|
||||
"ButtonPlaying": "מנגן",
|
||||
"ButtonPlaylists": "רשימת השמעה",
|
||||
"HeaderOpenRSSFeed": "פתח ערוץ RSS",
|
||||
"LabelAddedDate": "נוסף ב-{0}"
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
"ButtonLibrary": "पुस्तकालय",
|
||||
"ButtonOpenFeed": "फ़ीड खोलें",
|
||||
"ButtonPlay": "चलाएँ",
|
||||
"ButtonPlaying": "चल रही है",
|
||||
"ButtonPlaylists": "प्लेलिस्ट्स",
|
||||
"ButtonRead": "पढ़ लिया",
|
||||
"ButtonRemove": "हटाएं",
|
||||
|
|
|
@ -35,7 +35,6 @@
|
|||
"ButtonPause": "Pauziraj",
|
||||
"ButtonPlay": "Reproduciraj",
|
||||
"ButtonPlayEpisode": "Reproduciraj nastavak",
|
||||
"ButtonPlaying": "Izvodi se",
|
||||
"ButtonPlaylists": "Popisi za izvođenje",
|
||||
"ButtonRead": "Pročitaj",
|
||||
"ButtonReadLess": "Pročitaj manje",
|
||||
|
|
|
@ -35,7 +35,6 @@
|
|||
"ButtonPause": "Szünet",
|
||||
"ButtonPlay": "Lejátszás",
|
||||
"ButtonPlayEpisode": "Epizód lejátszása",
|
||||
"ButtonPlaying": "Lejátszás folyamatban",
|
||||
"ButtonPlaylists": "Lejátszási listák",
|
||||
"ButtonRead": "Olvasás",
|
||||
"ButtonReadLess": "Mutass kevesebbet",
|
||||
|
|
|
@ -35,7 +35,6 @@
|
|||
"ButtonPause": "Pausa",
|
||||
"ButtonPlay": "Riproduci",
|
||||
"ButtonPlayEpisode": "Riproduci episodio",
|
||||
"ButtonPlaying": "In riproduzione",
|
||||
"ButtonPlaylists": "Playlist",
|
||||
"ButtonRead": "Leggi",
|
||||
"ButtonReadLess": "Riduci",
|
||||
|
|
|
@ -28,7 +28,6 @@
|
|||
"ButtonPause": "Pauzė",
|
||||
"ButtonPlay": "Groti",
|
||||
"ButtonPlayEpisode": "Groti episodą",
|
||||
"ButtonPlaying": "Grojama",
|
||||
"ButtonPlaylists": "Grojaraščiai",
|
||||
"ButtonRead": "Skaityti",
|
||||
"ButtonReadLess": "Skaityti mažiau",
|
||||
|
|
|
@ -34,7 +34,6 @@
|
|||
"ButtonPause": "Pauze",
|
||||
"ButtonPlay": "Afspelen",
|
||||
"ButtonPlayEpisode": "Aflevering afspelen",
|
||||
"ButtonPlaying": "Speelt",
|
||||
"ButtonPlaylists": "Afspeellijsten",
|
||||
"ButtonRead": "Lees",
|
||||
"ButtonReadLess": "Minder lezen",
|
||||
|
|
|
@ -31,7 +31,6 @@
|
|||
"ButtonPause": "Pause",
|
||||
"ButtonPlay": "Spill av",
|
||||
"ButtonPlayEpisode": "Spill episode",
|
||||
"ButtonPlaying": "Spiller av",
|
||||
"ButtonPlaylists": "Spillelister",
|
||||
"ButtonRead": "Les",
|
||||
"ButtonRemove": "Fjern",
|
||||
|
|
|
@ -34,7 +34,6 @@
|
|||
"ButtonPause": "Wstrzymaj",
|
||||
"ButtonPlay": "Odtwarzaj",
|
||||
"ButtonPlayEpisode": "Odtwórz odcinek",
|
||||
"ButtonPlaying": "Odtwarzane",
|
||||
"ButtonPlaylists": "Listy odtwarzania",
|
||||
"ButtonRead": "Czytaj",
|
||||
"ButtonReadLess": "Czytaj mniej",
|
||||
|
|
|
@ -34,7 +34,6 @@
|
|||
"ButtonPause": "Pausar",
|
||||
"ButtonPlay": "Reproduzir",
|
||||
"ButtonPlayEpisode": "Reproduzir Episódio",
|
||||
"ButtonPlaying": "Reproduzindo",
|
||||
"ButtonPlaylists": "Lista de Reprodução",
|
||||
"ButtonRead": "Ler",
|
||||
"ButtonReadLess": "Ler menos",
|
||||
|
|
|
@ -35,7 +35,6 @@
|
|||
"ButtonPause": "Пауза",
|
||||
"ButtonPlay": "Слушать",
|
||||
"ButtonPlayEpisode": "Воспроизвести эпизод",
|
||||
"ButtonPlaying": "Проигрывается",
|
||||
"ButtonPlaylists": "Плейлисты",
|
||||
"ButtonRead": "Читать",
|
||||
"ButtonReadLess": "Читать меньше",
|
||||
|
|
|
@ -35,7 +35,6 @@
|
|||
"ButtonPause": "Premor",
|
||||
"ButtonPlay": "Predvajaj",
|
||||
"ButtonPlayEpisode": "Predvajan epizodo",
|
||||
"ButtonPlaying": "Predvajam",
|
||||
"ButtonPlaylists": "Seznami predvajanj",
|
||||
"ButtonRead": "Preberi",
|
||||
"ButtonReadLess": "Preberi manj",
|
||||
|
|
|
@ -35,7 +35,6 @@
|
|||
"ButtonPause": "Pausa",
|
||||
"ButtonPlay": "Spela",
|
||||
"ButtonPlayEpisode": "Spela Episod",
|
||||
"ButtonPlaying": "Spelar",
|
||||
"ButtonPlaylists": "Spellistor",
|
||||
"ButtonRead": "Läs",
|
||||
"ButtonRemove": "Ta bort",
|
||||
|
|
|
@ -35,7 +35,6 @@
|
|||
"ButtonPause": "Призупинити",
|
||||
"ButtonPlay": "Слухати",
|
||||
"ButtonPlayEpisode": "Слухати епізод",
|
||||
"ButtonPlaying": "Відтворюється",
|
||||
"ButtonPlaylists": "Списки відтворення",
|
||||
"ButtonRead": "Читати",
|
||||
"ButtonReadLess": "Читати менше",
|
||||
|
|
|
@ -32,7 +32,6 @@
|
|||
"ButtonOverride": "Bỏ Qua",
|
||||
"ButtonPause": "Tạm Dừng",
|
||||
"ButtonPlay": "Phát",
|
||||
"ButtonPlaying": "Đang Phát",
|
||||
"ButtonPlaylists": "Danh Sách Phát",
|
||||
"ButtonRead": "Đọc",
|
||||
"ButtonRemove": "Xóa",
|
||||
|
|
|
@ -35,7 +35,6 @@
|
|||
"ButtonPause": "暂停",
|
||||
"ButtonPlay": "播放",
|
||||
"ButtonPlayEpisode": "播放剧集",
|
||||
"ButtonPlaying": "正在播放",
|
||||
"ButtonPlaylists": "播放列表",
|
||||
"ButtonRead": "读取",
|
||||
"ButtonReadLess": "阅读较少",
|
||||
|
|
|
@ -34,7 +34,6 @@
|
|||
"ButtonPause": "暫停",
|
||||
"ButtonPlay": "播放",
|
||||
"ButtonPlayEpisode": "播放劇集",
|
||||
"ButtonPlaying": "正在播放",
|
||||
"ButtonPlaylists": "播放列表",
|
||||
"ButtonRead": "讀取",
|
||||
"ButtonRemove": "移除",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue