├── .eslintrc.js ├── .github └── FUNDING.yml ├── .gitignore ├── .prettierrc.js ├── LICENSE ├── README.md ├── docs ├── .vuepress │ ├── config.js │ └── public │ │ ├── favicon.ico │ │ ├── logo.svg │ │ └── preview1.png ├── README.md ├── demo │ └── simple │ │ └── README.md ├── vueber-fire │ ├── demo │ │ └── README.md │ ├── getting-started │ │ └── README.md │ ├── introduction │ │ └── README.md │ ├── props │ │ └── README.md │ └── usage │ │ └── README.md └── vueber │ ├── demo │ └── README.md │ ├── getting-started │ └── README.md │ ├── introduction │ └── README.md │ ├── props │ └── README.md │ └── usage │ └── README.md ├── package.json ├── packages ├── README.md ├── vueber-demo │ ├── README.md │ ├── assets │ │ └── user2.jpeg │ ├── babel.config.js │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ └── index.html │ └── src │ │ ├── App.vue │ │ ├── initialData.js │ │ └── main.js ├── vueber-fire-nuxt-demo │ ├── .nuxt │ │ ├── App.js │ │ ├── client.js │ │ ├── components │ │ │ ├── nuxt-build-indicator.vue │ │ │ ├── nuxt-child.js │ │ │ ├── nuxt-error.vue │ │ │ ├── nuxt-link.client.js │ │ │ ├── nuxt-link.server.js │ │ │ ├── nuxt-loading.vue │ │ │ └── nuxt.js │ │ ├── empty.js │ │ ├── firebase-module │ │ │ └── main.js │ │ ├── index.js │ │ ├── loading.html │ │ ├── middleware.js │ │ ├── router.js │ │ ├── router.scrollBehavior.js │ │ ├── server.js │ │ ├── utils.js │ │ └── views │ │ │ ├── app.template.html │ │ │ └── error.html │ ├── README.md │ ├── layouts │ │ └── default.vue │ ├── nuxt.config.js │ ├── package.json │ ├── pages │ │ └── index.vue │ └── plugins │ │ └── vuefire.js ├── vueber-fire │ ├── README.md │ ├── package-lock.json │ ├── package.json │ └── src │ │ ├── components │ │ ├── main.vue │ │ └── unreadConversations.vue │ │ ├── helpers │ │ └── index.js │ │ └── index.js └── vueber │ ├── README.md │ ├── package.json │ ├── postcss.config.js │ └── src │ ├── assets │ ├── demoData │ │ ├── getDemoData.js │ │ ├── user1.jpeg │ │ ├── user2.jpeg │ │ ├── user3.jpeg │ │ ├── user4.jpeg │ │ └── user5.jpeg │ └── noAvatar_xs.png │ ├── components │ ├── _elements │ │ ├── avatar.vue │ │ ├── btn.vue │ │ ├── icon.vue │ │ ├── textField.vue │ │ └── toolbarWrapper.vue │ ├── headerBar │ │ └── index.vue │ ├── index.vue │ ├── inputFooter │ │ ├── emojiPicker.vue │ │ └── index.vue │ ├── leftSidebar │ │ ├── conversationTile.vue │ │ └── index.vue │ ├── loginView │ │ └── index.vue │ ├── messagesView │ │ ├── index.vue │ │ └── message │ │ │ ├── index.vue │ │ │ └── isReadFlag.vue │ ├── noSelectionView │ │ └── index.vue │ └── rightSidebar │ │ ├── index.vue │ │ ├── mobileHeader.vue │ │ └── userActions.vue │ └── index.js └── yarn.lock /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | node: true 5 | }, 6 | parserOptions: { 7 | parser: 'babel-eslint' 8 | }, 9 | extends: [ 10 | 'plugin:vue/recommended', 11 | 'prettier', 12 | 'prettier/vue', 13 | 'plugin:prettier/recommended', 14 | 'eslint:recommended' 15 | ], 16 | plugins: ['prettier'], 17 | rules: { 18 | 'no-console': 'off', 19 | 'no-unused-vars': 'warn', 20 | 'object-shorthand': 'warn', 21 | 'vue/max-attributes-per-line': [ 22 | 2, 23 | { 24 | singleline: 20, 25 | multiline: { 26 | max: 1, 27 | allowFirstLine: false 28 | } 29 | } 30 | ] 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # Enables "Sponsor" button on project 2 | 3 | github: lupas 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | dist 4 | build 5 | 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | # Log files 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | 15 | # Editor directories and files 16 | .idea 17 | .vscode 18 | *.suo 19 | *.ntvs* 20 | *.njsproj 21 | *.sln 22 | *.sw? 23 | 24 | .nuxt 25 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | singleQuote: true, 3 | arrowParens: 'always', 4 | semi: false 5 | } 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019-present Pascal Luther 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 💬 Vueber 2 | 3 |

4 | 5 | > Messenger & Chat framework for Vue & Nuxt apps. 💬 6 | 7 | 📖 [**Read Full Documentation**]() (coming soon) 8 | 9 | # Information 10 | 11 | This package is being developed and in alpha phase. 12 | 13 | Therefore, there is no documentation available yet. 14 | 15 | Do not use it in any app, it will change A LOT in the next few days, weeks and months. 16 | 17 | # Preview 18 | 19 | Currently looks something like this: 20 | 21 | ![previewImage](https://vueber.netlify.com/preview1.png 'Preview Image') 22 | -------------------------------------------------------------------------------- /docs/.vuepress/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | title: 'vueber', 3 | description: 'Messenger & Chat framework for Vue.js.', 4 | themeConfig: { 5 | heroImage: '/logo.svg', 6 | logo: '/logo.svg', 7 | repo: 'lupas/vueber', 8 | sidebar: [ 9 | { 10 | title: '💬 vueber', 11 | sidebarDepth: 3, 12 | children: [ 13 | '/vueber/introduction/', 14 | '/vueber/getting-started/', 15 | '/vueber/props/', 16 | '/vueber/usage/', 17 | '/vueber/demo/' 18 | ] 19 | }, 20 | { 21 | title: '🔥 vueber-fire', 22 | sidebarDepth: 3, 23 | children: [ 24 | '/vueber-fire/introduction/', 25 | '/vueber-fire/getting-started/', 26 | '/vueber-fire/props/', 27 | '/vueber-fire/usage/', 28 | '/vueber-fire/demo/' 29 | ] 30 | }, 31 | { 32 | title: 'Demo', 33 | sidebarDepth: 1, 34 | children: [ 35 | '/demo/simple/' 36 | ] 37 | } 38 | ], 39 | sidebarDepth: 2, 40 | nav: [ 41 | { text: 'Home', link: '/' }, 42 | { text: 'Guide', link: '/vueber/introduction/' }, 43 | { text: 'Sponsor', link: 'https://github.com/sponsors/lupas' } 44 | ] 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /docs/.vuepress/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lupas/vueber/fa1f193b87eaeadddf29fcc3619c88f1f6a4887f/docs/.vuepress/public/favicon.ico -------------------------------------------------------------------------------- /docs/.vuepress/public/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 10 | 15 | 17 | 19 | 20 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /docs/.vuepress/public/preview1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lupas/vueber/fa1f193b87eaeadddf29fcc3619c88f1f6a4887f/docs/.vuepress/public/preview1.png -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | home: true 3 | heroImage: /logo.svg 4 | heroText: Vueber Chat 5 | tagline: Messenger & Chat framework for Vue.js. 6 | actionText: Get Started → 7 | actionLink: /vueber/introduction/ 8 | features: 9 | - title: Easy Setup 10 | details: Set up a 1-on-1 chat feature in your application without much effort. 11 | - title: Reactive & Real Time 12 | details: Our chat framework is fully reactive and will give your users a smooth chat experience. 13 | - title: Addons 14 | details: Various addons that make your life easier, like popup message windows (coming soon) or Firebase integration. 15 | footer: MIT Licensed | Copyright © 2019-present Pascal Luther 16 | --- 17 | -------------------------------------------------------------------------------- /docs/demo/simple/README.md: -------------------------------------------------------------------------------- 1 | # Simple Demo 2 | 3 | -------------------------------------------------------------------------------- /docs/vueber-fire/demo/README.md: -------------------------------------------------------------------------------- 1 | # Demo 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/vueber-fire/getting-started/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | ## Requirements 4 | 5 | Make sure you are using the newest Version of Vue.js installed in your project. 6 | 7 | If using Nuxt, add vueber to build.transpile in nuxt.config.js: 8 | 9 | ```js 10 | build: { 11 | transpile: [/^vueber/], 12 | } 13 | ``` 14 | 15 | ## Install 16 | 17 | Install Vueber and Vueber-Fire via NPM or Yarn. 18 | 19 | ```js 20 | yarn add vueber # OR npm i vueber 21 | yarn add vueber-fire # OR npm i vueber-fire 22 | 23 | // for now (will be fixed) 24 | yarn add @nuxtjs/firebase 25 | yarn add anchorme 26 | yarn add emoji-mart-vue 27 | yarn add firewings 28 | 29 | yarn add firebase 30 | yarn add @nuxtjs/firebase 31 | 32 | // If you run into core-js issues: 33 | yarn add core-js@2 34 | ``` 35 | 36 | ## Install More 37 | 38 | ### Install and configure Vuefire 39 | 40 | ```js 41 | // For Nuxt 42 | plugins: ['@/plugins/vuefire'], 43 | 44 | // Plugin 45 | import Vue from 'vue' 46 | import { firestorePlugin } from 'vuefire' 47 | 48 | Vue.use(firestorePlugin) 49 | ``` 50 | 51 | ### After first run - create indexes 52 | 53 | ``` 54 | The query requires an index. You can create it here: URL 55 | ``` 56 | -------------------------------------------------------------------------------- /docs/vueber-fire/introduction/README.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | Vueber-fire combines Firebases (Firestore) with the vueber chat framework. 4 | 5 | -------------------------------------------------------------------------------- /docs/vueber-fire/props/README.md: -------------------------------------------------------------------------------- 1 | # Props 2 | 3 | -------------------------------------------------------------------------------- /docs/vueber-fire/usage/README.md: -------------------------------------------------------------------------------- 1 | # Usage 2 | 3 | Import and add the VueberFire component wherever you want: 4 | 5 | ```html 6 | 11 | ``` 12 | 13 | ```js 14 | 27 | ``` 28 | 29 | The minimum props required are: 30 | 31 | - currentUser 32 | - ... 33 | -------------------------------------------------------------------------------- /docs/vueber/demo/README.md: -------------------------------------------------------------------------------- 1 | # Demo 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/vueber/getting-started/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | ## Requirements 4 | 5 | Make sure you have the newest Version of Vue.js installed. 6 | 7 | ## Install 8 | 9 | Install Nuxt-Fire via NPM or Yarn. 10 | 11 | ```bash 12 | yarn add vueber # OR npm i vueber 13 | ``` -------------------------------------------------------------------------------- /docs/vueber/introduction/README.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | Vueber is a Chat & Messenger framework for Vue.js that let's you easily implement a __1-on-1 chat__ feature (soon also group chats) to your application or website. 4 | 5 | While the main module does not contain any database logic, additional modules like `vueber-fire` help you easily implement a chat feature with your database without much effort. 6 | 7 | ::: warning 8 | We just started and are currently in alpha phase - so be patient. The framework is already working but has various bugs and might change a lot in future updates. Keep that in mind when using this framework at this point. -------------------------------------------------------------------------------- /docs/vueber/props/README.md: -------------------------------------------------------------------------------- 1 | # Props 2 | 3 | ## current-user 4 | 5 | Sets the avatar path, 6 | 7 | ```js 8 | currentUser: { 9 | id: '123', 10 | 11 | } 12 | ``` 13 | 14 | -------------------------------------------------------------------------------- /docs/vueber/usage/README.md: -------------------------------------------------------------------------------- 1 | # Usage 2 | 3 | Add the vueber component anywhere in your application like so: 4 | 5 | ```vue 6 | 21 | 22 | 29 | ``` 30 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "workspaces": [ 4 | "packages/*" 5 | ], 6 | "author": "Pascal Luther", 7 | "repository": "https://github.com/lupas/vueber", 8 | "homepage": "https://github.com/lupas", 9 | "keywords": [ 10 | "vue, vuejs, chat, vueber, messaging, inbox" 11 | ], 12 | "scripts": { 13 | "demo": "cd packages/vueber-fire-nuxt-demo && npm run dev", 14 | "demo-basic": "cd packages/vueber-demo && yarn dev", 15 | "docs:dev": "vuepress dev docs", 16 | "docs:build": "vuepress build docs", 17 | "npm:publish": "cd packages/vueber && npm publish", 18 | "npm:publishAlpha": "cd packages/vueber && npm publish --tag alpha" 19 | }, 20 | "devDependencies": { 21 | "@vue/eslint-config-prettier": "^6.0.0", 22 | "babel-eslint": "^10.0.1", 23 | "eslint": "^6.7.2", 24 | "eslint-config-prettier": "^6.5.0", 25 | "eslint-plugin-prettier": "^3.0.1", 26 | "eslint-plugin-vue": "^6.0.1", 27 | "prettier": "^2.0.4", 28 | "vuepress": "^1.2.0" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/README.md: -------------------------------------------------------------------------------- 1 |

These are the packages for the vueber monorepo.

2 | 3 | ## Packages 4 | 5 | - **vueber** - main source code for vueber 6 | - **vueber-demo** - demo project source code for vueber 7 | - **vueber-firemo** - main source code for vueber-fire 8 | -------------------------------------------------------------------------------- /packages/vueber-demo/README.md: -------------------------------------------------------------------------------- 1 | # simple-demo 2 | 3 | tbd -------------------------------------------------------------------------------- /packages/vueber-demo/assets/user2.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lupas/vueber/fa1f193b87eaeadddf29fcc3619c88f1f6a4887f/packages/vueber-demo/assets/user2.jpeg -------------------------------------------------------------------------------- /packages/vueber-demo/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['@vue/cli-plugin-babel/preset'], 3 | } 4 | -------------------------------------------------------------------------------- /packages/vueber-demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vueber-demo", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "dev": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "vue": "^2.6.10", 12 | "vueber": "0.0.1-alpha.0" 13 | }, 14 | "devDependencies": { 15 | "@vue/cli-plugin-babel": "^4.0.0", 16 | "@vue/cli-plugin-eslint": "^4.0.0", 17 | "@vue/cli-service": "^4.0.0", 18 | "babel-eslint": "^10.0.3", 19 | "eslint": "^6.8.0", 20 | "eslint-plugin-vue": "^6.2.1", 21 | "vue-template-compiler": "^2.6.10" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/vueber-demo/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lupas/vueber/fa1f193b87eaeadddf29fcc3619c88f1f6a4887f/packages/vueber-demo/public/favicon.ico -------------------------------------------------------------------------------- /packages/vueber-demo/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | vueber-simple-demo 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /packages/vueber-demo/src/App.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 83 | 84 | 105 | -------------------------------------------------------------------------------- /packages/vueber-demo/src/initialData.js: -------------------------------------------------------------------------------- 1 | export function getInitialConversations() { 2 | return [ 3 | { 4 | lastMessage: { 5 | hasPendingNotification: true, 6 | isRead: false, 7 | message: "Hello, I'm User 1", 8 | senderId: 'user1', 9 | senderName: 'User1', 10 | sentDate: '2019-11-21T08:50:57.796Z', 11 | _ownMessage: false, 12 | }, 13 | noOfMessages: 13, 14 | participants: { 15 | user1: { 16 | avatarPath: require('./../assets/user2.jpeg'), 17 | chatDisabled: false, 18 | id: 'user1', 19 | username: 'User1', 20 | }, 21 | }, 22 | participantsArray: ['user0', 'user1'], 23 | id: 'conversation1', 24 | path: 'conversations/conversation1', 25 | _chatpartner: { 26 | avatarPath: null, 27 | chatDisabled: false, 28 | id: 'user1', 29 | username: 'User1', 30 | }, 31 | }, 32 | { 33 | lastMessage: { 34 | hasPendingNotification: false, 35 | isRead: true, 36 | message: "Hey there's user 2.", 37 | senderId: 'user2', 38 | senderName: 'User2', 39 | sentDate: '2019-11-20T08:50:57.796Z', 40 | _ownMessage: false, 41 | }, 42 | noOfMessages: 3, 43 | participants: { 44 | user2: { 45 | avatarPath: require('./../assets/user2.jpeg'), 46 | chatDisabled: false, 47 | id: 'user2', 48 | username: 'User2', 49 | }, 50 | }, 51 | participantsArray: ['user2', 'user0'], 52 | id: 'conversation2', 53 | path: 'conversations/conversation2', 54 | _chatpartner: { 55 | avatarPath: null, 56 | chatDisabled: false, 57 | id: 'user2', 58 | username: 'User2', 59 | }, 60 | }, 61 | ] 62 | } 63 | 64 | export function getInitialUsers() { 65 | return [ 66 | { 67 | id: 'user1', 68 | avatar: require('./../assets/user2.jpeg'), 69 | username: 'User1', 70 | }, 71 | { 72 | id: 'user2', 73 | avatar: require('./../assets/user2.jpeg'), 74 | username: 'User2', 75 | }, 76 | { 77 | id: 'user3', 78 | avatar: require('./../assets/user2.jpeg'), 79 | username: 'User3', 80 | }, 81 | ] 82 | } 83 | 84 | export function getInitialMessages() { 85 | return [ 86 | { 87 | hasPendingNotification: true, 88 | isRead: true, 89 | message: 'Hallo, dies ist ein Test', 90 | senderId: 'user2', 91 | senderName: 'User2', 92 | sentDate: '2019-01-07T10:36:38.296Z', 93 | id: 'message1', 94 | conversationId: 'conversation1', 95 | }, 96 | { 97 | hasPendingNotification: true, 98 | isRead: true, 99 | message: 'Testchat', 100 | senderId: 'user2', 101 | senderName: 'User2', 102 | sentDate: '2019-01-07T10:36:38.296Z', 103 | id: 'message2', 104 | conversationId: 'conversation2', 105 | }, 106 | ] 107 | } 108 | -------------------------------------------------------------------------------- /packages/vueber-demo/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | 4 | Vue.config.productionTip = false 5 | 6 | new Vue({ 7 | render: (h) => h(App), 8 | }).$mount('#app') 9 | -------------------------------------------------------------------------------- /packages/vueber-fire-nuxt-demo/.nuxt/App.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | import { 4 | getMatchedComponentsInstances, 5 | promisify, 6 | globalHandleError 7 | } from './utils' 8 | 9 | import NuxtLoading from './components/nuxt-loading.vue' 10 | import NuxtBuildIndicator from './components/nuxt-build-indicator' 11 | 12 | import _6f6c098b from '../layouts/default.vue' 13 | 14 | const layouts = { "_default": _6f6c098b } 15 | 16 | export default { 17 | head: {"title":"vueber-nuxt-fire-demo","meta":[{"charset":"utf-8"},{"name":"viewport","content":"width=device-width, initial-scale=1"},{"hid":"description","name":"description","content":"My astounding Nuxt.js project"}],"link":[{"rel":"icon","type":"image\u002Fx-icon","href":"\u002Ffavicon.ico"}],"style":[],"script":[]}, 18 | 19 | render (h, props) { 20 | const loadingEl = h('NuxtLoading', { ref: 'loading' }) 21 | 22 | const layoutEl = h(this.layout || 'nuxt') 23 | const templateEl = h('div', { 24 | domProps: { 25 | id: '__layout' 26 | }, 27 | key: this.layoutName 28 | }, [layoutEl]) 29 | 30 | const transitionEl = h('transition', { 31 | props: { 32 | name: 'layout', 33 | mode: 'out-in' 34 | }, 35 | on: { 36 | beforeEnter (el) { 37 | // Ensure to trigger scroll event after calling scrollBehavior 38 | window.$nuxt.$nextTick(() => { 39 | window.$nuxt.$emit('triggerScroll') 40 | }) 41 | } 42 | } 43 | }, [templateEl]) 44 | 45 | return h('div', { 46 | domProps: { 47 | id: '__nuxt' 48 | } 49 | }, [ 50 | loadingEl, 51 | h(NuxtBuildIndicator), 52 | transitionEl 53 | ]) 54 | }, 55 | 56 | data: () => ({ 57 | isOnline: true, 58 | 59 | layout: null, 60 | layoutName: '' 61 | }), 62 | 63 | beforeCreate () { 64 | Vue.util.defineReactive(this, 'nuxt', this.$options.nuxt) 65 | }, 66 | created () { 67 | // Add this.$nuxt in child instances 68 | Vue.prototype.$nuxt = this 69 | // add to window so we can listen when ready 70 | if (process.client) { 71 | window.$nuxt = this 72 | 73 | this.refreshOnlineStatus() 74 | // Setup the listeners 75 | window.addEventListener('online', this.refreshOnlineStatus) 76 | window.addEventListener('offline', this.refreshOnlineStatus) 77 | } 78 | // Add $nuxt.error() 79 | this.error = this.nuxt.error 80 | // Add $nuxt.context 81 | this.context = this.$options.context 82 | }, 83 | 84 | mounted () { 85 | this.$loading = this.$refs.loading 86 | }, 87 | watch: { 88 | 'nuxt.err': 'errorChanged' 89 | }, 90 | 91 | computed: { 92 | isOffline () { 93 | return !this.isOnline 94 | } 95 | }, 96 | 97 | methods: { 98 | refreshOnlineStatus () { 99 | if (process.client) { 100 | if (typeof window.navigator.onLine === 'undefined') { 101 | // If the browser doesn't support connection status reports 102 | // assume that we are online because most apps' only react 103 | // when they now that the connection has been interrupted 104 | this.isOnline = true 105 | } else { 106 | this.isOnline = window.navigator.onLine 107 | } 108 | } 109 | }, 110 | 111 | async refresh () { 112 | const pages = getMatchedComponentsInstances(this.$route) 113 | 114 | if (!pages.length) { 115 | return 116 | } 117 | this.$loading.start() 118 | 119 | const promises = pages.map((page) => { 120 | const p = [] 121 | 122 | if (page.$options.fetch) { 123 | p.push(promisify(page.$options.fetch, this.context)) 124 | } 125 | 126 | if (page.$options.asyncData) { 127 | p.push( 128 | promisify(page.$options.asyncData, this.context) 129 | .then((newData) => { 130 | for (const key in newData) { 131 | Vue.set(page.$data, key, newData[key]) 132 | } 133 | }) 134 | ) 135 | } 136 | 137 | return Promise.all(p) 138 | }) 139 | try { 140 | await Promise.all(promises) 141 | } catch (error) { 142 | this.$loading.fail() 143 | globalHandleError(error) 144 | this.error(error) 145 | } 146 | this.$loading.finish() 147 | }, 148 | 149 | errorChanged () { 150 | if (this.nuxt.err && this.$loading) { 151 | if (this.$loading.fail) { 152 | this.$loading.fail() 153 | } 154 | if (this.$loading.finish) { 155 | this.$loading.finish() 156 | } 157 | } 158 | }, 159 | 160 | setLayout (layout) { 161 | if(layout && typeof layout !== 'string') { 162 | throw new Error('[nuxt] Avoid using non-string value as layout property.') 163 | } 164 | 165 | if (!layout || !layouts['_' + layout]) { 166 | layout = 'default' 167 | } 168 | this.layoutName = layout 169 | this.layout = layouts['_' + layout] 170 | return this.layout 171 | }, 172 | loadLayout (layout) { 173 | if (!layout || !layouts['_' + layout]) { 174 | layout = 'default' 175 | } 176 | return Promise.resolve(layouts['_' + layout]) 177 | } 178 | }, 179 | 180 | components: { 181 | NuxtLoading 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /packages/vueber-fire-nuxt-demo/.nuxt/client.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import fetch from 'unfetch' 3 | import middleware from './middleware.js' 4 | import { 5 | applyAsyncData, 6 | promisify, 7 | middlewareSeries, 8 | sanitizeComponent, 9 | resolveRouteComponents, 10 | getMatchedComponents, 11 | getMatchedComponentsInstances, 12 | flatMapComponents, 13 | setContext, 14 | getLocation, 15 | compile, 16 | getQueryDiff, 17 | globalHandleError 18 | } from './utils.js' 19 | import { createApp, NuxtError } from './index.js' 20 | import NuxtLink from './components/nuxt-link.client.js' // should be included after ./index.js 21 | 22 | // Component: 23 | Vue.component(NuxtLink.name, NuxtLink) 24 | Vue.component('NLink', NuxtLink) 25 | 26 | if (!global.fetch) { global.fetch = fetch } 27 | 28 | // Global shared references 29 | let _lastPaths = [] 30 | let app 31 | let router 32 | 33 | // Try to rehydrate SSR data from window 34 | const NUXT = window.__NUXT__ || {} 35 | 36 | Object.assign(Vue.config, {"silent":false,"performance":true}) 37 | 38 | const logs = NUXT.logs || [] 39 | if (logs.length > 0) { 40 | const ssrLogSyle = 'background: #2E495E;border-radius: 0.5em;color: white;font-weight: bold;padding: 2px 0.5em;' 41 | console.group && console.group ('%cNuxt SSR', ssrLogSyle) 42 | logs.forEach(logObj => (console[logObj.type] || console.log)(...logObj.args)) 43 | delete NUXT.logs 44 | console.groupEnd && console.groupEnd() 45 | } 46 | 47 | // Setup global Vue error handler 48 | if (!Vue.config.$nuxt) { 49 | const defaultErrorHandler = Vue.config.errorHandler 50 | Vue.config.errorHandler = (err, vm, info, ...rest) => { 51 | // Call other handler if exist 52 | let handled = null 53 | if (typeof defaultErrorHandler === 'function') { 54 | handled = defaultErrorHandler(err, vm, info, ...rest) 55 | } 56 | if (handled === true) { 57 | return handled 58 | } 59 | 60 | if (vm && vm.$root) { 61 | const nuxtApp = Object.keys(Vue.config.$nuxt) 62 | .find(nuxtInstance => vm.$root[nuxtInstance]) 63 | 64 | // Show Nuxt Error Page 65 | if (nuxtApp && vm.$root[nuxtApp].error && info !== 'render function') { 66 | vm.$root[nuxtApp].error(err) 67 | } 68 | } 69 | 70 | if (typeof defaultErrorHandler === 'function') { 71 | return handled 72 | } 73 | 74 | // Log to console 75 | if (process.env.NODE_ENV !== 'production') { 76 | console.error(err) 77 | } else { 78 | console.error(err.message || err) 79 | } 80 | } 81 | Vue.config.$nuxt = {} 82 | } 83 | Vue.config.$nuxt.$nuxt = true 84 | 85 | const errorHandler = Vue.config.errorHandler || console.error 86 | 87 | // Create and mount App 88 | createApp().then(mountApp).catch(errorHandler) 89 | 90 | function componentOption (component, key, ...args) { 91 | if (!component || !component.options || !component.options[key]) { 92 | return {} 93 | } 94 | const option = component.options[key] 95 | if (typeof option === 'function') { 96 | return option(...args) 97 | } 98 | return option 99 | } 100 | 101 | function mapTransitions (Components, to, from) { 102 | const componentTransitions = (component) => { 103 | const transition = componentOption(component, 'transition', to, from) || {} 104 | return (typeof transition === 'string' ? { name: transition } : transition) 105 | } 106 | 107 | return Components.map((Component) => { 108 | // Clone original object to prevent overrides 109 | const transitions = Object.assign({}, componentTransitions(Component)) 110 | 111 | // Combine transitions & prefer `leave` transitions of 'from' route 112 | if (from && from.matched.length && from.matched[0].components.default) { 113 | const fromTransitions = componentTransitions(from.matched[0].components.default) 114 | Object.keys(fromTransitions) 115 | .filter(key => fromTransitions[key] && key.toLowerCase().includes('leave')) 116 | .forEach((key) => { transitions[key] = fromTransitions[key] }) 117 | } 118 | 119 | return transitions 120 | }) 121 | } 122 | 123 | async function loadAsyncComponents (to, from, next) { 124 | // Check if route path changed (this._pathChanged), only if the page is not an error (for validate()) 125 | this._pathChanged = Boolean(app.nuxt.err) || from.path !== to.path 126 | this._queryChanged = JSON.stringify(to.query) !== JSON.stringify(from.query) 127 | this._diffQuery = (this._queryChanged ? getQueryDiff(to.query, from.query) : []) 128 | 129 | if (this._pathChanged && this.$loading.start && !this.$loading.manual) { 130 | this.$loading.start() 131 | } 132 | 133 | try { 134 | if (!this._pathChanged && this._queryChanged) { 135 | const Components = await resolveRouteComponents( 136 | to, 137 | (Component, instance) => ({ Component, instance }) 138 | ) 139 | // Add a marker on each component that it needs to refresh or not 140 | const startLoader = Components.some(({ Component, instance }) => { 141 | const watchQuery = Component.options.watchQuery 142 | if (watchQuery === true) { 143 | return true 144 | } 145 | if (Array.isArray(watchQuery)) { 146 | return watchQuery.some(key => this._diffQuery[key]) 147 | } 148 | if (typeof watchQuery === 'function') { 149 | return watchQuery.apply(instance, [to.query, from.query]) 150 | } 151 | return false 152 | }) 153 | if (startLoader && this.$loading.start && !this.$loading.manual) { 154 | this.$loading.start() 155 | } 156 | } 157 | 158 | // Call next() 159 | next() 160 | } catch (error) { 161 | const err = error || {} 162 | const statusCode = err.statusCode || err.status || (err.response && err.response.status) || 500 163 | const message = err.message || '' 164 | 165 | // Handle chunk loading errors 166 | // This may be due to a new deployment or a network problem 167 | if (/^Loading( CSS)? chunk (\d)+ failed\./.test(message)) { 168 | window.location.reload(true /* skip cache */) 169 | return // prevent error page blinking for user 170 | } 171 | 172 | this.error({ statusCode, message }) 173 | this.$nuxt.$emit('routeChanged', to, from, err) 174 | next() 175 | } 176 | } 177 | 178 | function applySSRData (Component, ssrData) { 179 | if (NUXT.serverRendered && ssrData) { 180 | applyAsyncData(Component, ssrData) 181 | } 182 | 183 | Component._Ctor = Component 184 | return Component 185 | } 186 | 187 | // Get matched components 188 | function resolveComponents (router) { 189 | const path = getLocation(router.options.base, router.options.mode) 190 | 191 | return flatMapComponents(router.match(path), async (Component, _, match, key, index) => { 192 | // If component is not resolved yet, resolve it 193 | if (typeof Component === 'function' && !Component.options) { 194 | Component = await Component() 195 | } 196 | // Sanitize it and save it 197 | const _Component = applySSRData(sanitizeComponent(Component), NUXT.data ? NUXT.data[index] : null) 198 | match.components[key] = _Component 199 | return _Component 200 | }) 201 | } 202 | 203 | function callMiddleware (Components, context, layout) { 204 | let midd = [] 205 | let unknownMiddleware = false 206 | 207 | // If layout is undefined, only call global middleware 208 | if (typeof layout !== 'undefined') { 209 | midd = [] // Exclude global middleware if layout defined (already called before) 210 | layout = sanitizeComponent(layout) 211 | if (layout.options.middleware) { 212 | midd = midd.concat(layout.options.middleware) 213 | } 214 | Components.forEach((Component) => { 215 | if (Component.options.middleware) { 216 | midd = midd.concat(Component.options.middleware) 217 | } 218 | }) 219 | } 220 | 221 | midd = midd.map((name) => { 222 | if (typeof name === 'function') { 223 | return name 224 | } 225 | if (typeof middleware[name] !== 'function') { 226 | unknownMiddleware = true 227 | this.error({ statusCode: 500, message: 'Unknown middleware ' + name }) 228 | } 229 | return middleware[name] 230 | }) 231 | 232 | if (unknownMiddleware) { 233 | return 234 | } 235 | return middlewareSeries(midd, context) 236 | } 237 | 238 | async function render (to, from, next) { 239 | if (this._pathChanged === false && this._queryChanged === false) { 240 | return next() 241 | } 242 | // Handle first render on SPA mode 243 | if (to === from) { 244 | _lastPaths = [] 245 | } else { 246 | const fromMatches = [] 247 | _lastPaths = getMatchedComponents(from, fromMatches).map((Component, i) => { 248 | return compile(from.matched[fromMatches[i]].path)(from.params) 249 | }) 250 | } 251 | 252 | // nextCalled is true when redirected 253 | let nextCalled = false 254 | const _next = (path) => { 255 | if (from.path === path.path && this.$loading.finish) { 256 | this.$loading.finish() 257 | } 258 | 259 | if (from.path !== path.path && this.$loading.pause) { 260 | this.$loading.pause() 261 | } 262 | 263 | if (nextCalled) { 264 | return 265 | } 266 | 267 | nextCalled = true 268 | next(path) 269 | } 270 | 271 | // Update context 272 | await setContext(app, { 273 | route: to, 274 | from, 275 | next: _next.bind(this) 276 | }) 277 | this._dateLastError = app.nuxt.dateErr 278 | this._hadError = Boolean(app.nuxt.err) 279 | 280 | // Get route's matched components 281 | const matches = [] 282 | const Components = getMatchedComponents(to, matches) 283 | 284 | // If no Components matched, generate 404 285 | if (!Components.length) { 286 | // Default layout 287 | await callMiddleware.call(this, Components, app.context) 288 | if (nextCalled) { 289 | return 290 | } 291 | 292 | // Load layout for error page 293 | const errorLayout = (NuxtError.options || NuxtError).layout 294 | const layout = await this.loadLayout( 295 | typeof errorLayout === 'function' 296 | ? errorLayout.call(NuxtError, app.context) 297 | : errorLayout 298 | ) 299 | 300 | await callMiddleware.call(this, Components, app.context, layout) 301 | if (nextCalled) { 302 | return 303 | } 304 | 305 | // Show error page 306 | app.context.error({ statusCode: 404, message: 'This page could not be found' }) 307 | return next() 308 | } 309 | 310 | // Update ._data and other properties if hot reloaded 311 | Components.forEach((Component) => { 312 | if (Component._Ctor && Component._Ctor.options) { 313 | Component.options.asyncData = Component._Ctor.options.asyncData 314 | Component.options.fetch = Component._Ctor.options.fetch 315 | } 316 | }) 317 | 318 | // Apply transitions 319 | this.setTransitions(mapTransitions(Components, to, from)) 320 | 321 | try { 322 | // Call middleware 323 | await callMiddleware.call(this, Components, app.context) 324 | if (nextCalled) { 325 | return 326 | } 327 | if (app.context._errored) { 328 | return next() 329 | } 330 | 331 | // Set layout 332 | let layout = Components[0].options.layout 333 | if (typeof layout === 'function') { 334 | layout = layout(app.context) 335 | } 336 | layout = await this.loadLayout(layout) 337 | 338 | // Call middleware for layout 339 | await callMiddleware.call(this, Components, app.context, layout) 340 | if (nextCalled) { 341 | return 342 | } 343 | if (app.context._errored) { 344 | return next() 345 | } 346 | 347 | // Call .validate() 348 | let isValid = true 349 | try { 350 | for (const Component of Components) { 351 | if (typeof Component.options.validate !== 'function') { 352 | continue 353 | } 354 | 355 | isValid = await Component.options.validate(app.context) 356 | 357 | if (!isValid) { 358 | break 359 | } 360 | } 361 | } catch (validationError) { 362 | // ...If .validate() threw an error 363 | this.error({ 364 | statusCode: validationError.statusCode || '500', 365 | message: validationError.message 366 | }) 367 | return next() 368 | } 369 | 370 | // ...If .validate() returned false 371 | if (!isValid) { 372 | this.error({ statusCode: 404, message: 'This page could not be found' }) 373 | return next() 374 | } 375 | 376 | let instances 377 | // Call asyncData & fetch hooks on components matched by the route. 378 | await Promise.all(Components.map((Component, i) => { 379 | // Check if only children route changed 380 | Component._path = compile(to.matched[matches[i]].path)(to.params) 381 | Component._dataRefresh = false 382 | // Check if Component need to be refreshed (call asyncData & fetch) 383 | // Only if its slug has changed or is watch query changes 384 | if ((this._pathChanged && this._queryChanged) || Component._path !== _lastPaths[i]) { 385 | Component._dataRefresh = true 386 | } else if (!this._pathChanged && this._queryChanged) { 387 | const watchQuery = Component.options.watchQuery 388 | if (watchQuery === true) { 389 | Component._dataRefresh = true 390 | } else if (Array.isArray(watchQuery)) { 391 | Component._dataRefresh = watchQuery.some(key => this._diffQuery[key]) 392 | } else if (typeof watchQuery === 'function') { 393 | if (!instances) { 394 | instances = getMatchedComponentsInstances(to) 395 | } 396 | Component._dataRefresh = watchQuery.apply(instances[i], [to.query, from.query]) 397 | } 398 | } 399 | if (!this._hadError && this._isMounted && !Component._dataRefresh) { 400 | return 401 | } 402 | 403 | const promises = [] 404 | 405 | const hasAsyncData = ( 406 | Component.options.asyncData && 407 | typeof Component.options.asyncData === 'function' 408 | ) 409 | 410 | const hasFetch = Boolean(Component.options.fetch) 411 | 412 | const loadingIncrease = (hasAsyncData && hasFetch) ? 30 : 45 413 | 414 | // Call asyncData(context) 415 | if (hasAsyncData) { 416 | const promise = promisify(Component.options.asyncData, app.context) 417 | .then((asyncDataResult) => { 418 | applyAsyncData(Component, asyncDataResult) 419 | 420 | if (this.$loading.increase) { 421 | this.$loading.increase(loadingIncrease) 422 | } 423 | }) 424 | promises.push(promise) 425 | } 426 | 427 | // Check disabled page loading 428 | this.$loading.manual = Component.options.loading === false 429 | 430 | // Call fetch(context) 431 | if (hasFetch) { 432 | let p = Component.options.fetch(app.context) 433 | if (!p || (!(p instanceof Promise) && (typeof p.then !== 'function'))) { 434 | p = Promise.resolve(p) 435 | } 436 | p.then((fetchResult) => { 437 | if (this.$loading.increase) { 438 | this.$loading.increase(loadingIncrease) 439 | } 440 | }) 441 | promises.push(p) 442 | } 443 | 444 | return Promise.all(promises) 445 | })) 446 | 447 | // If not redirected 448 | if (!nextCalled) { 449 | if (this.$loading.finish && !this.$loading.manual) { 450 | this.$loading.finish() 451 | } 452 | 453 | next() 454 | } 455 | } catch (err) { 456 | const error = err || {} 457 | if (error.message === 'ERR_REDIRECT') { 458 | return this.$nuxt.$emit('routeChanged', to, from, error) 459 | } 460 | _lastPaths = [] 461 | 462 | globalHandleError(error) 463 | 464 | // Load error layout 465 | let layout = (NuxtError.options || NuxtError).layout 466 | if (typeof layout === 'function') { 467 | layout = layout(app.context) 468 | } 469 | await this.loadLayout(layout) 470 | 471 | this.error(error) 472 | this.$nuxt.$emit('routeChanged', to, from, error) 473 | next() 474 | } 475 | } 476 | 477 | // Fix components format in matched, it's due to code-splitting of vue-router 478 | function normalizeComponents (to, ___) { 479 | flatMapComponents(to, (Component, _, match, key) => { 480 | if (typeof Component === 'object' && !Component.options) { 481 | // Updated via vue-router resolveAsyncComponents() 482 | Component = Vue.extend(Component) 483 | Component._Ctor = Component 484 | match.components[key] = Component 485 | } 486 | return Component 487 | }) 488 | } 489 | 490 | function showNextPage (to) { 491 | // Hide error component if no error 492 | if (this._hadError && this._dateLastError === this.$options.nuxt.dateErr) { 493 | this.error() 494 | } 495 | 496 | // Set layout 497 | let layout = this.$options.nuxt.err 498 | ? (NuxtError.options || NuxtError).layout 499 | : to.matched[0].components.default.options.layout 500 | 501 | if (typeof layout === 'function') { 502 | layout = layout(app.context) 503 | } 504 | this.setLayout(layout) 505 | } 506 | 507 | // When navigating on a different route but the same component is used, Vue.js 508 | // Will not update the instance data, so we have to update $data ourselves 509 | function fixPrepatch (to, ___) { 510 | if (this._pathChanged === false && this._queryChanged === false) { 511 | return 512 | } 513 | 514 | const instances = getMatchedComponentsInstances(to) 515 | const Components = getMatchedComponents(to) 516 | 517 | Vue.nextTick(() => { 518 | instances.forEach((instance, i) => { 519 | if (!instance || instance._isDestroyed) { 520 | return 521 | } 522 | 523 | if ( 524 | instance.constructor._dataRefresh && 525 | Components[i] === instance.constructor && 526 | instance.$vnode.data.keepAlive !== true && 527 | typeof instance.constructor.options.data === 'function' 528 | ) { 529 | const newData = instance.constructor.options.data.call(instance) 530 | for (const key in newData) { 531 | Vue.set(instance.$data, key, newData[key]) 532 | } 533 | 534 | // Ensure to trigger scroll event after calling scrollBehavior 535 | window.$nuxt.$nextTick(() => { 536 | window.$nuxt.$emit('triggerScroll') 537 | }) 538 | } 539 | }) 540 | showNextPage.call(this, to) 541 | 542 | // Hot reloading 543 | setTimeout(() => hotReloadAPI(this), 100) 544 | }) 545 | } 546 | 547 | function nuxtReady (_app) { 548 | window.onNuxtReadyCbs.forEach((cb) => { 549 | if (typeof cb === 'function') { 550 | cb(_app) 551 | } 552 | }) 553 | // Special JSDOM 554 | if (typeof window._onNuxtLoaded === 'function') { 555 | window._onNuxtLoaded(_app) 556 | } 557 | // Add router hooks 558 | router.afterEach((to, from) => { 559 | // Wait for fixPrepatch + $data updates 560 | Vue.nextTick(() => _app.$nuxt.$emit('routeChanged', to, from)) 561 | }) 562 | } 563 | 564 | const noopData = () => { return {} } 565 | const noopFetch = () => {} 566 | 567 | // Special hot reload with asyncData(context) 568 | function getNuxtChildComponents ($parent, $components = []) { 569 | $parent.$children.forEach(($child) => { 570 | if ($child.$vnode && $child.$vnode.data.nuxtChild && !$components.find(c =>(c.$options.__file === $child.$options.__file))) { 571 | $components.push($child) 572 | } 573 | if ($child.$children && $child.$children.length) { 574 | getNuxtChildComponents($child, $components) 575 | } 576 | }) 577 | 578 | return $components 579 | } 580 | 581 | function hotReloadAPI(_app) { 582 | if (!module.hot) return 583 | 584 | let $components = getNuxtChildComponents(_app.$nuxt, []) 585 | 586 | $components.forEach(addHotReload.bind(_app)) 587 | } 588 | 589 | function addHotReload ($component, depth) { 590 | if ($component.$vnode.data._hasHotReload) return 591 | $component.$vnode.data._hasHotReload = true 592 | 593 | var _forceUpdate = $component.$forceUpdate.bind($component.$parent) 594 | 595 | $component.$vnode.context.$forceUpdate = async () => { 596 | let Components = getMatchedComponents(router.currentRoute) 597 | let Component = Components[depth] 598 | if (!Component) { 599 | return _forceUpdate() 600 | } 601 | if (typeof Component === 'object' && !Component.options) { 602 | // Updated via vue-router resolveAsyncComponents() 603 | Component = Vue.extend(Component) 604 | Component._Ctor = Component 605 | } 606 | this.error() 607 | let promises = [] 608 | const next = function (path) { 609 | this.$loading.finish && this.$loading.finish() 610 | router.push(path) 611 | } 612 | await setContext(app, { 613 | route: router.currentRoute, 614 | isHMR: true, 615 | next: next.bind(this) 616 | }) 617 | const context = app.context 618 | 619 | if (this.$loading.start && !this.$loading.manual) { 620 | this.$loading.start() 621 | } 622 | 623 | callMiddleware.call(this, Components, context) 624 | .then(() => { 625 | // If layout changed 626 | if (depth !== 0) { 627 | return 628 | } 629 | 630 | let layout = Component.options.layout || 'default' 631 | if (typeof layout === 'function') { 632 | layout = layout(context) 633 | } 634 | if (this.layoutName === layout) { 635 | return 636 | } 637 | let promise = this.loadLayout(layout) 638 | promise.then(() => { 639 | this.setLayout(layout) 640 | Vue.nextTick(() => hotReloadAPI(this)) 641 | }) 642 | return promise 643 | }) 644 | 645 | .then(() => { 646 | return callMiddleware.call(this, Components, context, this.layout) 647 | }) 648 | 649 | .then(() => { 650 | // Call asyncData(context) 651 | let pAsyncData = promisify(Component.options.asyncData || noopData, context) 652 | pAsyncData.then((asyncDataResult) => { 653 | applyAsyncData(Component, asyncDataResult) 654 | this.$loading.increase && this.$loading.increase(30) 655 | }) 656 | promises.push(pAsyncData) 657 | 658 | // Call fetch() 659 | Component.options.fetch = Component.options.fetch || noopFetch 660 | let pFetch = Component.options.fetch(context) 661 | if (!pFetch || (!(pFetch instanceof Promise) && (typeof pFetch.then !== 'function'))) { pFetch = Promise.resolve(pFetch) } 662 | pFetch.then(() => this.$loading.increase && this.$loading.increase(30)) 663 | promises.push(pFetch) 664 | 665 | return Promise.all(promises) 666 | }) 667 | .then(() => { 668 | this.$loading.finish && this.$loading.finish() 669 | _forceUpdate() 670 | setTimeout(() => hotReloadAPI(this), 100) 671 | }) 672 | } 673 | } 674 | 675 | async function mountApp (__app) { 676 | // Set global variables 677 | app = __app.app 678 | router = __app.router 679 | 680 | // Create Vue instance 681 | const _app = new Vue(app) 682 | 683 | // Load layout 684 | const layout = NUXT.layout || 'default' 685 | await _app.loadLayout(layout) 686 | _app.setLayout(layout) 687 | 688 | // Mounts Vue app to DOM element 689 | const mount = () => { 690 | _app.$mount('#__nuxt') 691 | 692 | // Add afterEach router hooks 693 | router.afterEach(normalizeComponents) 694 | router.afterEach(fixPrepatch.bind(_app)) 695 | 696 | // Listen for first Vue update 697 | Vue.nextTick(() => { 698 | // Call window.{{globals.readyCallback}} callbacks 699 | nuxtReady(_app) 700 | 701 | // Enable hot reloading 702 | hotReloadAPI(_app) 703 | }) 704 | } 705 | 706 | // Resolve route components 707 | const Components = await Promise.all(resolveComponents(router)) 708 | 709 | // Enable transitions 710 | _app.setTransitions = _app.$options.nuxt.setTransitions.bind(_app) 711 | if (Components.length) { 712 | _app.setTransitions(mapTransitions(Components, router.currentRoute)) 713 | _lastPaths = router.currentRoute.matched.map(route => compile(route.path)(router.currentRoute.params)) 714 | } 715 | 716 | // Initialize error handler 717 | _app.$loading = {} // To avoid error while _app.$nuxt does not exist 718 | if (NUXT.error) { 719 | _app.error(NUXT.error) 720 | } 721 | 722 | // Add beforeEach router hooks 723 | router.beforeEach(loadAsyncComponents.bind(_app)) 724 | router.beforeEach(render.bind(_app)) 725 | 726 | // If page already is server rendered 727 | if (NUXT.serverRendered) { 728 | mount() 729 | return 730 | } 731 | 732 | // First render on client-side 733 | const clientFirstMount = () => { 734 | normalizeComponents(router.currentRoute, router.currentRoute) 735 | showNextPage.call(_app, router.currentRoute) 736 | // Don't call fixPrepatch.call(_app, router.currentRoute, router.currentRoute) since it's first render 737 | mount() 738 | } 739 | 740 | render.call(_app, router.currentRoute, router.currentRoute, (path) => { 741 | // If not redirected 742 | if (!path) { 743 | clientFirstMount() 744 | return 745 | } 746 | 747 | // Add a one-time afterEach hook to 748 | // mount the app wait for redirect and route gets resolved 749 | const unregisterHook = router.afterEach((to, from) => { 750 | unregisterHook() 751 | clientFirstMount() 752 | }) 753 | 754 | // Push the path and let route to be resolved 755 | router.push(path, undefined, (err) => { 756 | if (err) { 757 | errorHandler(err) 758 | } 759 | }) 760 | }) 761 | } 762 | -------------------------------------------------------------------------------- /packages/vueber-fire-nuxt-demo/.nuxt/components/nuxt-build-indicator.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 112 | 113 | 143 | -------------------------------------------------------------------------------- /packages/vueber-fire-nuxt-demo/.nuxt/components/nuxt-child.js: -------------------------------------------------------------------------------- 1 | 2 | export default { 3 | name: 'NuxtChild', 4 | functional: true, 5 | props: { 6 | nuxtChildKey: { 7 | type: String, 8 | default: '' 9 | }, 10 | keepAlive: Boolean, 11 | keepAliveProps: { 12 | type: Object, 13 | default: undefined 14 | } 15 | }, 16 | render (h, { parent, data, props }) { 17 | data.nuxtChild = true 18 | const _parent = parent 19 | const transitions = parent.$nuxt.nuxt.transitions 20 | const defaultTransition = parent.$nuxt.nuxt.defaultTransition 21 | 22 | let depth = 0 23 | while (parent) { 24 | if (parent.$vnode && parent.$vnode.data.nuxtChild) { 25 | depth++ 26 | } 27 | parent = parent.$parent 28 | } 29 | data.nuxtChildDepth = depth 30 | const transition = transitions[depth] || defaultTransition 31 | const transitionProps = {} 32 | transitionsKeys.forEach((key) => { 33 | if (typeof transition[key] !== 'undefined') { 34 | transitionProps[key] = transition[key] 35 | } 36 | }) 37 | 38 | const listeners = {} 39 | listenersKeys.forEach((key) => { 40 | if (typeof transition[key] === 'function') { 41 | listeners[key] = transition[key].bind(_parent) 42 | } 43 | }) 44 | // Add triggerScroll event on beforeEnter (fix #1376) 45 | const beforeEnter = listeners.beforeEnter 46 | listeners.beforeEnter = (el) => { 47 | // Ensure to trigger scroll event after calling scrollBehavior 48 | window.$nuxt.$nextTick(() => { 49 | window.$nuxt.$emit('triggerScroll') 50 | }) 51 | if (beforeEnter) { 52 | return beforeEnter.call(_parent, el) 53 | } 54 | } 55 | 56 | // make sure that leave is called asynchronous (fix #5703) 57 | if (transition.css === false) { 58 | const leave = listeners.leave 59 | 60 | // only add leave listener when user didnt provide one 61 | // or when it misses the done argument 62 | if (!leave || leave.length < 2) { 63 | listeners.leave = (el, done) => { 64 | if (leave) { 65 | leave.call(_parent, el) 66 | } 67 | 68 | _parent.$nextTick(done) 69 | } 70 | } 71 | } 72 | 73 | let routerView = h('routerView', data) 74 | 75 | if (props.keepAlive) { 76 | routerView = h('keep-alive', { props: props.keepAliveProps }, [routerView]) 77 | } 78 | 79 | return h('transition', { 80 | props: transitionProps, 81 | on: listeners 82 | }, [routerView]) 83 | } 84 | } 85 | 86 | const transitionsKeys = [ 87 | 'name', 88 | 'mode', 89 | 'appear', 90 | 'css', 91 | 'type', 92 | 'duration', 93 | 'enterClass', 94 | 'leaveClass', 95 | 'appearClass', 96 | 'enterActiveClass', 97 | 'enterActiveClass', 98 | 'leaveActiveClass', 99 | 'appearActiveClass', 100 | 'enterToClass', 101 | 'leaveToClass', 102 | 'appearToClass' 103 | ] 104 | 105 | const listenersKeys = [ 106 | 'beforeEnter', 107 | 'enter', 108 | 'afterEnter', 109 | 'enterCancelled', 110 | 'beforeLeave', 111 | 'leave', 112 | 'afterLeave', 113 | 'leaveCancelled', 114 | 'beforeAppear', 115 | 'appear', 116 | 'afterAppear', 117 | 'appearCancelled' 118 | ] 119 | -------------------------------------------------------------------------------- /packages/vueber-fire-nuxt-demo/.nuxt/components/nuxt-error.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 52 | 53 | 98 | -------------------------------------------------------------------------------- /packages/vueber-fire-nuxt-demo/.nuxt/components/nuxt-link.client.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | const requestIdleCallback = window.requestIdleCallback || 4 | function (cb) { 5 | const start = Date.now() 6 | return setTimeout(function () { 7 | cb({ 8 | didTimeout: false, 9 | timeRemaining: () => Math.max(0, 50 - (Date.now() - start)) 10 | }) 11 | }, 1) 12 | } 13 | 14 | const cancelIdleCallback = window.cancelIdleCallback || function (id) { 15 | clearTimeout(id) 16 | } 17 | 18 | const observer = window.IntersectionObserver && new window.IntersectionObserver((entries) => { 19 | entries.forEach(({ intersectionRatio, target: link }) => { 20 | if (intersectionRatio <= 0) { 21 | return 22 | } 23 | link.__prefetch() 24 | }) 25 | }) 26 | 27 | export default { 28 | name: 'NuxtLink', 29 | extends: Vue.component('RouterLink'), 30 | props: { 31 | prefetch: { 32 | type: Boolean, 33 | default: true 34 | }, 35 | noPrefetch: { 36 | type: Boolean, 37 | default: false 38 | } 39 | }, 40 | mounted () { 41 | if (this.prefetch && !this.noPrefetch) { 42 | this.handleId = requestIdleCallback(this.observe, { timeout: 2e3 }) 43 | } 44 | }, 45 | beforeDestroy () { 46 | cancelIdleCallback(this.handleId) 47 | 48 | if (this.__observed) { 49 | observer.unobserve(this.$el) 50 | delete this.$el.__prefetch 51 | } 52 | }, 53 | methods: { 54 | observe () { 55 | // If no IntersectionObserver, avoid prefetching 56 | if (!observer) { 57 | return 58 | } 59 | // Add to observer 60 | if (this.shouldPrefetch()) { 61 | this.$el.__prefetch = this.prefetchLink.bind(this) 62 | observer.observe(this.$el) 63 | this.__observed = true 64 | } 65 | }, 66 | shouldPrefetch () { 67 | return this.getPrefetchComponents().length > 0 68 | }, 69 | canPrefetch () { 70 | const conn = navigator.connection 71 | const hasBadConnection = this.$nuxt.isOffline || (conn && ((conn.effectiveType || '').includes('2g') || conn.saveData)) 72 | 73 | return !hasBadConnection 74 | }, 75 | getPrefetchComponents () { 76 | const ref = this.$router.resolve(this.to, this.$route, this.append) 77 | const Components = ref.resolved.matched.map(r => r.components.default) 78 | 79 | return Components.filter(Component => typeof Component === 'function' && !Component.options && !Component.__prefetched) 80 | }, 81 | prefetchLink () { 82 | if (!this.canPrefetch()) { 83 | return 84 | } 85 | // Stop observing this link (in case of internet connection changes) 86 | observer.unobserve(this.$el) 87 | const Components = this.getPrefetchComponents() 88 | 89 | for (const Component of Components) { 90 | const componentOrPromise = Component() 91 | if (componentOrPromise instanceof Promise) { 92 | componentOrPromise.catch(() => {}) 93 | } 94 | Component.__prefetched = true 95 | } 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /packages/vueber-fire-nuxt-demo/.nuxt/components/nuxt-link.server.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | export default { 4 | name: 'NuxtLink', 5 | extends: Vue.component('RouterLink'), 6 | props: { 7 | prefetch: { 8 | type: Boolean, 9 | default: true 10 | }, 11 | noPrefetch: { 12 | type: Boolean, 13 | default: false 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/vueber-fire-nuxt-demo/.nuxt/components/nuxt-loading.vue: -------------------------------------------------------------------------------- 1 | 155 | 156 | 178 | -------------------------------------------------------------------------------- /packages/vueber-fire-nuxt-demo/.nuxt/components/nuxt.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import { compile } from '../utils' 3 | 4 | import NuxtError from './nuxt-error.vue' 5 | 6 | import NuxtChild from './nuxt-child' 7 | 8 | export default { 9 | name: 'Nuxt', 10 | components: { 11 | NuxtChild, 12 | NuxtError 13 | }, 14 | props: { 15 | nuxtChildKey: { 16 | type: String, 17 | default: undefined 18 | }, 19 | keepAlive: Boolean, 20 | keepAliveProps: { 21 | type: Object, 22 | default: undefined 23 | }, 24 | name: { 25 | type: String, 26 | default: 'default' 27 | } 28 | }, 29 | errorCaptured (error) { 30 | // if we receive and error while showing the NuxtError component 31 | // capture the error and force an immediate update so we re-render 32 | // without the NuxtError component 33 | if (this.displayingNuxtError) { 34 | this.errorFromNuxtError = error 35 | this.$forceUpdate() 36 | } 37 | }, 38 | computed: { 39 | routerViewKey () { 40 | // If nuxtChildKey prop is given or current route has children 41 | if (typeof this.nuxtChildKey !== 'undefined' || this.$route.matched.length > 1) { 42 | return this.nuxtChildKey || compile(this.$route.matched[0].path)(this.$route.params) 43 | } 44 | 45 | const [matchedRoute] = this.$route.matched 46 | 47 | if (!matchedRoute) { 48 | return this.$route.path 49 | } 50 | 51 | const Component = matchedRoute.components.default 52 | 53 | if (Component && Component.options) { 54 | const { options } = Component 55 | 56 | if (options.key) { 57 | return (typeof options.key === 'function' ? options.key(this.$route) : options.key) 58 | } 59 | } 60 | 61 | const strict = /\/$/.test(matchedRoute.path) 62 | return strict ? this.$route.path : this.$route.path.replace(/\/$/, '') 63 | } 64 | }, 65 | beforeCreate () { 66 | Vue.util.defineReactive(this, 'nuxt', this.$root.$options.nuxt) 67 | }, 68 | render (h) { 69 | // if there is no error 70 | if (!this.nuxt.err) { 71 | // Directly return nuxt child 72 | return h('NuxtChild', { 73 | key: this.routerViewKey, 74 | props: this.$props 75 | }) 76 | } 77 | 78 | // if an error occured within NuxtError show a simple 79 | // error message instead to prevent looping 80 | if (this.errorFromNuxtError) { 81 | this.$nextTick(() => (this.errorFromNuxtError = false)) 82 | 83 | return h('div', {}, [ 84 | h('h2', 'An error occured while showing the error page'), 85 | h('p', 'Unfortunately an error occured and while showing the error page another error occured'), 86 | h('p', `Error details: ${this.errorFromNuxtError.toString()}`), 87 | h('nuxt-link', { props: { to: '/' } }, 'Go back to home') 88 | ]) 89 | } 90 | 91 | // track if we are showing the NuxtError component 92 | this.displayingNuxtError = true 93 | this.$nextTick(() => (this.displayingNuxtError = false)) 94 | 95 | return h(NuxtError, { 96 | props: { 97 | error: this.nuxt.err 98 | } 99 | }) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /packages/vueber-fire-nuxt-demo/.nuxt/empty.js: -------------------------------------------------------------------------------- 1 | // This file is intentionally left empty for noop aliases 2 | -------------------------------------------------------------------------------- /packages/vueber-fire-nuxt-demo/.nuxt/firebase-module/main.js: -------------------------------------------------------------------------------- 1 | import firebase from 'firebase/app' 2 | 3 | /** --------------------------------------------------------------------------------------------- **/ 4 | /** -------------------------------------- END: Import Scripts ---------------------------------- **/ 5 | /** --------------------------------------------------------------------------------------------- **/ 6 | 7 | export default async (ctx, inject) => { 8 | const options = {"config":{"apiKey":"AIzaSyBPbKMVLeY1x_1sgikvuP5cyfUd6iFs0l0","authDomain":"vueber-fire-nuxt-demo.firebaseapp.com","databaseURL":"https:\u002F\u002Fvueber-fire-nuxt-demo.firebaseio.com","projectId":"vueber-fire-nuxt-demo","storageBucket":"vueber-fire-nuxt-demo.appspot.com","messagingSenderId":"499279429791","appId":"1:499279429791:web:e0ef9deb856d8ed2c1a2aa"},"services":{"firestore":true}} 9 | const firebaseConfig = options.config 10 | 11 | // Don't include when Firebase is already initialized 12 | if (!firebase.apps.length) { 13 | firebase.initializeApp(firebaseConfig) 14 | } 15 | 16 | /** --------------------------------------------------------------------------------------------- **/ 17 | /** -------------------------------------- FIREBASE AUTH ---------------------------------------- **/ 18 | /** --------------------------------------------------------------------------------------------- **/ 19 | 20 | /** --------------------------------------------------------------------------------------------- **/ 21 | /** -------------------------------------- FIREBASE REALTIME DB --------------------------------- **/ 22 | /** --------------------------------------------------------------------------------------------- **/ 23 | 24 | /** --------------------------------------------------------------------------------------------- **/ 25 | /** ---------------------------------------- FIREBASE FIRESTORE --------------------------------- **/ 26 | /** --------------------------------------------------------------------------------------------- **/ 27 | 28 | await import(/* webpackChunkName: 'firebase-firestore' */'firebase/firestore') 29 | 30 | const fireStore = firebase.firestore() 31 | const fireStoreObj = firebase.firestore 32 | inject('fireStore', fireStore) 33 | inject('fireStoreObj', fireStoreObj) 34 | 35 | const enablePersistence = options.services.firestore.enablePersistence 36 | if (enablePersistence && process.client) { 37 | try { 38 | await fireStore.enablePersistence(( 39 | typeof enablePersistence === 'object' 40 | ? enablePersistence 41 | : {} 42 | )) 43 | } catch (err) { 44 | if (err.code == 'failed-precondition') { 45 | console.info("Firestore Persistence not enabled. Multiple tabs open, persistence can only be enabled in one tab at a a time.") 46 | } else if (err.code == 'unimplemented') { 47 | console.info("Firestore Persistence not enabled. The current browser does not support all of the features required to enable persistence.") 48 | } 49 | } 50 | } 51 | 52 | /** --------------------------------------------------------------------------------------------- **/ 53 | /** ------------------------------------------ FIREBASE STORAGE --------------------------------- **/ 54 | /** --------------------------------------------------------------------------------------------- **/ 55 | 56 | /** --------------------------------------------------------------------------------------------- **/ 57 | /** ---------------------------------------- FIREBASE FUNCTIONS --------------------------------- **/ 58 | /** --------------------------------------------------------------------------------------------- **/ 59 | 60 | /** --------------------------------------------------------------------------------------------- **/ 61 | /** ---------------------------------------- FIREBASE MESSAGING --------------------------------- **/ 62 | /** --------------------------------------------------------------------------------------------- **/ 63 | 64 | /** --------------------------------------------------------------------------------------------- **/ 65 | /** -------------------------------------- FIREBASE REALTIME DB --------------------------------- **/ 66 | /** --------------------------------------------------------------------------------------------- **/ 67 | 68 | // Firebase Performance can only be initiated on client side 69 | 70 | /** --------------------------------------------------------------------------------------------- **/ 71 | /** ---------------------------------------- FIREBASE ANALYTICS --------------------------------- **/ 72 | /** --------------------------------------------------------------------------------------------- **/ 73 | 74 | // Firebase Analytics can only be initiated on the client side 75 | 76 | /** --------------------------------------------------------------------------------------------- **/ 77 | /** --------------------------------- FIREBASE REMOTE CONFIG DB --------------------------------- **/ 78 | /** --------------------------------------------------------------------------------------------- **/ 79 | } 80 | -------------------------------------------------------------------------------- /packages/vueber-fire-nuxt-demo/.nuxt/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Meta from 'vue-meta' 3 | import ClientOnly from 'vue-client-only' 4 | import NoSsr from 'vue-no-ssr' 5 | import { createRouter } from './router.js' 6 | import NuxtChild from './components/nuxt-child.js' 7 | import NuxtError from './components/nuxt-error.vue' 8 | import Nuxt from './components/nuxt.js' 9 | import App from './App.js' 10 | import { setContext, getLocation, getRouteData, normalizeError } from './utils' 11 | 12 | /* Plugins */ 13 | 14 | import nuxt_plugin_main_fd5aff36 from 'nuxt_plugin_main_fd5aff36' // Source: ./firebase-module/main.js (mode: 'all') 15 | import nuxt_plugin_vuefire_e59e0770 from 'nuxt_plugin_vuefire_e59e0770' // Source: ../plugins/vuefire (mode: 'all') 16 | 17 | // Component: 18 | Vue.component(ClientOnly.name, ClientOnly) 19 | 20 | // TODO: Remove in Nuxt 3: 21 | Vue.component(NoSsr.name, { 22 | ...NoSsr, 23 | render (h, ctx) { 24 | if (process.client && !NoSsr._warned) { 25 | NoSsr._warned = true 26 | 27 | console.warn(' has been deprecated and will be removed in Nuxt 3, please use instead') 28 | } 29 | return NoSsr.render(h, ctx) 30 | } 31 | }) 32 | 33 | // Component: 34 | Vue.component(NuxtChild.name, NuxtChild) 35 | Vue.component('NChild', NuxtChild) 36 | 37 | // Component NuxtLink is imported in server.js or client.js 38 | 39 | // Component: 40 | Vue.component(Nuxt.name, Nuxt) 41 | 42 | Vue.use(Meta, {"keyName":"head","attribute":"data-n-head","ssrAttribute":"data-n-head-ssr","tagIDKeyName":"hid"}) 43 | 44 | const defaultTransition = {"name":"page","mode":"out-in","appear":false,"appearClass":"appear","appearActiveClass":"appear-active","appearToClass":"appear-to"} 45 | 46 | async function createApp (ssrContext) { 47 | const router = await createRouter(ssrContext) 48 | 49 | // Create Root instance 50 | 51 | // here we inject the router and store to all child components, 52 | // making them available everywhere as `this.$router` and `this.$store`. 53 | const app = { 54 | router, 55 | nuxt: { 56 | defaultTransition, 57 | transitions: [defaultTransition], 58 | setTransitions (transitions) { 59 | if (!Array.isArray(transitions)) { 60 | transitions = [transitions] 61 | } 62 | transitions = transitions.map((transition) => { 63 | if (!transition) { 64 | transition = defaultTransition 65 | } else if (typeof transition === 'string') { 66 | transition = Object.assign({}, defaultTransition, { name: transition }) 67 | } else { 68 | transition = Object.assign({}, defaultTransition, transition) 69 | } 70 | return transition 71 | }) 72 | this.$options.nuxt.transitions = transitions 73 | return transitions 74 | }, 75 | 76 | err: null, 77 | dateErr: null, 78 | error (err) { 79 | err = err || null 80 | app.context._errored = Boolean(err) 81 | err = err ? normalizeError(err) : null 82 | const nuxt = this.nuxt || this.$options.nuxt 83 | nuxt.dateErr = Date.now() 84 | nuxt.err = err 85 | // Used in src/server.js 86 | if (ssrContext) { 87 | ssrContext.nuxt.error = err 88 | } 89 | return err 90 | } 91 | }, 92 | ...App 93 | } 94 | 95 | const next = ssrContext ? ssrContext.next : location => app.router.push(location) 96 | // Resolve route 97 | let route 98 | if (ssrContext) { 99 | route = router.resolve(ssrContext.url).route 100 | } else { 101 | const path = getLocation(router.options.base, router.options.mode) 102 | route = router.resolve(path).route 103 | } 104 | 105 | // Set context to app.context 106 | await setContext(app, { 107 | route, 108 | next, 109 | error: app.nuxt.error.bind(app), 110 | payload: ssrContext ? ssrContext.payload : undefined, 111 | req: ssrContext ? ssrContext.req : undefined, 112 | res: ssrContext ? ssrContext.res : undefined, 113 | beforeRenderFns: ssrContext ? ssrContext.beforeRenderFns : undefined, 114 | ssrContext 115 | }) 116 | 117 | const inject = function (key, value) { 118 | if (!key) { 119 | throw new Error('inject(key, value) has no key provided') 120 | } 121 | if (value === undefined) { 122 | throw new Error('inject(key, value) has no value provided') 123 | } 124 | 125 | key = '$' + key 126 | // Add into app 127 | app[key] = value 128 | 129 | // Check if plugin not already installed 130 | const installKey = '__nuxt_' + key + '_installed__' 131 | if (Vue[installKey]) { 132 | return 133 | } 134 | Vue[installKey] = true 135 | // Call Vue.use() to install the plugin into vm 136 | Vue.use(() => { 137 | if (!Object.prototype.hasOwnProperty.call(Vue, key)) { 138 | Object.defineProperty(Vue.prototype, key, { 139 | get () { 140 | return this.$root.$options[key] 141 | } 142 | }) 143 | } 144 | }) 145 | } 146 | 147 | // Plugin execution 148 | 149 | if (typeof nuxt_plugin_main_fd5aff36 === 'function') { 150 | await nuxt_plugin_main_fd5aff36(app.context, inject) 151 | } 152 | 153 | if (typeof nuxt_plugin_vuefire_e59e0770 === 'function') { 154 | await nuxt_plugin_vuefire_e59e0770(app.context, inject) 155 | } 156 | 157 | // If server-side, wait for async component to be resolved first 158 | if (process.server && ssrContext && ssrContext.url) { 159 | await new Promise((resolve, reject) => { 160 | router.push(ssrContext.url, resolve, () => { 161 | // navigated to a different route in router guard 162 | const unregister = router.afterEach(async (to, from, next) => { 163 | ssrContext.url = to.fullPath 164 | app.context.route = await getRouteData(to) 165 | app.context.params = to.params || {} 166 | app.context.query = to.query || {} 167 | unregister() 168 | resolve() 169 | }) 170 | }) 171 | }) 172 | } 173 | 174 | return { 175 | app, 176 | router 177 | } 178 | } 179 | 180 | export { createApp, NuxtError } 181 | -------------------------------------------------------------------------------- /packages/vueber-fire-nuxt-demo/.nuxt/loading.html: -------------------------------------------------------------------------------- 1 | 97 | 98 | 106 | 107 |
Loading...
108 | 109 | 110 | -------------------------------------------------------------------------------- /packages/vueber-fire-nuxt-demo/.nuxt/middleware.js: -------------------------------------------------------------------------------- 1 | const middleware = {} 2 | 3 | export default middleware 4 | -------------------------------------------------------------------------------- /packages/vueber-fire-nuxt-demo/.nuxt/router.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | import { interopDefault } from './utils' 4 | import scrollBehavior from './router.scrollBehavior.js' 5 | 6 | const _32f3a421 = () => interopDefault(import('../pages/index.vue' /* webpackChunkName: "pages/index" */)) 7 | 8 | // TODO: remove in Nuxt 3 9 | const emptyFn = () => {} 10 | const originalPush = Router.prototype.push 11 | Router.prototype.push = function push (location, onComplete = emptyFn, onAbort) { 12 | return originalPush.call(this, location, onComplete, onAbort) 13 | } 14 | 15 | Vue.use(Router) 16 | 17 | export const routerOptions = { 18 | mode: 'history', 19 | base: decodeURI('/'), 20 | linkActiveClass: 'nuxt-link-active', 21 | linkExactActiveClass: 'nuxt-link-exact-active', 22 | scrollBehavior, 23 | 24 | routes: [{ 25 | path: "/", 26 | component: _32f3a421, 27 | name: "index" 28 | }], 29 | 30 | fallback: false 31 | } 32 | 33 | export function createRouter () { 34 | return new Router(routerOptions) 35 | } 36 | -------------------------------------------------------------------------------- /packages/vueber-fire-nuxt-demo/.nuxt/router.scrollBehavior.js: -------------------------------------------------------------------------------- 1 | import { getMatchedComponents } from './utils' 2 | 3 | if (process.client) { 4 | if ('scrollRestoration' in window.history) { 5 | window.history.scrollRestoration = 'manual' 6 | 7 | // reset scrollRestoration to auto when leaving page, allowing page reload 8 | // and back-navigation from other pages to use the browser to restore the 9 | // scrolling position. 10 | window.addEventListener('beforeunload', () => { 11 | window.history.scrollRestoration = 'auto' 12 | }) 13 | 14 | // Setting scrollRestoration to manual again when returning to this page. 15 | window.addEventListener('load', () => { 16 | window.history.scrollRestoration = 'manual' 17 | }) 18 | } 19 | } 20 | 21 | export default function (to, from, savedPosition) { 22 | // if the returned position is falsy or an empty object, 23 | // will retain current scroll position. 24 | let position = false 25 | 26 | // if no children detected and scrollToTop is not explicitly disabled 27 | const Pages = getMatchedComponents(to) 28 | if ( 29 | Pages.length < 2 && 30 | Pages.every(Page => Page.options.scrollToTop !== false) 31 | ) { 32 | // scroll to the top of the page 33 | position = { x: 0, y: 0 } 34 | } else if (Pages.some(Page => Page.options.scrollToTop)) { 35 | // if one of the children has scrollToTop option set to true 36 | position = { x: 0, y: 0 } 37 | } 38 | 39 | // savedPosition is only available for popstate navigations (back button) 40 | if (savedPosition) { 41 | position = savedPosition 42 | } 43 | 44 | const nuxt = window.$nuxt 45 | 46 | // triggerScroll is only fired when a new component is loaded 47 | if (to.path === from.path && to.hash !== from.hash) { 48 | nuxt.$nextTick(() => nuxt.$emit('triggerScroll')) 49 | } 50 | 51 | return new Promise((resolve) => { 52 | // wait for the out transition to complete (if necessary) 53 | nuxt.$once('triggerScroll', () => { 54 | // coords will be used if no selector is provided, 55 | // or if the selector didn't match any element. 56 | if (to.hash) { 57 | let hash = to.hash 58 | // CSS.escape() is not supported with IE and Edge. 59 | if (typeof window.CSS !== 'undefined' && typeof window.CSS.escape !== 'undefined') { 60 | hash = '#' + window.CSS.escape(hash.substr(1)) 61 | } 62 | try { 63 | if (document.querySelector(hash)) { 64 | // scroll to anchor by returning the selector 65 | position = { selector: hash } 66 | } 67 | } catch (e) { 68 | console.warn('Failed to save scroll position. Please add CSS.escape() polyfill (https://github.com/mathiasbynens/CSS.escape).') 69 | } 70 | } 71 | resolve(position) 72 | }) 73 | }) 74 | } 75 | -------------------------------------------------------------------------------- /packages/vueber-fire-nuxt-demo/.nuxt/server.js: -------------------------------------------------------------------------------- 1 | import { stringify } from 'querystring' 2 | import Vue from 'vue' 3 | import fetch from 'node-fetch' 4 | import middleware from './middleware.js' 5 | import { 6 | applyAsyncData, 7 | middlewareSeries, 8 | sanitizeComponent, 9 | getMatchedComponents, 10 | promisify 11 | } from './utils.js' 12 | import { createApp, NuxtError } from './index.js' 13 | import NuxtLink from './components/nuxt-link.server.js' // should be included after ./index.js 14 | 15 | // Component: 16 | Vue.component(NuxtLink.name, NuxtLink) 17 | Vue.component('NLink', NuxtLink) 18 | 19 | if (!global.fetch) { global.fetch = fetch } 20 | 21 | const noopApp = () => new Vue({ render: h => h('div') }) 22 | 23 | function urlJoin () { 24 | return Array.prototype.slice.call(arguments).join('/').replace(/\/+/g, '/') 25 | } 26 | 27 | const createNext = ssrContext => (opts) => { 28 | ssrContext.redirected = opts 29 | // If nuxt generate 30 | if (!ssrContext.res) { 31 | ssrContext.nuxt.serverRendered = false 32 | return 33 | } 34 | opts.query = stringify(opts.query) 35 | opts.path = opts.path + (opts.query ? '?' + opts.query : '') 36 | const routerBase = '/' 37 | if (!opts.path.startsWith('http') && (routerBase !== '/' && !opts.path.startsWith(routerBase))) { 38 | opts.path = urlJoin(routerBase, opts.path) 39 | } 40 | // Avoid loop redirect 41 | if (opts.path === ssrContext.url) { 42 | ssrContext.redirected = false 43 | return 44 | } 45 | ssrContext.res.writeHead(opts.status, { 46 | Location: opts.path 47 | }) 48 | ssrContext.res.end() 49 | } 50 | 51 | // This exported function will be called by `bundleRenderer`. 52 | // This is where we perform data-prefetching to determine the 53 | // state of our application before actually rendering it. 54 | // Since data fetching is async, this function is expected to 55 | // return a Promise that resolves to the app instance. 56 | export default async (ssrContext) => { 57 | // Create ssrContext.next for simulate next() of beforeEach() when wanted to redirect 58 | ssrContext.redirected = false 59 | ssrContext.next = createNext(ssrContext) 60 | // Used for beforeNuxtRender({ Components, nuxtState }) 61 | ssrContext.beforeRenderFns = [] 62 | // Nuxt object (window{{globals.context}}, defaults to window.__NUXT__) 63 | ssrContext.nuxt = { layout: 'default', data: [], error: null, serverRendered: true } 64 | // Create the app definition and the instance (created for each request) 65 | const { app, router } = await createApp(ssrContext) 66 | const _app = new Vue(app) 67 | 68 | // Add meta infos (used in renderer.js) 69 | ssrContext.meta = _app.$meta() 70 | 71 | // Keep asyncData for each matched component in ssrContext (used in app/utils.js via this.$ssrContext) 72 | ssrContext.asyncData = {} 73 | 74 | const beforeRender = async () => { 75 | // Call beforeNuxtRender() methods 76 | await Promise.all(ssrContext.beforeRenderFns.map(fn => promisify(fn, { Components, nuxtState: ssrContext.nuxt }))) 77 | } 78 | 79 | const renderErrorPage = async () => { 80 | // Load layout for error page 81 | const layout = (NuxtError.options || NuxtError).layout 82 | const errLayout = typeof layout === 'function' ? layout.call(NuxtError, app.context) : layout 83 | ssrContext.nuxt.layout = errLayout || 'default' 84 | await _app.loadLayout(errLayout) 85 | _app.setLayout(errLayout) 86 | 87 | await beforeRender() 88 | return _app 89 | } 90 | const render404Page = () => { 91 | app.context.error({ statusCode: 404, path: ssrContext.url, message: 'This page could not be found' }) 92 | return renderErrorPage() 93 | } 94 | 95 | const s = Date.now() 96 | 97 | // Components are already resolved by setContext -> getRouteData (app/utils.js) 98 | const Components = getMatchedComponents(router.match(ssrContext.url)) 99 | 100 | /* 101 | ** Call global middleware (nuxt.config.js) 102 | */ 103 | let midd = [] 104 | midd = midd.map((name) => { 105 | if (typeof name === 'function') { 106 | return name 107 | } 108 | if (typeof middleware[name] !== 'function') { 109 | app.context.error({ statusCode: 500, message: 'Unknown middleware ' + name }) 110 | } 111 | return middleware[name] 112 | }) 113 | await middlewareSeries(midd, app.context) 114 | // ...If there is a redirect or an error, stop the process 115 | if (ssrContext.redirected) { 116 | return noopApp() 117 | } 118 | if (ssrContext.nuxt.error) { 119 | return renderErrorPage() 120 | } 121 | 122 | /* 123 | ** Set layout 124 | */ 125 | let layout = Components.length ? Components[0].options.layout : NuxtError.layout 126 | if (typeof layout === 'function') { 127 | layout = layout(app.context) 128 | } 129 | await _app.loadLayout(layout) 130 | if (ssrContext.nuxt.error) { 131 | return renderErrorPage() 132 | } 133 | layout = _app.setLayout(layout) 134 | ssrContext.nuxt.layout = _app.layoutName 135 | 136 | /* 137 | ** Call middleware (layout + pages) 138 | */ 139 | midd = [] 140 | 141 | layout = sanitizeComponent(layout) 142 | if (layout.options.middleware) { 143 | midd = midd.concat(layout.options.middleware) 144 | } 145 | 146 | Components.forEach((Component) => { 147 | if (Component.options.middleware) { 148 | midd = midd.concat(Component.options.middleware) 149 | } 150 | }) 151 | midd = midd.map((name) => { 152 | if (typeof name === 'function') { 153 | return name 154 | } 155 | if (typeof middleware[name] !== 'function') { 156 | app.context.error({ statusCode: 500, message: 'Unknown middleware ' + name }) 157 | } 158 | return middleware[name] 159 | }) 160 | await middlewareSeries(midd, app.context) 161 | // ...If there is a redirect or an error, stop the process 162 | if (ssrContext.redirected) { 163 | return noopApp() 164 | } 165 | if (ssrContext.nuxt.error) { 166 | return renderErrorPage() 167 | } 168 | 169 | /* 170 | ** Call .validate() 171 | */ 172 | let isValid = true 173 | try { 174 | for (const Component of Components) { 175 | if (typeof Component.options.validate !== 'function') { 176 | continue 177 | } 178 | 179 | isValid = await Component.options.validate(app.context) 180 | 181 | if (!isValid) { 182 | break 183 | } 184 | } 185 | } catch (validationError) { 186 | // ...If .validate() threw an error 187 | app.context.error({ 188 | statusCode: validationError.statusCode || '500', 189 | message: validationError.message 190 | }) 191 | return renderErrorPage() 192 | } 193 | 194 | // ...If .validate() returned false 195 | if (!isValid) { 196 | // Don't server-render the page in generate mode 197 | if (ssrContext._generate) { 198 | ssrContext.nuxt.serverRendered = false 199 | } 200 | // Render a 404 error page 201 | return render404Page() 202 | } 203 | 204 | // If no Components found, returns 404 205 | if (!Components.length) { 206 | return render404Page() 207 | } 208 | 209 | // Call asyncData & fetch hooks on components matched by the route. 210 | const asyncDatas = await Promise.all(Components.map((Component) => { 211 | const promises = [] 212 | 213 | // Call asyncData(context) 214 | if (Component.options.asyncData && typeof Component.options.asyncData === 'function') { 215 | const promise = promisify(Component.options.asyncData, app.context) 216 | promise.then((asyncDataResult) => { 217 | ssrContext.asyncData[Component.cid] = asyncDataResult 218 | applyAsyncData(Component) 219 | return asyncDataResult 220 | }) 221 | promises.push(promise) 222 | } else { 223 | promises.push(null) 224 | } 225 | 226 | // Call fetch(context) 227 | if (Component.options.fetch) { 228 | promises.push(Component.options.fetch(app.context)) 229 | } else { 230 | promises.push(null) 231 | } 232 | 233 | return Promise.all(promises) 234 | })) 235 | 236 | if (process.env.DEBUG && asyncDatas.length) console.debug('Data fetching ' + ssrContext.url + ': ' + (Date.now() - s) + 'ms') 237 | 238 | // datas are the first row of each 239 | ssrContext.nuxt.data = asyncDatas.map(r => r[0] || {}) 240 | 241 | // ...If there is a redirect or an error, stop the process 242 | if (ssrContext.redirected) { 243 | return noopApp() 244 | } 245 | if (ssrContext.nuxt.error) { 246 | return renderErrorPage() 247 | } 248 | 249 | // Call beforeNuxtRender methods & add store state 250 | await beforeRender() 251 | 252 | return _app 253 | } 254 | -------------------------------------------------------------------------------- /packages/vueber-fire-nuxt-demo/.nuxt/utils.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | // window.{{globals.loadedCallback}} hook 4 | // Useful for jsdom testing or plugins (https://github.com/tmpvar/jsdom#dealing-with-asynchronous-script-loading) 5 | if (process.client) { 6 | window.onNuxtReadyCbs = [] 7 | window.onNuxtReady = (cb) => { 8 | window.onNuxtReadyCbs.push(cb) 9 | } 10 | } 11 | 12 | export function empty () {} 13 | 14 | export function globalHandleError (error) { 15 | if (Vue.config.errorHandler) { 16 | Vue.config.errorHandler(error) 17 | } 18 | } 19 | 20 | export function interopDefault (promise) { 21 | return promise.then(m => m.default || m) 22 | } 23 | 24 | export function applyAsyncData (Component, asyncData) { 25 | if ( 26 | // For SSR, we once all this function without second param to just apply asyncData 27 | // Prevent doing this for each SSR request 28 | !asyncData && Component.options.__hasNuxtData 29 | ) { 30 | return 31 | } 32 | 33 | const ComponentData = Component.options._originDataFn || Component.options.data || function () { return {} } 34 | Component.options._originDataFn = ComponentData 35 | 36 | Component.options.data = function () { 37 | const data = ComponentData.call(this, this) 38 | if (this.$ssrContext) { 39 | asyncData = this.$ssrContext.asyncData[Component.cid] 40 | } 41 | return { ...data, ...asyncData } 42 | } 43 | 44 | Component.options.__hasNuxtData = true 45 | 46 | if (Component._Ctor && Component._Ctor.options) { 47 | Component._Ctor.options.data = Component.options.data 48 | } 49 | } 50 | 51 | export function sanitizeComponent (Component) { 52 | // If Component already sanitized 53 | if (Component.options && Component._Ctor === Component) { 54 | return Component 55 | } 56 | if (!Component.options) { 57 | Component = Vue.extend(Component) // fix issue #6 58 | Component._Ctor = Component 59 | } else { 60 | Component._Ctor = Component 61 | Component.extendOptions = Component.options 62 | } 63 | // For debugging purpose 64 | if (!Component.options.name && Component.options.__file) { 65 | Component.options.name = Component.options.__file 66 | } 67 | return Component 68 | } 69 | 70 | export function getMatchedComponents (route, matches = false, prop = 'components') { 71 | return Array.prototype.concat.apply([], route.matched.map((m, index) => { 72 | return Object.keys(m[prop]).map((key) => { 73 | matches && matches.push(index) 74 | return m[prop][key] 75 | }) 76 | })) 77 | } 78 | 79 | export function getMatchedComponentsInstances (route, matches = false) { 80 | return getMatchedComponents(route, matches, 'instances') 81 | } 82 | 83 | export function flatMapComponents (route, fn) { 84 | return Array.prototype.concat.apply([], route.matched.map((m, index) => { 85 | return Object.keys(m.components).reduce((promises, key) => { 86 | if (m.components[key]) { 87 | promises.push(fn(m.components[key], m.instances[key], m, key, index)) 88 | } else { 89 | delete m.components[key] 90 | } 91 | return promises 92 | }, []) 93 | })) 94 | } 95 | 96 | export function resolveRouteComponents (route, fn) { 97 | return Promise.all( 98 | flatMapComponents(route, async (Component, instance, match, key) => { 99 | // If component is a function, resolve it 100 | if (typeof Component === 'function' && !Component.options) { 101 | Component = await Component() 102 | } 103 | match.components[key] = Component = sanitizeComponent(Component) 104 | return typeof fn === 'function' ? fn(Component, instance, match, key) : Component 105 | }) 106 | ) 107 | } 108 | 109 | export async function getRouteData (route) { 110 | if (!route) { 111 | return 112 | } 113 | // Make sure the components are resolved (code-splitting) 114 | await resolveRouteComponents(route) 115 | // Send back a copy of route with meta based on Component definition 116 | return { 117 | ...route, 118 | meta: getMatchedComponents(route).map((Component, index) => { 119 | return { ...Component.options.meta, ...(route.matched[index] || {}).meta } 120 | }) 121 | } 122 | } 123 | 124 | export async function setContext (app, context) { 125 | // If context not defined, create it 126 | if (!app.context) { 127 | app.context = { 128 | isStatic: process.static, 129 | isDev: true, 130 | isHMR: false, 131 | app, 132 | 133 | payload: context.payload, 134 | error: context.error, 135 | base: '/', 136 | env: {} 137 | } 138 | // Only set once 139 | if (context.req) { 140 | app.context.req = context.req 141 | } 142 | if (context.res) { 143 | app.context.res = context.res 144 | } 145 | if (context.ssrContext) { 146 | app.context.ssrContext = context.ssrContext 147 | } 148 | app.context.redirect = (status, path, query) => { 149 | if (!status) { 150 | return 151 | } 152 | app.context._redirected = true 153 | // if only 1 or 2 arguments: redirect('/') or redirect('/', { foo: 'bar' }) 154 | let pathType = typeof path 155 | if (typeof status !== 'number' && (pathType === 'undefined' || pathType === 'object')) { 156 | query = path || {} 157 | path = status 158 | pathType = typeof path 159 | status = 302 160 | } 161 | if (pathType === 'object') { 162 | path = app.router.resolve(path).route.fullPath 163 | } 164 | // "/absolute/route", "./relative/route" or "../relative/route" 165 | if (/(^[.]{1,2}\/)|(^\/(?!\/))/.test(path)) { 166 | app.context.next({ 167 | path, 168 | query, 169 | status 170 | }) 171 | } else { 172 | path = formatUrl(path, query) 173 | if (process.server) { 174 | app.context.next({ 175 | path, 176 | status 177 | }) 178 | } 179 | if (process.client) { 180 | // https://developer.mozilla.org/en-US/docs/Web/API/Location/replace 181 | window.location.replace(path) 182 | 183 | // Throw a redirect error 184 | throw new Error('ERR_REDIRECT') 185 | } 186 | } 187 | } 188 | if (process.server) { 189 | app.context.beforeNuxtRender = fn => context.beforeRenderFns.push(fn) 190 | } 191 | if (process.client) { 192 | app.context.nuxtState = window.__NUXT__ 193 | } 194 | } 195 | 196 | // Dynamic keys 197 | const [currentRouteData, fromRouteData] = await Promise.all([ 198 | getRouteData(context.route), 199 | getRouteData(context.from) 200 | ]) 201 | 202 | if (context.route) { 203 | app.context.route = currentRouteData 204 | } 205 | 206 | if (context.from) { 207 | app.context.from = fromRouteData 208 | } 209 | 210 | app.context.next = context.next 211 | app.context._redirected = false 212 | app.context._errored = false 213 | app.context.isHMR = Boolean(context.isHMR) 214 | app.context.params = app.context.route.params || {} 215 | app.context.query = app.context.route.query || {} 216 | } 217 | 218 | export function middlewareSeries (promises, appContext) { 219 | if (!promises.length || appContext._redirected || appContext._errored) { 220 | return Promise.resolve() 221 | } 222 | return promisify(promises[0], appContext) 223 | .then(() => { 224 | return middlewareSeries(promises.slice(1), appContext) 225 | }) 226 | } 227 | 228 | export function promisify (fn, context) { 229 | let promise 230 | if (fn.length === 2) { 231 | console.warn('Callback-based asyncData, fetch or middleware calls are deprecated. ' + 232 | 'Please switch to promises or async/await syntax') 233 | 234 | // fn(context, callback) 235 | promise = new Promise((resolve) => { 236 | fn(context, function (err, data) { 237 | if (err) { 238 | context.error(err) 239 | } 240 | data = data || {} 241 | resolve(data) 242 | }) 243 | }) 244 | } else { 245 | promise = fn(context) 246 | } 247 | 248 | if (promise && promise instanceof Promise && typeof promise.then === 'function') { 249 | return promise 250 | } 251 | return Promise.resolve(promise) 252 | } 253 | 254 | // Imported from vue-router 255 | export function getLocation (base, mode) { 256 | let path = decodeURI(window.location.pathname) 257 | if (mode === 'hash') { 258 | return window.location.hash.replace(/^#\//, '') 259 | } 260 | if (base && path.indexOf(base) === 0) { 261 | path = path.slice(base.length) 262 | } 263 | return (path || '/') + window.location.search + window.location.hash 264 | } 265 | 266 | // Imported from path-to-regexp 267 | 268 | /** 269 | * Compile a string to a template function for the path. 270 | * 271 | * @param {string} str 272 | * @param {Object=} options 273 | * @return {!function(Object=, Object=)} 274 | */ 275 | export function compile (str, options) { 276 | return tokensToFunction(parse(str, options), options) 277 | } 278 | 279 | export function getQueryDiff (toQuery, fromQuery) { 280 | const diff = {} 281 | const queries = { ...toQuery, ...fromQuery } 282 | for (const k in queries) { 283 | if (String(toQuery[k]) !== String(fromQuery[k])) { 284 | diff[k] = true 285 | } 286 | } 287 | return diff 288 | } 289 | 290 | export function normalizeError (err) { 291 | let message 292 | if (!(err.message || typeof err === 'string')) { 293 | try { 294 | message = JSON.stringify(err, null, 2) 295 | } catch (e) { 296 | message = `[${err.constructor.name}]` 297 | } 298 | } else { 299 | message = err.message || err 300 | } 301 | return { 302 | ...err, 303 | message, 304 | statusCode: (err.statusCode || err.status || (err.response && err.response.status) || 500) 305 | } 306 | } 307 | 308 | /** 309 | * The main path matching regexp utility. 310 | * 311 | * @type {RegExp} 312 | */ 313 | const PATH_REGEXP = new RegExp([ 314 | // Match escaped characters that would otherwise appear in future matches. 315 | // This allows the user to escape special characters that won't transform. 316 | '(\\\\.)', 317 | // Match Express-style parameters and un-named parameters with a prefix 318 | // and optional suffixes. Matches appear as: 319 | // 320 | // "/:test(\\d+)?" => ["/", "test", "\d+", undefined, "?", undefined] 321 | // "/route(\\d+)" => [undefined, undefined, undefined, "\d+", undefined, undefined] 322 | // "/*" => ["/", undefined, undefined, undefined, undefined, "*"] 323 | '([\\/.])?(?:(?:\\:(\\w+)(?:\\(((?:\\\\.|[^\\\\()])+)\\))?|\\(((?:\\\\.|[^\\\\()])+)\\))([+*?])?|(\\*))' 324 | ].join('|'), 'g') 325 | 326 | /** 327 | * Parse a string for the raw tokens. 328 | * 329 | * @param {string} str 330 | * @param {Object=} options 331 | * @return {!Array} 332 | */ 333 | function parse (str, options) { 334 | const tokens = [] 335 | let key = 0 336 | let index = 0 337 | let path = '' 338 | const defaultDelimiter = (options && options.delimiter) || '/' 339 | let res 340 | 341 | while ((res = PATH_REGEXP.exec(str)) != null) { 342 | const m = res[0] 343 | const escaped = res[1] 344 | const offset = res.index 345 | path += str.slice(index, offset) 346 | index = offset + m.length 347 | 348 | // Ignore already escaped sequences. 349 | if (escaped) { 350 | path += escaped[1] 351 | continue 352 | } 353 | 354 | const next = str[index] 355 | const prefix = res[2] 356 | const name = res[3] 357 | const capture = res[4] 358 | const group = res[5] 359 | const modifier = res[6] 360 | const asterisk = res[7] 361 | 362 | // Push the current path onto the tokens. 363 | if (path) { 364 | tokens.push(path) 365 | path = '' 366 | } 367 | 368 | const partial = prefix != null && next != null && next !== prefix 369 | const repeat = modifier === '+' || modifier === '*' 370 | const optional = modifier === '?' || modifier === '*' 371 | const delimiter = res[2] || defaultDelimiter 372 | const pattern = capture || group 373 | 374 | tokens.push({ 375 | name: name || key++, 376 | prefix: prefix || '', 377 | delimiter, 378 | optional, 379 | repeat, 380 | partial, 381 | asterisk: Boolean(asterisk), 382 | pattern: pattern ? escapeGroup(pattern) : (asterisk ? '.*' : '[^' + escapeString(delimiter) + ']+?') 383 | }) 384 | } 385 | 386 | // Match any characters still remaining. 387 | if (index < str.length) { 388 | path += str.substr(index) 389 | } 390 | 391 | // If the path exists, push it onto the end. 392 | if (path) { 393 | tokens.push(path) 394 | } 395 | 396 | return tokens 397 | } 398 | 399 | /** 400 | * Prettier encoding of URI path segments. 401 | * 402 | * @param {string} 403 | * @return {string} 404 | */ 405 | function encodeURIComponentPretty (str, slashAllowed) { 406 | const re = slashAllowed ? /[?#]/g : /[/?#]/g 407 | return encodeURI(str).replace(re, (c) => { 408 | return '%' + c.charCodeAt(0).toString(16).toUpperCase() 409 | }) 410 | } 411 | 412 | /** 413 | * Encode the asterisk parameter. Similar to `pretty`, but allows slashes. 414 | * 415 | * @param {string} 416 | * @return {string} 417 | */ 418 | function encodeAsterisk (str) { 419 | return encodeURIComponentPretty(str, true) 420 | } 421 | 422 | /** 423 | * Escape a regular expression string. 424 | * 425 | * @param {string} str 426 | * @return {string} 427 | */ 428 | function escapeString (str) { 429 | return str.replace(/([.+*?=^!:${}()[\]|/\\])/g, '\\$1') 430 | } 431 | 432 | /** 433 | * Escape the capturing group by escaping special characters and meaning. 434 | * 435 | * @param {string} group 436 | * @return {string} 437 | */ 438 | function escapeGroup (group) { 439 | return group.replace(/([=!:$/()])/g, '\\$1') 440 | } 441 | 442 | /** 443 | * Expose a method for transforming tokens into the path function. 444 | */ 445 | function tokensToFunction (tokens, options) { 446 | // Compile all the tokens into regexps. 447 | const matches = new Array(tokens.length) 448 | 449 | // Compile all the patterns before compilation. 450 | for (let i = 0; i < tokens.length; i++) { 451 | if (typeof tokens[i] === 'object') { 452 | matches[i] = new RegExp('^(?:' + tokens[i].pattern + ')$', flags(options)) 453 | } 454 | } 455 | 456 | return function (obj, opts) { 457 | let path = '' 458 | const data = obj || {} 459 | const options = opts || {} 460 | const encode = options.pretty ? encodeURIComponentPretty : encodeURIComponent 461 | 462 | for (let i = 0; i < tokens.length; i++) { 463 | const token = tokens[i] 464 | 465 | if (typeof token === 'string') { 466 | path += token 467 | 468 | continue 469 | } 470 | 471 | const value = data[token.name || 'pathMatch'] 472 | let segment 473 | 474 | if (value == null) { 475 | if (token.optional) { 476 | // Prepend partial segment prefixes. 477 | if (token.partial) { 478 | path += token.prefix 479 | } 480 | 481 | continue 482 | } else { 483 | throw new TypeError('Expected "' + token.name + '" to be defined') 484 | } 485 | } 486 | 487 | if (Array.isArray(value)) { 488 | if (!token.repeat) { 489 | throw new TypeError('Expected "' + token.name + '" to not repeat, but received `' + JSON.stringify(value) + '`') 490 | } 491 | 492 | if (value.length === 0) { 493 | if (token.optional) { 494 | continue 495 | } else { 496 | throw new TypeError('Expected "' + token.name + '" to not be empty') 497 | } 498 | } 499 | 500 | for (let j = 0; j < value.length; j++) { 501 | segment = encode(value[j]) 502 | 503 | if (!matches[i].test(segment)) { 504 | throw new TypeError('Expected all "' + token.name + '" to match "' + token.pattern + '", but received `' + JSON.stringify(segment) + '`') 505 | } 506 | 507 | path += (j === 0 ? token.prefix : token.delimiter) + segment 508 | } 509 | 510 | continue 511 | } 512 | 513 | segment = token.asterisk ? encodeAsterisk(value) : encode(value) 514 | 515 | if (!matches[i].test(segment)) { 516 | throw new TypeError('Expected "' + token.name + '" to match "' + token.pattern + '", but received "' + segment + '"') 517 | } 518 | 519 | path += token.prefix + segment 520 | } 521 | 522 | return path 523 | } 524 | } 525 | 526 | /** 527 | * Get the flags for a regexp from the options. 528 | * 529 | * @param {Object} options 530 | * @return {string} 531 | */ 532 | function flags (options) { 533 | return options && options.sensitive ? '' : 'i' 534 | } 535 | 536 | /** 537 | * Format given url, append query to url query string 538 | * 539 | * @param {string} url 540 | * @param {string} query 541 | * @return {string} 542 | */ 543 | function formatUrl (url, query) { 544 | let protocol 545 | const index = url.indexOf('://') 546 | if (index !== -1) { 547 | protocol = url.substring(0, index) 548 | url = url.substring(index + 3) 549 | } else if (url.startsWith('//')) { 550 | url = url.substring(2) 551 | } 552 | 553 | let parts = url.split('/') 554 | let result = (protocol ? protocol + '://' : '//') + parts.shift() 555 | 556 | let path = parts.filter(Boolean).join('/') 557 | let hash 558 | parts = path.split('#') 559 | if (parts.length === 2) { 560 | [path, hash] = parts 561 | } 562 | 563 | result += path ? '/' + path : '' 564 | 565 | if (query && JSON.stringify(query) !== '{}') { 566 | result += (url.split('?').length === 2 ? '&' : '?') + formatQuery(query) 567 | } 568 | result += hash ? '#' + hash : '' 569 | 570 | return result 571 | } 572 | 573 | /** 574 | * Transform data object to query string 575 | * 576 | * @param {object} query 577 | * @return {string} 578 | */ 579 | function formatQuery (query) { 580 | return Object.keys(query).sort().map((key) => { 581 | const val = query[key] 582 | if (val == null) { 583 | return '' 584 | } 585 | if (Array.isArray(val)) { 586 | return val.slice().map(val2 => [key, '=', val2].join('')).join('&') 587 | } 588 | return key + '=' + val 589 | }).filter(Boolean).join('&') 590 | } 591 | -------------------------------------------------------------------------------- /packages/vueber-fire-nuxt-demo/.nuxt/views/app.template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ HEAD }} 5 | 6 | 7 | {{ APP }} 8 | 9 | 10 | -------------------------------------------------------------------------------- /packages/vueber-fire-nuxt-demo/.nuxt/views/error.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Server error 5 | 6 | 7 | 10 | 11 | 12 |
13 |
14 | 15 |
Server error
16 |
{{ message }}
17 |
18 | 21 |
22 | 23 | 24 | -------------------------------------------------------------------------------- /packages/vueber-fire-nuxt-demo/README.md: -------------------------------------------------------------------------------- 1 | # vueber-nuxt-fire-demo 2 | 3 | tbd 4 | -------------------------------------------------------------------------------- /packages/vueber-fire-nuxt-demo/layouts/default.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | -------------------------------------------------------------------------------- /packages/vueber-fire-nuxt-demo/nuxt.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | mode: 'universal', 3 | /* 4 | ** Headers of the page 5 | */ 6 | head: { 7 | title: process.env.npm_package_name || '', 8 | meta: [ 9 | { charset: 'utf-8' }, 10 | { name: 'viewport', content: 'width=device-width, initial-scale=1' }, 11 | { 12 | hid: 'description', 13 | name: 'description', 14 | content: process.env.npm_package_description || '', 15 | }, 16 | ], 17 | link: [{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }], 18 | }, 19 | /* 20 | ** Customize the progress-bar color 21 | */ 22 | loading: { color: '#fff' }, 23 | /* 24 | ** Global CSS 25 | */ 26 | css: [], 27 | /* 28 | ** Plugins to load before mounting the App 29 | */ 30 | plugins: ['@/plugins/vuefire'], 31 | /* 32 | ** Nuxt.js dev-modules 33 | */ 34 | buildModules: [], 35 | /* 36 | ** Nuxt.js modules 37 | */ 38 | modules: [ 39 | [ 40 | '@nuxtjs/firebase', 41 | { 42 | config: { 43 | apiKey: 'AIzaSyBPbKMVLeY1x_1sgikvuP5cyfUd6iFs0l0', 44 | authDomain: 'vueber-fire-nuxt-demo.firebaseapp.com', 45 | databaseURL: 'https://vueber-fire-nuxt-demo.firebaseio.com', 46 | projectId: 'vueber-fire-nuxt-demo', 47 | storageBucket: 'vueber-fire-nuxt-demo.appspot.com', 48 | messagingSenderId: '499279429791', 49 | appId: '1:499279429791:web:e0ef9deb856d8ed2c1a2aa', 50 | }, 51 | services: { 52 | firestore: true, 53 | }, 54 | }, 55 | ], 56 | ], 57 | /* 58 | ** Build configuration 59 | */ 60 | build: { 61 | transpile: [/^vueber/], 62 | /* 63 | ** You can extend webpack config here 64 | */ 65 | extend(config, ctx) {}, 66 | }, 67 | } 68 | -------------------------------------------------------------------------------- /packages/vueber-fire-nuxt-demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vueber-nuxt-fire-demo", 3 | "version": "1.0.0", 4 | "description": "My astounding Nuxt.js project", 5 | "author": "Pascal Luther", 6 | "private": true, 7 | "scripts": { 8 | "dev": "nuxt", 9 | "build": "nuxt build", 10 | "start": "nuxt start", 11 | "generate": "nuxt generate" 12 | }, 13 | "dependencies": { 14 | "@nuxtjs/firebase": "^5.0.7", 15 | "anchorme": "^2.0.0", 16 | "core-js": "^2", 17 | "emoji-mart-vue": "^2.6.6", 18 | "firebase": "^7.9.1", 19 | "firewings": "^1.0.3", 20 | "nuxt": "^2.0.0", 21 | "vueber": "0.0.1-alpha.0", 22 | "vueber-fire": "0.0.1-alpha.0", 23 | "vuefire": "^2.2.1" 24 | }, 25 | "devDependencies": {} 26 | } 27 | -------------------------------------------------------------------------------- /packages/vueber-fire-nuxt-demo/pages/index.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 31 | 32 | 37 | -------------------------------------------------------------------------------- /packages/vueber-fire-nuxt-demo/plugins/vuefire.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import { firestorePlugin } from 'vuefire' 3 | 4 | Vue.use(firestorePlugin) 5 | -------------------------------------------------------------------------------- /packages/vueber-fire/README.md: -------------------------------------------------------------------------------- 1 | # vueber-fire 2 | 3 | This package is being developed and in alpha phase. 4 | 5 | Do not use it in any app, it will change a lot in the next few days! ;-) 6 | -------------------------------------------------------------------------------- /packages/vueber-fire/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vueber-fire", 3 | "version": "0.0.1-alpha.0", 4 | "lockfileVersion": 1 5 | } 6 | -------------------------------------------------------------------------------- /packages/vueber-fire/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vueber-fire", 3 | "version": "0.0.1-alpha.0", 4 | "license": "MIT", 5 | "description": "Linking Firebase Firestore to Vueber.", 6 | "main": "src/index.js", 7 | "author": "Pascal Luther", 8 | "repository": "https://github.com/lupas/vueber", 9 | "homepage": "https://github.com/lupas", 10 | "keywords": [ 11 | "vue, vuejs, chat, vueber, messaging, inbox, firebase, firestore" 12 | ], 13 | "publishConfig": { 14 | "access": "public" 15 | }, 16 | "peerDependencies": { 17 | "firewings": "^1.0.3", 18 | "vueber": "0.0.1-alpha.0", 19 | "vueber-fire": "0.0.1-alpha.0", 20 | "vuefire": "^2.2.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/vueber-fire/src/components/main.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 280 | -------------------------------------------------------------------------------- /packages/vueber-fire/src/components/unreadConversations.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 89 | -------------------------------------------------------------------------------- /packages/vueber-fire/src/helpers/index.js: -------------------------------------------------------------------------------- 1 | export const serializeConversation = (currentUserId) => { 2 | return (doc) => { 3 | const conversation = doc.data() 4 | conversation.id = doc.id 5 | 6 | // Create Date Object 7 | if (conversation.lastMessage.sentDate) { 8 | conversation.lastMessage.sentDate = conversation.lastMessage.sentDate.toDate() 9 | } 10 | 11 | // Delete Own Participant 12 | delete conversation.participants[currentUserId] 13 | 14 | // Add _chatpartner 15 | let chatpartner 16 | for (const i in conversation.participants) { 17 | chatpartner = conversation.participants[i] 18 | break 19 | } 20 | conversation._chatpartner = chatpartner 21 | 22 | // Set _ownMessage Flag 23 | const isOwnMessage = conversation.lastMessage.senderId === currentUserId 24 | conversation.lastMessage._ownMessage = isOwnMessage 25 | 26 | return conversation 27 | } 28 | } 29 | 30 | export const serializeMessage = (doc) => { 31 | const message = doc.data() 32 | message.id = doc.id 33 | if (message.sentDate) { 34 | message.sentDate = message.sentDate.toDate() 35 | } 36 | return message 37 | } 38 | -------------------------------------------------------------------------------- /packages/vueber-fire/src/index.js: -------------------------------------------------------------------------------- 1 | import VueberFire from './components/main.vue' 2 | import UnreadConversations from './components/unreadConversations.vue' 3 | 4 | // Register Components 5 | 6 | export { VueberFire, UnreadConversations } 7 | 8 | export const meta = require('../package.json') 9 | -------------------------------------------------------------------------------- /packages/vueber/README.md: -------------------------------------------------------------------------------- 1 | # vueber 2 | 3 | This package is being developed and in alpha phase. 4 | 5 | Do not use it in any app, it will change a lot in the next few days! ;-) 6 | -------------------------------------------------------------------------------- /packages/vueber/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vueber", 3 | "version": "0.0.1-alpha.0", 4 | "license": "MIT", 5 | "description": "Messenger & Chat framework for Vue.js applications.", 6 | "main": "src/index.js", 7 | "author": "Pascal Luther", 8 | "repository": "https://github.com/lupas/vueber", 9 | "homepage": "https://github.com/lupas", 10 | "keywords": [ 11 | "vue, vuejs, chat, vueber, messaging, inbox" 12 | ], 13 | "publishConfig": { 14 | "access": "public" 15 | }, 16 | "peerDependencies": {}, 17 | "devDependencies": { 18 | "@mdi/js": "^5.1.45", 19 | "anchorme": "^2.0.0", 20 | "emoji-mart-vue": "^2.6.6", 21 | "moment": "^2.24.0", 22 | "v-tooltip": "^2.0.2" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/vueber/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // TODO: Can be deleted at some point? 3 | } 4 | -------------------------------------------------------------------------------- /packages/vueber/src/assets/demoData/getDemoData.js: -------------------------------------------------------------------------------- 1 | export default function getDemoData() { 2 | const avatar1 = require('./user1.jpeg') 3 | const avatar2 = require('./user2.jpeg') 4 | const avatar3 = require('./user3.jpeg') 5 | const avatar4 = require('./user4.jpeg') 6 | const avatar5 = require('./user5.jpeg') 7 | return { 8 | conversations: [ 9 | { 10 | lastMessage: { 11 | hasPendingNotification: true, 12 | isRead: false, 13 | message: "Hello, I'm User 1", 14 | senderId: 'user1', 15 | senderName: 'User 1', 16 | sentDate: new Date('2019-11-21T08:50:57.796Z'), 17 | _ownMessage: false 18 | }, 19 | noOfMessages: 13, 20 | participants: { 21 | user1: { 22 | avatarPath: avatar1, 23 | chatDisabled: false, 24 | id: 'user1', 25 | username: 'User 1' 26 | }, 27 | user2: { 28 | avatarPath: avatar2, 29 | chatDisabled: false, 30 | id: 'user2', 31 | username: 'User 2' 32 | } 33 | }, 34 | participantsArray: ['user1', 'user2'], 35 | id: 'conversation1' 36 | // path: 'conversations/conversation1', 37 | // _chatpartner: { 38 | // avatarPath: null, 39 | // chatDisabled: false, 40 | // id: 'user1', 41 | // username: 'User1' 42 | // } 43 | }, 44 | { 45 | lastMessage: { 46 | hasPendingNotification: false, 47 | isRead: true, 48 | message: "Hey it's User 3.", 49 | senderId: 'user3', 50 | senderName: 'User 3', 51 | sentDate: new Date('2019-11-20T08:50:57.796Z'), 52 | _ownMessage: false 53 | }, 54 | noOfMessages: 3, 55 | participants: { 56 | user1: { 57 | avatarPath: avatar1, 58 | chatDisabled: false, 59 | id: 'user1', 60 | username: 'User 1' 61 | }, 62 | user3: { 63 | avatarPath: avatar3, 64 | chatDisabled: false, 65 | id: 'user3', 66 | username: 'User 3' 67 | } 68 | }, 69 | participantsArray: ['user1', 'user3'], 70 | id: 'conversation2' 71 | // path: 'conversations/conversation2', 72 | // _chatpartner: { 73 | // avatarPath: null, 74 | // chatDisabled: false, 75 | // id: 'user1', 76 | // username: 'User1' 77 | // } 78 | }, 79 | { 80 | lastMessage: { 81 | hasPendingNotification: false, 82 | isRead: true, 83 | message: "Hey it's User 4.", 84 | senderId: 'user4', 85 | senderName: 'User 4', 86 | sentDate: new Date('2019-11-20T08:50:57.796Z'), 87 | _ownMessage: false 88 | }, 89 | noOfMessages: 3, 90 | participants: { 91 | user1: { 92 | avatarPath: avatar1, 93 | chatDisabled: false, 94 | id: 'user1', 95 | username: 'User 1' 96 | }, 97 | user4: { 98 | avatarPath: avatar4, 99 | chatDisabled: false, 100 | id: 'user4', 101 | username: 'User 4' 102 | } 103 | }, 104 | participantsArray: ['user1', 'user4'], 105 | id: 'conversation3' 106 | // path: 'conversations/conversation3', 107 | // _chatpartner: { 108 | // avatarPath: null, 109 | // chatDisabled: false, 110 | // id: 'user1', 111 | // username: 'User1' 112 | // } 113 | }, 114 | { 115 | lastMessage: { 116 | hasPendingNotification: false, 117 | isRead: true, 118 | message: "Hey it's User 5.", 119 | senderId: 'user5', 120 | senderName: 'User 5', 121 | sentDate: new Date('2019-11-20T08:50:57.796Z'), 122 | _ownMessage: false 123 | }, 124 | noOfMessages: 3, 125 | participants: { 126 | user1: { 127 | avatarPath: avatar1, 128 | chatDisabled: false, 129 | id: 'user1', 130 | username: 'User 1' 131 | }, 132 | user5: { 133 | avatarPath: avatar5, 134 | chatDisabled: false, 135 | id: 'user5', 136 | username: 'User 5' 137 | } 138 | }, 139 | participantsArray: ['user1', 'user5'], 140 | id: 'conversation4' 141 | // path: 'conversations/conversation4', 142 | // _chatpartner: { 143 | // avatarPath: null, 144 | // chatDisabled: false, 145 | // id: 'user5', 146 | // username: 'User 5' 147 | // } 148 | } 149 | ], 150 | users: [ 151 | { 152 | id: 'user1', 153 | avatar: avatar1, 154 | username: 'User1' 155 | }, 156 | { 157 | id: 'user2', 158 | avatar: avatar2, 159 | username: 'User2' 160 | }, 161 | { 162 | id: 'user3', 163 | avatar: avatar3, 164 | username: 'User3' 165 | } 166 | ], 167 | messages: [ 168 | { 169 | hasPendingNotification: true, 170 | isRead: true, 171 | message: 'Hallo, dies ist ein Test', 172 | senderId: 'user2', 173 | senderName: 'User2', 174 | sentDate: new Date('2019-01-07T10:36:38.296Z'), 175 | id: 'message1', 176 | conversationId: 'conversation1' 177 | }, 178 | { 179 | hasPendingNotification: true, 180 | isRead: true, 181 | message: 'Testchat', 182 | senderId: 'user2', 183 | senderName: 'User2', 184 | sentDate: new Date('2019-01-07T10:36:38.296Z'), 185 | id: 'message2', 186 | conversationId: 'conversation2' 187 | } 188 | ] 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /packages/vueber/src/assets/demoData/user1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lupas/vueber/fa1f193b87eaeadddf29fcc3619c88f1f6a4887f/packages/vueber/src/assets/demoData/user1.jpeg -------------------------------------------------------------------------------- /packages/vueber/src/assets/demoData/user2.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lupas/vueber/fa1f193b87eaeadddf29fcc3619c88f1f6a4887f/packages/vueber/src/assets/demoData/user2.jpeg -------------------------------------------------------------------------------- /packages/vueber/src/assets/demoData/user3.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lupas/vueber/fa1f193b87eaeadddf29fcc3619c88f1f6a4887f/packages/vueber/src/assets/demoData/user3.jpeg -------------------------------------------------------------------------------- /packages/vueber/src/assets/demoData/user4.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lupas/vueber/fa1f193b87eaeadddf29fcc3619c88f1f6a4887f/packages/vueber/src/assets/demoData/user4.jpeg -------------------------------------------------------------------------------- /packages/vueber/src/assets/demoData/user5.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lupas/vueber/fa1f193b87eaeadddf29fcc3619c88f1f6a4887f/packages/vueber/src/assets/demoData/user5.jpeg -------------------------------------------------------------------------------- /packages/vueber/src/assets/noAvatar_xs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lupas/vueber/fa1f193b87eaeadddf29fcc3619c88f1f6a4887f/packages/vueber/src/assets/noAvatar_xs.png -------------------------------------------------------------------------------- /packages/vueber/src/components/_elements/avatar.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 40 | 41 | 46 | -------------------------------------------------------------------------------- /packages/vueber/src/components/_elements/btn.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 38 | 39 | 76 | -------------------------------------------------------------------------------- /packages/vueber/src/components/_elements/icon.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 53 | -------------------------------------------------------------------------------- /packages/vueber/src/components/_elements/textField.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 48 | 49 | 75 | -------------------------------------------------------------------------------- /packages/vueber/src/components/_elements/toolbarWrapper.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 51 | 52 | 81 | -------------------------------------------------------------------------------- /packages/vueber/src/components/headerBar/index.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 64 | 65 | 72 | -------------------------------------------------------------------------------- /packages/vueber/src/components/index.vue: -------------------------------------------------------------------------------- 1 | 67 | 68 | 210 | 211 | 321 | -------------------------------------------------------------------------------- /packages/vueber/src/components/inputFooter/emojiPicker.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 34 | 35 | 40 | -------------------------------------------------------------------------------- /packages/vueber/src/components/inputFooter/index.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 115 | 116 | 122 | -------------------------------------------------------------------------------- /packages/vueber/src/components/leftSidebar/conversationTile.vue: -------------------------------------------------------------------------------- 1 | 57 | 58 | 114 | 115 | 162 | -------------------------------------------------------------------------------- /packages/vueber/src/components/leftSidebar/index.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 49 | 50 | 63 | -------------------------------------------------------------------------------- /packages/vueber/src/components/loginView/index.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 24 | -------------------------------------------------------------------------------- /packages/vueber/src/components/messagesView/index.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 127 | 128 | 149 | -------------------------------------------------------------------------------- /packages/vueber/src/components/messagesView/message/index.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 110 | 111 | 157 | -------------------------------------------------------------------------------- /packages/vueber/src/components/messagesView/message/isReadFlag.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 29 | 30 | 35 | -------------------------------------------------------------------------------- /packages/vueber/src/components/noSelectionView/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 10 | -------------------------------------------------------------------------------- /packages/vueber/src/components/rightSidebar/index.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 52 | 53 | 80 | -------------------------------------------------------------------------------- /packages/vueber/src/components/rightSidebar/mobileHeader.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /packages/vueber/src/components/rightSidebar/userActions.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 30 | 31 | 36 | -------------------------------------------------------------------------------- /packages/vueber/src/index.js: -------------------------------------------------------------------------------- 1 | import Vueber from './components/index.vue' 2 | 3 | // Register Components 4 | 5 | export default Vueber 6 | 7 | export const meta = require('../package.json') 8 | --------------------------------------------------------------------------------