├── .gitignore ├── LICENSE ├── README.md ├── firebase.json ├── functions ├── package-lock.json ├── package.json └── src │ ├── attachUserToProject.js │ ├── createUser.js │ ├── deleteProject.js │ ├── index.js │ ├── initSettings.js │ └── publishJson.js ├── package.json ├── public ├── favicon.ico ├── index.html ├── powered-by-editlayer.png └── powered-by-editlayer.svg ├── rules ├── cors.json ├── firestore.rules └── storage.rules ├── scripts ├── cors.js ├── deploy.js └── open.js └── src ├── App.vue ├── assets └── image-background.png ├── components ├── SideItem.vue ├── content │ ├── Editor.vue │ ├── PartnerInfo.vue │ └── PublishButton.vue ├── core │ ├── Alert.vue │ ├── Button.vue │ ├── Card.vue │ ├── Heading.vue │ ├── Notification.vue │ └── registerAll.js ├── dashboard │ ├── Navigation.vue │ ├── NewProjectButton.vue │ ├── NewProjectModal.vue │ └── Projects.vue ├── navigation │ ├── BackButton.vue │ ├── Breadcrumb.vue │ └── Navigation.vue ├── panel │ ├── Item.vue │ ├── ItemsFromArray.vue │ ├── ItemsFromObject.vue │ └── Panel.vue ├── schema │ └── SchemaEditor.vue ├── settings │ ├── DeleteProject.vue │ ├── FileLocation.vue │ ├── NewUserButton.vue │ ├── NewUserModal.vue │ ├── Users.vue │ └── Webhook.vue └── utils │ ├── FooterContent.vue │ ├── LoaderOverlay.vue │ └── Notifications.vue ├── editors ├── Calendar.vue ├── Checkbox.vue ├── Code.vue ├── Color.vue ├── Image.vue ├── Markdown.vue ├── Number.vue ├── Radio.vue ├── RichText.vue ├── Select.vue ├── SelectImage.vue ├── Text.vue ├── Textarea.vue ├── Video.vue ├── common │ ├── BaseEditor.vue │ └── BasePreview.vue ├── config.js └── previews │ ├── Array.vue │ ├── Calendar.vue │ ├── Code.vue │ ├── Color.vue │ ├── Image.vue │ ├── Option.vue │ ├── Text.vue │ └── Video.vue ├── main.js ├── router.js ├── sass ├── codemirror.sass ├── core │ ├── _breakpoint.sass │ ├── _center.sass │ ├── _chain.sass │ ├── _container.sass │ ├── _gap.sass │ ├── _grid.sass │ ├── _hx.sass │ ├── _invert.sass │ ├── _mask.sass │ ├── initialize.sass │ └── mixins.sass ├── transitions.sass └── variables.sass ├── store ├── index.js └── modules │ ├── auth.js │ ├── content.js │ ├── firestore.js │ ├── notifications.js │ ├── projects.js │ ├── schema.js │ ├── structure.js │ ├── uploader.js │ └── utils.js ├── utils ├── buildJson.js ├── buildStructure.js ├── codemirror.js ├── firebase.js ├── icons.js └── webhook.js └── views ├── Auth.vue ├── Content.vue ├── Dashboard.vue ├── Schema.vue └── Settings.vue /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | dist 4 | __* 5 | .env*.local 6 | .firebaserc 7 | editalyer-archive.zip 8 | .firebase 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Editlayer documentation 2 | https://editlayer.rane.io/documentation 3 | -------------------------------------------------------------------------------- /firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "firestore": { 3 | "rules": "rules/firestore.rules" 4 | }, 5 | "hosting": { 6 | "public": "dist", 7 | "ignore": [ 8 | "firebase.json", 9 | "**/.*", 10 | "**/node_modules/**" 11 | ], 12 | "rewrites": [ 13 | { 14 | "source": "**", 15 | "destination": "/index.html" 16 | } 17 | ] 18 | }, 19 | "storage": { 20 | "rules": "rules/storage.rules" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /functions/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "editlayer-functions", 3 | "description": "Firebase Cloud Functions for Editlayer", 4 | "main": "dist/index.js", 5 | "dependencies": { 6 | "firebase-admin": "~8.4.0", 7 | "firebase-functions": "^3.2.0", 8 | "js-base64": "^2.5.1", 9 | "lodash": "^4.17.21" 10 | }, 11 | "babel": { 12 | "presets": [ 13 | [ 14 | "env", 15 | { 16 | "targets": { 17 | "node": "6.14.0" 18 | } 19 | } 20 | ] 21 | ] 22 | }, 23 | "private": true 24 | } 25 | -------------------------------------------------------------------------------- /functions/src/attachUserToProject.js: -------------------------------------------------------------------------------- 1 | import admin from 'firebase-admin' 2 | import _ from 'lodash' 3 | import { Base64 } from 'js-base64' 4 | 5 | export default { 6 | async whenAdded (snap, context) { 7 | const firestore = admin.firestore() 8 | const auth = admin.auth() 9 | const jobData = snap.data() 10 | 11 | if (jobData.job !== 'attachUserToProject') return false 12 | 13 | const userId = await auth 14 | .getUserByEmail(jobData.email) 15 | .then(userRecord => userRecord.uid) 16 | .catch(error => { 17 | console.error(error) 18 | return false 19 | }) 20 | 21 | if (userId === false) return false 22 | 23 | const project = await firestore 24 | .collection('projects') 25 | .doc(jobData.projectId) 26 | .get() 27 | 28 | const projectData = project.data() 29 | const user = projectData.users[jobData.awaitId] 30 | 31 | let updateData = {} 32 | 33 | // Create new user with real id 34 | updateData[`users.${userId}`] = { 35 | email: user.email, 36 | permissions: user.permissions, 37 | userExist: true, 38 | } 39 | 40 | // Remove await user 41 | updateData[`users.${jobData.awaitId}`] = admin.firestore.FieldValue.delete() 42 | 43 | firestore 44 | .collection('projects') 45 | .doc(context.params.projectId) 46 | .update(updateData) 47 | 48 | console.log('attachUserToProjectWhenAdded done') 49 | return true 50 | }, 51 | 52 | whenRegister (userRecord, context) { 53 | const firestore = admin.firestore() 54 | const awaitId = `await-${Base64.encodeURI(userRecord.email)}` 55 | 56 | return firestore 57 | .collection('projects') 58 | .get() 59 | .then(querySnapshot => { 60 | querySnapshot.forEach((doc) => { 61 | if (_.has(doc.data().users, awaitId)) { 62 | const user = doc.data().users[awaitId] 63 | let updateData = {} 64 | 65 | // Create new user with real id 66 | updateData[`users.${userRecord.uid}`] = { 67 | email: user.email, 68 | permissions: user.permissions, 69 | userExist: true, 70 | } 71 | 72 | // Remove await user 73 | updateData[`users.${awaitId}`] = admin.firestore.FieldValue.delete() 74 | 75 | firestore 76 | .collection('projects') 77 | .doc(doc.id) 78 | .update(updateData) 79 | } 80 | }) 81 | 82 | console.log('attachUserToProjectWhenRegister done') 83 | return true 84 | }) 85 | .catch(error => console.error('User adding when register failed', error)) 86 | }, 87 | } 88 | -------------------------------------------------------------------------------- /functions/src/createUser.js: -------------------------------------------------------------------------------- 1 | import admin from 'firebase-admin' 2 | 3 | export default async (userRecord, context) => { 4 | const firestore = admin.firestore() 5 | 6 | const settings = await firestore 7 | .collection('settings') 8 | .doc('general') 9 | .get() 10 | .then(snap => snap.data()) 11 | .catch(error => console.error('Settings getting failed', error)) 12 | 13 | const firstUser = await firestore 14 | .collection('users') 15 | .get() 16 | .then(snap => snap.size === 0) 17 | .catch(error => console.error('Users getting failed', error)) 18 | 19 | const allowCreateProject = settings.allowNewUsersCreateProject === true || firstUser === true 20 | 21 | await firestore 22 | .collection('users') 23 | .doc(userRecord.uid) 24 | .set({ 25 | email: userRecord.email, 26 | permissions: { 27 | createProject: allowCreateProject, 28 | }, 29 | }) 30 | .catch(error => console.error('User added failed', error)) 31 | 32 | console.log('createUser done') 33 | return true 34 | } 35 | -------------------------------------------------------------------------------- /functions/src/deleteProject.js: -------------------------------------------------------------------------------- 1 | import admin from 'firebase-admin' 2 | 3 | export default (snap, context) => { 4 | const firestore = admin.firestore() 5 | const bucket = admin.storage().bucket() 6 | const storeData = snap.data() 7 | 8 | if (storeData.job !== 'deleteProject') return false 9 | if (storeData.deleteProjectId !== context.params.projectId) return false 10 | 11 | return bucket 12 | .getFiles({ prefix: context.params.projectId }) 13 | .then((result) => { 14 | let files = result[0] 15 | 16 | files.forEach((file) => { 17 | bucket.file(file.name).delete() 18 | }) 19 | 20 | return true 21 | }) 22 | .then(() => { 23 | return firestore 24 | .collection('projects') 25 | .doc(context.params.projectId) 26 | .collection('versions') 27 | .get() 28 | }) 29 | .then((versions) => { 30 | versions.forEach(doc => { 31 | firestore 32 | .collection('projects') 33 | .doc(context.params.projectId) 34 | .collection('versions') 35 | .doc(doc.id) 36 | .delete() 37 | }) 38 | 39 | return firestore 40 | .collection('projects') 41 | .doc(context.params.projectId) 42 | .collection('jobs') 43 | .get() 44 | }) 45 | .then((jobs) => { 46 | jobs.forEach(doc => { 47 | firestore 48 | .collection('projects') 49 | .doc(context.params.projectId) 50 | .collection('jobs') 51 | .doc(doc.id) 52 | .delete() 53 | }) 54 | 55 | console.log('deleteProject done') 56 | return firestore 57 | .collection('projects') 58 | .doc(context.params.projectId) 59 | .delete() 60 | }) 61 | .catch(error => { 62 | console.log('Project deleting failed', error) 63 | return false 64 | }) 65 | } 66 | -------------------------------------------------------------------------------- /functions/src/index.js: -------------------------------------------------------------------------------- 1 | import * as functions from 'firebase-functions' 2 | import admin from 'firebase-admin' 3 | import publishJson from './publishJson' 4 | import attachUserToProject from './attachUserToProject' 5 | import createUser from './createUser' 6 | import deleteProject from './deleteProject' 7 | import initSettings from './initSettings' 8 | 9 | admin.initializeApp() 10 | 11 | exports.newUser = functions 12 | .auth 13 | .user() 14 | .onCreate(async (snap, context) => { 15 | await initSettings(snap, context) 16 | await createUser(snap, context) 17 | await attachUserToProject.whenRegister(snap, context) 18 | return true 19 | }) 20 | 21 | exports.newVersion = functions 22 | .firestore 23 | .document('projects/{projectId}/versions/{versionId}') 24 | .onCreate(async (snap, context) => { 25 | await publishJson(snap, context) 26 | return true 27 | }) 28 | 29 | exports.newJob = functions 30 | .firestore 31 | .document('projects/{projectId}/jobs/{jobId}') 32 | .onCreate(async (snap, context) => { 33 | await attachUserToProject.whenAdded(snap, context) 34 | await deleteProject(snap, context) 35 | return true 36 | }) 37 | -------------------------------------------------------------------------------- /functions/src/initSettings.js: -------------------------------------------------------------------------------- 1 | import admin from 'firebase-admin' 2 | 3 | export default async (userRecord, context) => { 4 | const firestore = admin.firestore() 5 | 6 | const isSettingsExsist = await firestore 7 | .collection('settings') 8 | .get() 9 | .then(snap => snap.size > 0) 10 | .catch(error => console.error('Settings failed', error)) 11 | 12 | if (isSettingsExsist === false) { 13 | await firestore 14 | .collection('settings') 15 | .doc('general') 16 | .set({ 17 | allowNewUsersCreateProject: false, 18 | }) 19 | } 20 | 21 | console.log('initSettings done') 22 | return false 23 | } 24 | -------------------------------------------------------------------------------- /functions/src/publishJson.js: -------------------------------------------------------------------------------- 1 | import admin from 'firebase-admin' 2 | import fs from 'fs' 3 | import _ from 'lodash' 4 | 5 | export default (snap, context) => { 6 | const bucket = admin.storage().bucket() 7 | const firestore = admin.firestore() 8 | const tempFilePath = '/tmp/tempfile.json' 9 | const versionData = snap.data() 10 | const destinationPath = `${context.params.projectId}/content.json` 11 | 12 | const jsonFileContent = _.merge(versionData.json, { 13 | PUBLISHED_AT: versionData.publishedAt, 14 | VERSION_ID: context.params.versionId, 15 | }) 16 | 17 | const json = JSON.stringify(jsonFileContent, null, 2) 18 | 19 | fs.writeFileSync(tempFilePath, json) 20 | 21 | return bucket 22 | .upload(tempFilePath, { 23 | destination: destinationPath, 24 | metadata: { 25 | metadata: { 26 | firebaseStorageDownloadTokens: versionData.token, 27 | }, 28 | }, 29 | }) 30 | .then(() => { 31 | console.log('JSON publishing done') 32 | 33 | let updateData = {} 34 | updateData[`publishedVersion`] = { 35 | draft: versionData.draft, 36 | schema: versionData.schema, 37 | json: json, 38 | publishedAt: versionData.publishedAt, 39 | versionId: context.params.versionId, 40 | } 41 | 42 | firestore 43 | .collection('projects') 44 | .doc(context.params.projectId) 45 | .update(updateData) 46 | 47 | console.log('publishJson done') 48 | return true 49 | }) 50 | .catch((error) => console.error('JSON publishing failed', error.message)) 51 | } 52 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "editlayer-cms", 3 | "description": "Editlayer is headless CMS that saves content as JSON", 4 | "version": "0.7.0", 5 | "private": true, 6 | "scripts": { 7 | "init": "npm install && cd functions && npm install", 8 | "serve": "vue-cli-service serve --port 8000 --open", 9 | "deploy": "node scripts/deploy.js", 10 | "cors": "node scripts/cors.js", 11 | "open": "node scripts/open.js", 12 | "archive": "git archive --format=zip --output=editlayer-archive.zip HEAD" 13 | }, 14 | "dependencies": { 15 | "@jaames/iro": "^4.5.1", 16 | "animejs": "^3.1.0", 17 | "autosize": "^4.0.2", 18 | "axios": "^0.19.0", 19 | "blob-util": "^2.0.2", 20 | "caret-pos": "^1.2.1", 21 | "copy-to-clipboard": "^3.2.0", 22 | "dayjs": "^1.8.16", 23 | "firebase": "^6.5.0", 24 | "image-compressor.js": "^1.1.4", 25 | "is-upper-case": "^1.1.2", 26 | "js-base64": "^2.5.1", 27 | "js-video-url-parser": "^0.4.0", 28 | "lodash": "^4.17.15", 29 | "nanoid": "^2.1.0", 30 | "parse-json": "^5.0.0", 31 | "slugg": "^1.2.1", 32 | "title-case": "^2.1.1", 33 | "typeface-open-sans": "0.0.75", 34 | "v-calendar": "^0.9.7", 35 | "vue": "^2.6.10", 36 | "vue-awesome": "^3.5.4", 37 | "vue-codemirror": "^4.0.6", 38 | "vue-quill-editor": "^3.0.6", 39 | "vue-router": "^3.1.3", 40 | "vuex": "^3.1.1", 41 | "vuex-router-sync": "^5.0.0" 42 | }, 43 | "devDependencies": { 44 | "@vue/cli": "^3.11.0", 45 | "@vue/cli-plugin-babel": "^3.11.0", 46 | "@vue/cli-plugin-eslint": "^3.11.0", 47 | "@vue/cli-service": "^3.11.0", 48 | "@vue/eslint-config-standard": "^4.0.0", 49 | "babel-cli": "^6.26.0", 50 | "babel-core": "7.0.0-bridge.0", 51 | "babel-preset-env": "^1.7.0", 52 | "firebase-tools": "^7.3.0", 53 | "node-sass": "^4.12.0", 54 | "open": "^6.4.0", 55 | "sass-loader": "^8.0.0", 56 | "vue-template-compiler": "^2.6.10" 57 | }, 58 | "babel": { 59 | "presets": [ 60 | "@vue/app" 61 | ] 62 | }, 63 | "eslintConfig": { 64 | "root": true, 65 | "env": { 66 | "node": true 67 | }, 68 | "extends": [ 69 | "plugin:vue/essential", 70 | "@vue/standard" 71 | ], 72 | "rules": { 73 | "comma-dangle": [ 74 | "error", 75 | "always-multiline" 76 | ], 77 | "brace-style": [ 78 | "error", 79 | "stroustrup" 80 | ] 81 | }, 82 | "parserOptions": { 83 | "parser": "babel-eslint" 84 | } 85 | }, 86 | "postcss": { 87 | "plugins": { 88 | "autoprefixer": {} 89 | } 90 | }, 91 | "browserslist": [ 92 | "> 1%", 93 | "last 2 versions", 94 | "not ie <= 8" 95 | ] 96 | } 97 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raneio/editlayer/9fe46e34377c13a6626780acc98971eb023eca5a/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Editlayer 9 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /public/powered-by-editlayer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raneio/editlayer/9fe46e34377c13a6626780acc98971eb023eca5a/public/powered-by-editlayer.png -------------------------------------------------------------------------------- /rules/cors.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "origin": ["*"], 4 | "method": ["GET"], 5 | "maxAgeSeconds": 3600 6 | } 7 | ] 8 | -------------------------------------------------------------------------------- /rules/firestore.rules: -------------------------------------------------------------------------------- 1 | service cloud.firestore { 2 | match /databases/{database}/documents { 3 | 4 | match /users/{userId} { 5 | allow read: 6 | if request.auth != null 7 | && request.auth.uid == userId; 8 | } 9 | 10 | match /projects/{projectId} { 11 | function existingData() { 12 | return resource.data; 13 | } 14 | 15 | function incomingData() { 16 | return request.resource.data; 17 | } 18 | 19 | function allowListProject() { 20 | return request.auth != null 21 | && existingData().users[request.auth.uid].email == request.auth.token.email; 22 | } 23 | 24 | function allowCreateProject() { 25 | return request.auth != null 26 | && get(/databases/$(database)/documents/users/$(request.auth.uid)).data.permissions.createProject == true 27 | && incomingData().users[request.auth.uid].email == request.auth.token.email 28 | && incomingData().users[request.auth.uid].userExist == true 29 | && incomingData().users[request.auth.uid].permissions.updateDraft == true 30 | && incomingData().users[request.auth.uid].permissions.updateSchema == true 31 | && incomingData().users[request.auth.uid].permissions.updateSettings == true 32 | && incomingData().users[request.auth.uid].permissions.updateUsers == true 33 | && incomingData().users[request.auth.uid].permissions.publish == true 34 | && incomingData().users[request.auth.uid].permissions.createJob == true 35 | && projectId != 'index' 36 | && projectId != 'edit' 37 | && projectId != 'content' 38 | && projectId != 'schema' 39 | && projectId != 'settings' 40 | && projectId != 'project'; 41 | } 42 | 43 | function allowUpdateDraft() { 44 | return request.auth != null 45 | && existingData().users[request.auth.uid].permissions.updateDraft == true 46 | && request.writeFields.size() == 1 47 | && request.writeFields[0].matches('^draft\\..*'); 48 | } 49 | 50 | function allowUpdatePublished() { 51 | return request.auth != null 52 | && existingData().users[request.auth.uid].permissions.publish == true 53 | && request.writeFields.size() == 1 54 | && request.writeFields[0] == 'published'; 55 | } 56 | 57 | function allowUpdateSchema() { 58 | return request.auth != null 59 | && existingData().users[request.auth.uid].permissions.updateSchema == true 60 | && request.writeFields.size() == 1 61 | && request.writeFields[0] == 'schema'; 62 | } 63 | 64 | function allowUpdateSettings() { 65 | return request.auth != null 66 | && existingData().users[request.auth.uid].permissions.updateSettings == true 67 | && request.writeFields.size() == 1 68 | && request.writeFields[0].matches('^settings\\..*'); 69 | } 70 | 71 | function allowUpdateUsers() { 72 | return request.auth != null 73 | && existingData().users[request.auth.uid].permissions.updateUsers == true 74 | && request.writeFields.size() == 1 75 | && request.writeFields[0].matches('^users\\..*'); 76 | } 77 | 78 | allow list: 79 | if allowListProject(); 80 | 81 | allow create: 82 | if allowCreateProject(); 83 | 84 | allow update: 85 | if allowUpdateDraft() 86 | || allowUpdatePublished() 87 | || allowUpdateSchema() 88 | || allowUpdateSettings() 89 | || allowUpdateUsers(); 90 | } 91 | 92 | match /projects/{projectId}/versions/{versionId} { 93 | allow create: 94 | if request.auth != null 95 | && get(/databases/$(database)/documents/projects/$(projectId)).data.users[request.auth.uid].permissions.publish == true; 96 | } 97 | 98 | match /projects/{projectId}/jobs/{jobId} { 99 | allow create: 100 | if request.auth != null 101 | && get(/databases/$(database)/documents/projects/$(projectId)).data.users[request.auth.uid].permissions.createJob == true; 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /rules/storage.rules: -------------------------------------------------------------------------------- 1 | service firebase.storage { 2 | match /b/{bucket}/o { 3 | match /{projectId}/{file} { 4 | allow write: if request.auth != null 5 | && resource == null 6 | && request.resource.size < 5 * 1024 * 1024 7 | && request.resource.contentType.matches('image/.*'); 8 | 9 | allow read: if request.auth != null 10 | && resource.contentType.matches('image/.*'); 11 | } 12 | 13 | // match /{projectId}/public/{file} { 14 | // allow read: if resource.contentType.matches('application/json; charset=utf-8'); 15 | // } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /scripts/cors.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | const execa = require('execa') 4 | 5 | ;(async () => { 6 | const firebasersPath = path.join(__dirname, '/../.firebaserc') 7 | const firebaserc = JSON.parse(fs.readFileSync(firebasersPath).toString()) 8 | const aliases = Object.keys(firebaserc.projects) 9 | const arguments = process.argv.slice(2) 10 | const job = arguments[0] 11 | const alias = arguments[1] 12 | const envPath = path.join(__dirname, `/../.env.${alias}.local`) 13 | 14 | // Is gsutil installed 15 | try { 16 | execa.shellSync(`gsutil version`) 17 | } 18 | catch (error) { 19 | console.log(` 20 | You need to install gsutil 21 | https://cloud.google.com/storage/docs/gsutil_install 22 | `) 23 | return false 24 | } 25 | 26 | // Job is required 27 | if (!['allow', 'disallow', 'status'].includes(job)) { 28 | console.log(` 29 | Job "${job}" is not available. Use can use the following: 30 | - allow 31 | - disallow 32 | - status 33 | Example: npm run cors allow production 34 | `) 35 | return false 36 | } 37 | 38 | // Alias need to be in .firebaserc 39 | if (!aliases.includes(alias)) { 40 | console.log(` 41 | Alias "${alias}" is not available. Use can use the following: 42 | - ${aliases.join('\n - ')} 43 | Example: npm run deploy production 44 | `) 45 | return false 46 | } 47 | 48 | // Get storage bucket name 49 | const storageBucket = fs.readFileSync(envPath) 50 | .toString() 51 | .split('\n') 52 | .filter(line => { 53 | return line.startsWith('VUE_APP_FIREBASE_STORAGE_BUCKET=') 54 | }) 55 | .toString() 56 | .replace('VUE_APP_FIREBASE_STORAGE_BUCKET=', '') 57 | 58 | // Run the job 59 | if (job === 'allow') { 60 | console.log(`Allow CORS on the alias ${alias}`) 61 | const rulesCorsPath = path.join(__dirname, `/../rules/cors.json`) 62 | execa.shellSync(`gsutil cors set ${rulesCorsPath} gs://${storageBucket}`, { stdio: 'inherit' }) 63 | } 64 | else if (job === 'disallow') { 65 | console.log(`Disallow CORS on the alias ${alias}`) 66 | const tempPath = path.join(__dirname, `/tempDisallow.json`) 67 | fs.writeFileSync(tempPath, '[]', 'utf-8') 68 | execa.shellSync(`gsutil cors set ${tempPath} gs://${storageBucket}`, { stdio: 'inherit' }) 69 | fs.unlinkSync(tempPath) 70 | } 71 | else if (job === 'status') { 72 | console.log(`Get CORS status from the alias ${alias}`) 73 | } 74 | 75 | execa.shellSync(`gsutil cors get gs://${storageBucket}`, { stdio: 'inherit' }) 76 | 77 | })().catch(err => console.error(err)) 78 | -------------------------------------------------------------------------------- /scripts/deploy.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | const execa = require('execa') 4 | 5 | const createEnvFile = (envPath, alias) => { 6 | const setupWeb = execa.shellSync(`firebase setup:web`) 7 | const envFileContent = setupWeb.stdout 8 | .toString() 9 | .split('\n') 10 | .map(line => { 11 | if (line.includes('": "')) { 12 | const pair = line.split('"') 13 | const key = pair[1] 14 | const value = pair[3] 15 | 16 | if (key === 'apiKey') { 17 | return `VUE_APP_FIREBASE_API_KEY=${value}` 18 | } 19 | else if (key === 'authDomain') { 20 | return `VUE_APP_FIREBASE_AUTH_DOMAIN=${value}` 21 | } 22 | else if (key === 'databaseURL') { 23 | return `VUE_APP_FIREBASE_DATABASE_URL=${value}` 24 | } 25 | else if (key === 'projectId') { 26 | return `VUE_APP_FIREBASE_PROJECT_ID=${value}` 27 | } 28 | else if (key === 'storageBucket') { 29 | return `VUE_APP_FIREBASE_STORAGE_BUCKET=${value}` 30 | } 31 | } 32 | 33 | return null 34 | }) 35 | .filter(line => line) 36 | .join('\n') 37 | 38 | fs.writeFileSync(envPath, envFileContent, 'utf-8') 39 | 40 | console.log(` 41 | Created file .env.${alias}.local 42 | ${envFileContent} 43 | `) 44 | } 45 | 46 | ;(async () => { 47 | const firebasersPath = path.join(__dirname, '/../.firebaserc') 48 | const firebaserc = JSON.parse(fs.readFileSync(firebasersPath).toString()) 49 | const aliases = Object.keys(firebaserc.projects) 50 | const arguments = process.argv.slice(2) 51 | const alias = arguments[0] 52 | const envPath = path.join(__dirname, `/../.env.${alias}.local`) 53 | 54 | // Is firebase-tools installed 55 | try { 56 | execa.shellSync(`npx firebase --version`) 57 | } 58 | catch (error) { 59 | console.log(` 60 | You need to Firebase Tools 61 | npm install -g firebase-tools 62 | https://firebase.google.com/docs/cli/ 63 | `) 64 | return false 65 | } 66 | 67 | // Alias need to be in .firebaserc 68 | if (!aliases.includes(alias)) { 69 | console.log(` 70 | Alias "${alias}" is not available. Use can use the following: 71 | - ${aliases.join('\n - ')} 72 | Example: npm run deploy production 73 | `) 74 | return false 75 | } 76 | 77 | // Lint 78 | execa.shellSync(`npx vue-cli-service lint`, { stdio: 'inherit' }) 79 | execa.shellSync(`npx eslint ./functions/src`, { stdio: 'inherit' }) 80 | 81 | // Use selected alias 82 | execa.shellSync(`npx firebase use ${alias}`, { stdio: 'inherit' }) 83 | 84 | // Create .env.[alias].local File if not exists or project id is changed 85 | if (!fs.existsSync(envPath)) { 86 | createEnvFile(envPath, alias) 87 | } 88 | else { 89 | const projectId = fs.readFileSync(envPath) 90 | .toString() 91 | .split('\n') 92 | .map(line => { 93 | const pair = line.split('=') 94 | const key = pair[0] 95 | const value = pair[1] 96 | 97 | if (key === 'VUE_APP_FIREBASE_PROJECT_ID') { 98 | return value 99 | } 100 | 101 | return null 102 | }) 103 | .filter(line => line) 104 | .toString() 105 | 106 | if (firebaserc.projects[alias] !== projectId) { 107 | createEnvFile(envPath, alias) 108 | } 109 | } 110 | 111 | // Build functions 112 | execa.shellSync('npx babel functions/src --out-dir functions/dist', { stdio: 'inherit' }) 113 | 114 | // Build app if not development 115 | if (alias !== 'development') { 116 | execa.shellSync(`npx vue-cli-service build --mode ${alias}`, { stdio: 'inherit' }) 117 | } 118 | 119 | // Deploy to Firebase 120 | if (alias !== 'development') { 121 | execa.shellSync('npx firebase deploy', { stdio: 'inherit' }) 122 | } 123 | else { 124 | execa.shellSync('npx firebase deploy --only functions,firestore,storage', { stdio: 'inherit' }) 125 | } 126 | 127 | // Back to development env if exsist 128 | if (aliases.includes('development')) { 129 | execa.shellSync(`npx firebase use development`, { stdio: 'inherit' }) 130 | } 131 | 132 | })().catch(err => console.error(err)) 133 | -------------------------------------------------------------------------------- /scripts/open.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | const execa = require('execa') 4 | const open = require('open') 5 | 6 | ;(async () => { 7 | const firebasersPath = path.join(__dirname, '/../.firebaserc') 8 | const firebaserc = JSON.parse(fs.readFileSync(firebasersPath).toString()) 9 | const aliases = Object.keys(firebaserc.projects) 10 | const arguments = process.argv.slice(2) 11 | const alias = arguments[0] 12 | const envPath = path.join(__dirname, `/../.env.${alias}.local`) 13 | 14 | // Alias need to be in .firebaserc 15 | if (!aliases.includes(alias)) { 16 | console.log(` 17 | Alias "${alias}" is not available. You can use the following: 18 | - ${aliases.join('\n - ')} 19 | Example: npm run deploy production 20 | `) 21 | return false 22 | } 23 | 24 | // Get url 25 | const url = fs.readFileSync(envPath) 26 | .toString() 27 | .split('\n') 28 | .filter(line => { 29 | return line.startsWith('VUE_APP_FIREBASE_AUTH_DOMAIN=') 30 | }) 31 | .toString() 32 | .replace('VUE_APP_FIREBASE_AUTH_DOMAIN=', 'https://') 33 | 34 | if (alias !== 'development') { 35 | console.log(`Opening URL ${url} in the browser.`) 36 | open(url, {wait: false}) 37 | } 38 | else { 39 | execa.shellSync(`npm run serve`, { stdio: 'inherit' }) 40 | } 41 | 42 | })().catch(err => console.error(err)) 43 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 90 | 91 | 122 | 123 | 174 | -------------------------------------------------------------------------------- /src/assets/image-background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raneio/editlayer/9fe46e34377c13a6626780acc98971eb023eca5a/src/assets/image-background.png -------------------------------------------------------------------------------- /src/components/content/Editor.vue: -------------------------------------------------------------------------------- 1 | 75 | 76 | 102 | 103 | 138 | -------------------------------------------------------------------------------- /src/components/content/PartnerInfo.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 25 | 26 | 34 | -------------------------------------------------------------------------------- /src/components/content/PublishButton.vue: -------------------------------------------------------------------------------- 1 | 90 | 91 | 124 | 125 | 168 | -------------------------------------------------------------------------------- /src/components/core/Alert.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 30 | 31 | 69 | -------------------------------------------------------------------------------- /src/components/core/Button.vue: -------------------------------------------------------------------------------- 1 | 45 | 46 | 62 | 63 | 150 | -------------------------------------------------------------------------------- /src/components/core/Card.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 35 | 36 | 91 | -------------------------------------------------------------------------------- /src/components/core/Heading.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 23 | 24 | 46 | -------------------------------------------------------------------------------- /src/components/core/Notification.vue: -------------------------------------------------------------------------------- 1 | 85 | 86 | 101 | 102 | 148 | -------------------------------------------------------------------------------- /src/components/core/registerAll.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Alert from '@/components/core/Alert' 3 | import Button from '@/components/core/Button' 4 | import Heading from '@/components/core/Heading' 5 | import Card from '@/components/core/Card' 6 | import Notification from '@/components/core/Notification' 7 | 8 | Vue.component('alert-core', Alert) 9 | Vue.component('button-core', Button) 10 | Vue.component('heading-core', Heading) 11 | Vue.component('card-core', Card) 12 | Vue.component('notification-core', Notification) 13 | -------------------------------------------------------------------------------- /src/components/dashboard/Navigation.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 45 | 46 | 89 | -------------------------------------------------------------------------------- /src/components/dashboard/NewProjectButton.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 50 | 51 | 55 | -------------------------------------------------------------------------------- /src/components/dashboard/NewProjectModal.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 59 | 60 | 83 | -------------------------------------------------------------------------------- /src/components/dashboard/Projects.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | 83 | 84 | 127 | -------------------------------------------------------------------------------- /src/components/navigation/BackButton.vue: -------------------------------------------------------------------------------- 1 | 109 | 110 | 117 | 118 | 123 | -------------------------------------------------------------------------------- /src/components/navigation/Breadcrumb.vue: -------------------------------------------------------------------------------- 1 | 104 | 105 | 131 | 132 | 156 | -------------------------------------------------------------------------------- /src/components/navigation/Navigation.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 85 | 86 | 142 | -------------------------------------------------------------------------------- /src/components/panel/Item.vue: -------------------------------------------------------------------------------- 1 | 106 | 107 | 131 | 132 | 217 | -------------------------------------------------------------------------------- /src/components/panel/ItemsFromObject.vue: -------------------------------------------------------------------------------- 1 | 92 | 93 | 113 | -------------------------------------------------------------------------------- /src/components/panel/Panel.vue: -------------------------------------------------------------------------------- 1 | 76 | 77 | 85 | 86 | 122 | -------------------------------------------------------------------------------- /src/components/schema/SchemaEditor.vue: -------------------------------------------------------------------------------- 1 | 102 | 103 | 129 | 130 | 156 | -------------------------------------------------------------------------------- /src/components/settings/DeleteProject.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 50 | 51 | 59 | -------------------------------------------------------------------------------- /src/components/settings/FileLocation.vue: -------------------------------------------------------------------------------- 1 | 46 | 47 | 86 | 87 | 110 | -------------------------------------------------------------------------------- /src/components/settings/NewUserButton.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 50 | 51 | 55 | -------------------------------------------------------------------------------- /src/components/settings/NewUserModal.vue: -------------------------------------------------------------------------------- 1 | 68 | 69 | 95 | 96 | 122 | -------------------------------------------------------------------------------- /src/components/settings/Users.vue: -------------------------------------------------------------------------------- 1 | 74 | 75 | 125 | 126 | 169 | -------------------------------------------------------------------------------- /src/components/settings/Webhook.vue: -------------------------------------------------------------------------------- 1 | 128 | 129 | 170 | 171 | 198 | -------------------------------------------------------------------------------- /src/components/utils/FooterContent.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 20 | 21 | 68 | -------------------------------------------------------------------------------- /src/components/utils/LoaderOverlay.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 15 | 16 | 47 | -------------------------------------------------------------------------------- /src/components/utils/Notifications.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 33 | 34 | 47 | -------------------------------------------------------------------------------- /src/editors/Checkbox.vue: -------------------------------------------------------------------------------- 1 | 58 | 59 | 78 | 79 | 93 | -------------------------------------------------------------------------------- /src/editors/Code.vue: -------------------------------------------------------------------------------- 1 | 74 | 75 | 86 | 87 | 91 | -------------------------------------------------------------------------------- /src/editors/Color.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 95 | 96 | 111 | 112 | 157 | -------------------------------------------------------------------------------- /src/editors/Image.vue: -------------------------------------------------------------------------------- 1 | 74 | 75 | 106 | 107 | 133 | -------------------------------------------------------------------------------- /src/editors/Markdown.vue: -------------------------------------------------------------------------------- 1 | 112 | 113 | 153 | 154 | 257 | -------------------------------------------------------------------------------- /src/editors/Number.vue: -------------------------------------------------------------------------------- 1 | 39 | 40 | 53 | 54 | 58 | -------------------------------------------------------------------------------- /src/editors/Radio.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 58 | 59 | 73 | -------------------------------------------------------------------------------- /src/editors/RichText.vue: -------------------------------------------------------------------------------- 1 | 134 | 135 | 167 | 168 | 248 | -------------------------------------------------------------------------------- /src/editors/Select.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 56 | 57 | 61 | -------------------------------------------------------------------------------- /src/editors/SelectImage.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 68 | 69 | 102 | -------------------------------------------------------------------------------- /src/editors/Text.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 48 | 49 | 53 | -------------------------------------------------------------------------------- /src/editors/Textarea.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 |