Skip to content

Commit d37f257

Browse files
authored
Merge pull request #3498 from ramarivera/fixes/markdown-renderer
fix: Inline attachment previews are now visible in Live Preview and Reading modes
2 parents 4764eff + 1191dfe commit d37f257

File tree

10 files changed

+62
-14
lines changed

10 files changed

+62
-14
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,6 @@ yarn-error.log
5656

5757
# VS code config
5858
.vscode/
59+
60+
# Obsidian's hot reload plugin marker file
61+
.hotreload

src/Obsidian/InlineRenderer.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { MarkdownPostProcessorContext, Plugin } from 'obsidian';
1+
import type { App, MarkdownPostProcessorContext, Plugin } from 'obsidian';
22
import { MarkdownRenderChild } from 'obsidian';
33
import { GlobalFilter } from '../Config/GlobalFilter';
44
import { TaskLayoutOptions } from '../Layout/TaskLayoutOptions';
@@ -23,7 +23,11 @@ import { TaskLocation } from '../Task/TaskLocation';
2323
* See also {@link LivePreviewExtension} which handles Markdown task lines in Obsidian's Live Preview mode.
2424
*/
2525
export class InlineRenderer {
26-
constructor({ plugin }: { plugin: Plugin }) {
26+
private readonly app: App;
27+
28+
constructor({ plugin, app }: { plugin: Plugin; app: App }) {
29+
this.app = app;
30+
2731
plugin.registerMarkdownPostProcessor((el, ctx) => {
2832
plugin.app.workspace.onLayoutReady(() => {
2933
this.markdownPostProcessor(el, ctx);
@@ -114,6 +118,7 @@ export class InlineRenderer {
114118
}
115119

116120
const taskLineRenderer = new TaskLineRenderer({
121+
obsidianApp: this.app,
117122
obsidianComponent: childComponent,
118123
parentUlElement: element,
119124
taskLayoutOptions: new TaskLayoutOptions(),

src/Renderer/QueryRenderer.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,17 +120,19 @@ class QueryRenderChild extends MarkdownRenderChild {
120120
}) {
121121
super(container);
122122

123+
this.app = app;
124+
123125
this.queryResultsRenderer = new QueryResultsRenderer(
124126
this.containerEl.className,
125127
source,
126128
tasksFile,
127-
MarkdownRenderer.renderMarkdown,
129+
MarkdownRenderer.render,
128130
this,
131+
this.app,
129132
);
130133

131134
this.queryResultsRenderer.query.debug('[render] QueryRenderChild.constructor() entered');
132135

133-
this.app = app;
134136
this.plugin = plugin;
135137
this.events = events;
136138

src/Renderer/QueryResultsRenderer.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { Component, TFile } from 'obsidian';
1+
import type { App, Component, TFile } from 'obsidian';
22
import { GlobalFilter } from '../Config/GlobalFilter';
33
import { GlobalQuery } from '../Config/GlobalQuery';
44
import { postponeButtonTitle, shouldShowPostponeButton } from '../DateTime/Postponer';
@@ -67,19 +67,28 @@ export class QueryResultsRenderer {
6767
// Renders the group heading in this class:
6868
private readonly renderMarkdown;
6969
private readonly obsidianComponent: Component | null;
70+
private readonly obsidianApp: App;
7071

7172
constructor(
7273
className: string,
7374
source: string,
7475
tasksFile: TasksFile,
75-
renderMarkdown: (markdown: string, el: HTMLElement, sourcePath: string, component: Component) => Promise<void>,
76+
renderMarkdown: (
77+
app: App,
78+
markdown: string,
79+
el: HTMLElement,
80+
sourcePath: string,
81+
component: Component,
82+
) => Promise<void>,
7683
obsidianComponent: Component | null,
84+
obsidianApp: App,
7785
textRenderer: TextRenderer = TaskLineRenderer.obsidianMarkdownRenderer,
7886
) {
7987
this.source = source;
8088
this._tasksFile = tasksFile;
8189
this.renderMarkdown = renderMarkdown;
8290
this.obsidianComponent = obsidianComponent;
91+
this.obsidianApp = obsidianApp;
8392
this.textRenderer = textRenderer;
8493

8594
// The engine is chosen on the basis of the code block language. Currently,
@@ -257,6 +266,7 @@ export class QueryResultsRenderer {
257266

258267
const taskLineRenderer = new TaskLineRenderer({
259268
textRenderer: this.textRenderer,
269+
obsidianApp: this.obsidianApp,
260270
obsidianComponent: this.obsidianComponent,
261271
parentUlElement: taskList,
262272
taskLayoutOptions: this.query.taskLayoutOptions,
@@ -469,7 +479,13 @@ export class QueryResultsRenderer {
469479
if (this.obsidianComponent === null) {
470480
return;
471481
}
472-
await this.renderMarkdown(group.displayName, headerEl, this.tasksFile.path, this.obsidianComponent);
482+
await this.renderMarkdown(
483+
this.obsidianApp,
484+
group.displayName,
485+
headerEl,
486+
this.tasksFile.path,
487+
this.obsidianComponent,
488+
);
473489
}
474490

475491
private addBacklinks(

src/Renderer/TaskLineRenderer.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { Moment } from 'moment';
2-
import { Component, MarkdownRenderer } from 'obsidian';
2+
import { type App, Component, MarkdownRenderer } from 'obsidian';
33
import { GlobalFilter } from '../Config/GlobalFilter';
44
import { TASK_FORMATS, getSettings } from '../Config/Settings';
55
import type { QueryLayoutOptions } from '../Layout/QueryLayoutOptions';
@@ -21,6 +21,7 @@ import { TaskFieldRenderer } from './TaskFieldRenderer';
2121
* The function used to render a Markdown task line into an existing HTML element.
2222
*/
2323
export type TextRenderer = (
24+
app: App,
2425
text: string,
2526
element: HTMLSpanElement,
2627
path: string,
@@ -65,12 +66,14 @@ export function createAndAppendElement<K extends keyof HTMLElementTagNameMap>(
6566
*/
6667
export class TaskLineRenderer {
6768
private readonly textRenderer: TextRenderer;
69+
private readonly obsidianApp: App;
6870
private readonly obsidianComponent: Component | null;
6971
private readonly parentUlElement: HTMLElement;
7072
private readonly taskLayoutOptions: TaskLayoutOptions;
7173
private readonly queryLayoutOptions: QueryLayoutOptions;
7274

7375
public static async obsidianMarkdownRenderer(
76+
app: App,
7477
text: string,
7578
element: HTMLSpanElement,
7679
path: string,
@@ -79,7 +82,8 @@ export class TaskLineRenderer {
7982
if (!obsidianComponent) {
8083
return;
8184
}
82-
await MarkdownRenderer.renderMarkdown(text, element, path, obsidianComponent);
85+
86+
await MarkdownRenderer.render(app, text, element, path, obsidianComponent);
8387
}
8488

8589
/**
@@ -99,18 +103,21 @@ export class TaskLineRenderer {
99103
*/
100104
constructor({
101105
textRenderer = TaskLineRenderer.obsidianMarkdownRenderer,
106+
obsidianApp,
102107
obsidianComponent,
103108
parentUlElement,
104109
taskLayoutOptions,
105110
queryLayoutOptions,
106111
}: {
107112
textRenderer?: TextRenderer;
113+
obsidianApp: App;
108114
obsidianComponent: Component | null;
109115
parentUlElement: HTMLElement;
110116
taskLayoutOptions: TaskLayoutOptions;
111117
queryLayoutOptions: QueryLayoutOptions;
112118
}) {
113119
this.textRenderer = textRenderer;
120+
this.obsidianApp = obsidianApp;
114121
this.obsidianComponent = obsidianComponent;
115122
this.parentUlElement = parentUlElement;
116123
this.taskLayoutOptions = taskLayoutOptions;
@@ -301,7 +308,7 @@ export class TaskLineRenderer {
301308
// Add some debug output to enable hidden information in the task to be inspected.
302309
description += `<br>🐛 <b>${task.lineNumber}</b> . ${task.sectionStart} . ${task.sectionIndex} . '<code>${task.originalMarkdown}</code>'<br>'<code>${task.path}</code>' > '<code>${task.precedingHeader}</code>'<br>`;
303310
}
304-
await this.textRenderer(description, span, task.path, this.obsidianComponent);
311+
await this.textRenderer(this.obsidianApp, description, span, task.path, this.obsidianComponent);
305312

306313
// If the task is a block quote, the block quote wraps the p-tag that contains the content.
307314
// In that case, we need to unwrap the p-tag *inside* the surrounding block quote.
@@ -495,6 +502,7 @@ export class TaskLineRenderer {
495502

496503
const span = createAndAppendElement('span', li);
497504
await this.textRenderer(
505+
this.obsidianApp,
498506
listItem.description,
499507
span,
500508
listItem.findClosestParentTask()?.path ?? '',

src/main.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ export default class TasksPlugin extends Plugin {
6161
events,
6262
});
6363

64-
this.inlineRenderer = new InlineRenderer({ plugin: this });
64+
this.inlineRenderer = new InlineRenderer({ plugin: this, app: this.app });
6565
this.queryRenderer = new QueryRenderer({ plugin: this, events });
6666

6767
// Update types.json.

tests/Renderer/QueryResultsRenderer.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { prettifyHTML } from '../TestingTools/HTMLHelpers';
1717
import { TaskBuilder } from '../TestingTools/TaskBuilder';
1818
import { toMarkdown } from '../TestingTools/TestHelpers';
1919
import { resetSettings, updateSettings } from '../../src/Config/Settings';
20+
import { mockApp } from '../__mocks__/obsidian';
2021
import { mockHTMLRenderer } from './RenderingTestHelpers';
2122

2223
window.moment = moment;
@@ -39,6 +40,7 @@ function makeQueryResultsRenderer(source: string, tasksFile: TasksFile) {
3940
tasksFile,
4041
() => Promise.resolve(),
4142
null,
43+
mockApp,
4244
mockHTMLRenderer,
4345
);
4446
}
Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
export const mockHTMLRenderer = async (text: string, element: HTMLSpanElement, _path: string) => {
1+
import type { App } from 'obsidian';
2+
3+
export const mockHTMLRenderer = async (_obsidianApp: App, text: string, element: HTMLSpanElement, _path: string) => {
24
// Contrary to the default mockTextRenderer(),
35
// instead of the rendered HTMLSpanElement.innerText,
46
// we need the plain HTML here like in TaskLineRenderer.renderComponentText(),
57
// to ensure that description and tags are retained.
68
element.innerHTML = text;
79
};
810

9-
export const mockTextRenderer = async (text: string, element: HTMLSpanElement, _path: string) => {
11+
export const mockTextRenderer = async (_obsidianApp: App, text: string, element: HTMLSpanElement, _path: string) => {
1012
element.innerText = text;
1113
};

tests/Renderer/TaskLineRenderer.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { verifyWithFileExtension } from '../TestingTools/ApprovalTestHelpers';
1717
import { prettifyHTML } from '../TestingTools/HTMLHelpers';
1818
import { TaskBuilder } from '../TestingTools/TaskBuilder';
1919
import { fromLine } from '../TestingTools/TestHelpers';
20+
import { mockApp } from '../__mocks__/obsidian';
2021
import { mockHTMLRenderer, mockTextRenderer } from './RenderingTestHelpers';
2122

2223
jest.mock('obsidian');
@@ -41,6 +42,7 @@ async function renderListItem(
4142
) {
4243
const taskLineRenderer = new TaskLineRenderer({
4344
textRenderer: testRenderer ?? mockTextRenderer,
45+
obsidianApp: mockApp,
4446
obsidianComponent: null,
4547
parentUlElement: document.createElement('div'),
4648
taskLayoutOptions: taskLayoutOptions ?? new TaskLayoutOptions(),
@@ -84,6 +86,7 @@ describe('task line rendering - HTML', () => {
8486
const ulElement = document.createElement('ul');
8587
const taskLineRenderer = new TaskLineRenderer({
8688
textRenderer: mockTextRenderer,
89+
obsidianApp: mockApp,
8790
obsidianComponent: null,
8891
parentUlElement: ulElement,
8992
taskLayoutOptions: new TaskLayoutOptions(),

tests/__mocks__/obsidian.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
1-
import type { CachedMetadata } from 'obsidian';
1+
import type { App, CachedMetadata } from 'obsidian';
22

33
export {};
44

5+
/**
6+
* Since we don't use the app object's method or properties directly,
7+
* and just treat it as an "opaque object" for markdown rendering, there is
8+
* not a lot to mock in particular.
9+
*/
10+
export const mockApp = {} as unknown as App;
11+
512
export class MenuItem {
613
public title: string | DocumentFragment = '';
714
public callback: (evt: MouseEvent | KeyboardEvent) => any;

0 commit comments

Comments
 (0)