diff --git a/assets/api.ts b/assets/api.ts index 957914c..0c5b528 100644 --- a/assets/api.ts +++ b/assets/api.ts @@ -1,5 +1,7 @@ import _axios, { AxiosInstance } from 'axios' +const WANIKANI_API_KEY = 'WANIKANI_API_KEY' + export type ITypeLevel = 'character' | 'vocabulary' export type IType = ITypeLevel | 'sentence' @@ -93,35 +95,27 @@ class Api { } async setWanikaniApiKey(apiKey?: string) { + apiKey = apiKey || localStorage.getItem(WANIKANI_API_KEY) || '' + console.log(apiKey) + const setApiKey = () => { if (apiKey) { this.axios.defaults.headers.common['Authorization'] = `Bearer ${apiKey}` + localStorage.setItem(WANIKANI_API_KEY, apiKey) } else { delete this.axios.defaults.headers.common['Authorization'] + localStorage.removeItem(WANIKANI_API_KEY) } } setApiKey() + const r = await window.$nuxt.$accessor.updateSettings() - return await this.isAuthorized().then((r) => { - if (!r) { - apiKey = '' - setApiKey() - } - return r - }) - } - - async isAuthorized(): Promise { - if (this._isAuthorized && !this._user) { - const r = await this.user_getSettings({ select: ['identifier'] }) - if (r) { - this._user = r.identifier - this._isAuthorized = true - } + if (!r) { + apiKey = '' + setApiKey() } - - return this._isAuthorized + return r } async user_getSettings(params: { select: (keyof IUserSettings)[] }) { diff --git a/components/tabs/LookupTab.vue b/components/tabs/LookupTab.vue index 7e14690..3328c61 100644 --- a/components/tabs/LookupTab.vue +++ b/components/tabs/LookupTab.vue @@ -30,12 +30,10 @@
- {{ current }} + {{ entry.entry }}
@@ -57,7 +55,7 @@
-
+
Tags: @@ -66,11 +64,11 @@
-
+
Level - {{ level }} + {{ entry.level }}
@@ -91,14 +89,14 @@
- {{ reading.join(' / ') }} + {{ entry.reading.join(' / ') }}
- {{ translation.join(' / ') }} + {{ entry.translation.join(' / ') }}
- +
{{ h }}
- +
{{ h }}
- +
{{ h }}
- - -
-

Vocabularies

- - - -
- -
-
- - {{ v.entry }} - - - - {{ v.alt.join(' ') }} - - - [{{ v.reading.join(' / ') }}] - - {{ v.translation.join(' / ') }} -
-
-
- - -
-

Sentences

- - - -
- -
-
- - {{ s.entry[0] }} - - - {{ s.translation[0] }} -
-
-
@@ -278,10 +226,31 @@ import { Component, Prop, Ref, Vue, Watch } from 'nuxt-property-decorator' import ContextMenu from '@/components/ContextMenu.vue' import { api, IDictionaryLookup, IEntryLookup, IType } from '~/assets/api' -import { capitalize } from '~/assets/util' type ILookupEntry = Omit & IEntryLookup +const makeLookupEntry = ( + entry = '', + type: IType = 'vocabulary' +): ILookupEntry => { + return { + entry, + list: [], + type, + reading: [], + translation: [], + audio: [], + description: '', + tag: [], + sub: [], + sup: [], + variant: [], + visual: [], + amalgamation: [], + component: [], + } +} + @Component({ components: { ContextMenu, @@ -300,13 +269,12 @@ export default class LookupTab extends Vue { @Prop({ default: () => ({}) }) query!: { q?: string entry?: string + type?: 'character' | 'vocabulary' } - @Prop() type!: 'character' | 'vocabulary' - @Ref() context!: ContextMenu - title = this.type === 'character' ? 'Kanji' : capitalize(this.type) + title = 'Lookup' entries: ILookupEntry[] = [] i = 0 @@ -320,6 +288,7 @@ export default class LookupTab extends Vue { selected: { entry: string type: string + audio?: ILookupEntry['audio'] } = { entry: '', type: '', @@ -328,8 +297,8 @@ export default class LookupTab extends Vue { q = '' q0 = '' - get current() { - return this.entries[this.i]?.entry || '' + get entry() { + return this.entries[this.i] || makeLookupEntry() } get additionalContext() { @@ -338,7 +307,7 @@ export default class LookupTab extends Vue { { name: 'Reload', handler: async () => { - const { result } = await api.all_getRandom({ type: this.type }) + const { result } = await api.all_getRandom({ type: 'vocabulary' }) this.q0 = result }, }, @@ -348,12 +317,8 @@ export default class LookupTab extends Vue { return [] } - openContext( - evt: MouseEvent, - entry = this.selected.entry, - type = this.selected.type - ) { - this.selected = { entry, type } + openContext(evt: MouseEvent, entry = this.selected) { + this.selected = entry this.context.open(evt) } @@ -362,72 +327,23 @@ export default class LookupTab extends Vue { this.$emit('title', (q ? q + ' - ' : '') + this.title) this.entries = [] - if (this.type === 'character') { - if (/\p{sc=Han}/u.test(q)) { - let qs = q.split('').filter((h) => /\p{sc=Han}/u.test(h)) - qs = qs.filter((h, i) => qs.indexOf(h) === i) - - this.entries = qs.map( - (entry) => - ({ - entry, - list: [], - type: this.type, - reading: [], - translation: [], - audio: [], - description: '', - tag: [], - sub: [], - sup: [], - variant: [], - visual: [], - amalgamation: [], - component: [], - } as ILookupEntry) - ) - } else { - if (!q.trim()) { - const { result } = await api.all_getRandom({ type: this.type }) - - this.q0 = result - this.q = result - return - } else { - const r = await api.all_getQuery({ - q, - type: this.type, - distinct: 'id', - }) - this.entries = r.result.map( - ({ entry }) => - ({ - entry, - list: [], - type: this.type, - reading: [], - translation: [], - audio: [], - description: '', - tag: [], - sub: [], - sup: [], - variant: [], - visual: [], - amalgamation: [], - component: [], - } as ILookupEntry) - ) - } - } + if (q.trim()) { + const r = await api.all_getQuery({ + q, + type: this.query.type, + distinct: 'id', + }) + this.entries = r.result.map(({ entry, type }) => + makeLookupEntry(entry, type) + ) } this.i = 0 } - @Watch('current') + @Watch('entry.entry') load() { - if (this.current) { + if (this.entry.entry) { this.loadComponents() this.loadVocabularies() this.loadSentences() @@ -441,17 +357,18 @@ export default class LookupTab extends Vue { } async loadComponents() { - if (!this.current.trim()) return + const entry = this.entries[this.i] + if (!entry) return const [comp, dict] = await Promise.all([ api.all_getComponent({ - entry: this.current, - type: this.type, + entry: entry.entry, + type: entry.type, }), api .all_getLookup({ - entry: this.current, - type: this.type, + entry: entry.entry, + type: entry.type, limit: 1, }) .then(({ result: [r] }) => r), @@ -459,9 +376,9 @@ export default class LookupTab extends Vue { this.$set(this.entries, this.i, { ...(dict || { - entry: this.current, + entry: entry.entry, list: [], - type: this.type, + type: entry.type, reading: [], translation: [], audio: [], @@ -480,22 +397,25 @@ export default class LookupTab extends Vue { } async loadVocabularies() { - if (this.type === 'character') { - if (!/^\p{sc=Han}$/u.test(this.current)) { + const entry = this.entries[this.i] + if (!entry) return + + if (entry.type === 'character') { + if (!/^\p{sc=Han}$/u.test(entry.entry)) { this.lookup.vocabulary = [] return } this.lookup.vocabulary = await api .character_getVocabulary({ - entry: this.current, + entry: entry.entry, distinct: 'id', }) .then((r) => r.result) } else { this.lookup.vocabulary = await api .vocabulary_getVocabulary({ - entry: this.current, + entry: entry.entry, distinct: 'id', }) .then((r) => r.result) @@ -503,22 +423,25 @@ export default class LookupTab extends Vue { } async loadSentences() { - if (this.type === 'character') { - if (!/^\p{sc=Han}$/u.test(this.current)) { + const entry = this.entries[this.i] + if (!entry) return + + if (entry.type === 'character') { + if (!/^\p{sc=Han}$/u.test(entry.entry)) { this.lookup.sentence = [] return } this.lookup.sentence = await api .character_getSentence({ - entry: this.current, + entry: entry.entry, distinct: 'id', }) .then((r) => r.result) } else { this.lookup.sentence = await api .vocabulary_getSentence({ - entry: this.current, + entry: entry.entry, distinct: 'id', }) .then((r) => r.result) diff --git a/layouts/default.vue b/layouts/default.vue index d9c3269..359f101 100644 --- a/layouts/default.vue +++ b/layouts/default.vue @@ -1,74 +1,37 @@ - + + diff --git a/nuxt.config.ts b/nuxt.config.ts index 13d7d2c..2eadc2d 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -1,6 +1,8 @@ import { NuxtConfig } from '@nuxt/types' export default async (): Promise => { + const NODE_ENV = process.env['NODE_ENV'] || '' + return { // Disable server-side rendering: https://go.nuxtjs.dev/ssr-mode ssr: false, @@ -10,11 +12,15 @@ export default async (): Promise => { // Global page headers: https://go.nuxtjs.dev/config-head head: { - title: 'jaquiz', + title: 'WaniKani-based jaquiz', meta: [ { charset: 'utf-8' }, { name: 'viewport', content: 'width=device-width, initial-scale=1' }, - { hid: 'description', name: 'description', content: '' }, + { + hid: 'description', + name: 'description', + content: 'WaniKani-based jaquiz', + }, { name: 'format-detection', content: 'telephone=no' }, ], link: [{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }], @@ -24,7 +30,7 @@ export default async (): Promise => { css: [], // Plugins to run before rendering page: https://go.nuxtjs.dev/config-plugins - plugins: [], + plugins: ['~/plugins/filter.ts', '~/plugins/vue-context.client.js'], // Auto import components: https://go.nuxtjs.dev/config-components components: true, @@ -33,6 +39,7 @@ export default async (): Promise => { buildModules: [ // https://go.nuxtjs.dev/typescript '@nuxt/typescript-build', + 'nuxt-typed-vuex', ], // Modules: https://go.nuxtjs.dev/config-modules @@ -41,6 +48,43 @@ export default async (): Promise => { 'nuxt-buefy', // https://go.nuxtjs.dev/pwa '@nuxtjs/pwa', + '@nuxtjs/proxy', + [ + '@nuxtjs/fontawesome', + { + component: 'fontawesome', + icons: { + brands: ['faGithub'], + solid: [ + 'faAngleLeft', + 'faAngleRight', + 'faAngleUp', + 'faArrowUp', + 'faBookReader', + 'faCaretDown', + 'faCaretUp', + 'faChalkboardTeacher', + 'faCog', + 'faExclamationCircle', + 'faExclamationTriangle', + 'faEye', + 'faEyeSlash', + 'faInfoCircle', + 'faRandom', + 'faSearch', + 'faTag', + 'faListUl', + 'faListOl', + 'faBars', + 'faSave', + 'faTrash', + 'faTools', + 'faTh', + 'faRedoAlt', + ], + }, + }, + ], ], // PWA module configuration: https://go.nuxtjs.dev/pwa @@ -50,6 +94,14 @@ export default async (): Promise => { }, }, + proxy: { + '/api': 'http://localhost:35594', + }, + + env: { + NODE_ENV, + }, + // Build Configuration: https://go.nuxtjs.dev/config-build build: {}, } diff --git a/package.json b/package.json index 1baad86..1963829 100644 --- a/package.json +++ b/package.json @@ -9,22 +9,28 @@ "generate": "nuxt generate" }, "dependencies": { + "@fortawesome/fontawesome-svg-core": "^1.2.36", + "@fortawesome/free-brands-svg-icons": "^5.15.4", + "@fortawesome/free-solid-svg-icons": "^5.15.4", + "@fortawesome/vue-fontawesome": "^2.0.6", + "@nuxtjs/fontawesome": "^1.1.2", + "@nuxtjs/proxy": "^2.1.0", "@nuxtjs/pwa": "^3.3.5", - "axios": "^0.23.0", - "core-js": "^3.15.1", + "axios": "^0.24.0", + "core-js": "^3.19.0", "jsonschema-definer": "^1.3.2", - "nuxt": "^2.15.7", - "nuxt-buefy": "^0.4.8", + "nuxt": "^2.15.8", + "nuxt-buefy": "^0.4.11", "nuxt-property-decorator": "^2.9.1", "nuxt-typed-vuex": "^0.2.0", "vue-context": "^6.0.0" }, "devDependencies": { - "@nuxt/types": "^2.15.7", + "@nuxt/types": "^2.15.8", "@nuxt/typescript-build": "^2.1.0", "eslint-config-prettier": "^8.3.0", - "prettier": "^2.3.2", - "sass": "^1.43.3", + "prettier": "^2.4.1", + "sass": "^1.43.4", "sass-loader": "~10.1.1" } -} +} \ No newline at end of file diff --git a/pages/app.vue b/pages/app.vue index 20ab548..c4339ee 100644 --- a/pages/app.vue +++ b/pages/app.vue @@ -129,16 +129,14 @@ import { api } from '~/assets/api' LibraryTab, }, async mounted() { - await this.$accessor.updateSettings() - - if (!this.$accessor.isApp) { - this.$router.replace('/') - } else { + if (await api.setWanikaniApiKey()) { this.$accessor.ADD_TAB({ component: 'Lookup', first: true, }) this.isReady = true + } else { + this.$router.replace('/') } }, }) diff --git a/pages/index.vue b/pages/index.vue index 6937638..ba92311 100644 --- a/pages/index.vue +++ b/pages/index.vue @@ -23,7 +23,7 @@ import { api } from '~/assets/api' @Component({ async mounted() { - if (await api.isAuthorized()) { + if (await api.setWanikaniApiKey()) { this.$router.replace('/app') } else { this.isReady = true diff --git a/plugins/filter.ts b/plugins/filter.ts new file mode 100644 index 0000000..a3d4843 --- /dev/null +++ b/plugins/filter.ts @@ -0,0 +1,337 @@ +import Vue from 'vue' + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +Vue.filter('format', (v: any) => { + if (typeof v === 'number') { + return v || v === 0 ? v.toLocaleString() : '' + } else if (v instanceof Date) { + return v.toLocaleDateString([], { + year: 'numeric', + month: 'long', + day: 'numeric', + weekday: 'short', + }) + } else if (v && typeof v === 'object') { + return JSON.stringify(v) + } + return v +}) + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +Vue.filter('formatDate', (v: any) => { + return v + ? new Date(v).toLocaleDateString([], { + year: 'numeric', + month: 'long', + day: 'numeric', + weekday: 'short', + }) + : '' +}) + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +Vue.filter('duration', (v: any) => { + return v + ? new Duration(new Date(v), new Date()).toString({ + sign: false, + granularity: 2, + }) + : '' +}) + +/** + * Possible duration units in this parser + */ +export type DurationUnit = 'ms' | 's' | 'min' | 'h' | 'd' | 'w' | 'mo' | 'y' + +export interface IDurationOptions { + /** + * @default true + */ + sign?: boolean + /** + * Number of units plus subunits + */ + granularity?: number + /** + * Number of max units shown + */ + maxUnit?: number + /** + * Smallest unit shown + * + * @default 's' + */ + smallest?: DurationUnit + /** + * Custom naming for units + */ + unit?: Partial> +} + +export class Duration { + /** + * Sign in front of the output toString() + */ + sign: '+' | '-' | '' = '+' + + /** + * Milliseconds + */ + ms: number + /** + * Seconds + */ + s: number + /** + * Minutes + */ + min: number + /** + * Hours + */ + h: number + /** + * Days + */ + d: number + /** + * Weeks + */ + w: number + /** + * Months + */ + mo: number + /** + * Years + */ + y: number + + private _dates: [Date, Date] = [new Date(this.from), new Date(this.to)] + + /** + * Parse milliseconds (i.e. epoch) to Duration, based on before present time + */ + static of(msec: number) { + const to = new Date() + const output = new this(new Date(+to - msec), to) + output.sign = '' + + return output + } + + constructor( + /** + * Starting Date + */ + public from: Date, + /** + * Ending date + */ + public to: Date + ) { + if (from > to) { + this.sign = '-' + this._dates = this._dates.reverse() as [Date, Date] + } + + this.ms = this._parse((d) => d.getMilliseconds(), { + get: (d) => d.getSeconds(), + set: (d, v) => d.setSeconds(v), + inc: () => 1000, + }) + + this.s = this._parse((d) => d.getSeconds(), { + get: (d) => d.getMinutes(), + set: (d, v) => d.setMinutes(v), + inc: () => 60, + }) + + this.min = this._parse((d) => d.getMinutes(), { + get: (d) => d.getHours(), + set: (d, v) => d.setHours(v), + inc: () => 60, + }) + + this.h = this._parse((d) => d.getHours(), { + get: (d) => d.getDate(), + set: (d, v) => d.setDate(v), + inc: () => 24, + }) + + this.d = this._parse((d) => d.getDate(), { + get: (d) => d.getMonth(), + set: (d, v) => d.setMonth(v), + inc: (d) => { + const y = d.getFullYear() + let isLeapYear = true + if (y % 4) { + isLeapYear = false + } else if (y % 100) { + isLeapYear = true + } else if (y % 400) { + isLeapYear = false + } + + return [ + 31, // Jan + isLeapYear ? 29 : 28, // Feb + 31, // Mar + 30, // Apr + 31, // May + 30, // Jun + 31, // Jul + 31, // Aug + 30, // Sep + 31, // Oct + 30, // Nov + 31, // Dec + ][d.getMonth()]! + }, + }) + + this.w = Math.floor(this.d / 7) + this.d = this.d % 7 + + this.mo = this._parse((d) => d.getMonth(), { + get: (d) => d.getFullYear(), + set: (d, v) => d.setFullYear(v), + inc: () => 12, + }) + + this.y = this._parse((d) => d.getFullYear()) + } + + /** + * To JSON-serializable OrderedDict + */ + toOrderedDict(): [DurationUnit, number][] { + return [ + ['ms', this.ms], + ['s', this.s], + ['min', this.min], + ['h', this.h], + ['d', this.d], + ['w', this.w], + ['mo', this.mo], + ['y', this.y], + ] + } + + /** + * To String + * + * Works with `${duration}` also + */ + toString({ + sign = true, + granularity, + maxUnit, + smallest = 's', + unit = {}, + }: IDurationOptions = {}) { + const odict = this.toOrderedDict() + const smallestIndex = odict.map(([k]) => k).indexOf(smallest) + const filteredDict = odict.filter(([, v], i) => v && i >= smallestIndex) + + const str = filteredDict + .slice(granularity ? filteredDict.length - granularity : 0) + .reverse() + .slice(0, maxUnit) + .map(([k, v]) => `${v.toLocaleString()}${unit[k] || k}`) + .join(' ') + + if (sign) { + return this.sign + str + } + + return str + } + + private _parse( + current: (d: Date) => number, + upper?: { + get: (d: Date) => number + set: (d: Date, v: number) => void + inc: (d: Date) => number + } + ) { + let a = current(this._dates[1]) - current(this._dates[0]) + + if (upper) { + while (a < 0) { + upper.set(this._dates[1], upper.get(this._dates[1]) - 1) + this._dates[1] = new Date(this._dates[1]) + + a += upper.inc(this._dates[1]) + } + } + + return a + } +} + +/** + * Date adding functions + */ +export function addDate(d: Date): Record Date> { + return { + /** + * Milliseconds + */ + ms: (n) => { + d.setMilliseconds(d.getMilliseconds() + n) + return new Date(d) + }, + /** + * Seconds + */ + s: (n) => { + d.setSeconds(d.getSeconds() + n) + return new Date(d) + }, + /** + * Minutes + */ + min: (n) => { + d.setMinutes(d.getMinutes() + n) + return new Date(d) + }, + /** + * Hours + */ + h: (n) => { + d.setHours(d.getHours() + n) + return new Date(d) + }, + /** + * Date + */ + d: (n) => { + d.setDate(d.getDate() + n) + return new Date(d) + }, + /** + * Weeks + */ + w: (n) => { + d.setDate(d.getDate() + n * 7) + return new Date(d) + }, + /** + * Months + */ + mo: (n) => { + d.setMonth(d.getMonth() + n) + return new Date(d) + }, + /** + * Years + */ + y: (n) => { + d.setFullYear(d.getFullYear() + n) + return new Date(d) + }, + } +} diff --git a/plugins/vue-context.client.js b/plugins/vue-context.client.js new file mode 100644 index 0000000..a467dcb --- /dev/null +++ b/plugins/vue-context.client.js @@ -0,0 +1,6 @@ +import 'vue-context/dist/css/vue-context.css' + +import Vue from 'vue' +import VueContext from 'vue-context' + +Vue.component('VueContext', VueContext) diff --git a/store/index.ts b/store/index.ts index 83faa18..46d4046 100644 --- a/store/index.ts +++ b/store/index.ts @@ -1,4 +1,5 @@ import { actionTree, getAccessorType, mutationTree } from 'typed-vuex' + import { api } from '~/assets/api' import { jsonClone } from '~/assets/util' @@ -29,6 +30,24 @@ export const state = () => ({ identifier: '', }) +const SET_ALL_TABS = ( + s: ReturnType, + o: { + tabs: ITab[] + activeTab: number + } = s +) => { + localStorage.setItem( + TAB_SETTINGS, + JSON.stringify({ + tabs: o.tabs, + activeTabs: o.activeTab, + }) + ) + s.tabs = jsonClone(o.tabs) + s.activeTab = jsonClone(o.activeTab) +} + export const mutations = mutationTree(state, { SET_SETTINGS(state, settings: ISettings) { state.settings = settings @@ -36,26 +55,17 @@ export const mutations = mutationTree(state, { SET_IS_APP(state, isApp: boolean) { state.isApp = isApp }, - SET_ALL_TABS( - state, - o: { - tabs: ITab[] - activeTab: number - } = state - ) { - localStorage.setItem( - TAB_SETTINGS, - JSON.stringify({ - tabs: o.tabs, - activeTabs: o.activeTab, - }) - ) - state.tabs = jsonClone(o.tabs) - state.activeTab = jsonClone(o.activeTab) - }, SET_TAB_TITLE(state, { i, title }: { i: number; title: string }) { - state.tabs[i].title = title - this.SET_ALL_TABS(state) + if (state.tabs[i]) { + state.tabs[i]!.title = title + SET_ALL_TABS(state) + } + }, + SET_ALL_TABS(state) { + const settings = localStorage.getItem(TAB_SETTINGS) + if (settings) { + Object.assign(state, JSON.parse(settings)) + } }, ADD_TAB( state, @@ -90,7 +100,7 @@ export const mutations = mutationTree(state, { } state.activeTab = state.tabs.length - 1 - this.SET_ALL_TABS(state) + SET_ALL_TABS(state) }, DELETE_TAB(state, { i }: { i: number }) { state.tabs.splice(i, 1) @@ -99,15 +109,17 @@ export const mutations = mutationTree(state, { state.activeTab = state.tabs.length - 1 } - this.SET_ALL_TABS(state) + SET_ALL_TABS(state) }, SET_TAB_COMPONENT(state, { i, component }: { i: number; component: string }) { - state.tabs[i].component = component - this.SET_ALL_TABS(state) + if (state.tabs[i]) { + state.tabs[i]!.component = component + SET_ALL_TABS(state) + } }, SET_TAB_CURRENT(state, { i }: { i: number }) { state.activeTab = i - this.SET_ALL_TABS(state) + SET_ALL_TABS(state) }, SET_IDENTIFIER(state, name: string) { state.identifier = name @@ -117,16 +129,15 @@ export const mutations = mutationTree(state, { export const actions = actionTree( { state, mutations }, { - async nuxtServerInit({ dispatch, commit }) { - await dispatch('updateSetings') - - const settings = localStorage.getItem(TAB_SETTINGS) - if (settings) commit('SET_ALL_TABS', JSON.parse(settings)) + async nuxtServerInit({ commit }) { + commit('SET_ALL_TABS') }, async updateSettings({ commit }) { - const r = await api.user_getSettings({ - select: ['identifier', 'levelMin', 'level'], - }) + const r = await api + .user_getSettings({ + select: ['identifier', 'levelMin', 'level'], + }) + .catch(() => null) commit('SET_IS_APP', !!r) commit('SET_IDENTIFIER', r?.identifier || '') @@ -144,6 +155,8 @@ export const actions = actionTree( level: r?.level || null, levelMin: r?.levelMin || null, }) + + return !!r }, } ) diff --git a/yarn.lock b/yarn.lock index c63dde0..004cf2c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -908,6 +908,42 @@ resolved "https://registry.yarnpkg.com/@csstools/convert-colors/-/convert-colors-1.4.0.tgz#ad495dc41b12e75d588c6db8b9834f08fa131eb7" integrity sha512-5a6wqoJV/xEdbRNKVo6I4hO3VjyDq//8q2f9I6PBAvMesJHFauXDorcNCsr9RzvsZnaWi5NYCcfyqP1QeFHFbw== +"@fortawesome/fontawesome-common-types@^0.2.36": + version "0.2.36" + resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.36.tgz#b44e52db3b6b20523e0c57ef8c42d315532cb903" + integrity sha512-a/7BiSgobHAgBWeN7N0w+lAhInrGxksn13uK7231n2m8EDPE3BMCl9NZLTGrj9ZXfCmC6LM0QLqXidIizVQ6yg== + +"@fortawesome/fontawesome-svg-core@^1.2.27", "@fortawesome/fontawesome-svg-core@^1.2.36": + version "1.2.36" + resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.36.tgz#4f2ea6f778298e0c47c6524ce2e7fd58eb6930e3" + integrity sha512-YUcsLQKYb6DmaJjIHdDWpBIGCcyE/W+p/LMGvjQem55Mm2XWVAP5kWTMKWLv9lwpCVjpLxPyOMOyUocP1GxrtA== + dependencies: + "@fortawesome/fontawesome-common-types" "^0.2.36" + +"@fortawesome/free-brands-svg-icons@^5.15.4": + version "5.15.4" + resolved "https://registry.yarnpkg.com/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-5.15.4.tgz#ec8a44dd383bcdd58aa7d1c96f38251e6fec9733" + integrity sha512-f1witbwycL9cTENJegcmcZRYyawAFbm8+c6IirLmwbbpqz46wyjbQYLuxOc7weXFXfB7QR8/Vd2u5R3q6JYD9g== + dependencies: + "@fortawesome/fontawesome-common-types" "^0.2.36" + +"@fortawesome/free-solid-svg-icons@^5.15.4": + version "5.15.4" + resolved "https://registry.yarnpkg.com/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.15.4.tgz#2a68f3fc3ddda12e52645654142b9e4e8fbb6cc5" + integrity sha512-JLmQfz6tdtwxoihXLg6lT78BorrFyCf59SAwBM6qV/0zXyVeDygJVb3fk+j5Qat+Yvcxp1buLTY5iDh1ZSAQ8w== + dependencies: + "@fortawesome/fontawesome-common-types" "^0.2.36" + +"@fortawesome/vue-fontawesome@^0.1.9": + version "0.1.10" + resolved "https://registry.yarnpkg.com/@fortawesome/vue-fontawesome/-/vue-fontawesome-0.1.10.tgz#eeeec1e4e8850bed0468f938292b06cda793bf34" + integrity sha512-b2+SLF31h32LSepVcXe+BQ63yvbq5qmTCy4KfFogCYm2bn68H5sDWUnX+U7MBqnM2aeEk9M7xSoqGnu+wSdY6w== + +"@fortawesome/vue-fontawesome@^2.0.6": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@fortawesome/vue-fontawesome/-/vue-fontawesome-2.0.6.tgz#87e691ed87f28f4667238573a29743f543a087f6" + integrity sha512-V3vT3flY15AKbUS31aZOP12awQI3aAzkr2B1KnqcHLmwrmy51DW3pwyBczKdypV8QxBZ8U68Hl2XxK2nudTxpg== + "@gar/promisify@^1.0.1": version "1.1.2" resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.2.tgz#30aa825f11d438671d585bd44e7fd564535fc210" @@ -1165,7 +1201,7 @@ rc9 "^1.2.0" std-env "^2.3.0" -"@nuxt/types@^2.15.7": +"@nuxt/types@^2.15.8": version "2.15.8" resolved "https://registry.yarnpkg.com/@nuxt/types/-/types-2.15.8.tgz#1249de448f68169fe17e9379ee7b5caa0eb336b0" integrity sha512-zBAG5Fy+SIaZIerOVF1vxy1zz16ZK07QSbsuQAjdtEFlvr+vKK+0AqCv8r8DBY5IVqdMIaw5FgNUz5py0xWdPg== @@ -1302,6 +1338,21 @@ webpack-node-externals "^3.0.0" webpackbar "^4.0.0" +"@nuxtjs/fontawesome@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@nuxtjs/fontawesome/-/fontawesome-1.1.2.tgz#0add6519095b392bdffb6e3ad40f3026d20f5c44" + integrity sha512-QAfo7hdc6hiCOohdR861oNQ+riKW/kD22bYyvaC++xXiiC1hBQcrRQ6xXd5gln+6SKCwT09+C4kGjzTgrwtr7w== + dependencies: + "@fortawesome/fontawesome-svg-core" "^1.2.27" + "@fortawesome/vue-fontawesome" "^0.1.9" + +"@nuxtjs/proxy@^2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@nuxtjs/proxy/-/proxy-2.1.0.tgz#fa7715a11d237fa1273503c4e9e137dd1bf5575b" + integrity sha512-/qtoeqXgZ4Mg6LRg/gDUZQrFpOlOdHrol/vQYMnKu3aN3bP90UfOUB3QSDghUUK7OISAJ0xp8Ld78aHyCTcKCQ== + dependencies: + http-proxy-middleware "^1.0.6" + "@nuxtjs/pwa@^3.3.5": version "3.3.5" resolved "https://registry.yarnpkg.com/@nuxtjs/pwa/-/pwa-3.3.5.tgz#db7c905536ebe8a464a347b6ae3215810642c044" @@ -1470,6 +1521,13 @@ "@types/relateurl" "*" "@types/uglify-js" "*" +"@types/http-proxy@^1.17.5": + version "1.17.7" + resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.7.tgz#30ea85cc2c868368352a37f0d0d3581e24834c6f" + integrity sha512-9hdj6iXH64tHSLTY+Vt2eYOGzSogC+JQ2H7bdPWkuh7KXP5qLllWx++t+K9Wk556c3dkDdPws/SpMRi0sdCT1w== + dependencies: + "@types/node" "*" + "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8": version "7.0.9" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d" @@ -2121,10 +2179,10 @@ autoprefixer@^9.6.1: postcss "^7.0.32" postcss-value-parser "^4.1.0" -axios@^0.23.0: - version "0.23.0" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.23.0.tgz#b0fa5d0948a8d1d75e3d5635238b6c4625b05149" - integrity sha512-NmvAE4i0YAv5cKq8zlDoPd1VLKAqX5oLuZKs8xkJa4qi6RGn0uhCYFjWtHHC9EM/MwOwYWOs53W+V0aqEXq1sg== +axios@^0.24.0: + version "0.24.0" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.24.0.tgz#804e6fa1e4b9c5288501dd9dff56a7a0940d20d6" + integrity sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA== dependencies: follow-redirects "^1.14.4" @@ -2356,10 +2414,10 @@ browserslist@*, browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.16.6, node-releases "^2.0.1" picocolors "^1.0.0" -buefy@^0.9.10: - version "0.9.10" - resolved "https://registry.yarnpkg.com/buefy/-/buefy-0.9.10.tgz#17f64ee1ba43a145d1d3c56f45cba95e4e2975fa" - integrity sha512-xXEoy/NTgBNiIfBTCdHi2Vu5SJJdB046py6ekUvYuUgYwRvulySZksdecVNNWdfEVU8iD4esZaRbTLwCegFcVQ== +buefy@^0.9.11: + version "0.9.11" + resolved "https://registry.yarnpkg.com/buefy/-/buefy-0.9.11.tgz#69ab7f46e4f172d43247916f770f29764f9e0931" + integrity sha512-WP32SiaM9WVxDtzgdiq7V2zyIvn41NboPgluVqdB6OAi1/QhjO/63m6hd/jy6Vk8r+zuhIZD+aP9KlQ10EhxTQ== dependencies: bulma "0.9.3" @@ -2894,10 +2952,10 @@ core-js@^2.6.5: resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec" integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ== -core-js@^3.15.1: - version "3.18.3" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.18.3.tgz#86a0bba2d8ec3df860fefcc07a8d119779f01509" - integrity sha512-tReEhtMReZaPFVw7dajMx0vlsz3oOb8ajgPoHVYGxr8ErnZ6PcYEvvmjGmXlfpnxpkYSdOQttjB+MvVbCGfvLw== +core-js@^3.19.0: + version "3.19.0" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.19.0.tgz#9e40098a9bc326c7e81b486abbd5e12b9d275176" + integrity sha512-L1TpFRWXZ76vH1yLM+z6KssLZrP8Z6GxxW4auoCj+XiViOzNPJCAuTIkn03BGdFe6Z5clX5t64wRIRypsZQrUg== core-util-is@~1.0.0: version "1.0.3" @@ -3599,6 +3657,11 @@ etag@^1.8.1, etag@~1.8.1: resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= +eventemitter3@^4.0.0: + version "4.0.7" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" + integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== + events@^3.0.0: version "3.3.0" resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" @@ -3837,6 +3900,11 @@ flush-write-stream@^1.0.0: inherits "^2.0.3" readable-stream "^2.3.6" +follow-redirects@^1.0.0: + version "1.14.5" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.5.tgz#f09a5848981d3c772b5392309778523f8d85c381" + integrity sha512-wtphSXy7d4/OR+MvIFbCVBDzZ5520qV8XfPklSN5QtxuMUJZ+b0Wnst1e1lCDocfzuCkHqj8k0FpZqO+UIaKNA== + follow-redirects@^1.14.4: version "1.14.4" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.4.tgz#838fdf48a8bbdd79e52ee51fb1c94e3ed98b9379" @@ -4302,6 +4370,26 @@ http-errors@~1.7.2: statuses ">= 1.5.0 < 2" toidentifier "1.0.0" +http-proxy-middleware@^1.0.6: + version "1.3.1" + resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-1.3.1.tgz#43700d6d9eecb7419bf086a128d0f7205d9eb665" + integrity sha512-13eVVDYS4z79w7f1+NPllJtOQFx/FdUW4btIvVRMaRlUY9VGstAbo5MOhLEuUgZFRHn3x50ufn25zkj/boZnEg== + dependencies: + "@types/http-proxy" "^1.17.5" + http-proxy "^1.18.1" + is-glob "^4.0.1" + is-plain-obj "^3.0.0" + micromatch "^4.0.2" + +http-proxy@^1.18.1: + version "1.18.1" + resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549" + integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ== + dependencies: + eventemitter3 "^4.0.0" + follow-redirects "^1.0.0" + requires-port "^1.0.0" + https-browserify@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" @@ -4658,6 +4746,11 @@ is-plain-obj@^1.0.0: resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4= +is-plain-obj@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-3.0.0.tgz#af6f2ea14ac5a646183a5bbdb5baabbc156ad9d7" + integrity sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA== + is-plain-object@^2.0.3, is-plain-object@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" @@ -5158,7 +5251,7 @@ micromatch@^3.1.10, micromatch@^3.1.4: snapdragon "^0.8.1" to-regex "^3.0.2" -micromatch@^4.0.0, micromatch@^4.0.4: +micromatch@^4.0.0, micromatch@^4.0.2, micromatch@^4.0.4: version "4.0.4" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg== @@ -5517,12 +5610,12 @@ num2fraction@^1.2.2: resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede" integrity sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4= -nuxt-buefy@^0.4.8: - version "0.4.10" - resolved "https://registry.yarnpkg.com/nuxt-buefy/-/nuxt-buefy-0.4.10.tgz#7e091ebee5c7dd2bf9de46a3b7a36a3a4d0d4fba" - integrity sha512-Kk/QrNXUrIe3/iPecb0v1OmcZWfdihioEuPtqW4OUfaDDPrVUdfPPRKLMHNgy1OPxDEGSor/h3zMr23d9/0Nyw== +nuxt-buefy@^0.4.11: + version "0.4.11" + resolved "https://registry.yarnpkg.com/nuxt-buefy/-/nuxt-buefy-0.4.11.tgz#38a4d2a3abae05c64034d005e46728cfd694fa25" + integrity sha512-mld92vqY5uo+aQp4E8OnVE0abXPgf5t+k4ckmsMgYGfn0T3pDqJq7O4JLELxfBxfmBxmwce8lJ6ZaJ2GxCNaWQ== dependencies: - buefy "^0.9.10" + buefy "^0.9.11" nuxt-property-decorator@^2.9.1: version "2.9.1" @@ -5542,7 +5635,7 @@ nuxt-typed-vuex@^0.2.0: typed-vuex "0.2.0" upath "^2.0.1" -nuxt@^2.15.7: +nuxt@^2.15.8: version "2.15.8" resolved "https://registry.yarnpkg.com/nuxt/-/nuxt-2.15.8.tgz#946cba46bdaaf0e3918aa27fd9ea0fed8ed303b0" integrity sha512-ceK3qLg/Baj7J8mK9bIxqw9AavrF+LXqwYEreBdY/a4Sj8YV4mIvhqea/6E7VTCNNGvKT2sJ/TTJjtfQ597lTA== @@ -6600,7 +6693,7 @@ prettier@^1.18.2: resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb" integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew== -prettier@^2.3.2: +prettier@^2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.4.1.tgz#671e11c89c14a4cfc876ce564106c4a6726c9f5c" integrity sha512-9fbDAXSBcc6Bs1mZrDYb3XKzDLm4EXXL9sC1LqKP5rZkT6KRr/rf9amVUcODVXgguK/isJz0d0hP72WeaKWsvA== @@ -6925,6 +7018,11 @@ repeat-string@^1.6.1: resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= +requires-port@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" + integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= + resolve-from@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" @@ -7062,10 +7160,10 @@ sass-loader@10.1.1, sass-loader@~10.1.1: schema-utils "^3.0.0" semver "^7.3.2" -sass@^1.43.3: - version "1.43.3" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.43.3.tgz#aa16a69131b84f0cd23189a242571e8905f1ce43" - integrity sha512-BJnLngqWpMeS65UvlYYEuCb3/fLxDxhHtOB/gWPxs6NKrslTxGt3ZxwIvOe/0Jm4tWwM/+tIpE3wj4dLEhPDeQ== +sass@^1.43.4: + version "1.43.4" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.43.4.tgz#68c7d6a1b004bef49af0d9caf750e9b252105d1f" + integrity sha512-/ptG7KE9lxpGSYiXn7Ar+lKOv37xfWsZRtFYal2QHNigyVQDx685VFT/h7ejVr+R8w7H4tmUgtulsKl5YpveOg== dependencies: chokidar ">=3.0.0 <4.0.0"