From 7860d9f92becd8b4d3f808e3bb32c4d74e654c61 Mon Sep 17 00:00:00 2001 From: bkfox Date: Sun, 8 Nov 2020 17:54:49 +0100 Subject: [PATCH] work on lists filters + nav items --- aircox/models/page.py | 16 ++++++- aircox/static/aircox/admin.js | 10 ++--- aircox/static/aircox/main.js | 10 ++--- aircox/static/aircox/streamer.js | 2 +- aircox/templates/aircox/base.html | 12 ------ aircox/templates/aircox/basepage_list.html | 43 +++++++++++++++++++ aircox/templates/aircox/diffusion_list.html | 2 +- aircox/templates/aircox/log_list.html | 2 +- aircox/templates/aircox/page_list.html | 46 --------------------- aircox/views/episode.py | 12 ++++++ aircox/views/page.py | 21 +++++++--- assets/public/app.js | 23 ++++++++--- assets/public/index.js | 13 ++++-- 13 files changed, 126 insertions(+), 86 deletions(-) diff --git a/aircox/models/page.py b/aircox/models/page.py index 6a51195..ef5b390 100644 --- a/aircox/models/page.py +++ b/aircox/models/page.py @@ -185,12 +185,25 @@ class StaticPage(BasePage): (ATTACH_TO_EPISODES, _('Episodes list')), (ATTACH_TO_ARTICLES, _('Articles list')), ) + VIEWS = { + ATTACH_TO_HOME: 'home', + ATTACH_TO_DIFFUSIONS: 'diffusion-list', + ATTACH_TO_LOGS: 'log-list', + ATTACH_TO_PROGRAMS: 'program-list', + ATTACH_TO_EPISODES: 'episode-list', + ATTACH_TO_ARTICLES: 'article-list', + } attach_to = models.SmallIntegerField( _('attach to'), choices=ATTACH_TO_CHOICES, blank=True, null=True, help_text=_('display this page content to related element'), ) + def get_absolute_url(self): + if self.attach_to: + return reverse(self.VIEWS[self.attach_to]) + return super().get_absolute_url() + class Comment(models.Model): page = models.ForeignKey( @@ -216,8 +229,7 @@ class NavItem(models.Model): text = models.CharField(_('title'), max_length=64) url = models.CharField(_('url'), max_length=256, blank=True, null=True) page = models.ForeignKey(StaticPage, models.CASCADE, - verbose_name=_('page'), blank=True, null=True, - limit_choices_to={'attach_to__isnull': True}) + verbose_name=_('page'), blank=True, null=True) class Meta: verbose_name = _('Menu item') verbose_name_plural = _('Menu items') diff --git a/aircox/static/aircox/admin.js b/aircox/static/aircox/admin.js index 30e4fa7..a5611e3 100644 --- a/aircox/static/aircox/admin.js +++ b/aircox/static/aircox/admin.js @@ -222,7 +222,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _nod /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"defaultConfig\", function() { return defaultConfig; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return AppBuilder; });\n/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! vue */ \"./node_modules/vue/dist/vue.esm.browser.js\");\n\n\nconst defaultConfig = {\n el: '#app',\n delimiters: ['[[', ']]'],\n\n computed: {\n player() { return window.aircox.player; },\n },\n}\n\n\nclass AppBuilder {\n constructor(config={}) {\n this._config = config;\n this.app = null;\n }\n\n get config() {\n let config = this._config instanceof Function ? this._config() : this._config;\n for(var k of new Set([...Object.keys(config || {}), ...Object.keys(defaultConfig)])) {\n if(!config[k] && defaultConfig[k])\n config[k] = defaultConfig[k]\n else if(config[k] instanceof Object)\n config[k] = {...defaultConfig[k], ...config[k]}\n }\n return config;\n }\n\n set config(value) {\n this._config = value;\n }\n\n destroy() {\n self.app && self.app.$destroy();\n self.app = null;\n }\n\n fetch(url, options) {\n return fetch(url, options).then(response => response.text())\n .then(content => {\n let doc = new DOMParser().parseFromString(content, 'text/html');\n let app = doc.getElementById('app');\n content = app ? app.innerHTML : content;\n return this.load({sync: true, content, title: doc.title, url })\n })\n }\n\n load({async=false,content=null, title=null, url=null}={}) {\n var self = this;\n return new Promise((resolve, reject) => {\n let func = () => {\n try {\n let config = self.config;\n const el = document.querySelector(config.el);\n if(!el)\n return reject(`Error: can't get element ${config.el}`)\n\n if(content)\n el.innerHTML = content\n if(title)\n document.title = title;\n if(url && content)\n history.pushState({ content: content, title: title }, '', url)\n\n this.app = new vue__WEBPACK_IMPORTED_MODULE_0__[\"default\"](config);\n resolve(self.app)\n } catch(error) {\n self.destroy();\n reject(error)\n }};\n async ? window.addEventListener('load', func) : func();\n });\n }\n\n loadFromState(state) {\n return this.load({ content: state.content, title: state.title });\n }\n}\n\n\n\n\n//# sourceURL=webpack:///./assets/public/app.js?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"defaultConfig\", function() { return defaultConfig; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return AppBuilder; });\n/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! vue */ \"./node_modules/vue/dist/vue.esm.browser.js\");\n\n\nconst defaultConfig = {\n el: '#app',\n delimiters: ['[[', ']]'],\n\n computed: {\n player() { return window.aircox.player; },\n },\n}\n\n\nclass AppBuilder {\n constructor(config={}) {\n this._config = config;\n this.title = null;\n this.app = null;\n }\n\n get config() {\n let config = this._config instanceof Function ? this._config() : this._config;\n for(var k of new Set([...Object.keys(config || {}), ...Object.keys(defaultConfig)])) {\n if(!config[k] && defaultConfig[k])\n config[k] = defaultConfig[k]\n else if(config[k] instanceof Object)\n config[k] = {...defaultConfig[k], ...config[k]}\n }\n return config;\n }\n\n set config(value) {\n this._config = value;\n }\n\n destroy() {\n self.app && self.app.$destroy();\n self.app = null;\n }\n\n fetch(url, options) {\n return fetch(url, options).then(response => response.text())\n .then(content => {\n let doc = new DOMParser().parseFromString(content, 'text/html');\n let app = doc.getElementById('app');\n content = app ? app.innerHTML : content;\n return this.load({sync: true, content, title: doc.title, url })\n })\n }\n\n load({async=false,content=null,title=null}={}) {\n var self = this;\n return new Promise((resolve, reject) => {\n let func = () => {\n try {\n let config = self.config;\n const el = document.querySelector(config.el);\n if(!el)\n return reject(`Error: can't get element ${config.el}`)\n\n if(content)\n el.innerHTML = content\n if(title)\n document.title = title;\n this.app = new vue__WEBPACK_IMPORTED_MODULE_0__[\"default\"](config);\n resolve(self.app)\n } catch(error) {\n self.destroy();\n reject(error)\n }};\n async ? window.addEventListener('load', func) : func();\n });\n }\n\n /// Save application state into browser history\n historySave(url,replace=false) {\n const el = document.querySelector(this.config.el);\n const state = {\n content: el.innerHTML,\n title: document.title,\n };\n\n if(replace)\n history.replaceState(state, '', url)\n else\n history.pushState(state, '', url)\n }\n\n /// Load application from browser history's state\n historyLoad(state) {\n return this.load({ content: state.content, title: state.title });\n }\n}\n\n\n\n\n//# sourceURL=webpack:///./assets/public/app.js?"); /***/ }), @@ -306,7 +306,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _nod /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! vue */ \"./node_modules/vue/dist/vue.esm.browser.js\");\n/* harmony import */ var _fortawesome_fontawesome_free_css_all_min_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @fortawesome/fontawesome-free/css/all.min.css */ \"./node_modules/@fortawesome/fontawesome-free/css/all.min.css\");\n/* harmony import */ var _fortawesome_fontawesome_free_css_all_min_css__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_fortawesome_fontawesome_free_css_all_min_css__WEBPACK_IMPORTED_MODULE_1__);\n/* harmony import */ var _fortawesome_fontawesome_free_css_fontawesome_min_css__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @fortawesome/fontawesome-free/css/fontawesome.min.css */ \"./node_modules/@fortawesome/fontawesome-free/css/fontawesome.min.css\");\n/* harmony import */ var _fortawesome_fontawesome_free_css_fontawesome_min_css__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(_fortawesome_fontawesome_free_css_fontawesome_min_css__WEBPACK_IMPORTED_MODULE_2__);\n/* harmony import */ var _app__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./app */ \"./assets/public/app.js\");\n/* harmony import */ var _sound__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./sound */ \"./assets/public/sound.js\");\n/* harmony import */ var _model__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./model */ \"./assets/public/model.js\");\n/* harmony import */ var _styles_scss__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./styles.scss */ \"./assets/public/styles.scss\");\n/* harmony import */ var _styles_scss__WEBPACK_IMPORTED_MODULE_6___default = /*#__PURE__*/__webpack_require__.n(_styles_scss__WEBPACK_IMPORTED_MODULE_6__);\n/* harmony import */ var _autocomplete__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./autocomplete */ \"./assets/public/autocomplete.vue\");\n/* harmony import */ var _episode__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./episode */ \"./assets/public/episode.vue\");\n/* harmony import */ var _player__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./player */ \"./assets/public/player.vue\");\n/* harmony import */ var _playlist__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ./playlist */ \"./assets/public/playlist.vue\");\n/* harmony import */ var _soundItem__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ./soundItem */ \"./assets/public/soundItem.vue\");\n/**\n * This module includes code available for both the public website and\n * administration interface)\n */\n//-- vendor\n\n\n\n\n\n\n//-- aircox\n\n\n\n\n\n\n\n\n\n\n\n\nvue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].component('a-autocomplete', _autocomplete__WEBPACK_IMPORTED_MODULE_7__[\"default\"])\nvue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].component('a-episode', _episode__WEBPACK_IMPORTED_MODULE_8__[\"default\"])\nvue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].component('a-player', _player__WEBPACK_IMPORTED_MODULE_9__[\"default\"])\nvue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].component('a-playlist', _playlist__WEBPACK_IMPORTED_MODULE_10__[\"default\"])\nvue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].component('a-sound-item', _soundItem__WEBPACK_IMPORTED_MODULE_11__[\"default\"])\n\n\nwindow.aircox = {\n // main application\n appBuilder: null,\n appConfig: {},\n get app() { return this.appBuilder.app },\n\n // player application\n playerBuilder: null,\n get playerApp() { return this.playerBuilder && this.playerBuilder.app },\n get player() { return this.playerApp && this.playerApp.$refs.player },\n\n onPageFetch(event) {\n let submit = event.type == 'submit';\n let target = submit || event.target.tagName == 'A'\n ? event.target : event.target.closest('a');\n if(!target || target.hasAttribute('target'))\n return;\n\n let url = submit ? target.getAttribute('action') || ''\n : target.getAttribute('href');\n if(url===null || !(url === '' || url.startsWith('/') || url.startsWith('?')))\n return;\n\n let options = {};\n if(submit) {\n let formData = new FormData(event.target);\n if(target.method == 'get')\n url += '?' + (new URLSearchParams(formData)).toString();\n else {\n options['method'] = target.method;\n options['body'] = formData;\n }\n }\n this.appBuilder.fetch(url, options);\n event.preventDefault();\n event.stopPropagation();\n },\n\n Set: _model__WEBPACK_IMPORTED_MODULE_5__[\"Set\"], Sound: _sound__WEBPACK_IMPORTED_MODULE_4__[\"default\"],\n};\nwindow.Vue = vue__WEBPACK_IMPORTED_MODULE_0__[\"default\"];\n\n\naircox.playerBuilder = new _app__WEBPACK_IMPORTED_MODULE_3__[\"default\"]({el: '#player'});\naircox.playerBuilder.load({async:true});\naircox.appBuilder = new _app__WEBPACK_IMPORTED_MODULE_3__[\"default\"](x => window.aircox.appConfig);\naircox.appBuilder.load({async:true}).then(app => {\n //-- load page hooks\n window.addEventListener('click', event => aircox.onPageFetch(event), true);\n window.addEventListener('submit', event => aircox.onPageFetch(event), true);\n window.addEventListener('popstate', event => {\n if(event.state && event.state.content)\n aircox.appBuilder.loadFromState(event.state);\n });\n})\n\n\n\n\n//# sourceURL=webpack:///./assets/public/index.js?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! vue */ \"./node_modules/vue/dist/vue.esm.browser.js\");\n/* harmony import */ var _fortawesome_fontawesome_free_css_all_min_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @fortawesome/fontawesome-free/css/all.min.css */ \"./node_modules/@fortawesome/fontawesome-free/css/all.min.css\");\n/* harmony import */ var _fortawesome_fontawesome_free_css_all_min_css__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_fortawesome_fontawesome_free_css_all_min_css__WEBPACK_IMPORTED_MODULE_1__);\n/* harmony import */ var _fortawesome_fontawesome_free_css_fontawesome_min_css__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @fortawesome/fontawesome-free/css/fontawesome.min.css */ \"./node_modules/@fortawesome/fontawesome-free/css/fontawesome.min.css\");\n/* harmony import */ var _fortawesome_fontawesome_free_css_fontawesome_min_css__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(_fortawesome_fontawesome_free_css_fontawesome_min_css__WEBPACK_IMPORTED_MODULE_2__);\n/* harmony import */ var _app__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./app */ \"./assets/public/app.js\");\n/* harmony import */ var _sound__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./sound */ \"./assets/public/sound.js\");\n/* harmony import */ var _model__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./model */ \"./assets/public/model.js\");\n/* harmony import */ var _styles_scss__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./styles.scss */ \"./assets/public/styles.scss\");\n/* harmony import */ var _styles_scss__WEBPACK_IMPORTED_MODULE_6___default = /*#__PURE__*/__webpack_require__.n(_styles_scss__WEBPACK_IMPORTED_MODULE_6__);\n/* harmony import */ var _autocomplete__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./autocomplete */ \"./assets/public/autocomplete.vue\");\n/* harmony import */ var _episode__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./episode */ \"./assets/public/episode.vue\");\n/* harmony import */ var _player__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./player */ \"./assets/public/player.vue\");\n/* harmony import */ var _playlist__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ./playlist */ \"./assets/public/playlist.vue\");\n/* harmony import */ var _soundItem__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ./soundItem */ \"./assets/public/soundItem.vue\");\n/**\n * This module includes code available for both the public website and\n * administration interface)\n */\n//-- vendor\n\n\n\n\n\n\n//-- aircox\n\n\n\n\n\n\n\n\n\n\n\n\nvue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].component('a-autocomplete', _autocomplete__WEBPACK_IMPORTED_MODULE_7__[\"default\"])\nvue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].component('a-episode', _episode__WEBPACK_IMPORTED_MODULE_8__[\"default\"])\nvue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].component('a-player', _player__WEBPACK_IMPORTED_MODULE_9__[\"default\"])\nvue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].component('a-playlist', _playlist__WEBPACK_IMPORTED_MODULE_10__[\"default\"])\nvue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].component('a-sound-item', _soundItem__WEBPACK_IMPORTED_MODULE_11__[\"default\"])\n\n\nwindow.aircox = {\n // main application\n appBuilder: null,\n appConfig: {},\n get app() { return this.appBuilder.app },\n\n // player application\n playerBuilder: null,\n get playerApp() { return this.playerBuilder && this.playerBuilder.app },\n get player() { return this.playerApp && this.playerApp.$refs.player },\n\n // Handle hot-reload (link click and form submits).\n onPageFetch(event) {\n let submit = event.type == 'submit';\n let target = submit || event.target.tagName == 'A'\n ? event.target : event.target.closest('a');\n if(!target || target.hasAttribute('target'))\n return;\n\n let url = submit ? target.getAttribute('action') || ''\n : target.getAttribute('href');\n if(url===null || !(url === '' || url.startsWith('/') || url.startsWith('?')))\n return;\n\n let options = {};\n if(submit) {\n let formData = new FormData(event.target);\n if(target.method == 'get')\n url += '?' + (new URLSearchParams(formData)).toString();\n else {\n options['method'] = target.method;\n options['body'] = formData;\n }\n }\n this.appBuilder.fetch(url, options).then(app => {\n this.appBuilder.historySave(url);\n });\n event.preventDefault();\n event.stopPropagation();\n },\n\n Set: _model__WEBPACK_IMPORTED_MODULE_5__[\"Set\"], Sound: _sound__WEBPACK_IMPORTED_MODULE_4__[\"default\"],\n};\nwindow.Vue = vue__WEBPACK_IMPORTED_MODULE_0__[\"default\"];\n\n\naircox.playerBuilder = new _app__WEBPACK_IMPORTED_MODULE_3__[\"default\"]({el: '#player'});\naircox.playerBuilder.load({async:true});\naircox.appBuilder = new _app__WEBPACK_IMPORTED_MODULE_3__[\"default\"](x => window.aircox.appConfig);\naircox.appBuilder.load({async:true}).then(app => {\n aircox.appBuilder.historySave(document.location, true);\n\n //-- load page hooks\n window.addEventListener('click', event => aircox.onPageFetch(event), true);\n window.addEventListener('submit', event => aircox.onPageFetch(event), true);\n window.addEventListener('popstate', event => {\n if(event.state && event.state.content) {\n document.title = aircox.appBuilder.title;\n aircox.appBuilder.historyLoad(event.state);\n }\n });\n})\n\n\n\n\n//# sourceURL=webpack:///./assets/public/index.js?"); /***/ }), @@ -374,11 +374,11 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) * /*!********************************!*\ !*** ./assets/public/page.vue ***! \********************************/ -/*! no static exports found */ +/*! exports provided: default */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _page_vue_vue_type_template_id_a4303912___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./page.vue?vue&type=template&id=a4303912& */ \"./assets/public/page.vue?vue&type=template&id=a4303912&\");\n/* harmony import */ var _page_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./page.vue?vue&type=script&lang=js& */ \"./assets/public/page.vue?vue&type=script&lang=js&\");\n/* harmony reexport (unknown) */ for(var __WEBPACK_IMPORT_KEY__ in _page_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1__) if([\"default\"].indexOf(__WEBPACK_IMPORT_KEY__) < 0) (function(key) { __webpack_require__.d(__webpack_exports__, key, function() { return _page_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1__[key]; }) }(__WEBPACK_IMPORT_KEY__));\n/* harmony import */ var _node_modules_vue_loader_lib_runtime_componentNormalizer_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../../node_modules/vue-loader/lib/runtime/componentNormalizer.js */ \"./node_modules/vue-loader/lib/runtime/componentNormalizer.js\");\n\n\n\n\n\n/* normalize component */\n\nvar component = Object(_node_modules_vue_loader_lib_runtime_componentNormalizer_js__WEBPACK_IMPORTED_MODULE_2__[\"default\"])(\n _page_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1__[\"default\"],\n _page_vue_vue_type_template_id_a4303912___WEBPACK_IMPORTED_MODULE_0__[\"render\"],\n _page_vue_vue_type_template_id_a4303912___WEBPACK_IMPORTED_MODULE_0__[\"staticRenderFns\"],\n false,\n null,\n null,\n null\n \n)\n\n/* hot reload */\nif (false) { var api; }\ncomponent.options.__file = \"assets/public/page.vue\"\n/* harmony default export */ __webpack_exports__[\"default\"] = (component.exports);\n\n//# sourceURL=webpack:///./assets/public/page.vue?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _page_vue_vue_type_template_id_a4303912___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./page.vue?vue&type=template&id=a4303912& */ \"./assets/public/page.vue?vue&type=template&id=a4303912&\");\n/* harmony import */ var _page_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./page.vue?vue&type=script&lang=js& */ \"./assets/public/page.vue?vue&type=script&lang=js&\");\n/* empty/unused harmony star reexport *//* harmony import */ var _node_modules_vue_loader_lib_runtime_componentNormalizer_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../../node_modules/vue-loader/lib/runtime/componentNormalizer.js */ \"./node_modules/vue-loader/lib/runtime/componentNormalizer.js\");\n\n\n\n\n\n/* normalize component */\n\nvar component = Object(_node_modules_vue_loader_lib_runtime_componentNormalizer_js__WEBPACK_IMPORTED_MODULE_2__[\"default\"])(\n _page_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1__[\"default\"],\n _page_vue_vue_type_template_id_a4303912___WEBPACK_IMPORTED_MODULE_0__[\"render\"],\n _page_vue_vue_type_template_id_a4303912___WEBPACK_IMPORTED_MODULE_0__[\"staticRenderFns\"],\n false,\n null,\n null,\n null\n \n)\n\n/* hot reload */\nif (false) { var api; }\ncomponent.options.__file = \"assets/public/page.vue\"\n/* harmony default export */ __webpack_exports__[\"default\"] = (component.exports);\n\n//# sourceURL=webpack:///./assets/public/page.vue?"); /***/ }), @@ -386,7 +386,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _pag /*!*********************************************************!*\ !*** ./assets/public/page.vue?vue&type=script&lang=js& ***! \*********************************************************/ -/*! no static exports found */ +/*! exports provided: default */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; diff --git a/aircox/static/aircox/main.js b/aircox/static/aircox/main.js index 4fe990b..63813b6 100644 --- a/aircox/static/aircox/main.js +++ b/aircox/static/aircox/main.js @@ -163,7 +163,7 @@ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"defaultConfig\", function() { return defaultConfig; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return AppBuilder; });\n/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! vue */ \"./node_modules/vue/dist/vue.esm.browser.js\");\n\n\nconst defaultConfig = {\n el: '#app',\n delimiters: ['[[', ']]'],\n\n computed: {\n player() { return window.aircox.player; },\n },\n}\n\n\nclass AppBuilder {\n constructor(config={}) {\n this._config = config;\n this.app = null;\n }\n\n get config() {\n let config = this._config instanceof Function ? this._config() : this._config;\n for(var k of new Set([...Object.keys(config || {}), ...Object.keys(defaultConfig)])) {\n if(!config[k] && defaultConfig[k])\n config[k] = defaultConfig[k]\n else if(config[k] instanceof Object)\n config[k] = {...defaultConfig[k], ...config[k]}\n }\n return config;\n }\n\n set config(value) {\n this._config = value;\n }\n\n destroy() {\n self.app && self.app.$destroy();\n self.app = null;\n }\n\n fetch(url, options) {\n return fetch(url, options).then(response => response.text())\n .then(content => {\n let doc = new DOMParser().parseFromString(content, 'text/html');\n let app = doc.getElementById('app');\n content = app ? app.innerHTML : content;\n return this.load({sync: true, content, title: doc.title, url })\n })\n }\n\n load({async=false,content=null, title=null, url=null}={}) {\n var self = this;\n return new Promise((resolve, reject) => {\n let func = () => {\n try {\n let config = self.config;\n const el = document.querySelector(config.el);\n if(!el)\n return reject(`Error: can't get element ${config.el}`)\n\n if(content)\n el.innerHTML = content\n if(title)\n document.title = title;\n if(url && content)\n history.pushState({ content: content, title: title }, '', url)\n\n this.app = new vue__WEBPACK_IMPORTED_MODULE_0__[\"default\"](config);\n resolve(self.app)\n } catch(error) {\n self.destroy();\n reject(error)\n }};\n async ? window.addEventListener('load', func) : func();\n });\n }\n\n loadFromState(state) {\n return this.load({ content: state.content, title: state.title });\n }\n}\n\n\n\n\n//# sourceURL=webpack:///./assets/public/app.js?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"defaultConfig\", function() { return defaultConfig; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return AppBuilder; });\n/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! vue */ \"./node_modules/vue/dist/vue.esm.browser.js\");\n\n\nconst defaultConfig = {\n el: '#app',\n delimiters: ['[[', ']]'],\n\n computed: {\n player() { return window.aircox.player; },\n },\n}\n\n\nclass AppBuilder {\n constructor(config={}) {\n this._config = config;\n this.title = null;\n this.app = null;\n }\n\n get config() {\n let config = this._config instanceof Function ? this._config() : this._config;\n for(var k of new Set([...Object.keys(config || {}), ...Object.keys(defaultConfig)])) {\n if(!config[k] && defaultConfig[k])\n config[k] = defaultConfig[k]\n else if(config[k] instanceof Object)\n config[k] = {...defaultConfig[k], ...config[k]}\n }\n return config;\n }\n\n set config(value) {\n this._config = value;\n }\n\n destroy() {\n self.app && self.app.$destroy();\n self.app = null;\n }\n\n fetch(url, options) {\n return fetch(url, options).then(response => response.text())\n .then(content => {\n let doc = new DOMParser().parseFromString(content, 'text/html');\n let app = doc.getElementById('app');\n content = app ? app.innerHTML : content;\n return this.load({sync: true, content, title: doc.title, url })\n })\n }\n\n load({async=false,content=null,title=null}={}) {\n var self = this;\n return new Promise((resolve, reject) => {\n let func = () => {\n try {\n let config = self.config;\n const el = document.querySelector(config.el);\n if(!el)\n return reject(`Error: can't get element ${config.el}`)\n\n if(content)\n el.innerHTML = content\n if(title)\n document.title = title;\n this.app = new vue__WEBPACK_IMPORTED_MODULE_0__[\"default\"](config);\n resolve(self.app)\n } catch(error) {\n self.destroy();\n reject(error)\n }};\n async ? window.addEventListener('load', func) : func();\n });\n }\n\n /// Save application state into browser history\n historySave(url,replace=false) {\n const el = document.querySelector(this.config.el);\n const state = {\n content: el.innerHTML,\n title: document.title,\n };\n\n if(replace)\n history.replaceState(state, '', url)\n else\n history.pushState(state, '', url)\n }\n\n /// Load application from browser history's state\n historyLoad(state) {\n return this.load({ content: state.content, title: state.title });\n }\n}\n\n\n\n\n//# sourceURL=webpack:///./assets/public/app.js?"); /***/ }), @@ -247,7 +247,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _nod /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! vue */ \"./node_modules/vue/dist/vue.esm.browser.js\");\n/* harmony import */ var _fortawesome_fontawesome_free_css_all_min_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @fortawesome/fontawesome-free/css/all.min.css */ \"./node_modules/@fortawesome/fontawesome-free/css/all.min.css\");\n/* harmony import */ var _fortawesome_fontawesome_free_css_all_min_css__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_fortawesome_fontawesome_free_css_all_min_css__WEBPACK_IMPORTED_MODULE_1__);\n/* harmony import */ var _fortawesome_fontawesome_free_css_fontawesome_min_css__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @fortawesome/fontawesome-free/css/fontawesome.min.css */ \"./node_modules/@fortawesome/fontawesome-free/css/fontawesome.min.css\");\n/* harmony import */ var _fortawesome_fontawesome_free_css_fontawesome_min_css__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(_fortawesome_fontawesome_free_css_fontawesome_min_css__WEBPACK_IMPORTED_MODULE_2__);\n/* harmony import */ var _app__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./app */ \"./assets/public/app.js\");\n/* harmony import */ var _sound__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./sound */ \"./assets/public/sound.js\");\n/* harmony import */ var _model__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./model */ \"./assets/public/model.js\");\n/* harmony import */ var _styles_scss__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./styles.scss */ \"./assets/public/styles.scss\");\n/* harmony import */ var _styles_scss__WEBPACK_IMPORTED_MODULE_6___default = /*#__PURE__*/__webpack_require__.n(_styles_scss__WEBPACK_IMPORTED_MODULE_6__);\n/* harmony import */ var _autocomplete__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./autocomplete */ \"./assets/public/autocomplete.vue\");\n/* harmony import */ var _episode__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./episode */ \"./assets/public/episode.vue\");\n/* harmony import */ var _player__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./player */ \"./assets/public/player.vue\");\n/* harmony import */ var _playlist__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ./playlist */ \"./assets/public/playlist.vue\");\n/* harmony import */ var _soundItem__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ./soundItem */ \"./assets/public/soundItem.vue\");\n/**\n * This module includes code available for both the public website and\n * administration interface)\n */\n//-- vendor\n\n\n\n\n\n\n//-- aircox\n\n\n\n\n\n\n\n\n\n\n\n\nvue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].component('a-autocomplete', _autocomplete__WEBPACK_IMPORTED_MODULE_7__[\"default\"])\nvue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].component('a-episode', _episode__WEBPACK_IMPORTED_MODULE_8__[\"default\"])\nvue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].component('a-player', _player__WEBPACK_IMPORTED_MODULE_9__[\"default\"])\nvue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].component('a-playlist', _playlist__WEBPACK_IMPORTED_MODULE_10__[\"default\"])\nvue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].component('a-sound-item', _soundItem__WEBPACK_IMPORTED_MODULE_11__[\"default\"])\n\n\nwindow.aircox = {\n // main application\n appBuilder: null,\n appConfig: {},\n get app() { return this.appBuilder.app },\n\n // player application\n playerBuilder: null,\n get playerApp() { return this.playerBuilder && this.playerBuilder.app },\n get player() { return this.playerApp && this.playerApp.$refs.player },\n\n onPageFetch(event) {\n let submit = event.type == 'submit';\n let target = submit || event.target.tagName == 'A'\n ? event.target : event.target.closest('a');\n if(!target || target.hasAttribute('target'))\n return;\n\n let url = submit ? target.getAttribute('action') || ''\n : target.getAttribute('href');\n if(url===null || !(url === '' || url.startsWith('/') || url.startsWith('?')))\n return;\n\n let options = {};\n if(submit) {\n let formData = new FormData(event.target);\n if(target.method == 'get')\n url += '?' + (new URLSearchParams(formData)).toString();\n else {\n options['method'] = target.method;\n options['body'] = formData;\n }\n }\n this.appBuilder.fetch(url, options);\n event.preventDefault();\n event.stopPropagation();\n },\n\n Set: _model__WEBPACK_IMPORTED_MODULE_5__[\"Set\"], Sound: _sound__WEBPACK_IMPORTED_MODULE_4__[\"default\"],\n};\nwindow.Vue = vue__WEBPACK_IMPORTED_MODULE_0__[\"default\"];\n\n\naircox.playerBuilder = new _app__WEBPACK_IMPORTED_MODULE_3__[\"default\"]({el: '#player'});\naircox.playerBuilder.load({async:true});\naircox.appBuilder = new _app__WEBPACK_IMPORTED_MODULE_3__[\"default\"](x => window.aircox.appConfig);\naircox.appBuilder.load({async:true}).then(app => {\n //-- load page hooks\n window.addEventListener('click', event => aircox.onPageFetch(event), true);\n window.addEventListener('submit', event => aircox.onPageFetch(event), true);\n window.addEventListener('popstate', event => {\n if(event.state && event.state.content)\n aircox.appBuilder.loadFromState(event.state);\n });\n})\n\n\n\n\n//# sourceURL=webpack:///./assets/public/index.js?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! vue */ \"./node_modules/vue/dist/vue.esm.browser.js\");\n/* harmony import */ var _fortawesome_fontawesome_free_css_all_min_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @fortawesome/fontawesome-free/css/all.min.css */ \"./node_modules/@fortawesome/fontawesome-free/css/all.min.css\");\n/* harmony import */ var _fortawesome_fontawesome_free_css_all_min_css__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_fortawesome_fontawesome_free_css_all_min_css__WEBPACK_IMPORTED_MODULE_1__);\n/* harmony import */ var _fortawesome_fontawesome_free_css_fontawesome_min_css__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @fortawesome/fontawesome-free/css/fontawesome.min.css */ \"./node_modules/@fortawesome/fontawesome-free/css/fontawesome.min.css\");\n/* harmony import */ var _fortawesome_fontawesome_free_css_fontawesome_min_css__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(_fortawesome_fontawesome_free_css_fontawesome_min_css__WEBPACK_IMPORTED_MODULE_2__);\n/* harmony import */ var _app__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./app */ \"./assets/public/app.js\");\n/* harmony import */ var _sound__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./sound */ \"./assets/public/sound.js\");\n/* harmony import */ var _model__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./model */ \"./assets/public/model.js\");\n/* harmony import */ var _styles_scss__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./styles.scss */ \"./assets/public/styles.scss\");\n/* harmony import */ var _styles_scss__WEBPACK_IMPORTED_MODULE_6___default = /*#__PURE__*/__webpack_require__.n(_styles_scss__WEBPACK_IMPORTED_MODULE_6__);\n/* harmony import */ var _autocomplete__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./autocomplete */ \"./assets/public/autocomplete.vue\");\n/* harmony import */ var _episode__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./episode */ \"./assets/public/episode.vue\");\n/* harmony import */ var _player__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./player */ \"./assets/public/player.vue\");\n/* harmony import */ var _playlist__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ./playlist */ \"./assets/public/playlist.vue\");\n/* harmony import */ var _soundItem__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ./soundItem */ \"./assets/public/soundItem.vue\");\n/**\n * This module includes code available for both the public website and\n * administration interface)\n */\n//-- vendor\n\n\n\n\n\n\n//-- aircox\n\n\n\n\n\n\n\n\n\n\n\n\nvue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].component('a-autocomplete', _autocomplete__WEBPACK_IMPORTED_MODULE_7__[\"default\"])\nvue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].component('a-episode', _episode__WEBPACK_IMPORTED_MODULE_8__[\"default\"])\nvue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].component('a-player', _player__WEBPACK_IMPORTED_MODULE_9__[\"default\"])\nvue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].component('a-playlist', _playlist__WEBPACK_IMPORTED_MODULE_10__[\"default\"])\nvue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].component('a-sound-item', _soundItem__WEBPACK_IMPORTED_MODULE_11__[\"default\"])\n\n\nwindow.aircox = {\n // main application\n appBuilder: null,\n appConfig: {},\n get app() { return this.appBuilder.app },\n\n // player application\n playerBuilder: null,\n get playerApp() { return this.playerBuilder && this.playerBuilder.app },\n get player() { return this.playerApp && this.playerApp.$refs.player },\n\n // Handle hot-reload (link click and form submits).\n onPageFetch(event) {\n let submit = event.type == 'submit';\n let target = submit || event.target.tagName == 'A'\n ? event.target : event.target.closest('a');\n if(!target || target.hasAttribute('target'))\n return;\n\n let url = submit ? target.getAttribute('action') || ''\n : target.getAttribute('href');\n if(url===null || !(url === '' || url.startsWith('/') || url.startsWith('?')))\n return;\n\n let options = {};\n if(submit) {\n let formData = new FormData(event.target);\n if(target.method == 'get')\n url += '?' + (new URLSearchParams(formData)).toString();\n else {\n options['method'] = target.method;\n options['body'] = formData;\n }\n }\n this.appBuilder.fetch(url, options).then(app => {\n this.appBuilder.historySave(url);\n });\n event.preventDefault();\n event.stopPropagation();\n },\n\n Set: _model__WEBPACK_IMPORTED_MODULE_5__[\"Set\"], Sound: _sound__WEBPACK_IMPORTED_MODULE_4__[\"default\"],\n};\nwindow.Vue = vue__WEBPACK_IMPORTED_MODULE_0__[\"default\"];\n\n\naircox.playerBuilder = new _app__WEBPACK_IMPORTED_MODULE_3__[\"default\"]({el: '#player'});\naircox.playerBuilder.load({async:true});\naircox.appBuilder = new _app__WEBPACK_IMPORTED_MODULE_3__[\"default\"](x => window.aircox.appConfig);\naircox.appBuilder.load({async:true}).then(app => {\n aircox.appBuilder.historySave(document.location, true);\n\n //-- load page hooks\n window.addEventListener('click', event => aircox.onPageFetch(event), true);\n window.addEventListener('submit', event => aircox.onPageFetch(event), true);\n window.addEventListener('popstate', event => {\n if(event.state && event.state.content) {\n document.title = aircox.appBuilder.title;\n aircox.appBuilder.historyLoad(event.state);\n }\n });\n})\n\n\n\n\n//# sourceURL=webpack:///./assets/public/index.js?"); /***/ }), @@ -315,11 +315,11 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) * /*!********************************!*\ !*** ./assets/public/page.vue ***! \********************************/ -/*! no static exports found */ +/*! exports provided: default */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _page_vue_vue_type_template_id_a4303912___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./page.vue?vue&type=template&id=a4303912& */ \"./assets/public/page.vue?vue&type=template&id=a4303912&\");\n/* harmony import */ var _page_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./page.vue?vue&type=script&lang=js& */ \"./assets/public/page.vue?vue&type=script&lang=js&\");\n/* harmony reexport (unknown) */ for(var __WEBPACK_IMPORT_KEY__ in _page_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1__) if([\"default\"].indexOf(__WEBPACK_IMPORT_KEY__) < 0) (function(key) { __webpack_require__.d(__webpack_exports__, key, function() { return _page_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1__[key]; }) }(__WEBPACK_IMPORT_KEY__));\n/* harmony import */ var _node_modules_vue_loader_lib_runtime_componentNormalizer_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../../node_modules/vue-loader/lib/runtime/componentNormalizer.js */ \"./node_modules/vue-loader/lib/runtime/componentNormalizer.js\");\n\n\n\n\n\n/* normalize component */\n\nvar component = Object(_node_modules_vue_loader_lib_runtime_componentNormalizer_js__WEBPACK_IMPORTED_MODULE_2__[\"default\"])(\n _page_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1__[\"default\"],\n _page_vue_vue_type_template_id_a4303912___WEBPACK_IMPORTED_MODULE_0__[\"render\"],\n _page_vue_vue_type_template_id_a4303912___WEBPACK_IMPORTED_MODULE_0__[\"staticRenderFns\"],\n false,\n null,\n null,\n null\n \n)\n\n/* hot reload */\nif (false) { var api; }\ncomponent.options.__file = \"assets/public/page.vue\"\n/* harmony default export */ __webpack_exports__[\"default\"] = (component.exports);\n\n//# sourceURL=webpack:///./assets/public/page.vue?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _page_vue_vue_type_template_id_a4303912___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./page.vue?vue&type=template&id=a4303912& */ \"./assets/public/page.vue?vue&type=template&id=a4303912&\");\n/* harmony import */ var _page_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./page.vue?vue&type=script&lang=js& */ \"./assets/public/page.vue?vue&type=script&lang=js&\");\n/* empty/unused harmony star reexport *//* harmony import */ var _node_modules_vue_loader_lib_runtime_componentNormalizer_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../../node_modules/vue-loader/lib/runtime/componentNormalizer.js */ \"./node_modules/vue-loader/lib/runtime/componentNormalizer.js\");\n\n\n\n\n\n/* normalize component */\n\nvar component = Object(_node_modules_vue_loader_lib_runtime_componentNormalizer_js__WEBPACK_IMPORTED_MODULE_2__[\"default\"])(\n _page_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1__[\"default\"],\n _page_vue_vue_type_template_id_a4303912___WEBPACK_IMPORTED_MODULE_0__[\"render\"],\n _page_vue_vue_type_template_id_a4303912___WEBPACK_IMPORTED_MODULE_0__[\"staticRenderFns\"],\n false,\n null,\n null,\n null\n \n)\n\n/* hot reload */\nif (false) { var api; }\ncomponent.options.__file = \"assets/public/page.vue\"\n/* harmony default export */ __webpack_exports__[\"default\"] = (component.exports);\n\n//# sourceURL=webpack:///./assets/public/page.vue?"); /***/ }), @@ -327,7 +327,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _pag /*!*********************************************************!*\ !*** ./assets/public/page.vue?vue&type=script&lang=js& ***! \*********************************************************/ -/*! no static exports found */ +/*! exports provided: default */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; diff --git a/aircox/static/aircox/streamer.js b/aircox/static/aircox/streamer.js index 76193f5..a12d92d 100644 --- a/aircox/static/aircox/streamer.js +++ b/aircox/static/aircox/streamer.js @@ -163,7 +163,7 @@ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"defaultConfig\", function() { return defaultConfig; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return AppBuilder; });\n/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! vue */ \"./node_modules/vue/dist/vue.esm.browser.js\");\n\n\nconst defaultConfig = {\n el: '#app',\n delimiters: ['[[', ']]'],\n\n computed: {\n player() { return window.aircox.player; },\n },\n}\n\n\nclass AppBuilder {\n constructor(config={}) {\n this._config = config;\n this.app = null;\n }\n\n get config() {\n let config = this._config instanceof Function ? this._config() : this._config;\n for(var k of new Set([...Object.keys(config || {}), ...Object.keys(defaultConfig)])) {\n if(!config[k] && defaultConfig[k])\n config[k] = defaultConfig[k]\n else if(config[k] instanceof Object)\n config[k] = {...defaultConfig[k], ...config[k]}\n }\n return config;\n }\n\n set config(value) {\n this._config = value;\n }\n\n destroy() {\n self.app && self.app.$destroy();\n self.app = null;\n }\n\n fetch(url, options) {\n return fetch(url, options).then(response => response.text())\n .then(content => {\n let doc = new DOMParser().parseFromString(content, 'text/html');\n let app = doc.getElementById('app');\n content = app ? app.innerHTML : content;\n return this.load({sync: true, content, title: doc.title, url })\n })\n }\n\n load({async=false,content=null, title=null, url=null}={}) {\n var self = this;\n return new Promise((resolve, reject) => {\n let func = () => {\n try {\n let config = self.config;\n const el = document.querySelector(config.el);\n if(!el)\n return reject(`Error: can't get element ${config.el}`)\n\n if(content)\n el.innerHTML = content\n if(title)\n document.title = title;\n if(url && content)\n history.pushState({ content: content, title: title }, '', url)\n\n this.app = new vue__WEBPACK_IMPORTED_MODULE_0__[\"default\"](config);\n resolve(self.app)\n } catch(error) {\n self.destroy();\n reject(error)\n }};\n async ? window.addEventListener('load', func) : func();\n });\n }\n\n loadFromState(state) {\n return this.load({ content: state.content, title: state.title });\n }\n}\n\n\n\n\n//# sourceURL=webpack:///./assets/public/app.js?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"defaultConfig\", function() { return defaultConfig; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return AppBuilder; });\n/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! vue */ \"./node_modules/vue/dist/vue.esm.browser.js\");\n\n\nconst defaultConfig = {\n el: '#app',\n delimiters: ['[[', ']]'],\n\n computed: {\n player() { return window.aircox.player; },\n },\n}\n\n\nclass AppBuilder {\n constructor(config={}) {\n this._config = config;\n this.title = null;\n this.app = null;\n }\n\n get config() {\n let config = this._config instanceof Function ? this._config() : this._config;\n for(var k of new Set([...Object.keys(config || {}), ...Object.keys(defaultConfig)])) {\n if(!config[k] && defaultConfig[k])\n config[k] = defaultConfig[k]\n else if(config[k] instanceof Object)\n config[k] = {...defaultConfig[k], ...config[k]}\n }\n return config;\n }\n\n set config(value) {\n this._config = value;\n }\n\n destroy() {\n self.app && self.app.$destroy();\n self.app = null;\n }\n\n fetch(url, options) {\n return fetch(url, options).then(response => response.text())\n .then(content => {\n let doc = new DOMParser().parseFromString(content, 'text/html');\n let app = doc.getElementById('app');\n content = app ? app.innerHTML : content;\n return this.load({sync: true, content, title: doc.title, url })\n })\n }\n\n load({async=false,content=null,title=null}={}) {\n var self = this;\n return new Promise((resolve, reject) => {\n let func = () => {\n try {\n let config = self.config;\n const el = document.querySelector(config.el);\n if(!el)\n return reject(`Error: can't get element ${config.el}`)\n\n if(content)\n el.innerHTML = content\n if(title)\n document.title = title;\n this.app = new vue__WEBPACK_IMPORTED_MODULE_0__[\"default\"](config);\n resolve(self.app)\n } catch(error) {\n self.destroy();\n reject(error)\n }};\n async ? window.addEventListener('load', func) : func();\n });\n }\n\n /// Save application state into browser history\n historySave(url,replace=false) {\n const el = document.querySelector(this.config.el);\n const state = {\n content: el.innerHTML,\n title: document.title,\n };\n\n if(replace)\n history.replaceState(state, '', url)\n else\n history.pushState(state, '', url)\n }\n\n /// Load application from browser history's state\n historyLoad(state) {\n return this.load({ content: state.content, title: state.title });\n }\n}\n\n\n\n\n//# sourceURL=webpack:///./assets/public/app.js?"); /***/ }), diff --git a/aircox/templates/aircox/base.html b/aircox/templates/aircox/base.html index 259e521..7bfe4ad 100644 --- a/aircox/templates/aircox/base.html +++ b/aircox/templates/aircox/base.html @@ -5,7 +5,6 @@ variables. Usefull context: - cover: image cover - site: current website -- has_filters: display filter bar (using block "filters") - model: view model or displayed `object`'s - sidebar_object_list: item to display in sidebar - sidebar_url_name: url name sidebar item complete list @@ -99,17 +98,6 @@ Usefull context:
{{ page.content|safe }}
{% endif %} {% endblock %} - - {# TODO: change block name #} - {% if has_filters %} - {% comment %}Translators: extra toolbar displayed on the top of page lists {% endcomment %} - - {% endif %} - - {% endblock main %} diff --git a/aircox/templates/aircox/basepage_list.html b/aircox/templates/aircox/basepage_list.html index 7833cb7..43656d8 100644 --- a/aircox/templates/aircox/basepage_list.html +++ b/aircox/templates/aircox/basepage_list.html @@ -24,6 +24,49 @@ {% block main %}{{ block.super }} +{% block before_list %} +{% if filters %} +
+
+ {% block filters %} + {% for label, name, choices in filters %} +
+
+ +
+
+
+
+ {% for label, value, checked in choices %} + + {% endfor %} +
+
+
+
+ {% endfor %} + {% endblock %} +
+
+
+
+ +
+ +
+
+
+{% endif %} + +{% endblock %} +
{% block pages_list %} {% with has_headline=True %} diff --git a/aircox/templates/aircox/diffusion_list.html b/aircox/templates/aircox/diffusion_list.html index f7ac9c6..f55082c 100644 --- a/aircox/templates/aircox/diffusion_list.html +++ b/aircox/templates/aircox/diffusion_list.html @@ -14,7 +14,7 @@ {% block subtitle %}{{ date|date:"l d F Y" }}{% endblock %} -{% block filters %} +{% block before_list %} {% with "diffusion-list" as url_name %} {% include "aircox/widgets/dates_menu.html" %} {% endwith %} diff --git a/aircox/templates/aircox/log_list.html b/aircox/templates/aircox/log_list.html index f8c1599..fe36bbf 100644 --- a/aircox/templates/aircox/log_list.html +++ b/aircox/templates/aircox/log_list.html @@ -15,7 +15,7 @@ {% block subtitle %}{{ date|date:"l d F Y" }}{% endblock %} -{% block filters %} +{% block before_list %} {% with "log-list" as url_name %} {% include "aircox/widgets/dates_menu.html" %} {% endwith %} diff --git a/aircox/templates/aircox/page_list.html b/aircox/templates/aircox/page_list.html index 69ce2dc..32ae43b 100644 --- a/aircox/templates/aircox/page_list.html +++ b/aircox/templates/aircox/page_list.html @@ -2,49 +2,3 @@ {% comment %}Display a list of Pages{% endcomment %} {% load i18n aircox %} -{% block filters %} -{# FIXME #} -{% if filter_categories %} - -{% endif %} -{% endblock %} - - diff --git a/aircox/views/episode.py b/aircox/views/episode.py index fb23b0f..e3cbb1b 100644 --- a/aircox/views/episode.py +++ b/aircox/views/episode.py @@ -1,6 +1,7 @@ from collections import OrderedDict import datetime +from django.utils.translation import gettext_lazy as _ from django.views.generic import ListView from ..models import Diffusion, Episode, Program, StaticPage, Sound @@ -29,6 +30,17 @@ class EpisodeListView(PageListView): parent_model = Program attach_to_value = StaticPage.ATTACH_TO_EPISODES + def get_queryset(self): + qs = super().get_queryset() + if self.filters and 'podcasts' in self.filters: + qs = qs.filter(sound__is_public=True) + return qs + + def get_filters(self): + return super().get_filters() + ( + (_('Podcasts'), 'podcasts', tuple()), + ) + class DiffusionListView(GetDateMixin, AttachedToMixin, BaseView, ListView): """ View for timetables """ diff --git a/aircox/views/page.py b/aircox/views/page.py index b77458f..edd9215 100644 --- a/aircox/views/page.py +++ b/aircox/views/page.py @@ -76,9 +76,11 @@ class PageListView(BasePageListView): template_name = None has_filters = True categories = None + filters = None def get(self, *args, **kwargs): self.categories = set(self.request.GET.getlist('categories')) + self.filters = set(self.request.GET.getlist('filters')) return super().get(*args, **kwargs) def get_template_names(self): @@ -94,16 +96,25 @@ class PageListView(BasePageListView): qs = qs.filter(category__slug__in=self.categories) return qs - def get_categories_queryset(self): - # TODO: use generic reverse field lookup + def get_filters(self): categories = self.model.objects.published() \ .filter(category__isnull=False) \ .values_list('category', flat=True) - return Category.objects.filter(id__in=categories) + categories = [ (c.title, c.slug, c.slug in self.categories) + for c in Category.objects.filter(id__in=categories) ] + return ( + (_('Categories'), 'categories', categories), + ) def get_context_data(self, **kwargs): - kwargs.setdefault('filter_categories', self.get_categories_queryset()) - kwargs.setdefault('categories', self.categories) + if not 'filters' in kwargs: + filters = self.get_filters() + for label, fieldName, choices in filters: + if choices: + kwargs['filters'] = filters + break; + else: + kwargs['filters'] = tuple() return super().get_context_data(**kwargs) diff --git a/assets/public/app.js b/assets/public/app.js index c257dae..c403254 100644 --- a/assets/public/app.js +++ b/assets/public/app.js @@ -13,6 +13,7 @@ export const defaultConfig = { export default class AppBuilder { constructor(config={}) { this._config = config; + this.title = null; this.app = null; } @@ -46,7 +47,7 @@ export default class AppBuilder { }) } - load({async=false,content=null, title=null, url=null}={}) { + load({async=false,content=null,title=null}={}) { var self = this; return new Promise((resolve, reject) => { let func = () => { @@ -60,9 +61,6 @@ export default class AppBuilder { el.innerHTML = content if(title) document.title = title; - if(url && content) - history.pushState({ content: content, title: title }, '', url) - this.app = new Vue(config); resolve(self.app) } catch(error) { @@ -73,7 +71,22 @@ export default class AppBuilder { }); } - loadFromState(state) { + /// Save application state into browser history + historySave(url,replace=false) { + const el = document.querySelector(this.config.el); + const state = { + content: el.innerHTML, + title: document.title, + }; + + if(replace) + history.replaceState(state, '', url) + else + history.pushState(state, '', url) + } + + /// Load application from browser history's state + historyLoad(state) { return this.load({ content: state.content, title: state.title }); } } diff --git a/assets/public/index.js b/assets/public/index.js index cbe3bbd..d9f8590 100644 --- a/assets/public/index.js +++ b/assets/public/index.js @@ -40,6 +40,7 @@ window.aircox = { get playerApp() { return this.playerBuilder && this.playerBuilder.app }, get player() { return this.playerApp && this.playerApp.$refs.player }, + // Handle hot-reload (link click and form submits). onPageFetch(event) { let submit = event.type == 'submit'; let target = submit || event.target.tagName == 'A' @@ -62,7 +63,9 @@ window.aircox = { options['body'] = formData; } } - this.appBuilder.fetch(url, options); + this.appBuilder.fetch(url, options).then(app => { + this.appBuilder.historySave(url); + }); event.preventDefault(); event.stopPropagation(); }, @@ -76,12 +79,16 @@ aircox.playerBuilder = new AppBuilder({el: '#player'}); aircox.playerBuilder.load({async:true}); aircox.appBuilder = new AppBuilder(x => window.aircox.appConfig); aircox.appBuilder.load({async:true}).then(app => { + aircox.appBuilder.historySave(document.location, true); + //-- load page hooks window.addEventListener('click', event => aircox.onPageFetch(event), true); window.addEventListener('submit', event => aircox.onPageFetch(event), true); window.addEventListener('popstate', event => { - if(event.state && event.state.content) - aircox.appBuilder.loadFromState(event.state); + if(event.state && event.state.content) { + document.title = aircox.appBuilder.title; + aircox.appBuilder.historyLoad(event.state); + } }); })