浏览代码

Upgrade themes from vue2 to vue3 to fix the build themes error

zhixin 1 年之前
父节点
当前提交
e8d29a0bff

+ 3 - 7
.github/workflows/deploy.yml

@@ -21,13 +21,9 @@ jobs:
           cd ${{ github.workspace }}/site/_themes
           yarn install --frozen-lockfile
           yarn build
-          mv dist/js ../assets/js/themes
-          cd ../assets/js/themes
-          FILE=../../../_includes/themes.html
-          echo "" > $FILE
-          for f in *.js; do
-            echo "<script src=/assets/js/themes/$f></script>" >> $FILE
-          done
+          rm -rf ../assets/js/themes
+          mv dist/themes.html ../_includes/themes.html
+          mv dist ../assets/js/themes
 
       - uses: ruby/setup-ruby@v1
         with:

site/_themes/.eslintrc.js → site/_themes/.eslintrc.cjs


+ 0 - 5
site/_themes/babel.config.js

@@ -1,5 +0,0 @@
-module.exports = {
-  presets: [
-    '@vue/cli-plugin-babel/preset'
-  ]
-}

文件差异内容过多而无法显示
+ 21 - 11
site/_themes/public/index.html


+ 11 - 14
site/_themes/package.json

@@ -2,23 +2,20 @@
   "name": "themes",
   "version": "1.0.0",
   "private": true,
+  "module": "type",
   "scripts": {
-    "serve": "vue-cli-service serve",
-    "build": "vue-cli-service build",
-    "lint": "vue-cli-service lint"
+    "serve": "vite",
+    "build": "vite build",
+    "lint": "eslint ."
   },
   "dependencies": {
-    "core-js": "^3.4.3",
-    "vue": "^2.6.10"
+    "vue": "^3.4.31"
   },
   "devDependencies": {
-    "@vue/cli-plugin-babel": "^4.1.0",
-    "@vue/cli-plugin-eslint": "^4.1.0",
-    "@vue/cli-service": "^4.1.0",
-    "babel-eslint": "^10.0.3",
-    "eslint": "^5.16.0",
-    "eslint-plugin-vue": "^5.0.0",
-    "vue-template-compiler": "^2.6.10"
+    "@vitejs/plugin-vue": "^5.0.5",
+    "eslint": "^8.49.0",
+    "eslint-plugin-vue": "^9.27.0",
+    "vite": "^5.3.3"
   },
   "eslintConfig": {
     "root": true,
@@ -26,12 +23,12 @@
       "node": true
     },
     "extends": [
-      "plugin:vue/essential",
+      "plugin:vue/vue3-essential",
       "eslint:recommended"
     ],
     "rules": {},
     "parserOptions": {
-      "parser": "babel-eslint"
+      "ecmaVersion": "latest"
     }
   },
   "browserslist": [

+ 0 - 1
site/_themes/public/themes.html

@@ -1 +0,0 @@
-<!-- built files will be auto injected -->

+ 145 - 168
site/_themes/src/App.vue

@@ -2,7 +2,7 @@
   <div>
     <Categories
       :categories="categories"
-      @active="onActive"
+      @active="key => current = key"
     />
 
     <List
@@ -15,180 +15,157 @@
   </div>
 </template>
 
-<script>
+<script setup>
+import { ref } from 'vue'
 import Categories from './components/Categories'
 import List from './components/List'
 
-export default {
-  name: 'App',
-  components: {
-    Categories,
-    List
-  },
-  data () {
-    return {
-      current: 'all',
-      categories: {
-        all: 'All Themes',
-        admin: 'Admin & Dashboards',
-        css: 'CSS Frameworks',
-        vue: 'Vuejs',
-        others: 'Others'
-      },
-      themes: {
-        admin: [
-          {
-            name: 'Light Bootstrap Dashboard Pro',
-            desc: 'Responsive Bootstrap 4 Admin Template.',
-            img: '/assets/images/light-bootstrap.jpg',
-            url: 'https://secure.2checkout.com/affiliate.php?ACCOUNT=CREATIV&AFFILIATE=117417&PATH=https%3A%2F%2Fwww.creative-tim.com%2Fproduct%2Flight-bootstrap-dashboard-pro%3FAFFILIATE%3D117417',
-            demo: 'https://secure.2checkout.com/affiliate.php?ACCOUNT=CREATIV&AFFILIATE=117417&PATH=https%3A%2F%2Fdemos.creative-tim.com%2Flight-bootstrap-dashboard-pro%2Fexamples%2Ftables%2Fbootstrap-table.html%3FAFFILIATE%3D117417',
-            price: '$39'
-          },
-          {
-            name: 'Paper Dashboard Pro',
-            desc: 'Premium Bootstrap Admin Template.',
-            img: '/assets/images/pager-dashboard.jpg',
-            url: 'https://secure.2checkout.com/affiliate.php?ACCOUNT=CREATIV&AFFILIATE=117417&PATH=https%3A%2F%2Fwww.creative-tim.com%2Fproduct%2Fpaper-dashboard-pro%3FAFFILIATE%3D117417',
-            demo: 'https://secure.2checkout.com/affiliate.php?ACCOUNT=CREATIV&AFFILIATE=117417&PATH=https%3A%2F%2Fdemos.creative-tim.com%2Fpaper-dashboard-pro%2Fexamples%2Ftables%2Fbootstrap-table.html%3FAFFILIATE%3D117417',
-            price: '$39'
-          },
-          {
-            name: 'Remark',
-            desc: 'Responsive Bootstrap 4 Admin Template.',
-            img: 'https://themeforest.img.customer.envatousercontent.com/files/256697282/Preview.__large_preview.png?auto=compress%2Cformat&q=80&fit=crop&crop=top&max-h=8000&max-w=590&s=c4340f8d5e70cd6fb77fd5b2f670b486',
-            url: 'https://1.envato.market/j0mdP',
-            demo: 'https://1.envato.market/PEbGe',
-            price: '$24'
-          },
-          {
-            name: 'Ubold',
-            desc: 'Admin & Dashboard Template.',
-            img: 'https://themeforest.img.customer.envatousercontent.com/files/273195428/01_ubold.__large_preview.jpg?auto=compress%2Cformat&q=80&fit=crop&crop=top&max-h=8000&max-w=590&s=a1318683a895fcf026cecd3e0b74d97b',
-            url: 'https://1.envato.market/5EnJ2',
-            demo: 'https://1.envato.market/kEWdL',
-            price: '$29'
-          },
-          {
-            name: 'Metronic',
-            desc: 'Bootstrap 4, Angular 8, React Admin Dashboard Theme.',
-            img: 'https://themeforest.img.customer.envatousercontent.com/files/276839308/plyushka.__large_preview.jpg?auto=compress%2Cformat&q=80&fit=crop&crop=top&max-h=8000&max-w=590&s=b7f0504d8697450845aa1d2f71a8c80b',
-            url: 'https://1.envato.market/KmzbN',
-            demo: 'https://1.envato.market/7qMPg',
-            price: '$35'
-          },
-          {
-            name: 'Pages',
-            desc: 'Admin Dashboard Template with Angular 6, Bootstrap 4 & HTML.',
-            img: 'https://themeforest.img.customer.envatousercontent.com/files/253934566/preview.__large_preview.png?auto=compress%2Cformat&q=80&fit=crop&crop=top&max-h=8000&max-w=590&s=bcc481514196e409152ae30e8668f0eb',
-            url: 'https://1.envato.market/LB26M',
-            demo: 'https://1.envato.market/q9yMY',
-            price: '$24'
-          }
-        ],
-        css: [
-          {
-            name: 'Bootstrap v5',
-            desc: 'The most popular HTML, CSS, and JavaScript framework.',
-            img: '/assets/images/bootstrap5.jpg',
-            url: '/docs/getting-started/introduction/',
-            demo: 'https://examples.bootstrap-table.com/index.html?bootstrap5',
-            price: ''
-          },
-          {
-            name: 'Bootstrap v4',
-            desc: 'The most popular HTML, CSS, and JavaScript framework.',
-            img: '/assets/images/bootstrap4.jpg',
-            url: '/themes/bootstrap4/',
-            demo: 'https://examples.bootstrap-table.com/',
-            price: ''
-          },
-          {
-            name: 'Bootstrap v3',
-            desc: 'The most popular HTML, CSS, and JavaScript framework.',
-            img: '/assets/images/bootstrap3.jpg',
-            url: '/themes/bootstrap3/',
-            demo: 'https://examples.bootstrap-table.com/index.html?bootstrap3',
-            price: ''
-          },
-          {
-            name: 'Bootstrap Table',
-            desc: 'Our custom theme of Bootstrap Table.',
-            img: '/assets/images/bootstrap-table.jpg',
-            url: '/themes/bootstrap-table/',
-            demo: 'https://examples.bootstrap-table.com/index.html?bootstrap-table',
-            price: ''
-          },
-          {
-            name: 'Semantic UI',
-            desc: 'UI component framework based around useful principles from natural language.',
-            img: '/assets/images/semantic.jpg',
-            url: '/themes/semantic/',
-            demo: 'https://examples.bootstrap-table.com/index.html?semantic',
-            price: ''
-          },
-          {
-            name: 'Bulma',
-            desc: 'Modern CSS framework based on Flexbox.',
-            img: '/assets/images/bulma.jpg',
-            url: '/themes/bulma/',
-            demo: 'https://examples.bootstrap-table.com/index.html?bulma',
-            price: ''
-          },
-          {
-            name: 'Materialize',
-            desc: 'A modern responsive front-end framework based on Material Design.',
-            img: '/assets/images/materialize.jpg',
-            url: '/themes/materialize/',
-            demo: 'https://examples.bootstrap-table.com/index.html?materialize',
-            price: ''
-          },
-          {
-            name: 'Foundation',
-            desc: 'The most advanced responsive front-end framework in the world.',
-            img: '/assets/images/foundation.jpg',
-            url: '/themes/foundation/',
-            demo: 'https://examples.bootstrap-table.com/index.html?foundation',
-            price: ''
-          }
-        ],
-        vue: [
-          {
-            name: 'Element Table',
-            desc: 'An extended table to integration with bootstrap-table and element-ui.',
-            img: '/assets/images/element-table.jpg',
-            url: 'https://element.bootstrap-table.com/',
-            demo: 'https://element.bootstrap-table.com/',
-            price: ''
-          }
-        ],
-        others: [
-          {
-            name: 'Fresh Bootstrap Table',
-            desc: 'Fresh Bootstrap Table Template.',
-            img: '/assets/images/fresh-table.jpg',
-            url: 'https://secure.2checkout.com/affiliate.php?ACCOUNT=CREATIV&AFFILIATE=117417&PATH=https%3A%2F%2Fwww.creative-tim.com%2Fproduct%2Ffresh-bootstrap-table%3FAFFILIATE%3D117417',
-            demo: 'https://wenzhixin.github.io/fresh-bootstrap-table/compact-table.html',
-            price: ''
-          }
-        ]
-      }
+const current = ref('all')
+const categories = {
+  all: 'All Themes',
+  admin: 'Admin & Dashboards',
+  css: 'CSS Frameworks',
+  vue: 'Vuejs',
+  others: 'Others'
+}
+const themes = {
+  admin: [
+    {
+      name: 'Light Bootstrap Dashboard Pro',
+      desc: 'Responsive Bootstrap 4 Admin Template.',
+      img: '/assets/images/light-bootstrap.jpg',
+      url: 'https://secure.2checkout.com/affiliate.php?ACCOUNT=CREATIV&AFFILIATE=117417&PATH=https%3A%2F%2Fwww.creative-tim.com%2Fproduct%2Flight-bootstrap-dashboard-pro%3FAFFILIATE%3D117417',
+      demo: 'https://secure.2checkout.com/affiliate.php?ACCOUNT=CREATIV&AFFILIATE=117417&PATH=https%3A%2F%2Fdemos.creative-tim.com%2Flight-bootstrap-dashboard-pro%2Fexamples%2Ftables%2Fbootstrap-table.html%3FAFFILIATE%3D117417',
+      price: '$39'
+    },
+    {
+      name: 'Paper Dashboard Pro',
+      desc: 'Premium Bootstrap Admin Template.',
+      img: '/assets/images/pager-dashboard.jpg',
+      url: 'https://secure.2checkout.com/affiliate.php?ACCOUNT=CREATIV&AFFILIATE=117417&PATH=https%3A%2F%2Fwww.creative-tim.com%2Fproduct%2Fpaper-dashboard-pro%3FAFFILIATE%3D117417',
+      demo: 'https://secure.2checkout.com/affiliate.php?ACCOUNT=CREATIV&AFFILIATE=117417&PATH=https%3A%2F%2Fdemos.creative-tim.com%2Fpaper-dashboard-pro%2Fexamples%2Ftables%2Fbootstrap-table.html%3FAFFILIATE%3D117417',
+      price: '$39'
+    },
+    {
+      name: 'Ubold',
+      desc: 'Admin & Dashboard Template.',
+      img: 'https://themeforest.img.customer.envatousercontent.com/files/273195428/01_ubold.__large_preview.jpg?auto=compress%2Cformat&q=80&fit=crop&crop=top&max-h=8000&max-w=590&s=a1318683a895fcf026cecd3e0b74d97b',
+      url: 'https://1.envato.market/5EnJ2',
+      demo: 'https://1.envato.market/kEWdL',
+      price: '$29'
+    },
+    {
+      name: 'Metronic',
+      desc: 'Bootstrap 4, Angular 8, React Admin Dashboard Theme.',
+      img: 'https://themeforest.img.customer.envatousercontent.com/files/276839308/plyushka.__large_preview.jpg?auto=compress%2Cformat&q=80&fit=crop&crop=top&max-h=8000&max-w=590&s=b7f0504d8697450845aa1d2f71a8c80b',
+      url: 'https://1.envato.market/KmzbN',
+      demo: 'https://1.envato.market/7qMPg',
+      price: '$35'
+    },
+    {
+      name: 'Pages',
+      desc: 'Admin Dashboard Template with Angular 6, Bootstrap 4 & HTML.',
+      img: 'https://themeforest.img.customer.envatousercontent.com/files/253934566/preview.__large_preview.png?auto=compress%2Cformat&q=80&fit=crop&crop=top&max-h=8000&max-w=590&s=bcc481514196e409152ae30e8668f0eb',
+      url: 'https://1.envato.market/LB26M',
+      demo: 'https://1.envato.market/q9yMY',
+      price: '$24'
+    }
+  ],
+  css: [
+    {
+      name: 'Bootstrap v5',
+      desc: 'The most popular HTML, CSS, and JavaScript framework.',
+      img: '/assets/images/bootstrap5.jpg',
+      url: '/docs/getting-started/introduction/',
+      demo: 'https://examples.bootstrap-table.com/index.html?bootstrap5',
+      price: ''
+    },
+    {
+      name: 'Bootstrap v4',
+      desc: 'The most popular HTML, CSS, and JavaScript framework.',
+      img: '/assets/images/bootstrap4.jpg',
+      url: '/themes/bootstrap4/',
+      demo: 'https://examples.bootstrap-table.com/',
+      price: ''
+    },
+    {
+      name: 'Bootstrap v3',
+      desc: 'The most popular HTML, CSS, and JavaScript framework.',
+      img: '/assets/images/bootstrap3.jpg',
+      url: '/themes/bootstrap3/',
+      demo: 'https://examples.bootstrap-table.com/index.html?bootstrap3',
+      price: ''
+    },
+    {
+      name: 'Bootstrap Table',
+      desc: 'Our custom theme of Bootstrap Table.',
+      img: '/assets/images/bootstrap-table.jpg',
+      url: '/themes/bootstrap-table/',
+      demo: 'https://examples.bootstrap-table.com/index.html?bootstrap-table',
+      price: ''
+    },
+    {
+      name: 'Semantic UI',
+      desc: 'UI component framework based around useful principles from natural language.',
+      img: '/assets/images/semantic.jpg',
+      url: '/themes/semantic/',
+      demo: 'https://examples.bootstrap-table.com/index.html?semantic',
+      price: ''
+    },
+    {
+      name: 'Bulma',
+      desc: 'Modern CSS framework based on Flexbox.',
+      img: '/assets/images/bulma.jpg',
+      url: '/themes/bulma/',
+      demo: 'https://examples.bootstrap-table.com/index.html?bulma',
+      price: ''
+    },
+    {
+      name: 'Materialize',
+      desc: 'A modern responsive front-end framework based on Material Design.',
+      img: '/assets/images/materialize.jpg',
+      url: '/themes/materialize/',
+      demo: 'https://examples.bootstrap-table.com/index.html?materialize',
+      price: ''
+    },
+    {
+      name: 'Foundation',
+      desc: 'The most advanced responsive front-end framework in the world.',
+      img: '/assets/images/foundation.jpg',
+      url: '/themes/foundation/',
+      demo: 'https://examples.bootstrap-table.com/index.html?foundation',
+      price: ''
+    }
+  ],
+  vue: [
+    {
+      name: 'Element Table',
+      desc: 'An extended table to integration with bootstrap-table and element-ui.',
+      img: '/assets/images/element-table.jpg',
+      url: 'https://element.bootstrap-table.com/',
+      demo: 'https://element.bootstrap-table.com/',
+      price: ''
     }
-  },
-  methods: {
-    onActive (current) {
-      this.current = current
+  ],
+  others: [
+    {
+      name: 'Fresh Bootstrap Table',
+      desc: 'Fresh Bootstrap Table Template.',
+      img: '/assets/images/fresh-table.jpg',
+      url: 'https://secure.2checkout.com/affiliate.php?ACCOUNT=CREATIV&AFFILIATE=117417&PATH=https%3A%2F%2Fwww.creative-tim.com%2Fproduct%2Ffresh-bootstrap-table%3FAFFILIATE%3D117417',
+      demo: 'https://wenzhixin.github.io/fresh-bootstrap-table/compact-table.html',
+      price: ''
     }
-  }
+  ]
 }
 </script>
 
 <style>
-  .bs-tooltip-top .arrow::before {
-    border-top-color: #FAE5A8;
-  }
-  .tooltip-inner {
-    background: #FAE5A8;
-    color: #724729;
-  }
+.bs-tooltip-top .arrow::before {
+  border-top-color: #FAE5A8;
+}
+.tooltip-inner {
+  background: #FAE5A8;
+  color: #724729;
+}
 </style>

+ 52 - 55
site/_themes/src/components/Categories.vue

@@ -14,67 +14,64 @@
   </ul>
 </template>
 
-<script>
-export default {
-  props: {
-    categories: {
-      type: Object,
-      required: true
-    }
-  },
-  data () {
-    return {
-      current: 'all'
-    }
-  },
-  methods: {
-    onClick (key) {
-      this.current = key
-      this.$emit('active', key)
-    }
+<script setup>
+import { ref } from 'vue'
+
+defineProps({
+  categories: {
+    type: Object,
+    required: true
   }
+})
+
+const emit = defineEmits(['active'])
+
+const current = ref('all')
+const onClick = key => {
+  current.value = key
+  emit('active', key)
 }
 </script>
 
 <style scoped>
-  ul.categories-list {
-    list-style-type: none;
-    margin-top: -88px;
-    z-index: 9999;
-    padding: 20px 15px;
-    position: relative;
-    border: 0;
-    z-index: 1;
-    background-color: #5d4a8e;
-    border-radius: 8px;
-    box-shadow: 0 13px 27px -5px rgba(50, 50, 93, 0.25), 0 8px 16px -8px rgba(0, 0, 0, 0.3), 0 -6px 16px -6px rgba(0, 0, 0, 0.025);
-    width: 100%;
-  }
+ul.categories-list {
+  list-style-type: none;
+  margin-top: -88px;
+  z-index: 9999;
+  padding: 20px 15px;
+  position: relative;
+  border: 0;
+  z-index: 1;
+  background-color: #5d4a8e;
+  border-radius: 8px;
+  box-shadow: 0 13px 27px -5px rgba(50, 50, 93, 0.25), 0 8px 16px -8px rgba(0, 0, 0, 0.3), 0 -6px 16px -6px rgba(0, 0, 0, 0.025);
+  width: 100%;
+}
 
-  ul.categories-list>li {
-    float: left;
-    display: block;
-    padding: 3px 0;
-  }
+ul.categories-list>li {
+  float: left;
+  display: block;
+  padding: 3px 0;
+}
 
-  ul.categories-list>li>a {
-    display: block;
-    text-align: center;
-    padding: 12px 12px;
-    font-weight: 600;
-    border-radius: 30px;
-    font-size: 12px;
-    margin-left: 0px;
-    margin-right: 10px;
-    text-decoration: none;
-    color: white;
-    text-transform: uppercase;
-    transition: background .2s;
-  }
+ul.categories-list>li>a {
+  display: block;
+  text-align: center;
+  padding: 12px 12px;
+  font-weight: 600;
+  border-radius: 30px;
+  font-size: 12px;
+  margin-left: 0px;
+  margin-right: 10px;
+  text-decoration: none;
+  color: white;
+  text-transform: uppercase;
+  transition: background .2s;
+}
 
-  ul.categories-list>li>a:hover,
-  ul.categories-list>li.active>a {
-    color: #6d4eba;
-    background: white;
-  }
+ul.categories-list>li>a:hover,
+ul.categories-list>li.active>a {
+  color: #6d4eba;
+  background: white;
+}
 </style>

+ 396 - 410
site/_themes/src/components/List.vue

@@ -1,5 +1,8 @@
 <template>
-  <div class="row">
+  <div
+    ref="el"
+    class="row"
+  >
     <div class="col-md-12">
       <div class="landing-title">
         <h2 class="title">
@@ -29,8 +32,6 @@
               :href="theme.url"
               target="_blank"
               class="btn btn-neutral btn-round btn-fill"
-              data-toggle="tooltip"
-              data-placement="top"
               title="More Details"
             >
               <i
@@ -41,8 +42,6 @@
               :href="theme.demo"
               class="btn btn-neutral btn-fill btn-round"
               target="_blank"
-              data-toggle="tooltip"
-              data-placement="top"
               title="Live Preview"
             >
               <i class="fa fa-laptop" />
@@ -77,499 +76,486 @@
   </div>
 </template>
 
-<script>
-const $ = window.$
-
-export default {
-  props: {
-    title: {
-      type: String,
-      required: true
-    },
-    themes: {
-      type: Array,
-      required: true
-    }
+<script setup>
+defineProps({
+  title: {
+    type: String,
+    required: true
   },
-
-  mounted () {
-    $('[data-toggle="tooltip"]').tooltip()
-    $(this.$el).find('a').click(() => {
-      setTimeout(() => {
-        $('[data-toggle="tooltip"]').tooltip('hide')
-      }, 200)
-    })
+  themes: {
+    type: Array,
+    required: true
   }
-}
+})
 </script>
 
 <style scoped>
-  a {
-    text-decoration: none;
-  }
+a {
+  text-decoration: none;
+}
 
-  .title {
-    font-size: 28px;
-    font-weight: 300;
-    margin: 7px 0;
-    font-weight: normal;
-    color: #777;
-    margin-bottom: 20px;
-  }
+.title {
+  font-size: 28px;
+  font-weight: 300;
+  margin: 7px 0;
+  font-weight: normal;
+  color: #777;
+  margin-bottom: 20px;
+}
 
-  .free {
-    color: #777;
-    font-size: 15px;
-  }
+.free {
+  color: #777;
+  font-size: 15px;
+}
 
-  .card {
-    border-radius: 8px;
-    position: relative;
-    margin-bottom: 60px;
-    border: 0;
-  }
+.card {
+  border-radius: 8px;
+  position: relative;
+  margin-bottom: 60px;
+  border: 0;
+}
 
-  .card .thumbnail {
-    border: 0 none;
-    padding: 0;
-    margin: 0;
-    min-height: 250px;
-    position: relative;
-    background: transparent
-  }
+.card .thumbnail {
+  border: 0 none;
+  padding: 0;
+  margin: 0;
+  min-height: 250px;
+  position: relative;
+  background: transparent
+}
 
-  .card-small .thumbnail {
-    min-height: 200px;
-  }
+.card-small .thumbnail {
+  min-height: 200px;
+}
 
-  .card .thumbnail img {
-    width: 100%;
-    height: 100%;
-  }
+.card .thumbnail img {
+  width: 100%;
+  height: 100%;
+}
 
-  .card .thumbnail>img {
-    border-radius: 8px 8px;
-    box-shadow: 0 25px 20px -21px rgba(0, 0, 0, 0.57)
-  }
+.card .thumbnail>img {
+  border-radius: 8px 8px;
+  box-shadow: 0 25px 20px -21px rgba(0, 0, 0, 0.57)
+}
 
-  .card .details {
-    top: 0;
-    display: block;
-    height: 60px;
-    padding: 10px 15px 0;
-    position: absolute;
-    width: 100%;
-    border-radius: 8px 8px 0 0
-  }
+.card .details {
+  top: 0;
+  display: block;
+  height: 60px;
+  padding: 10px 15px 0;
+  position: absolute;
+  width: 100%;
+  border-radius: 8px 8px 0 0
+}
 
-  .card .header,
-  .card .main,
-  .card .footer {
-    display: block
-  }
+.card .header,
+.card .main,
+.card .footer {
+  display: block
+}
 
-  .card .time {
-    color: #777777;
-    font-size: 15px;
-    margin-top: 2px;
-    text-transform: uppercase
-  }
+.card .time {
+  color: #777777;
+  font-size: 15px;
+  margin-top: 2px;
+  text-transform: uppercase
+}
 
-  .card .time.premium-product {
-    color: #444444;
-    font-size: 19px;
-    margin: 1px
-  }
+.card .time.premium-product {
+  color: #444444;
+  font-size: 19px;
+  margin: 1px
+}
 
-  .card .numbers {
-    color: #FFFFFF;
-    float: right;
-    margin-top: 6px;
-    text-shadow: 0 2px 3px rgba(0, 0, 0, 0.34)
-  }
+.card .numbers {
+  color: #FFFFFF;
+  float: right;
+  margin-top: 6px;
+  text-shadow: 0 2px 3px rgba(0, 0, 0, 0.34)
+}
 
-  .card .numbers .downloads,
-  .card .numbers .comments-icon {
-    margin-left: 6px;
-    font-size: 15px;
-    font-weight: 500
-  }
+.card .numbers .downloads,
+.card .numbers .comments-icon {
+  margin-left: 6px;
+  font-size: 15px;
+  font-weight: 500
+}
 
-  .card .numbers .fa {
-    font-size: 18px
-  }
+.card .numbers .fa {
+  font-size: 18px
+}
 
-  .card .description {
-    color: #FFFFFF;
-    margin-top: 40px;
-    height: 125px;
-    font-size: 18px;
-    opacity: 0.7
-  }
+.card .description {
+  color: #FFFFFF;
+  margin-top: 40px;
+  height: 125px;
+  font-size: 18px;
+  opacity: 0.7
+}
 
-  .card a:hover .description {
-    opacity: 1
-  }
+.card a:hover .description {
+  opacity: 1
+}
 
-  .card .actions .btn {
-    margin-left: 5px;
-    margin-right: 5px;
-    top: 50%;
-    position: relative;
-    transform: translateY(-50%);
-    -webkit-transform: translateY(-50%);
-    -moz-transform: translateY(-50%)
-  }
+.card .actions .btn {
+  margin-left: 5px;
+  margin-right: 5px;
+  top: 50%;
+  position: relative;
+  transform: translateY(-50%);
+  -webkit-transform: translateY(-50%);
+  -moz-transform: translateY(-50%)
+}
 
-  .card .actions .btn-round:not(.btn-radius) {
-    font-size: 18px;
-    padding: 14px 14px;
-    line-height: 1;
-    display: inline-block;
-    width: 48px;
-    height: 48px
-  }
+.card .actions .btn-round:not(.btn-radius) {
+  font-size: 18px;
+  padding: 14px 14px;
+  line-height: 1;
+  display: inline-block;
+  width: 48px;
+  height: 48px
+}
 
-  .btn-neutral.btn-fill {
-    color: #666666 !important;
-    background-color: white;
-    opacity: 1;
-    filter: alpha(opacity=100);
-  }
+.btn-neutral.btn-fill {
+  color: #666666 !important;
+  background-color: white;
+  opacity: 1;
+  filter: alpha(opacity=100);
+}
 
-  .btn-round {
-    border-width: 1px;
-    border-radius: 30px !important;
-    opacity: 0.79;
-    padding: 9px 18px;
-  }
+.btn-round {
+  border-width: 1px;
+  border-radius: 30px !important;
+  opacity: 0.79;
+  padding: 9px 18px;
+}
 
-  .card .thumb-cover {
-    padding: 15px 20px;
-    height: 100%;
-    top: 0;
-    position: absolute;
-    opacity: 0;
-    display: block;
-    width: 100%;
-    background-color: rgba(0, 0, 0, 0.75);
-    z-index: 3;
-    content: "";
-    left: 0;
-    border-radius: 8px
-  }
+.card .thumb-cover {
+  padding: 15px 20px;
+  height: 100%;
+  top: 0;
+  position: absolute;
+  opacity: 0;
+  display: block;
+  width: 100%;
+  background-color: rgba(0, 0, 0, 0.75);
+  z-index: 3;
+  content: "";
+  left: 0;
+  border-radius: 8px
+}
 
-  .card .actions {
-    position: absolute;
-    z-index: 3;
-    top: 50%;
-    left: 0;
-    text-align: center;
-    width: 100%;
-    height: 0;
-    opacity: 0;
-    transition: all .5s ease;
-    -webkit-transition: all .5s ease;
-    -moz-transition: all .5s ease
-  }
+.card .actions {
+  position: absolute;
+  z-index: 3;
+  top: 50%;
+  left: 0;
+  text-align: center;
+  width: 100%;
+  height: 0;
+  opacity: 0;
+  transition: all .5s ease;
+  -webkit-transition: all .5s ease;
+  -moz-transition: all .5s ease
+}
 
-  .card:hover .thumb-cover,
-  .card:hover .actions {
-    opacity: 1
-  }
+.card:hover .thumb-cover,
+.card:hover .actions {
+  opacity: 1
+}
 
-  .card-small .actions {
-    height: 55px;
-    font-size: 14px
-  }
+.card-small .actions {
+  height: 55px;
+  font-size: 14px
+}
 
-  .card:hover .actions {
-    opacity: 1
-  }
+.card:hover .actions {
+  opacity: 1
+}
 
-  .card .title {
-    margin-top: 45px;
-    min-height: 115px
-  }
+.card .title {
+  margin-top: 45px;
+  min-height: 115px
+}
 
-  .card a .title h3 {
-    color: #FFFFFF;
-    font-size: 24px;
-    font-weight: 400;
-    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.57)
-  }
+.card a .title h3 {
+  color: #FFFFFF;
+  font-size: 24px;
+  font-weight: 400;
+  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.57)
+}
 
-  .card .user {
-    font-weight: 400;
-    color: #FFFFFF;
-    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.23);
-    line-height: 20px;
-    display: block
-  }
+.card .user {
+  font-weight: 400;
+  color: #FFFFFF;
+  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.23);
+  line-height: 20px;
+  display: block
+}
 
-  .card .user .name {
-    line-height: 35px;
-    margin-left: 10px;
-    font-size: 16px;
-    float: left
-  }
+.card .user .name {
+  line-height: 35px;
+  margin-left: 10px;
+  font-size: 16px;
+  float: left
+}
 
-  .card .user .user-photo {
-    width: 35px;
-    height: 35px;
-    border: 2px solid #FFFFFF;
-    border-radius: 50%;
-    overflow: hidden;
-    float: left
-  }
+.card .user .user-photo {
+  width: 35px;
+  height: 35px;
+  border: 2px solid #FFFFFF;
+  border-radius: 50%;
+  overflow: hidden;
+  float: left
+}
 
-  .card .user-photo img {
-    width: 33px
-  }
+.card .user-photo img {
+  width: 33px
+}
 
-  .card-info {
-    padding: 15px;
-    border-radius: 0 0 12px 12px
-  }
+.card-info {
+  padding: 15px;
+  border-radius: 0 0 12px 12px
+}
 
-  .card-info [class^='circle-'] {
-    float: left;
-    margin-right: 5px
-  }
+.card-info [class^='circle-'] {
+  float: left;
+  margin-right: 5px
+}
 
-  .card-info a:not(.btn) {
-    color: #434343
-  }
+.card-info a:not(.btn) {
+  color: #434343
+}
 
-  .card-info a:not(.btn):hover {
-    color: #232323
-  }
+.card-info a:not(.btn):hover {
+  color: #232323
+}
 
-  .card-info .actions a {
-    color: #777777
-  }
+.card-info .actions a {
+  color: #777777
+}
 
-  .card-info .actions a:hover {
-    color: #555555
-  }
+.card-info .actions a:hover {
+  color: #555555
+}
 
-  .card-info .actions .blue-text {
-    color: #00bbff
-  }
+.card-info .actions .blue-text {
+  color: #00bbff
+}
 
-  .card-info .actions .blue-text:hover {
-    color: #3883c4
-  }
+.card-info .actions .blue-text:hover {
+  color: #3883c4
+}
 
-  .card-info h3 {
-    margin-top: 10px;
-    margin-bottom: 5px;
-    font-size: 18px
-  }
+.card-info h3 {
+  margin-top: 10px;
+  margin-bottom: 5px;
+  font-size: 18px
+}
 
-  .card-small .card-info h3 {
-    font-size: 18px
-  }
+.card-small .card-info h3 {
+  font-size: 18px
+}
 
-  .card-info p {
-    font-size: 14px;
-    margin: 0;
-    color: #666666;
-    min-height: 50px
-  }
+.card-info p {
+  font-size: 14px;
+  margin: 0;
+  color: #666666;
+  min-height: 50px
+}
 
-  .card-info .label {
-    background: transparent;
-    border: 1px solid;
-    border-radius: 14px;
-    padding: 5px 10px;
-    font-size: 10px;
-    text-transform: uppercase;
-    position: relative;
-    top: -4px
-  }
+.card-info .label {
+  background: transparent;
+  border: 1px solid;
+  border-radius: 14px;
+  padding: 5px 10px;
+  font-size: 10px;
+  text-transform: uppercase;
+  position: relative;
+  top: -4px
+}
 
-  .card-info .label.label-danger {
-    background: transparent
-  }
+.card-info .label.label-danger {
+  background: transparent
+}
 
-  .card-info .label .fa {
-    font-size: 12px
-  }
+.card-info .label .fa {
+  font-size: 12px
+}
 
-  @media (max-width: 800px) {
-    .card.card-bundle .thumbnail {
-      min-height: 150px
-    }
-
-    .card.card-bundle .details .framework-logo {
-      padding-top: 20px
-    }
-
-    .card.card-bundle .details .framework-logo img {
-      width: 40px;
-      height: 40px
-    }
-
-    .card.card-bundle .details .framework-logo img.default,
-    .card.card-bundle .details .framework-logo img.material {
-      width: 30px;
-      height: 30px
-    }
-
-    .card.card-bundle .details .framework-logo p {
-      padding-top: 5px;
-      font-size: 20px
-    }
+@media (max-width: 800px) {
+  .card.card-bundle .thumbnail {
+    min-height: 150px
   }
 
-  @media (max-width: 1100px) {
-    .card.card-bundle .details .bundle-details {
-      padding: 10px
-    }
-
-    .card.card-bundle .details .bundle-details i {
-      font-size: 20px
-    }
+  .card.card-bundle .details .framework-logo {
+    padding-top: 20px
+  }
 
-    .card.card-bundle .details .bundle-details span {
-      font-size: 20px
-    }
+  .card.card-bundle .details .framework-logo img {
+    width: 40px;
+    height: 40px
   }
 
-  @media (min-width: 801px) and (max-width: 1100px) {
-    .card.card-bundle .details .framework-logo {
-      padding-top: 25px
-    }
-
-    .card.card-bundle .details .framework-logo img {
-      width: 50px;
-      height: 50px
-    }
-
-    .card.card-bundle .details .framework-logo img.default,
-    .card.card-bundle .details .framework-logo img.material {
-      width: 40px;
-      height: 40px
-    }
+  .card.card-bundle .details .framework-logo img.default,
+  .card.card-bundle .details .framework-logo img.material {
+    width: 30px;
+    height: 30px
   }
 
-  .card.card-bundle .details {
-    text-align: center;
-    height: auto;
-    color: #FFF;
-    height: 100%
+  .card.card-bundle .details .framework-logo p {
+    padding-top: 5px;
+    font-size: 20px
   }
+}
 
+@media (max-width: 1100px) {
   .card.card-bundle .details .bundle-details {
-    position: absolute;
-    width: 100%;
-    top: auto;
-    left: 0;
-    right: 0;
-    bottom: 0;
-    padding: 24px;
-    margin-right: auto;
-    margin-left: auto
+    padding: 10px
   }
 
   .card.card-bundle .details .bundle-details i {
-    font-size: 30px
+    font-size: 20px
   }
 
   .card.card-bundle .details .bundle-details span {
-    display: block;
-    font-size: 35px;
-    font-weight: 300
-  }
-
-  .card.card-bundle .details .bundle-details p {
-    font-size: 12px;
-    font-weight: 500
+    font-size: 20px
   }
+}
 
+@media (min-width: 801px) and (max-width: 1100px) {
   .card.card-bundle .details .framework-logo {
-    position: absolute;
-    width: 100%;
-    padding-bottom: 5px;
-    padding-top: 40px;
-    left: 0;
-    right: 0;
-    top: 0;
-    margin-right: auto;
-    margin-left: auto
+    padding-top: 25px
   }
 
   .card.card-bundle .details .framework-logo img {
-    width: 70px;
-    height: 70px;
-    border-radius: 50%
+    width: 50px;
+    height: 50px
   }
 
   .card.card-bundle .details .framework-logo img.default,
   .card.card-bundle .details .framework-logo img.material {
-    width: 50px;
-    height: 50px;
-    margin-left: 10px;
-    margin-top: 10px;
-    margin-bottom: 10px
+    width: 40px;
+    height: 40px
   }
+}
 
-  .card.card-bundle .details .framework-logo p {
-    padding-top: 20px;
-    font-size: 30px;
-    font-weight: 400
-  }
+.card.card-bundle .details {
+  text-align: center;
+  height: auto;
+  color: #FFF;
+  height: 100%
+}
 
-  .card.card-bundle .mask {
-    display: block;
-    position: absolute;
-    width: 100%;
-    height: 100%;
-    left: 0;
-    top: 0;
-    opacity: .8;
-    border-radius: 8px
-  }
+.card.card-bundle .details .bundle-details {
+  position: absolute;
+  width: 100%;
+  top: auto;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  padding: 24px;
+  margin-right: auto;
+  margin-left: auto
+}
 
-  .card .authors-container {
-    width: 38px
-  }
+.card.card-bundle .details .bundle-details i {
+  font-size: 30px
+}
 
-  .card .authors-container[data-collaborators="2"] {
-    width: 60px
-  }
+.card.card-bundle .details .bundle-details span {
+  display: block;
+  font-size: 35px;
+  font-weight: 300
+}
 
-  .card .authors-container[data-collaborators="3"] {
-    width: 80px
-  }
+.card.card-bundle .details .bundle-details p {
+  font-size: 12px;
+  font-weight: 500
+}
 
-  .card .authors-container[data-collaborators="4"] {
-    width: 100px
-  }
+.card.card-bundle .details .framework-logo {
+  position: absolute;
+  width: 100%;
+  padding-bottom: 5px;
+  padding-top: 40px;
+  left: 0;
+  right: 0;
+  top: 0;
+  margin-right: auto;
+  margin-left: auto
+}
 
-  .card .authors-container .author:nth-child(2) {
-    margin-left: -14px
-  }
+.card.card-bundle .details .framework-logo img {
+  width: 70px;
+  height: 70px;
+  border-radius: 50%
+}
 
-  .card .authors-container .author:nth-child(3) {
-    margin-left: -14px
-  }
+.card.card-bundle .details .framework-logo img.default,
+.card.card-bundle .details .framework-logo img.material {
+  width: 50px;
+  height: 50px;
+  margin-left: 10px;
+  margin-top: 10px;
+  margin-bottom: 10px
+}
 
-  .card .authors-container .author:nth-child(4) {
-    margin-left: -14px
-  }
+.card.card-bundle .details .framework-logo p {
+  padding-top: 20px;
+  font-size: 30px;
+  font-weight: 400
+}
 
-  .card .authors-container {
-    display: inline-block
-  }
+.card.card-bundle .mask {
+  display: block;
+  position: absolute;
+  width: 100%;
+  height: 100%;
+  left: 0;
+  top: 0;
+  opacity: .8;
+  border-radius: 8px
+}
 
-  .card .authors-container>.author>.account-photo {
-    width: 36px;
-    height: 36px
-  }
+.card .authors-container {
+  width: 38px
+}
 
-  .card .author>.account-photo.account-product-owner {
-    border-color: #ff9500
-  }
+.card .authors-container[data-collaborators="2"] {
+  width: 60px
+}
+
+.card .authors-container[data-collaborators="3"] {
+  width: 80px
+}
+
+.card .authors-container[data-collaborators="4"] {
+  width: 100px
+}
+
+.card .authors-container .author:nth-child(2) {
+  margin-left: -14px
+}
+
+.card .authors-container .author:nth-child(3) {
+  margin-left: -14px
+}
+
+.card .authors-container .author:nth-child(4) {
+  margin-left: -14px
+}
+
+.card .authors-container {
+  display: inline-block
+}
+
+.card .authors-container>.author>.account-photo {
+  width: 36px;
+  height: 36px
+}
+
+.card .author>.account-photo.account-product-owner {
+  border-color: #ff9500
+}
 </style>

+ 5 - 5
site/_themes/src/main.js

@@ -1,8 +1,8 @@
-import Vue from 'vue'
+import { createApp } from 'vue'
 import App from './App.vue'
 
-Vue.config.productionTip = false
+const app = createApp(App)
 
-new Vue({
-  render: h => h(App)
-}).$mount('#app')
+app.__VUE_PROD_DEVTOOLS__ = process.env.NODE_ENV === 'development'
+
+app.mount('#app')

+ 3 - 0
site/_themes/themes.html

@@ -0,0 +1,3 @@
+<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.5.2/css/all.min.css">
+<div id="app"></div>
+<script type="module" src="/src/main.js"></script>

+ 33 - 0
site/_themes/vite.config.js

@@ -0,0 +1,33 @@
+import { defineConfig } from 'vite'
+import vue from '@vitejs/plugin-vue'
+
+const proxyTarget = {
+  target: 'http://localhost:9001'
+}
+
+// https://vitejs.dev/config/
+export default defineConfig({
+  base: process.env.NODE_ENV === 'production' ? '/assets/js/themes/' : './',
+  plugins: [
+    vue()
+  ],
+  resolve: {
+    extensions: ['.js', '.vue', '.json']
+  },
+  build: {
+    rollupOptions: {
+      input: process.env.NODE_ENV === 'production' ? 'themes.html' : 'index.html',
+    }
+  },
+  server: {
+    host: '0.0.0.0',
+    https: false,
+    hotOnly: false,
+    proxy: {
+      '/favicon.png': proxyTarget,
+      '/assets': proxyTarget,
+      '/docs': proxyTarget,
+      '/themes': proxyTarget
+    }
+  }
+})

+ 0 - 30
site/_themes/vue.config.js

@@ -1,30 +0,0 @@
-module.exports = {
-  productionSourceMap: false,
-  publicPath: process.env.NODE_ENV === 'production' ? '/assets/js/themes/' : './',
-  css: { extract: false },
-  chainWebpack: config => {
-    config
-      .plugin('html')
-      .tap(args => {
-        if (process.env.NODE_ENV === 'production') {
-          args[0].filename = 'themes.html'
-          args[0].template = './public/themes.html'
-        }
-        return args
-      })
-  },
-  devServer: {
-    open: false,
-    host: '0.0.0.0',
-    https: false,
-    hotOnly: false,
-    proxy: {
-      '/assets': {
-        target: 'http://localhost:9001'
-      },
-      '/themes': {
-        target: 'http://localhost:9001'
-      }
-    }
-  }
-}