tiptapify 0.0.28 → 0.0.30

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "tiptapify",
3
3
  "types": "./index.d.ts",
4
- "version": "0.0.28",
4
+ "version": "0.0.30",
5
5
  "description": "Tiptap3 editor with Vuetify3 menu implementation",
6
6
  "exports": {
7
7
  ".": {
@@ -1,6 +1,7 @@
1
1
  import { default as EmojiButton } from "@tiptapify/extensions/components/media/emoji/Button.vue";
2
2
  import { default as LinkButton } from "@tiptapify/extensions/components/media/link/Button.vue";
3
3
  import { default as ImageButton } from "@tiptapify/extensions/components/media/image/Button.vue";
4
+ import { default as IframeButton } from "@tiptapify/extensions/components/media/iframe/Button.vue";
4
5
  import { default as TableButton } from "@tiptapify/extensions/components/media/table/Button.vue";
5
6
  import { default as VideoButton } from "@tiptapify/extensions/components/media/video/Button.vue";
6
7
  import { markRaw } from "vue";
@@ -21,6 +22,10 @@ export default {
21
22
  name: 'video',
22
23
  component: markRaw(VideoButton),
23
24
  },
25
+ {
26
+ name: 'iframe',
27
+ component: markRaw(IframeButton),
28
+ },
24
29
  {
25
30
  name: 'emoji',
26
31
  component: markRaw(EmojiButton),
@@ -1,7 +1,7 @@
1
1
  <script lang="ts" setup>
2
2
 
3
3
  import * as mdi from "@mdi/js";
4
- import { inject, nextTick, ref, watch } from "vue";
4
+ import { inject, nextTick, onBeforeUnmount, onMounted, ref, watch } from "vue";
5
5
 
6
6
  const props = defineProps({
7
7
  module: String,
@@ -100,6 +100,42 @@ watch(() => dialog.value, async () => {
100
100
  dragElement(movableHandler.value.$el as HTMLElement, movableHandler.value.$el.parentNode as HTMLElement)
101
101
  }
102
102
  })
103
+
104
+ function resizeListener() {
105
+ if (!dialog.value) {
106
+ return
107
+ }
108
+
109
+ const dialogCoordinates = movableHandler.value.$el.parentNode.getBoundingClientRect();
110
+
111
+ function checkBoundLeftTop(type) {
112
+ if (dialogCoordinates[type] < 0) {
113
+ const dialogOffset = movableHandler.value.$el.parentNode.style[type]
114
+ const newOffset = parseInt(dialogOffset) - dialogCoordinates[type]
115
+ movableHandler.value.$el.parentNode.style[type] = `${newOffset}px`
116
+ }
117
+ }
118
+ checkBoundLeftTop('left')
119
+ checkBoundLeftTop('top')
120
+
121
+ function checkBoundRightBottom(type: 'right' | 'bottom') {
122
+ const data = { side: type === 'right' ? 'left' : 'top', dim: type === 'right' ? 'width' : 'height', windowDim: type === 'right' ? 'innerWidth' : 'innerHeight' }
123
+ if ((dialogCoordinates[data.side] + dialogCoordinates[data.dim]) > window[data.windowDim]) {
124
+ const newOffset = ((window[data.windowDim] - dialogCoordinates[data.dim]) / 2).toFixed()
125
+ movableHandler.value.$el.parentNode.style[data.side] = `${newOffset}px`
126
+ }
127
+ }
128
+ checkBoundRightBottom('right')
129
+ checkBoundRightBottom('bottom')
130
+ }
131
+
132
+ onMounted(() => {
133
+ addEventListener('resize', resizeListener)
134
+ })
135
+
136
+ onBeforeUnmount(() => {
137
+ removeEventListener('resize', resizeListener)
138
+ })
103
139
  </script>
104
140
 
105
141
  <template>
@@ -25,9 +25,10 @@ import { TableKit } from '@tiptap/extension-table'
25
25
  import { CodeBlockLowlight } from '@tiptap/extension-code-block-lowlight'
26
26
  import { InvisibleCharacters } from '@tiptap/extension-invisible-characters'
27
27
 
28
+ import { default as Iframe } from '@tiptapify/extensions/components/media/iframe'
28
29
  import { BulletListCircle, BulletListSquare } from '@tiptapify/extensions/components/list/bullet'
29
- import { TiptapifyLink } from '@tiptapify/extensions/components/media/link'
30
30
  import { TiptapifyImage } from '@tiptapify/extensions/components/media/image'
31
+ import { TiptapifyLink } from '@tiptapify/extensions/components/media/link'
31
32
  import { TiptapifyVideo } from '@tiptapify/extensions/components/media/video'
32
33
  import CodeBlockComponent from '@tiptapify/extensions/components/CodeBlockComponent.vue'
33
34
  import SlashCommands from '@tiptapify/extensions/slash-commands'
@@ -84,6 +85,7 @@ export function editorExtensions (placeholder: string, slashCommands: boolean, c
84
85
  }),
85
86
  TiptapifyImage,
86
87
  TiptapifyVideo,
88
+ Iframe,
87
89
  Superscript,
88
90
  Subscript,
89
91
  TableKit,
@@ -54,9 +54,9 @@ checkButtonOrMenu()
54
54
 
55
55
  function toggleList(listType: string) {
56
56
  switch(listType) {
57
- case 'disc': editor.value.commands.toggleBulletList(); break;
58
- case 'circle': editor.value.commands.toggleBulletListCircle(); break;
59
- case 'square': editor.value.commands.toggleBulletListSquare(); break;
57
+ case 'bulletList': editor.value.commands.toggleBulletList(); break;
58
+ case 'bulletListCircle': editor.value.commands.toggleBulletListCircle(); break;
59
+ case 'bulletListSquare': editor.value.commands.toggleBulletListSquare(); break;
60
60
  }
61
61
  }
62
62
 
@@ -68,8 +68,10 @@ const buttonActive = computed(() => {
68
68
 
69
69
  const buttonDisabled = computed(() => {
70
70
  return !editor.value.can().chain().focus().toggleBulletList().run() &&
71
- !editor.value.can().chain().focus().toggleBulletListCircle().run() &&
72
- !editor.value.can().chain().focus().toggleBulletListSquare().run()
71
+ !editor.value.can().chain().focus().toggleBulletListCircle().run() &&
72
+ !editor.value.can().chain().focus().toggleBulletListSquare().run() &&
73
+ !editor.value.can().chain().focus().toggleOrderedList().run() &&
74
+ !editor.value.can().chain().focus().toggleTaskList().run()
73
75
  })
74
76
 
75
77
  watch(() => bulletLists.value, () => {
@@ -100,7 +102,7 @@ watch(() => bulletLists.value, () => {
100
102
  :key="key"
101
103
  link
102
104
  :active="editor.isActive(bulletList.name)"
103
- @click="toggleList(key)"
105
+ @click="toggleList(bulletList.name)"
104
106
  >
105
107
  <VTooltip activator="parent">
106
108
  {{ t('lists.bullet') }}
@@ -3,7 +3,7 @@
3
3
  import * as mdi from '@mdi/js'
4
4
  import { Editor } from "@tiptap/vue-3";
5
5
  import BtnIcon from "@tiptapify/components/UI/BtnIcon.vue";
6
- import { inject, Ref } from "vue";
6
+ import { computed, inject, Ref } from "vue";
7
7
 
8
8
  import defaults from '@tiptapify/constants/defaults'
9
9
 
@@ -15,12 +15,19 @@ const editor = inject('tiptapifyEditor') as Ref<Editor>
15
15
 
16
16
  const { t } = inject('tiptapifyI18n') as any
17
17
 
18
+ const buttonDisabled = computed(() => {
19
+ return !editor.value.can().chain().focus().toggleBulletList().run() &&
20
+ !editor.value.can().chain().focus().toggleBulletListCircle().run() &&
21
+ !editor.value.can().chain().focus().toggleBulletListSquare().run() &&
22
+ !editor.value.can().chain().focus().toggleOrderedList().run() &&
23
+ !editor.value.can().chain().focus().toggleTaskList().run()
24
+ })
18
25
  </script>
19
26
 
20
27
  <template>
21
28
  <VBtn
22
29
  :color="editor.isActive('orderedList') ? 'primary' : ''"
23
- :disabled="!editor.can().chain().focus().toggleOrderedList().run()"
30
+ :disabled="buttonDisabled"
24
31
  :variant="variantBtn"
25
32
  @click="editor.commands.toggleOrderedList()"
26
33
  size="32"
@@ -3,7 +3,7 @@
3
3
  import * as mdi from '@mdi/js'
4
4
  import { Editor } from "@tiptap/vue-3";
5
5
  import BtnIcon from "@tiptapify/components/UI/BtnIcon.vue";
6
- import { inject, Ref } from "vue";
6
+ import { computed, inject, Ref } from "vue";
7
7
 
8
8
  import defaults from '@tiptapify/constants/defaults'
9
9
 
@@ -15,12 +15,19 @@ const editor = inject('tiptapifyEditor') as Ref<Editor>
15
15
 
16
16
  const { t } = inject('tiptapifyI18n') as any
17
17
 
18
+ const buttonDisabled = computed(() => {
19
+ return !editor.value.can().chain().focus().toggleBulletList().run() &&
20
+ !editor.value.can().chain().focus().toggleBulletListCircle().run() &&
21
+ !editor.value.can().chain().focus().toggleBulletListSquare().run() &&
22
+ !editor.value.can().chain().focus().toggleOrderedList().run() &&
23
+ !editor.value.can().chain().focus().toggleTaskList().run()
24
+ })
18
25
  </script>
19
26
 
20
27
  <template>
21
28
  <VBtn
22
29
  :color="editor.isActive('taskList') ? 'primary' : ''"
23
- :disabled="!editor.can().chain().focus().toggleTaskList().run()"
30
+ :disabled="buttonDisabled"
24
31
  :variant="variantBtn"
25
32
  @click="editor.commands.toggleTaskList()"
26
33
  size="32"
@@ -0,0 +1,42 @@
1
+ <script lang="ts" setup>
2
+
3
+ import { Editor } from "@tiptap/vue-3";
4
+ import BtnIcon from "@tiptapify/components/UI/BtnIcon.vue";
5
+ import IframeDialog from "@tiptapify/extensions/components/media/iframe/IframeDialog.vue";
6
+ import { inject, ref, Ref } from "vue";
7
+
8
+ import defaults from '@tiptapify/constants/defaults'
9
+
10
+ defineProps({
11
+ variantBtn: { type: String, default: defaults.variantBtn }
12
+ })
13
+
14
+ const editor = inject('tiptapifyEditor') as Ref<Editor>
15
+
16
+ const { t } = inject('tiptapifyI18n') as any
17
+
18
+ const dialog = ref(null)
19
+
20
+ const icon = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M1 12h9.8L8.3 9.5l1.4-1.4l4.9 4.9l-4.9 4.9l-1.4-1.4l2.5-2.5H1zM21 2H3c-1.1 0-2 .9-2 2v6.1h2V6h18v14H3v-4H1v4c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2"/></svg>'
21
+ </script>
22
+
23
+ <template>
24
+ <VBtn
25
+ :color="editor.isActive('iframe') ? 'primary' : ''"
26
+ :variant="variantBtn"
27
+ @click="dialog?.open()"
28
+ size="32"
29
+ >
30
+ <VTooltip activator="parent">
31
+ {{ t('media.iframe') }}
32
+ </VTooltip>
33
+ <BtnIcon :icon="icon" />
34
+ </VBtn>
35
+
36
+ <IframeDialog ref="dialog" />
37
+ <!-- Dialog here -->
38
+ </template>
39
+
40
+ <style lang="scss" scoped>
41
+
42
+ </style>
@@ -0,0 +1,192 @@
1
+ <script setup lang="ts">
2
+
3
+ import { Editor } from "@tiptap/vue-3";
4
+ import TiptapifyDialog from "@tiptapify/components/UI/TiptapifyDialog.vue";
5
+ import defaults from "@tiptapify/constants/defaults";
6
+
7
+ import type { iframeOptions } from '@tiptapify/types/iframe'
8
+
9
+ import { computed, inject, Ref, ref, watch } from 'vue'
10
+
11
+ defineProps({
12
+ variantBtn: { type: String, default() { return defaults.variantBtn }},
13
+ variantField: { type: String, default() { return defaults.variantField }}
14
+ })
15
+
16
+ const editor = inject('tiptapifyEditor') as Ref<Editor>
17
+ const { t } = inject('tiptapifyI18n') as any
18
+
19
+ const generateIframeAttrs = (): iframeOptions => ({
20
+ src: '',
21
+ name: '',
22
+ title: '',
23
+ height: null,
24
+ width: null,
25
+ frameborder: 0,
26
+ })
27
+
28
+ const attrs = ref(generateIframeAttrs())
29
+ const srcInvalid = ref(false)
30
+
31
+ const dialog = ref(null)
32
+
33
+ const isDisabled = computed(() => {
34
+ const { src } = attrs.value
35
+ return !src
36
+ })
37
+
38
+ function apply() {
39
+ let { src, name, title, width, height, frameborder } = attrs.value
40
+
41
+ const options: iframeOptions = {
42
+ src,
43
+ name,
44
+ title,
45
+ }
46
+
47
+ if (width) {
48
+ options.width = width
49
+ }
50
+
51
+ if (height) {
52
+ options.height = height
53
+ }
54
+
55
+ if (frameborder) {
56
+ options.frameborder = Number(frameborder)
57
+ }
58
+
59
+ if (src) {
60
+ editor.value.chain().focus().setIframe(options).run()
61
+ }
62
+
63
+ close()
64
+ }
65
+
66
+ function clear() {
67
+ editor.value.commands.deleteSelection()
68
+
69
+ close()
70
+ }
71
+
72
+ function close() {
73
+ dialog.value.close()
74
+
75
+ attrs.value = generateIframeAttrs()
76
+ }
77
+
78
+ function open () {
79
+ const iframe = editor.value.getAttributes('iframe')
80
+
81
+ attrs.value.src = iframe?.src
82
+ attrs.value.name = iframe?.name
83
+ attrs.value.title = iframe?.title
84
+ attrs.value.width = iframe?.width
85
+ attrs.value.height = iframe?.height
86
+ attrs.value.frameborder = !!iframe?.frameborder
87
+
88
+ dialog.value.open()
89
+ }
90
+
91
+ defineExpose({ open })
92
+
93
+ watch(() => attrs.value.src, () => {
94
+ const azAZ09 = 'a-zA-Z0-9'
95
+
96
+ const regexHrefProto = 'https?:\\/\\/'
97
+ const regexHrefDomain = `[${azAZ09}]+(\\.[${azAZ09}]+(-?[${azAZ09}]+)?)+`
98
+ const regexHrefPath = `(\\/[${azAZ09}\\-]+)*`
99
+ const regexHrefFragment = '(#[^\\s]*)?'
100
+ const regexHrefQueryParam = `(\\?[${azAZ09}\\-_]+((\\[[${azAZ09}]+\\])?=[${azAZ09}\\-_%]+)?)?`
101
+ const regexHrefQueryParamExtra = `(&[${azAZ09}\\-_]+((\\[[${azAZ09}]+\\])?=[${azAZ09}\\-_%]+)?)*`
102
+ const regexHref = `${regexHrefProto}${regexHrefDomain}${regexHrefPath}${regexHrefFragment}${regexHrefQueryParam}${regexHrefQueryParamExtra}`
103
+
104
+ const regex = new RegExp(`^(${regexHref})$`, 'i')
105
+
106
+ srcInvalid.value = (attrs.value.src || '') !== '' && !regex.test(attrs.value.src)
107
+ })
108
+ </script>
109
+
110
+ <template>
111
+ <TiptapifyDialog ref="dialog" module="iframe" :title="t('dialog.iframe.dialog_title')" :max-width="800">
112
+ <template #content>
113
+ <VCardText>
114
+ <VRow>
115
+ <VCol cols="12">
116
+ <VTextField
117
+ v-model="attrs.src"
118
+ density="compact"
119
+ variant="outlined"
120
+ :label="t('dialog.iframe.src')"
121
+ :error-messages="srcInvalid ? t('dialog.iframe.src_invalid') : ''"
122
+ />
123
+ </VCol>
124
+
125
+ <VCol cols="12" md="6">
126
+ <VRow>
127
+ <VCol cols="12">
128
+ <VTextField v-model="attrs.name" density="compact" variant="outlined" :label="t('dialog.iframe.name')" />
129
+ </VCol>
130
+
131
+ <VCol cols="12">
132
+ <VTextField v-model="attrs.title" density="compact" variant="outlined" :label="t('dialog.iframe.title')" />
133
+ </VCol>
134
+ </VRow>
135
+ </VCol>
136
+
137
+ <VCol cols="12" md="6">
138
+ <VRow>
139
+ <VCol cols="12" md="6">
140
+ <VTextField
141
+ v-model="attrs.width"
142
+ type="number"
143
+ density="compact"
144
+ variant="outlined"
145
+ :precision="0"
146
+ :min="1"
147
+ :label="t('dialog.iframe.width')"
148
+ />
149
+ </VCol>
150
+
151
+ <VCol cols="12" md="6">
152
+ <VTextField
153
+ v-model="attrs.height"
154
+ type="number"
155
+ density="compact"
156
+ variant="outlined"
157
+ :precision="0"
158
+ :min="1"
159
+ :label="t('dialog.iframe.height')"
160
+ />
161
+ </VCol>
162
+
163
+ <VCol cols="12" class="ml-2">
164
+ <VSwitch v-model="attrs.frameborder" density="compact" :hideDetails="true" :label="t('dialog.iframe.frameborder')" />
165
+ </VCol>
166
+ </VRow>
167
+ </VCol>
168
+ </VRow>
169
+ </VCardText>
170
+ </template>
171
+
172
+ <template #actions>
173
+ <VCardActions>
174
+ <VRow>
175
+ <VCol class="d-flex justify-start">
176
+ <VBtn color="warning" v-if="editor.isActive('image')" :variant="variantBtn" :disabled="isDisabled" @click="clear">
177
+ {{ t('dialog.clear') }}
178
+ </VBtn>
179
+ </VCol>
180
+ <VCol class="d-flex justify-end">
181
+ <VBtn :variant="variantBtn" @click="close" class="mr-2">
182
+ {{ t('dialog.close') }}
183
+ </VBtn>
184
+ <VBtn color="primary" :variant="variantBtn" :disabled="isDisabled" @click="apply">
185
+ {{ t('dialog.apply') }}
186
+ </VBtn>
187
+ </VCol>
188
+ </VRow>
189
+ </VCardActions>
190
+ </template>
191
+ </TiptapifyDialog>
192
+ </template>
@@ -0,0 +1,92 @@
1
+ import { Node } from '@tiptap/core'
2
+
3
+ export interface IframeOptions {
4
+ allowFullscreen: boolean
5
+ HTMLAttributes: {
6
+ [key: string]: any
7
+ }
8
+ }
9
+
10
+ declare module '@tiptap/core' {
11
+ interface Commands<ReturnType> {
12
+ iframe: {
13
+ /**
14
+ * Add an iframe
15
+ */
16
+ setIframe: (options: { src: string }) => ReturnType
17
+ }
18
+ }
19
+ }
20
+
21
+ export default Node.create<IframeOptions>({
22
+ name: 'iframe',
23
+
24
+ group: 'block',
25
+
26
+ atom: true,
27
+
28
+ addOptions() {
29
+ return {
30
+ allowFullscreen: true,
31
+ HTMLAttributes: {
32
+ class: 'iframe-wrapper',
33
+ },
34
+ }
35
+ },
36
+
37
+ addAttributes() {
38
+ return {
39
+ src: {
40
+ default: null,
41
+ },
42
+ name: {
43
+ default: null,
44
+ },
45
+ title: {
46
+ default: null,
47
+ },
48
+ width: {
49
+ default: null,
50
+ },
51
+ height: {
52
+ default: null,
53
+ },
54
+ frameborder: {
55
+ default: 0,
56
+ },
57
+ allowfullscreen: {
58
+ default: this.options.allowFullscreen,
59
+ parseHTML: () => this.options.allowFullscreen,
60
+ },
61
+ }
62
+ },
63
+
64
+ parseHTML() {
65
+ return [
66
+ {
67
+ tag: 'iframe',
68
+ },
69
+ ]
70
+ },
71
+
72
+ renderHTML({ HTMLAttributes }) {
73
+ return ['div', this.options.HTMLAttributes, ['iframe', HTMLAttributes]]
74
+ },
75
+
76
+ addCommands() {
77
+ return {
78
+ setIframe:
79
+ (options: { src: string }) =>
80
+ ({ tr, dispatch }) => {
81
+ const { selection } = tr
82
+ const node = this.type.create(options)
83
+
84
+ if (dispatch) {
85
+ tr.replaceRangeWith(selection.from, selection.to, node)
86
+ }
87
+
88
+ return true
89
+ },
90
+ }
91
+ },
92
+ })
@@ -102,7 +102,7 @@ watch(() => attrs.value.href, () => {
102
102
 
103
103
  const regex = new RegExp(`^(${regexAll})$`, 'i')
104
104
 
105
- hrefInvalid.value = attrs.value.href !== '' && !regex.test(attrs.value.href)
105
+ hrefInvalid.value = (attrs.value.href || '') !== '' && !regex.test(attrs.value.href)
106
106
  })
107
107
  </script>
108
108
 
@@ -44,7 +44,7 @@ async function changeEditorContainer(source: string, target: string) {
44
44
  <template>
45
45
  <VBtn @click="dialog ? dialogClose() : dialogOpen()" size="32" :variant="variantBtn">
46
46
  <VTooltip activator="parent">
47
- {{ t('misc.preview') }}
47
+ {{ t('misc.fullscreen') }}
48
48
  </VTooltip>
49
49
  <BtnIcon :icon="dialog ? `mdiSvg:${mdi.mdiFullscreenExit}` : `mdiSvg:${mdi.mdiFullscreen}`" />
50
50
  </VBtn>
@@ -95,6 +95,16 @@
95
95
  "apply": "应用",
96
96
  "clear": "清除",
97
97
  "close": "关闭",
98
+ "iframe": {
99
+ "dialog_title": "添加/编辑内嵌框架",
100
+ "src": "源地址",
101
+ "name": "名称",
102
+ "title": "标题",
103
+ "width": "宽度",
104
+ "height": "高度",
105
+ "frameborder": "框架边框",
106
+ "src_invalid": "输入的URL无效"
107
+ },
98
108
  "image": {
99
109
  "title": "添加/修改图片",
100
110
  "src": "源",
@@ -127,8 +137,9 @@
127
137
  }
128
138
  },
129
139
  "misc": {
130
- "source": "查看源代码",
140
+ "fullscreen": "全屏模式",
131
141
  "preview": "预览",
142
+ "source": "查看源代码",
132
143
  "toggleInvisibleCharacters": "显示/隐藏不可见字符"
133
144
  },
134
145
  "footer": {
@@ -95,6 +95,16 @@
95
95
  "apply": "Použít",
96
96
  "clear": "Vymazat",
97
97
  "close": "Zavřít",
98
+ "iframe": {
99
+ "dialog_title": "Přidat/upravit iframe",
100
+ "src": "Zdroj",
101
+ "name": "Název",
102
+ "title": "Titulek",
103
+ "width": "Šířka",
104
+ "height": "Výška",
105
+ "frameborder": "Okraj rámce",
106
+ "src_invalid": "Zadaná URL adresa je neplatná"
107
+ },
98
108
  "image": {
99
109
  "title": "Přidání/úprava obrázku",
100
110
  "src": "Zdroj",
@@ -127,8 +137,9 @@
127
137
  }
128
138
  },
129
139
  "misc": {
130
- "source": "Zobrazit zdrojový kód",
140
+ "fullscreen": "Režim celé obrazovky",
131
141
  "preview": "Náhled",
142
+ "source": "Zobrazit zdrojový kód",
132
143
  "toggleInvisibleCharacters": "Zobrazit/skrýt neviditelné znaky"
133
144
  },
134
145
  "footer": {
@@ -95,6 +95,16 @@
95
95
  "apply": "Anwenden",
96
96
  "clear": "Löschen",
97
97
  "close": "Schließen",
98
+ "iframe": {
99
+ "dialog_title": "Iframe hinzufügen/bearbeiten",
100
+ "src": "Quelle",
101
+ "name": "Name",
102
+ "title": "Titel",
103
+ "width": "Breite",
104
+ "height": "Höhe",
105
+ "frameborder": "Rahmenrand",
106
+ "src_invalid": "Die eingegebene URL ist ungültig"
107
+ },
98
108
  "image": {
99
109
  "title": "Bild hinzufügen/ändern",
100
110
  "src": "Quelle",
@@ -127,8 +137,9 @@
127
137
  }
128
138
  },
129
139
  "misc": {
130
- "source": "Quellcode anzeigen",
140
+ "fullscreen": "Vollbildmodus",
131
141
  "preview": "Vorschau",
142
+ "source": "Quellcode anzeigen",
132
143
  "toggleInvisibleCharacters": "Unsichtbare Zeichen anzeigen/ausblenden"
133
144
  },
134
145
  "footer": {
@@ -40,6 +40,7 @@
40
40
  },
41
41
  "media": {
42
42
  "link": "External link",
43
+ "iframe": "iframe",
43
44
  "image": "Image",
44
45
  "emoji": {
45
46
  "title": "Emoji",
@@ -97,6 +98,16 @@
97
98
  "apply": "Apply",
98
99
  "clear": "Clear",
99
100
  "close": "Close",
101
+ "iframe": {
102
+ "dialog_title": "Add/edit iframe",
103
+ "src": "Source",
104
+ "name": "Name",
105
+ "title": "Title",
106
+ "width": "Width",
107
+ "height": "Height",
108
+ "frameborder": "iframe border",
109
+ "src_invalid": "The input is invalid URL"
110
+ },
100
111
  "image": {
101
112
  "title": "Add/edit image",
102
113
  "src": "Source",
@@ -129,8 +140,9 @@
129
140
  }
130
141
  },
131
142
  "misc": {
132
- "source": "View source code",
143
+ "fullscreen": "Fullscreen mode",
133
144
  "preview": "Preview",
145
+ "source": "View source code",
134
146
  "toggleInvisibleCharacters": "Show/hide invisible characters"
135
147
  },
136
148
  "footer": {