Browse Source

read more

tum 2 years ago
parent
commit
c159e1b993
5 changed files with 136 additions and 13 deletions
  1. 47 0
      package-lock.json
  2. 4 0
      package.json
  3. 45 9
      src/components/CourseMat.vue
  4. 13 2
      src/composable/settings.ts
  5. 27 2
      src/views/CourseDetailPage.vue

+ 47 - 0
package-lock.json

26
         "axios": "^1.6.0",
26
         "axios": "^1.6.0",
27
         "axios-cache-interceptor": "^1.3.2",
27
         "axios-cache-interceptor": "^1.3.2",
28
         "axios-extensions": "^3.1.6",
28
         "axios-extensions": "^3.1.6",
29
+        "human-number": "^2.0.4",
30
+        "humanize-plus": "^1.8.2",
29
         "ionicons": "^7.0.0",
31
         "ionicons": "^7.0.0",
30
         "marked": "^10.0.0",
32
         "marked": "^10.0.0",
31
         "moment": "^2.29.4",
33
         "moment": "^2.29.4",
34
+        "pluralize": "^8.0.0",
32
         "swiper": "^11.0.3",
35
         "swiper": "^11.0.3",
33
         "vue": "^3.2.45",
36
         "vue": "^3.2.45",
34
         "vue-core-video-player": "^0.2.0",
37
         "vue-core-video-player": "^0.2.0",
38
+        "vue-read-more": "^1.1.1",
35
         "vue-router": "^4.1.6",
39
         "vue-router": "^4.1.6",
36
         "vue-vimeo-player": "^1.1.2"
40
         "vue-vimeo-player": "^1.1.2"
37
       },
41
       },
6373
         "node": ">= 6"
6377
         "node": ">= 6"
6374
       }
6378
       }
6375
     },
6379
     },
6380
+    "node_modules/human-number": {
6381
+      "version": "2.0.4",
6382
+      "resolved": "https://registry.npmjs.org/human-number/-/human-number-2.0.4.tgz",
6383
+      "integrity": "sha512-OENvA941poJU1VGR6s5Nf/GpYNPE+81lmHkIVLO9FgiyHxB+BSlVOJV3lnItk5tfHzcEbZv3kTQrzpZK0+ExRA==",
6384
+      "dependencies": {
6385
+        "round-to": "~5.0.0"
6386
+      },
6387
+      "engines": {
6388
+        "node": ">= 8"
6389
+      }
6390
+    },
6376
     "node_modules/human-signals": {
6391
     "node_modules/human-signals": {
6377
       "version": "1.1.1",
6392
       "version": "1.1.1",
6378
       "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz",
6393
       "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz",
6382
         "node": ">=8.12.0"
6397
         "node": ">=8.12.0"
6383
       }
6398
       }
6384
     },
6399
     },
6400
+    "node_modules/humanize-plus": {
6401
+      "version": "1.8.2",
6402
+      "resolved": "https://registry.npmjs.org/humanize-plus/-/humanize-plus-1.8.2.tgz",
6403
+      "integrity": "sha512-jaLeQyyzjjINGv7O9JJegjsaUcWjSj/1dcXvLEgU3pGdqCdP1PiC/uwr+saJXhTNBHZtmKnmpXyazgh+eceRxA==",
6404
+      "engines": {
6405
+        "node": ">= 0.8.0"
6406
+      }
6407
+    },
6385
     "node_modules/iconv-lite": {
6408
     "node_modules/iconv-lite": {
6386
       "version": "0.6.3",
6409
       "version": "0.6.3",
6387
       "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
6410
       "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
8004
         "node": ">=10.4.0"
8027
         "node": ">=10.4.0"
8005
       }
8028
       }
8006
     },
8029
     },
8030
+    "node_modules/pluralize": {
8031
+      "version": "8.0.0",
8032
+      "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz",
8033
+      "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==",
8034
+      "engines": {
8035
+        "node": ">=4"
8036
+      }
8037
+    },
8007
     "node_modules/postcss": {
8038
     "node_modules/postcss": {
8008
       "version": "8.4.31",
8039
       "version": "8.4.31",
8009
       "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
8040
       "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
8420
         "fsevents": "~2.3.2"
8451
         "fsevents": "~2.3.2"
8421
       }
8452
       }
8422
     },
8453
     },
8454
+    "node_modules/round-to": {
8455
+      "version": "5.0.0",
8456
+      "resolved": "https://registry.npmjs.org/round-to/-/round-to-5.0.0.tgz",
8457
+      "integrity": "sha512-i4+Ntwmo5kY7UWWFSDEVN3RjT2PX1FqkZ9iCcAO3sKML3Ady9NgsjM/HLdYKUAnrxK4IlSvXzpBMDvMHZQALRQ==",
8458
+      "engines": {
8459
+        "node": ">=10"
8460
+      },
8461
+      "funding": {
8462
+        "url": "https://github.com/sponsors/sindresorhus"
8463
+      }
8464
+    },
8423
     "node_modules/rrweb-cssom": {
8465
     "node_modules/rrweb-cssom": {
8424
       "version": "0.6.0",
8466
       "version": "0.6.0",
8425
       "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz",
8467
       "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz",
9737
       "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
9779
       "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
9738
       "dev": true
9780
       "dev": true
9739
     },
9781
     },
9782
+    "node_modules/vue-read-more": {
9783
+      "version": "1.1.1",
9784
+      "resolved": "https://registry.npmjs.org/vue-read-more/-/vue-read-more-1.1.1.tgz",
9785
+      "integrity": "sha512-FFv0y5Pg1763fsLo6MA5RSpibThNEoJG6teSRH+rRIQe8O0LAeU4juEn6MRHZN0qOwtXOlbOjO8oit7YSd1NcA=="
9786
+    },
9740
     "node_modules/vue-router": {
9787
     "node_modules/vue-router": {
9741
       "version": "4.2.5",
9788
       "version": "4.2.5",
9742
       "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.2.5.tgz",
9789
       "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.2.5.tgz",

+ 4 - 0
package.json

30
     "axios": "^1.6.0",
30
     "axios": "^1.6.0",
31
     "axios-cache-interceptor": "^1.3.2",
31
     "axios-cache-interceptor": "^1.3.2",
32
     "axios-extensions": "^3.1.6",
32
     "axios-extensions": "^3.1.6",
33
+    "human-number": "^2.0.4",
34
+    "humanize-plus": "^1.8.2",
33
     "ionicons": "^7.0.0",
35
     "ionicons": "^7.0.0",
34
     "marked": "^10.0.0",
36
     "marked": "^10.0.0",
35
     "moment": "^2.29.4",
37
     "moment": "^2.29.4",
38
+    "pluralize": "^8.0.0",
36
     "swiper": "^11.0.3",
39
     "swiper": "^11.0.3",
37
     "vue": "^3.2.45",
40
     "vue": "^3.2.45",
38
     "vue-core-video-player": "^0.2.0",
41
     "vue-core-video-player": "^0.2.0",
42
+    "vue-read-more": "^1.1.1",
39
     "vue-router": "^4.1.6",
43
     "vue-router": "^4.1.6",
40
     "vue-vimeo-player": "^1.1.2"
44
     "vue-vimeo-player": "^1.1.2"
41
   },
45
   },

+ 45 - 9
src/components/CourseMat.vue

1
 <template>
1
 <template>
2
-  <ion-card :router-link="'/tabs/mat_detail/'+obj.id">
3
-    <ion-img alt="Silhouette of mountains" :src="thmb"  v-if="thmb" class="img_16_9"/>
4
-    <ion-card-header>
2
+  <ion-card >
3
+    <ion-img  :router-link="'/tabs/mat_detail/'+obj.id" alt="Silhouette of mountains" :src="thmb"  v-if="thmb" class="img_16_9"/>
4
+    <ion-card-header :router-link="'/tabs/mat_detail/'+obj.id">
5
       <ion-card-title>{{ name }}</ion-card-title>
5
       <ion-card-title>{{ name }}</ion-card-title>
6
     </ion-card-header>
6
     </ion-card-header>
7
 
7
 
8
-    <ion-card-content class='ion-text-wrap'>
8
+    <ion-card-content class='ion-text-wrap ion-no-padding ion-padding-horizontal' :router-link="'/tabs/mat_detail/'+obj.id">
9
       {{ dt }}
9
       {{ dt }}
10
     </ion-card-content>
10
     </ion-card-content>
11
+    <ion-button fill="clear" @click="likeMatClick"><ion-icon :icon="heart" :color="isLike ? 'danger' : 'medium'  "></ion-icon> 
12
+      <template v-if="obj.total_likes > 0">
13
+        &nbsp;&nbsp;
14
+      {{  Humanize.compactInteger(obj.total_likes, 1) }} {{ pluralize('Like', obj.total_likes) }}
15
+      </template>
16
+    </ion-button>
11
   </ion-card>
17
   </ion-card>
12
 </template>
18
 </template>
13
 
19
 
14
 <script setup lang="ts">
20
 <script setup lang="ts">
15
-  import { IonPage, IonHeader, IonToolbar, IonTitle, IonContent, IonIcon, onIonViewWillEnter, IonCard, IonCardHeader, IonCardTitle, IonCardSubtitle, IonCardContent, IonImg } from '@ionic/vue';
21
+  import { IonPage, IonHeader, IonToolbar, IonTitle, IonContent, IonIcon, onIonViewWillEnter, IonCard, IonCardHeader, IonCardTitle, IonCardSubtitle, IonCardContent, IonImg, IonButton } from '@ionic/vue';
16
   import moment from 'moment'
22
   import moment from 'moment'
17
-import { getTrainers, listCourses } from '@/composable/settings';
23
+  import { getTrainers, listCourses, isLogin, getPref, likeMat } from '@/composable/settings';
18
   import { ref, onMounted  } from 'vue'
24
   import { ref, onMounted  } from 'vue'
25
+  import { heart } from 'ionicons/icons';
26
+  import pluralize from 'pluralize';
27
+  import Humanize from 'humanize-plus/dist/humanize';
19
 
28
 
20
   const props = defineProps({
29
   const props = defineProps({
21
     obj: Object,
30
     obj: Object,
25
   const dt = ref()
34
   const dt = ref()
26
   const thmb = ref()
35
   const thmb = ref()
27
   const name = ref()
36
   const name = ref()
28
-
29
-  onMounted(() => {
37
+  let pref = null
38
+  const isLike = ref(false)
39
+  onMounted(async() => {
30
     console.log("--- onmounted ---")
40
     console.log("--- onmounted ---")
31
     console.log("name === ", props.obj)
41
     console.log("name === ", props.obj)
42
+
32
     const obj = props.obj
43
     const obj = props.obj
44
+    if(await isLogin()) {
45
+      pref = await getPref()
46
+      console.debug(" pref = ", pref)
47
+      for(const m of pref.mat_likes) {
48
+        if(obj.id == m.id) {
49
+          isLike.value = true
50
+          break
51
+        }
52
+      }
53
+    }
33
     let temp  = obj.display_datetime || obj.vimeo_created || obj.created_at 
54
     let temp  = obj.display_datetime || obj.vimeo_created || obj.created_at 
34
     dt.value = moment(temp).format('LLL'); 
55
     dt.value = moment(temp).format('LLL'); 
35
 
56
 
45
     }
66
     }
46
     name.value = obj.vimeo_name || props.courseName
67
     name.value = obj.vimeo_name || props.courseName
47
   })
68
   })
48
-  
69
+  const likeMatClick = async() => {
70
+    console.debug(props.obj)
71
+    pref = await likeMat(props.obj.id)
72
+    console.debug(pref)
73
+    isLike.value = false
74
+    for(const m of pref.mat_likes) {
75
+      if(props.obj.id == m.id) {
76
+        isLike.value = true
77
+        props.obj.total_likes += 1
78
+        break
79
+      }
80
+    }
81
+    if(isLike.value == false) {
82
+      props.obj.total_likes -= 1
83
+    }
84
+  } 
49
 </script>
85
 </script>
50
 
86
 
51
 <style scoped>
87
 <style scoped>

+ 13 - 2
src/composable/settings.ts

9
 
9
 
10
 export const TOKEN = '173cb9e357a861abd91e8008fab9246e0cc116af'
10
 export const TOKEN = '173cb9e357a861abd91e8008fab9246e0cc116af'
11
 //export const BASE_URL = 'http://192.168.1.35:8020/'
11
 //export const BASE_URL = 'http://192.168.1.35:8020/'
12
-export const BASE_URL = 'http://localhost:8020/'
13
-//export const BASE_URL = 'https://www.tigermuaythai.live/'
12
+//export const BASE_URL = 'http://localhost:8020/'
13
+export const BASE_URL = 'https://www.tigermuaythai.live/'
14
 
14
 
15
 //const axios = setupCache(Axios); 
15
 //const axios = setupCache(Axios); 
16
 //
16
 //
378
   console.info(data)
378
   console.info(data)
379
   return data
379
   return data
380
 }
380
 }
381
+export const likeMat = async (cid) => {
382
+  const token = await Preferences.get({ key: 'token' });
383
+  console.log("token = ", token)
384
+  const { data } = await axios.get(BASE_URL + `backend/api/profiles/like_mat/?cid=${cid}`, {
385
+    headers: { 
386
+    'Authorization': `Token ${token.value}`
387
+    }
388
+  })
389
+  console.info(data)
390
+  return data
391
+}

+ 27 - 2
src/views/CourseDetailPage.vue

15
           <ion-title size="large" v-if="course">{{ course.name }}</ion-title>
15
           <ion-title size="large" v-if="course">{{ course.name }}</ion-title>
16
         </ion-toolbar>
16
         </ion-toolbar>
17
       </ion-header>
17
       </ion-header>
18
+        <ion-img :src="course.feature_img" v-if="course" />
18
         <template v-if="processing">
19
         <template v-if="processing">
19
             <ion-progress-bar type="indeterminate" class='ion-margin-vertical'></ion-progress-bar>
20
             <ion-progress-bar type="indeterminate" class='ion-margin-vertical'></ion-progress-bar>
20
         </template>
21
         </template>
22
+        <read-more class='ion-padding' v-if="render_body" more-str="read more" :text="render_body" link="#" less-str="read less" max-chars="200"></read-more>
23
+
24
+      <h1 class='ion-padding'>Trainings</h1>
21
       <ion-grid>
25
       <ion-grid>
22
         <ion-row>
26
         <ion-row>
23
           <ion-col size="12" size-md="4" size-lg="2" v-for="c in mats">
27
           <ion-col size="12" size-md="4" size-lg="2" v-for="c in mats">
31
     </ion-content>
35
     </ion-content>
32
   </ion-page>
36
   </ion-page>
33
 </template>
37
 </template>
34
-
35
 <script setup lang="ts">
38
 <script setup lang="ts">
36
 import { IonPage, IonHeader, IonToolbar, IonTitle, IonContent,   IonNavLink,
39
 import { IonPage, IonHeader, IonToolbar, IonTitle, IonContent,   IonNavLink,
37
     IonButton,
40
     IonButton,
38
     IonButtons,
41
     IonButtons,
39
     IonBackButton, onIonViewWillEnter, IonRow, IonGrid, IonCol, IonProgressBar,  IonInfiniteScroll,
42
     IonBackButton, onIonViewWillEnter, IonRow, IonGrid, IonCol, IonProgressBar,  IonInfiniteScroll,
40
-    IonInfiniteScrollContent, InfiniteScrollCustomEvent  } from '@ionic/vue';
43
+    IonInfiniteScrollContent, InfiniteScrollCustomEvent, IonImg  } from '@ionic/vue';
41
 import { useRoute } from 'vue-router';
44
 import { useRoute } from 'vue-router';
42
 import { listCourseMats, getCourse, listCourseMatsByLevel, callUrl} from '@/composable/settings';
45
 import { listCourseMats, getCourse, listCourseMatsByLevel, callUrl} from '@/composable/settings';
43
 import { ref } from 'vue';
46
 import { ref } from 'vue';
44
 import CourseMat from '@/components/CourseMat.vue'
47
 import CourseMat from '@/components/CourseMat.vue'
48
+import { marked } from 'marked';
49
+import ReadMore from 'vue-read-more/components/ReadMoreComponent.vue';
45
 
50
 
46
 const route = useRoute();
51
 const route = useRoute();
47
 const { id } = route.params;
52
 const { id } = route.params;
50
 const course = ref()
55
 const course = ref()
51
 let next_url = null
56
 let next_url = null
52
 const processing = ref(false)
57
 const processing = ref(false)
58
+const render_body = ref()
59
+
53
 onIonViewWillEnter(async () => {
60
 onIonViewWillEnter(async () => {
54
   course.value = await getCourse(id)
61
   course.value = await getCourse(id)
62
+  render_body.value = marked(course.value.body)
55
   processing.value = true
63
   processing.value = true
56
   listCourseMatsByLevel(course.value.clevels).then( (data) => { 
64
   listCourseMatsByLevel(course.value.clevels).then( (data) => { 
57
     mats.value = data.results;
65
     mats.value = data.results;
73
     }
81
     }
74
   };
82
   };
75
 </script>
83
 </script>
84
+
85
+<style>
86
+ion-title {
87
+  font-size:1.5rem;
88
+}
89
+#readmore {
90
+  display:block;
91
+  width:100%;
92
+  text-align:center;
93
+  text-transform:capitalize;
94
+  text-decoration:none;
95
+  font-weight:bold;
96
+  background-color:#ccc;
97
+  padding:0.5rem 0;
98
+  color:#333;
99
+}
100
+</style>