@slidev/client 0.48.0-beta.16 → 0.48.0-beta.18

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.
package/App.vue CHANGED
@@ -1,7 +1,14 @@
1
1
  <script setup lang="ts">
2
+ import { watchEffect } from 'vue'
3
+ import { themeVars } from './env'
2
4
  import setupRoot from './setup/root'
3
5
 
4
6
  setupRoot()
7
+
8
+ watchEffect(() => {
9
+ for (const [key, value] of Object.entries(themeVars.value))
10
+ document.body.style.setProperty(key, value.toString())
11
+ })
5
12
  </script>
6
13
 
7
14
  <template>
@@ -56,8 +56,12 @@ onUnmounted(() => {
56
56
  clicks!.unregister(id)
57
57
  })
58
58
 
59
+ watchEffect(() => {
60
+ el.value?.classList.toggle('slidev-code-line-numbers', props.lines)
61
+ })
62
+
59
63
  onMounted(() => {
60
- if (!clicks || clicks.disabled)
64
+ if (!clicks || clicks.disabled || !props.ranges?.length)
61
65
  return
62
66
 
63
67
  const { start, end, delta } = clicks.resolve(props.at, props.ranges.length - 1)
@@ -121,9 +125,12 @@ function copyCode() {
121
125
 
122
126
  <template>
123
127
  <div
124
- ref="el" class="slidev-code-wrapper relative group" :class="{
128
+ ref="el"
129
+ class="slidev-code-wrapper relative group"
130
+ :class="{
125
131
  'slidev-code-line-numbers': props.lines,
126
- }" :style="{
132
+ }"
133
+ :style="{
127
134
  'max-height': props.maxHeight,
128
135
  'overflow-y': props.maxHeight ? 'scroll' : undefined,
129
136
  '--start': props.startLine,
@@ -55,7 +55,7 @@ onUnmounted(() => {
55
55
  })
56
56
 
57
57
  onMounted(() => {
58
- if (!clicks || clicks.disabled)
58
+ if (!clicks || clicks.disabled || !props.ranges?.length)
59
59
  return
60
60
 
61
61
  const { start, end, delta } = clicks.resolve(props.at, props.ranges.length - 1)
@@ -48,6 +48,7 @@ watchEffect(async (onCleanup) => {
48
48
  }
49
49
  catch (e) {
50
50
  error.value = `${e}`
51
+ console.warn(e)
51
52
  }
52
53
  })
53
54
 
@@ -76,6 +77,6 @@ watchEffect(() => {
76
77
  </script>
77
78
 
78
79
  <template>
79
- <pre v-if="error" border="1 red rounded" class="pa-3">{{ error }}</pre>
80
+ <pre v-if="error" border="1 red rounded" class="pa-3 text-wrap">{{ error }}</pre>
80
81
  <ShadowRoot v-else class="mermaid" :inner-html="html" @shadow="el = $event" />
81
82
  </template>
@@ -12,11 +12,10 @@ Learn more: https://sli.dev/guide/syntax.html#monaco-editor
12
12
  -->
13
13
 
14
14
  <script setup lang="ts">
15
- import * as monaco from 'monaco-editor'
15
+ import type * as monaco from 'monaco-editor'
16
16
  import { computed, nextTick, onMounted, ref } from 'vue'
17
17
  import { debounce } from '@antfu/utils'
18
- import { decompressFromBase64 } from 'lz-string'
19
- import setup from '../setup/monaco'
18
+ import lz from 'lz-string'
20
19
  import { makeId } from '../logic/utils'
21
20
 
22
21
  const props = withDefaults(defineProps<{
@@ -37,8 +36,8 @@ const props = withDefaults(defineProps<{
37
36
  ata: true,
38
37
  })
39
38
 
40
- const code = decompressFromBase64(props.codeLz).trimEnd()
41
- const diff = props.diffLz && decompressFromBase64(props.diffLz).trimEnd()
39
+ const code = lz.decompressFromBase64(props.codeLz).trimEnd()
40
+ const diff = props.diffLz && lz.decompressFromBase64(props.diffLz).trimEnd()
42
41
 
43
42
  const langMap: Record<string, string> = {
44
43
  ts: 'typescript',
@@ -67,7 +66,9 @@ const height = computed(() => {
67
66
  })
68
67
 
69
68
  onMounted(async () => {
70
- const { ata } = await setup()
69
+ // Lazy load monaco, so it will be bundled in async chunk
70
+ const { default: setup } = await import('../setup/monaco')
71
+ const { ata, monaco } = await setup()
71
72
  const model = monaco.editor.createModel(code, lang, monaco.Uri.parse(`file:///${makeId()}.${ext}`))
72
73
  const commonOptions = {
73
74
  automaticLayout: true,
@@ -88,7 +89,7 @@ onMounted(async () => {
88
89
 
89
90
  let editableEditor: monaco.editor.IStandaloneCodeEditor
90
91
  if (diff) {
91
- const diffModel = monaco.editor.createModel(diff, lang, monaco.Uri.parse(`file:///${nanoid()}.${ext}`))
92
+ const diffModel = monaco.editor.createModel(diff, lang, monaco.Uri.parse(`file:///${makeId()}.${ext}`))
92
93
  const editor = monaco.editor.createDiffEditor(container.value!, {
93
94
  renderOverviewRuler: false,
94
95
  ...commonOptions,
@@ -2,7 +2,7 @@
2
2
  import { ShikiMagicMovePrecompiled } from 'shiki-magic-move/vue'
3
3
  import type { KeyedTokensInfo } from 'shiki-magic-move/types'
4
4
  import { onMounted, onUnmounted, ref, watchEffect } from 'vue'
5
- import { decompressFromBase64 } from 'lz-string'
5
+ import lz from 'lz-string'
6
6
  import { useSlideContext } from '../context'
7
7
  import { makeId } from '../logic/utils'
8
8
 
@@ -13,7 +13,7 @@ const props = defineProps<{
13
13
  at?: string | number
14
14
  }>()
15
15
 
16
- const steps = JSON.parse(decompressFromBase64(props.stepsLz)) as KeyedTokensInfo[]
16
+ const steps = JSON.parse(lz.decompressFromBase64(props.stepsLz)) as KeyedTokensInfo[]
17
17
  const { $clicksContext: clicks, $scale: scale } = useSlideContext()
18
18
  const id = makeId()
19
19
  const index = ref(0)
package/builtin/Tweet.vue CHANGED
@@ -7,9 +7,8 @@ Usage:
7
7
  -->
8
8
 
9
9
  <script setup lang="ts">
10
- import { getCurrentInstance, onMounted, ref } from 'vue'
10
+ import { onMounted, ref } from 'vue'
11
11
  import { isDark } from '../logic/dark'
12
- import { useTweetScript } from '../composables/useTweetScript'
13
12
 
14
13
  const props = defineProps<{
15
14
  id: string | number
@@ -20,11 +19,10 @@ const props = defineProps<{
20
19
 
21
20
  const tweet = ref<HTMLElement | null>()
22
21
 
23
- const vm = getCurrentInstance()!
24
22
  const loaded = ref(false)
25
23
  const tweetNotFound = ref(false)
26
24
 
27
- async function create() {
25
+ onMounted(async () => {
28
26
  // @ts-expect-error global
29
27
  const element = await window.twttr.widgets.createTweet(
30
28
  props.id.toString(),
@@ -38,13 +36,7 @@ async function create() {
38
36
  loaded.value = true
39
37
  if (element === undefined)
40
38
  tweetNotFound.value = true
41
- }
42
-
43
- // @ts-expect-error global
44
- if (window?.twttr?.widgets)
45
- onMounted(create)
46
- else
47
- useTweetScript(vm, create)
39
+ })
48
40
  </script>
49
41
 
50
42
  <template>
@@ -30,10 +30,14 @@ function onMousedown() {
30
30
  <div
31
31
  class="flex gap-0.5 items-center select-none"
32
32
  :title="`Clicks in this slide: ${total}`"
33
+ :class="total ? '' : 'op50'"
33
34
  >
34
35
  <div class="flex gap-1 items-center min-w-16">
35
36
  <carbon:cursor-1 text-sm op50 />
36
- <span v-if="current <= total && current >= 0" text-primary>{{ current }}/</span>
37
+ <template v-if="current <= total && current >= 0">
38
+ <span text-primary>{{ current }}</span>
39
+ <span op50>/</span>
40
+ </template>
37
41
  <span op50>{{ total }}</span>
38
42
  </div>
39
43
  <div
@@ -47,16 +51,16 @@ function onMousedown() {
47
51
  i === 0 ? 'rounded-l border-l' : '',
48
52
  i === total ? 'rounded-r border-r' : '',
49
53
  ]"
50
- :style="{ width: `${1 / total * 100}%` }"
54
+ :style="{ width: total > 0 ? `${1 / total * 100}%` : '100%' }"
51
55
  >
52
- <div absolute inset-0 z--1 :class=" i <= current ? 'bg-primary op20' : ''" />
56
+ <div absolute inset-0 :class="i <= current ? 'bg-primary op20' : ''" />
53
57
  <div
54
58
  :class="[
55
59
  +i === +current ? 'text-primary font-bold op100 border-primary' : 'op30 border-main',
56
60
  i === 0 ? 'rounded-l' : '',
57
61
  i === total ? 'rounded-r' : 'border-r-2',
58
62
  ]"
59
- w-full h-full text-xs flex items-center justify-center
63
+ w-full h-full text-xs flex items-center justify-center z-1
60
64
  >
61
65
  {{ i }}
62
66
  </div>
@@ -91,10 +91,12 @@ function calculateEditorHeight() {
91
91
  }
92
92
 
93
93
  watch(
94
- note,
95
- () => nextTick(() => {
96
- calculateEditorHeight()
97
- }),
94
+ [note, editing],
95
+ () => {
96
+ nextTick(() => {
97
+ calculateEditorHeight()
98
+ })
99
+ },
98
100
  { flush: 'post', immediate: true },
99
101
  )
100
102
  </script>
@@ -50,6 +50,6 @@ provideLocal(injectionSlideScale, scale)
50
50
  }
51
51
 
52
52
  .print-slide-container {
53
- @apply relative overflow-hidden break-after-page;
53
+ @apply relative overflow-hidden break-after-page translate-0;
54
54
  }
55
55
  </style>
@@ -1,7 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import { useEventListener, useVModel } from '@vueuse/core'
3
3
  import { computed, ref, watchEffect } from 'vue'
4
- import { themeVars } from '../env'
5
4
  import { breakpoints, showOverview, windowSize } from '../state'
6
5
  import { currentPage, go as goSlide, rawRoutes } from '../logic/nav'
7
6
  import { currentOverviewPage, overviewRowCount } from '../logic/overview'
@@ -128,7 +127,6 @@ watchEffect(() => {
128
127
  <div
129
128
  class="inline-block border rounded overflow-hidden bg-main hover:border-primary transition"
130
129
  :class="(focus(idx + 1) || currentOverviewPage === idx + 1) ? 'border-primary' : 'border-main'"
131
- :style="themeVars"
132
130
  @click="go(+route.path)"
133
131
  >
134
132
  <SlideContainer
@@ -1,5 +1,5 @@
1
1
  import mermaid from 'mermaid/dist/mermaid.esm.mjs'
2
- import { decompressFromBase64 } from 'lz-string'
2
+ import lz from 'lz-string'
3
3
  import { clearUndefined } from '@antfu/utils'
4
4
  import setupMermaid from '../setup/mermaid'
5
5
  import { makeId } from '../logic/utils'
@@ -20,7 +20,7 @@ export async function renderMermaid(lzEncoded: string, options: any) {
20
20
  ...clearUndefined(setupMermaid() || {}),
21
21
  ...clearUndefined(options),
22
22
  })
23
- const code = decompressFromBase64(lzEncoded)
23
+ const code = lz.decompressFromBase64(lzEncoded)
24
24
  const id = makeId()
25
25
  const { svg } = await mermaid.render(id, code)
26
26
  cache.set(key, svg)
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@slidev/client",
3
3
  "type": "module",
4
- "version": "0.48.0-beta.16",
4
+ "version": "0.48.0-beta.18",
5
5
  "description": "Presentation slides for developers",
6
6
  "author": "antfu <anthonyfu117@hotmail.com>",
7
7
  "license": "MIT",
@@ -39,7 +39,6 @@
39
39
  "@vueuse/math": "^10.8.0",
40
40
  "@vueuse/motion": "^2.1.0",
41
41
  "codemirror": "^5.65.16",
42
- "defu": "^6.1.4",
43
42
  "drauu": "^0.4.0",
44
43
  "file-saver": "^2.0.5",
45
44
  "floating-vue": "^5.2.2",
@@ -49,17 +48,15 @@
49
48
  "lz-string": "^1.5.0",
50
49
  "mermaid": "^10.8.0",
51
50
  "monaco-editor": "^0.46.0",
52
- "nanoid": "^5.0.6",
53
51
  "prettier": "^3.2.5",
54
52
  "recordrtc": "^5.6.2",
55
- "resolve": "^1.22.8",
56
53
  "shiki": "^1.1.7",
57
54
  "shiki-magic-move": "^0.1.0",
58
55
  "unocss": "^0.58.5",
59
56
  "vue": "^3.4.20",
60
57
  "vue-router": "^4.3.0",
61
- "@slidev/parser": "0.48.0-beta.16",
62
- "@slidev/types": "0.48.0-beta.16"
58
+ "@slidev/types": "0.48.0-beta.18",
59
+ "@slidev/parser": "0.48.0-beta.18"
63
60
  },
64
61
  "devDependencies": {
65
62
  "vite": "^5.1.4"
@@ -3,7 +3,7 @@ import { computed, nextTick, onMounted, reactive, ref } from 'vue'
3
3
  import { useHead } from '@unhead/vue'
4
4
  import type { RouteRecordRaw } from 'vue-router'
5
5
  import type { ClicksContext } from 'packages/types'
6
- import { configs, themeVars } from '../env'
6
+ import { configs } from '../env'
7
7
  import { openInEditor, rawRoutes } from '../logic/nav'
8
8
  import { useFixedClicks } from '../composables/useClicks'
9
9
  import { isColorSchemaConfigured, isDark, toggleDark } from '../logic/dark'
@@ -13,7 +13,7 @@ import SlideWrapper from '../internals/SlideWrapper'
13
13
  import DrawingPreview from '../internals/DrawingPreview.vue'
14
14
  import IconButton from '../internals/IconButton.vue'
15
15
  import NoteEditable from '../internals/NoteEditable.vue'
16
- import OverviewClicksSlider from '../internals/OverviewClicksSlider.vue'
16
+ import ClicksSlider from '../internals/ClicksSlider.vue'
17
17
  import { CLICKS_MAX } from '../constants'
18
18
 
19
19
  const cardWidth = 450
@@ -164,7 +164,6 @@ onMounted(() => {
164
164
  <div class="flex flex-col gap-2 my5">
165
165
  <div
166
166
  class="border rounded border-main overflow-hidden bg-main select-none h-max"
167
- :style="themeVars"
168
167
  @dblclick="openSlideInNewTab(route.path)"
169
168
  >
170
169
  <SlideContainer
@@ -184,7 +183,7 @@ onMounted(() => {
184
183
  <DrawingPreview :page="+route.path" />
185
184
  </SlideContainer>
186
185
  </div>
187
- <OverviewClicksSlider
186
+ <ClicksSlider
188
187
  v-if="getSlideClicks(route)"
189
188
  mt-2
190
189
  :clicks-context="getClicksContext(route)"
package/pages/play.vue CHANGED
@@ -4,7 +4,7 @@ import { isEditorVertical, isScreenVertical, showEditor, slideScale, windowSize
4
4
  import { isEmbedded, isPrintMode, next, prev, useSwipeControls } from '../logic/nav'
5
5
  import { isDrawing } from '../logic/drawings'
6
6
  import { registerShortcuts } from '../logic/shortcuts'
7
- import { configs, themeVars } from '../env'
7
+ import { configs } from '../env'
8
8
  import Controls from '../internals/Controls.vue'
9
9
  import SlideContainer from '../internals/SlideContainer.vue'
10
10
  import NavControls from '../internals/NavControls.vue'
@@ -42,7 +42,10 @@ if (__SLIDEV_FEATURE_DRAWINGS__)
42
42
 
43
43
  <template>
44
44
  <PrintStyle v-if="isPrintMode" />
45
- <div id="page-root" ref="root" class="grid" :class="isEditorVertical ? 'grid-rows-[1fr_max-content]' : 'grid-cols-[1fr_max-content]'" :style="themeVars">
45
+ <div
46
+ id="page-root" ref="root" class="grid"
47
+ :class="isEditorVertical ? 'grid-rows-[1fr_max-content]' : 'grid-cols-[1fr_max-content]'"
48
+ >
46
49
  <SlideContainer
47
50
  class="w-full h-full"
48
51
  :style="{ background: 'var(--slidev-slide-container-background, black)' }"
@@ -2,7 +2,7 @@
2
2
  import { computed } from 'vue'
3
3
  import { useStyleTag } from '@vueuse/core'
4
4
  import { useHead } from '@unhead/vue'
5
- import { configs, themeVars } from '../../env'
5
+ import { configs } from '../../env'
6
6
  import { rawRoutes, total } from '../../logic/nav'
7
7
  import NoteDisplay from '../../internals/NoteDisplay.vue'
8
8
 
@@ -34,7 +34,7 @@ const slidesWithNote = computed(() => rawRoutes
34
34
  </script>
35
35
 
36
36
  <template>
37
- <div id="page-root" :style="themeVars">
37
+ <div id="page-root">
38
38
  <div class="m-4">
39
39
  <div class="mb-10">
40
40
  <h1 class="text-4xl font-bold mt-2">
@@ -4,13 +4,13 @@ import { computed, onMounted, reactive, ref, shallowRef, watch } from 'vue'
4
4
  import { useMouse, useWindowFocus } from '@vueuse/core'
5
5
  import { clicksContext, currentPage, currentRoute, currentSlideId, hasNext, nextRoute, queryClicks, rawRoutes, total, useSwipeControls } from '../logic/nav'
6
6
  import { decreasePresenterFontSize, increasePresenterFontSize, presenterLayout, presenterNotesFontSize, showEditor, showOverview, showPresenterCursor } from '../state'
7
- import { configs, themeVars } from '../env'
7
+ import { configs } from '../env'
8
8
  import { sharedState } from '../state/shared'
9
9
  import { registerShortcuts } from '../logic/shortcuts'
10
10
  import { getSlideClass } from '../utils'
11
11
  import { useTimer } from '../logic/utils'
12
12
  import { isDrawing } from '../logic/drawings'
13
- import { useFixedClicks } from '../composables/useClicks'
13
+ import { useFixedClicks, usePrimaryClicks } from '../composables/useClicks'
14
14
  import SlideWrapper from '../internals/SlideWrapper'
15
15
  import SlideContainer from '../internals/SlideContainer.vue'
16
16
  import NavControls from '../internals/NavControls.vue'
@@ -21,6 +21,7 @@ import Goto from '../internals/Goto.vue'
21
21
  import SlidesShow from '../internals/SlidesShow.vue'
22
22
  import DrawingControls from '../internals/DrawingControls.vue'
23
23
  import IconButton from '../internals/IconButton.vue'
24
+ import ClicksSlider from '../internals/ClicksSlider.vue'
24
25
 
25
26
  const main = ref<HTMLDivElement>()
26
27
 
@@ -93,20 +94,25 @@ onMounted(() => {
93
94
  <template>
94
95
  <div class="bg-main h-full slidev-presenter">
95
96
  <div class="grid-container" :class="`layout${presenterLayout}`">
96
- <div ref="main" class="relative grid-section main flex flex-col p-2 lg:p-4" :style="themeVars">
97
+ <div ref="main" class="relative grid-section main flex flex-col">
97
98
  <SlideContainer
98
99
  key="main"
99
- class="h-full w-full"
100
+ class="h-full w-full p-2 lg:p-4 flex-auto"
100
101
  >
101
102
  <template #default>
102
103
  <SlidesShow render-context="presenter" />
103
104
  </template>
104
105
  </SlideContainer>
106
+ <ClicksSlider
107
+ :key="currentRoute?.path"
108
+ :clicks-context="usePrimaryClicks(currentRoute)"
109
+ class="w-full pb2 px4 flex-none"
110
+ />
105
111
  <div class="absolute left-0 top-0 bg-main border-b border-r border-main px2 py1 op50 text-sm">
106
112
  Current
107
113
  </div>
108
114
  </div>
109
- <div class="relative grid-section next flex flex-col p-2 lg:p-4" :style="themeVars">
115
+ <div class="relative grid-section next flex flex-col p-2 lg:p-4">
110
116
  <SlideContainer
111
117
  v-if="nextFrame && nextFrameClicksCtx"
112
118
  key="next"
@@ -182,7 +188,7 @@ onMounted(() => {
182
188
  </div>
183
189
  <div class="progress-bar">
184
190
  <div
185
- class="progress h-2px bg-primary transition-all"
191
+ class="progress h-3px bg-primary transition-all"
186
192
  :style="{ width: `${(currentPage - 1) / (total - 1) * 100}%` }"
187
193
  />
188
194
  </div>
@@ -257,7 +263,7 @@ onMounted(() => {
257
263
  }
258
264
 
259
265
  .progress-bar {
260
- --uno: fixed left-0 right-0 bottom-0;
266
+ --uno: fixed left-0 right-0 top-0;
261
267
  }
262
268
 
263
269
  .grid-section {
package/pages/print.vue CHANGED
@@ -2,7 +2,6 @@
2
2
  import { watchEffect } from 'vue'
3
3
  import { windowSize } from '../state'
4
4
  import { isPrintMode } from '../logic/nav'
5
- import { themeVars } from '../env'
6
5
  import PrintContainer from '../internals/PrintContainer.vue'
7
6
  import PrintStyle from '../internals/PrintStyle.vue'
8
7
 
@@ -16,7 +15,7 @@ watchEffect(() => {
16
15
 
17
16
  <template>
18
17
  <PrintStyle v-if="isPrintMode" />
19
- <div id="page-root" class="grid grid-cols-[1fr_max-content]" :style="themeVars">
18
+ <div id="page-root" class="grid grid-cols-[1fr_max-content]">
20
19
  <PrintContainer
21
20
  class="w-full h-full"
22
21
  :style="{ background: 'var(--slidev-slide-container-background, black)' }"
@@ -1,7 +1,7 @@
1
1
  import type { Ref, WritableComputedRef } from 'vue'
2
2
  import { onClickOutside } from '@vueuse/core'
3
3
  import { watch } from 'vue'
4
- import * as _CodeMirror from 'codemirror'
4
+ import { fromTextArea } from 'codemirror'
5
5
  import 'codemirror/mode/javascript/javascript'
6
6
  import 'codemirror/mode/css/css'
7
7
  import 'codemirror/mode/markdown/markdown'
@@ -10,14 +10,12 @@ import 'codemirror/mode/htmlmixed/htmlmixed'
10
10
  import 'codemirror/addon/display/placeholder'
11
11
  import 'codemirror/lib/codemirror.css'
12
12
 
13
- const CodeMirror = _CodeMirror.default ?? ('fromTextArea' in _CodeMirror ? _CodeMirror : globalThis.CodeMirror)
14
-
15
13
  export async function useCodeMirror(
16
14
  textarea: Ref<HTMLTextAreaElement | null | undefined>,
17
15
  input: Ref<string> | WritableComputedRef<string>,
18
16
  options: CodeMirror.EditorConfiguration = {},
19
17
  ) {
20
- const cm = CodeMirror.fromTextArea(
18
+ const cm = fromTextArea(
21
19
  textarea.value!,
22
20
  {
23
21
  theme: 'vars',
package/setup/monaco.ts CHANGED
@@ -24,7 +24,6 @@ import { isDark } from '../logic/dark'
24
24
  import configs from '#slidev/configs'
25
25
 
26
26
  /* __imports__ */
27
-
28
27
  window.MonacoEnvironment = {
29
28
  getWorker(_, label) {
30
29
  if (label === 'json')
@@ -1,17 +0,0 @@
1
- import { createSharedComposable, useScriptTag } from '@vueuse/core'
2
- import type { ComponentInternalInstance } from 'vue'
3
- import { onMounted } from 'vue'
4
-
5
- export const useTweetScript = createSharedComposable(
6
- (vm: ComponentInternalInstance, create: () => void) =>
7
- useScriptTag(
8
- 'https://platform.twitter.com/widgets.js',
9
- () => {
10
- if (vm.isMounted)
11
- create()
12
- else
13
- onMounted(create, vm)
14
- },
15
- { async: true },
16
- ),
17
- )