mirror of
https://github.com/hkalexling/Mango.git
synced 2025-08-03 11:25:29 -04:00
Refactor date filtering and use native date picker
This commit is contained in:
parent
87e54aa89c
commit
e44213960f
@ -6,8 +6,8 @@ const component = () => {
|
|||||||
chapters: undefined, // undefined: not searched yet, []: empty
|
chapters: undefined, // undefined: not searched yet, []: empty
|
||||||
manga: undefined, // undefined: not searched yet, []: empty
|
manga: undefined, // undefined: not searched yet, []: empty
|
||||||
allChapters: [],
|
allChapters: [],
|
||||||
query: '',
|
query: "",
|
||||||
mangaTitle: '',
|
mangaTitle: "",
|
||||||
searching: false,
|
searching: false,
|
||||||
adding: false,
|
adding: false,
|
||||||
sortOptions: [],
|
sortOptions: [],
|
||||||
@ -16,73 +16,82 @@ const component = () => {
|
|||||||
chaptersLimit: 500,
|
chaptersLimit: 500,
|
||||||
listManga: false,
|
listManga: false,
|
||||||
subscribing: false,
|
subscribing: false,
|
||||||
subscriptionName: '',
|
subscriptionName: "",
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
const tableObserver = new MutationObserver(() => {
|
const tableObserver = new MutationObserver(() => {
|
||||||
console.log('table mutated');
|
console.log("table mutated");
|
||||||
$('#selectable').selectable({
|
$("#selectable").selectable({
|
||||||
filter: 'tr'
|
filter: "tr",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
tableObserver.observe($('table').get(0), {
|
tableObserver.observe($("table").get(0), {
|
||||||
childList: true,
|
childList: true,
|
||||||
subtree: true
|
subtree: true,
|
||||||
});
|
});
|
||||||
fetch(`${base_url}api/admin/plugin`)
|
fetch(`${base_url}api/admin/plugin`)
|
||||||
.then(res => res.json())
|
.then((res) => res.json())
|
||||||
.then(data => {
|
.then((data) => {
|
||||||
if (!data.success)
|
if (!data.success) throw new Error(data.error);
|
||||||
throw new Error(data.error);
|
|
||||||
this.plugins = data.plugins;
|
this.plugins = data.plugins;
|
||||||
|
|
||||||
const pid = localStorage.getItem('plugin');
|
const pid = localStorage.getItem("plugin");
|
||||||
if (pid && this.plugins.map(p => p.id).includes(pid))
|
if (pid && this.plugins.map((p) => p.id).includes(pid))
|
||||||
return this.loadPlugin(pid);
|
return this.loadPlugin(pid);
|
||||||
|
|
||||||
if (this.plugins.length > 0)
|
if (this.plugins.length > 0)
|
||||||
this.loadPlugin(this.plugins[0].id);
|
this.loadPlugin(this.plugins[0].id);
|
||||||
})
|
})
|
||||||
.catch(e => {
|
.catch((e) => {
|
||||||
alert('danger', `Failed to list the available plugins. Error: ${e}`);
|
alert(
|
||||||
|
"danger",
|
||||||
|
`Failed to list the available plugins. Error: ${e}`
|
||||||
|
);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
loadPlugin(pid) {
|
loadPlugin(pid) {
|
||||||
fetch(`${base_url}api/admin/plugin/info?${new URLSearchParams({
|
fetch(
|
||||||
plugin: pid
|
`${base_url}api/admin/plugin/info?${new URLSearchParams({
|
||||||
})}`)
|
plugin: pid,
|
||||||
.then(res => res.json())
|
})}`
|
||||||
.then(data => {
|
)
|
||||||
if (!data.success)
|
.then((res) => res.json())
|
||||||
throw new Error(data.error);
|
.then((data) => {
|
||||||
|
if (!data.success) throw new Error(data.error);
|
||||||
this.info = data.info;
|
this.info = data.info;
|
||||||
this.pid = pid;
|
this.pid = pid;
|
||||||
})
|
})
|
||||||
.catch(e => {
|
.catch((e) => {
|
||||||
alert('danger', `Failed to get plugin metadata. Error: ${e}`);
|
alert(
|
||||||
|
"danger",
|
||||||
|
`Failed to get plugin metadata. Error: ${e}`
|
||||||
|
);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
pluginChanged() {
|
pluginChanged() {
|
||||||
this.loadPlugin(this.pid);
|
this.loadPlugin(this.pid);
|
||||||
localStorage.setItem('plugin', this.pid);
|
localStorage.setItem("plugin", this.pid);
|
||||||
},
|
},
|
||||||
get chapterKeys() {
|
get chapterKeys() {
|
||||||
if (this.allChapters.length < 1) return [];
|
if (this.allChapters.length < 1) return [];
|
||||||
return Object.keys(this.allChapters[0]).filter(k => !['manga_title'].includes(k));
|
return Object.keys(this.allChapters[0]).filter(
|
||||||
|
(k) => !["manga_title"].includes(k)
|
||||||
|
);
|
||||||
},
|
},
|
||||||
searchChapters(query) {
|
searchChapters(query) {
|
||||||
this.searching = true;
|
this.searching = true;
|
||||||
this.allChapters = [];
|
this.allChapters = [];
|
||||||
this.chapters = undefined;
|
this.chapters = undefined;
|
||||||
this.listManga = false;
|
this.listManga = false;
|
||||||
fetch(`${base_url}api/admin/plugin/list?${new URLSearchParams({
|
fetch(
|
||||||
plugin: this.pid,
|
`${base_url}api/admin/plugin/list?${new URLSearchParams({
|
||||||
query: query
|
plugin: this.pid,
|
||||||
})}`)
|
query: query,
|
||||||
.then(res => res.json())
|
})}`
|
||||||
.then(data => {
|
)
|
||||||
if (!data.success)
|
.then((res) => res.json())
|
||||||
throw new Error(data.error);
|
.then((data) => {
|
||||||
|
if (!data.success) throw new Error(data.error);
|
||||||
try {
|
try {
|
||||||
this.mangaTitle = data.chapters[0].manga_title;
|
this.mangaTitle = data.chapters[0].manga_title;
|
||||||
if (!this.mangaTitle) throw new Error();
|
if (!this.mangaTitle) throw new Error();
|
||||||
@ -90,16 +99,20 @@ const component = () => {
|
|||||||
this.mangaTitle = data.title;
|
this.mangaTitle = data.title;
|
||||||
}
|
}
|
||||||
|
|
||||||
data.chapters.forEach(c => {
|
data.chapters.forEach((c) => {
|
||||||
c.array = ['hello', 'world', 'haha', 'wtf'].sort(() => 0.5 - Math.random()).slice(0, 2);
|
c.array = ["hello", "world", "haha", "wtf"]
|
||||||
c.date = ['4 Jun, 1989', '1 July, 2021'].sort(() => 0.5 - Math.random())[0];
|
.sort(() => 0.5 - Math.random())
|
||||||
|
.slice(0, 2);
|
||||||
|
c.date = [612892800000, "1625068800000"].sort(
|
||||||
|
() => 0.5 - Math.random()
|
||||||
|
)[0];
|
||||||
});
|
});
|
||||||
|
|
||||||
this.allChapters = data.chapters;
|
this.allChapters = data.chapters;
|
||||||
this.chapters = data.chapters;
|
this.chapters = data.chapters;
|
||||||
})
|
})
|
||||||
.catch(e => {
|
.catch((e) => {
|
||||||
alert('danger', `Failed to list chapters. Error: ${e}`);
|
alert("danger", `Failed to list chapters. Error: ${e}`);
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
this.searching = false;
|
this.searching = false;
|
||||||
@ -110,19 +123,20 @@ const component = () => {
|
|||||||
this.allChapters = [];
|
this.allChapters = [];
|
||||||
this.chapters = undefined;
|
this.chapters = undefined;
|
||||||
this.manga = undefined;
|
this.manga = undefined;
|
||||||
fetch(`${base_url}api/admin/plugin/search?${new URLSearchParams({
|
fetch(
|
||||||
plugin: this.pid,
|
`${base_url}api/admin/plugin/search?${new URLSearchParams({
|
||||||
query: query
|
plugin: this.pid,
|
||||||
})}`)
|
query: query,
|
||||||
.then(res => res.json())
|
})}`
|
||||||
.then(data => {
|
)
|
||||||
if (!data.success)
|
.then((res) => res.json())
|
||||||
throw new Error(data.error);
|
.then((data) => {
|
||||||
|
if (!data.success) throw new Error(data.error);
|
||||||
this.manga = data.manga;
|
this.manga = data.manga;
|
||||||
this.listManga = true;
|
this.listManga = true;
|
||||||
})
|
})
|
||||||
.catch(e => {
|
.catch((e) => {
|
||||||
alert('danger', `Search failed. Error: ${e}`);
|
alert("danger", `Search failed. Error: ${e}`);
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
this.searching = false;
|
this.searching = false;
|
||||||
@ -140,53 +154,64 @@ const component = () => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
selectAll() {
|
selectAll() {
|
||||||
$('tbody > tr').each((i, e) => {
|
$("tbody > tr").each((i, e) => {
|
||||||
$(e).addClass('ui-selected');
|
$(e).addClass("ui-selected");
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
clearSelection() {
|
clearSelection() {
|
||||||
$('tbody > tr').each((i, e) => {
|
$("tbody > tr").each((i, e) => {
|
||||||
$(e).removeClass('ui-selected');
|
$(e).removeClass("ui-selected");
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
download() {
|
download() {
|
||||||
const selected = $('tbody > tr.ui-selected').get();
|
const selected = $("tbody > tr.ui-selected").get();
|
||||||
if (selected.length === 0) return;
|
if (selected.length === 0) return;
|
||||||
|
|
||||||
UIkit.modal.confirm(`Download ${selected.length} selected chapters?`).then(() => {
|
UIkit.modal
|
||||||
const ids = selected.map(e => e.id);
|
.confirm(`Download ${selected.length} selected chapters?`)
|
||||||
const chapters = this.chapters.filter(c => ids.includes(c.id));
|
.then(() => {
|
||||||
console.log(chapters);
|
const ids = selected.map((e) => e.id);
|
||||||
this.adding = true;
|
const chapters = this.chapters.filter((c) =>
|
||||||
fetch(`${base_url}api/admin/plugin/download`, {
|
ids.includes(c.id)
|
||||||
method: 'POST',
|
);
|
||||||
|
console.log(chapters);
|
||||||
|
this.adding = true;
|
||||||
|
fetch(`${base_url}api/admin/plugin/download`, {
|
||||||
|
method: "POST",
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
chapters,
|
chapters,
|
||||||
plugin: this.pid,
|
plugin: this.pid,
|
||||||
title: this.mangaTitle
|
title: this.mangaTitle,
|
||||||
}),
|
}),
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json",
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
.then(res => res.json())
|
.then((res) => res.json())
|
||||||
.then(data => {
|
.then((data) => {
|
||||||
if (!data.success)
|
if (!data.success) throw new Error(data.error);
|
||||||
throw new Error(data.error);
|
const successCount = parseInt(data.success);
|
||||||
const successCount = parseInt(data.success);
|
const failCount = parseInt(data.fail);
|
||||||
const failCount = parseInt(data.fail);
|
alert(
|
||||||
alert('success', `${successCount} of ${successCount + failCount} chapters added to the download queue. You can view and manage your download queue on the <a href="${base_url}admin/downloads">download manager page</a>.`);
|
"success",
|
||||||
})
|
`${successCount} of ${
|
||||||
.catch(e => {
|
successCount + failCount
|
||||||
alert('danger', `Failed to add chapters to the download queue. Error: ${e}`);
|
} chapters added to the download queue. You can view and manage your download queue on the <a href="${base_url}admin/downloads">download manager page</a>.`
|
||||||
})
|
);
|
||||||
.finally(() => {
|
})
|
||||||
this.adding = false;
|
.catch((e) => {
|
||||||
});
|
alert(
|
||||||
})
|
"danger",
|
||||||
|
`Failed to add chapters to the download queue. Error: ${e}`
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.adding = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
},
|
},
|
||||||
thClicked(event) {
|
thClicked(event) {
|
||||||
const idx = parseInt(event.currentTarget.id.split('-')[1]);
|
const idx = parseInt(event.currentTarget.id.split("-")[1]);
|
||||||
if (idx === undefined || isNaN(idx)) return;
|
if (idx === undefined || isNaN(idx)) return;
|
||||||
const curOption = this.sortOptions[idx];
|
const curOption = this.sortOptions[idx];
|
||||||
let option;
|
let option;
|
||||||
@ -202,40 +227,58 @@ const component = () => {
|
|||||||
option = 1;
|
option = 1;
|
||||||
}
|
}
|
||||||
this.sortOptions[idx] = option;
|
this.sortOptions[idx] = option;
|
||||||
this.sort(this.chapterKeys[idx], option)
|
this.sort(this.chapterKeys[idx], option);
|
||||||
},
|
},
|
||||||
// Returns an array of filtered but unsorted chapters. Useful when
|
// Returns an array of filtered but unsorted chapters. Useful when
|
||||||
// reseting the sort options.
|
// reseting the sort options.
|
||||||
get filteredChapters() {
|
get filteredChapters() {
|
||||||
let ary = this.allChapters.slice();
|
let ary = this.allChapters.slice();
|
||||||
|
|
||||||
console.log('initial size:', ary.length);
|
console.log("initial size:", ary.length);
|
||||||
for (let filter of this.appliedFilters) {
|
for (let filter of this.appliedFilters) {
|
||||||
if (!filter.value) continue;
|
if (!filter.value || isNaN(filter.value)) continue;
|
||||||
if (filter.type === 'array' && filter.value === 'all') continue;
|
if (filter.type === "array" && filter.value === "all") continue;
|
||||||
|
|
||||||
console.log('applying filter:', filter);
|
console.log("applying filter:", filter);
|
||||||
|
|
||||||
if (filter.type === 'string') {
|
if (filter.type === "string") {
|
||||||
ary = ary.filter(ch => ch[filter.key].toLowerCase().includes(filter.value.toLowerCase()));
|
ary = ary.filter((ch) =>
|
||||||
|
ch[filter.key]
|
||||||
|
.toLowerCase()
|
||||||
|
.includes(filter.value.toLowerCase())
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (filter.type === 'number-min') {
|
if (filter.type === "number-min") {
|
||||||
ary = ary.filter(ch => Number(ch[filter.key]) >= Number(filter.value));
|
ary = ary.filter(
|
||||||
|
(ch) => Number(ch[filter.key]) >= Number(filter.value)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (filter.type === 'number-max') {
|
if (filter.type === "number-max") {
|
||||||
ary = ary.filter(ch => Number(ch[filter.key]) <= Number(filter.value));
|
ary = ary.filter(
|
||||||
|
(ch) => Number(ch[filter.key]) <= Number(filter.value)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (filter.type === 'date-min') {
|
if (filter.type === "date-min") {
|
||||||
ary = ary.filter(ch => this.parseDate(ch[filter.key]) >= this.parseDate(filter.value));
|
ary = ary.filter(
|
||||||
|
(ch) => Number(ch[filter.key]) >= Number(filter.value)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (filter.type === 'date-max') {
|
if (filter.type === "date-max") {
|
||||||
ary = ary.filter(ch => this.parseDate(ch[filter.key]) <= this.parseDate(filter.value));
|
ary = ary.filter(
|
||||||
|
(ch) => Number(ch[filter.key]) <= Number(filter.value)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (filter.type === 'array') {
|
if (filter.type === "array") {
|
||||||
ary = ary.filter(ch => ch[filter.key].map(s => typeof s === 'string' ? s.toLowerCase() : s).includes(filter.value.toLowerCase()));
|
ary = ary.filter((ch) =>
|
||||||
|
ch[filter.key]
|
||||||
|
.map((s) =>
|
||||||
|
typeof s === "string" ? s.toLowerCase() : s
|
||||||
|
)
|
||||||
|
.includes(filter.value.toLowerCase())
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('filtered size:', ary.length);
|
console.log("filtered size:", ary.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ary;
|
return ary;
|
||||||
@ -258,124 +301,146 @@ const component = () => {
|
|||||||
compare(a, b) {
|
compare(a, b) {
|
||||||
if (a === b) return 0;
|
if (a === b) return 0;
|
||||||
|
|
||||||
// try numbers
|
// try numbers (also covers dates)
|
||||||
// this must come before the date checks, because any integer would
|
if (!isNaN(a) && !isNaN(b)) return Number(a) - Number(b);
|
||||||
// also be parsed as a date.
|
|
||||||
if (!isNaN(a) && !isNaN(b))
|
|
||||||
return Number(a) - Number(b);
|
|
||||||
|
|
||||||
// try dates
|
|
||||||
if (!isNaN(this.parseDate(a)) && !isNaN(this.parseDate(b)))
|
|
||||||
return this.parseDate(a) - this.parseDate(b);
|
|
||||||
|
|
||||||
const preprocessString = (val) => {
|
const preprocessString = (val) => {
|
||||||
if (typeof val !== 'string') return val;
|
if (typeof val !== "string") return val;
|
||||||
return val.toLowerCase().replace(/\s\s/g, ' ').trim();
|
return val.toLowerCase().replace(/\s\s/g, " ").trim();
|
||||||
};
|
};
|
||||||
|
|
||||||
return preprocessString(a) > preprocessString(b) ? 1 : -1;
|
return preprocessString(a) > preprocessString(b) ? 1 : -1;
|
||||||
},
|
},
|
||||||
fieldType(values) {
|
fieldType(values) {
|
||||||
if (values.every(v => !isNaN(v))) return 'number'; // display input for number range
|
if (values.every((v) => this.numIsDate(v))) return "date"; // 328896000000 => 1 Jan, 1980
|
||||||
if (values.every(v => !isNaN(this.parseDate(v)))) return 'date'; // display input for date range
|
if (values.every((v) => !isNaN(v))) return "number";
|
||||||
if (values.every(v => Array.isArray(v))) return 'array'; // display select
|
if (values.every((v) => Array.isArray(v))) return "array";
|
||||||
return 'string'; // display input for string searching.
|
return "string";
|
||||||
},
|
},
|
||||||
get filters() {
|
get filters() {
|
||||||
if (this.allChapters.length < 1) return [];
|
if (this.allChapters.length < 1) return [];
|
||||||
const keys = Object.keys(this.allChapters[0]).filter(k => !['manga_title', 'id'].includes(k));
|
const keys = Object.keys(this.allChapters[0]).filter(
|
||||||
return keys.map(k => {
|
(k) => !["manga_title", "id"].includes(k)
|
||||||
let values = this.allChapters.map(c => c[k]);
|
);
|
||||||
|
return keys.map((k) => {
|
||||||
|
let values = this.allChapters.map((c) => c[k]);
|
||||||
const type = this.fieldType(values);
|
const type = this.fieldType(values);
|
||||||
|
|
||||||
if (type === 'array') {
|
if (type === "array") {
|
||||||
// if the type is an array, return the list of available elements
|
// if the type is an array, return the list of available elements
|
||||||
// example: an array of groups or authors
|
// example: an array of groups or authors
|
||||||
values = Array.from(new Set(values.flat().map(v => {
|
values = Array.from(
|
||||||
if (typeof v === 'string') return v.toLowerCase();
|
new Set(
|
||||||
})));
|
values.flat().map((v) => {
|
||||||
|
if (typeof v === "string")
|
||||||
|
return v.toLowerCase();
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
key: k,
|
key: k,
|
||||||
type: type,
|
type: type,
|
||||||
values: values
|
values: values,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
get filterSettings() {
|
get filterSettings() {
|
||||||
return $('#filter-form input:visible, #filter-form select:visible')
|
return $("#filter-form input:visible, #filter-form select:visible")
|
||||||
.get()
|
.get()
|
||||||
.map(i => ({
|
.map((i) => {
|
||||||
key: i.getAttribute('data-filter-key'),
|
const type = i.getAttribute("data-filter-type");
|
||||||
value: i.value.trim(),
|
let value = i.value.trim();
|
||||||
type: i.getAttribute('data-filter-type')
|
if (type.startsWith("date"))
|
||||||
}));
|
value = value ? Date.parse(value).toString() : "";
|
||||||
|
return {
|
||||||
|
key: i.getAttribute("data-filter-key"),
|
||||||
|
value: value,
|
||||||
|
type: type,
|
||||||
|
};
|
||||||
|
});
|
||||||
},
|
},
|
||||||
applyFilters() {
|
applyFilters() {
|
||||||
this.appliedFilters = this.filterSettings;
|
this.appliedFilters = this.filterSettings;
|
||||||
this.chapters = this.filteredChapters;
|
this.chapters = this.filteredChapters;
|
||||||
},
|
},
|
||||||
clearFilters() {
|
clearFilters() {
|
||||||
$('#filter-form input').get().forEach(i => i.value = '');
|
$("#filter-form input")
|
||||||
|
.get()
|
||||||
|
.forEach((i) => (i.value = ""));
|
||||||
this.appliedFilters = [];
|
this.appliedFilters = [];
|
||||||
this.chapters = this.filteredChapters;
|
this.chapters = this.filteredChapters;
|
||||||
},
|
},
|
||||||
mangaSelected(event) {
|
mangaSelected(event) {
|
||||||
const mid = event.currentTarget.getAttribute('data-id');
|
const mid = event.currentTarget.getAttribute("data-id");
|
||||||
this.searchChapters(mid);
|
this.searchChapters(mid);
|
||||||
},
|
},
|
||||||
parseDate(str) {
|
|
||||||
const regex = /([0-9]+[/\-,\ ][0-9]+[/\-,\ ][0-9]+)|([A-Za-z]+)[/\-,\ ]+[0-9]+(st|nd|rd|th)?[/\-,\ ]+[0-9]+/g;
|
|
||||||
// Basic sanity check to make sure it's an actual date.
|
|
||||||
// We need this because Date.parse thinks 'Chapter 1' is a date.
|
|
||||||
if (!regex.test(str))
|
|
||||||
return NaN;
|
|
||||||
return Date.parse(str);
|
|
||||||
},
|
|
||||||
subscribe(modal) {
|
subscribe(modal) {
|
||||||
// TODO:
|
|
||||||
// - use select2
|
|
||||||
|
|
||||||
this.subscribing = true;
|
this.subscribing = true;
|
||||||
fetch(`${base_url}api/admin/plugin/subscribe`, {
|
fetch(`${base_url}api/admin/plugin/subscribe`, {
|
||||||
method: 'POST',
|
method: "POST",
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
filters: this.filterSettings,
|
filters: this.filterSettings,
|
||||||
plugin: this.pid,
|
plugin: this.pid,
|
||||||
name: this.subscriptionName.trim()
|
name: this.subscriptionName.trim(),
|
||||||
}),
|
}),
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json",
|
||||||
}
|
},
|
||||||
|
})
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((data) => {
|
||||||
|
if (!data.success) throw new Error(data.error);
|
||||||
|
alert("success", "Subscription created");
|
||||||
})
|
})
|
||||||
.then(res => res.json())
|
.catch((e) => {
|
||||||
.then(data => {
|
alert("danger", `Failed to subscribe. Error: ${e}`);
|
||||||
if (!data.success)
|
|
||||||
throw new Error(data.error);
|
|
||||||
alert('success', 'Subscription created');
|
|
||||||
})
|
|
||||||
.catch(e => {
|
|
||||||
alert('danger', `Failed to subscribe. Error: ${e}`);
|
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
this.subscribing = false;
|
this.subscribing = false;
|
||||||
UIkit.modal(modal).hide();
|
UIkit.modal(modal).hide();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
filterTypeToReadable(type) {
|
numIsDate(num) {
|
||||||
|
return !isNaN(num) && Number(num) > 328896000000; // 328896000000 => 1 Jan, 1980
|
||||||
|
},
|
||||||
|
renderCell(value) {
|
||||||
|
if (this.numIsDate(value))
|
||||||
|
return `<span>${moment(Number(value)).format(
|
||||||
|
"MMM D, YYYY"
|
||||||
|
)}</span>`;
|
||||||
|
const maxLength = 40;
|
||||||
|
if (value.length > maxLength)
|
||||||
|
return `<span>${value.substr(
|
||||||
|
0,
|
||||||
|
maxLength
|
||||||
|
)}...</span><div uk-dropdown>${value}</div>`;
|
||||||
|
return `<span>${value}</span>`;
|
||||||
|
},
|
||||||
|
renderFilterRow(ft) {
|
||||||
|
const key = ft.key;
|
||||||
|
let type = ft.type;
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'number-min':
|
case "number-min":
|
||||||
return 'number (minimum value)';
|
type = "number (minimum value)";
|
||||||
case 'number-max':
|
break;
|
||||||
return 'number (maximum value)';
|
case "number-max":
|
||||||
case 'date-min':
|
type = "number (maximum value)";
|
||||||
return 'minimum date';
|
break;
|
||||||
case 'date-max':
|
case "date-min":
|
||||||
return 'maximum date';
|
type = "minimum date";
|
||||||
default:
|
break;
|
||||||
return type;
|
case "date-max":
|
||||||
|
type = "maximum date";
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
let value = ft.value;
|
||||||
}
|
if (!value || isNaN(value)) value = "";
|
||||||
|
else if (ft.type.startsWith("date"))
|
||||||
|
value = moment(Number(value)).format("MMM D, YYYY");
|
||||||
|
|
||||||
|
return `<td>${key}</td><td>${type}</td><td>${value}</td>`;
|
||||||
|
},
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
@ -114,10 +114,10 @@
|
|||||||
|
|
||||||
<div x-show="field.type === 'date'" class="uk-grid-small" uk-grid>
|
<div x-show="field.type === 'date'" class="uk-grid-small" uk-grid>
|
||||||
<div class="uk-width-1-2@s">
|
<div class="uk-width-1-2@s">
|
||||||
<input class="uk-input" placeholder="minimum date, e.g., Jan 1 1970" :data-filter-key="field.key" data-filter-type="date-min">
|
<input class="uk-input" type="date" placeholder="minimum date (yyyy-mm-dd)" :data-filter-key="field.key" data-filter-type="date-min">
|
||||||
</div>
|
</div>
|
||||||
<div class="uk-width-1-2@s">
|
<div class="uk-width-1-2@s">
|
||||||
<input class="uk-input" placeholder="maximum date, e.g., Jan 1 1970" :data-filter-key="field.key" data-filter-type="date-max">
|
<input class="uk-input" type="date" placeholder="maximum date (yyyy-mm-dd)" :data-filter-key="field.key" data-filter-type="date-max">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -161,17 +161,7 @@
|
|||||||
<template x-for="ch in chapters" :key="ch">
|
<template x-for="ch in chapters" :key="ch">
|
||||||
<tr class="ui-widget-content" :id="ch.id">
|
<tr class="ui-widget-content" :id="ch.id">
|
||||||
<template x-for="k in chapterKeys" :key="k">
|
<template x-for="k in chapterKeys" :key="k">
|
||||||
<td>
|
<td x-html="renderCell(ch[k])"></td>
|
||||||
<template x-if="ch[k].length > 40">
|
|
||||||
<span>
|
|
||||||
<span x-text="`${ch[k].substring(0, 40)}...`"></span>
|
|
||||||
<div uk-dropdown><span x-text="ch[k]"></span></div>
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
<template x-if="!ch[k].length || ch[k].length <= 40">
|
|
||||||
<span x-text="ch[k]"></span>
|
|
||||||
</template>
|
|
||||||
</td>
|
|
||||||
</template>
|
</template>
|
||||||
</tr>
|
</tr>
|
||||||
</template>
|
</template>
|
||||||
@ -201,11 +191,7 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<template x-for="ft in filterSettings" :key="ft">
|
<template x-for="ft in filterSettings" :key="ft">
|
||||||
<tr>
|
<tr x-html="renderFilterRow(ft)"></tr>
|
||||||
<td x-text="ft.key"></td>
|
|
||||||
<td x-text="filterTypeToReadable(ft.type)"></td>
|
|
||||||
<td x-text="ft.value"></td>
|
|
||||||
</tr>
|
|
||||||
</template>
|
</template>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@ -222,6 +208,7 @@
|
|||||||
|
|
||||||
<% content_for "script" do %>
|
<% content_for "script" do %>
|
||||||
<%= render_component "jquery-ui" %>
|
<%= render_component "jquery-ui" %>
|
||||||
|
<%= render_component "moment" %>
|
||||||
<script src="<%= base_url %>js/alert.js"></script>
|
<script src="<%= base_url %>js/alert.js"></script>
|
||||||
<script src="<%= base_url %>js/plugin-download.js"></script>
|
<script src="<%= base_url %>js/plugin-download.js"></script>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user