feat: login component, icon svg symbols, project structure
This commit is contained in:
@@ -12,7 +12,7 @@ import { ApolloClient } from 'apollo-client'
|
||||
import { HttpLink } from 'apollo-link-http'
|
||||
import { InMemoryCache } from 'apollo-cache-inmemory'
|
||||
import store from './store'
|
||||
import icons from '../svg/nc-icons.svg'
|
||||
import icons from '../svg/icons.svg'
|
||||
|
||||
// ====================================
|
||||
// Load Modules
|
||||
@@ -47,6 +47,7 @@ import modalDiscardPageComponent from './components/modal-discard-page.vue'
|
||||
import modalMovePageComponent from './components/modal-move-page.vue'
|
||||
import modalProfile2faComponent from './components/modal-profile-2fa.vue'
|
||||
import modalUpgradeSystemComponent from './components/modal-upgrade-system.vue'
|
||||
import navigatorComponent from './components/navigator.vue'
|
||||
import pageLoaderComponent from './components/page-loader.vue'
|
||||
import searchComponent from './components/search.vue'
|
||||
import toggleComponent from './components/toggle.vue'
|
||||
@@ -127,6 +128,7 @@ Vue.component('modalDiscardPage', modalDiscardPageComponent)
|
||||
Vue.component('modalMovePage', modalMovePageComponent)
|
||||
Vue.component('modalProfile2fa', modalProfile2faComponent)
|
||||
Vue.component('modalUpgradeSystem', modalUpgradeSystemComponent)
|
||||
Vue.component('navigator', navigatorComponent)
|
||||
Vue.component('pageLoader', pageLoaderComponent)
|
||||
Vue.component('search', searchComponent)
|
||||
Vue.component('setup', setupComponent)
|
||||
|
37
client/js/compatibility.js
Normal file
37
client/js/compatibility.js
Normal file
@@ -0,0 +1,37 @@
|
||||
// =======================================
|
||||
// Intl polyfill
|
||||
// =======================================
|
||||
// Requirement: Safari 9 and below
|
||||
|
||||
if (!global.Intl) {
|
||||
require('intl')
|
||||
require('intl/locale-data/jsonp/en')
|
||||
require('intl/locale-data/jsonp/fr')
|
||||
}
|
||||
|
||||
// =======================================
|
||||
// Promise polyfill
|
||||
// =======================================
|
||||
// Requirement: IE 11 and below
|
||||
|
||||
if (!window.Promise) {
|
||||
window.Promise = require('bluebird')
|
||||
}
|
||||
|
||||
// =======================================
|
||||
// Array.from polyfill
|
||||
// =======================================
|
||||
// Requirement: IE 11 and below
|
||||
|
||||
if (!Array.from) {
|
||||
require('./polyfills/array-from')
|
||||
}
|
||||
|
||||
// =======================================
|
||||
// Fetch polyfill
|
||||
// =======================================
|
||||
// Requirement: Safari 9 and below, IE 11 and below
|
||||
|
||||
if (!window.fetch) {
|
||||
require('whatwg-fetch')
|
||||
}
|
@@ -1,23 +1,18 @@
|
||||
<template lang="pug">
|
||||
.login(:class='{ "is-error": error }')
|
||||
.login-container(:class='{ "is-expanded": strategies.length > 1 }')
|
||||
.login-error(v-if='error')
|
||||
strong
|
||||
i.icon-warning-outline
|
||||
| {{ error.title }}
|
||||
span {{ error.message }}
|
||||
.login-providers(v-show='strategies.length > 1')
|
||||
button(v-for='strategy in strategies', :class='{ "is-active": strategy.key === selectedStrategy }', @click='selectStrategy(strategy.key, strategy.useForm)', :title='strategy.title')
|
||||
em(v-html='strategy.icon')
|
||||
span {{ strategy.title }}
|
||||
.login-providers-fill
|
||||
.login-frame
|
||||
h1 {{ siteTitle }}
|
||||
h2 {{ $t('auth:loginrequired') }}
|
||||
form(method='post', action='/login')
|
||||
input#login-user(type='text', name='email', :placeholder='$t("auth:fields.emailuser")')
|
||||
input#login-pass(type='password', name='password', :placeholder='$t("auth:fields.password")')
|
||||
button.button.is-light-blue.is-fullwidth(type='submit')
|
||||
span {{ $t('auth:actions.login') }}
|
||||
input(type='text', name='email', :placeholder='$t("auth:fields.emailuser")')
|
||||
input(type='password', name='password', :placeholder='$t("auth:fields.password")')
|
||||
button.button.is-orange.is-fullwidth(@click='login')
|
||||
span {{ $t('auth:actions.login') }}
|
||||
.login-copyright
|
||||
span {{ $t('footer.poweredby') }}
|
||||
a(href='https://wiki.js.org', rel='external', title='Wiki.js') Wiki.js
|
||||
@@ -62,9 +57,17 @@ export default {
|
||||
}).catch(err => {
|
||||
console.error(err)
|
||||
})
|
||||
},
|
||||
login() {
|
||||
this.$store.dispatch('alert', {
|
||||
style: 'error',
|
||||
icon: 'gg-warning',
|
||||
msg: 'Email or password is invalid'
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$store.commit('navigator/subtitleStatic', 'Login')
|
||||
this.refreshStrategies()
|
||||
}
|
||||
}
|
||||
|
69
client/js/components/navigator.vue
Normal file
69
client/js/components/navigator.vue
Normal file
@@ -0,0 +1,69 @@
|
||||
<template lang="pug">
|
||||
.navigator
|
||||
.navigator-bar
|
||||
.navigator-fab
|
||||
.navigator-fab-button(@click='toggleMainMenu')
|
||||
svg.icons.is-24(role='img')
|
||||
title Navigation
|
||||
use(xlink:href='#gg-apps-grid')
|
||||
.navigator-title
|
||||
h1 {{ siteTitle }}
|
||||
.navigator-subtitle(:class='subtitleClass')
|
||||
|
||||
svg.icons.is-24(role='img', v-if='subtitleIcon')
|
||||
title {{subtitleText}}
|
||||
use(:xlink:href='subtitleIconClass')
|
||||
h2 {{subtitleText}}
|
||||
.navigator-action
|
||||
.navigator-action-item
|
||||
svg.icons.is-32(role='img')
|
||||
title User
|
||||
use(xlink:href='#nc-user-circle')
|
||||
.navigator-row
|
||||
.navigator-nav
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/* global CONSTANTS, graphQL, siteConfig */
|
||||
|
||||
import { mapState } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'navigator',
|
||||
data() {
|
||||
return { }
|
||||
},
|
||||
computed: {
|
||||
...mapState('navigator', [
|
||||
'subtitleShown',
|
||||
'subtitleStyle',
|
||||
'subtitleText',
|
||||
'subtitleIcon'
|
||||
]),
|
||||
siteTitle() {
|
||||
return siteConfig.title
|
||||
},
|
||||
subtitleClass() {
|
||||
return {
|
||||
'is-active': this.subtitleShown,
|
||||
'is-error': this.subtitleStyle === 'error',
|
||||
'is-warning': this.subtitleStyle === 'warning',
|
||||
'is-success': this.subtitleStyle === 'success',
|
||||
'is-info': this.subtitleStyle === 'info'
|
||||
}
|
||||
},
|
||||
subtitleIconClass() { return '#' + this.subtitleIcon }
|
||||
},
|
||||
methods: {
|
||||
toggleMainMenu() {
|
||||
this.$store.dispatch('navigator/alert', {
|
||||
style: 'success',
|
||||
icon: 'nc-check-simple',
|
||||
msg: 'Changes were saved successfully!'
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
}
|
||||
}
|
||||
</script>
|
78
client/js/polyfills/array-from.js
Normal file
78
client/js/polyfills/array-from.js
Normal file
@@ -0,0 +1,78 @@
|
||||
// Production steps of ECMA-262, Edition 6, 22.1.2.1
|
||||
if (!Array.from) {
|
||||
Array.from = (function () {
|
||||
var toStr = Object.prototype.toString
|
||||
var isCallable = function (fn) {
|
||||
return typeof fn === 'function' || toStr.call(fn) === '[object Function]'
|
||||
}
|
||||
var toInteger = function (value) {
|
||||
var number = Number(value)
|
||||
if (isNaN(number)) { return 0 }
|
||||
if (number === 0 || !isFinite(number)) { return number }
|
||||
return (number > 0 ? 1 : -1) * Math.floor(Math.abs(number))
|
||||
}
|
||||
var maxSafeInteger = Math.pow(2, 53) - 1
|
||||
var toLength = function (value) {
|
||||
var len = toInteger(value)
|
||||
return Math.min(Math.max(len, 0), maxSafeInteger)
|
||||
}
|
||||
|
||||
// The length property of the from method is 1.
|
||||
return function from (arrayLike/*, mapFn, thisArg */) {
|
||||
// 1. Let C be the this value.
|
||||
var C = this
|
||||
|
||||
// 2. Let items be ToObject(arrayLike).
|
||||
var items = Object(arrayLike)
|
||||
|
||||
// 3. ReturnIfAbrupt(items).
|
||||
if (arrayLike == null) {
|
||||
throw new TypeError('Array.from requires an array-like object - not null or undefined')
|
||||
}
|
||||
|
||||
// 4. If mapfn is undefined, then let mapping be false.
|
||||
var mapFn = arguments.length > 1 ? arguments[1] : void undefined
|
||||
var T
|
||||
if (typeof mapFn !== 'undefined') {
|
||||
// 5. else
|
||||
// 5. a If IsCallable(mapfn) is false, throw a TypeError exception.
|
||||
if (!isCallable(mapFn)) {
|
||||
throw new TypeError('Array.from: when provided, the second argument must be a function')
|
||||
}
|
||||
|
||||
// 5. b. If thisArg was supplied, let T be thisArg; else let T be undefined.
|
||||
if (arguments.length > 2) {
|
||||
T = arguments[2]
|
||||
}
|
||||
}
|
||||
|
||||
// 10. Let lenValue be Get(items, "length").
|
||||
// 11. Let len be ToLength(lenValue).
|
||||
var len = toLength(items.length)
|
||||
|
||||
// 13. If IsConstructor(C) is true, then
|
||||
// 13. a. Let A be the result of calling the [[Construct]] internal method
|
||||
// of C with an argument list containing the single item len.
|
||||
// 14. a. Else, Let A be ArrayCreate(len).
|
||||
var A = isCallable(C) ? Object(new C(len)) : new Array(len)
|
||||
|
||||
// 16. Let k be 0.
|
||||
var k = 0
|
||||
// 17. Repeat, while k < len… (also steps a - h)
|
||||
var kValue
|
||||
while (k < len) {
|
||||
kValue = items[k]
|
||||
if (mapFn) {
|
||||
A[k] = typeof T === 'undefined' ? mapFn(kValue, k) : mapFn.call(T, kValue, k)
|
||||
} else {
|
||||
A[k] = kValue
|
||||
}
|
||||
k += 1
|
||||
}
|
||||
// 18. Let putStatus be Put(A, "length", len, true).
|
||||
A.length = len
|
||||
// 20. Return A.
|
||||
return A
|
||||
}
|
||||
}())
|
||||
}
|
@@ -1,7 +1,6 @@
|
||||
import Vue from 'vue'
|
||||
import Vuex from 'vuex'
|
||||
|
||||
import alert from './modules/alert'
|
||||
import anchor from './modules/anchor'
|
||||
import editor from './modules/editor'
|
||||
import editorCodeblock from './modules/editor-codeblock'
|
||||
@@ -15,6 +14,7 @@ import modalDiscardPage from './modules/modal-discard-page'
|
||||
import modalMovePage from './modules/modal-move-page'
|
||||
import modalProfile2fa from './modules/modal-profile-2fa'
|
||||
import modalUpgradeSystem from './modules/modal-upgrade-system'
|
||||
import navigator from './modules/navigator'
|
||||
import pageLoader from './modules/page-loader'
|
||||
|
||||
Vue.use(Vuex)
|
||||
@@ -27,12 +27,12 @@ export default new Vuex.Store({
|
||||
loadingChange: (state, loadingState) => { state.loading = loadingState }
|
||||
},
|
||||
actions: {
|
||||
alert({ dispatch }, opts) { dispatch('navigator/alert', opts) },
|
||||
startLoading({ commit }) { commit('loadingChange', true) },
|
||||
stopLoading({ commit }) { commit('loadingChange', false) }
|
||||
},
|
||||
getters: {},
|
||||
modules: {
|
||||
alert,
|
||||
anchor,
|
||||
editor,
|
||||
editorCodeblock,
|
||||
@@ -46,6 +46,7 @@ export default new Vuex.Store({
|
||||
modalMovePage,
|
||||
modalProfile2fa,
|
||||
modalUpgradeSystem,
|
||||
navigator,
|
||||
pageLoader
|
||||
}
|
||||
})
|
||||
|
@@ -1,32 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
import debounce from 'lodash/debounce'
|
||||
|
||||
export default {
|
||||
state: {
|
||||
shown: false,
|
||||
style: 'green',
|
||||
icon: 'check',
|
||||
msg: ''
|
||||
},
|
||||
getters: {},
|
||||
mutations: {
|
||||
alertChange: (state, opts) => {
|
||||
state.shown = (opts.shown === true)
|
||||
state.style = opts.style || 'green'
|
||||
state.icon = opts.icon || 'check'
|
||||
state.msg = opts.msg || ''
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
alert({ commit, dispatch }, opts) {
|
||||
opts.shown = true
|
||||
commit('alertChange', opts)
|
||||
dispatch('alertDismiss')
|
||||
},
|
||||
alertDismiss: debounce(({ commit }) => {
|
||||
let opts = { shown: false }
|
||||
commit('alertChange', opts)
|
||||
}, 3000)
|
||||
}
|
||||
}
|
40
client/js/store/modules/navigator.js
Normal file
40
client/js/store/modules/navigator.js
Normal file
@@ -0,0 +1,40 @@
|
||||
import debounce from 'lodash/debounce'
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state: {
|
||||
subtitleShown: false,
|
||||
subtitleStyle: '',
|
||||
subtitleIcon: false,
|
||||
subtitleText: '',
|
||||
subtitleStatic: 'Welcome'
|
||||
},
|
||||
getters: {},
|
||||
mutations: {
|
||||
subtitleChange (state, opts) {
|
||||
state.subtitleShown = (opts.shown === true)
|
||||
state.subtitleStyle = opts.style || ''
|
||||
state.subtitleIcon = opts.icon || false
|
||||
state.subtitleText = opts.msg || ''
|
||||
},
|
||||
subtitleStatic (state, text) {
|
||||
state.subtitleText = text
|
||||
state.subtitleStatic = text
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
alert ({ commit, dispatch }, opts) {
|
||||
opts.shown = true
|
||||
commit('subtitleChange', opts)
|
||||
dispatch('alertDismiss')
|
||||
},
|
||||
alertDismiss: debounce(({ commit, state }) => {
|
||||
let opts = {
|
||||
shown: false,
|
||||
style: state.subtitleStyle,
|
||||
msg: state.subtitleStatic
|
||||
}
|
||||
commit('subtitleChange', opts)
|
||||
}, 5000)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user