Quellcode durchsuchen

Merge branch 'master' of http://git.hghweb.ir/ali.s/palizvani-front-vite into production

hgh vor 2 Jahren
Ursprung
Commit
79ab3cce86

+ 2 - 1
src/api/models/PageSetting.js

@@ -63,7 +63,8 @@ export default class PageSettingAPI extends APIRepository {
   }
 
   update (data) {
-    const settingKey = encodeURI(data.key)
+    // const settingKey = encodeURI(data.key)
+    const settingKey = data.key
     const settingValue = data.value
     return this.sendRequest({
       apiMethod: 'post',

+ 19 - 23
src/boot/axios.js

@@ -26,45 +26,40 @@ const AxiosHooks = (function () {
   function handleErrors (error, router, store) {
     const messages = []
     if (!error || !error.response) {
-      return
+      return error // Reject in case of network errors
     }
+
     const statusCode = parseInt(error.response.status)
+
     if (statusCode >= 500 && statusCode <= 599) {
       messages.push('مشکلی رخ داده است. مجدد تلاش کنید.')
     } else if (statusCode === 404) {
-      if (error.response.data.detail) {
-        messages.push(error.response.data.detail)
-      } else {
-        messages.push('موردی یافت نشد.')
-      }
+      messages.push(error.response.data.detail || 'موردی یافت نشد.')
     } else if (statusCode === 401) {
-      if (error.response.data.detail) {
-        messages.push(error.response.data.detail)
-      }
-      // const message = error.response.data.message
-      // messages.push(message)
+      messages.push(error.response.data.detail || 'Unauthorized access.')
       deAuthorizeUser(router, store)
     } else if (statusCode === 403) {
-      const message = error.response.data.detail
-      messages.push(message)
+      messages.push(error.response.data.detail || 'Forbidden access.')
     } else if (error.response.data) {
+      // Handle other errors by iterating over response data
       if (error.response.data.detail) {
         messages.push(error.response.data.detail)
       } else {
         for (const key of Object.keys(error.response.data)) {
-          if (typeof error.response.data[key] === 'string') {
-            messages.push(key + ': ' + error.response.data[key])
-          } else if (Array.isArray(error.response.data[key])) {
-            error.response.data[key].forEach(message => {
-              messages.push(key + ': ' + message)
-            })
+          const message = error.response.data[key]
+          if (typeof message === 'string') {
+            messages.push(`${key}: ${message}`)
+          } else if (Array.isArray(message)) {
+            message.forEach(msg => messages.push(`${key}: ${msg}`))
           }
         }
       }
     }
 
     toastMessages(messages)
-    return Promise.reject(error)
+
+    // Return the error to be handled by the calling function
+    return error
   }
 
   function handleFulfilled (response) {
@@ -158,9 +153,10 @@ export default boot(({ app, store, router, ssrContext }) => {
   if (appApi.interceptors) {
     appApi.interceptors.response.use(function (response) {
       AxiosHooks.handleFulfilled(response)
-      return Promise.resolve(response)
-    }, async function (error) {
-      return Promise.reject(await AxiosHooks.handleErrors(error, router, store))
+      return response
+    }, function (error) {
+      AxiosHooks.handleErrors(error, router, store)
+      return Promise.reject(error) // Ensure the error is rejected properly
     })
   }
 

+ 6 - 3
src/components/FormBuilderCustumComponents/ContentCategorySelector.vue

@@ -20,7 +20,8 @@
           </div>
           <q-select v-model="mainCategory"
                     :options="mainCategoryOptions"
-                    :loading="categories.laoding" />
+                    :loading="categories.laoding"
+                    clearable />
         </div>
         <div class="col-md-4 col-12">
           <div>
@@ -28,7 +29,8 @@
           </div>
           <q-select v-model="subCategory"
                     :options="subCategoryOptions"
-                    :loading="categories.laoding" />
+                    :loading="categories.laoding"
+                    clearable />
         </div>
         <div class="col-md-4 col-12">
           <div>
@@ -36,7 +38,8 @@
           </div>
           <q-select v-model="bakhshCategory"
                     :options="bakhshCategoryOptions"
-                    :loading="categories.laoding" />
+                    :loading="categories.laoding"
+                    clearable />
         </div>
       </div>
     </q-card-section>

+ 6 - 3
src/components/FormBuilderCustumComponents/PostCategorySelector.vue

@@ -6,7 +6,8 @@
       </div>
       <q-select v-model="mainCategory"
                 :options="mainCategoryOptions"
-                :loading="postCategories.laoding" />
+                :loading="postCategories.laoding"
+                clearable />
     </div>
     <div class="col-md-4 col-12">
       <div>
@@ -14,7 +15,8 @@
       </div>
       <q-select v-model="subCategory"
                 :options="subCategoryOptions"
-                :loading="postCategories.laoding" />
+                :loading="postCategories.laoding"
+                clearable />
     </div>
     <div class="col-md-4 col-12">
       <div>
@@ -22,7 +24,8 @@
       </div>
       <q-select v-model="bakhshCategory"
                 :options="bakhshCategoryOptions"
-                :loading="postCategories.laoding" />
+                :loading="postCategories.laoding"
+                clearable />
     </div>
   </div>
 </template>

+ 2 - 2
src/components/Template/Header/MainHeader.vue

@@ -68,7 +68,7 @@
                     <q-item-section>کتب سطح سه</q-item-section>
                   </q-item>
                   <q-item v-close-popup
-                          href="https://formafzar.com/form/dttpf"
+                          href="https://formafzar.com/form/yk7z7"
                           target="_blank"
                           clickable>
                     <q-item-section>کتب سطح چهار</q-item-section>
@@ -92,7 +92,7 @@
                     <q-item-section>کتب جریان‌شناسی سطح دو</q-item-section>
                   </q-item>
                   <q-item v-close-popup
-                          href="https://formafzar.com/form/j3x1d"
+                          href="https://formafzar.com/form/p1y9a"
                           target="_blank"
                           clickable>
                     <q-item-section>کتاب تشکیلات بهشتی</q-item-section>

+ 2 - 2
src/components/Template/SideBar/Main.vue

@@ -95,7 +95,7 @@ export default {
             },
             {
               title: 'کتب سطح چهار',
-              externalLink: 'https://formafzar.com/form/dttpf',
+              externalLink: 'https://formafzar.com/form/yk7z7',
               show: true
             },
             {
@@ -115,7 +115,7 @@ export default {
             },
             {
               title: 'کتاب تشکیلات بهشتی',
-              externalLink: 'https://formafzar.com/form/j3x1d',
+              externalLink: 'https://formafzar.com/form/p1y9a',
               show: true
             },
             {

+ 144 - 0
src/components/Utils/Editor2.vue

@@ -0,0 +1,144 @@
+<template>
+  <q-editor ref="editor"
+            v-model="editorValue"
+            min-height="10rem"
+            :toolbar="[
+              [
+                {
+                  label: $q.lang.editor.align,
+                  icon: $q.iconSet.editor.align,
+                  fixedLabel: true,
+                  options: ['left', 'center', 'right', 'justify']
+                }
+              ],
+              ['bold', 'italic', 'strike', 'underline', 'subscript', 'superscript', 'colorpicker'],
+              ['token', 'hr', 'link', 'custom_btn'],
+              ['print', 'fullscreen'],
+              [
+                {
+                  label: $q.lang.editor.formatting,
+                  icon: $q.iconSet.editor.formatting,
+                  list: 'no-icons',
+                  options: ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'code']
+                },
+                {
+                  label: $q.lang.editor.fontSize,
+                  icon: $q.iconSet.editor.fontSize,
+                  fixedLabel: true,
+                  fixedIcon: true,
+                  list: 'no-icons',
+                  options: [
+                    'size-1',
+                    'size-2',
+                    'size-3',
+                    'size-4',
+                    'size-5',
+                    'size-6',
+                    'size-7'
+                  ]
+                },
+                {
+                  label: $q.lang.editor.defaultFont,
+                  icon: $q.iconSet.editor.font,
+                  fixedIcon: true,
+                  list: 'no-icons',
+                  options: [
+                    'default_font',
+                    'arial',
+                    'arial_black',
+                    'comic_sans',
+                    'courier_new',
+                    'impact',
+                    'lucida_grande',
+                    'times_new_roman',
+                    'verdana'
+                  ]
+                },
+                'removeFormat'
+              ],
+              ['quote', 'unordered', 'ordered', 'outdent', 'indent'],
+
+              ['undo', 'redo'],
+              ['viewsource']
+            ]"
+            :fonts="{
+              IRANSans: 'IRANSans',
+              arial: 'Arial',
+              arial_black: 'Arial Black',
+              comic_sans: 'Comic Sans MS',
+              courier_new: 'Courier New',
+              impact: 'Impact',
+              lucida_grande: 'Lucida Grande',
+              times_new_roman: 'Times New Roman',
+              verdana: 'Verdana',
+            }"
+            @update:model-value="onChangeText"
+            @paste="onPaste">
+    <template v-slot:token>
+      <q-btn-dropdown ref="colorPicker"
+                      dense
+                      unelevated
+                      icon="colorize"
+                      size="sm">
+        <q-list dense>
+          <q-item tag="label"
+                  clickable>
+            <q-item-section>
+              <q-btn icon="check"
+                     @click="color('foreColor', foreColor)" />
+              <q-color v-model="foreColor"
+                       class="my-picker" />
+            </q-item-section>
+          </q-item>
+        </q-list>
+      </q-btn-dropdown>
+    </template>
+  </q-editor>
+</template>
+
+<script>
+export default {
+  name: 'Editor',
+  props: {
+    value: {
+      type: String,
+      default() {
+        return null
+      }
+    }
+  },
+  data() {
+    return {
+      foreColor: '#000000'
+    }
+  },
+  computed: {
+    editorValue: {
+      get() {
+        return this.value
+      },
+      set(newValue) {
+        this.$emit('update:value', newValue)
+      }
+    }
+  },
+  methods: {
+    onPaste(evt) {
+      const str = evt.clipboardData.getData('text')
+      const replace = str.replace(/(<([^>]+)>)/ig, '')
+      this.editorValue = replace
+    },
+    color(cmd, name) {
+      const edit = this.$refs.editor
+      this.$refs.colorPicker.hide()
+      edit.caret.restore()
+      edit.runCmd(cmd, name)
+      edit.focus()
+    }
+  }
+}
+</script>
+
+<style scoped>
+
+</style>

+ 22 - 0
src/components/Widgets/Banner/Banner.vue

@@ -0,0 +1,22 @@
+<template>
+  <q-banner :style="localOptions.style">
+    <div v-html="localOptions.text" />
+  </q-banner>
+</template>
+
+<script>
+import { mixinWidget } from 'src/mixin/Mixins.js'
+
+export default {
+  name: 'Banner',
+  mixins: [mixinWidget],
+  data() {
+    return {
+      defaultOptions: {
+        style: null,
+        text: null
+      }
+    }
+  }
+}
+</script>

+ 48 - 0
src/components/Widgets/Banner/OptionPanel.vue

@@ -0,0 +1,48 @@
+<template>
+  <option-panel-tabs v-model:options="localOptions">
+    <template #main-tab>
+      <div class="option-panel-container">
+        <div class="input-container q-py-md">
+          <div class="outsideLabel">text</div>
+          <editor v-model:value="localOptions.text" />
+        </div>
+      </div>
+    </template>
+  </option-panel-tabs>
+</template>
+<script>
+import { defineComponent } from 'vue'
+import Editor from 'src/components/Utils/Editor.vue'
+import { mixinOptionPanel } from 'quasar-ui-q-page-builder'
+import OptionPanelTabs from 'quasar-ui-q-page-builder/src/components/OptionPanelComponents/OptionPanelTabs.vue'
+
+export default defineComponent({
+  name: 'OptionPanel',
+  components: { OptionPanelTabs, Editor },
+  mixins: [mixinOptionPanel],
+  props: {
+    options: {
+      type: Object,
+      default() {
+        return {}
+      }
+    }
+  },
+  data () {
+    return {
+      defaultOptions: {
+        style: null,
+        text: null
+      }
+    }
+  },
+  watch: {
+    localOptions: {
+      handler(newVal) {
+        this.$emit('update:options', newVal)
+      },
+      deep: true
+    }
+  }
+})
+</script>

+ 72 - 10
src/components/Widgets/ContentCategory/ContentCategoryShow/ContentCategoryShow.vue

@@ -66,7 +66,7 @@
 import { EntityIndex } from 'quasar-crud'
 import { Content } from 'src/models/Content.js'
 import { Product } from 'src/models/Product.js'
-import { mixinWidget } from 'src/mixin/Mixins.js'
+import { mixinWidget, mixinPrefetchServerData } from 'src/mixin/Mixins.js'
 import { APIGateway } from 'src/api/APIGateway.js'
 import { FormBuilderAssist } from 'quasar-form-builder'
 import { ContentCategory } from 'src/models/ContentCategory.js'
@@ -77,7 +77,7 @@ import ContentItem from './ContentItem.vue'
 export default {
   name: 'ContentCategoryShow',
   components: { EntityIndex, Breadcrumbs, productItem, ContentItem },
-  mixins: [mixinWidget],
+  mixins: [mixinWidget, mixinPrefetchServerData],
   data () {
     return {
       productApi: APIGateway.product.APIAdresses.base,
@@ -160,18 +160,49 @@ export default {
       return null
     }
   },
-  created() {
-    this.updateBreadcrumbs()
-    FormBuilderAssist.setAttributeByName(this.productInputs, 'content_category', 'value', this.contentCategoryId)
-    FormBuilderAssist.setAttributeByName(this.contentInputs, 'category', 'value', this.contentCategoryId)
-    if (this.contentCategoryId) {
-      this.loadContentCategory()
-    }
-  },
   mounted () {
+    this.updateBreadcrumbs()
     this.mounted = true
   },
   methods: {
+    prefetchServerDataPromise () {
+      FormBuilderAssist.setAttributeByName(this.productInputs, 'content_category', 'value', this.contentCategoryId)
+      FormBuilderAssist.setAttributeByName(this.contentInputs, 'category', 'value', this.contentCategoryId)
+      if (this.contentCategoryId) {
+        this.contentCategory.loading = true
+        return this.getContentCategoryInfo(this.contentCategoryId)
+      }
+
+      return Promise.resolve(null)
+    },
+    prefetchServerDataPromiseThen (contentCategory) {
+      this.contentCategory = new ContentCategory(contentCategory)
+      this.updateBreadcrumbs()
+      if (this.contentCategory.parent) {
+        this.getContentCategoryInfo(this.contentCategory.parent)
+          .then((parentContentCategory) => {
+            this.contentCategoryParent = new ContentCategory(parentContentCategory)
+            this.updateBreadcrumbs()
+            if (this.contentCategoryParent.parent) {
+              this.getContentCategoryInfo(this.contentCategoryParent.parent)
+                .then((parentParentContentCategory) => {
+                  this.contentCategoryParentParent = new ContentCategory(parentParentContentCategory)
+                  this.updateBreadcrumbs()
+                })
+                .finally(() => {
+                  this.contentCategory.loading = false
+                })
+            }
+          })
+          .finally(() => {
+            this.contentCategory.loading = false
+          })
+      }
+    },
+    prefetchServerDataPromiseCatch () {
+      this.contentCategory.loading = false
+    },
+
     getContent (data) {
       return new Content(data)
     },
@@ -212,6 +243,37 @@ export default {
           this.contentCategory.loading = false
         })
     },
+    loadContentCategory1 () {
+      this.contentCategory.loading = true
+      this.getContentCategoryInfo(this.contentCategoryId)
+        .then((contentCategory) => {
+          this.contentCategory = new ContentCategory(contentCategory)
+          this.updateBreadcrumbs()
+          if (this.contentCategory.parent) {
+            this.getContentCategoryInfo(this.contentCategory.parent)
+              .then((parentContentCategory) => {
+                this.contentCategoryParent = new ContentCategory(parentContentCategory)
+                this.updateBreadcrumbs()
+                if (this.contentCategoryParent.parent) {
+                  this.getContentCategoryInfo(this.contentCategoryParent.parent)
+                    .then((parentParentContentCategory) => {
+                      this.contentCategoryParentParent = new ContentCategory(parentParentContentCategory)
+                      this.updateBreadcrumbs()
+                    })
+                    .finally(() => {
+                      this.contentCategory.loading = false
+                    })
+                }
+              })
+              .finally(() => {
+                this.contentCategory.loading = false
+              })
+          }
+        })
+        .finally(() => {
+          this.contentCategory.loading = false
+        })
+    },
     getContentCategoryInfo (contentCategoryId) {
       return APIGateway.contentCategory.get({ data: { id: contentCategoryId } })
     },

+ 116 - 45
src/components/Widgets/FeatureBox/FeatureBox.vue

@@ -1,75 +1,146 @@
 <template>
-  <q-card class="feature-card">
-    <q-card-section class="feature-horizontal-section"
-                    :class="localOptions.className"
-                    :horizontal="localOptions.horizontal">
-      <img class="feature-image"
-           :src="localOptions.image">
-      <q-card-section>
-        <div class="feature-title ellipsis">{{ localOptions.title }}</div>
-        <div class="feature-description">
-          {{ localOptions.description }}
-        </div>
-      </q-card-section>
-    </q-card-section>
-  </q-card>
+  <div class="feature-card"
+       :style="localOptions.style">
+    <img class="feature-image"
+         :class="{'has-url': localOptions.url}"
+         :src="localOptions.image"
+         @click="goToUrl">
+    <div class="feature-meta">
+      <div class="feature-title ellipsis-2-lines">
+        <template v-if="localOptions.url">
+          <a :href="localOptions.url">
+            {{ localOptions.title }}
+          </a>
+        </template>
+        <template v-else>
+          {{ localOptions.title }}
+        </template>
+      </div>
+      <div class="feature-description"
+           v-html="localOptions.description" />
+    </div>
+  </div>
 </template>
 
 <script>
-import { mixinWidget, mixinPrefetchServerData } from 'src/mixin/Mixins.js'
+import { mixinWidget } from 'src/mixin/Mixins.js'
 
 export default {
   name: 'FeatureBox',
-  mixins: [mixinPrefetchServerData, mixinWidget],
+  mixins: [mixinWidget],
   data() {
     return {
       defaultOptions: {
         title: null,
         description: null,
         image: null,
-        horizontal: true
+        horizontal: true,
+        cssHoverEffects: {
+          boxShadows: [],
+          borderStyle: {
+            borderCssString: '',
+            borderRadiusCssString: ''
+          },
+          transition: {
+            time: 0
+          },
+          transform: {
+            rotate: 0,
+            scaleX: 1,
+            scaleY: 1,
+            skewX: 0,
+            skewY: 0,
+            translateX: 0,
+            translateY: 0
+          }
+        }
       }
     }
+  },
+  computed: {
+    hoverShadows () {
+      const shadows = []
+      if (!Array.isArray(this.localOptions.cssHoverEffects?.boxShadows)) {
+        return ''
+      }
+      this.localOptions.cssHoverEffects.boxShadows.forEach(shadow => {
+        shadows.push(shadow.cssString)
+      })
+
+      return shadows.join(', ')
+    },
+    cssHoverEffectsBorderStyle () {
+      const borderCssString = this.localOptions.cssHoverEffects?.borderStyle?.borderCssString ? this.localOptions.cssHoverEffects?.borderStyle?.borderCssString : ''
+      const borderRadiusCssString = this.localOptions.cssHoverEffects?.borderStyle?.borderRadiusCssString ? this.localOptions.cssHoverEffects?.borderStyle?.borderRadiusCssString : ''
+
+      return {
+        borderCssString,
+        borderRadiusCssString
+      }
+    }
+  },
+  methods: {
+    goToUrl () {
+      window.location.href = this.localOptions.url
+    }
   }
 }
 </script>
 
 <style lang="scss" scoped>
-.feature-card {
-  width: 100%;
-  max-width: 100%;
-  box-shadow: none;
-  .feature-horizontal-section {
+$hoverShadows: v-bind('hoverShadows');
+$hoverBorder: v-bind('cssHoverEffectsBorderStyle.borderCssString');
+$hoverBorderRadius: v-bind('cssHoverEffectsBorderStyle.borderRadiusCssString');
+$skewX: v-bind('localOptions.cssHoverEffects.transform.skewX');
+$skewY: v-bind('localOptions.cssHoverEffects.transform.skewY');
+$rotate: v-bind('localOptions.cssHoverEffects.transform.rotate');
+$scaleX: v-bind('localOptions.cssHoverEffects.transform.scaleX');
+$scaleY: v-bind('localOptions.cssHoverEffects.transform.scaleY');
+$translateX: v-bind('localOptions.cssHoverEffects.transform.translateX');
+$translateY: v-bind('localOptions.cssHoverEffects.transform.translateY');
+$transitionTime: v-bind('localOptions.cssHoverEffects.transition.time');
 
-    .feature-image {
-      width: 100px;
-      height: 100px;
+.feature-card {
+  display: flex;
+  align-items: flex-start;
+  justify-content: flex-start;
+  gap: 33px;
+  .feature-image {
+    width: 215px;
+    height: auto;
+    border-radius: 20px;
+    align-self: center;
+    &.has-url {
+      cursor: pointer;
     }
-
+  }
+  .feature-meta {
     .feature-title {
-      font-weight: 700;
+      font-size: 18px;
+      letter-spacing: 0;
+      color: #212121;
+    }
+    .feature-description {
       font-size: 20px;
-      line-height: 35px;
-
-      @media screen and (max-width: 600px) {
-        font-weight: 600;
-        font-size: 18px;
-        line-height: 26px;
-      }
+      letter-spacing: 0;
+      color: #707070;
     }
+  }
 
-    .feature-description {
-      max-width: 300px;
-      font-weight: 400;
-      font-size: 18px;
-      line-height: 31px;
+  &:hover {
+    transform: rotate(calc(#{$rotate} * 1deg)) translate(calc(#{$translateX} * 1px), calc(#{$translateY} * 1px)) scale($scaleX, $scaleY) skew(calc(#{$skewX} * 1deg), calc(#{$skewY} * 1deg));
+    transition: all calc(#{$transitionTime} * 1s);
+    box-shadow: $hoverShadows;
+    -webkit-box-shadow: $hoverShadows;
+    -moz-box-shadow: $hoverShadows;
+    border-radius: $hoverBorderRadius;
+    -webkit-border-radius: $hoverBorderRadius;
+    -moz-border-radius: $hoverBorderRadius;
+    border: $hoverBorder;
+  }
 
-      @media screen and (max-width: 600px) {
-        font-weight: 400;
-        font-size: 14px;
-        line-height: 22px;
-      }
-    }
+  @media screen and (max-width: 800px) {
+    flex-flow: column;
   }
 }
 </style>

+ 42 - 7
src/components/Widgets/FeatureBox/OptionPanel.vue

@@ -1,21 +1,25 @@
 <template>
-  <option-panel-tabs v-model:options="localOptions">
+  <option-panel-tabs v-model:options="localOptions"
+                     :show-hover-effects-tab="true">
     <template #main-tab>
       <div class="option-panel-container">
         <div class="input-container q-py-md">
           <div class="outsideLabel">image</div>
-          <q-input v-model="localOptions.image"
-                   label="image" />
+          <image-uploader v-model:file="localOptions.image" />
         </div>
         <div class="input-container q-py-md">
           <div class="outsideLabel">title</div>
           <q-input v-model="localOptions.title"
-                   label="image" />
+                   label="title" />
+        </div>
+        <div class="input-container q-py-md">
+          <div class="outsideLabel">url</div>
+          <q-input v-model="localOptions.url"
+                   label="url" />
         </div>
         <div class="input-container q-py-md">
           <div class="outsideLabel">description</div>
-          <q-input v-model="localOptions.description"
-                   label="image" />
+          <editor v-model:value="localOptions.description" />
         </div>
       </div>
     </template>
@@ -23,12 +27,14 @@
 </template>
 <script>
 import { defineComponent } from 'vue'
+import Editor from 'src/components/Utils/Editor.vue'
 import { mixinOptionPanel } from 'quasar-ui-q-page-builder'
+import ImageUploader from 'src/components/ImageUploader.vue'
 import OptionPanelTabs from 'quasar-ui-q-page-builder/src/components/OptionPanelComponents/OptionPanelTabs.vue'
 
 export default defineComponent({
   name: 'OptionPanel',
-  components: { OptionPanelTabs },
+  components: { OptionPanelTabs, ImageUploader, Editor },
   mixins: [mixinOptionPanel],
   props: {
     options: {
@@ -38,6 +44,35 @@ export default defineComponent({
       }
     }
   },
+  data() {
+    return {
+      defaultOptions: {
+        title: null,
+        description: null,
+        image: null,
+        horizontal: true,
+        cssHoverEffects: {
+          boxShadows: [],
+          borderStyle: {
+            borderCssString: '',
+            borderRadiusCssString: ''
+          },
+          transition: {
+            time: 0
+          },
+          transform: {
+            rotate: 0,
+            scaleX: 1,
+            scaleY: 1,
+            skewX: 0,
+            skewY: 0,
+            translateX: 0,
+            translateY: 0
+          }
+        }
+      }
+    }
+  },
   watch: {
     localOptions: {
       handler(newVal) {

+ 6 - 6
src/components/Widgets/ImageWidget/ImageWidget.vue

@@ -202,12 +202,12 @@ export default {
       }
       if (this.windowWidth >= 1920) {
         return options.xl?.src ? options.xl.src : options.lg?.src ? options.lg.src : options.sm?.src ? options.md.src : options.sm?.src ? options.sm.src : options.xs?.src
-      } else if (this.windowWidth <= 1919 && this.windowWidth > 1440) {
-        return options.lg.src ? options.lg.src : options.md?.src ? options.md.src : options.sm?.src ? options.sm.src : options.xs?.src ? options.xs?.src : options.xl?.src
-      } else if (this.windowWidth <= 1439 && this.windowWidth > 1024) {
-        return options.md.src ? options.md.src : options.sm?.src ? options.sm.src : options.xs?.src ? options.xs.src : options.lg?.src ? options.lg.src : options.xl?.src
-      } else if (this.windowWidth <= 1023 && this.windowWidth > 600) {
-        return options.sm.src ? options.sm.src : options.xs?.src ? options.xs.src : options.md?.src ? options.md.src : options.lg?.src ? options.lg.src : options.xl?.src
+      } else if (this.windowWidth <= 1919 && this.windowWidth >= 1440) {
+        return options.lg?.src ? options.lg.src : options.md?.src ? options.md.src : options.sm?.src ? options.sm.src : options.xs?.src ? options.xs?.src : options.xl?.src
+      } else if (this.windowWidth <= 1439 && this.windowWidth >= 1024) {
+        return options.md?.src ? options.md.src : options.sm?.src ? options.sm.src : options.xs?.src ? options.xs.src : options.lg?.src ? options.lg.src : options.xl?.src
+      } else if (this.windowWidth <= 1023 && this.windowWidth >= 600) {
+        return options.sm?.src ? options.sm.src : options.xs?.src ? options.xs.src : options.md?.src ? options.md.src : options.lg?.src ? options.lg.src : options.xl?.src
       } else if (this.windowWidth <= 599) {
         return options.xs?.src ? options.xs.src : options.sm?.src ? options.sm.src : options.md?.src ? options.md.src : options.lg?.src ? options.lg.src : options.xl?.src
       } else {

+ 6 - 0
src/components/Widgets/ImageWidget/OptionPanel.vue

@@ -10,6 +10,12 @@
             <div class="outsideLabel">آدرس عکس</div>
             <image-uploader v-model:file="localOptions.imageSource" />
           </div>
+          <div class="full-width">
+            <q-btn class="full-width"
+                   @click="localOptions.imageSource = null">
+              حذف عکس یکتا
+            </q-btn>
+          </div>
           <!--          <div class="input-container q-py-md">-->
           <!--            <div class="outsideLabel">height</div>-->
           <!--            <q-input v-model="localOptions.height"-->

+ 8 - 1
src/components/Widgets/Invoice/AdminInvoiceList/AdminInvoiceList.vue

@@ -145,10 +145,17 @@ export default {
           {
             name: 'creation_time',
             required: true,
-            label: 'زمان',
+            label: 'زمان ثبت',
             align: 'left',
             field: row => Assist.miladiToShamsi(row.creation_time)
           },
+          {
+            name: 'creation_time',
+            required: true,
+            label: 'زمان آخرین به روزرسانی',
+            align: 'left',
+            field: row => Assist.miladiToShamsi(row.last_modification_time)
+          },
           {
             name: 'action',
             required: true,

+ 8 - 1
src/components/Widgets/Invoice/InvoiceList/InvoiceList.vue

@@ -131,10 +131,17 @@ export default {
           {
             name: 'creation_time',
             required: true,
-            label: 'زمان',
+            label: 'زمان ثبت',
             align: 'left',
             field: row => Assist.miladiToShamsi(row.creation_time)
           },
+          {
+            name: 'creation_time',
+            required: true,
+            label: 'زمان آخرین به روزرسانی',
+            align: 'left',
+            field: row => Assist.miladiToShamsi(row.last_modification_time)
+          },
           {
             name: 'action',
             required: true,

+ 3 - 2
src/components/Widgets/TextWidget/OptionPanel.vue

@@ -1,5 +1,6 @@
 <template>
-  <option-panel-tabs v-model:options="localOptions">
+  <option-panel-tabs v-model:options="localOptions"
+                     :show-border-style-tab="true">
     <template #main-tab>
       <div class="option-panel-container">
         <div class="row q-mt-sm">
@@ -16,7 +17,7 @@
 import { defineComponent } from 'vue'
 import { mixinOptionPanel } from 'quasar-ui-q-page-builder'
 import OptionPanelTabs from 'quasar-ui-q-page-builder/src/components/OptionPanelComponents/OptionPanelTabs.vue'
-import Editor from 'components/Utils/Editor.vue'
+import Editor from 'components/Utils/Editor2.vue'
 import EditorOptions from 'components/EditorOptions.vue'
 
 export default defineComponent({

+ 42 - 30
src/components/Widgets/TextWidget/TextWidget.vue

@@ -50,6 +50,10 @@ export default {
           fontWeight: null,
           fontStyle: null,
           lineHeight: null
+        },
+        borderStyle: {
+          borderCssString: '',
+          borderRadiusCssString: ''
         }
       }
     }
@@ -58,40 +62,48 @@ export default {
 </script>
 
 <style scoped lang="scss">
-.text {
-  line-height: v-bind('localOptions.xl.lineHeight');
-  color: v-bind('localOptions.color');
-  font-size: v-bind('localOptions.xl.fontSize');
-  font-weight: v-bind('localOptions.xl.fontWeight');
-  font-family: v-bind('localOptions.fontFamily');
-  font-style: v-bind('localOptions.xl.fontStyle');
+$border: v-bind('localOptions.borderStyle.borderCssString');
+$borderRadius: v-bind('localOptions.borderStyle.borderRadiusCssString');
 
-  @media screen and (max-width: 1920px) {
-    font-size: v-bind('localOptions.lg.fontSize');
-    font-weight: v-bind('localOptions.lg.fontWeight');
-    font-style: v-bind('localOptions.lg.fontStyle');
-    line-height: v-bind('localOptions.lg.lineHeight');
-  }
+.text-widget-container {
+  -webkit-border-radius: $borderRadius;
+  -moz-border-radius: $borderRadius;
+  border: $border;
+  .text {
+    line-height: v-bind('localOptions.xl.lineHeight');
+    color: v-bind('localOptions.color');
+    font-size: v-bind('localOptions.xl.fontSize');
+    font-weight: v-bind('localOptions.xl.fontWeight');
+    font-family: v-bind('localOptions.fontFamily');
+    font-style: v-bind('localOptions.xl.fontStyle');
 
-  @media screen and (max-width: 1440px) {
-    font-size: v-bind('localOptions.md.fontSize');
-    font-weight: v-bind('localOptions.md.fontWeight');
-    font-style: v-bind('localOptions.md.fontStyle');
-    line-height: v-bind('localOptions.md.lineHeight');
-  }
+    @media screen and (max-width: 1920px) {
+      font-size: v-bind('localOptions.lg.fontSize');
+      font-weight: v-bind('localOptions.lg.fontWeight');
+      font-style: v-bind('localOptions.lg.fontStyle');
+      line-height: v-bind('localOptions.lg.lineHeight');
+    }
 
-  @media screen and (max-width: 1024px) {
-    font-size: v-bind('localOptions.sm.fontSize');
-    font-weight: v-bind('localOptions.sm.fontWeight');
-    font-style: v-bind('localOptions.sm.fontStyle');
-    line-height: v-bind('localOptions.sm.lineHeight');
-  }
+    @media screen and (max-width: 1440px) {
+      font-size: v-bind('localOptions.md.fontSize');
+      font-weight: v-bind('localOptions.md.fontWeight');
+      font-style: v-bind('localOptions.md.fontStyle');
+      line-height: v-bind('localOptions.md.lineHeight');
+    }
+
+    @media screen and (max-width: 1024px) {
+      font-size: v-bind('localOptions.sm.fontSize');
+      font-weight: v-bind('localOptions.sm.fontWeight');
+      font-style: v-bind('localOptions.sm.fontStyle');
+      line-height: v-bind('localOptions.sm.lineHeight');
+    }
 
-  @media screen and (max-width: 600px) {
-    font-size: v-bind('localOptions.xs.fontSize');
-    font-weight: v-bind('localOptions.xs.fontWeight');
-    font-style: v-bind('localOptions.xs.fontStyle');
-    line-height: v-bind('localOptions.xs.lineHeight');
+    @media screen and (max-width: 600px) {
+      font-size: v-bind('localOptions.xs.fontSize');
+      font-weight: v-bind('localOptions.xs.fontWeight');
+      font-style: v-bind('localOptions.xs.fontStyle');
+      line-height: v-bind('localOptions.xs.lineHeight');
+    }
   }
 }
 </style>

+ 1 - 1
src/pages/User/Profile/sessionInfo.vue

@@ -173,7 +173,7 @@ export default {
         { type: 'separator', name: 'space', size: '1px', col: 'col-md-12' },
         { type: 'inputEditor', name: 'assignment_description', responseKey: 'assignment_description', label: 'تکلیف جلسه', col: 'col-md-12' },
         { type: ContentsSelectorComp, name: 'contents', responseKey: 'contents_info', showPageLink: false, showDownloadLink: true, col: 'col-12' },
-        { type: ContentMediasComp, name: 'medias', responseKey: 'medias_info', sourceType: 'SESSION', col: 'col-12' },
+        { type: ContentMediasComp, name: 'medias', responseKey: 'medias_info', showPageLink: false, showDownloadLink: true, canDelete: false, sourceType: 'SESSION', col: 'col-12' },
 
         { type: 'hidden', name: 'title', responseKey: 'title', label: 'نام جلسه', col: 'col-md-1' },
         { type: 'hidden', name: 'beginning_time', responseKey: 'beginning_time', label: 'زمان شروع جلسه', col: 'col-md-1' },

+ 19 - 4
src/router/routes/Public.js

@@ -175,12 +175,27 @@ export default {
     },
     {
       name: 'Public.Professor',
-      path: 'professor/:professor_name',
+      path: 'professor',
       meta: {
-        hasDynamicSetting: true,
-        hasDynamicSettingWithParams: true
+        hasDynamicSetting: true
       },
-      component: () => import('src/pages/Public/professorLanding.vue')
+      component: () => import('layouts/bareLayout.vue'),
+      children: [
+        {
+          path: '',
+          name: 'Public.Professor.List',
+          component: () => import('src/pages/BasicDynamicPage.vue')
+        },
+        {
+          path: ':professor_name',
+          name: 'Public.Professor.Show',
+          meta: {
+            hasDynamicSetting: true,
+            hasDynamicSettingWithParams: true
+          },
+          component: () => import('src/pages/Public/professorLanding.vue')
+        }
+      ]
     },
     {
       name: 'Public.Rules',