ali 3 年之前
父節點
當前提交
3bb1f031c3

+ 3 - 2
package.json

@@ -33,8 +33,6 @@
     "deepmerge": "^4.3.1",
     "dotenv": "^16.0.3",
     "eslint": "8.22.0",
-    "vue-tiptap-katex-core": "^2.1.1",
-    "vue3-tiptap-katex": "^1.1.19",
     "eslint-config-standard": "^17.0.0",
     "eslint-plugin-import": "^2.26.0",
     "eslint-plugin-n": "^15.6.0",
@@ -44,6 +42,7 @@
     "firebase": "^9.23.0",
     "fs": "^0.0.1-security",
     "fslightbox-vue": "^2.0.2",
+    "html2pdf.js": "^0.10.1",
     "js-abstract-model": "^0.1.35",
     "leaflet": "^1.9.3",
     "moment": "^2.29.4",
@@ -67,8 +66,10 @@
     "vue-advanced-cropper": "^2.8.8",
     "vue-dragscroll": "^4.0.5",
     "vue-router": "^4.1.6",
+    "vue-tiptap-katex-core": "^2.1.1",
     "vue3-carousel": "^0.3.1",
     "vue3-otp-input": "^0.4.1",
+    "vue3-tiptap-katex": "^1.1.19",
     "vuex": "^4.1.0",
     "vuex-persistedstate": "^4.1.0",
     "workbox-build": "^7.0.0",

+ 68 - 0
src/api/models/Project.js

@@ -0,0 +1,68 @@
+import { appApi } from 'src/boot/axios.js'
+import APIRepository from '../classes/APIRepository.js'
+import { Project, ProjectList } from 'src/models/Project.js'
+
+export default class ProjectAPI extends APIRepository {
+  constructor() {
+    super('project', appApi, '/lma/projects', Project)
+    this.APIAdresses = {
+      base: '/lma/projects',
+      byId: (id) => '/lma/projects/' + id
+    }
+    this.CacheList = {
+      base: this.name + this.APIAdresses.base,
+      byId: (id) => this.name + this.APIAdresses.byId(id)
+    }
+  }
+
+  index(data) {
+    return this.sendRequest({
+      apiMethod: 'get',
+      api: this.api,
+      request: this.APIAdresses.base,
+      data: this.getNormalizedSendData({
+        category: null, // Number
+        per_page: 10, // Number
+        page: 1 // Number
+      }, data),
+      resolveCallback: (response) => {
+        const paginate = response.data
+        const results = response.data.results
+        delete paginate.results
+        return {
+          list: new ProjectList(results),
+          paginate
+          // {
+          //   "count": 1,
+          //   "num_pages": 1,
+          //   "per_page": 10,
+          //   "current": 1,
+          //   "next": null,
+          //   "previous": null,
+          // }
+        }
+      },
+      rejectCallback: (error) => {
+        return error
+      }
+    })
+  }
+
+  create (data) {
+    return this.sendRequest({
+      apiMethod: 'post',
+      api: this.api,
+      request: this.APIAdresses.base,
+      data: this.getNormalizedSendData({
+        title: null, // String
+        unit: null // Number
+      }, data),
+      resolveCallback: (response) => {
+        return new Project(response.data)
+      },
+      rejectCallback: (error) => {
+        return error
+      }
+    })
+  }
+}

+ 24 - 33
src/components/PostItem.vue

@@ -1,32 +1,21 @@
 <template>
   <q-card flat
-          class="PostItem col-md-3 col-sm-6 col-12">
-    <q-card-section class="thumbnail">
+          class="PostItem col-md-4 col-sm-6 col-12">
+    <div class="thumbnail">
       <router-link :to="{name: 'Public.Post.Show', params: {id: post.id}}">
-        <q-img class="q-ma-xs"
-               :src="post.thumbnail" />
+        <q-img :src="post.thumbnail" />
       </router-link>
-    </q-card-section>
-    <q-card-section class="title">
+    </div>
+    <div class="title">
       <router-link :to="{name: 'Public.Post.Show', params: {id: post.id}}">
         {{post.title}}
       </router-link>
-    </q-card-section>
+    </div>
     <q-card-actions class="action-section">
       <q-btn color="primary"
              class="btn-show-post"
              :to="{name: 'Public.Post.Show', params: {id: post.id}}">
-        مشاهده
-        <svg xmlns="http://www.w3.org/2000/svg"
-             width="18.387"
-             height="11.502"
-             viewBox="0 0 18.387 11.502">
-          <path id="Combined_Shape"
-                data-name="Combined Shape"
-                d="M-9.338,11.408a.748.748,0,0,0,.388-.656V6.5h8.2A.751.751,0,0,0,0,5.75.75.75,0,0,0-.75,5h-8.2V.75A.751.751,0,0,0-9.338.093.769.769,0,0,0-9.7,0a.735.735,0,0,0-.4.115l-7.938,5a.747.747,0,0,0-.35.635.742.742,0,0,0,.35.634l7.938,5a.751.751,0,0,0,.4.116A.746.746,0,0,0-9.338,11.408ZM-10.45,9.392l-5.78-3.641,5.78-3.642Z"
-                transform="translate(18.387)"
-                fill="#fff" />
-        </svg>
+        مشاهده جزئیات
       </q-btn>
     </q-card-actions>
   </q-card>
@@ -49,29 +38,31 @@ export default defineComponent({
 
 <style lang="scss" scoped>
 .PostItem {
-  background-color: #F6F6F6;
-  border-radius: 8px;
+  background-color: #f6f6f6;
+  border-radius: 54px 0 54px 54px;
+  padding: 20px 20px 30px;
   .thumbnail {
-    padding: 17px 22px 26px;
-
-    .RegisteredSign {
-      display: none;
-    }
-    :deep(img) {
-      border-radius: 10px;
+    margin-bottom: 43px;
+    .q-img {
+      border-radius: 54px 0 54px 54px;
     }
   }
   .title {
-    text-align: left;
-    padding: 0 46px 33px;
+    font-size: 17px;
+    letter-spacing: 0;
+    padding-right: 26px;
+    padding-left: 26px;
+    margin-bottom: 31px;
+    a {
+      color: #475F4A;
+    }
   }
   .action-section {
-    padding: 0;
     .btn-show-post {
       width: 100%;
-      height: 60px;
-      border-top-right-radius: 0;
-      border-top-left-radius: 0;
+      height: 48px;
+      max-width: 252px;
+      margin: auto;
     }
   }
 }

+ 21 - 1
src/components/Widgets/Post/PostList/PostList.vue

@@ -1,6 +1,11 @@
 <template>
   <div class="PostList"
        :style="localOptions.style">
+    <breadcrumbs class="q-mb-xl"
+                 style="margin-top: 29px; margin-bottom: 19px;" />
+    <q-banner class="q-mb-xl">
+      یادداشت ها و مقالات
+    </q-banner>
     <entity-index v-if="mounted"
                   ref="entityIndex"
                   v-model:value="inputs"
@@ -25,10 +30,11 @@ import { EntityIndex } from 'quasar-crud'
 import { mixinWidget } from 'src/mixin/Mixins.js'
 import { APIGateway } from 'src/api/APIGateway.js'
 import PostItem from 'src/components/PostItem.vue'
+import Breadcrumbs from 'src/components/Widgets/Breadcrumbs/Breadcrumbs.vue'
 
 export default {
   name: 'PostList',
-  components: { PostItem, EntityIndex },
+  components: { PostItem, EntityIndex, Breadcrumbs },
   mixins: [mixinWidget],
   data: () => {
     return {
@@ -50,6 +56,20 @@ export default {
   },
   mounted() {
     this.mounted = true
+    this.$store.commit('AppLayout/updateBreadcrumbs', {
+      visible: true,
+      loading: false,
+      path: [
+        {
+          label: 'خانه',
+          to: { name: 'Public.Home' }
+        },
+        {
+          label: 'یادداشت ها و مقالات',
+          to: { name: 'Public.Post.List' }
+        }
+      ]
+    })
   }
 }
 </script>

+ 240 - 35
src/components/Widgets/Post/PostShow/PostShow.vue

@@ -1,39 +1,81 @@
 <template>
-  <div class="post">
-    <q-banner v-if="!post.post"
-              class="banner post-banner">
-      {{post.title}}
-    </q-banner>
-    <q-card v-if="!post.post"
-            flat>
-      <q-card-section>
-        <div class="row q-col-gutter-md">
-          <div class="col-md-2 col-sm-4 col-12 post-thumbnail">
-            <q-img :src="post.thumbnail" />
+  <div class="post printable">
+    <template v-if="!post.loading">
+      <breadcrumbs class="q-mb-xl"
+                   style="margin-top: 29px; margin-bottom: 19px;" />
+      <div ref="printArea"
+           class="print-area">
+        <q-banner v-if="!post.post"
+                  class="banner post-banner">
+          عنوان
+        </q-banner>
+        <div class="attributes row">
+          <div class="col-md-4 col-12">
+            <div class="attribute-item">
+              <div class="attribute-item-icon">
+                <q-icon name="auto_stories"
+                        size="23px" />
+              </div>
+              <div class="attribute-item-title">
+                {{post.title}}
+              </div>
+            </div>
+            <div class="attribute-item">
+              <div v-if="post.category_info"
+                   class="attribute-item-title">
+                {{post.category_info.title}}
+              </div>
+            </div>
           </div>
-          <div class="col-md-10 col-sm-8 col-12 post-summary"
+          <div class="col-md-4 col-12">
+            <div class="attribute-item">
+              <div v-if="post.category_info?.parent?.parent"
+                   class="attribute-item-title">
+                دسته بندی کلی:
+                {{post.category_info.parent.parent.title}}
+              </div>
+            </div>
+            <div class="attribute-item">
+              <div v-if="post.category_info?.parent"
+                   class="attribute-item-title">
+                دسته بندی جزئی:
+                {{post.category_info.parent.title}}
+              </div>
+            </div>
+          </div>
+          <div v-if="false"
+               class="col-md-4 col-12 flex justify-end items-start">
+            <q-btn color="primary"
+                   class="print-btn"
+                   @click="print">
+              دریافت فایل
+            </q-btn>
+          </div>
+        </div>
+        <div class="summary">
+          <div class="summary-title">
+            خلاصه
+          </div>
+          <div class="summary-body"
                v-html="post.summary" />
         </div>
-      </q-card-section>
 
-      <q-separator />
-      <q-card-section>
-        <div class="post-text"
-             v-html="post.text" />
-      </q-card-section>
-    </q-card>
+        <q-separator />
 
-    <q-skeleton v-if="post.post"
-                type="circle"
-                size="100px"
-                class="q-mt-md" />
-    <q-skeleton v-if="post.post"
-                width="150px"
-                class="q-mt-md" />
-    <q-skeleton v-if="post.post"
-                height="150px"
-                class="q-mt-md" />
+        <div class="post-body"
+             v-html="post.text" />
+      </div>
+    </template>
 
+    <template v-else>
+      <q-skeleton type="circle"
+                  size="100px"
+                  class="q-mt-md" />
+      <q-skeleton width="150px"
+                  class="q-mt-md" />
+      <q-skeleton height="150px"
+                  class="q-mt-md" />
+    </template>
   </div>
 </template>
 
@@ -42,17 +84,29 @@ import { Post } from 'src/models/Post.js'
 import ShamsiDate from 'src/assets/ShamsiDate.js'
 import { APIGateway } from 'src/api/APIGateway.js'
 import { mixinPrefetchServerData, mixinWidget } from 'src/mixin/Mixins.js'
+import Breadcrumbs from 'src/components/Widgets/Breadcrumbs/Breadcrumbs.vue'
+
+let html2pdf
+if (typeof window !== 'undefined') {
+  import('html2pdf.js')
+    .then((html2pdfLib) => {
+      html2pdf = html2pdfLib.default
+    })
+}
 
 export default {
   name: 'ShowClassroomInfo',
+  components: { Breadcrumbs },
   mixins: [mixinWidget, mixinPrefetchServerData],
   emits: ['onloadn'],
-  data: () => ({
-    defaultOptions: {
-      profileMode: false
-    },
-    post: new Post()
-  }),
+  data () {
+    return {
+      defaultOptions: {
+        profileMode: false
+      },
+      post: new Post()
+    }
+  },
   methods: {
     prefetchServerDataPromise () {
       return this.getPost()
@@ -60,6 +114,24 @@ export default {
     prefetchServerDataPromiseThen (post) {
       this.post = new Post(post)
       this.post.post = false
+      this.$store.commit('AppLayout/updateBreadcrumbs', {
+        visible: true,
+        loading: false,
+        path: [
+          {
+            label: 'خانه',
+            to: { name: 'Public.Home' }
+          },
+          {
+            label: 'یادداشت ها و مقالات',
+            to: { name: 'Public.Post.List' }
+          },
+          {
+            label: this.post.title,
+            to: { name: 'Public.Post.Show', params: { id: this.post.id } }
+          }
+        ]
+      })
       this.$emit('onloadn', this.post)
     },
     prefetchServerDataPromiseCatch () {
@@ -75,6 +147,94 @@ export default {
     getPost () {
       this.post.post = true
       return APIGateway.post.get(this.$route.params.id)
+    },
+    print () {
+      this.convertPhotosToBase64()
+      const html2pdfConfig = {
+        margin: [0, 0, 0, 0],
+        image: {
+          type: 'jpeg',
+          quality: 0.6
+        },
+        filename: this.post.title,
+        html2canvas: {
+          dpi: 1,
+          scale: 2.5,
+          letterRendering: true,
+          useCORS: true
+        },
+        jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait' }
+      }
+      html2pdf()
+        .set(html2pdfConfig)
+        .from(this.$refs.printArea)
+        .save()
+        .thenExternal(() => {
+          this.downloadLoading = false
+        })
+    },
+    toDataURL(src, callback, outputFormat) {
+      const img = new Image()
+      img.crossOrigin = 'Anonymous'
+      img.onload = function() {
+        const canvas = document.createElement('CANVAS')
+        const ctx = canvas.getContext('2d')
+        canvas.height = this.naturalHeight
+        canvas.width = this.naturalWidth
+        ctx.drawImage(this, 0, 0)
+        const dataURL = canvas.toDataURL(outputFormat)
+        callback(dataURL)
+      }
+      if (src.includes('?')) {
+        img.src = src
+      } else {
+        img.src = src + '?test=123'
+      }
+      if (img.complete || img.complete === undefined) {
+        img.src = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw=='
+        img.src = src
+      }
+    },
+    convertPhotosToBase64 () {
+      this.convertSvgToBase64()
+      this.convertImagesToBase64()
+    },
+    convertImagesToBase64 () {
+      if (!this.$refs.printArea) {
+        return
+      }
+      const images = this.$refs.printArea.getElementsByTagName('img')
+      images.forEach(image => {
+        this.toDataURL(image.src, function(dataUrl) {
+          image.src = dataUrl
+        })
+      })
+    },
+    convertSvgToBase64 () {
+      if (!this.$refs.printArea) {
+        return
+      }
+      const svgs = this.$refs.printArea.getElementsByTagName('svg')
+      svgs.forEach(svg => {
+        // Convert the SVG node to HTML.
+        const div = document.createElement('div')
+        div.appendChild(svg.cloneNode(true))
+
+        // Encode the SVG as base64
+        const b64 = 'data:image/svg+xml;base64,' + window.btoa(div.innerHTML)
+        const url = 'url("' + b64 + '")'
+        svg.style.backgroundImage = url
+        svg.style.backgroundSize = 'cover'
+        if (svg.parentElement.classList.contains('brace-left')) {
+          svg.style.backgroundPosition = 'left'
+        }
+        if (svg.parentElement.classList.contains('brace-center')) {
+          svg.style.backgroundPosition = 'bottom'
+        }
+        if (svg.parentElement.classList.contains('brace-right')) {
+          svg.style.backgroundPosition = 'right'
+        }
+      })
     }
   }
 }
@@ -86,5 +246,50 @@ export default {
     margin-top: 52px;
     margin-bottom: 42px;
   }
+  .attributes {
+    .attribute-item {
+      display: flex;
+      flex-flow: row;
+      margin-bottom: 46px;
+      .attribute-item-icon {
+        color: #AAA095;
+        margin-right: 8px;
+      }
+      .attribute-item-title {
+        color: #475f4a;
+        font-size: 16px;
+      }
+    }
+  }
+  .print-btn {
+    width: 252px;
+  }
+  .summary {
+    margin-bottom: 16px;
+    .summary-title {
+      font-size: 18px;
+      color: #212121;
+      position: relative;
+      padding-left: 8px;
+      margin-bottom: 33px;
+      &:before {
+        content: ' ';
+        position: absolute;
+        top: 11px;
+        left: 0;
+        background: #EAC38A;
+        width: 4px;
+        height: 4px;
+        border-radius: 2px;
+      }
+    }
+    .summary-body {
+      font-size: 20px;
+      color: #707070;
+    }
+  }
+  .post-body {
+    margin-top: 32px;
+  }
 }
 </style>

+ 48 - 0
src/models/Project.js

@@ -0,0 +1,48 @@
+import { Classroom } from 'src/models/Classroom.js'
+import { ContentList } from 'src/models/Content.js'
+import { Model, Collection } from 'js-abstract-model'
+
+class Project extends Model {
+  constructor (data) {
+    super(data, [
+      { key: 'id' },
+      { key: 'title' },
+      { key: 'description' },
+      { key: 'type' },
+      { key: 'beginning_doing_period' },
+      { key: 'ending_doing_period' },
+      { key: 'classroom' },
+      {
+        key: 'classroom_info',
+        relatedModel: Classroom
+      },
+      { key: 'contents' },
+      {
+        key: 'contents_info',
+        relatedModel: ContentList
+      }
+    ], {
+      type: {
+        infoKey: 'type_info',
+        enumListKey: 'typeEnums',
+        enums: [
+          {
+            label: 'گروهی',
+            value: 'TEAM'
+          },
+          {
+            label: 'تکی',
+            value: 'SOLO'
+          }
+        ]
+      }
+    })
+  }
+}
+
+class ProjectList extends Collection {
+  model () {
+    return Project
+  }
+}
+export { Project, ProjectList }

+ 143 - 2
yarn.lock

@@ -927,6 +927,13 @@
   dependencies:
     regenerator-runtime "^0.13.11"
 
+"@babel/runtime@^7.14.0":
+  version "7.22.15"
+  resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.15.tgz#38f46494ccf6cf020bd4eed7124b425e83e523b8"
+  integrity sha512-T0O+aa+4w0u06iNmapipJXMV4HoUir03hpx3/YqXXhu9xim3w+dVphjFWl1OH8NbZHw5Lbm9k45drDkgq2VNNA==
+  dependencies:
+    regenerator-runtime "^0.14.0"
+
 "@babel/template@^7.22.5":
   version "7.22.5"
   resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.5.tgz#0c8c4d944509875849bd0344ff0050756eefc6ec"
@@ -2184,6 +2191,11 @@
   resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb"
   integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==
 
+"@types/raf@^3.4.0":
+  version "3.4.0"
+  resolved "https://registry.yarnpkg.com/@types/raf/-/raf-3.4.0.tgz#2b72cbd55405e071f1c4d29992638e022b20acc2"
+  integrity sha512-taW5/WYqo36N7V39oYyHP9Ipfd5pNFvGTIQsNGj86xV88YQ7GnI30/yMfKDF7Zgin0m3e+ikX88FvImnK4RjGw==
+
 "@types/range-parser@*":
   version "1.2.4"
   resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc"
@@ -2600,6 +2612,11 @@ at-least-node@^1.0.0:
   resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2"
   integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==
 
+atob@^2.1.2:
+  version "2.1.2"
+  resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
+  integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
+
 autoprefixer@^10.4.14:
   version "10.4.14"
   resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.14.tgz#e28d49902f8e759dd25b153264e862df2705f79d"
@@ -2663,6 +2680,11 @@ balanced-match@^1.0.0:
   resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
   integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
 
+base64-arraybuffer@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz#1c37589a7c4b0746e34bd1feb951da2df01c1bdc"
+  integrity sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==
+
 base64-js@^1.3.1, base64-js@^1.5.1:
   version "1.5.1"
   resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
@@ -2749,6 +2771,11 @@ browserslist@^4.21.3, browserslist@^4.21.5:
     node-releases "^2.0.12"
     update-browserslist-db "^1.0.11"
 
+btoa@^1.2.1:
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/btoa/-/btoa-1.2.1.tgz#01a9909f8b2c93f6bf680ba26131eb30f7fa3d73"
+  integrity sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==
+
 buffer-crc32@^0.2.1, buffer-crc32@^0.2.13, buffer-crc32@~0.2.3:
   version "0.2.13"
   resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
@@ -2825,6 +2852,20 @@ caniuse-lite@^1.0.30001464, caniuse-lite@^1.0.30001503:
   resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001507.tgz#fae53f6286e7564783eadea9b447819410a59534"
   integrity sha512-SFpUDoSLCaE5XYL2jfqe9ova/pbQHEmbheDf5r4diNwbAgR3qxM9NQtfsiSscjqoya5K7kFcHPUQ+VsUkIJR4A==
 
+canvg@^3.0.6:
+  version "3.0.10"
+  resolved "https://registry.yarnpkg.com/canvg/-/canvg-3.0.10.tgz#8e52a2d088b6ffa23ac78970b2a9eebfae0ef4b3"
+  integrity sha512-qwR2FRNO9NlzTeKIPIKpnTY6fqwuYSequ8Ru8c0YkYU7U0oW+hLUvWadLvAu1Rl72OMNiFhoLu4f8eUjQ7l/+Q==
+  dependencies:
+    "@babel/runtime" "^7.12.5"
+    "@types/raf" "^3.4.0"
+    core-js "^3.8.3"
+    raf "^3.4.1"
+    regenerator-runtime "^0.13.7"
+    rgbcolor "^1.0.1"
+    stackblur-canvas "^2.0.0"
+    svg-pathdata "^6.0.3"
+
 case-anything@^2.1.13:
   version "2.1.13"
   resolved "https://registry.yarnpkg.com/case-anything/-/case-anything-2.1.13.tgz#0cdc16278cb29a7fcdeb072400da3f342ba329e9"
@@ -3063,7 +3104,7 @@ core-js@^2.4.0:
   resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec"
   integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==
 
-core-js@^3.26.1:
+core-js@^3.26.1, core-js@^3.6.0, core-js@^3.8.3:
   version "3.32.2"
   resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.32.2.tgz#172fb5949ef468f93b4be7841af6ab1f21992db7"
   integrity sha512-pxXSw1mYZPDGvTQqEc5vgIb83jGQKFGYWY76z4a7weZXUolw3G+OvpZqSRcfYOoOVUQJYEPsWeQK8pKEnUtWxQ==
@@ -3115,6 +3156,13 @@ crypto-random-string@^2.0.0:
   resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5"
   integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==
 
+css-line-break@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/css-line-break/-/css-line-break-2.1.0.tgz#bfef660dfa6f5397ea54116bb3cb4873edbc4fa0"
+  integrity sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==
+  dependencies:
+    utrie "^1.0.2"
+
 cssesc@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
@@ -3232,6 +3280,11 @@ domino@^2.1.6:
   resolved "https://registry.yarnpkg.com/domino/-/domino-2.1.6.tgz#fe4ace4310526e5e7b9d12c7de01b7f485a57ffe"
   integrity sha512-3VdM/SXBZX2omc9JF9nOPCtDaYQ67BGp5CoLpIQlO2KCAPETs8TcDHacF26jXadGbvUteZzRTeos2fhID5+ucQ==
 
+dompurify@^2.2.0:
+  version "2.4.7"
+  resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.4.7.tgz#277adeb40a2c84be2d42a8bcd45f582bfa4d0cfc"
+  integrity sha512-kxxKlPEDa6Nc5WJi+qRgPbOAbgTpSULL+vI3NUXsZMlkJxTqYI9wg5ZTay2sFrdZRWHPWNi+EdAhcJf81WtoMQ==
+
 dot-prop@6.0.1:
   version "6.0.1"
   resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-6.0.1.tgz#fc26b3cf142b9e59b74dbd39ed66ce620c681083"
@@ -3393,6 +3446,11 @@ es5-shim@^4.5.1:
   resolved "https://registry.yarnpkg.com/es5-shim/-/es5-shim-4.6.7.tgz#bc67ae0fc3dd520636e0a1601cc73b450ad3e955"
   integrity sha512-jg21/dmlrNQI7JyyA2w7n+yifSxBng0ZralnSfVZjoCawgNTCnS+yBCyVM9DL5itm7SUnDGgv7hcq2XCZX4iRQ==
 
+es6-promise@^4.2.5:
+  version "4.2.8"
+  resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a"
+  integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==
+
 esbuild-android-64@0.14.51:
   version "0.14.51"
   resolved "https://registry.yarnpkg.com/esbuild-android-64/-/esbuild-android-64-0.14.51.tgz#414a087cb0de8db1e347ecca6c8320513de433db"
@@ -3992,6 +4050,11 @@ fd-slicer@~1.1.0:
   dependencies:
     pend "~1.2.0"
 
+fflate@^0.4.8:
+  version "0.4.8"
+  resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.4.8.tgz#f90b82aefbd8ac174213abb338bd7ef848f0f5ae"
+  integrity sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==
+
 figures@^3.0.0:
   version "3.2.0"
   resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af"
@@ -4470,6 +4533,23 @@ html-minifier@^4.0.0:
     relateurl "^0.2.7"
     uglify-js "^3.5.1"
 
+html2canvas@^1.0.0, html2canvas@^1.0.0-rc.5:
+  version "1.4.1"
+  resolved "https://registry.yarnpkg.com/html2canvas/-/html2canvas-1.4.1.tgz#7cef1888311b5011d507794a066041b14669a543"
+  integrity sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==
+  dependencies:
+    css-line-break "^2.1.0"
+    text-segmentation "^1.0.3"
+
+html2pdf.js@^0.10.1:
+  version "0.10.1"
+  resolved "https://registry.yarnpkg.com/html2pdf.js/-/html2pdf.js-0.10.1.tgz#9363910cca52a54113633e552a726722209a8eed"
+  integrity sha512-3onwwhOWsZfNjIZwV6YIJ6FVhXk+X9YxHSqzeS6hup+1dGi2DHI+zZYUJ+iFnvtaYcjlhyrILL1fvRCUOa8Fcg==
+  dependencies:
+    es6-promise "^4.2.5"
+    html2canvas "^1.0.0"
+    jspdf "^2.3.1"
+
 http-errors@2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3"
@@ -4909,6 +4989,21 @@ jsonpointer@^5.0.0:
   resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-5.0.1.tgz#2110e0af0900fd37467b5907ecd13a7884a1b559"
   integrity sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==
 
+jspdf@^2.3.1:
+  version "2.5.1"
+  resolved "https://registry.yarnpkg.com/jspdf/-/jspdf-2.5.1.tgz#00c85250abf5447a05f3b32ab9935ab4a56592cc"
+  integrity sha512-hXObxz7ZqoyhxET78+XR34Xu2qFGrJJ2I2bE5w4SM8eFaFEkW2xcGRVUss360fYelwRSid/jT078kbNvmoW0QA==
+  dependencies:
+    "@babel/runtime" "^7.14.0"
+    atob "^2.1.2"
+    btoa "^1.2.1"
+    fflate "^0.4.8"
+  optionalDependencies:
+    canvg "^3.0.6"
+    core-js "^3.6.0"
+    dompurify "^2.2.0"
+    html2canvas "^1.0.0-rc.5"
+
 karma-safaritechpreview-launcher@0.0.6:
   version "0.0.6"
   resolved "https://registry.yarnpkg.com/karma-safaritechpreview-launcher/-/karma-safaritechpreview-launcher-0.0.6.tgz#7a841105aeb7053940e33df850edcf220eed906a"
@@ -5698,6 +5793,11 @@ pend@~1.2.0:
   resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
   integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==
 
+performance-now@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
+  integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==
+
 persian-date@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/persian-date/-/persian-date-1.1.0.tgz#a06155797bca20217fa8302101ea5efd041a3221"
@@ -6074,6 +6174,13 @@ queue-microtask@^1.2.2:
   resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
   integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
 
+raf@^3.4.1:
+  version "3.4.1"
+  resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39"
+  integrity sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==
+  dependencies:
+    performance-now "^2.1.0"
+
 randombytes@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
@@ -6149,11 +6256,16 @@ regenerator-runtime@^0.11.0:
   resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9"
   integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==
 
-regenerator-runtime@^0.13.11:
+regenerator-runtime@^0.13.11, regenerator-runtime@^0.13.7:
   version "0.13.11"
   resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9"
   integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==
 
+regenerator-runtime@^0.14.0:
+  version "0.14.0"
+  resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz#5e19d68eb12d486f797e15a3c6a918f7cec5eb45"
+  integrity sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==
+
 regenerator-transform@^0.15.1:
   version "0.15.1"
   resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.15.1.tgz#f6c4e99fc1b4591f780db2586328e4d9a9d8dc56"
@@ -6241,6 +6353,11 @@ reusify@^1.0.4:
   resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
   integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
 
+rgbcolor@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/rgbcolor/-/rgbcolor-1.0.1.tgz#d6505ecdb304a6595da26fa4b43307306775945d"
+  integrity sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==
+
 rimraf@^2.2.8:
   version "2.7.1"
   resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
@@ -6591,6 +6708,11 @@ stack-trace@^1.0.0-pre2:
   resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-1.0.0-pre2.tgz#46a83a79f1b287807e9aaafc6a5dd8bcde626f9c"
   integrity sha512-2ztBJRek8IVofG9DBJqdy2N5kulaacX30Nz7xmkYF6ale9WBVmIy6mFBchvGX7Vx/MyjBhx+Rcxqrj+dbOnQ6A==
 
+stackblur-canvas@^2.0.0:
+  version "2.6.0"
+  resolved "https://registry.yarnpkg.com/stackblur-canvas/-/stackblur-canvas-2.6.0.tgz#7876bab4ea99bfc97b69ce662614d7a1afb2d71b"
+  integrity sha512-8S1aIA+UoF6erJYnglGPug6MaHYGo1Ot7h5fuXx4fUPvcvQfcdw2o/ppCse63+eZf8PPidSu4v1JnmEVtEDnpg==
+
 statuses@2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63"
@@ -6715,6 +6837,11 @@ supports-preserve-symlinks-flag@^1.0.0:
   resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
   integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
 
+svg-pathdata@^6.0.3:
+  version "6.0.3"
+  resolved "https://registry.yarnpkg.com/svg-pathdata/-/svg-pathdata-6.0.3.tgz#80b0e0283b652ccbafb69ad4f8f73e8d3fbf2cac"
+  integrity sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==
+
 table@^6.8.0:
   version "6.8.1"
   resolved "https://registry.yarnpkg.com/table/-/table-6.8.1.tgz#ea2b71359fe03b017a5fbc296204471158080bdf"
@@ -6774,6 +6901,13 @@ terser@^5.0.0:
     commander "^2.20.0"
     source-map-support "~0.5.20"
 
+text-segmentation@^1.0.3:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/text-segmentation/-/text-segmentation-1.0.3.tgz#52a388159efffe746b24a63ba311b6ac9f2d7943"
+  integrity sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==
+  dependencies:
+    utrie "^1.0.2"
+
 text-table@^0.2.0:
   version "0.2.0"
   resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
@@ -7110,6 +7244,13 @@ utils-merge@1.0.1:
   resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
   integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==
 
+utrie@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/utrie/-/utrie-1.0.2.tgz#d42fe44de9bc0119c25de7f564a6ed1b2c87a645"
+  integrity sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==
+  dependencies:
+    base64-arraybuffer "^1.0.2"
+
 v8-compile-cache@^2.0.3:
   version "2.3.0"
   resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee"