tiptapify 0.0.9 → 0.0.10

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.
@@ -1,35 +1,38 @@
1
1
  <script setup lang="ts">
2
2
 
3
- import * as mdi from '@mdi/js'
4
3
  import { Editor } from "@tiptap/vue-3";
5
4
 
6
5
  import { useI18n } from 'vue-i18n'
7
- import { computed, inject, onMounted, onUnmounted, Ref, ref } from 'vue'
6
+ import { computed, inject, onMounted, onUnmounted, Ref, ref, watch } from 'vue'
8
7
 
9
- import helpers from '@tiptapify/utils/helpers'
8
+ import Dialog from "@tiptapify/components/UI/Dialog.vue"
10
9
 
11
10
  defineProps({
12
11
  variantBtn: { type: String, default() { return 'elevated' }},
13
- variantField: { type: String, default() { return 'solo' }}
12
+ variantField: { type: String, default() { return 'outlined' }}
14
13
  })
15
14
 
16
- const { ucFirst } = helpers
17
-
18
15
  const editor = inject('tiptapifyEditor') as Ref<Editor>
19
16
  const { t } = useI18n()
20
17
 
21
18
  const generateLinkAttrs = () => ({
22
19
  href: '',
23
- target: '_blank',
20
+ target: targetAttrs.value[0],
24
21
  cssClass: '',
25
22
  rel: ''
26
23
  })
27
24
 
28
- const relAttrs = ['alternate', 'author', 'bookmark', 'external', 'help', 'license', 'next', 'nofollow', 'noreferrer', 'noopener', 'prev', 'search', 'tag']
25
+ const relAttrs = ['alternate', 'author', 'bookmark', 'external', 'help', 'license', 'me', 'next', 'nofollow', 'noopener', 'noreferrer', 'opener', 'prev', 'privacy-policy', 'search', 'tag', 'terms-of-service']
26
+
27
+ const targetAttrs = computed(() => [
28
+ { value: '_blank', title: t('dialog.link.target_blank') },
29
+ { value: '_self', title: t('dialog.link.target_self') }
30
+ ])
29
31
 
30
32
  const attrs = ref(generateLinkAttrs())
33
+ const hrefInvalid = ref(false)
31
34
 
32
- const dialog = ref<boolean>(false)
35
+ const dialog = ref(null)
33
36
 
34
37
  const isDisabled = computed(() => {
35
38
  const { href } = attrs.value
@@ -38,11 +41,10 @@ const isDisabled = computed(() => {
38
41
 
39
42
  function apply() {
40
43
  let { href, target, rel, cssClass } = attrs.value
41
- target = target ? '_blank' : '_self'
42
- rel = rel.join(' ')
44
+ rel = rel?.length ? rel.join(' ') : null
43
45
 
44
46
  if (href) {
45
- editor.value.chain().focus().extendMarkRange('link').setLink({ href, target, rel, class: cssClass }).run()
47
+ editor.value.chain().focus().extendMarkRange('link').setLink({ href, target: target.value, rel, class: cssClass }).run()
46
48
  }
47
49
 
48
50
  close()
@@ -55,18 +57,18 @@ function clear() {
55
57
  }
56
58
 
57
59
  function close() {
58
- dialog.value = false
59
-
60
60
  attrs.value = generateLinkAttrs()
61
+
62
+ dialog.value.close()
61
63
  }
62
64
 
63
65
  const showLink = (event: CustomEvent) => {
64
66
  attrs.value.href = event.detail.link?.href
65
- attrs.value.target = event.detail.link?.target === '_blank'
67
+ attrs.value.target = targetAttrs.value.find(item => item.value === event.detail.link?.target) ?? targetAttrs[0]
66
68
  attrs.value.rel = event.detail.link?.rel?.split(' ')
67
69
  attrs.value.cssClass = event.detail.link?.class
68
70
 
69
- dialog.value = true;
71
+ dialog.value.open()
70
72
  }
71
73
 
72
74
  onMounted(() => {
@@ -76,67 +78,80 @@ onMounted(() => {
76
78
  onUnmounted(() => {
77
79
  window.removeEventListener('tiptapify-show-link', showLink as EventListener)
78
80
  })
79
- </script>
80
81
 
81
- <template>
82
- <VDialog v-model="dialog" max-width="800" absolute @click:outside="close">
83
- <VCard>
84
- <VToolbar class="px-6" density="compact">
85
- <span class="headline">{{ ucFirst(t('dialog.link.title')) }}</span>
86
-
87
- <VSpacer />
82
+ watch(() => attrs.value.href, () => {
83
+ const regex = new RegExp(/^((https?|ftps?|sftp):\/\/[a-z0-9]+(\.[a-z0-9]+)+|mailto:\w+@[a-z]+(\.[a-z]+)*|tel:\+?\d+)$/, 'igu')
88
84
 
89
- <VBtn class="mx-0" icon @click="close">
90
- <VIcon :icon="mdi.mdiClose" />
91
- </VBtn>
92
- </VToolbar>
85
+ hrefInvalid.value = !regex.test(attrs.value.href)
86
+ })
87
+ </script>
93
88
 
89
+ <template>
90
+ <Dialog ref="dialog" module="link">
91
+ <template #content>
94
92
  <VCardText>
95
93
  <VRow>
96
- <VCol cols="12" md="9">
97
- <VTextField v-model="attrs.href" :variant="variantField" :label="ucFirst(t('dialog.link.href'))" autofocus />
94
+ <VCol cols="12">
95
+ <VTextField
96
+ v-model="attrs.href"
97
+ density="compact"
98
+ variant="outlined"
99
+ :label="t('dialog.link.href')"
100
+ :error-messages="hrefInvalid ? t('dialog.link.href_error') : ''"
101
+ autofocus
102
+ />
98
103
  </VCol>
99
104
 
100
- <VCol cols="12" md="3">
101
- <VCheckbox v-model="attrs.target" color="primary" :label="ucFirst(t('dialog.link.target'))" />
105
+ <VCol cols="12" md="4">
106
+ <VSelect
107
+ v-model="attrs.target"
108
+ :items="targetAttrs"
109
+ :label="t('dialog.link.target')"
110
+ variant="outlined"
111
+ return-object
112
+ density="compact"
113
+ />
114
+ </VCol>
115
+
116
+ <VCol cols="12" md="8">
117
+ <VTextField v-model="attrs.cssClass" density="compact" variant="outlined" :label="t('dialog.link.class')" />
102
118
  </VCol>
103
119
 
104
120
  <VCol cols="12">
105
121
  <VSelect
106
122
  v-model="attrs.rel"
107
123
  :items="relAttrs"
108
- :label="ucFirst(t('dialog.link.rel'))"
109
- :variant="variantField"
124
+ :label="t('dialog.link.rel')"
125
+ variant="outlined"
110
126
  multiple
111
127
  chips
112
128
  closable-chips
113
129
  clearable
130
+ density="compact"
114
131
  />
115
132
  </VCol>
116
-
117
- <VCol cols="12">
118
- <VTextField v-model="attrs.cssClass" :variant="variantField" :label="ucFirst(t('dialog.link.class'))" />
119
- </VCol>
120
133
  </VRow>
121
134
  </VCardText>
135
+ </template>
122
136
 
137
+ <template #actions>
123
138
  <VCardActions>
124
139
  <VRow>
125
140
  <VCol class="d-flex justify-start">
126
141
  <VBtn color="warning" v-if="editor.isActive('link')" :variant="variantBtn" :disabled="isDisabled" @click="clear">
127
- {{ ucFirst(t('dialog.clear')) }}
142
+ {{ t('dialog.clear') }}
128
143
  </VBtn>
129
144
  </VCol>
130
145
  <VCol class="d-flex justify-end">
131
146
  <VBtn :variant="variantBtn" @click="close" class="mr-2">
132
- {{ ucFirst(t('dialog.close')) }}
147
+ {{ t('dialog.close') }}
133
148
  </VBtn>
134
149
  <VBtn color="primary" :variant="variantBtn" :disabled="isDisabled" @click="apply">
135
- {{ ucFirst(t('dialog.apply')) }}
150
+ {{ t('dialog.apply') }}
136
151
  </VBtn>
137
152
  </VCol>
138
153
  </VRow>
139
154
  </VCardActions>
140
- </VCard>
141
- </VDialog>
155
+ </template>
156
+ </Dialog>
142
157
  </template>
@@ -1,17 +1,15 @@
1
1
  <script setup lang="ts">
2
+ import Dialog from "@tiptapify/components/UI/Dialog.vue";
2
3
  import { ref, onMounted, onUnmounted } from 'vue'
3
- import { useI18n } from "vue-i18n";
4
- import * as mdi from '@mdi/js'
5
-
6
- const { t } = useI18n();
7
4
 
8
5
  const content = ref()
9
6
 
10
- const dialog = ref(false)
7
+ const dialog = ref(null)
11
8
 
12
9
  const showDialog = (event: CustomEvent) => {
13
10
  content.value = event.detail.html
14
- dialog.value = true;
11
+
12
+ dialog.value.open()
15
13
  }
16
14
 
17
15
  onMounted(() => {
@@ -24,19 +22,13 @@ onUnmounted(() => {
24
22
  </script>
25
23
 
26
24
  <template>
27
- <VDialog v-model="dialog" fullscreen>
28
- <VCard>
29
- <VToolbar>
30
- <VBtn :icon="mdi.mdiClose" @click="dialog = false" />
31
-
32
- <VToolbarTitle>Preview</VToolbarTitle>
33
- </VToolbar>
34
-
25
+ <Dialog ref="dialog" module="preview" fullscreen>
26
+ <template #content>
35
27
  <VCardItem>
36
28
  <div class="tiptap" v-html="content"></div>
37
29
  </VCardItem>
38
- </VCard>
39
- </VDialog>
30
+ </template>
31
+ </Dialog>
40
32
  </template>
41
33
 
42
34
  <style lang="scss">
@@ -1,23 +1,20 @@
1
1
  <script setup lang="ts">
2
2
  import { Editor } from "@tiptap/vue-3";
3
+ import Dialog from "@tiptapify/components/UI/Dialog.vue";
3
4
  import { ref, onMounted, onUnmounted, watch, inject, Ref } from 'vue'
4
5
  import { useI18n } from "vue-i18n";
5
6
 
6
- import helpers from "@tiptapify/utils/helpers";
7
-
8
7
  const props = defineProps({
9
8
  indent: { type: Number, default: 2 },
10
9
  variantBtn: { type: String, default: 'elevated' },
11
10
  variantField: { type: String, default: 'solo' }
12
11
  })
13
12
 
14
- const { ucFirst } = helpers;
15
-
16
13
  const { t } = useI18n();
17
14
 
18
15
  const editor = inject('tiptapifyEditor') as Ref<Editor>
19
16
 
20
- const dialog = ref(false)
17
+ const dialog = ref(null)
21
18
  const formatted = ref(false)
22
19
  const sourceCode = ref('')
23
20
 
@@ -57,11 +54,12 @@ const unformatHtml = (html: string): string => {
57
54
 
58
55
  const showDialog = (event: CustomEvent) => {
59
56
  sourceCode.value = event.detail.html
60
- dialog.value = true;
57
+
58
+ dialog.value.open()
61
59
  }
62
60
 
63
61
  const saveChanges = () => {
64
- dialog.value = false
62
+ dialog.value.close()
65
63
 
66
64
  editor.value.commands.setContent(sourceCode.value, true)
67
65
  }
@@ -80,16 +78,14 @@ watch(() => formatted.value, () => {
80
78
  </script>
81
79
 
82
80
  <template>
83
- <VDialog v-model="dialog" max-width="1500">
84
- <VCard>
85
- <VCardTitle>{{ ucFirst(t('dialog.source.title')) }}</VCardTitle>
86
-
81
+ <Dialog ref="dialog" module="source" :max-width="1500">
82
+ <template #content>
87
83
  <VCardText>
88
84
  <VContainer fluid class="pt-0 pl-0 pr-0">
89
85
  <VRow>
90
86
  <VCol>
91
87
  <VBtn v-model="formatted" :color="`${formatted ? 'primary' : ''}`" @click="formatted = !formatted">
92
- {{ ucFirst(t('dialog.source.prettify')) }}
88
+ {{ t('dialog.source.prettify') }}
93
89
  </VBtn>
94
90
  </VCol>
95
91
  </VRow>
@@ -103,18 +99,20 @@ watch(() => formatted.value, () => {
103
99
  class="source-code-area"
104
100
  />
105
101
  </VCardText>
102
+ </template>
106
103
 
104
+ <template #actions>
107
105
  <VCardActions>
108
106
  <VSpacer></VSpacer>
109
107
  <VBtn :variant="variantBtn" @click="dialog = false">
110
- {{ ucFirst(t('dialog.close')) }}
108
+ {{ t('dialog.close') }}
111
109
  </VBtn>
112
110
  <VBtn :variant="variantBtn" color="primary" @click="saveChanges">
113
- {{ ucFirst(t('dialog.apply')) }}
111
+ {{ t('dialog.apply') }}
114
112
  </VBtn>
115
113
  </VCardActions>
116
- </VCard>
117
- </VDialog>
114
+ </template>
115
+ </Dialog>
118
116
  </template>
119
117
 
120
118
  <style scoped lang="scss">
@@ -7,7 +7,7 @@ import { computed, inject, Ref, ref } from 'vue'
7
7
 
8
8
  const { t } = useI18n()
9
9
 
10
- defineExpose({ open })
10
+ defineExpose({ open, close })
11
11
 
12
12
  const props = defineProps({
13
13
  show: { type: Boolean, default: true },
@@ -21,25 +21,52 @@ const emit = defineEmits(['close'])
21
21
  const editor = inject('tiptapifyEditor') as Ref<Editor>
22
22
 
23
23
  const colorPicker = ref(false)
24
+ const initialColor = ref(computed(() => props.color).value)
24
25
  const customColor = ref(computed(() => props.color).value)
25
26
 
26
- const colors = {
27
+ const colorSelected = ref<boolean>(false)
28
+
29
+ const greyShades = {
27
30
  black: '#000',
31
+ '#222': '#222',
28
32
  darkgray: '#444',
29
33
  gray: '#888',
30
34
  lightgray: '#ccc',
31
35
  white: '#fff',
32
- cyan: '#00FFFF',
33
- light: '#0088ff',
36
+ }
37
+ const blueShades = {
38
+ '#000044': '#000044',
39
+ '#00006e': '#00006e',
40
+ '#0000bb': '#0000bb',
34
41
  blue: '#0000ff',
35
- indigo: '#4b0082',
36
- purple: '#800080',
37
- pink: '#ff00ff',
42
+ lightblue: '#0088ff',
43
+ cyan: '#00FFFF',
44
+ }
45
+ const greenShades = {
46
+ '#003100': '#003100',
47
+ '#005200': '#005200',
48
+ '#007000': '#007000',
49
+ '#00b700': '#00b700',
50
+ green: '#00ff00',
51
+ '#70ff70': '#70ff70',
52
+ }
53
+ const redShades = {
54
+ '#520000': '#520000',
55
+ '#810000': '#810000',
56
+ '#b20000': '#b20000',
38
57
  red: '#ff0000',
58
+ '#ff2323': '#ff2323',
59
+ '#ff5c5c': '#ff5c5c',
60
+ }
61
+ const otherShades = {
62
+ brown: '#964B00',
39
63
  orange: '#ff9900',
40
64
  yellow: '#ffff00',
41
- green: '#00ff00',
65
+ pink: '#ff00ff',
66
+ purple: '#800080',
67
+ indigo: '#4b0082',
42
68
  }
69
+ const colors = { ...greyShades, ...blueShades, ...greenShades, ...redShades, ...otherShades }
43
70
 
44
71
  type Color = { r: number, g: number, b: number, a?: number }
45
72
 
@@ -67,14 +94,38 @@ function hexToRgb(hex: string): Color {
67
94
  };
68
95
  }
69
96
 
70
- function setColor(color: string) {
97
+ function hoverColor(color: string) {
98
+ colorSelected.value = false
99
+
71
100
  if (props.fontColor) {
72
101
  editor.value.chain().focus().setColor(color).run()
73
102
  }
74
103
 
75
104
  if (props.backgroundColor) {
76
- editor.value.chain().focus().setHighlight({ color }).run()
105
+ editor.value.chain().focus().setHighlight({ color: color }).run()
106
+ }
107
+ }
108
+
109
+ function close() {
110
+ resetColor()
111
+ }
112
+
113
+ function resetColor() {
114
+ if (colorSelected.value) {
115
+ return
116
+ }
117
+
118
+ if (props.fontColor) {
119
+ editor.value.chain().focus().setColor(initialColor.value).run()
120
+ }
121
+
122
+ if (props.backgroundColor) {
123
+ editor.value.chain().focus().setHighlight({ color: initialColor.value }).run()
77
124
  }
125
+ }
126
+
127
+ function setColor() {
128
+ colorSelected.value = true
78
129
 
79
130
  emit('close')
80
131
  }
@@ -103,7 +154,9 @@ function isColorActive(color: string): boolean {
103
154
  <div
104
155
  class="tiptapify-style-color-item"
105
156
  :class="isColorActive(colorCode) ? 'tiptapify-style-color-item-active' : ''"
106
- @click="setColor(colorCode)"
157
+ @mouseenter="hoverColor(colorCode)"
158
+ @mouseleave="resetColor()"
159
+ @click="setColor"
107
160
  >
108
161
  <div
109
162
  class="tiptapify-style-color-picker"
@@ -133,12 +186,12 @@ function isColorActive(color: string): boolean {
133
186
 
134
187
  <VCard>
135
188
  <VCardItem>
136
- <VColorPicker v-model="customColor" elevated elevation="24" hide-inputs />
189
+ <VColorPicker v-model="customColor" elevated elevation="24" hide-inputs @update:model-value="hoverColor($event)" />
137
190
  </VCardItem>
138
191
 
139
192
  <VCardActions>
140
- <VBtn variant="flat" color="primary" @click="setColor(customColor)">OK</VBtn>
141
- <VBtn variant="flat" color="grey-400" @click="colorPicker = !colorPicker; customColor = color">Cancel</VBtn>
193
+ <VBtn variant="flat" color="primary" @click="setColor">OK</VBtn>
194
+ <VBtn variant="flat" color="grey-400" @click="colorPicker = !colorPicker; customColor = initialColor; resetColor()">Cancel</VBtn>
142
195
  </VCardActions>
143
196
  </VCard>
144
197
  </VMenu>
@@ -154,7 +207,7 @@ function isColorActive(color: string): boolean {
154
207
  <style lang="scss" scoped>
155
208
  .tiptapify-style-color-container {
156
209
  display: grid;
157
- grid-template-columns: repeat(5, 1fr);
210
+ grid-template-columns: repeat(6, 1fr);
158
211
  }
159
212
 
160
213
  .tiptapify-style-color-item {
@@ -1,15 +1,12 @@
1
1
  <script setup lang="ts">
2
2
 
3
3
  import { Editor } from "@tiptap/vue-3";
4
- import helpers from '@tiptapify/utils/helpers'
5
4
  import { useI18n } from 'vue-i18n'
6
5
 
7
6
  import { inject, Ref, ref } from 'vue'
8
7
 
9
8
  const { t } = useI18n()
10
9
 
11
- const { ucFirst } = helpers
12
-
13
10
  defineExpose({ open })
14
11
 
15
12
  defineProps({
@@ -55,7 +52,7 @@ function printSelection() {
55
52
  v-model="withHeaderRow"
56
53
  density="compact"
57
54
  color="primary"
58
- :label="ucFirst(t('media.tables.insertWithHeaderRow'))" hide-details
55
+ :label="t('media.tables.insertWithHeaderRow')" hide-details
59
56
  />
60
57
 
61
58
  <div v-for="rowNum in maxRows" :key="`row-${rowNum}`" class="tiptapify-insert-table-row">
@@ -73,8 +70,8 @@ function printSelection() {
73
70
 
74
71
  <div class="tiptapify-table-builder-info">
75
72
  <span>
76
- {{ ucFirst(t('media.tables.rows')) }}: {{ rowHover }}
77
- {{ ucFirst(t('media.tables.cols')) }}: {{ colHover }}
73
+ {{ t('media.tables.rows') }}: {{ rowHover }}
74
+ {{ t('media.tables.cols') }}: {{ colHover }}
78
75
  </span>
79
76
  <span>
80
77
  {{ printSelection() }}
@@ -0,0 +1,118 @@
1
+ {
2
+ "content": {
3
+ "placeholder": "Schriibä Sie öppis da..."
4
+ },
5
+ "style": {
6
+ "paragraph": "Absatz",
7
+ "heading": "Überschrift",
8
+ "headings": {
9
+ "h1": "Überschrift Stufe 1",
10
+ "h2": "Überschrift Stufe 2",
11
+ "h3": "Überschrift Stufe 3",
12
+ "h4": "Überschrift Stufe 4",
13
+ "h5": "Überschrift Stufe 5",
14
+ "h6": "Überschrift Stufe 6"
15
+ },
16
+ "fontFamily": "Schriftart",
17
+ "fontSize": "Schriftgrösse",
18
+ "lineHeight": "Zylähöchi",
19
+ "color": {
20
+ "highlight": "Markierigsfäru",
21
+ "text": "Täxtfäru",
22
+ "unset": "Färu entfärnä",
23
+ "custom": "Benutzerdefinierti Färu"
24
+ }
25
+ },
26
+ "format": {
27
+ "bold": "Fett",
28
+ "italic": "Kursiv",
29
+ "strike": "Durchgstrichä",
30
+ "underline": "Unterstrichä",
31
+ "sup": "Hochgstellt",
32
+ "sub": "Tiefgstellt",
33
+ "break": "Hartä Umbruch",
34
+ "line": "Horizontali Linie",
35
+ "blockquote": "Zitat",
36
+ "code": "Code",
37
+ "codeblock": "Code-Block",
38
+ "formatClear": "Formatierig löschä"
39
+ },
40
+ "media": {
41
+ "link": "Äxtärnä Link",
42
+ "image": "Bild",
43
+ "tables": {
44
+ "table": "Tabälle",
45
+ "insertTable": "Tabälle yfügä",
46
+ "deleteTable": "Tabälle löschä",
47
+ "insertWithHeaderRow": "Tabälle mit Chopfzyle yfügä",
48
+ "rows": "Zylä",
49
+ "row": "Zyle",
50
+ "insertRowBefore": "Zyle dervor yfügä",
51
+ "insertRowAfter": "Zyle dernah yfügä",
52
+ "deleteRow": "Zyle löschä",
53
+ "cols": "Spaltä",
54
+ "col": "Spalte",
55
+ "insertColBefore": "Spalte dervor yfügä",
56
+ "insertColAfter": "Spalte dernah yfügä",
57
+ "deleteCol": "Spalte löschä",
58
+ "mergeCells": "Zällä zämmäfüärä",
59
+ "splitCell": "Zälle teilä"
60
+ }
61
+ },
62
+ "action": {
63
+ "undo": "Rückgängig",
64
+ "redo": "Wiederholä"
65
+ },
66
+ "alignment": "Usrichtig",
67
+ "alignments": {
68
+ "left": "Links usrichtä",
69
+ "center": "Zentriert usrichtä",
70
+ "right": "Rächts usrichtä",
71
+ "justify": "Blocksatz"
72
+ },
73
+ "list": "Lischtä",
74
+ "lists": {
75
+ "bullet": "Ungordneti Lischtä",
76
+ "numbered": "Nummerierti Lischtä",
77
+ "task": "Uufgabälischtä",
78
+ "indent": "Lischtäelemänt yrückä",
79
+ "outdent": "Lischtäelemänt usrückä"
80
+ },
81
+ "dialog": {
82
+ "apply": "Awändä",
83
+ "clear": "Löschä",
84
+ "close": "Schliässä",
85
+ "image": {
86
+ "title": "Bild hinzuefügä/bearbyitä",
87
+ "src": "Quälle",
88
+ "alt": "Alt-Täxt",
89
+ "width": "Bryti",
90
+ "height": "Höchi"
91
+ },
92
+ "link": {
93
+ "title": "Link hinzuefügä/bearbyitä",
94
+ "href": "Link-Adrässe",
95
+ "href_error": "Ungültigi Link-Adrässe",
96
+ "target": "Öffnä i...",
97
+ "target_blank": "Neus Fänschter",
98
+ "target_self": "Aktuells Fänschter",
99
+ "rel": "Rel",
100
+ "class": "CSS-Klasse"
101
+ },
102
+ "preview": {
103
+ "title": "Vorschau"
104
+ },
105
+ "source": {
106
+ "title": "Quellcode azygä",
107
+ "prettify": "Verschönärä"
108
+ }
109
+ },
110
+ "misc": {
111
+ "source": "Quellcode azygä",
112
+ "preview": "Vorschau"
113
+ },
114
+ "footer": {
115
+ "words": "Wörter",
116
+ "chars": "Zeichä"
117
+ }
118
+ }