Skip to content

Commit bd34c1b

Browse files
authored
Better fix image rendering in Jira Cloud tickets' description and comments (#105)
* better fix for images * fix typo
1 parent c8b2dc7 commit bd34c1b

File tree

12 files changed

+130
-200
lines changed

12 files changed

+130
-200
lines changed

src/ipc/prActions.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,3 @@ export interface GetImageAction extends Action {
172172
action: 'getImage';
173173
url: string;
174174
}
175-
176-
export function isGetImage(a: Action): a is GetImageAction {
177-
return (<GetImageAction>a).action === 'getImage';
178-
}

src/lib/ipc/fromUI/startWork.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@ export enum StartWorkActionType {
99
ClosePage = 'closePage',
1010
StartRequest = 'startRequest',
1111
OpenSettings = 'openSettings',
12+
GetImage = 'getImage',
1213
}
1314

1415
export type StartWorkAction =
1516
| ReducerAction<StartWorkActionType.ClosePage, {}>
1617
| ReducerAction<StartWorkActionType.StartRequest, StartRequestAction>
1718
| ReducerAction<StartWorkActionType.OpenSettings, OpenSettingsAction>
19+
| ReducerAction<StartWorkActionType.GetImage, GetImageAction>
1820
| CommonAction;
1921

2022
export interface StartRequestAction {
@@ -31,3 +33,9 @@ export interface OpenSettingsAction {
3133
section?: ConfigSection;
3234
subsection?: ConfigSubSection;
3335
}
36+
37+
export interface GetImageAction {
38+
nonce: string;
39+
url: string;
40+
siteDetailsStringified: string;
41+
}

src/lib/webview/controller/startwork/startWorkWebviewController.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import { CommonActionType } from '../../../ipc/fromUI/common';
66
import { StartWorkAction, StartWorkActionType } from '../../../ipc/fromUI/startWork';
77
import { WebViewID } from '../../../ipc/models/common';
88
import { CommonMessage, CommonMessageType } from '../../../ipc/toUI/common';
9+
// eslint-disable-next-line no-restricted-imports
10+
import { Container } from '../../../../container';
911
import {
1012
BranchType,
1113
emptyStartWorkIssueMessage,
@@ -158,6 +160,49 @@ export class StartWorkWebviewController implements WebviewController<StartWorkIs
158160
this.api.openSettings(msg.section, msg.subsection);
159161
break;
160162
}
163+
case StartWorkActionType.GetImage: {
164+
try {
165+
const siteDetails = JSON.parse(msg.siteDetailsStringified);
166+
const baseApiUrl = new URL(
167+
siteDetails.baseApiUrl.slice(0, siteDetails.baseApiUrl.lastIndexOf('/rest')),
168+
);
169+
// Prefix base URL for a relative URL
170+
const href = msg.url.startsWith('/') ? new URL(baseApiUrl.href + msg.url) : new URL(msg.url);
171+
// Skip fetching external images (that do not belong to the site)
172+
if (href.hostname !== baseApiUrl.hostname) {
173+
this.postMessage({
174+
type: 'getImageDone',
175+
imgData: '',
176+
nonce: msg.nonce,
177+
} as any);
178+
}
179+
180+
const url = href.toString();
181+
182+
const client = await Container.clientManager.jiraClient(siteDetails);
183+
const response = await client.transportFactory().get(url, {
184+
method: 'GET',
185+
headers: {
186+
Authorization: await client.authorizationProvider('GET', url),
187+
},
188+
responseType: 'arraybuffer',
189+
});
190+
const imgData = Buffer.from(response.data, 'binary').toString('base64');
191+
this.postMessage({
192+
type: 'getImageDone',
193+
imgData: imgData,
194+
nonce: msg.nonce,
195+
} as any);
196+
} catch (e) {
197+
this.logger.error(new Error(`error fetching image: ${msg.url}`));
198+
this.postMessage({
199+
type: 'getImageDone',
200+
imgData: '',
201+
nonce: msg.nonce,
202+
} as any);
203+
}
204+
break;
205+
}
161206
case CommonActionType.Refresh: {
162207
try {
163208
await this.invalidate();

src/react/atlascode/startwork/StartWorkPage.tsx

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ import { PrepareCommitTip } from '../common/PrepareCommitTip';
4646
import { StartWorkControllerContext, useStartWorkController } from './startWorkController';
4747
import { AtlascodeErrorBoundary } from '../common/ErrorBoundary';
4848
import { AnalyticsView } from 'src/analyticsTypes';
49+
import { RenderedContent } from '../../../webviews/components/RenderedContent';
50+
import { StartWorkAction, StartWorkActionType } from '../../../lib/ipc/fromUI/startWork';
51+
import { OnMessageEventPromise } from '../../../util/reactpromise';
52+
import { ConnectionTimeout } from '../../../util/time';
53+
import uuid from 'uuid';
4954

5055
const useStyles = makeStyles((theme: Theme) => ({
5156
title: {
@@ -308,6 +313,33 @@ const StartWorkPage: React.FunctionComponent = () => {
308313
setTransition(inProgressTransitionGuess);
309314
}, [state.issue]);
310315

316+
const postMessageWithEventPromise = (
317+
send: StartWorkAction,
318+
waitForEvent: string,
319+
timeout: number,
320+
nonce?: string,
321+
): Promise<any> => {
322+
controller.postMessage(send);
323+
return OnMessageEventPromise(waitForEvent, timeout, nonce);
324+
};
325+
326+
const fetchImage = async (url: string): Promise<any> => {
327+
const nonce = uuid.v4();
328+
return (
329+
await postMessageWithEventPromise(
330+
{
331+
type: StartWorkActionType.GetImage,
332+
nonce: nonce,
333+
url: url,
334+
siteDetailsStringified: JSON.stringify(state.issue.siteDetails),
335+
},
336+
'getImageDone',
337+
ConnectionTimeout,
338+
nonce,
339+
)
340+
).imgData;
341+
};
342+
311343
return (
312344
<StartWorkControllerContext.Provider value={controller}>
313345
<AtlascodeErrorBoundary
@@ -380,10 +412,12 @@ const StartWorkPage: React.FunctionComponent = () => {
380412
</Grid>
381413
</Grid>
382414
<Grid item>
383-
<Typography
384-
variant="body2"
385-
dangerouslySetInnerHTML={{ __html: state.issue.descriptionHtml }}
386-
></Typography>
415+
{state.issue.siteDetails.baseApiUrl && (
416+
<RenderedContent
417+
html={state.issue.descriptionHtml}
418+
fetchImage={(url) => fetchImage(url)}
419+
/>
420+
)}
387421
</Grid>
388422
<Grid item>
389423
<Divider />

src/util/html.test.ts

Lines changed: 0 additions & 75 deletions
This file was deleted.

src/util/html.ts

Lines changed: 0 additions & 15 deletions
This file was deleted.

src/webviews/components/WebviewComponent.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import * as React from 'react';
22
import { Action, LegacyPMFData } from '../../ipc/messaging';
33
import { OnMessageEventPromise } from '../../util/reactpromise';
4+
import { ConnectionTimeout } from '../../util/time';
45
import { darken, lighten, opacity } from './colors';
6+
import uuid from 'uuid';
57

68
interface VsCodeApi {
79
postMessage(msg: {}): void;
@@ -113,4 +115,20 @@ export abstract class WebviewComponent<A extends Action, R, P, S> extends React.
113115
this._api.postMessage(send);
114116
return OnMessageEventPromise(waitForEvent, timeout, nonce);
115117
}
118+
119+
protected async fetchImage(url: string): Promise<any> {
120+
const nonce = uuid.v4();
121+
return (
122+
await this.postMessageWithEventPromise(
123+
{
124+
action: 'getImage',
125+
nonce: nonce,
126+
url: url,
127+
},
128+
'getImageDone',
129+
ConnectionTimeout,
130+
nonce,
131+
)
132+
).imgData;
133+
}
116134
}

src/webviews/components/issue/AbstractIssueEditorPage.tsx

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ import {
4040
} from '../../../ipc/issueMessaging';
4141
import { Action, HostErrorMessage, Message } from '../../../ipc/messaging';
4242
import { ConnectionTimeout } from '../../../util/time';
43-
import { replaceRelativeURLsWithAbsolute } from '../../../util/html';
4443
import { colorToLozengeAppearanceMap } from '../colors';
4544
import * as FieldValidators from '../fieldValidators';
4645
import * as SelectFieldHelper from '../selectFieldHelper';
@@ -372,7 +371,7 @@ export abstract class AbstractIssueEditorPage<
372371
}
373372
}, 100);
374373

375-
protected getInputMarkup(field: FieldUI, baseApiUrl: string, editmode: boolean = false): any {
374+
protected getInputMarkup(field: FieldUI, editmode: boolean = false): any {
376375
switch (field.uiType) {
377376
case UIType.Input: {
378377
let validateFunc = this.getValidateFunction(field, editmode);
@@ -402,12 +401,10 @@ export abstract class AbstractIssueEditorPage<
402401
let markup: React.ReactNode = <p></p>;
403402

404403
if ((field as InputFieldUI).isMultiline) {
405-
const html = this.state.fieldValues[`${field.key}.rendered`] || undefined;
406-
const fixedHtml = replaceRelativeURLsWithAbsolute(html, baseApiUrl);
407404
markup = (
408405
<EditRenderedTextArea
409406
text={this.state.fieldValues[`${field.key}`]}
410-
renderedText={fixedHtml}
407+
renderedText={this.state.fieldValues[`${field.key}.rendered`]}
411408
fetchUsers={async (input: string) =>
412409
(await this.fetchUsers(input)).map((user) => ({
413410
displayName: user.displayName,
@@ -420,21 +417,7 @@ export abstract class AbstractIssueEditorPage<
420417
onSave={async (val: string) => {
421418
await this.handleInlineEdit(field, val);
422419
}}
423-
fetchImage={async (url: string) => {
424-
const nonce = uuid.v4();
425-
return (
426-
await this.postMessageWithEventPromise(
427-
{
428-
action: 'getImage',
429-
nonce: nonce,
430-
url: url,
431-
},
432-
'getImageDone',
433-
ConnectionTimeout,
434-
nonce,
435-
)
436-
).imgData;
437-
}}
420+
fetchImage={(img) => this.fetchImage(img)}
438421
/>
439422
);
440423
} else {

src/webviews/components/issue/CommentComponent.tsx

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,11 @@ import React, { useState } from 'react';
1212
import { DetailedSiteInfo } from '../../../atlclients/authInfo';
1313
import { RenderedContent } from '../RenderedContent';
1414
import { TextAreaEditor } from './TextAreaEditor';
15-
import { replaceRelativeURLsWithAbsolute } from '../../../util/html';
1615

1716
type Props = {
1817
siteDetails: DetailedSiteInfo;
1918
comment: JiraComment;
2019
isServiceDeskProject: boolean;
21-
baseApiUrl: string;
2220
fetchUsers: (input: string) => Promise<any[]>;
2321
onSave: (commentBody: string, commentId?: string, restriction?: CommentVisibility) => void;
2422
onDelete: (commentId: string) => void;
@@ -29,7 +27,6 @@ export const CommentComponent: React.FC<Props> = ({
2927
siteDetails,
3028
comment,
3129
isServiceDeskProject,
32-
baseApiUrl,
3330
fetchUsers,
3431
onSave,
3532
onDelete,
@@ -40,7 +37,7 @@ export const CommentComponent: React.FC<Props> = ({
4037
const [isSaving, setIsSaving] = useState(false);
4138

4239
const prettyCreated = `${formatDistanceToNow(parseISO(comment.created))} ago`;
43-
const body = replaceRelativeURLsWithAbsolute(comment.renderedBody!, baseApiUrl) || comment.body;
40+
const body = comment.renderedBody ? comment.renderedBody : comment.body;
4441
const type = isServiceDeskProject ? (comment.jsdPublic ? 'external' : 'internal') : undefined;
4542

4643
if (editing && !isSaving) {

src/webviews/components/issue/CreateIssuePage.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -278,11 +278,11 @@ export default class CreateIssuePage extends AbstractIssueEditorPage<Emit, Accep
278278
};
279279

280280
getCommonFieldMarkup(): any {
281-
return this.commonFields.map((field) => this.getInputMarkup(field, this.state.siteDetails.baseApiUrl));
281+
return this.commonFields.map((field) => this.getInputMarkup(field));
282282
}
283283

284284
getAdvancedFieldMarkup(): any {
285-
return this.advancedFields.map((field) => this.getInputMarkup(field, this.state.siteDetails.baseApiUrl));
285+
return this.advancedFields.map((field) => this.getInputMarkup(field));
286286
}
287287

288288
public render() {

0 commit comments

Comments
 (0)