mango-cms 0.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/README.md +17 -0
  2. package/bin/mango +4 -0
  3. package/frontend-starter/README.md +8 -0
  4. package/frontend-starter/dist/_redirects +1 -0
  5. package/frontend-starter/dist/assets/index.00922bd5.js +99 -0
  6. package/frontend-starter/dist/assets/index.1781f175.css +1 -0
  7. package/frontend-starter/dist/favicon.png +0 -0
  8. package/frontend-starter/dist/index.html +53 -0
  9. package/frontend-starter/dist/index.js +66 -0
  10. package/frontend-starter/index.html +25 -0
  11. package/frontend-starter/index.js +197 -0
  12. package/frontend-starter/package-lock.json +5454 -0
  13. package/frontend-starter/package.json +40 -0
  14. package/frontend-starter/postcss.config.js +6 -0
  15. package/frontend-starter/public/_redirects +1 -0
  16. package/frontend-starter/public/favicon.png +0 -0
  17. package/frontend-starter/public/index.js +66 -0
  18. package/frontend-starter/src/App.vue +27 -0
  19. package/frontend-starter/src/components/layout/login.vue +212 -0
  20. package/frontend-starter/src/components/layout/modal.vue +113 -0
  21. package/frontend-starter/src/components/layout/spinner.vue +17 -0
  22. package/frontend-starter/src/components/pages/404.vue +28 -0
  23. package/frontend-starter/src/components/pages/home.vue +74 -0
  24. package/frontend-starter/src/components/partials/button.vue +31 -0
  25. package/frontend-starter/src/helpers/Mango.vue +455 -0
  26. package/frontend-starter/src/helpers/breakpoints.js +34 -0
  27. package/frontend-starter/src/helpers/darkMode.js +38 -0
  28. package/frontend-starter/src/helpers/email.js +32 -0
  29. package/frontend-starter/src/helpers/formatPhone.js +18 -0
  30. package/frontend-starter/src/helpers/localDB.js +315 -0
  31. package/frontend-starter/src/helpers/mango.js +338 -0
  32. package/frontend-starter/src/helpers/model.js +9 -0
  33. package/frontend-starter/src/helpers/multiSelect.vue +252 -0
  34. package/frontend-starter/src/helpers/pills.vue +75 -0
  35. package/frontend-starter/src/helpers/reconnecting-websocket.js +357 -0
  36. package/frontend-starter/src/helpers/uploadFile.vue +157 -0
  37. package/frontend-starter/src/helpers/uploadFiles.vue +100 -0
  38. package/frontend-starter/src/helpers/uploadImages.vue +89 -0
  39. package/frontend-starter/src/helpers/user.js +40 -0
  40. package/frontend-starter/src/index.css +281 -0
  41. package/frontend-starter/src/main.js +145 -0
  42. package/frontend-starter/tailwind.config.js +46 -0
  43. package/frontend-starter/vite.config.js +10 -0
  44. package/frontend-starter/yarn.lock +3380 -0
  45. package/mango-cms-0.0.13.tgz +0 -0
  46. package/package.json +24 -0
  47. package/src/cli.js +93 -0
  48. package/src/default-config/automation/index.js +37 -0
  49. package/src/default-config/collections/examples.js +60 -0
  50. package/src/default-config/config/.collections.json +1 -0
  51. package/src/default-config/config/globalFields.js +15 -0
  52. package/src/default-config/config/settings.json +23 -0
  53. package/src/default-config/config/statuses.js +0 -0
  54. package/src/default-config/config/users.js +35 -0
  55. package/src/default-config/endpoints/index.js +19 -0
  56. package/src/default-config/fields/vimeo.js +36 -0
  57. package/src/default-config/hooks/test.js +5 -0
  58. package/src/default-config/plugins/mango-stand/index.js +206 -0
  59. package/src/main.js +278 -0
@@ -0,0 +1,157 @@
1
+ <template>
2
+ <label :for="_uid" :uploading="uploading" :error="error" :url="url" :class="{'block relative border-2 border-transparent': showProgress}" ref="container">
3
+
4
+ <svg class="absolute -left-[2px] -top-[2px] z-50 w-[calc(100%+4px)] h-[calc(100%+4px)]" :style="progressRingStyle" v-if="progress">
5
+ <!-- <circle class="progress-ring__circle" stroke="green" stroke-width="4" fill="transparent" r="58" cx="60" cy="60"/> -->
6
+ <circle class="progress-ring__circle" stroke="#22c55e" :stroke-width="progressWidth" fill="transparent" :r="radius" :cx="containerSize/2" :cy="containerSize/2" :style="progressRingStyle"/>
7
+ </svg>
8
+
9
+ <button type="button" v-if="showDelete" @click.stop.prevent="url = null; $emit('url', null)" class="absolute -top-1 -right-1 w-[0.9rem] h-[0.9rem] rounded-full border-2 border-black/50 dark:bg-red-900 bg-red-500 flex items-center justify-center text-4xs font-bold z-50">
10
+ <span class=""><i class="fa fa-times opacity-50"/></span>
11
+ </button>
12
+
13
+ <slot :progress="progress" />
14
+
15
+ <input
16
+ :id="_uid"
17
+ hidden
18
+ type="file"
19
+ :accept="accept"
20
+ @change="upload"
21
+ />
22
+ </label>
23
+ </template>
24
+
25
+ <script>
26
+ import Compress from 'compress.js'
27
+ const compress = new Compress()
28
+ // import Modernizr from 'modernizr'
29
+
30
+ export default {
31
+ props: {
32
+ accept: {type: String, default: 'image/png, image/jpeg, image/heic'},
33
+ showProgress: {type: Boolean, default: true},
34
+ miultiple: {type: Boolean, default: true},
35
+ showDelete: {type: Boolean, default: true},
36
+ },
37
+ data() {
38
+ return {
39
+ _uid: Math.random().toString(36).substr(2, 10),
40
+ uploading: false,
41
+ error: false,
42
+ url: null,
43
+ progress: 0,
44
+ filename: null,
45
+ containerSize: 0,
46
+ progressWidth: 2,
47
+ }
48
+ },
49
+ watch: {
50
+ uploading() { this.$emit('uploading', this.uploading) },
51
+ error() { this.$emit('error', this.error) },
52
+ url() { this.$emit('url', this.url) },
53
+ filename() { this.$emit('filename', this.filename) },
54
+ // progress() { this.$emit('progress', this.progress) },
55
+ },
56
+ mounted() {
57
+ this.$nextTick(() => {
58
+ this.containerSize = this.$refs.container.offsetWidth;
59
+ })
60
+ },
61
+ computed: {
62
+ radius() {
63
+ return ((this.containerSize / 2)-(this.progressWidth/2)) || 0; // Subtract the stroke width
64
+ },
65
+ progressRingStyle() {
66
+ const circumference = 2 * Math.PI * this.radius;
67
+ const offset = circumference - (this.progress / 100) * circumference;
68
+ return {
69
+ strokeDasharray: `${circumference} ${circumference}`,
70
+ strokeDashoffset: offset,
71
+ };
72
+ },
73
+ },
74
+ methods: {
75
+ async upload(event) {
76
+
77
+ let file = event.target.files[0]
78
+ const formData = new FormData()
79
+
80
+ this.uploading = true
81
+ this.filename = file.name
82
+
83
+ // // Use Modernizr to detect if exit-orientation is supported.
84
+ // const browserSupportsExifOrientation = () => {
85
+ // return new Promise((resolve) => Modernizr.on("exiforientation", resolve));
86
+ // };
87
+
88
+ // // Only rotate if browser does not support exit orientation.
89
+ // const shouldRotate = async () => {
90
+ // const supported = await browserSupportsExifOrientation();
91
+ // return !supported;
92
+ // };
93
+
94
+ // const rotate = await shouldRotate();
95
+
96
+ // Compress the image
97
+ if (file.type.includes('image')) {
98
+ let results = await compress.compress([file], {
99
+ quality: .75, // the quality of the image, max is 1,
100
+ maxWidth: 1920, // the max width of the output image, defaults to 1920px
101
+ maxHeight: 1920, // the max height of the output image, defaults to 1920px
102
+ resize: true, // defaults to true, set false if you do not want to resize the image width and height
103
+ rotate: false, // See the rotation section below
104
+ })
105
+ const img1 = results[0]
106
+ const base64str = img1.data
107
+ const imgExt = img1.ext
108
+ const filename = file.name
109
+ file = Compress.convertBase64ToFile(base64str, imgExt)
110
+ file = new File([file], filename, { type: file.type });
111
+ console.log('file', file)
112
+ }
113
+
114
+ formData.append('file', file)
115
+
116
+ const xhr = new XMLHttpRequest()
117
+
118
+ xhr.open('POST', 'https://api.churchandfamilylife.com/upload', true)
119
+
120
+ xhr.upload.onprogress = (event) => {
121
+ if (event.lengthComputable) {
122
+ this.progress = (event.loaded / event.total) * 100
123
+ }
124
+ }
125
+
126
+ xhr.onload = () => {
127
+ if (xhr.status === 200) {
128
+ const json = JSON.parse(xhr.response)
129
+ const path = json.paths[0]
130
+ const url = `https://api.churchandfamilylife.com` + path
131
+ this.uploading = false
132
+ this.url = url
133
+ this.progress = 0
134
+ } else {
135
+ this.error = 'Error while uploading file'
136
+ this.uploading = false
137
+ }
138
+ }
139
+
140
+ xhr.onerror = () => {
141
+ this.error = 'Error while uploading file'
142
+ this.uploading = false
143
+ }
144
+
145
+ xhr.send(formData)
146
+ },
147
+ }
148
+ }
149
+ </script>
150
+
151
+ <style scoped>
152
+ .progress-ring__circle {
153
+ transition: stroke-dashoffset 0.35s;
154
+ transform: rotate(-90deg);
155
+ transform-origin: 50% 50%;
156
+ }
157
+ </style>
@@ -0,0 +1,100 @@
1
+ <template>
2
+
3
+ <div class="p-4 rounded-lg bg-gray-100 dark:bg-gray-800/50 w-full space-y-4">
4
+ <div class="font-semibold uppercase tracking-widest text-gray-500/50 text-sm">Documents</div>
5
+ <div class="w-full grid grid-cols-4 gap-2">
6
+
7
+ <Draggable v-model="sortableDocuments" v-if="sortableDocuments.length" item-key="id" class="w-full grid grid-cols-4 gap-2 col-span-4" >
8
+ <template #item="{element:document}">
9
+ <div class="bg-center bg-cover w-full aspect-square shrink-0 overflow-hidden rounded relative group border-4">
10
+ <div class="absolute inset-0 flex flex-col space-y-4 items-center justify-center">
11
+ <i class="fa fa-file-alt text-xl sm:text-3xl" />
12
+ <div class="text-xs md:text-sm lg:text-base">{{ document.name }}</div>
13
+ </div>
14
+ <div class="bg-black/50 absolute h-full top-0 right-0" :style="`width: ${100-document?.progress}%;`" />
15
+ <button @click="removeDocument(document)" class="w-full h-full hidden group-hover:flex absolute backdrop-blur items-center justify-center">
16
+ <i class="fa fa-trash-alt text-3xl text-red-800" />
17
+ </button>
18
+ </div>
19
+ </template>
20
+ </Draggable>
21
+
22
+ <FileUpload
23
+ ref="upload"
24
+ v-model="documents"
25
+ key="documents"
26
+ input-id="documents"
27
+ :post-action="`${api}/upload`"
28
+ :drop="true"
29
+ :multiple="true"
30
+ :drop-directory="true"
31
+ class="w-full aspect-square border-2 dark:border-gray-600 bg-gray-300 dark:bg-gray-800 block cursor-pointer relative rounded"
32
+ >
33
+ <div class="w-full h-full flex items-center justify-center">
34
+ <i class="fad fa-file-alt text-3xl dark:text-gray-700" />
35
+ </div>
36
+ <div class="w-full h-full hidden group-hover:flex absolute backdrop-blur items-center justify-center">
37
+ <i class="fa fa-pencil text-3xl text-gray-800" />
38
+ </div>
39
+ </FileUpload>
40
+
41
+ </div>
42
+ </div>
43
+
44
+ </template>
45
+
46
+ <script>
47
+ import FileUpload from 'vue-upload-component'
48
+ import Draggable from 'vuedraggable'
49
+ import { useModel } from './model'
50
+
51
+ export default {
52
+ components: {FileUpload, Draggable},
53
+ inject: ['axios','api'],
54
+ props: ['modelValue'],
55
+ data() {
56
+ return {
57
+ // documents: [],
58
+ sortableDocuments: [],
59
+ }
60
+ },
61
+ setup(props, { emit }) {
62
+ return {
63
+ documents: useModel(props, emit, 'modelValue')
64
+ }
65
+ },
66
+ watch: {
67
+ documents: {
68
+ handler() {
69
+ this.sortableDocuments = this.documents?.map(i => ({id:i?.id, url:i?.url, name: i.name})) || []
70
+
71
+ let documentsToUpload = this.documents?.filter(i => i.file) || []
72
+ if (this.$refs.upload) this.$refs.upload.active = true
73
+ documentsToUpload.forEach(i => i.blob = i.blob ? i.blob : URL.createObjectURL(i.file))
74
+ },
75
+ deep: true,
76
+ immediate: true
77
+ },
78
+ urls() {
79
+ this.$emit('urls', this.urls)
80
+ }
81
+ },
82
+ computed: {
83
+ done() {
84
+ return this.documents.every(i => i.progress == 100)
85
+ },
86
+ urls() {
87
+ return this.sortableDocuments.map(document => document.url ? document : this.documents?.find(i => i.id == document.id)?.response?.paths?.[0])
88
+ }
89
+ },
90
+ methods: {
91
+ removeDocument(document) {
92
+ let id = document?.id
93
+ let index = id ? this.documents?.findIndex(i => i.id == id) : this.documents?.findIndex(i => i.url == document.url)
94
+ console.log('index', index)
95
+ if (isNaN(index)) return
96
+ this.documents.splice(index,1)
97
+ }
98
+ }
99
+ }
100
+ </script>
@@ -0,0 +1,89 @@
1
+ <template>
2
+
3
+ <div class="p-4 rounded-lg bg-gray-100 dark:bg-gray-800/50 w-full space-y-4">
4
+ <div class="font-semibold uppercase tracking-widest text-gray-500/50 text-sm">Images</div>
5
+ <div class="w-full grid grid-cols-4 gap-2">
6
+
7
+ <Draggable v-model="sortableImages" v-if="sortableImages.length" item-key="id" class="w-full grid grid-cols-4 gap-2 col-span-4" >
8
+ <template #item="{element:id}">
9
+ <div class="bg-center bg-cover w-full aspect-square shrink-0 overflow-hidden rounded relative group" :style="`background-image: url(${images.find(i => i.id == id)?.blob || images.find(i => i.id == id)?.url || `https://api.churchandfamilylife.com/${images.find(i => i.id == id)?.response?.paths?.[0]}`})`">
10
+ <div class="bg-black/50 absolute h-full top-0 right-0" :style="`width: ${100-images.find(i => i.id == id)?.progress}%;`" />
11
+ <button @click="removeImage(id)" class="w-full h-full hidden group-hover:flex absolute backdrop-blur items-center justify-center">
12
+ <i class="fa fa-trash-alt text-3xl text-red-800" />
13
+ </button>
14
+ </div>
15
+ </template>
16
+ </Draggable>
17
+
18
+ <FileUpload
19
+ ref="upload"
20
+ v-model="images"
21
+ post-action="https://api.churchandfamilylife.com/upload"
22
+ :drop="true"
23
+ :multiple="true"
24
+ :drop-directory="true"
25
+ :extensions="['png','jpg','jpeg','heic']"
26
+ class="w-full aspect-square border-2 dark:border-gray-600 bg-gray-300 dark:bg-gray-800 block cursor-pointer relative rounded"
27
+ >
28
+ <div class="w-full h-full flex items-center justify-center">
29
+ <i class="fa fa-image text-3xl dark:text-gray-700" />
30
+ </div>
31
+ <div class="w-full h-full hidden group-hover:flex absolute backdrop-blur items-center justify-center">
32
+ <i class="fa fa-pencil text-3xl text-gray-800" />
33
+ </div>
34
+ </FileUpload>
35
+
36
+ </div>
37
+ </div>
38
+
39
+ </template>
40
+
41
+ <script>
42
+ import FileUpload from 'vue-upload-component'
43
+ import Draggable from 'vuedraggable'
44
+
45
+ export default {
46
+ components: {FileUpload, Draggable},
47
+ inject: ['axios'],
48
+ data() {
49
+ return {
50
+ images: [],
51
+ sortableImages: [],
52
+ }
53
+ },
54
+ watch: {
55
+ images: {
56
+ handler() {
57
+ // this.updateImages()
58
+ console.log('image watcher')
59
+ console.log('this', this.$refs.upload)
60
+ this.$refs.upload.active = true
61
+ this.sortableImages = this.images.map(i => i.id)
62
+ this.images.forEach(i => i.blob = i.blob ? i.blob : URL.createObjectURL(i.file))
63
+ },
64
+ deep: true
65
+ },
66
+ urls() {
67
+ this.$emit('urls', this.urls)
68
+ },
69
+
70
+ },
71
+ computed: {
72
+ done() {
73
+ return this.images.every(i => i.progress == 100)
74
+ },
75
+ urls() {
76
+ return this.sortableImages.map(id => this.images?.find(i => i.id == id)?.response?.paths?.[0])
77
+ }
78
+ },
79
+ methods: {
80
+ removeImage(id) {
81
+ console.log('removing')
82
+ let index = this.images?.findIndex(i => i.id == id)
83
+ console.log('index', index)
84
+ if (isNaN(index)) return
85
+ this.images.splice(index,1)
86
+ }
87
+ }
88
+ }
89
+ </script>
@@ -0,0 +1,40 @@
1
+ import { ref } from 'vue'
2
+ import Mango from './mango'
3
+
4
+ const getUser = async (user) => {
5
+
6
+ user = user || { value: {} }
7
+
8
+ if (window.user?.id) {
9
+
10
+ console.log('got it!')
11
+ user.value = window.user
12
+
13
+ /*
14
+ Gotta clear this out so next time
15
+ we try to call getUser we don't get
16
+ stale data. ;)
17
+ */
18
+ window.user = null
19
+ return user.value
20
+ }
21
+
22
+ let userId = window.localStorage.getItem('user')
23
+
24
+ if (!userId) return {}
25
+
26
+ user.value = await Mango.member(userId, { depthLimit: 0 }).then(r => user.value = r)
27
+
28
+ return user.value
29
+
30
+ }
31
+
32
+ const initUser = () => {
33
+
34
+ const user = ref({})
35
+ getUser(user)
36
+ return user
37
+
38
+ }
39
+
40
+ export { initUser, getUser }
@@ -0,0 +1,281 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ .article a {
6
+ @apply font-semibold cursor-pointer
7
+ }
8
+
9
+ .article a:hover {
10
+ @apply underline
11
+ }
12
+
13
+ .article p,
14
+ .article div,
15
+ .article ul,
16
+ .article ol,
17
+ .article h2,
18
+ .article h3,
19
+ .article h4,
20
+ .article blockquote {
21
+ @apply mt-4 text-sm leading-relaxed break-words
22
+ }
23
+
24
+ .article li {
25
+ @apply mt-2
26
+ }
27
+
28
+ .article h2 {
29
+ @apply text-base font-semibold
30
+ }
31
+
32
+ .article > :first-child,
33
+ .article > :first-child > :first-child,
34
+ .article > :first-child > :first-child > :first-child {
35
+ margin-top: 0px!important;
36
+ }
37
+
38
+ .article > .ql-container > .ql-editor > :first-child,
39
+ .article > .ql-container > .ql-editor > :first-child > :first-child,
40
+ .article > .ql-container > .ql-editor > :first-child > :first-child > :first-child {
41
+ margin-top: 0px!important;
42
+ }
43
+ /*.article > :first-child > :first-of-type {
44
+ margin-top: 0px
45
+ }*/
46
+
47
+ /*.ql-editor > :first-child {
48
+ margin-top: 0px
49
+ }*/
50
+
51
+ .article blockquote {
52
+ @apply
53
+ px-8
54
+ italic
55
+ text-gray-600
56
+ }
57
+
58
+ @screen md {
59
+ .article p,
60
+ .article div,
61
+ .article ul,
62
+ .article ol,
63
+ .article h2,
64
+ .article h3,
65
+ .article h4,
66
+ .article blockquote {
67
+ @apply mt-8 text-base leading-relaxed
68
+ }
69
+
70
+ .article li {
71
+ @apply mt-4
72
+ }
73
+
74
+ .article h2 {
75
+ @apply text-2xl font-semibold
76
+ }
77
+ }
78
+
79
+
80
+ .space-1 {
81
+ @apply p-4
82
+ }
83
+
84
+ @screen md {
85
+ .space-1 {
86
+ @apply p-6
87
+ }
88
+ }
89
+
90
+ @screen xl {
91
+ .space-1 {
92
+ @apply p-8
93
+ }
94
+ }
95
+
96
+
97
+
98
+ .book-article a {
99
+ @apply font-semibold cursor-pointer
100
+ }
101
+
102
+ .book-article a:hover {
103
+ @apply underline
104
+ }
105
+
106
+ @screen md {
107
+
108
+ .book-article p,
109
+ .book-article div,
110
+ .book-article ul,
111
+ .book-article ol,
112
+ .book-article h2,
113
+ .book-article h3,
114
+ .book-article h4,
115
+ .book-article blockquote {
116
+ @apply mt-16
117
+ }
118
+
119
+ .book-article li {
120
+ @apply mt-8
121
+ }
122
+
123
+ .book-article h2 {
124
+ @apply text-5xl font-semibold
125
+ }
126
+ }
127
+
128
+ .book-article p,
129
+ .book-article div,
130
+ .book-article ul,
131
+ .book-article ol,
132
+ .book-article h2,
133
+ .book-article h3,
134
+ .book-article h4,
135
+ .book-article blockquote {
136
+ @apply mt-8
137
+ }
138
+
139
+ .book-article ul {
140
+ @apply list-disc
141
+ }
142
+
143
+ .book-article li {
144
+ @apply mt-4
145
+ }
146
+
147
+ .book-article h2 {
148
+ @apply text-2xl font-bold
149
+ }
150
+
151
+ .book-article > :first-child,
152
+ .book-article > :first-child > :first-child,
153
+ .book-article > :first-child > :first-child > :first-child {
154
+ margin-top: 0px!important;
155
+ }
156
+
157
+ .book-article > .ql-container > .ql-editor > :first-child,
158
+ .book-article > .ql-container > .ql-editor > :first-child > :first-child,
159
+ .book-article > .ql-container > .ql-editor > :first-child > :first-child > :first-child {
160
+ margin-top: 0px!important;
161
+ }
162
+ /*.book-article > :first-child > :first-of-type {
163
+ margin-top: 0px
164
+ }*/
165
+
166
+ /*.ql-editor > :first-child {
167
+ margin-top: 0px
168
+ }*/
169
+
170
+ .book-article blockquote {
171
+ @apply
172
+ px-8
173
+ italic
174
+ text-gray-500
175
+ }
176
+
177
+
178
+ .ql-clipboard,
179
+ .ql-tooltip {
180
+ display: none!important;
181
+ }
182
+
183
+ .ql-editor {
184
+ outline: none!important;
185
+ }
186
+
187
+ table.schedule {
188
+ @apply
189
+ my-4
190
+ w-full
191
+ text-xs
192
+ border-y
193
+ table-auto
194
+ border-collapse
195
+ dark:text-gray-500
196
+ dark:border-gray-800
197
+ }
198
+
199
+ table.schedule tr:nth-child(odd) {
200
+ @apply bg-gray-100 dark:bg-gray-800
201
+ }
202
+
203
+ table.schedule td {
204
+ @apply m-0 p-2
205
+ }
206
+
207
+
208
+ @screen md {
209
+ table.schedule {
210
+ @apply
211
+ my-8
212
+ text-sm
213
+ }
214
+
215
+ table.schedule td {
216
+ @apply m-0 p-4
217
+ }
218
+ }
219
+
220
+
221
+ .article-xs p,
222
+ .article-xs div,
223
+ .article-xs ul,
224
+ .article-xs ol,
225
+ .article-xs h2,
226
+ .article-xs h3,
227
+ .article-xs h4,
228
+ .article-xs blockquote {
229
+ @apply mt-2 text-xs leading-relaxed
230
+ }
231
+
232
+ .article-xs li {
233
+ @apply mt-1
234
+ }
235
+
236
+ .article-xs h2 {
237
+ @apply text-sm
238
+ }
239
+
240
+ .article-sm p,
241
+ .article-sm div,
242
+ .article-sm ul,
243
+ .article-sm ol,
244
+ .article-sm h2,
245
+ .article-sm h3,
246
+ .article-sm h4,
247
+ .article-sm blockquote {
248
+ @apply mt-4 text-sm leading-relaxed
249
+ }
250
+
251
+ .article-sm li {
252
+ @apply mt-2
253
+ }
254
+
255
+ .article-sm h2 {
256
+ @apply text-base
257
+ }
258
+
259
+ .article-md p,
260
+ .article-md div,
261
+ .article-md ul,
262
+ .article-md ol,
263
+ .article-md h2,
264
+ .article-md h3,
265
+ .article-md h4,
266
+ .article-md blockquote {
267
+ @apply mt-4 text-base leading-relaxed
268
+ }
269
+
270
+ .article-md li {
271
+ @apply mt-2
272
+ }
273
+
274
+ .article-md h2 {
275
+ @apply text-2xl
276
+ }
277
+
278
+ .video-container,
279
+ .video-container iframe {
280
+ @apply aspect-video w-full !h-auto
281
+ }