forked from rc/aircox
work on lists filters + nav items
This commit is contained in:
parent
774c558a36
commit
7860d9f92b
|
@ -185,12 +185,25 @@ class StaticPage(BasePage):
|
||||||
(ATTACH_TO_EPISODES, _('Episodes list')),
|
(ATTACH_TO_EPISODES, _('Episodes list')),
|
||||||
(ATTACH_TO_ARTICLES, _('Articles 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 = models.SmallIntegerField(
|
||||||
_('attach to'), choices=ATTACH_TO_CHOICES, blank=True, null=True,
|
_('attach to'), choices=ATTACH_TO_CHOICES, blank=True, null=True,
|
||||||
help_text=_('display this page content to related element'),
|
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):
|
class Comment(models.Model):
|
||||||
page = models.ForeignKey(
|
page = models.ForeignKey(
|
||||||
|
@ -216,8 +229,7 @@ class NavItem(models.Model):
|
||||||
text = models.CharField(_('title'), max_length=64)
|
text = models.CharField(_('title'), max_length=64)
|
||||||
url = models.CharField(_('url'), max_length=256, blank=True, null=True)
|
url = models.CharField(_('url'), max_length=256, blank=True, null=True)
|
||||||
page = models.ForeignKey(StaticPage, models.CASCADE,
|
page = models.ForeignKey(StaticPage, models.CASCADE,
|
||||||
verbose_name=_('page'), blank=True, null=True,
|
verbose_name=_('page'), blank=True, null=True)
|
||||||
limit_choices_to={'attach_to__isnull': True})
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _('Menu item')
|
verbose_name = _('Menu item')
|
||||||
verbose_name_plural = _('Menu items')
|
verbose_name_plural = _('Menu items')
|
||||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -163,7 +163,7 @@
|
||||||
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
||||||
|
|
||||||
"use strict";
|
"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?");
|
||||||
|
|
||||||
/***/ }),
|
/***/ }),
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,6 @@ variables.
|
||||||
Usefull context:
|
Usefull context:
|
||||||
- cover: image cover
|
- cover: image cover
|
||||||
- site: current website
|
- site: current website
|
||||||
- has_filters: display filter bar (using block "filters")
|
|
||||||
- model: view model or displayed `object`'s
|
- model: view model or displayed `object`'s
|
||||||
- sidebar_object_list: item to display in sidebar
|
- sidebar_object_list: item to display in sidebar
|
||||||
- sidebar_url_name: url name sidebar item complete list
|
- sidebar_url_name: url name sidebar item complete list
|
||||||
|
@ -99,17 +98,6 @@ Usefull context:
|
||||||
<section class="content">{{ page.content|safe }}</section>
|
<section class="content">{{ page.content|safe }}</section>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{# TODO: change block name #}
|
|
||||||
{% if has_filters %}
|
|
||||||
{% comment %}Translators: extra toolbar displayed on the top of page lists {% endcomment %}
|
|
||||||
<nav class="navbar toolbar"
|
|
||||||
aria-label="{% trans "list filters" %}">
|
|
||||||
{% block filters %}{% endblock %}
|
|
||||||
</nav>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
|
|
||||||
{% endblock main %}
|
{% endblock main %}
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,49 @@
|
||||||
|
|
||||||
{% block main %}{{ block.super }}
|
{% block main %}{{ block.super }}
|
||||||
|
|
||||||
|
{% block before_list %}
|
||||||
|
{% if filters %}
|
||||||
|
<form method="GET" action="" class="media">
|
||||||
|
<div class="media-content">
|
||||||
|
{% block filters %}
|
||||||
|
{% for label, name, choices in filters %}
|
||||||
|
<div class="field is-horizontal">
|
||||||
|
<div class="field-label">
|
||||||
|
<label class="label">{{ label }}</label>
|
||||||
|
</div>
|
||||||
|
<div class="field-body">
|
||||||
|
<div class="field is-narrow">
|
||||||
|
<div class="control">
|
||||||
|
{% for label, value, checked in choices %}
|
||||||
|
<label class="checkbox">
|
||||||
|
<input type="checkbox" class="checkbox" name="{{ name }}"
|
||||||
|
value="{{ value }}"
|
||||||
|
{% if checked %}checked{% endif %} />
|
||||||
|
{{ label }}
|
||||||
|
</label>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endblock %}
|
||||||
|
</div>
|
||||||
|
<div class="media-right">
|
||||||
|
<div class="field is-grouped is-grouped-right">
|
||||||
|
<div class="control">
|
||||||
|
<button class="button is-primary"/>{% trans "Apply" %}</button>
|
||||||
|
</div>
|
||||||
|
<div class="control">
|
||||||
|
<a href="?" class="button is-secondary">{% trans "Reset" %}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
<section role="list">
|
<section role="list">
|
||||||
{% block pages_list %}
|
{% block pages_list %}
|
||||||
{% with has_headline=True %}
|
{% with has_headline=True %}
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
|
|
||||||
{% block subtitle %}{{ date|date:"l d F Y" }}{% endblock %}
|
{% block subtitle %}{{ date|date:"l d F Y" }}{% endblock %}
|
||||||
|
|
||||||
{% block filters %}
|
{% block before_list %}
|
||||||
{% with "diffusion-list" as url_name %}
|
{% with "diffusion-list" as url_name %}
|
||||||
{% include "aircox/widgets/dates_menu.html" %}
|
{% include "aircox/widgets/dates_menu.html" %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
|
|
||||||
{% block subtitle %}{{ date|date:"l d F Y" }}{% endblock %}
|
{% block subtitle %}{{ date|date:"l d F Y" }}{% endblock %}
|
||||||
|
|
||||||
{% block filters %}
|
{% block before_list %}
|
||||||
{% with "log-list" as url_name %}
|
{% with "log-list" as url_name %}
|
||||||
{% include "aircox/widgets/dates_menu.html" %}
|
{% include "aircox/widgets/dates_menu.html" %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
|
|
@ -2,49 +2,3 @@
|
||||||
{% comment %}Display a list of Pages{% endcomment %}
|
{% comment %}Display a list of Pages{% endcomment %}
|
||||||
{% load i18n aircox %}
|
{% load i18n aircox %}
|
||||||
|
|
||||||
{% block filters %}
|
|
||||||
{# FIXME #}
|
|
||||||
{% if filter_categories %}
|
|
||||||
<form method="GET" action="" class="navbar-menu">
|
|
||||||
<div class="navbar-start">
|
|
||||||
<div class="navbar-item">
|
|
||||||
{% block list_filters %}
|
|
||||||
<div class="field is-horizontal">
|
|
||||||
<div class="field-label">
|
|
||||||
<label class="label">{% trans "Categories" %}</label>
|
|
||||||
</div>
|
|
||||||
<div class="field-body">
|
|
||||||
<div class="field is-narrow">
|
|
||||||
<div class="control">
|
|
||||||
{% for category in filter_categories %}
|
|
||||||
<label class="checkbox">
|
|
||||||
<input type="checkbox" class="checkbox" name="categories"
|
|
||||||
value="{{ category.slug }}"
|
|
||||||
{% if category.slug in categories %}checked{% endif %} />
|
|
||||||
{{ category.title }}
|
|
||||||
</label>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="navbar-end">
|
|
||||||
<div class="navbar-item">
|
|
||||||
<div class="field is-grouped is-grouped-right">
|
|
||||||
<div class="control">
|
|
||||||
<button class="button is-primary"/>{% trans "Apply" %}</button>
|
|
||||||
</div>
|
|
||||||
<div class="control">
|
|
||||||
<a href="?" class="button is-secondary">{% trans "Reset" %}</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.views.generic import ListView
|
from django.views.generic import ListView
|
||||||
|
|
||||||
from ..models import Diffusion, Episode, Program, StaticPage, Sound
|
from ..models import Diffusion, Episode, Program, StaticPage, Sound
|
||||||
|
@ -29,6 +30,17 @@ class EpisodeListView(PageListView):
|
||||||
parent_model = Program
|
parent_model = Program
|
||||||
attach_to_value = StaticPage.ATTACH_TO_EPISODES
|
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):
|
class DiffusionListView(GetDateMixin, AttachedToMixin, BaseView, ListView):
|
||||||
""" View for timetables """
|
""" View for timetables """
|
||||||
|
|
|
@ -76,9 +76,11 @@ class PageListView(BasePageListView):
|
||||||
template_name = None
|
template_name = None
|
||||||
has_filters = True
|
has_filters = True
|
||||||
categories = None
|
categories = None
|
||||||
|
filters = None
|
||||||
|
|
||||||
def get(self, *args, **kwargs):
|
def get(self, *args, **kwargs):
|
||||||
self.categories = set(self.request.GET.getlist('categories'))
|
self.categories = set(self.request.GET.getlist('categories'))
|
||||||
|
self.filters = set(self.request.GET.getlist('filters'))
|
||||||
return super().get(*args, **kwargs)
|
return super().get(*args, **kwargs)
|
||||||
|
|
||||||
def get_template_names(self):
|
def get_template_names(self):
|
||||||
|
@ -94,16 +96,25 @@ class PageListView(BasePageListView):
|
||||||
qs = qs.filter(category__slug__in=self.categories)
|
qs = qs.filter(category__slug__in=self.categories)
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
def get_categories_queryset(self):
|
def get_filters(self):
|
||||||
# TODO: use generic reverse field lookup
|
|
||||||
categories = self.model.objects.published() \
|
categories = self.model.objects.published() \
|
||||||
.filter(category__isnull=False) \
|
.filter(category__isnull=False) \
|
||||||
.values_list('category', flat=True)
|
.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):
|
def get_context_data(self, **kwargs):
|
||||||
kwargs.setdefault('filter_categories', self.get_categories_queryset())
|
if not 'filters' in kwargs:
|
||||||
kwargs.setdefault('categories', self.categories)
|
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)
|
return super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ export const defaultConfig = {
|
||||||
export default class AppBuilder {
|
export default class AppBuilder {
|
||||||
constructor(config={}) {
|
constructor(config={}) {
|
||||||
this._config = config;
|
this._config = config;
|
||||||
|
this.title = null;
|
||||||
this.app = 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;
|
var self = this;
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let func = () => {
|
let func = () => {
|
||||||
|
@ -60,9 +61,6 @@ export default class AppBuilder {
|
||||||
el.innerHTML = content
|
el.innerHTML = content
|
||||||
if(title)
|
if(title)
|
||||||
document.title = title;
|
document.title = title;
|
||||||
if(url && content)
|
|
||||||
history.pushState({ content: content, title: title }, '', url)
|
|
||||||
|
|
||||||
this.app = new Vue(config);
|
this.app = new Vue(config);
|
||||||
resolve(self.app)
|
resolve(self.app)
|
||||||
} catch(error) {
|
} 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 });
|
return this.load({ content: state.content, title: state.title });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,6 +40,7 @@ window.aircox = {
|
||||||
get playerApp() { return this.playerBuilder && this.playerBuilder.app },
|
get playerApp() { return this.playerBuilder && this.playerBuilder.app },
|
||||||
get player() { return this.playerApp && this.playerApp.$refs.player },
|
get player() { return this.playerApp && this.playerApp.$refs.player },
|
||||||
|
|
||||||
|
// Handle hot-reload (link click and form submits).
|
||||||
onPageFetch(event) {
|
onPageFetch(event) {
|
||||||
let submit = event.type == 'submit';
|
let submit = event.type == 'submit';
|
||||||
let target = submit || event.target.tagName == 'A'
|
let target = submit || event.target.tagName == 'A'
|
||||||
|
@ -62,7 +63,9 @@ window.aircox = {
|
||||||
options['body'] = formData;
|
options['body'] = formData;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.appBuilder.fetch(url, options);
|
this.appBuilder.fetch(url, options).then(app => {
|
||||||
|
this.appBuilder.historySave(url);
|
||||||
|
});
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
},
|
},
|
||||||
|
@ -76,12 +79,16 @@ aircox.playerBuilder = new AppBuilder({el: '#player'});
|
||||||
aircox.playerBuilder.load({async:true});
|
aircox.playerBuilder.load({async:true});
|
||||||
aircox.appBuilder = new AppBuilder(x => window.aircox.appConfig);
|
aircox.appBuilder = new AppBuilder(x => window.aircox.appConfig);
|
||||||
aircox.appBuilder.load({async:true}).then(app => {
|
aircox.appBuilder.load({async:true}).then(app => {
|
||||||
|
aircox.appBuilder.historySave(document.location, true);
|
||||||
|
|
||||||
//-- load page hooks
|
//-- load page hooks
|
||||||
window.addEventListener('click', event => aircox.onPageFetch(event), true);
|
window.addEventListener('click', event => aircox.onPageFetch(event), true);
|
||||||
window.addEventListener('submit', event => aircox.onPageFetch(event), true);
|
window.addEventListener('submit', event => aircox.onPageFetch(event), true);
|
||||||
window.addEventListener('popstate', event => {
|
window.addEventListener('popstate', event => {
|
||||||
if(event.state && event.state.content)
|
if(event.state && event.state.content) {
|
||||||
aircox.appBuilder.loadFromState(event.state);
|
document.title = aircox.appBuilder.title;
|
||||||
|
aircox.appBuilder.historyLoad(event.state);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user