├── .babelrc
├── .eslintrc.json
├── .gitignore
├── .npmignore
├── LICENSE
├── Logo
├── Vuse_Icon_Round.png
├── Vuse_Icon_Round_Gradient.png
├── Vuse_Icon_Square.png
├── Vuse_Icon_Square_Gradient.png
├── Vuse_Logo Only.png
├── Vuse_Logo Only_Gradient.png
├── Vuse_Logo_Horizontal.png
├── Vuse_Logo_Horizontal_Gradient.png
├── Vuse_Logo_Vertical.png
└── Vuse_Logo_Vertical_Gradient.png
├── README.md
├── demo
├── App.vue
├── Uploader.vue
├── app.js
├── img
│ ├── airbnb.svg
│ ├── atlassian.svg
│ ├── avatar.png
│ ├── baianat.png
│ ├── covers
│ │ ├── hero1.png
│ │ ├── hero2.png
│ │ ├── newsletter.png
│ │ ├── section1.png
│ │ ├── section2.png
│ │ ├── social1.png
│ │ ├── social2.png
│ │ ├── social3.png
│ │ └── social4.png
│ ├── crown.svg
│ ├── facebook.svg
│ └── google.svg
├── index.html
├── render.html
├── sections
│ ├── forms
│ │ └── newsletter.vue
│ ├── hero
│ │ ├── hero1.vue
│ │ └── hero2.vue
│ ├── section
│ │ ├── section1.vue
│ │ └── section2.vue
│ └── social
│ │ ├── social1.vue
│ │ ├── social2.vue
│ │ ├── social3.vue
│ │ └── social4.vue
├── style
│ ├── _demo.styl
│ ├── colors.styl
│ ├── header.styl
│ ├── helper.styl
│ ├── main.styl
│ ├── scrolling.styl
│ ├── section.styl
│ ├── social.styl
│ ├── transition.styl
│ ├── user.styl
│ └── variables.styl
└── webpack.config.js
├── docs
├── .vuepress
│ ├── config.js
│ ├── enhanceApp.js
│ └── public
│ │ ├── img
│ │ ├── airbnb.svg
│ │ ├── atlassian.svg
│ │ ├── avatar.png
│ │ ├── baianat.png
│ │ ├── covers
│ │ │ ├── hero1.png
│ │ │ ├── hero2.png
│ │ │ ├── newsletter.png
│ │ │ ├── section1.png
│ │ │ ├── section2.png
│ │ │ ├── social1.png
│ │ │ ├── social2.png
│ │ │ ├── social3.png
│ │ │ └── social4.png
│ │ ├── crown.svg
│ │ ├── facebook.svg
│ │ ├── google.svg
│ │ └── logo.png
│ │ └── style.css
├── API.md
├── README.md
├── example.md
├── exporting.md
├── getting-started.md
├── section.md
└── styler.md
├── package.json
├── scripts
├── build.js
├── config.js
└── deploy.sh
├── src
├── components
│ ├── VuseBuilder.vue
│ ├── VuseIcon.js
│ ├── VuseRenderer.vue
│ └── VuseStyler.vue
├── index.esm.js
├── index.js
├── mixin.js
├── plugins
│ ├── pwa.js
│ └── scrolling.js
├── section.js
├── seeder.js
├── styler.js
├── stylus
│ ├── _app.styl
│ ├── colors.styl
│ └── variables.styl
├── types.js
├── util.js
└── vuse.js
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["@babel/preset-env", { "modules": false }]
4 | ],
5 | "env": {
6 | "test": {
7 | "presets": [
8 | ["@babel/preset-env", { "targets": { "node": "current" }}]
9 | ]
10 | }
11 | }
12 | }
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "standard",
4 | "plugin:vue/recommended"
5 | ],
6 |
7 | "rules": {
8 | "semi": "off",
9 | "no-new": "off",
10 | "vue/valid-template-root": "off"
11 | }
12 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Dependency directories
9 | node_modules/
10 | jspm_packages/
11 |
12 | # Optional npm cache directory
13 | .npm
14 |
15 | # Optional eslint cache
16 | .eslintcache
17 |
18 | # Yarn Integrity file
19 | .yarn-integrity
20 |
21 | # dotenv environment variables file
22 | .env
23 |
24 | # visual studio code
25 | .vscode
26 |
27 | # macOS custom Icon
28 | Icon/
29 |
30 | # vuepress output
31 | docs/.vuepress/dist
32 |
33 | # dist folders
34 | dist
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | /docs
2 | /dev
3 | /src
4 | /static
5 | /scripts
6 | .*
7 | yarn.lock
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Baianat
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 |
--------------------------------------------------------------------------------
/Logo/Vuse_Icon_Round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/baianat/vuse/e6678f2f6ba86e36aea5c9ba76cce04a0a3c0bf1/Logo/Vuse_Icon_Round.png
--------------------------------------------------------------------------------
/Logo/Vuse_Icon_Round_Gradient.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/baianat/vuse/e6678f2f6ba86e36aea5c9ba76cce04a0a3c0bf1/Logo/Vuse_Icon_Round_Gradient.png
--------------------------------------------------------------------------------
/Logo/Vuse_Icon_Square.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/baianat/vuse/e6678f2f6ba86e36aea5c9ba76cce04a0a3c0bf1/Logo/Vuse_Icon_Square.png
--------------------------------------------------------------------------------
/Logo/Vuse_Icon_Square_Gradient.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/baianat/vuse/e6678f2f6ba86e36aea5c9ba76cce04a0a3c0bf1/Logo/Vuse_Icon_Square_Gradient.png
--------------------------------------------------------------------------------
/Logo/Vuse_Logo Only.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/baianat/vuse/e6678f2f6ba86e36aea5c9ba76cce04a0a3c0bf1/Logo/Vuse_Logo Only.png
--------------------------------------------------------------------------------
/Logo/Vuse_Logo Only_Gradient.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/baianat/vuse/e6678f2f6ba86e36aea5c9ba76cce04a0a3c0bf1/Logo/Vuse_Logo Only_Gradient.png
--------------------------------------------------------------------------------
/Logo/Vuse_Logo_Horizontal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/baianat/vuse/e6678f2f6ba86e36aea5c9ba76cce04a0a3c0bf1/Logo/Vuse_Logo_Horizontal.png
--------------------------------------------------------------------------------
/Logo/Vuse_Logo_Horizontal_Gradient.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/baianat/vuse/e6678f2f6ba86e36aea5c9ba76cce04a0a3c0bf1/Logo/Vuse_Logo_Horizontal_Gradient.png
--------------------------------------------------------------------------------
/Logo/Vuse_Logo_Vertical.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/baianat/vuse/e6678f2f6ba86e36aea5c9ba76cce04a0a3c0bf1/Logo/Vuse_Logo_Vertical.png
--------------------------------------------------------------------------------
/Logo/Vuse_Logo_Vertical_Gradient.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/baianat/vuse/e6678f2f6ba86e36aea5c9ba76cce04a0a3c0bf1/Logo/Vuse_Logo_Vertical_Gradient.png
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Vuse
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | > Logo by [chimzycash](https://github.com/chimzycash)
10 |
11 | **WIP: Vuse active development is now halted.**
12 |
13 | Advanced page/email builder based on [Vue.js](https://vuejs.org/).
14 |
15 | [documentation](https://baianat.github.io/vuse/)
16 |
17 | ## Credits
18 |
19 | - Logo by [chimzycash](https://github.com/chimzycash).
20 |
21 | ## License
22 |
23 | MIT
24 |
--------------------------------------------------------------------------------
/demo/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
16 |
--------------------------------------------------------------------------------
/demo/Uploader.vue:
--------------------------------------------------------------------------------
1 |
2 | div.uploader
3 | img(:src="src")
4 | input.uploader-input(
5 | type="file"
6 | ref="uploader"
7 | @change="updateImage"
8 | v-if="$builder.isEditing && mode === 'input'"
9 | )
10 |
11 |
12 |
44 |
45 |
68 |
--------------------------------------------------------------------------------
/demo/app.js:
--------------------------------------------------------------------------------
1 | // Vuse scripts
2 | import Vue from 'vue';
3 | import pwa from '../src/plugins/pwa';
4 | import Vuse from '../src';
5 |
6 | // demo scripts
7 | import './style/_demo.styl';
8 | import App from './App.vue';
9 | import Uploader from './Uploader'
10 | import hero1 from './sections/hero/hero1';
11 | import hero2 from './sections/hero/hero2';
12 | import section1 from './sections/section/section1';
13 | import section2 from './sections/section/section2';
14 | import social1 from './sections/social/social1';
15 | import social2 from './sections/social/social2';
16 | import social3 from './sections/social/social3';
17 | import social4 from './sections/social/social4';
18 | import newsletter from './sections/forms/newsletter';
19 |
20 | // add the uploader to the list of sub-components.
21 | Vuse.mix({
22 | components: {
23 | Uploader
24 | }
25 | });
26 |
27 | // register components.
28 | Vuse.component(hero1);
29 | Vuse.component(hero2);
30 | Vuse.component(section1);
31 | Vuse.component(section2);
32 | Vuse.component(social1);
33 | Vuse.component(social2);
34 | Vuse.component(social3);
35 | Vuse.component(social4);
36 | Vuse.component(newsletter);
37 |
38 | // install pwa plugin.
39 | Vuse.use(pwa);
40 |
41 | // install the builder
42 | Vue.use(Vuse, {
43 | // main css file
44 | assets: {
45 | css: 'css/style.css'
46 | },
47 | // builder default themes
48 | themes: [{
49 | name: 'Theme 1',
50 | sections: [hero1, section1, social1, social3, newsletter]
51 | }, {
52 | name: 'Theme 2',
53 | sections: [hero2, section2, social3, social4, newsletter]
54 | }]
55 | });
56 |
57 | new Vue({
58 | el: '#app',
59 | render: h => h(App)
60 | });
61 |
--------------------------------------------------------------------------------
/demo/img/airbnb.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/demo/img/atlassian.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/demo/img/avatar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/baianat/vuse/e6678f2f6ba86e36aea5c9ba76cce04a0a3c0bf1/demo/img/avatar.png
--------------------------------------------------------------------------------
/demo/img/baianat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/baianat/vuse/e6678f2f6ba86e36aea5c9ba76cce04a0a3c0bf1/demo/img/baianat.png
--------------------------------------------------------------------------------
/demo/img/covers/hero1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/baianat/vuse/e6678f2f6ba86e36aea5c9ba76cce04a0a3c0bf1/demo/img/covers/hero1.png
--------------------------------------------------------------------------------
/demo/img/covers/hero2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/baianat/vuse/e6678f2f6ba86e36aea5c9ba76cce04a0a3c0bf1/demo/img/covers/hero2.png
--------------------------------------------------------------------------------
/demo/img/covers/newsletter.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/baianat/vuse/e6678f2f6ba86e36aea5c9ba76cce04a0a3c0bf1/demo/img/covers/newsletter.png
--------------------------------------------------------------------------------
/demo/img/covers/section1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/baianat/vuse/e6678f2f6ba86e36aea5c9ba76cce04a0a3c0bf1/demo/img/covers/section1.png
--------------------------------------------------------------------------------
/demo/img/covers/section2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/baianat/vuse/e6678f2f6ba86e36aea5c9ba76cce04a0a3c0bf1/demo/img/covers/section2.png
--------------------------------------------------------------------------------
/demo/img/covers/social1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/baianat/vuse/e6678f2f6ba86e36aea5c9ba76cce04a0a3c0bf1/demo/img/covers/social1.png
--------------------------------------------------------------------------------
/demo/img/covers/social2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/baianat/vuse/e6678f2f6ba86e36aea5c9ba76cce04a0a3c0bf1/demo/img/covers/social2.png
--------------------------------------------------------------------------------
/demo/img/covers/social3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/baianat/vuse/e6678f2f6ba86e36aea5c9ba76cce04a0a3c0bf1/demo/img/covers/social3.png
--------------------------------------------------------------------------------
/demo/img/covers/social4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/baianat/vuse/e6678f2f6ba86e36aea5c9ba76cce04a0a3c0bf1/demo/img/covers/social4.png
--------------------------------------------------------------------------------
/demo/img/crown.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
7 |
8 |
9 |
11 |
12 |
13 |
14 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/demo/img/facebook.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/demo/img/google.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/demo/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Vuse
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/demo/render.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Vuse
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/demo/sections/forms/newsletter.vue:
--------------------------------------------------------------------------------
1 |
2 | section.header(
3 | v-styler:section="$sectionData.classes"
4 | :class="$sectionData.classes"
5 | )
6 | .container
7 | .grid.is-center
8 | .row
9 | h3.header-title Sign up for our newsletter
10 |
11 | form.row(@submit.prevent="onSubmit")
12 | .column.is-desktop-4
13 | .input-group.has-itemAfter
14 | input.input(type="text" name="email" placeholder="Email address" v-model="$sectionData.form.email")
15 | a.button(
16 | @click.prevent="onSubmit"
17 | :class="$sectionData.action.classes"
18 | :href="$sectionData.action.href"
19 | v-html="$sectionData.action.text"
20 | v-styler="$sectionData.action"
21 | )
22 |
23 |
24 |
55 |
--------------------------------------------------------------------------------
/demo/sections/hero/hero1.vue:
--------------------------------------------------------------------------------
1 |
2 | section.header(
3 | v-styler="$sectionData.classes"
4 | :class="$sectionData.classes"
5 | )
6 | .container
7 | .grid
8 | .column.is-desktop-6.add-center-vertical
9 | h3.header-title(
10 | v-html="$sectionData.title"
11 | v-styler="$sectionData.title"
12 | )
13 | p.header-content(
14 | v-html="$sectionData.content"
15 | v-styler="$sectionData.content"
16 | )
17 | a.button(
18 | :class="$sectionData.button.classes"
19 | :href="$sectionData.button.href"
20 | v-html="$sectionData.button.text"
21 | v-styler="$sectionData.button"
22 | )
23 | .column.is-desktop-6
24 | uploader(
25 | class="header-image"
26 | path="$sectionData.images[0]"
27 | )
28 |
29 |
30 |
52 |
--------------------------------------------------------------------------------
/demo/sections/hero/hero2.vue:
--------------------------------------------------------------------------------
1 |
2 | section.header(
3 | v-styler="$sectionData.classes"
4 | :class="$sectionData.classes"
5 | )
6 | .container
7 | .grid
8 | .column.is-desktop-6.is-offset-screen-3.add-center-horizontal.add-padding.add-text-center
9 | h3.header-title(
10 | v-html="$sectionData.title"
11 | v-styler="$sectionData.title"
12 | )
13 | p.header-content(
14 | v-html="$sectionData.content"
15 | v-styler="$sectionData.content"
16 | )
17 | a.button(
18 | :class="$sectionData.button.classes"
19 | :href="$sectionData.button.href"
20 | v-html="$sectionData.button.text"
21 | v-styler="$sectionData.button"
22 | )
23 | .column.is-desktop-8.is-offset-screen-2
24 | uploader(
25 | class="header-image"
26 | path="$sectionData.images[0]"
27 | )
28 |
29 |
30 |
52 |
--------------------------------------------------------------------------------
/demo/sections/section/section1.vue:
--------------------------------------------------------------------------------
1 |
2 | section.section(
3 | v-styler="$sectionData.classes"
4 | :class="$sectionData.classes"
5 | )
6 | .container
7 | .grid.is-center
8 | .column(
9 | :class="gridClasses[0]"
10 | v-styler:grid="$sectionData.columns[0].grid"
11 | )
12 | h2.section-title(
13 | v-html="$sectionData.columns[0].title"
14 | v-styler="$sectionData.columns[0].title"
15 | )
16 | p.section-paragraph(
17 | v-html="$sectionData.columns[0].content"
18 | v-styler="$sectionData.columns[0].content"
19 | )
20 | .column.is-offset-screen-1(
21 | :class="gridClasses[1]"
22 | v-styler:grid="$sectionData.columns[1].grid"
23 | )
24 | h2.section-title(
25 | v-html="$sectionData.columns[1].title"
26 | v-styler="$sectionData.columns[1].title"
27 | )
28 | p.section-paragraph(
29 | v-html="$sectionData.columns[1].content"
30 | v-styler="$sectionData.columns[1].content"
31 | )
32 |
33 |
34 |
69 |
--------------------------------------------------------------------------------
/demo/sections/section/section2.vue:
--------------------------------------------------------------------------------
1 |
2 | section.section(
3 | v-styler:section="$sectionData.classes"
4 | :class="$sectionData.classes"
5 | )
6 | .container
7 | .grid.is-center
8 | .column(
9 | :class="gridClasses[0]"
10 | v-styler="$sectionData.columns[0].grid"
11 | )
12 | h2.section-title(
13 | v-html="$sectionData.columns[0].title"
14 | v-styler="$sectionData.columns[0].title"
15 | )
16 | p.section-paragraph(
17 | v-html="$sectionData.columns[0].content"
18 | v-styler="$sectionData.columns[0].content"
19 | )
20 | .column(
21 | :class="gridClasses[1]"
22 | v-styler="$sectionData.columns[1].grid"
23 | )
24 | h2.section-title(
25 | v-html="$sectionData.columns[1].title"
26 | v-styler="$sectionData.columns[1].title"
27 | )
28 | p.section-paragraph(
29 | v-html="$sectionData.columns[1].content"
30 | v-styler="$sectionData.columns[1].content"
31 | )
32 | .column(
33 | :class="gridClasses[2]"
34 | v-styler="$sectionData.columns[2].grid"
35 | )
36 | h2.section-title(
37 | v-html="$sectionData.columns[2].title"
38 | v-styler="$sectionData.columns[2].title"
39 | )
40 | p.section-paragraph(
41 | v-html="$sectionData.columns[2].content"
42 | v-styler="$sectionData.columns[2].content"
43 | )
44 |
45 |
46 |
78 |
--------------------------------------------------------------------------------
/demo/sections/social/social1.vue:
--------------------------------------------------------------------------------
1 |
2 | section.social(
3 | v-styler:section="$sectionData.classes"
4 | :class="$sectionData.classes"
5 | )
6 | .container
7 | .grid.is-center
8 | .column.is-desktop-2.social-item
9 | h6.social-number(
10 | v-html="$sectionData.columns[0].content"
11 | v-styler="$sectionData.columns[0].content"
12 | )
13 | b.social-keyword(
14 | v-html="$sectionData.columns[0].title"
15 | v-styler="$sectionData.columns[0].title"
16 | )
17 | .column.is-desktop-2.social-item
18 | h6.social-number(
19 | v-html="$sectionData.columns[1].content"
20 | v-styler="$sectionData.columns[1].content"
21 | )
22 | b.social-keyword(
23 | v-html="$sectionData.columns[1].title"
24 | v-styler="$sectionData.columns[1].title"
25 | )
26 | .column.is-desktop-2.social-item
27 | h6.social-number(
28 | v-html="$sectionData.columns[2].content"
29 | v-styler="$sectionData.columns[2].content"
30 | )
31 | b.social-keyword(
32 | v-html="$sectionData.columns[2].title"
33 | v-styler="$sectionData.columns[2].title"
34 | )
35 |
36 |
37 |
66 |
--------------------------------------------------------------------------------
/demo/sections/social/social2.vue:
--------------------------------------------------------------------------------
1 |
2 | section.social(
3 | v-styler:section="$sectionData.classes"
4 | :class="$sectionData.classes"
5 | )
6 | .container
7 | .grid
8 | .row.is-center
9 | .column.is-desktop-6
10 | p.social-quote(
11 | v-html="$sectionData.content"
12 | v-styler="$sectionData.content"
13 | )
14 | .row.is-center
15 | .column.is-desktop-2
16 | .user.is-alt
17 | uploader(
18 | class="user-avatar"
19 | path="$sectionData.images[0]"
20 | )
21 | .user-data
22 | h4.user-name Ahmed
23 | span.user-caption SEO at Baianat
24 |
25 |
26 |
27 |
47 |
--------------------------------------------------------------------------------
/demo/sections/social/social3.vue:
--------------------------------------------------------------------------------
1 |
2 | section.social(
3 | v-styler:section="$sectionData.classes"
4 | :class="$sectionData.classes"
5 | )
6 | .container
7 | .grid
8 | .row.is-center
9 | .column.is-12.is-desktop-7
10 | h3.social-title(
11 | v-html="$sectionData.title"
12 | v-styler="$sectionData.title"
13 | )
14 | .column.is-12.is-desktop-7
15 | p.social-content(
16 | v-html="$sectionData.content"
17 | v-styler="$sectionData.content"
18 | )
19 | .row.is-center
20 | .column.is-desktop-2(v-for="(logo, index) in $sectionData.images")
21 | uploader(
22 | class="social-logo"
23 | :path="`$sectionData.images[${index}]`"
24 | )
25 |
26 |
27 |
51 |
--------------------------------------------------------------------------------
/demo/sections/social/social4.vue:
--------------------------------------------------------------------------------
1 |
2 | section.social(
3 | v-styler:section="$sectionData.classes"
4 | :class="$sectionData.classes"
5 | )
6 | .container
7 | .grid
8 | .row.is-center
9 | .column.is-12
10 | h3.social-title(
11 | v-html="$sectionData.title"
12 | v-styler="$sectionData.title"
13 | )
14 | .row.is-center
15 | .column.is-12.is-desktop-4
16 | p(
17 | v-html="$sectionData.columns[0].content"
18 | v-styler="$sectionData.columns[0].content"
19 | )
20 | .user
21 | uploader(
22 | class="user-avatar"
23 | path="$sectionData.images[0]"
24 | )
25 | .user-data
26 | h4.user-name Ahmed
27 | span.user-caption SEO at Baianat
28 |
29 | .column.is-12.is-desktop-4
30 | p(
31 | v-html="$sectionData.columns[1].content"
32 | v-styler:text="$sectionData.columns[1].content"
33 | )
34 | .user
35 | uploader(
36 | class="user-avatar"
37 | path="$sectionData.images[1]"
38 | )
39 | .user-data
40 | h4.user-name Ahmed
41 | span.user-caption SEO at Baianat
42 |
43 | .column.is-12.is-desktop-4
44 | p(
45 | v-html="$sectionData.columns[2].content"
46 | v-styler:text="$sectionData.columns[2].content"
47 | )
48 | .user
49 | uploader(
50 | class="user-avatar"
51 | path="$sectionData.images[2]"
52 | )
53 | .user-data
54 | h4.user-name Ahmed
55 | span.user-caption SEO at Baianat
56 |
57 |
58 |
86 |
--------------------------------------------------------------------------------
/demo/style/_demo.styl:
--------------------------------------------------------------------------------
1 | @import variables
2 | @import '~@baianat/base.framework/base'
3 |
4 | @import main
5 | @import header
6 | @import section
7 | @import social
8 | @import user
9 | @import helper
10 | @import scrolling
11 |
12 | .column
13 | transition: 0.2s
14 |
--------------------------------------------------------------------------------
/demo/style/colors.styl:
--------------------------------------------------------------------------------
1 | /*
2 | * Color theme
3 | */
4 | $magenta ?= #eb008b
5 | $blue ?= #0072FF
6 | $cyan ?= #00d4f0
7 | $green ?= #18d88b
8 | $yellow ?= #ffdd57
9 | $orange ?= #ffa557
10 | $red ?= #ff3d3d
11 | $purple ?= #a324ea
12 |
13 | /*
14 | * Graysacle
15 | */
16 | $black ?= #000
17 | $dark ?= #323c47
18 | $gray ?= #c1c1c1
19 | $gray-dark ?= darken($gray, 10%)
20 | $gray-light ?= lighten($gray, 10%)
21 | $light ?= #f5f5f5
22 | $white ?= #fff
23 |
--------------------------------------------------------------------------------
/demo/style/header.styl:
--------------------------------------------------------------------------------
1 | .header
2 | width: 100%
3 | padding: 50px
4 | color: $white
5 | &-title
6 | font-size: 50px
7 | line-height: 1em
8 | margin: 20px 0
9 | font-weight: lighter
10 | &-content
11 | font-size: 16px
12 | margin-bottom: 50px
13 | font-weight: lighter
14 | &-image
15 | >img
16 | width 100%
17 | &,
18 | &.is-red
19 | background-image: linear-gradient(30deg, $red, $yellow)
20 | &.is-black
21 | background-image: linear-gradient(30deg, $black, lighten($black, 30%))
22 | &.is-green
23 | background-image: linear-gradient(30deg, $green, $yellow)
24 | &.is-blue
25 | background-image: linear-gradient(30deg, $blue, lighten($blue, 30%))
26 | &.is-white
27 | background-image: linear-gradient(45deg, darken($white, 10%), $white)
--------------------------------------------------------------------------------
/demo/style/helper.styl:
--------------------------------------------------------------------------------
1 | +prefix-classes('add-')
2 | .center-horizontal
3 | display: flex
4 | flex-direction: column
5 | justify-content: center
6 | align-items: center
7 | .center-vertical
8 | display: flex
9 | flex-direction: column
10 | justify-content: center
11 | align-items: flex-start
12 | .padding
13 | padding: 20px
14 | .text-center
15 | text-align: center
16 | .full-width
17 | width: 100%
--------------------------------------------------------------------------------
/demo/style/main.styl:
--------------------------------------------------------------------------------
1 | ::selection
2 | color: inherit
3 |
4 |
5 | .button
6 | white-space: nowrap
--------------------------------------------------------------------------------
/demo/style/scrolling.styl:
--------------------------------------------------------------------------------
1 | .is-active
2 | img
3 | opacity: 1
4 | transition: 0.4s
5 | p
6 | h1
7 | h2
8 | h3
9 | h6
10 | b
11 | opacity: 1
12 | transform: translate3d(0, 0, 0)
13 | transition: 0.4s
14 |
15 | .is-inactive
16 | img
17 | opacity: 0
18 | transition: 0.4s
19 | p
20 | h1
21 | h2
22 | h3
23 | h6
24 | b
25 | opacity: 0
26 | transform: translate3d(0, -200px, 0)
27 | transition: 0.4s
--------------------------------------------------------------------------------
/demo/style/section.styl:
--------------------------------------------------------------------------------
1 | .section
2 | padding: 20px 30px
3 | &-title
4 | font-size: 30px
5 | color: $dark
6 |
7 | &-paragraph
8 | font-size: 16px
9 | color: lighten($dark, 20%)
10 |
11 | &.is-red
12 | background: $red
13 | &.is-black
14 | background: $black
15 | &.is-green
16 | background: $green
17 | &.is-blue
18 | background: $blue
19 | &.is-white
20 | background: $white
--------------------------------------------------------------------------------
/demo/style/social.styl:
--------------------------------------------------------------------------------
1 | .social
2 | padding: 100px 0
3 | &-number
4 | font-size: 50px
5 | margin: 20px 0 50px
6 | &-item
7 | display: flex
8 | flex-direction: column
9 | justify-content: center
10 | align-items: center
11 | &-title
12 | font-size: 30px
13 | margin: 30px 0
14 | text-align: center
15 | &-content
16 | font-size: 20px
17 | margin: 0 0 40px
18 | text-align: center
19 | &-logo
20 | width: 100%
21 | margin: 20px 0
22 | @extend $flexCenter
23 |
24 | &-quote
25 | font-size: 30px
26 | color: $black
27 | quotes: "“" "”" "‘" "’"
28 | &:before
29 | content: open-quote
30 | margin-right: 20px
31 | font-size: 40px
32 | &:after
33 | content: close-quote
34 | margin-left: 20px
35 | font-size: 40px
36 |
37 | &.is-red
38 | background: $red
39 | &.is-black
40 | background: $black
41 | &.is-green
42 | background: $green
43 | &.is-blue
44 | background: $blue
--------------------------------------------------------------------------------
/demo/style/transition.styl:
--------------------------------------------------------------------------------
1 | .fade-enter-active
2 | .fade-leave-active
3 | transition: all .3s ease
4 | transform: scaleX(1)
5 |
6 | .fade-enter
7 | .fade-leave-to
8 | transform: scaleX(0)
--------------------------------------------------------------------------------
/demo/style/user.styl:
--------------------------------------------------------------------------------
1 | .user
2 | display: flex
3 | align-items: center
4 | margin: 10px 0
5 | &.is-alt
6 | justify-content: center
7 | flex-direction: column
8 | &-avatar
9 | border-radius: 50%
10 | width: 60px
11 | height: 60px
12 | margin-right: 20px
13 | flex-shrink: 0
14 | overflow: hidden
15 | .is-alt &
16 | margin: 0 0 20px
17 | &-data
18 | text-align: left
19 | white-space: nowrap
20 | .is-alt &
21 | text-align: center
22 | &-name
23 | font-size: 16px
24 | color: $black
25 | margin: 5px 0
26 | &-caption
27 | font-size: 16px
28 | color: $gray
29 | margin: 0 0 5px
30 |
--------------------------------------------------------------------------------
/demo/style/variables.styl:
--------------------------------------------------------------------------------
1 |
2 | $flexCenter
3 | display: flex
4 | justify-content: center
5 | align-items: center
--------------------------------------------------------------------------------
/demo/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const webpack = require('webpack');
3 | const VueLoaderPlugin = require('vue-loader/lib/plugin')
4 | const HtmlWebpackPlugin = require('html-webpack-plugin');
5 | const CleanWebpackPlugin = require('clean-webpack-plugin');
6 | const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin');
7 | const CopyWebpackPlugin = require('copy-webpack-plugin');
8 | const ProgressBarPlugin = require('progress-bar-webpack-plugin');
9 | const MiniCssExtractPlugin = require("mini-css-extract-plugin");
10 |
11 | const env = process.env.NODE_ENV;
12 | const production = env === 'production';
13 |
14 | // render page
15 | const page = (name) => {
16 | return new HtmlWebpackPlugin({
17 | inject: true,
18 | template: path.join(__dirname, `./${name}.html`),
19 | filename: path.join(__dirname, `./dist/${name}.html`)
20 | });
21 | };
22 |
23 | const config = {
24 | mode: production ? 'production' : 'development',
25 | devtool: production ? 'source-map' : 'cheap-source-map',
26 | entry: {
27 | app: path.join(__dirname, './app.js')
28 | },
29 | output: {
30 | path: path.join(__dirname, 'dist'),
31 | filename: 'js/[name].js'
32 | },
33 | plugins: [
34 | new MiniCssExtractPlugin({
35 | filename: 'css/style.css'
36 | }),
37 | new CleanWebpackPlugin(['./dist']),
38 | new VueLoaderPlugin(),
39 | new webpack.LoaderOptionsPlugin({ options: {} }),
40 | new FriendlyErrorsWebpackPlugin(),
41 | new ProgressBarPlugin(),
42 | new CopyWebpackPlugin([{ from: path.join(__dirname, 'img'), to: './img/' }]),
43 | page('index'),
44 | page('render')
45 | ],
46 | watchOptions: {
47 | aggregateTimeout: 300,
48 | poll: 1000
49 | },
50 | devServer: {
51 | historyApiFallback: true,
52 | hot: true,
53 | inline: true,
54 | stats: 'errors-only',
55 | host: '0.0.0.0',
56 | port: 8080
57 | },
58 | module: {
59 | rules: [
60 | {
61 | test: /.js$/,
62 | exclude: /node_modules/,
63 | loader: 'eslint-loader',
64 | enforce: 'pre'
65 | },
66 | {
67 | test: /.js$/,
68 | exclude: /node_modules/,
69 | use: {
70 | loader: 'babel-loader',
71 | options: { babelrc: true }
72 | }
73 | },
74 | {
75 | test: /\.vue$/,
76 | loader: 'eslint-loader',
77 | enforce: 'pre'
78 | },
79 | {
80 | test: /\.vue$/,
81 | loader: 'vue-loader'
82 | },
83 | {
84 | test: /\.css$/,
85 | loader: ['style-loader', 'css-loader']
86 | },
87 | {
88 | test: /\.styl(us)?$/,
89 | use: [
90 | MiniCssExtractPlugin.loader,
91 | 'css-loader',
92 | 'stylus-loader'
93 | ]
94 | },
95 | {
96 | test: /\.(ttf|eot|svg)(\?.*)?$/,
97 | loader: 'file-loader',
98 | options: {
99 | name: 'font/[name].[ext]'
100 | }
101 | },
102 | {
103 | test: /.pug$/,
104 | exclude: /node_modules/,
105 | loader: 'pug-plain-loader',
106 | options: {
107 | pretty: true
108 | }
109 | }
110 | ]
111 | },
112 | resolve: {
113 | extensions: ['.js', '.vue', '.json']
114 | }
115 | };
116 |
117 | module.exports = config;
118 |
--------------------------------------------------------------------------------
/docs/.vuepress/config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | title: 'Vuse',
3 | description: 'Advanced page builder based on Vue.js',
4 | base: '/vuse/',
5 | themeConfig: {
6 | lastUpdated: 'Last Updated',
7 | repo: 'baianat/vuse',
8 | docsRepo: 'baianat/vuse',
9 | docsDir: 'docs',
10 | docsBranch: 'master',
11 | editLinks: true,
12 | sidebar: [
13 | '/',
14 | '/getting-started',
15 | '/section',
16 | '/styler',
17 | '/exporting',
18 | '/API',
19 | ],
20 | nav: [
21 | { text: 'API', link: '/API' },
22 | { text: 'Example', link: '/example' },
23 | ]
24 | }
25 | }
--------------------------------------------------------------------------------
/docs/.vuepress/enhanceApp.js:
--------------------------------------------------------------------------------
1 | // Vuse scripts
2 | import Vuse from '../../src';
3 |
4 | // demo scripts
5 | import '../../demo/style/_demo.styl';
6 | import Uploader from '../../demo/Uploader.vue'
7 | import hero1 from '../../demo/sections/hero/hero1';
8 | import hero2 from '../../demo/sections/hero/hero2';
9 | import section1 from '../../demo/sections/section/section1';
10 | import section2 from '../../demo/sections/section/section2';
11 | import social1 from '../../demo/sections/social/social1';
12 | import social2 from '../../demo/sections/social/social2';
13 | import social3 from '../../demo/sections/social/social3';
14 | import social4 from '../../demo/sections/social/social4';
15 | import newsletter from '../../demo/sections/forms/newsletter';
16 |
17 | // add the uploader to the list of sub-components.
18 | Vuse.mix({
19 | components: {
20 | Uploader
21 | }
22 | });
23 |
24 | // register components.
25 | Vuse.component(hero1);
26 | Vuse.component(hero2);
27 | Vuse.component(section1);
28 | Vuse.component(section2);
29 | Vuse.component(social1);
30 | Vuse.component(social2);
31 | Vuse.component(social3);
32 | Vuse.component(social4);
33 | Vuse.component(newsletter);
34 |
35 | export default ({ Vue }) => {
36 | // install the builder
37 | Vue.use(Vuse, {
38 | // main css file
39 | assets: {
40 | css: 'style.css'
41 | },
42 | // builder default themes
43 | themes: [{
44 | name: 'Theme 1',
45 | sections: [hero1, section1, social1, social3, newsletter]
46 | }, {
47 | name: 'Theme 2',
48 | sections: [hero2, section2, social3, social4, newsletter]
49 | }]
50 | });
51 | }
52 |
53 |
54 | function normalize (component) {
55 | component.cover = component.cover.replace('static', '.');
56 | }
--------------------------------------------------------------------------------
/docs/.vuepress/public/img/airbnb.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/.vuepress/public/img/atlassian.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/.vuepress/public/img/avatar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/baianat/vuse/e6678f2f6ba86e36aea5c9ba76cce04a0a3c0bf1/docs/.vuepress/public/img/avatar.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/img/baianat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/baianat/vuse/e6678f2f6ba86e36aea5c9ba76cce04a0a3c0bf1/docs/.vuepress/public/img/baianat.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/img/covers/hero1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/baianat/vuse/e6678f2f6ba86e36aea5c9ba76cce04a0a3c0bf1/docs/.vuepress/public/img/covers/hero1.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/img/covers/hero2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/baianat/vuse/e6678f2f6ba86e36aea5c9ba76cce04a0a3c0bf1/docs/.vuepress/public/img/covers/hero2.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/img/covers/newsletter.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/baianat/vuse/e6678f2f6ba86e36aea5c9ba76cce04a0a3c0bf1/docs/.vuepress/public/img/covers/newsletter.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/img/covers/section1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/baianat/vuse/e6678f2f6ba86e36aea5c9ba76cce04a0a3c0bf1/docs/.vuepress/public/img/covers/section1.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/img/covers/section2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/baianat/vuse/e6678f2f6ba86e36aea5c9ba76cce04a0a3c0bf1/docs/.vuepress/public/img/covers/section2.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/img/covers/social1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/baianat/vuse/e6678f2f6ba86e36aea5c9ba76cce04a0a3c0bf1/docs/.vuepress/public/img/covers/social1.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/img/covers/social2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/baianat/vuse/e6678f2f6ba86e36aea5c9ba76cce04a0a3c0bf1/docs/.vuepress/public/img/covers/social2.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/img/covers/social3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/baianat/vuse/e6678f2f6ba86e36aea5c9ba76cce04a0a3c0bf1/docs/.vuepress/public/img/covers/social3.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/img/covers/social4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/baianat/vuse/e6678f2f6ba86e36aea5c9ba76cce04a0a3c0bf1/docs/.vuepress/public/img/covers/social4.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/img/crown.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
7 |
8 |
9 |
11 |
12 |
13 |
14 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/docs/.vuepress/public/img/facebook.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/.vuepress/public/img/google.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/.vuepress/public/img/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/baianat/vuse/e6678f2f6ba86e36aea5c9ba76cce04a0a3c0bf1/docs/.vuepress/public/img/logo.png
--------------------------------------------------------------------------------
/docs/API.md:
--------------------------------------------------------------------------------
1 | # API
2 |
3 | Your section components will have some properties injected to help you customize their behavior in building phase and production/render phase.
4 |
5 | ## $section
6 |
7 | An instance of the [section class](https://github.com/baianat/vuse/blob/master/src/js/section.js) that represents this component.
8 |
9 | ## $builder
10 |
11 | An instance of the singleton [builder class](https://github.com/baianat/vuse/blob/master/src/js/builder.js)
12 |
13 | ## $sectionData
14 |
15 | Is a computed property that mirrors `$section.data` which contains the current values (text, images, etc...) for the section.
16 |
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | ---
2 | home: true
3 | actionText: Getting Started →
4 | actionLink: ./getting-started
5 | heroImage: /img/logo.png
6 | ---
7 |
--------------------------------------------------------------------------------
/docs/example.md:
--------------------------------------------------------------------------------
1 | ---
2 | navbar: true
3 | sidebar: false
4 | editLink: false
5 | pageClass: example
6 | ---
7 |
8 |
9 |
10 |
19 |
20 |
36 |
--------------------------------------------------------------------------------
/docs/exporting.md:
--------------------------------------------------------------------------------
1 | # Exporting
2 |
3 | There are three ways to export the built page: preview, pwa or json. When clicking the save button the `b-builder` component emits a saved event with the builder instance as its payload, which exposes an `export` method.
4 |
5 | ```html
6 |
7 |
8 |
9 |
10 |
22 | ```
--------------------------------------------------------------------------------
/docs/getting-started.md:
--------------------------------------------------------------------------------
1 | # getting-started
2 |
3 | ## What is this
4 |
5 | This builder (sections builder) reuses your Vue components as **editable sections** to produce an interactive page builder to the end user, you can use it as a prototyping tool as well as it is sort-of a block builder.
6 |
7 | The user/developer can then export the builder for usability in multiple formats, the following are the officially supported ones:
8 |
9 | - `json` A json object which can be later used to re-render a page, particularly useful if you plan to have dynamic pages or want to store them in a Database.
10 | - `preview` opens a new page without the editable logic in new tab to see the end result.
11 | - `pwa` produces a zip file which contains all the page files and images neatly packed, this is probably the format you will use for page/prototype landing page builder, The project is augmented by default with service workers to support offline viewing.
12 |
13 | The builder is just a Vue plugin, so you can integrate this into your own projects without needing to create separate bundles for it.
14 |
15 | ## Installation
16 |
17 | ### Package managers
18 |
19 | First step is to install it using `yarn` or `npm`:
20 |
21 | ```bash
22 | npm install vuse
23 |
24 | # or use yarn
25 | yarn add vuse
26 | ```
27 |
28 | ### CDNs
29 |
30 | Or add it as a script tag in your projects.
31 |
32 | - [unpkg](https://unpkg.com/vuse)
33 |
34 | ```html
35 |
36 |
37 | ```
38 |
39 | ### Usage
40 |
41 | ::: tip
42 | If you added it using a script tag, you can skip this section. as it will be auto-installed for you
43 | :::
44 |
45 | ```js
46 | import Builder from 'vuse';
47 |
48 | Vue.use(Builder);
49 | ```
50 |
51 | You can start using the `b-builder` component to build things now.
52 |
53 | This package does not include any sections. The builder is just a system of helpers for managing customizable sections, seeding fake data, exporting views and re-rendering them. The logic for your components/sections is written by you eventually. **we will be adding a huge number of sections soon to be used with this one**. You can use the included examples after reading the API to build your own for now.
--------------------------------------------------------------------------------
/docs/section.md:
--------------------------------------------------------------------------------
1 | # Section
2 |
3 | A section is the building block of the page, below is an example of a header section.
4 | ::: tip
5 | Examples use [pug](https://pugjs.org) template language to make it easier to work with templates.
6 | :::
7 |
8 | ```pug
9 |
10 | section.header(
11 | v-styler="$sectionData.classes"
12 | :class="[{'is-editable': $builder.isEditing}, $sectionData.classes]"
13 | )
14 | .container
15 | .grid
16 | .column.is-desktop-6.add-center-vertical
17 | h3.header-title(
18 | :class="{'is-editable': $builder.isEditing}"
19 | v-html="$sectionData.title"
20 | v-styler="$sectionData.title"
21 | )
22 | p.header-content(
23 | :class="{'is-editable': $builder.isEditing}"
24 | v-html="$sectionData.content"
25 | v-styler="$sectionData.content"
26 | )
27 | a.button(
28 | :class="[{'is-editable': $builder.isEditing}, $sectionData.button.classes]"
29 | :href="$sectionData.button.href"
30 | v-html="$sectionData.button.text"
31 | v-styler="$sectionData.button"
32 | )
33 | .column.is-desktop-6
34 | uploader(
35 | class="header-image"
36 | path="$sectionData.images[0]"
37 | )
38 |
39 |
40 |
61 | ```
62 |
63 | Each section has several elements that can be edited. Section data are stored in `$sectionData` object which is reactive.
64 |
65 | ## Adding the ability to edit elements in a section
66 |
67 | 1. Add `is-editable` class to it. Since editable state can be toggled off/on, it's always good to bind `is-editable` class to change when editing mode changes. e.g. `:class="{'is-editable': $builder.isEditing}"`
68 | 1. Add [`v-styler`](https://github.com/baianat/builder#v-styler) directive to the element
69 | 1. Bind the element’s innerHTML with its equivalent data e.g. `v-html="$sectionData.button.text"`
70 | 1. If you have any other data that `v-styler` changes, you have to bind it too. e.g. `:href="$sectionData.button.href"`
71 |
72 | Putting it all together
73 |
74 | ```html
75 |
81 | ```
82 |
83 | After creating the HTML structure, you should configure the section schema to use the built-in seeder to provide you with initial/fake values when the component is instantiated in build/edit mode. Or you can set the initial values yourself instead of seeding them.
84 |
85 | ```html
86 |
130 | ```
131 |
132 | ## Using the section
133 |
134 | Until now, we have only been creating our section component, we now need to introduce it to our builder so it can use it:
135 |
136 | ```js
137 | import Builder from 'vuse';
138 | import section from './components/sections/section';
139 |
140 | // Builder has Vue-like API when registering components.
141 | Builder.component(section);
142 | Vue.use(Builder);
143 |
144 | new Vue({
145 | el: '#app'
146 | });
147 | ```
148 |
149 | ```html
150 |
151 |
152 |
153 | ```
154 |
155 | You only have to register the component on the Builder plugin, which has a Vue-like API. This ensures your Vue global scope isn't polluted by the builder and keeps everything contained within the `b-builder` component.
--------------------------------------------------------------------------------
/docs/styler.md:
--------------------------------------------------------------------------------
1 | # Styler
2 |
3 | This directive is automatically injected in your section components, and can be used to facilitate editing elements greatly, since it has support for multiple element types like `div`, `a`, `button` and `p` tags as well.
4 |
5 | To tell styler which variable to update, you pass it as directive expression e.g. `v-styler="$sectionData.button"`
6 |
7 | The styler directive has four types `text`, `button`, `section` or `grid`. By default, the directive can know the type implicitly, from the element tag or from the provided schema.
8 |
9 | If you want to explicitly specify the type, you can pass it as a directive modifier e.g. `v-styler.button="$sectionData.button"`.
10 |
11 | ## How to use
12 |
13 | coming soon...
14 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vuse",
3 | "version": "0.1.1",
4 | "description": "Vue.js Page Builder",
5 | "author": "Abdelrahman Ismail ",
6 | "module": "dist/vuse.esm.js",
7 | "unpkg": "dist/vuse.min.js",
8 | "main": "dist/vuse.js",
9 | "scripts": {
10 | "dev": "webpack-dev-server --hot --inline --config ./demo/webpack.config.js",
11 | "build": "cross-env NODE_ENV=production node scripts/build.js",
12 | "build:demo": "cross-env NODE_ENV=production webpack --config ./demo/webpack.config.js",
13 | "docs:dev": "vuepress dev docs",
14 | "docs:build": "vuepress build docs",
15 | "docs:deploy": "scripts/deploy.sh",
16 | "lint": "eslint ./src --fix"
17 | },
18 | "devDependencies": {
19 | "@babel/core": "^7.0.0-rc.1",
20 | "@babel/plugin-proposal-class-properties": "^7.0.0-rc.1",
21 | "@babel/plugin-proposal-object-rest-spread": "^7.0.0-rc.1",
22 | "@babel/preset-env": "^7.0.0-rc.1",
23 | "babel-loader": "^8.0.0-beta",
24 | "chalk": "^2.4.1",
25 | "clean-webpack-plugin": "^0.1.19",
26 | "copy-webpack-plugin": "^4.5.1",
27 | "cross-env": "^5.0.5",
28 | "css-loader": "^1.0.0",
29 | "eslint": "^5.3.0",
30 | "eslint-config-standard": "^11.0.0",
31 | "eslint-loader": "^2.1.0",
32 | "eslint-plugin-import": "^2.7.0",
33 | "eslint-plugin-node": "^7.0.1",
34 | "eslint-plugin-promise": "^3.5.0",
35 | "eslint-plugin-standard": "^3.0.1",
36 | "eslint-plugin-vue": "^4.7.1",
37 | "file-loader": "^1.1.11",
38 | "filesize": "^3.6.1",
39 | "friendly-errors-webpack-plugin": "^1.6.1",
40 | "gzip-size": "^5.0.0",
41 | "html-webpack-plugin": "^4.0.0-alpha",
42 | "mini-css-extract-plugin": "^0.4.4",
43 | "mkdirp": "^0.5.1",
44 | "progress-bar-webpack-plugin": "^1.10.0",
45 | "pug": "^2.0.0-rc.3",
46 | "pug-plain-loader": "^1.0.0",
47 | "rollup": "^0.64.1",
48 | "rollup-plugin-buble": "^0.19.2",
49 | "rollup-plugin-commonjs": "^9.1.5",
50 | "rollup-plugin-css-only": "^0.4.0",
51 | "rollup-plugin-node-resolve": "^3.0.0",
52 | "rollup-plugin-replace": "^2.0.0",
53 | "rollup-plugin-uglify": "^4.0.0",
54 | "rollup-plugin-vue": "^4.3.2",
55 | "style-loader": "^0.22.1",
56 | "stylus": "^0.54.5",
57 | "stylus-loader": "^3.0.1",
58 | "stylus-relative-loader": "^3.4.0",
59 | "util": "^0.11.0",
60 | "vue": "^2.5.17",
61 | "vue-loader": "^15.3.0",
62 | "vue-template-compiler": "^2.5.17",
63 | "vuepress": "^0.14.8",
64 | "webpack": "^4.16.5",
65 | "webpack-cli": "^3.1.0",
66 | "webpack-dev-server": "^3.1.5"
67 | },
68 | "dependencies": {
69 | "@baianat/base.framework": "^2.0.0-beta.0",
70 | "intersection-observer": "^0.5.0",
71 | "jszip": "^3.1.4",
72 | "lodash-es": "^4.17.4",
73 | "popper.js": "^1.14.4",
74 | "save-as": "^0.1.8",
75 | "sortablejs": "^1.6.1",
76 | "vue-server-renderer": "2.5.17"
77 | },
78 | "license": "MIT",
79 | "files": [
80 | "dist/*.js",
81 | "dist/*.css"
82 | ],
83 | "keywords": [
84 | "page-builder",
85 | "vuejs",
86 | "ES6"
87 | ],
88 | "maintainers": [
89 | {
90 | "name": "Abdelrahman Awad",
91 | "email": "logaretm1@gmail.com"
92 | },
93 | {
94 | "name": "Abdelrahman Ismail",
95 | "email": "abdelrahman3d@gmail.com"
96 | }
97 | ]
98 | }
99 |
--------------------------------------------------------------------------------
/scripts/build.js:
--------------------------------------------------------------------------------
1 | const chalk = require('chalk');
2 | const mkdirpNode = require('mkdirp');
3 | const { promisify } = require('util');
4 | const { rollup } = require('rollup');
5 | const { paths, configs, utils } = require('./config');
6 | const mkdirp = promisify(mkdirpNode);
7 |
8 | async function buildConfig (build) {
9 | await mkdirp(paths.dist);
10 | const bundleName = build.output.file.replace(paths.dist, '');
11 | console.log(chalk.cyan(`📦 Generating ${bundleName}...`));
12 |
13 | const bundle = await rollup(build.input);
14 | await bundle.write(build.output);
15 |
16 | console.log(chalk.green(`👍 ${bundleName} ${utils.stats({ path: build.output.file })}`));
17 | }
18 |
19 | async function build () {
20 | await Promise.all(Object.keys(configs).map(key => {
21 | return buildConfig(configs[key]).catch(err => {
22 | console.log(err);
23 | });
24 | }));
25 | process.exit(0);
26 | }
27 |
28 | build();
29 |
--------------------------------------------------------------------------------
/scripts/config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const fs = require('fs');
3 | const replace = require('rollup-plugin-replace');
4 | const vue = require('rollup-plugin-vue').default;
5 | const resolve = require('rollup-plugin-node-resolve');
6 | const css = require('rollup-plugin-css-only');
7 | const buble = require('rollup-plugin-buble');
8 | const commonjs = require('rollup-plugin-commonjs');
9 | const filesize = require('filesize');
10 | const gzipSize = require('gzip-size');
11 | const { uglify } = require('rollup-plugin-uglify');
12 |
13 | const version = process.env.VERSION || require('../package.json').version;
14 |
15 | const common = {
16 | banner:
17 | `/**
18 | * Vuse v${version}
19 | * (c) ${new Date().getFullYear()} Baianat
20 | * @license MIT
21 | */`,
22 | paths: {
23 | input: path.join(__dirname, '../src/index.js'),
24 | src: path.join(__dirname, '../src/'),
25 | dist: path.join(__dirname, '../dist/')
26 | },
27 | builds: {
28 | umd: {
29 | file: 'vuse.js',
30 | format: 'umd',
31 | name: 'vuse',
32 | env: 'development'
33 | },
34 | umdMin: {
35 | file: 'vuse.min.js',
36 | format: 'umd',
37 | name: 'vuse',
38 | env: 'production'
39 | },
40 | esm: {
41 | input: path.join(__dirname, '../src/index.esm.js'),
42 | file: 'vuse.esm.js',
43 | format: 'es'
44 | }
45 | }
46 | };
47 |
48 | function genConfig (options) {
49 | const config = {
50 | description: '',
51 | input: {
52 | input: options.input || common.paths.input,
53 | plugins: [
54 | commonjs(),
55 | replace({ __VERSION__: version }),
56 | css(),
57 | vue({ css: false }),
58 | resolve(),
59 | buble()
60 | ]
61 | },
62 | output: {
63 | banner: common.banner,
64 | name: options.name,
65 | format: options.format,
66 | file: path.join(common.paths.dist, options.file)
67 | }
68 | };
69 |
70 | if (options.env) {
71 | config.input.plugins.unshift(replace({
72 | 'process.env.NODE_ENV': JSON.stringify(options.env)
73 | }));
74 | }
75 |
76 | if (options.env === 'production') {
77 | config.input.plugins.push(uglify());
78 | }
79 |
80 | return config;
81 | };
82 |
83 | const configs = Object.keys(common.builds).reduce((prev, key) => {
84 | prev[key] = genConfig(common.builds[key]);
85 |
86 | return prev;
87 | }, {});
88 |
89 | module.exports = {
90 | configs,
91 | uglifyOptions: common.uglifyOptions,
92 | paths: common.paths,
93 | utils: {
94 | stats ({ path }) {
95 | const code = fs.readFileSync(path);
96 | const { size } = fs.statSync(path);
97 | const gzipped = gzipSize.sync(code);
98 |
99 | return `| Size: ${filesize(size)} | Gzip: ${filesize(gzipped)}`;
100 | }
101 | }
102 | };
103 |
--------------------------------------------------------------------------------
/scripts/deploy.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | set -e
4 |
5 | npm run docs:build
6 |
7 | cd docs/.vuepress/dist
8 |
9 | git init
10 | git add -A
11 | git commit -m 'deploy'
12 |
13 | git push -f git@github.com:baianat/vuse.git master:gh-pages
14 |
15 | cd -
--------------------------------------------------------------------------------
/src/components/VuseBuilder.vue:
--------------------------------------------------------------------------------
1 |
2 | div
3 | div#artboard.artboard(
4 | ref="artboard"
5 | :class="{ 'is-sorting': $builder.isSorting, 'is-editable': $builder.isEditing }"
6 | )
7 | component(v-for='section in $builder.sections'
8 | :is='section.name'
9 | :key='section.id'
10 | :id='section.id'
11 | )
12 |
13 | .controller
14 | .controller-intro(v-if="showIntro && !this.$builder.sections.length")
15 | label(for="projectName") Hello, start your project
16 | input.controller-input(
17 | id="projectName"
18 | placeholder="project name"
19 | v-model="title"
20 | )
21 | template(v-if="themes")
22 | .controller-themes
23 | button.controller-theme(
24 | v-for="theme in themes"
25 | @click="addTheme(theme)"
26 | )
27 | | {{ theme.name }}
28 |
29 | .controller-panel
30 | button.controller-button.is-green(
31 | tooltip-position="top"
32 | tooltip="export"
33 | @click="submit"
34 | )
35 | VuseIcon(name='download')
36 | button.controller-button.is-red(
37 | v-if="!tempSections"
38 | tooltip-position="top"
39 | tooltip="clear sections"
40 | @click="clearSections"
41 | )
42 | VuseIcon(name='trash')
43 | button.controller-button.is-gray(
44 | v-if="tempSections"
45 | tooltip-position="top"
46 | tooltip="undo"
47 | @click="undo"
48 | )
49 | VuseIcon(name='undo')
50 | button.controller-button.is-blue(
51 | tooltip-position="top"
52 | tooltip="sorting"
53 | :class="{ 'is-red': $builder.isSorting }"
54 | @click="toggleSort"
55 | )
56 | VuseIcon(name='sort')
57 | button.controller-button.is-blue(
58 | tooltip-position="top"
59 | tooltip="add section"
60 | :class="{ 'is-red': listShown, 'is-rotated': listShown }"
61 | :disabled="!$builder.isEditing"
62 | @click="newSection"
63 | )
64 | VuseIcon(name='plus')
65 |
66 | ul.menu(:class="{ 'is-visiable': listShown }" ref="menu")
67 | li.menu-group(v-for="(group, name) in groups" v-if="group.length")
68 | .menu-header(@click="toggleGroupVisibility")
69 | span.menu-title {{ name }}
70 | span.menu-icon
71 | VuseIcon(name='arrowDown')
72 | .menu-body
73 | template(v-for="section in group")
74 | a.menu-element(
75 | @click="addSection(section)"
76 | @drag="currentSection = section"
77 | )
78 | img.menu-elementImage(v-if="section.cover" :src="section.cover")
79 | span.menu-elementTitle {{ section.name }}
80 |
81 |
82 |
83 |
264 |
265 |
446 |
--------------------------------------------------------------------------------
/src/components/VuseIcon.js:
--------------------------------------------------------------------------------
1 | const icons = {
2 | plus: 'M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z',
3 | tic: 'M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z',
4 | sort: 'M14 5h8v2h-8zm0 5.5h8v2h-8zm0 5.5h8v2h-8zM2 11.5C2 15.08 4.92 18 8.5 18H9v2l3-3-3-3v2h-.5C6.02 16 4 13.98 4 11.5S6.02 7 8.5 7H12V5H8.5C4.92 5 2 7.92 2 11.5z',
5 | link: 'M3.9 12c0-1.71 1.39-3.1 3.1-3.1h4V7H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-1.9H7c-1.71 0-3.1-1.39-3.1-3.1zM8 13h8v-2H8v2zm9-6h-4v1.9h4c1.71 0 3.1 1.39 3.1 3.1s-1.39 3.1-3.1 3.1h-4V17h4c2.76 0 5-2.24 5-5s-2.24-5-5-5z',
6 | palettes: 'M12 3c-4.97 0-9 4.03-9 9s4.03 9 9 9c.83 0 1.5-.67 1.5-1.5 0-.39-.15-.74-.39-1.01-.23-.26-.38-.61-.38-.99 0-.83.67-1.5 1.5-1.5H16c2.76 0 5-2.24 5-5 0-4.42-4.03-8-9-8zm-5.5 9c-.83 0-1.5-.67-1.5-1.5S5.67 9 6.5 9 8 9.67 8 10.5 7.33 12 6.5 12zm3-4C8.67 8 8 7.33 8 6.5S8.67 5 9.5 5s1.5.67 1.5 1.5S10.33 8 9.5 8zm5 0c-.83 0-1.5-.67-1.5-1.5S13.67 5 14.5 5s1.5.67 1.5 1.5S15.33 8 14.5 8zm3 4c-.83 0-1.5-.67-1.5-1.5S16.67 9 17.5 9s1.5.67 1.5 1.5-.67 1.5-1.5 1.5z',
7 | close: 'M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z',
8 | bold: 'M15.6 10.79c.97-.67 1.65-1.77 1.65-2.79 0-2.26-1.75-4-4-4H7v14h7.04c2.09 0 3.71-1.7 3.71-3.79 0-1.52-.86-2.82-2.15-3.42zM10 6.5h3c.83 0 1.5.67 1.5 1.5s-.67 1.5-1.5 1.5h-3v-3zm3.5 9H10v-3h3.5c.83 0 1.5.67 1.5 1.5s-.67 1.5-1.5 1.5z',
9 | italic: 'M10 4v3h2.21l-3.42 8H6v3h8v-3h-2.21l3.42-8H18V4z',
10 | underline: 'M12 17c3.31 0 6-2.69 6-6V3h-2.5v8c0 1.93-1.57 3.5-3.5 3.5S8.5 12.93 8.5 11V3H6v8c0 3.31 2.69 6 6 6zm-7 2v2h14v-2H5z',
11 | center: 'M7 15v2h10v-2H7zm-4 6h18v-2H3v2zm0-8h18v-2H3v2zm4-6v2h10V7H7zM3 3v2h18V3H3z',
12 | left: 'M15 15H3v2h12v-2zm0-8H3v2h12V7zM3 13h18v-2H3v2zm0 8h18v-2H3v2zM3 3v2h18V3H3z',
13 | right: 'M3 21h18v-2H3v2zm6-4h12v-2H9v2zm-6-4h18v-2H3v2zm6-4h12V7H9v2zM3 3v2h18V3H3z',
14 | trash: 'M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zm2.46-7.12l1.41-1.41L12 12.59l2.12-2.12 1.41 1.41L13.41 14l2.12 2.12-1.41 1.41L12 15.41l-2.12 2.12-1.41-1.41L10.59 14l-2.13-2.12zM15.5 4l-1-1h-5l-1 1H5v2h14V4z',
15 | align: 'M3 21h18v-2H3v2zm0-4h18v-2H3v2zm0-4h18v-2H3v2zm0-4h18V7H3v2zm0-6v2h18V3H3z',
16 | textStyle: 'M23 7V1h-6v2H7V1H1v6h2v10H1v6h6v-2h10v2h6v-6h-2V7h2zM3 3h2v2H3V3zm2 18H3v-2h2v2zm12-2H7v-2H5V7h2V5h10v2h2v10h-2v2zm4 2h-2v-2h2v2zM19 5V3h2v2h-2zm-5.27 9h-3.49l-.73 2H7.89l3.4-9h1.4l3.41 9h-1.63l-.74-2zm-3.04-1.26h2.61L12 8.91l-1.31 3.83z',
17 | section: 'M10 18h5v-6h-5v6zm-6 0h5V5H4v13zm12 0h5v-6h-5v6zM10 5v6h11V5H10z',
18 | arrowDown: 'M7.41 7.84L12 12.42l4.59-4.58L18 9.25l-6 6-6-6z',
19 | arrowRight: 'M8.59 16.34l4.58-4.59-4.58-4.59L10 5.75l6 6-6 6z',
20 | arrowLeft: 'M15.41 16.09l-4.58-4.59 4.58-4.59L14 5.5l-6 6 6 6z',
21 | mobile: 'M15.5 1h-8C6.12 1 5 2.12 5 3.5v17C5 21.88 6.12 23 7.5 23h8c1.38 0 2.5-1.12 2.5-2.5v-17C18 2.12 16.88 1 15.5 1zm-4 21c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5zm4.5-4H7V4h9v14z',
22 | tablet: 'M21 4H3c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h18c1.1 0 1.99-.9 1.99-2L23 6c0-1.1-.9-2-2-2zm-2 14H5V6h14v12z',
23 | laptop: 'M20 18c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2H4c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2H0v2h24v-2h-4zM4 6h16v10H4V6z',
24 | monitor: 'M21 2H3c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h7v2H8v2h8v-2h-2v-2h7c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H3V4h18v12z',
25 | download: 'M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z',
26 | eye: 'M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z',
27 | undo: 'M12.5 8c-2.65 0-5.05.99-6.9 2.6L2 7v9h9l-3.62-3.62c1.39-1.16 3.16-1.88 5.12-1.88 3.54 0 6.55 2.31 7.6 5.5l2.37-.78C21.08 11.03 17.15 8 12.5 8z'
28 | };
29 |
30 | export default {
31 | functional: true,
32 | props: {
33 | name: {
34 | type: String,
35 | required: true,
36 | validator: (val) => {
37 | if (!(val in icons) && process.env.NODE_ENV !== 'production') {
38 | console.warn(`Invalid icon name "${val}"`);
39 | return false;
40 | }
41 |
42 | return true;
43 | }
44 | }
45 | },
46 | render (h, { props }) {
47 | const path = h('path', {
48 | attrs: {
49 | d: icons[props.name]
50 | }
51 | });
52 |
53 | return h(
54 | 'svg',
55 | {
56 | attrs: {
57 | version: '1.1',
58 | xmlns: 'http://www.w3.org/2000/svg',
59 | class: 'vuse-icon',
60 | viewBox: '0 0 24 24'
61 | }
62 | },
63 | [path]
64 | );
65 | }
66 | };
67 |
--------------------------------------------------------------------------------
/src/components/VuseRenderer.vue:
--------------------------------------------------------------------------------
1 |
2 | #artboard.artboard
3 | component(v-for='section in $builder.sections'
4 | :is='section.name'
5 | :key='section.id'
6 | :id='section.id'
7 | )
8 |
9 |
10 |
29 |
--------------------------------------------------------------------------------
/src/components/VuseStyler.vue:
--------------------------------------------------------------------------------
1 |
2 | .styler(
3 | ref="styler"
4 | id="styler"
5 | v-if="$builder.isEditing"
6 | :class="{ 'is-visible': isVisible }"
7 | @click.stop=""
8 | )
9 | ul.styler-list
10 | li(v-if="type === 'button' || type === 'section'")
11 | button.styler-button(@click="updateOption('colorer')")
12 | VuseIcon(name='palettes')
13 | li(v-if="type === 'button'")
14 | button.styler-button(@click="updateOption('link')")
15 | VuseIcon(name='link')
16 | li(v-if="type === 'header' || type === 'section'")
17 | button.styler-button(@click="removeSection")
18 | VuseIcon(name='trash')
19 | template(v-if="type === 'text'")
20 | li: button.styler-button(@click="updateOption('textColor')")
21 | VuseIcon(name='palettes')
22 | li: button.styler-button(@click="updateOption('align')")
23 | VuseIcon(name='align')
24 | li: button.styler-button(@click="updateOption('textStyle')")
25 | VuseIcon(name='textStyle')
26 | template(v-if="type === 'grid'")
27 | li: button.styler-button(@click="selectDevice('mobile')")
28 | VuseIcon(name='mobile')
29 | //- li: button.styler-button(@click="selectDevice('tablet')")
30 | //- VuseIcon(name='tablet')
31 | li: button.styler-button(@click="selectDevice('desktop')")
32 | VuseIcon(name='laptop')
33 | //- li: button.styler-button(@click="selectDevice('widescreen')")
34 | //- VuseIcon(name='monitor')
35 |
36 | ul.styler-list
37 | li(v-if="currentOption === 'colorer'")
38 | ul.colorer
39 | li(v-for="color in colors")
40 | input(
41 | type="radio"
42 | :id="`color${color.charAt(0).toUpperCase() + color.slice(1)}`"
43 | name="colorer"
44 | :value="color"
45 | v-model="colorerColor"
46 | )
47 | li(v-if="currentOption === 'textColor'")
48 | ul.colorer
49 | li(v-for="(color, index) in colors")
50 | input(
51 | type="radio"
52 | :id="`color${color.charAt(0).toUpperCase() + color.slice(1)}`"
53 | name="colorer"
54 | :value="textColors[index]"
55 | v-model="textColor"
56 | )
57 | li(v-if="currentOption === 'link'")
58 | .input-group.is-rounded.has-itemAfter.is-primary
59 | input.input(type="text" placeholder="type your link" v-model="url")
60 | button.button(@click="addLink")
61 | VuseIcon(name='link')
62 |
63 | li(v-if="currentOption === 'align'")
64 | ul.align
65 | li: button.styler-button(@click="execute('justifyleft')")
66 | VuseIcon(name='left')
67 | li: button.styler-button(@click="execute('justifycenter')")
68 | VuseIcon(name='center')
69 | li: button.styler-button(@click="execute('justifyright')")
70 | VuseIcon(name='right')
71 |
72 | li(v-if="currentOption === 'textStyle'")
73 | ul.align
74 | li: button.styler-button(@click="execute('bold')")
75 | VuseIcon(name='bold')
76 | li: button.styler-button(@click="execute('italic')")
77 | VuseIcon(name='italic')
78 | li: button.styler-button(@click="execute('underline')")
79 | VuseIcon(name='underline')
80 |
81 | li(v-if="currentOption === 'columnWidth'")
82 | ul.align
83 | li: button.styler-button(@click="gridValue--")
84 | VuseIcon(name='arrowLeft')
85 | li: input(type="number" min="0" max="12" v-model="gridValue").styler-input
86 | li: button.styler-button(@click="gridValue++")
87 | VuseIcon(name='arrowRight')
88 |
89 |
90 |
91 |
259 |
260 |
362 |
--------------------------------------------------------------------------------
/src/index.esm.js:
--------------------------------------------------------------------------------
1 | import Vuse from './vuse';
2 | import * as types from './types';
3 |
4 | const version = '__VERSION__';
5 |
6 | // Auto install if Vue is defined globally.
7 | if (typeof Vue !== 'undefined') {
8 | // eslint-disable-next-line
9 | Vue.use(Builder);
10 | }
11 |
12 | export {
13 | Vuse,
14 | types,
15 | version
16 | };
17 |
18 | export default Vuse;
19 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import Vuse from './vuse';
2 | import * as types from './types';
3 |
4 | // Auto install if Vue is defined globally.
5 | if (typeof Vue !== 'undefined') {
6 | // eslint-disable-next-line
7 | Vue.use(Builder);
8 | }
9 |
10 | Vuse.version = '__VERSION__';
11 | Vuse.types = types;
12 |
13 | export default Vuse;
14 |
--------------------------------------------------------------------------------
/src/mixin.js:
--------------------------------------------------------------------------------
1 | function installMixin ({ builder }) {
2 | builder.mixin = {
3 | provide: function providesBuilder () {
4 | const provides = {};
5 | if (this.$builder) {
6 | provides.$builder = this.$builder;
7 | }
8 |
9 | if (this.$section) {
10 | provides.$section = this.$section;
11 | }
12 |
13 | return provides;
14 | },
15 | beforeCreate () {
16 | this.$builder = builder;
17 | if (!this.$options.propsData || this.$options.propsData.id === undefined) {
18 | return;
19 | }
20 | this.$section = this.$builder.find(this.$options.propsData.id);
21 | this.$options.computed = {
22 | $sectionData: function getSectionData () {
23 | return this.$section.data;
24 | },
25 | gridClasses: function getGridClasses () {
26 | return this.$sectionData.columns.map(column => {
27 | return Object.keys(column.grid).map(device => {
28 | if (!column.grid[device]) {
29 | return '';
30 | }
31 | const prefix = this.$builder.columnsPrefix[device]
32 | return `${prefix}${column.grid[device]}`;
33 | });
34 | })
35 | }
36 | }
37 | },
38 | updated () {
39 | Array.from(this.$el.querySelectorAll('[contentEditable]')).forEach((el) => {
40 | el.contentEditable = this.$builder.isEditing;
41 | });
42 | }
43 | };
44 | };
45 |
46 | export default installMixin;
47 |
--------------------------------------------------------------------------------
/src/plugins/pwa.js:
--------------------------------------------------------------------------------
1 | import JSZip from 'jszip';
2 | import saveAs from 'save-as';
3 | import { getImageBlob, cleanDOM } from '../../src/util';
4 |
5 | /**
6 | * Adds a service worker that caches the static assets.
7 | */
8 | function createSW (output, { images = [] } = {}) {
9 | output.file('sw.js', `
10 | const staticCacheName = 'bbuilder-static-v1';
11 |
12 | self.addEventListener('install', function(event) {
13 | event.waitUntil(
14 | caches.open(staticCacheName).then(function(cache) {
15 | return cache.addAll([
16 | '/',
17 | '/assets/js/main.js',
18 | ${images.map(i => "'/assets/img/" + i.name + "'").join(',').trim(',')}
19 | ]);
20 | })
21 | );
22 | });
23 |
24 | function serveAsset(request) {
25 | return caches.open(staticCacheName).then(function(cache) {
26 | return cache.match(request).then(function(response) {
27 | if (response) return response;
28 |
29 | return fetch(request).then(function(networkResponse) {
30 | cache.put(request, networkResponse.clone());
31 | return networkResponse;
32 | });
33 | });
34 | });
35 | }
36 |
37 | self.addEventListener('fetch', function(event) {
38 | const requestUrl = new URL(event.request.url);
39 |
40 | if (requestUrl.origin === location.origin) {
41 | if (requestUrl.pathname === '/') {
42 | event.respondWith(caches.match('/'));
43 | return;
44 | }
45 | if (requestUrl.pathname.startsWith('/assets/')) {
46 | event.respondWith(serveAsset(event.request));
47 | return;
48 | }
49 | }
50 |
51 | event.respondWith(
52 | caches.match(event.request).then(function(response) {
53 | return response || fetch(event.request);
54 | })
55 | );
56 | });
57 | `);
58 |
59 | const scripts = output.folder('assets/js');
60 | scripts.file('main.js', `
61 | function registerSW () {
62 | if (!navigator.serviceWorker) return;
63 | navigator.serviceWorker.register('/sw.js').then(function (reg) {
64 | console.log('SW registered!');
65 | });
66 | }
67 |
68 | registerSW();
69 | `);
70 | }
71 |
72 | /**
73 | * Adds some PWA features.
74 | */
75 | function createPWA (output, payload) {
76 | createSW(output, payload);
77 | }
78 |
79 | function download (assets) {
80 | const frag = this.outputFragment();
81 | const images = Array.from(frag.querySelectorAll('img'));
82 | const artboard = frag.querySelector('#artboard');
83 | const title = document.title;
84 | const zip = new JSZip();
85 | const output = zip.folder('project');
86 | const imgFolder = output.folder('assets/img');
87 | const cssFolder = output.folder('assets/css');
88 |
89 | Promise.all(images.map((image) => {
90 | const imageLoader = getImageBlob(image.src);
91 | return imageLoader.then((img) => {
92 | imgFolder.file(img.name, img.blob, { base64: true });
93 | image.setAttribute('src', `assets/img/${img.name}`);
94 |
95 | return img;
96 | });
97 | })).then(images => {
98 | createPWA(output, { images });
99 | }).then(() => {
100 | return new Promise((resolve, reject) => {
101 | const assetsClient = new XMLHttpRequest();
102 | assetsClient.open('GET', assets.css);
103 | assetsClient.onload = function () {
104 | resolve(this.response);
105 | }
106 | assetsClient.send(null);
107 | }).then((content) => {
108 | cssFolder.file('app.css', content);
109 | return content;
110 | });
111 | }).then(() => {
112 | cleanDOM(frag);
113 | output.file('index.html',
114 | `
115 |
116 | ${title}
117 |
118 |
119 |
120 | ${artboard.innerHTML}
121 |
122 |
123 | `);
124 |
125 | zip.generateAsync({ type: 'blob' }).then((blob) => {
126 | saveAs(blob, 'project.zip');
127 | });
128 | });
129 | }
130 |
131 | export default function install ({ builder }) {
132 | builder.download = download;
133 | };
134 |
--------------------------------------------------------------------------------
/src/plugins/scrolling.js:
--------------------------------------------------------------------------------
1 | import 'intersection-observer';
2 |
3 | const callback = (entries, observer) => {
4 | entries.forEach(entry => {
5 | if (entry.intersectionRatio > 0.5) {
6 | entry.target.classList.add('is-active');
7 | entry.target.classList.remove('is-inactive');
8 | return;
9 | }
10 | entry.target.classList.add('is-inactive');
11 | entry.target.classList.remove('is-active');
12 | });
13 | }
14 |
15 | const observer = new IntersectionObserver(callback, {
16 | root: null,
17 | rootMargin: '0px',
18 | threshold: [0.1, 0.5, 0.9, 1]
19 | });
20 |
21 | const scrolling = (rootEl) => {
22 | if (!rootEl) return;
23 | let sections = Array.from(rootEl.querySelectorAll('section'));
24 | sections.forEach(section => {
25 | section.classList.add('is-inactive');
26 | observer.observe(section);
27 | });
28 | }
29 |
30 | export default function install ({ builder }) {
31 | builder.scrolling = scrolling;
32 | };
33 |
--------------------------------------------------------------------------------
/src/section.js:
--------------------------------------------------------------------------------
1 | import getPath from 'lodash-es/get';
2 | import toPath from 'lodash-es/toPath';
3 | import Seeder from './seeder';
4 |
5 | const SECTION_OPTIONS = {
6 | name: null,
7 | schema: {}
8 | };
9 |
10 | let counter = 0;
11 |
12 | export default class Section {
13 | constructor (options) {
14 | this.id = counter++;
15 | options = Object.assign({}, SECTION_OPTIONS, options);
16 | this.name = options.name;
17 | this.schema = options.schema;
18 | this.data = options.data || Seeder.seed(options.schema);
19 | this.stylers = [];
20 | }
21 |
22 | set (name, value) {
23 | const path = toPath(name);
24 | const prop = path.pop();
25 |
26 | path.shift();
27 | const obj = path.length === 0 ? this.data : getPath(this.data, path);
28 | if (typeof value === 'function') {
29 | value(obj[prop]);
30 | return;
31 | }
32 |
33 | obj[prop] = value;
34 | }
35 |
36 | get (name) {
37 | const path = toPath(name);
38 | const prop = path.pop();
39 | path.shift();
40 | const obj = path.length === 0 ? this.data : getPath(this.data, path);
41 |
42 | return obj[prop];
43 | }
44 |
45 | destroy () {
46 | this.stylers.forEach(styler => styler.$destroy())
47 | }
48 | };
49 |
--------------------------------------------------------------------------------
/src/seeder.js:
--------------------------------------------------------------------------------
1 | import * as types from './types';
2 | import { isObject } from './util';
3 |
4 | const ASSETS_DIR = '.';
5 | const data = new Map([
6 | [types.Title, 'Awesome title'],
7 | [types.Text, 'We\'re creating the best place to go when starting a new business or company.With Baianat you can instantly search domain names, social media handles, and see your logo in beautiful logotypes.'],
8 | [types.Avatar, `${ASSETS_DIR}/img/avatar.png`],
9 | [types.Logo, `${ASSETS_DIR}/img/google.svg`],
10 | [types.Link, 'http://example.com'],
11 | [types.Image, `${ASSETS_DIR}/img/baianat.png`],
12 | [types.ClassList, () => []],
13 | [types.Button, () => ({ text: 'Click Me!', classes: [], href: 'http://example.com' })],
14 | [types.Quote, 'When you were made a leader, you weren\'t given a crown; you were given the responsibility to bring out the best in others.'],
15 | [types.Grid, () => ({mobile: '', tablet: '', desktop: '', widescreen: ''})],
16 | [Number, 100],
17 | [String, 'This is pretty neat']
18 | ]);
19 |
20 | export default class Seeder {
21 | // Seeds values using a schema.
22 | static seed (schema) {
23 | if (isObject(schema)) {
24 | return Object.keys(schema).reduce((values, key) => {
25 | values[key] = Seeder.seed(schema[key]);
26 | return values;
27 | }, {});
28 | } else if (Array.isArray(schema)) {
29 | return schema.map(s => {
30 | return Seeder.seed(s)
31 | });
32 | }
33 |
34 | let value = data.get(schema);
35 | if (value === undefined) {
36 | value = schema;
37 | }
38 | return typeof value === 'function' ? value() : value;
39 | }
40 | };
41 |
--------------------------------------------------------------------------------
/src/styler.js:
--------------------------------------------------------------------------------
1 | import Styler from './components/VuseStyler.vue';
2 | import { getTypeFromTagName, getTypeFromSchema } from './util';
3 |
4 | function installStyler ({ builder, Vue }) {
5 | const StylerInstance = Vue.extend(Styler).extend({
6 | beforeCreate () {
7 | this.$builder = builder;
8 | }
9 | });
10 |
11 | builder.styler = {
12 | inserted (el, binding, vnode) {
13 | const newNode = document.createElement('div');
14 | const section = vnode.context.$section;
15 | const rootApp = vnode.context.$root.$el;
16 | rootApp.appendChild(newNode);
17 | el.classList.add('is-editable');
18 | section.stylers.push(new StylerInstance({
19 | propsData: {
20 | el,
21 | section: section,
22 | type: binding.arg || getTypeFromSchema(binding.expression, section.schema) || getTypeFromTagName(el.tagName),
23 | name: binding.expression
24 | }
25 | }).$mount(newNode));
26 | }
27 | };
28 | };
29 |
30 | export default installStyler;
31 |
--------------------------------------------------------------------------------
/src/stylus/_app.styl:
--------------------------------------------------------------------------------
1 | @import variables
2 | @import colors
3 |
4 | .vuse-icon
5 | display: block
6 | width: 20px
7 | height: 20px
8 | $floatHover
9 | cursor: pointer
10 | box-shadow: 0 14px 28px alpha($black, 0.125), 0 10px 10px alpha($black, 0.1)
--------------------------------------------------------------------------------
/src/stylus/colors.styl:
--------------------------------------------------------------------------------
1 | /*
2 | * Color theme
3 | */
4 | $magenta ?= #eb008b
5 | $blue ?= #0072FF
6 | $cyan ?= #00d4f0
7 | $green ?= #18d88b
8 | $yellow ?= #ffdd57
9 | $orange ?= #ffa557
10 | $red ?= #ff3d3d
11 | $purple ?= #a324ea
12 |
13 | /*
14 | * Graysacle
15 | */
16 | $black ?= #000
17 | $dark ?= #323c47
18 | $gray ?= #c1c1c1
19 | $gray-dark ?= darken($gray, 10%)
20 | $gray-light ?= lighten($gray, 10%)
21 | $light ?= #f5f5f5
22 | $white ?= #fff
23 |
--------------------------------------------------------------------------------
/src/stylus/variables.styl:
--------------------------------------------------------------------------------
1 |
2 | $flexCenter
3 | display: flex
4 | justify-content: center
5 | align-items: center
--------------------------------------------------------------------------------
/src/types.js:
--------------------------------------------------------------------------------
1 | export class Avatar {};
2 |
3 | export class Title {};
4 |
5 | export class Text {};
6 |
7 | export class Logo {};
8 |
9 | export class Image {};
10 |
11 | export class Quote {};
12 |
13 | export class Link {};
14 |
15 | export class ClassList {};
16 |
17 | export class Button {};
18 |
19 | export class Grid { };
20 |
--------------------------------------------------------------------------------
/src/util.js:
--------------------------------------------------------------------------------
1 | import getPath from 'lodash/get';
2 | import * as types from './types';
3 |
4 | export function isObject (obj) {
5 | return obj && typeof obj === 'object' && obj !== null && !Array.isArray(obj);
6 | };
7 |
8 | export function isParentTo (target, parent) {
9 | let currentNode = target;
10 | while (currentNode !== null) {
11 | if (currentNode === parent) return true;
12 | currentNode = currentNode.parentNode;
13 | }
14 | return false;
15 | }
16 |
17 | /**
18 | *
19 | * @param {String} target
20 | * @param {Object} schema
21 | */
22 | export function getTypeFromSchema (target, schema) {
23 | const tempTarget = target.split('.');
24 | tempTarget.shift();
25 | const value = getPath(schema, tempTarget.join('.'));
26 | if (value === types.Grid) return 'grid';
27 | if (value === types.Text) return 'text';
28 | if (value === types.Title) return 'text';
29 | if (value === types.Button) return 'button';
30 | if (value === types.ClassList) return 'section';
31 | if (value === String) return 'text';
32 | if (value === Number) return 'text';
33 |
34 | return null;
35 | }
36 |
37 | export function getImageBlob (URL) {
38 | return new Promise((resolve, reject) => {
39 | const xhr = new XMLHttpRequest();
40 | xhr.open('GET', URL);
41 | xhr.responseType = 'blob';
42 |
43 | xhr.onload = function () {
44 | const imageBlob = this.response;
45 | const fileType = this.response.type.split('/')[1].split('+')[0];
46 | const randomNumber = new Date().getUTCMilliseconds();
47 | const filename = `image-${randomNumber}.${fileType}`;
48 | resolve({ blob: imageBlob, name: filename });
49 | }
50 | xhr.send(null);
51 | });
52 | }
53 |
54 | export function getTypeFromTagName (tagName) {
55 | tagName = tagName.toUpperCase();
56 | switch (tagName) {
57 | case 'H1':
58 | return 'text';
59 | case 'H2':
60 | return 'text';
61 | case 'H3':
62 | return 'text';
63 | case 'H4':
64 | return 'text';
65 | case 'H5':
66 | return 'text';
67 | case 'H6':
68 | return 'text';
69 | case 'P':
70 | return 'text';
71 | case 'B':
72 | return 'text';
73 | case 'SPAN':
74 | return 'text';
75 | case 'BUTTON':
76 | return 'button';
77 | case 'A':
78 | return 'button';
79 | case 'SECTION':
80 | return 'section';
81 | case 'HEADER':
82 | return 'section';
83 | default:
84 | break;
85 | }
86 | }
87 |
88 | export function cleanDOM (artboard) {
89 | const editables = Array.from(artboard.querySelectorAll('.is-editable'));
90 | const uploaders = Array.from(artboard.querySelectorAll('.uploader'));
91 | const stylers = Array.from(artboard.querySelectorAll('.styler'));
92 |
93 | editables.forEach((el) => {
94 | el.contentEditable = 'inherit';
95 | el.classList.remove('is-editable');
96 | });
97 | uploaders.forEach((el) => {
98 | const input = el.querySelector(':scope > input');
99 | const image = el.querySelector(':scope > img');
100 |
101 | image.classList.add('add-full-width');
102 | el.classList.remove('uploader');
103 | input.remove();
104 | });
105 | stylers.forEach((styler) => {
106 | styler.remove();
107 | });
108 | }
109 |
--------------------------------------------------------------------------------
/src/vuse.js:
--------------------------------------------------------------------------------
1 | import merge from 'lodash-es/merge';
2 | import Section from './section';
3 | import VuseBuilder from './components/VuseBuilder.vue';
4 | import VuseRenderer from './components/VuseRenderer.vue';
5 | import styler from './styler';
6 | import mixin from './mixin';
7 | import { cleanDOM } from './util';
8 |
9 | let PLUGINS = [];
10 | let mixier = {};
11 | const BUILDER_OPTIONS = {
12 | title: '',
13 | intro: true,
14 | sections: [],
15 | plugins: [],
16 | themes: [],
17 | columnsPrefix: {
18 | mobile: 'is-mobile-',
19 | tablet: 'is-tablet-',
20 | desktop: 'is-desktop-',
21 | widescreen: 'is-widescreen-',
22 | ultrawide: 'is-ultrawide-'
23 | }
24 | };
25 |
26 | // To tell if it is installed or not
27 | let _Vue = null;
28 |
29 | class Vuse {
30 | constructor (options) {
31 | this.isEditing = true;
32 | this.isSorting = false;
33 | this.isRendered = false;
34 | this.title = options.title;
35 | this.intro = options.intro;
36 | this.sections = options.sections;
37 | this.columnsPrefix = options.columnsPrefix;
38 | this.themes = options.themes;
39 | this.components = {};
40 | this.assets = {
41 | css: options.assets.css || 'dist/css/app.css'
42 | }
43 | this.installPlugins();
44 | }
45 |
46 | /**
47 | * Creates and adds a new section to the list of sections.
48 | * @param {*} options
49 | */
50 | add (options, position) {
51 | if (position !== undefined) {
52 | this.sections.splice(position, 0, new Section(options));
53 | return;
54 | }
55 | this.sections.push(new Section(options));
56 | }
57 |
58 | /**
59 | * Finds a section with the specified id.
60 | *
61 | * @param {String|Number} id
62 | */
63 | find (id) {
64 | return this.sections.find(s => s.id === id);
65 | }
66 |
67 | /**
68 | * Removes a section with the specified id.
69 | * @param {String|Number} id
70 | */
71 | remove (section) {
72 | const id = this.sections.findIndex(s => s.id === section.id);
73 | this.sections.splice(id, 1);
74 | section.destroy();
75 | }
76 |
77 | /**
78 | * Removes a section with the specified id.
79 | * @param {String|Number} oldIndex
80 | * @param {String|Number} newIndex
81 | */
82 | sort (oldIndex, newIndex) {
83 | const section = this.sections[oldIndex];
84 | this.sections.splice(oldIndex, 1);
85 | this.sections.splice(newIndex, 0, section);
86 | }
87 |
88 | /**
89 | * Constructs a document fragment.
90 | */
91 | outputFragment () {
92 | const frag = document.createDocumentFragment();
93 | frag.appendChild(document.head.cloneNode(true));
94 | frag.appendChild(this.rootEl.cloneNode(true));
95 |
96 | return frag;
97 | }
98 |
99 | /**
100 | * clears the builder sections.
101 | */
102 | clear () {
103 | const tempSections = this.sections;
104 | this.sections.forEach(section => section.destroy());
105 | this.sections = [];
106 | return tempSections;
107 | }
108 |
109 | /**
110 | * Static helper for components registration pre-installation.
111 | *
112 | * @param {String} name
113 | * @param {Object} definition
114 | */
115 | static component (name, definition) {
116 | // Just make a plugin that installs a component.
117 | Vuse.use((ctx) => {
118 | ctx.builder.component(name, definition);
119 | });
120 | }
121 |
122 | /**
123 | * Acts as a mixin for subcomponents.
124 | * @param {Object} mixinObj
125 | */
126 | static mix (mixinObj) {
127 | mixier = merge(mixier, mixinObj);
128 | }
129 |
130 | /**
131 | * Adds a component section to the builder and augments it with the styler.
132 | * @param {*} name
133 | * @param {*} definition
134 | */
135 | component (name, definition) {
136 | // reoslve the component name automatically.
137 | if (typeof name === 'object') {
138 | definition = name;
139 | name = definition.name;
140 | }
141 |
142 | // if passed a plain object
143 | if (!definition.extend) {
144 | definition = _Vue.extend(definition);
145 | }
146 |
147 | this.components[name] = definition.extend({
148 | directives: { styler: this.styler },
149 | mixins: [this.mixin],
150 | components: mixier.components
151 | });
152 | }
153 |
154 | /**
155 | * Installs added plugins.
156 | */
157 | installPlugins () {
158 | PLUGINS.forEach((ctx) => {
159 | ctx.plugin({ builder: this, Vue: _Vue }, ctx.options);
160 | });
161 | // reset to prevent duplications.
162 | PLUGINS = [];
163 | }
164 |
165 | static install (Vue, options = {}) {
166 | // already installed
167 | if (_Vue) return;
168 |
169 | _Vue = Vue;
170 | const builder = new Vuse(Object.assign({}, BUILDER_OPTIONS, options));
171 | // configer assets output location
172 | Vue.util.defineReactive(builder, 'sections', builder.sections);
173 | Vue.util.defineReactive(builder, 'isEditing', builder.isEditing);
174 | Vue.util.defineReactive(builder, 'isSorting', builder.isSorting);
175 | const extension = {
176 | components: builder.components,
177 | beforeCreate () {
178 | this.$builder = builder;
179 | }
180 | };
181 |
182 | // register the main components.
183 | Vue.component('VuseBuilder', Vue.extend(VuseBuilder).extend(extension));
184 | Vue.component('VuseRenderer', Vue.extend(VuseRenderer).extend(extension));
185 | }
186 |
187 | /**
188 | * The plugin to be installed with the builder. The function receives the installation context which
189 | * contains the builder instance and the Vue prototype.
190 | *
191 | * @param {Function} plugin
192 | * @param {Object} options
193 | */
194 | static use (plugin, options = {}) {
195 | if (typeof plugin !== 'function') {
196 | return console.warn('Plugins must be a function');
197 | }
198 |
199 | // append to the list of to-be installed plugins.
200 | PLUGINS.push({ plugin, options });
201 | }
202 |
203 | set (data) {
204 | this.title = data.title !== undefined ? data.title : this.title;
205 | if (data.sections && Array.isArray(data.sections)) {
206 | this.sections = data.sections.map(section => {
207 | const sectionData = {
208 | name: section.name,
209 | schema: section.schema,
210 | data: section.data
211 | };
212 | if (!sectionData.schema) {
213 | sectionData.schema = this.components[sectionData.name].options.$schema
214 | }
215 |
216 | return new Section(sectionData);
217 | });
218 | }
219 | }
220 |
221 | /**
222 | * Outputs a JSON representation of the builder that can be used for rendering with the renderer component.
223 | */
224 | toJSON () {
225 | return {
226 | title: this.title,
227 | sections: this.sections.map(s => ({
228 | name: s.name,
229 | data: s.data
230 | }))
231 | };
232 | }
233 |
234 | /**
235 | * Previews the created page in a seperate tap/window.
236 | */
237 | preview () {
238 | const frag = this.outputFragment();
239 | const artboard = frag.querySelector('#artboard');
240 | const printPreview = window.open('about:blank', 'print_preview');
241 | const printDocument = printPreview.document;
242 | cleanDOM(frag);
243 | printDocument.open();
244 | printDocument.write(
245 | `
246 |
247 |
248 | ${this.title}
249 |
250 |
251 |
252 | ${artboard.innerHTML}
253 |
254 | `
255 | );
256 | }
257 |
258 | /**
259 | * Exports the builder instance to a specified output. default is json.
260 | *
261 | * @param {String} method
262 | */
263 | export (method = 'json') {
264 | if (method === 'pwa' || method === 'zip') {
265 | if (typeof this.download === 'function') {
266 | return this.download(this.assets);
267 | }
268 |
269 | return console.warn('You do not have the zip plugin installed.');
270 | }
271 |
272 | if (method === 'preview') {
273 | return this.preview();
274 | }
275 |
276 | return this.toJSON();
277 | }
278 | };
279 |
280 | // use the plugin API to add the styler and mixin functionalities.
281 | Vuse.use(styler);
282 | Vuse.use(mixin);
283 |
284 | export default Vuse;
285 |
--------------------------------------------------------------------------------