diff --git a/CHANGELOG.md b/CHANGELOG.md index 54a761e..b98d93c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +1.4.0 / 2022-12-07 +------------------ +- Добавлена возможность расширенного поиска (раздел ""). Поиск не оптимизирован и может сильно нагружать сервер. +Отключить можно в конфиге, параметр extendedSearch +- Улучшение поддержки reverse-proxy, в конфиг добавлены параметры server.root и opds.root для встраивания inpx-web в уже существующий веб-сервер +- В настройки веб-интерфейса добавлена опция "Скачивать книги в виде zip-архива" +- Исправлен баг "Android-читалки не очень хорошо работают с OPDS" (#4) +- Добавлена сборка релизов для Linux arm64 +- В readme добавлена ссылка для донатов: [отблагодарить автора проекта](https://donatty.com/liberama) + 1.3.3 / 2022-11-28 ------------------ diff --git a/README.md b/README.md index 92d5bd7..e78a872 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,8 @@ OPDS-сервер доступен по адресу [http://127.0.0.1:12380/opd Для указания местоположения .inpx-файла или папки с файлами библиотеки, воспользуйтесь [параметрами командной строки](#cli). Дополнительные параметры сервера настраиваются в [конфигурационном файле](#config). +[Отблагодарить автора проекта](https://donatty.com/liberama) + ## * [Возможности программы](#capabilities) * [Использование](#usage) @@ -87,6 +89,11 @@ Options: // 0 - отключить таймаут, время доступа по паролю не ограничено "accessTimeout": 0, + // включить(true)/выключить(false) возможность расширенного поиска (раздел "") + // расширенный поиск не оптимизирован, поэтому может сильно нагружать сервер + // чтобы ускорить поиск, увеличьте параметр dbCacheSize + "extendedSearch": true, + // содержимое кнопки-ссылки "(читать)", если не задано - кнопка "(читать)" не показывается // пример: "https://omnireader.ru/#/reader?url=${DOWNLOAD_LINK}" // на место ${DOWNLOAD_LINK} будет подставлена ссылка на скачивание файла книги @@ -99,7 +106,7 @@ Options: // если надо кешировать всю БД, можно поставить значение от 1000 и больше "dbCacheSize": 5, - // максимальный размер в байтах директории закешированных файлов в <раб.дир>/public/files + // максимальный размер в байтах директории закешированных файлов в <раб.дир>/public-files // чистка каждый час "maxFilesDirSize": 1073741824, @@ -140,17 +147,23 @@ Options: "remoteLib": false, // настройки веб-сервера + // парамертр root указывает путь для кореневой страницы inpx-web + // например для "root": "/library", веб-интерфейс будет доступен по адресу http://127.0.0.1:12380/library + // root необходим при настройке reverse-proxy и встраивании inpx-web в уже существующий сервер "server": { "host": "0.0.0.0", - "port": "12380" + "port": "12380", + "root": "" }, // настройки opds-сервера // user, password используются для Basic HTTP authentication + // параметр root задает путь для доступа к opds-серверу "opds": { "enabled": true, "user": "", - "password": "" + "password": "", + "root": "/opds" } } ``` @@ -198,7 +211,6 @@ Options: { "info": { "collection": "Новое название коллекции", - "structure": "", "version": "1.0.0" }, "filter": "(r) => r.del == 0", @@ -280,6 +292,8 @@ sudo service nginx reload Сборка только в среде Linux. Необходима версия node.js не ниже 16. +Для сборки linux-arm64 необходимо предварительно установить [QEMU](https://wiki.debian.org/QemuUserEmulation). + ```sh git clone https://github.com/bookpauk/inpx-web cd inpx-web diff --git a/build/appdir.js b/build/appdir.js new file mode 100644 index 0000000..d9d1c2d --- /dev/null +++ b/build/appdir.js @@ -0,0 +1 @@ +module.exports = 'app0b58f8bd9fbfa95504ba'; \ No newline at end of file diff --git a/build/prepkg.js b/build/prepkg.js index ce9f94a..ea91575 100644 --- a/build/prepkg.js +++ b/build/prepkg.js @@ -12,8 +12,8 @@ const publicDir = `${tmpDir}/public`; const outDir = `${distDir}/${platform}`; async function build() { - if (platform != 'linux' && platform != 'win' && platform != 'macos') - throw new Error(`Unknown platform: ${platform}`); + if (!platform) + throw new Error(`Please set platform`); await fs.emptyDir(outDir); diff --git a/build/release.js b/build/release.js index 3799773..85839d1 100644 --- a/build/release.js +++ b/build/release.js @@ -22,6 +22,7 @@ async function main() { await fs.emptyDir(outDir); await makeRelease('win'); await makeRelease('linux'); + await makeRelease('linux-arm64'); await makeRelease('macos'); } catch(e) { console.error(e); diff --git a/build/webpack.base.config.js b/build/webpack.base.config.js index 16bb957..190cb54 100644 --- a/build/webpack.base.config.js +++ b/build/webpack.base.config.js @@ -2,6 +2,8 @@ const path = require('path'); const DefinePlugin = require('webpack').DefinePlugin; const { VueLoaderPlugin } = require('vue-loader'); +const appdir = require('./appdir'); + const clientDir = path.resolve(__dirname, '../client'); module.exports = { @@ -12,7 +14,7 @@ module.exports = { }, entry: [`${clientDir}/main.js`], output: { - publicPath: '/app/', + publicPath: `/${appdir}/`, clean: true }, diff --git a/build/webpack.dev.config.js b/build/webpack.dev.config.js index 5c64251..19556b5 100644 --- a/build/webpack.dev.config.js +++ b/build/webpack.dev.config.js @@ -16,9 +16,8 @@ module.exports = merge(baseWpConfig, { mode: 'development', devtool: 'inline-source-map', output: { - path: `${publicDir}/app`, + path: `${publicDir}${baseWpConfig.output.publicPath}`, filename: 'bundle.js', - clean: true }, module: { diff --git a/build/webpack.prod.config.js b/build/webpack.prod.config.js index 03adc5e..748c650 100644 --- a/build/webpack.prod.config.js +++ b/build/webpack.prod.config.js @@ -18,9 +18,8 @@ fs.emptyDirSync(publicDir); module.exports = merge(baseWpConfig, { mode: 'production', output: { - path: `${publicDir}/app`, + path: `${publicDir}${baseWpConfig.output.publicPath}`, filename: 'bundle.[contenthash].js', - clean: true }, module: { rules: [ diff --git a/client/components/Api/Api.vue b/client/components/Api/Api.vue index 7b34ed3..84bec97 100644 --- a/client/components/Api/Api.vue +++ b/client/components/Api/Api.vue @@ -235,6 +235,10 @@ class Api { return await this.request({action: 'search', from, query}, 30); } + async bookSearch(query) { + return await this.request({action: 'bookSearch', query}, 30); + } + async getAuthorBookList(authorId) { return await this.request({action: 'get-author-book-list', authorId}); } diff --git a/client/components/Api/webSocketConnection.js b/client/components/Api/webSocketConnection.js index f4c2e47..a644c0c 100644 --- a/client/components/Api/webSocketConnection.js +++ b/client/components/Api/webSocketConnection.js @@ -1,3 +1,7 @@ import WebSocketConnection from '../../../server/core/WebSocketConnection'; -export default new WebSocketConnection(); \ No newline at end of file +const protocol = (window.location.protocol == 'https:' ? 'wss:' : 'ws:'); +let url = `${protocol}//${window.location.host}${window.location.pathname}`; +url += (url[url.length - 1] === '/' ? 'ws' : '/ws'); + +export default new WebSocketConnection(url); \ No newline at end of file diff --git a/client/components/Search/BaseList.js b/client/components/Search/BaseList.js index 0082d58..dee8119 100644 --- a/client/components/Search/BaseList.js +++ b/client/components/Search/BaseList.js @@ -1,3 +1,4 @@ +import axios from 'axios'; import dayjs from 'dayjs'; import _ from 'lodash'; @@ -20,13 +21,16 @@ const componentOptions = { this.loadSettings(); }, search: { - handler(newValue) { - this.limit = newValue.limit; - - if (this.pageCount > 1) - this.prevPage = this.search.page; - - this.refresh(); + handler() { + if (!this.isExtendedSearch) + this.refresh(); + }, + deep: true, + }, + extSearch: { + handler() { + if (this.isExtendedSearch) + this.refresh(); }, deep: true, }, @@ -40,6 +44,7 @@ export default class BaseList { _props = { list: Object, search: Object, + extSearch: Object, genreMap: Object, }; @@ -50,6 +55,7 @@ export default class BaseList { expandedAuthor = []; expandedSeries = []; + downloadAsZip = false; showCounts = true; showRates = true; showGenres = true; @@ -66,6 +72,7 @@ export default class BaseList { tableData = []; created() { + this.isExtendedSearch = false; this.commit = this.$store.commit; this.api = this.$root.api; @@ -81,6 +88,7 @@ export default class BaseList { this.expandedAuthor = _.cloneDeep(settings.expandedAuthor); this.expandedSeries = _.cloneDeep(settings.expandedSeries); + this.downloadAsZip = settings.downloadAsZip; this.showCounts = settings.showCounts; this.showRates = settings.showRates; this.showGenres = settings.showGenres; @@ -105,16 +113,19 @@ export default class BaseList { } selectAuthor(author) { - this.search.author = `=${author}`; + const search = (this.isExtendedSearch ? this.extSearch : this.search); + search.author = `=${author}`; this.scrollToTop(); } selectSeries(series) { - this.search.series = `=${series}`; + const search = (this.isExtendedSearch ? this.extSearch : this.search); + search.series = `=${series}`; } selectTitle(title) { - this.search.title = `=${title}`; + const search = (this.isExtendedSearch ? this.extSearch : this.search); + search.title = `=${title}`; } async download(book, action) { @@ -133,13 +144,20 @@ export default class BaseList { const response = await this.api.getBookLink(book._uid); const link = response.link; - const href = `${window.location.origin}${link}`; + let href = `${window.location.origin}${link}`; + //downloadAsZip + if (this.downloadAsZip && (action == 'download' || action == 'copyLink')) { + href += '/zip'; + //подожлем формирования zip-файла + await axios.head(href); + } + + //action if (action == 'download') { //скачивание const d = this.$refs.download; d.href = href; - d.download = response.downFileName; d.click(); } else if (action == 'copyLink') { @@ -506,9 +524,9 @@ export default class BaseList { } getQuery() { - let newQuery = _.cloneDeep(this.search); - newQuery = newQuery.setDefaults(newQuery); - delete newQuery.setDefaults; + const search = (this.isExtendedSearch ? this.extSearch : this.search); + const newQuery = {}; + search.setDefaults(newQuery, search); //дата if (newQuery.date) { @@ -519,8 +537,8 @@ export default class BaseList { newQuery.offset = (newQuery.page - 1)*newQuery.limit; //del - if (!this.showDeleted) - newQuery.del = 0; + if (!newQuery.del && !this.showDeleted) + newQuery.del = '0'; return newQuery; } diff --git a/client/components/Search/BookView/BookView.vue b/client/components/Search/BookView/BookView.vue index 0f2bca8..dd0912c 100644 --- a/client/components/Search/BookView/BookView.vue +++ b/client/components/Search/BookView/BookView.vue @@ -33,7 +33,7 @@
-
+
{{ bookAuthor }}
@@ -46,7 +46,7 @@
{{ book.title }}
-
+
{{ bookSeries }}
@@ -79,10 +79,10 @@ {{ bookDate }}
-
-
- {{ book }} +
+
{{ book }}
+
@@ -117,6 +117,7 @@ class BookView { showGenres = true; showDeleted = false; showDates = false; + showJson = false; created() { this.loadSettings(); @@ -130,6 +131,7 @@ class BookView { this.showGenres = settings.showGenres; this.showDates = settings.showDates; this.showDeleted = settings.showDeleted; + this.showJson = settings.showJson; } get settings() { diff --git a/client/components/Search/ExtendedList/ExtendedList.vue b/client/components/Search/ExtendedList/ExtendedList.vue new file mode 100644 index 0000000..19a19d5 --- /dev/null +++ b/client/components/Search/ExtendedList/ExtendedList.vue @@ -0,0 +1,128 @@ + + + + + diff --git a/client/components/Search/Search.vue b/client/components/Search/Search.vue index 4292d95..05309bc 100644 --- a/client/components/Search/Search.vue +++ b/client/components/Search/Search.vue @@ -55,9 +55,9 @@ -
+
@@ -110,7 +110,7 @@
@@ -121,8 +121,8 @@
-
-
+
+
-
+
+{{ extendedParamsMessage }}
+ +
+ + + + + {{ extSearchNames }} + + + +
+ +
+ А +
+ +
+ +
+ +
+ С +
+ +
+ +
+ +
+ К +
+ +
+ +
+ + + +
@@ -197,12 +275,16 @@
{{ foundCountMessage }}
+ +
+ +
- +
@@ -224,6 +306,7 @@ +
@@ -234,6 +317,7 @@ import vueComponent from '../vueComponent.js'; import AuthorList from './AuthorList/AuthorList.vue'; import SeriesList from './SeriesList/SeriesList.vue'; import TitleList from './TitleList/TitleList.vue'; +import ExtendedList from './ExtendedList/ExtendedList.vue'; import PageScroller from './PageScroller/PageScroller.vue'; import SettingsDialog from './SettingsDialog/SettingsDialog.vue'; @@ -242,6 +326,7 @@ import SelectLangDialog from './SelectLangDialog/SelectLangDialog.vue'; import SelectLibRateDialog from './SelectLibRateDialog/SelectLibRateDialog.vue'; import SelectDateDialog from './SelectDateDialog/SelectDateDialog.vue'; import BookInfoDialog from './BookInfoDialog/BookInfoDialog.vue'; +import SelectExtSearchDialog from './SelectExtSearchDialog/SelectExtSearchDialog.vue'; import authorBooksStorage from './authorBooksStorage'; import DivBtn from '../share/DivBtn.vue'; @@ -252,10 +337,13 @@ import diffUtils from '../../share/diffUtils'; import _ from 'lodash'; +const maxLimit = 1000; + const route2component = { 'author': {component: 'AuthorList', label: 'Авторы'}, 'series': {component: 'SeriesList', label: 'Серии'}, 'title': {component: 'TitleList', label: 'Книги'}, + 'extended': {component: 'ExtendedList', label: 'Расширенный поиск'}, }; const componentOptions = { @@ -263,6 +351,7 @@ const componentOptions = { AuthorList, SeriesList, TitleList, + ExtendedList, PageScroller, SettingsDialog, SelectGenreDialog, @@ -270,6 +359,7 @@ const componentOptions = { SelectLibRateDialog, SelectDateDialog, BookInfoDialog, + SelectExtSearchDialog, Dialog, DivBtn }, @@ -292,6 +382,19 @@ const componentOptions = { this.makeTitle(); this.updateRouteQueryFromSearch(); this.updateSearchDate(true); + + //extSearch + if (this.isExtendedSearch) { + this.extSearch.page = newValue.page; + this.extSearch.limit = newValue.limit; + } + }, + deep: true, + }, + extSearch: { + handler() { + this.makeTitle(); + this.updateRouteQueryFromSearch(); }, deep: true, }, @@ -310,6 +413,9 @@ const componentOptions = { langDefault() { this.updateSearchFromRouteQuery(this.$route); }, + showJson(newValue) { + this.setSetting('showJson', newValue); + }, list: { handler(newValue) { this.updateGenreTreeIfNeeded(); @@ -337,6 +443,8 @@ const componentOptions = { if (this.getListRoute() != newValue) { this.updateRouteQueryFromSearch(); } + + this.makeTitle(); }, searchDate() { this.updateSearchDate(false); @@ -362,6 +470,7 @@ class Search { selectLibRateDialogVisible = false; selectDateDialogVisible = false; bookInfoDialogVisible = false; + selectExtSearchDialogVisible = false; pageCount = 1; @@ -370,21 +479,8 @@ class Search { inputDebounce = 200; //search fields - search = { - setDefaults(search) { - return Object.assign({}, search, { - author: search.author || '', - series: search.series || '', - title: search.title || '', - genre: search.genre || '', - lang: search.lang || '', - date: search.date || '', - librate: search.librate || '', - page: search.page || 1, - limit: search.limit || 50, - }); - }, - }; + search = {}; + extSearch = {}; searchDate = ''; prevManualDate = ''; @@ -394,6 +490,7 @@ class Search { langDefault = ''; limit = 20; extendedParams = false; + showJson = false; //stuff prevList = {}; @@ -423,12 +520,22 @@ class Search { {label: 'выбрать даты', value: 'manual'}, ]; + generateDefaults(obj, fields) { + obj.setDefaults = (self, value = {}) => { + for (const f of fields) + self[f] = value[f] || ''; + + self.page = value.page || 1; + self.limit = value.limit || 50; + }; + } + created() { this.commit = this.$store.commit; this.api = this.$root.api; - this.search = this.search.setDefaults(this.search); - this.search.lang = this.langDefault; + this.generateDefaults(this.search, ['author', 'series', 'title', 'genre', 'lang', 'date', 'librate']); + this.search.setDefaults(this.search); this.loadSettings(); } @@ -437,6 +544,10 @@ class Search { (async() => { await this.api.updateConfig(); + this.generateDefaults(this.extSearch, this.recStruct.map(f => f.field)); + this.extSearch.setDefaults(this.extSearch); + this.search.lang = this.langDefault; + //для встраивания в liberama window.addEventListener('message', (event) => { if (!_.isObject(event.data) || event.data.from != 'ExternalLibs') @@ -454,11 +565,11 @@ class Search { this.$refs.authorInput.focus(); this.updateListFromRoute(this.$route); - this.updateSearchFromRouteQuery(this.$route); - - this.sendMessage({type: 'mes', data: 'hello-from-inpx-web'}); this.ready = true; + + this.sendMessage({type: 'mes', data: 'hello-from-inpx-web'}); + this.updateSearchFromRouteQuery(this.$route); })(); } @@ -472,6 +583,7 @@ class Search { this.expandedSeries = _.cloneDeep(settings.expandedSeries); this.abCacheEnabled = settings.abCacheEnabled; this.langDefault = settings.langDefault; + this.showJson = settings.showJson; } recvMessage(d) { @@ -498,6 +610,13 @@ class Search { return this.$store.state.config; } + get recStruct() { + if (this.config.dbConfig && this.config.dbConfig.inpxInfo.recStruct) + return this.config.dbConfig.inpxInfo.recStruct; + else + return []; + } + get settings() { return this.$store.state.settings; } @@ -529,7 +648,13 @@ class Search { get listOptions() { const result = []; for (const [route, rec] of Object.entries(route2component)) - result.push({label: rec.label, value: route}); + if (route == 'extended') { + if (this.config.extendedSearch) { + result.push({value: route, icon: 'la la-code', size: '10px'}); + } + } else { + result.push({label: rec.label, value: route, icon: rec.icon}); + } return result; } @@ -543,6 +668,19 @@ class Search { return result.filter(s => s).join(', '); } + get isExtendedSearch() { + return this.selectedList === 'extended'; + } + + get extSearchNames() { + let result = []; + for (const f of this.recStruct) { + if (this.extSearch[f.field]) + result.push(`${f.field}=${this.extSearch[f.field]}`); + } + return result.join(', '); + } + inputBgColor(inp) { if (inp === this.selectedList) return 'white'; @@ -552,8 +690,12 @@ class Search { async updateListFromRoute(to) { const newPath = to.path; + let newList = this.getListRoute(newPath); + if (newList == 'extended' && !this.config.extendedSearch) + newList = ''; newList = (newList ? newList : 'author'); + if (this.selectedList != newList) this.selectedList = newList; } @@ -582,30 +724,35 @@ class Search { let result = `Коллекция ${this.collection}`; - const search = this.search; - const specSym = new Set(['*', '#']); - const correctValue = (v) => { - if (v) { - if (v[0] === '=') - v = v.substring(1); - else if (!specSym.has(v[0])) - v = '^' + v; + if (!this.isExtendedSearch) { + const search = this.search; + const specSym = new Set(['*', '#']); + const correctValue = (v) => { + if (v) { + if (v[0] === '=') + v = v.substring(1); + else if (!specSym.has(v[0])) + v = '^' + v; + } + return v || ''; + }; + + if (search.author || search.series || search.title) { + const as = (search.author ? search.author.split(',') : []); + const author = (as.length ? as[0] : '') + (as.length > 1 ? ' и др.' : ''); + + const a = correctValue(author); + let s = correctValue(search.series); + s = (s ? `(Серия: ${s})` : ''); + let t = correctValue(search.title); + t = (t ? `"${t}"` : ''); + + result = [s, t].filter(v => v).join(' '); + result = [a, result].filter(v => v).join(' '); } - return v || ''; - }; - - if (search.author || search.series || search.title) { - const as = (search.author ? search.author.split(',') : []); - const author = (as.length ? as[0] : '') + (as.length > 1 ? ' и др.' : ''); - - const a = correctValue(author); - let s = correctValue(search.series); - s = (s ? `(Серия: ${s})` : ''); - let t = correctValue(search.title); - t = (t ? `"${t}"` : ''); - - result = [s, t].filter(v => v).join(' '); - result = [a, result].filter(v => v).join(' '); + } else { + if (this.extSearchNames) + result = this.extSearchNames; } this.$root.setAppTitle(result); @@ -614,8 +761,7 @@ class Search { } showSearchHelp() { - let info = ''; - info += `
`; + let info = `
`; info += `

Для раздела Авторы, работу поискового движка можно описать простой фразой: найти авторов по указанным критериям. @@ -654,7 +800,7 @@ class Search {

Для разделов Серии, Книги все аналогично разделу Авторы.

-`; +`; this.$root.stdDialog.alert(info, 'Памятка', {iconName: 'la la-info-circle'}); } @@ -743,6 +889,16 @@ class Search { this.hideTooltip(); this.selectLibRateDialogVisible = true; } + + selectExtSearch() { + this.hideTooltip(); + this.selectExtSearchDialogVisible = true; + } + + clearExtSearch() { + const self = this.extSearch; + self.setDefaults(self, {page: self.page, limit: self.limit}); + } onScroll() { const curScrollTop = this.$refs.scroller.scrollTop; @@ -847,6 +1003,8 @@ class Search { } updateSearchFromRouteQuery(to) { + if (!this.ready) + return; if (this.list.liberamaReady) this.sendCurrentUrl(); @@ -855,22 +1013,34 @@ class Search { const query = to.query; - this.search = this.search.setDefaults( - Object.assign({}, this.search, { - author: query.author, - series: query.series, - title: query.title, - genre: query.genre, - lang: (typeof(query.lang) == 'string' ? query.lang : this.langDefault), - date: query.date, - librate: query.librate, - page: parseInt(query.page, 10), - limit: parseInt(query.limit, 10) || this.search.limit, - }) - ); + this.search.setDefaults(this.search, { + author: query.author, + series: query.series, + title: query.title, + genre: query.genre, + lang: (typeof(query.lang) == 'string' ? query.lang : this.langDefault), + date: query.date, + librate: query.librate, - if (this.search.limit > 1000) - this.search.limit = 1000; + page: parseInt(query.page, 10), + limit: parseInt(query.limit, 10) || this.search.limit, + }); + + if (this.search.limit > maxLimit) + this.search.limit = maxLimit; + + const queryExtSearch = { + page: this.search.page, + limit: this.search.limit, + }; + + for (const f of this.recStruct) { + const field = `ex_${f.field}`; + if (query[field]) + queryExtSearch[f.field] = query[field]; + } + + this.extSearch.setDefaults(this.extSearch, queryExtSearch); } updateRouteQueryFromSearch() { @@ -880,11 +1050,12 @@ class Search { this.routeUpdating = true; try { const oldQuery = this.$route.query; - const cloned = _.cloneDeep(this.search); + let query = {}; - delete cloned.setDefaults; + const cloned = {}; + this.search.setDefaults(cloned, this.search); - const query = _.pickBy(cloned); + query = _.pickBy(cloned); if (this.search.lang == this.langDefault) { delete query.lang; @@ -892,6 +1063,12 @@ class Search { query.lang = this.search.lang; } + for (const f of this.recStruct) { + const field = `ex_${f.field}`; + if (this.extSearch[f.field]) + query[field] = this.extSearch[f.field]; + } + const diff = diffUtils.getObjDiff(oldQuery, query); if (!diffUtils.isEmptyObjDiff(diff)) { this.$router.replace({path: this.selectedList, query}); @@ -990,6 +1167,12 @@ class Search { window.open(window.location.href, '_blank'); } + extToList(list) { + if (this.extSearch[list]) + this.search[list] = this.extSearch[list]; + this.selectedList = list; + } + async logout() { await this.api.logout(); } diff --git a/client/components/Search/SelectExtSearchDialog/SelectExtSearchDialog.vue b/client/components/Search/SelectExtSearchDialog/SelectExtSearchDialog.vue new file mode 100644 index 0000000..b4fe73d --- /dev/null +++ b/client/components/Search/SelectExtSearchDialog/SelectExtSearchDialog.vue @@ -0,0 +1,202 @@ + + + + + \ No newline at end of file diff --git a/client/components/Search/SettingsDialog/SettingsDialog.vue b/client/components/Search/SettingsDialog/SettingsDialog.vue index 1d25da2..42e8074 100644 --- a/client/components/Search/SettingsDialog/SettingsDialog.vue +++ b/client/components/Search/SettingsDialog/SettingsDialog.vue @@ -19,7 +19,8 @@ />
- + + @@ -60,6 +61,9 @@ const componentOptions = { limit(newValue) { this.commit('setSettings', {'limit': newValue}); }, + downloadAsZip(newValue) { + this.commit('setSettings', {'downloadAsZip': newValue}); + }, showCounts(newValue) { this.commit('setSettings', {'showCounts': newValue}); }, @@ -93,6 +97,7 @@ class SettingsDialog { //settings limit = 20; + downloadAsZip = false; showCounts = true; showRates = true; showInfo = true; @@ -129,6 +134,7 @@ class SettingsDialog { this.limit = settings.limit; + this.downloadAsZip = settings.downloadAsZip; this.showCounts = settings.showCounts; this.showRates = settings.showRates; this.showInfo = settings.showInfo; diff --git a/client/components/share/DivBtn.vue b/client/components/share/DivBtn.vue index d1b6c6d..e9208ae 100644 --- a/client/components/share/DivBtn.vue +++ b/client/components/share/DivBtn.vue @@ -1,5 +1,5 @@