Skip to content

Commit

Permalink
Remove access token from asset url when unnecessary (#10548)
Browse files Browse the repository at this point in the history
* Remove access token when unnecessary

* Add missing translations

* Refactor naming convention

* Refactor naming convention

* Refactor access token usage for media upload

* Use static token for previews

* Add missing imageToken for image upload

* Rename imageToken to staticAccessToken

* Remove temporary access token from embedUrl

* Catch invalid URLs when replacing token

* Fix embedUrl not updating when source changed

* Patch audio and iframe uploads

* Hide tinymce offscreen selection

* Fix merge of interface fields

* Use static tokens except for preview

* Remove dirty checks through v-model for wysiwyg

* Add missing translations

* Fix lint warnings

* Revert naming from staticAccessToken to imageToken
  • Loading branch information
licitdev committed Feb 16, 2022
1 parent 968d15c commit b3f7bd9
Show file tree
Hide file tree
Showing 8 changed files with 117 additions and 114 deletions.
8 changes: 7 additions & 1 deletion app/src/interfaces/input-rich-text-html/get-editor-styles.ts
Expand Up @@ -39,6 +39,9 @@ body.mce-content-readonly {
color: ${cssVar('--foreground-subdued')};
background-color: ${cssVar('--background-subdued')};
}
.mce-offscreen-selection {
display: none;
}
h1, h2, h3, h4, h5, h6 {
font-family: ${cssVar(`--family-${font}`)}, serif;
color: ${cssVar('--foreground-normal-alt')};
Expand Down Expand Up @@ -135,12 +138,15 @@ blockquote {
margin-left: 0px;
}
video,
iframe,
img {
max-width: 100%;
border-radius: ${cssVar('--border-radius')};
height: auto;
}
iframe {
max-width: 100%;
border-radius: ${cssVar('--border-radius')};
}
hr {
background-color: ${cssVar('--border-normal')};
height: 1px;
Expand Down
75 changes: 21 additions & 54 deletions app/src/interfaces/input-rich-text-html/input-rich-text-html.vue
Expand Up @@ -6,7 +6,6 @@
:init="editorOptions"
:disabled="disabled"
model-events="change keydown blur focus paste ExecCommand SetContent"
@dirty="setDirty"
@focusin="setFocus(true)"
@focusout="setFocus(false)"
/>
Expand Down Expand Up @@ -60,7 +59,7 @@
<interface-input-code
:value="code"
language="htmlmixed"
line-wrapping="true"
:line-wrapping="true"
@input="code = $event"
></interface-input-code>
</div>
Expand Down Expand Up @@ -117,9 +116,14 @@
<v-tabs-items v-model="openMediaTab">
<v-tab-item value="video">
<template v-if="mediaSelection">
<video class="media-preview" controls="controls">
<source :src="mediaSelection.source" />
<video v-if="mediaSelection.tag !== 'iframe'" class="media-preview" controls="controls">
<source :src="mediaSelection.previewUrl" />
</video>
<iframe
v-if="mediaSelection.tag === 'iframe'"
class="media-preview"
:src="mediaSelection.previewUrl"
></iframe>
<div class="grid">
<div class="field">
<div class="type-label">{{ t('source') }}</div>
Expand Down Expand Up @@ -182,13 +186,10 @@ import 'tinymce/icons/default';
import Editor from '@tinymce/tinymce-vue';
import getEditorStyles from './get-editor-styles';
import { escapeRegExp } from 'lodash';
import useImage from './useImage';
import useMedia from './useMedia';
import useLink from './useLink';
import useSourceCode from './useSourceCode';
import { getToken } from '@/api';
import { getPublicURL } from '@/utils/get-root-path';
import { percentage } from '@/utils/percentage';
type CustomFormat = {
Expand Down Expand Up @@ -265,13 +266,13 @@ export default defineComponent({
const { t } = useI18n();
const editorRef = ref<any | null>(null);
const editorElement = ref<ComponentPublicInstance | null>(null);
const isEditorDirty = ref(false);
const { imageToken } = toRefs(props);
let tinymceEditor: HTMLElement | null;
let count = ref(0);
onMounted(() => {
let iframe;
let contentLoaded = false;
const wysiwyg = document.getElementById(props.field);
if (wysiwyg) iframe = wysiwyg.getElementsByTagName('iframe');
Expand All @@ -282,6 +283,11 @@ export default defineComponent({
if (tinymceEditor) {
const observer = new MutationObserver((_mutations) => {
count.value = tinymceEditor?.textContent?.replace('\n', '')?.length ?? 0;
if (!contentLoaded) {
contentLoaded = true;
} else {
emit('input', editorRef.value.getContent());
}
});
const config = { characterData: true, childList: true, subtree: true };
Expand All @@ -291,7 +297,6 @@ export default defineComponent({
const { imageDrawerOpen, imageSelection, closeImageDrawer, onImageSelect, saveImage, imageButton } = useImage(
editorRef,
isEditorDirty,
imageToken
);
Expand All @@ -307,51 +312,18 @@ export default defineComponent({
mediaWidth,
mediaSource,
mediaButton,
} = useMedia(editorRef, isEditorDirty, imageToken);
} = useMedia(editorRef, imageToken);
const { linkButton, linkDrawerOpen, closeLinkDrawer, saveLink, linkSelection } = useLink(editorRef, isEditorDirty);
const { linkButton, linkDrawerOpen, closeLinkDrawer, saveLink, linkSelection } = useLink(editorRef);
const { codeDrawerOpen, code, closeCodeDrawer, saveCode, sourceCodeButton } = useSourceCode(
editorRef,
isEditorDirty
);
const replaceTokens = (value: string, token: string | null) => {
const url = getPublicURL();
const regex = new RegExp(
`(<[^]+?=")(${escapeRegExp(
url
)}assets/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}(?:\\?[^#"]*)?(?:#[^"]*)?)("[^>]*>)`,
'gi'
);
return value.replace(regex, (_, pre, matchedUrl, post) => {
const matched = new URL(matchedUrl.replace(/&amp;/g, '&'));
const params = new URLSearchParams(matched.search);
if (!token) {
params.delete('access_token');
} else {
params.set('access_token', token);
}
const paramsString = params.toString().length > 0 ? `?${params.toString().replace(/&/g, '&amp;')}` : '';
return `${pre}${matched.origin}${matched.pathname}${paramsString}${post}`;
});
};
const { codeDrawerOpen, code, closeCodeDrawer, saveCode, sourceCodeButton } = useSourceCode(editorRef);
const internalValue = computed({
get() {
if (!props.value) return '';
return replaceTokens(props.value, getToken());
return props.value || '';
},
set(newValue: string) {
if (!isEditorDirty.value) return;
if (newValue !== props.value && (props.value === null && newValue === '') === false) {
const removeToken = replaceTokens(newValue, props.imageToken ?? null);
emit('input', removeToken);
}
set() {
return;
},
});
Expand Down Expand Up @@ -390,7 +362,7 @@ export default defineComponent({
menubar: false,
convert_urls: false,
image_dimensions: false,
extended_valid_elements: 'audio[loop],source',
extended_valid_elements: 'audio[loop|controls],source',
toolbar: toolbarString,
style_formats: styleFormats,
file_picker_types: 'customImage customMedia image media',
Expand All @@ -410,7 +382,6 @@ export default defineComponent({
editorOptions,
internalValue,
setFocus,
setDirty,
onImageSelect,
saveImage,
imageDrawerOpen,
Expand Down Expand Up @@ -447,10 +418,6 @@ export default defineComponent({
editor.ui.registry.addButton('customCode', sourceCodeButton);
}
function setDirty() {
isEditorDirty.value = true;
}
function setFocus(val: boolean) {
if (editorElement.value == null) return;
const body = editorElement.value.$el.parentElement?.querySelector('.tox-tinymce');
Expand Down
40 changes: 29 additions & 11 deletions app/src/interfaces/input-rich-text-html/useImage.ts
@@ -1,4 +1,4 @@
import { addTokenToURL } from '@/api';
import { getToken } from '@/api';
import { i18n } from '@/lang';
import { addQueryToPath } from '@/utils/add-query-to-path';
import { getPublicURL } from '@/utils/get-root-path';
Expand Down Expand Up @@ -28,11 +28,7 @@ type UsableImage = {
imageButton: ImageButton;
};

export default function useImage(
editor: Ref<any>,
isEditorDirty: Ref<boolean>,
imageToken: Ref<string | undefined>
): UsableImage {
export default function useImage(editor: Ref<any>, imageToken: Ref<string | undefined>): UsableImage {
const imageDrawerOpen = ref(false);
const imageSelection = ref<ImageSelection | null>(null);

Expand All @@ -59,7 +55,7 @@ export default function useImage(
alt,
width,
height,
previewUrl: imageUrl,
previewUrl: replaceUrlAccessToken(imageUrl, imageToken.value ?? getToken()),
};
} else {
imageSelection.value = null;
Expand All @@ -86,14 +82,14 @@ export default function useImage(
}

function onImageSelect(image: Record<string, any>) {
const imageUrl = addTokenToURL(getPublicURL() + 'assets/' + image.id, imageToken.value);
const assetUrl = getPublicURL() + 'assets/' + image.id;

imageSelection.value = {
imageUrl,
imageUrl: replaceUrlAccessToken(assetUrl, imageToken.value),
alt: image.title,
width: image.width,
height: image.height,
previewUrl: imageUrl,
previewUrl: replaceUrlAccessToken(assetUrl, imageToken.value ?? getToken()),
};
}

Expand All @@ -105,8 +101,30 @@ export default function useImage(
...(img.height ? { height: img.height.toString() } : {}),
});
const imageHtml = `<img src="${resizedImageUrl}" alt="${img.alt}" />`;
isEditorDirty.value = true;
editor.value.selection.setContent(imageHtml);
closeImageDrawer();
}

function replaceUrlAccessToken(url: string, token: string | null | undefined): string {
// Only process assets URL
if (!url.includes(getPublicURL() + 'assets/')) {
return url;
}
try {
const parsedUrl = new URL(url);
const params = new URLSearchParams(parsedUrl.search);

if (!token) {
params.delete('access_token');
} else {
params.set('access_token', token);
}

return Array.from(params).length > 0
? `${parsedUrl.origin}${parsedUrl.pathname}?${params.toString()}`
: `${parsedUrl.origin}${parsedUrl.pathname}`;
} catch {
return url;
}
}
}
3 changes: 1 addition & 2 deletions app/src/interfaces/input-rich-text-html/useLink.ts
Expand Up @@ -23,7 +23,7 @@ type UsableLink = {
linkButton: LinkButton;
};

export default function useLink(editor: Ref<any>, isEditorDirty: Ref<boolean>): UsableLink {
export default function useLink(editor: Ref<any>): UsableLink {
const linkDrawerOpen = ref(false);
const defaultLinkSelection = {
url: null,
Expand Down Expand Up @@ -94,7 +94,6 @@ export default function useLink(editor: Ref<any>, isEditorDirty: Ref<boolean>):
link.displayText || link.url
}</a>`;

isEditorDirty.value = true;
editor.value.selection.setContent(linkHtml);
closeLinkDrawer();
}
Expand Down

0 comments on commit b3f7bd9

Please sign in to comment.