Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
:tabindex="tabindex"
role="textbox"
:aria-label="editorMode === 'edit' ? TipTapEditorLabel$() : TipTapViewerLabel$()"
aria-multiline="true"
:aria-multiline="String(!inlineOnly)"
@keydown="handleContainerKeydown"
@focusin="hasFocusWithin = true"
@focusout="handleFocusout"
Expand Down Expand Up @@ -96,6 +96,7 @@
import FormulasMenu from './components/math/FormulasMenu.vue';
import { preprocessMarkdown } from './utils/markdown';
import { resolveImageSrcs, toStoredImageSrcs } from './utils/imageSrc';
import { toInlineHTML } from './utils/inlineContent';
import MobileTopBar from './components/toolbar/MobileTopBar.vue';
import MobileFormattingBar from './components/toolbar/MobileFormattingBar.vue';
import { getTipTapEditorStrings } from './TipTapEditorStrings';
Expand All @@ -118,6 +119,11 @@
provide('isReady', isReady);
provide('insertContext', insertContext);
provide('insertActions', toRef(props, 'insertActions'));
// Read once: the schema is fixed when the editor is created.
const { inlineOnly } = props;
provide('inlineOnly', inlineOnly);
// The markdown serializer writes each inline child of an inline-only doc as a block.
const isHTML = computed(() => props.format === 'html' || inlineOnly);
// The anchored modals are measured and hit-tested through these refs, so that several
// editors mounted at once each work with their own modal.
Expand Down Expand Up @@ -150,7 +156,8 @@
const handleDrop = event => {
const file = event.dataTransfer.files[0];
if (file) {
// An inline-only schema has no image node to insert.
if (file && !inlineOnly) {
imageHandler.openCreateModal({ file });
}
};
Expand All @@ -160,7 +167,7 @@
// Image srcs are resolved for display on the way in, so they are reduced
// back to their stored form here — leaving this the one place that reads
// content out, whichever form the editor happens to be holding.
if (props.format === 'html') return toStoredImageSrcs(editor.value.getHTML());
if (isHTML.value) return toStoredImageSrcs(editor.value.getHTML());
if (!editor.value.storage?.markdown) return '';
return editor.value.storage.markdown.getMarkdown();
};
Expand Down Expand Up @@ -195,13 +202,16 @@
return;
}
const processedContent =
props.format === 'html' ? resolveImageSrcs(newValue) : preprocessMarkdown(newValue);
let processedContent;
if (inlineOnly) processedContent = toInlineHTML(newValue);
else if (isHTML.value) processedContent = resolveImageSrcs(newValue);
else processedContent = preprocessMarkdown(newValue);
if (!editor.value) {
initializeEditor(processedContent, props.mode, {
autofocus: props.autofocus,
extensions: props.extensions,
inlineOnly,
});
return;
}
Expand Down Expand Up @@ -308,6 +318,15 @@
type: Array,
default: () => [],
},
/**
* Holds one line of inline content; blocks and line breaks in the value become
* spaces. Read once, when the editor is created. Reads and writes HTML, whatever
* `format` says.
*/
inlineOnly: {
type: Boolean,
default: false,
},
/**
* Actions appended to the insert tools of every toolbar. Each is
* `{ name, title, icon, handler, isActive?, isAvailable?, prominent? }`:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,14 @@
import { computed, nextTick, onMounted, ref, watch } from 'vue';
import { useEditor } from '../composables/useEditor';
import { resolveInsertAction } from '../composables/useToolbarActions';
import { toInlineHTML } from '../utils/inlineContent';
export default {
name: 'RichTextEditor',
setup(props, { emit, listeners }) {
const input = ref(null);
const { editor, isReady, insertContext, initializeEditor } = useEditor();
const content = value => (props.inlineOnly ? toInlineHTML(value) : value);
// A real editor, so `ready` carries an instance whose schema holds the
// consumer's extensions and whose commands run. Built only for a test that
Expand All @@ -43,7 +45,10 @@
() => Boolean(props.extensions.length || props.insertActions.length || listeners.ready),
needed => {
if (needed && !editor.value) {
initializeEditor(props.value, props.mode, { extensions: props.extensions });
initializeEditor(content(props.value), props.mode, {
extensions: props.extensions,
inlineOnly: props.inlineOnly,
});
}
},
{ immediate: true },
Expand All @@ -55,7 +60,7 @@
watch(
() => props.value,
value => editor.value?.commands.setContent(value || '<p></p>'),
value => editor.value?.commands.setContent(content(value) || '<p></p>'),
);
// The textarea stands in for the editor's view, so focusing it places the
Expand Down Expand Up @@ -106,6 +111,10 @@
type: Array,
default: () => [],
},
inlineOnly: {
type: Boolean,
default: false,
},
},
emits: ['update', 'ready'],
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,17 @@
<ToolbarDivider />

<!-- Format dropdown -->
<div
role="group"
class="formatting-group toolbar-group"
:aria-label="textFormattingOptions$()"
>
<FormatDropdown />
</div>

<ToolbarDivider />
<template v-if="!inlineOnly">
<div
role="group"
class="formatting-group toolbar-group"
:aria-label="textFormattingOptions$()"
>
<FormatDropdown />
</div>

<ToolbarDivider />
</template>

<!-- Collapsible toolbar groups -->
<KListWithOverflow
Expand Down Expand Up @@ -162,7 +164,7 @@

<script>

import { ref, computed } from 'vue';
import { ref, computed, inject } from 'vue';
import { useToolbarActions } from '../composables/useToolbarActions';
import { getTipTapEditorStrings } from '../TipTapEditorStrings';
import { useDropdowns } from '../composables/useDropdowns';
Expand All @@ -183,13 +185,14 @@
setup(props, { emit }) {
const toolbarRef = ref(null);
useRovingTabIndex(toolbarRef);
const inlineOnly = inject('inlineOnly', false);

const {
handleCopy,
handleClearFormat,
canClearFormat,
historyActions,
alignAction,
alignActions,
textActions,
listActions,
insertTools,
Expand Down Expand Up @@ -262,7 +265,7 @@
},
{
name: 'align',
groupActions: [alignAction.value],
groupActions: alignActions.value,
},
{
name: 'clearFormat',
Expand Down Expand Up @@ -342,9 +345,10 @@
*/
const toolbarGroupsWithDividers = computed(() => {
const groups = [];
toolbarGroups.value.forEach((group, index) => {
const nonEmptyGroups = toolbarGroups.value.filter(group => group.groupActions.length);
nonEmptyGroups.forEach((group, index) => {
groups.push(group);
if (index < toolbarGroups.value.length - 1) {
if (index < nonEmptyGroups.length - 1) {
groups.push({ type: 'divider' });
}
});
Expand All @@ -357,6 +361,7 @@

return {
toolbarRef,
inlineOnly,
toolbarGroupsWithDividers,
flatOverflowOptions,
historyActions,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,67 +35,55 @@
@touchstart="event => event.stopPropagation()"
@touchend="event => event.stopPropagation()"
>
<div
class="formatting-buttons"
role="group"
:aria-label="formatSize$()"
>
<button
data-toolbar-item
:aria-disabled="canDecreaseFormat ? 'false' : 'true'"
:title="decreaseFormatSize$()"
:aria-label="decreaseFormatSize$()"
class="format-btn"
@mousedown.prevent
@click="decreaseFormat"
>
-
</button>
<img
src="../../../assets/icon-formatSize.svg"
:alt="formatSize$()"
aria-hidden="true"
<template v-if="!inlineOnly">
<div
class="formatting-buttons"
role="group"
:aria-label="formatSize$()"
>
<button
data-toolbar-item
:aria-disabled="canIncreaseFormat ? 'false' : 'true'"
:title="increaseFormatSize$()"
:aria-label="increaseFormatSize$()"
class="format-btn"
@mousedown.prevent
@click="increaseFormat"
>
+
</button>
</div>

<ToolbarDivider />

<ToolbarButton
v-for="action in textActions"
:key="action.name"
:title="action.title"
:icon="action.icon"
:is-active="action.isActive"
@click="action.handler"
/>
<ToolbarDivider />
<ToolbarButton
v-for="action in listActions"
:key="action.name"
:title="action.title"
:icon="action.icon"
:is-active="action.isActive"
@click="action.handler"
/>
<ToolbarDivider />
<ToolbarButton
:title="alignAction.title"
:icon="alignAction.icon"
:is-active="alignAction.isActive"
@click="alignAction.handler"
/>
<ToolbarDivider />
<button
data-toolbar-item
:aria-disabled="canDecreaseFormat ? 'false' : 'true'"
:title="decreaseFormatSize$()"
:aria-label="decreaseFormatSize$()"
class="format-btn"
@mousedown.prevent
@click="decreaseFormat"
>
-
</button>
<img
src="../../../assets/icon-formatSize.svg"
:alt="formatSize$()"
aria-hidden="true"
>
<button
data-toolbar-item
:aria-disabled="canIncreaseFormat ? 'false' : 'true'"
:title="increaseFormatSize$()"
:aria-label="increaseFormatSize$()"
class="format-btn"
@mousedown.prevent
@click="increaseFormat"
>
+
</button>
</div>

<ToolbarDivider />
</template>

<template v-for="(group, index) in actionGroups">
<ToolbarButton
v-for="action in group"
:key="action.name"
:title="action.title"
:icon="action.icon"
:is-active="action.isActive"
@click="action.handler"
/>
<ToolbarDivider :key="`divider-${index}`" />
</template>
<ToolbarButton
v-for="action in scriptActions"
:key="action.name"
Expand Down Expand Up @@ -123,7 +111,7 @@

<script>
import { defineComponent, ref, onMounted, onUnmounted, inject } from 'vue';
import { defineComponent, ref, computed, onMounted, onUnmounted, inject } from 'vue';
import { useToolbarActions } from '../../composables/useToolbarActions';
import { useFormatControls } from '../../composables/useFormatControls';
import { getTipTapEditorStrings } from '../../TipTapEditorStrings';
Expand All @@ -138,6 +126,7 @@
const isExpanded = ref(true);
const keyboardOffset = ref(0);
const editor = inject('editor');
const inlineOnly = inject('inlineOnly', false);
const toolbarRef = ref(null);
useRovingTabIndex(toolbarRef);
Expand All @@ -151,9 +140,13 @@
textFormattingToolbar$,
} = getTipTapEditorStrings();
const { textActions, listActions, scriptActions, alignAction, insertTools } =
const { textActions, listActions, scriptActions, alignActions, insertTools } =
useToolbarActions(emit);
const actionGroups = computed(() =>
[textActions.value, listActions.value, alignActions.value].filter(group => group.length),
);
const { canIncreaseFormat, canDecreaseFormat, increaseFormat, decreaseFormat } =
useFormatControls();
Expand Down Expand Up @@ -217,12 +210,11 @@
return {
isExpanded,
inlineOnly,
keyboardOffset,
toolbarRef,
textActions,
listActions,
actionGroups,
scriptActions,
alignAction,
insertTools,
toggleToolbar,
canIncreaseFormat,
Expand Down
Loading
Loading