Skip to content

Commit 836ea36

Browse files
Added support for importing comments.
1 parent 771fba2 commit 836ea36

File tree

9 files changed

+270
-28
lines changed

9 files changed

+270
-28
lines changed

package-lock.json

Lines changed: 41 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"devDependencies": {
77
"@types/mime-types": "^2.1.4",
88
"@types/node": "^16.11.6",
9+
"@types/turndown": "^5.0.4",
910
"@typescript-eslint/eslint-plugin": "5.29.0",
1011
"@typescript-eslint/parser": "5.29.0",
1112
"builtin-modules": "3.3.0",
@@ -20,6 +21,7 @@
2021
"mammoth": "^1.6.0",
2122
"mime-types": "^2.1.35",
2223
"monkey-around": "^2.3.0",
23-
"sass": "^1.70.0"
24+
"sass": "^1.70.0",
25+
"turndown": "^7.2.0"
2426
}
2527
}

src/convertable-file-views/docx.ts

Lines changed: 63 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import * as mammoth from "mammoth"
22
import { renderAsync } from 'docx-preview'
3-
import { htmlToMarkdown } from "obsidian"
4-
import ConvertableFileView from "src/core/convertable-file-view"
3+
import ConvertibleFileView from "src/core/convertible-file-view"
54
import FileUtils from "src/utils/file-utils"
65
import { extensions } from "mime-types"
6+
import ObsidianTurndown from "src/utils/obsidian-turndown"
7+
import { htmlToMarkdown } from "obsidian"
78

8-
export default class DocxFileView extends ConvertableFileView {
9+
export default class DocxFileView extends ConvertibleFileView {
910
static readonly VIEW_TYPE_ID = "docx-view"
1011

1112
getViewType(): string {
@@ -29,22 +30,78 @@ export default class DocxFileView extends ConvertableFileView {
2930
// Convert DOCX to HTML
3031
const fileBuffer = await this.app.vault.readBinary(this.file)
3132
const html = await mammoth.convertToHtml({ arrayBuffer: fileBuffer }, {
33+
styleMap: this.plugin.settings.getSetting("importComments") ? ["comment-reference => sup"] : undefined,
3234
convertImage: mammoth.images.imgElement(async (image: any) => {
35+
console.debug(`Extracting image ${image.altText ?? ""}`)
3336
const imageBinary = await image.read()
3437

3538
const fallbackFilename = this.plugin.settings.getSetting("fallbackAttachmentName")
36-
const attachmentAltText = image.altText ?? ""
39+
const attachmentAltText = image.altText?.replace(/\n/g, " ") ?? ""
3740
const fileExtension = extensions[image.contentType]?.first() || "png"
3841

3942
const path = await FileUtils.createBinary(this.app, attachmentsDirectory, attachmentAltText, fallbackFilename, fileExtension, imageBinary)
40-
console.log(`Extracted image to ${path}`)
43+
console.debug(`Extracted image to ${path}`)
4144

4245
return { src: path.contains(" ") ? `<${path}>` : path, alt: attachmentAltText }
4346
})
4447
})
4548

4649
// Convert HTML to Markdown
47-
const markdown = htmlToMarkdown(html.value)
50+
let markdown
51+
if (!this.plugin.settings.getSetting("importComments")) {
52+
markdown = htmlToMarkdown(html.value)
53+
} else {
54+
const turndownService = ObsidianTurndown.getService()
55+
56+
turndownService.addRule('comments-sup', {
57+
filter: ['sup'],
58+
replacement: function (content) {
59+
// [[MS2]](#comment-1) -> MS
60+
const author = content.match(/\[\[(\D+)\d*\]/)?.[1] ?? "Unknown Author"
61+
// [[MS2]](#comment-1) -> 2
62+
const commentNumber = content.match(/(\d+)/)?.[1] ?? "1"
63+
// [[MS2]](#comment-1) -> comment-1
64+
const commentId = content.match(/#([^\)]+)/)?.[1] ?? "comment-0"
65+
66+
return ` ([[#^${commentId}|Comment ${author} ${commentNumber}]])`
67+
}
68+
})
69+
70+
turndownService.addRule('comments-description-list', {
71+
filter: ['dl'],
72+
replacement: function (content) {
73+
console.log(content)
74+
/*
75+
Comment [MS1]
76+
77+
Hey [↑](#comment-ref-0)
78+
79+
Comment [AD2]
80+
81+
Test comment 2 [↑](#comment-ref-1)
82+
*/
83+
const comments = content.match(/Comment \[(\D+)\d+\]\n\n[\s\S]+? \[.\]\(#comment-ref-(\d+)\)/g)
84+
if (!comments) return content
85+
86+
const commentsCallouts = comments.map((comment) => {
87+
const author = comment.match(/Comment \[(\D+)\d+\]/)?.[1] ?? "Unknown Author"
88+
const number = comment.match(/Comment \[\D+(\d+)\]/)?.[1] ?? "1"
89+
const id = comment.match(/Comment \[\D+\d+\]\n\n[\s\S]+? \[.\]\(#comment-ref-(\d+)\)/)?.[1] ?? "0"
90+
const content = comment.match(/Comment \[\D+\d+\]\n\n([\s\S]+?) \[.\]\(#comment-ref-\d+\)/)?.[1] ?? ""
91+
92+
return (
93+
`>[!QUOTE] **Comment ${author} ${number}**\n`
94+
+ `> ${content}\n`
95+
+ `^comment-${id}`
96+
)
97+
})
98+
99+
return "---" + "\n\n" + commentsCallouts.join("\n\n")
100+
}
101+
})
102+
103+
markdown = turndownService.turndown(html.value)
104+
}
48105

49106
return markdown
50107
}

src/core/convertable-file-view.ts renamed to src/core/convertible-file-view.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,12 @@ export default abstract class ConvertableFileView extends TextFileView {
7272
private async convertFile() {
7373
if (!this.file) return
7474

75+
const convertedFilePath = FileUtils.toUnixPath(this.file.path).replace(/\.[^\.]*$/, ".md")
76+
if (this.app.vault.getAbstractFileByPath(convertedFilePath)) {
77+
new Notice("A file with the same name already exists.")
78+
return
79+
}
80+
7581
// Get the directory where the attachments will be saved
7682
const attachmentsDirectory = {
7783
"vault": "",
@@ -88,7 +94,6 @@ export default abstract class ConvertableFileView extends TextFileView {
8894
}
8995

9096
// Create the converted markdown file
91-
const convertedFilePath = FileUtils.toUnixPath(this.file.path).replace(/\.[^\.]*$/, ".md")
9297
const convertedFile = await this.app.vault.create(convertedFilePath, markdown)
9398
this.leaf.openFile(convertedFile)
9499

src/main.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import DocxFileView from "./convertable-file-views/docx"
2-
import ConvertableFileView from "./core/convertable-file-view"
2+
import ConvertableFileView from "./core/convertible-file-view"
33
import SettingsManager from "./settings"
44
import { Plugin, WorkspaceLeaf } from "obsidian"
55

src/settings.ts

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@ import DocxerPlugin from "./main"
33

44
export interface DocxerPluginSettings {
55
deleteFileAfterConversion: boolean
6+
importComments: boolean
67
fallbackAttachmentName: string
78
attachmentsFolder: "vault" | "custom" | "same" | "subfolder"
89
customAttachmentsFolder: string
910
}
1011

1112
export const DEFAULT_SETTINGS: Partial<DocxerPluginSettings> = {
1213
deleteFileAfterConversion: false,
14+
importComments: false,
1315
fallbackAttachmentName: "Attachment",
1416
attachmentsFolder: "subfolder",
1517
customAttachmentsFolder: "Attachments"
@@ -72,6 +74,21 @@ export class DocxerPluginSettingTab extends PluginSettingTab {
7274
.onChange(async (value) => await this.settingsManager.setSetting({ deleteFileAfterConversion: value }))
7375
)
7476

77+
new Setting(containerEl)
78+
.setName("Import docx comments")
79+
.setDesc("Import comments from docx files using reference links. Comments will be placed at the end of the markdown file.")
80+
.addToggle((toggle) =>
81+
toggle
82+
.setValue(this.settingsManager.getSetting('importComments'))
83+
.onChange(async (value) => await this.settingsManager.setSetting({ importComments: value }))
84+
)
85+
86+
new Setting(containerEl)
87+
.setHeading()
88+
.setClass('docxer-settings-heading')
89+
.setName("Attachments")
90+
.setDesc("Settings related to attachments extracted during file conversion.")
91+
7592
new Setting(containerEl)
7693
.setName("Fallback attachment name")
7794
.setDesc("Fallback name if the attachment file has no alt text or is written using only invalid characters.")
@@ -109,23 +126,6 @@ export class DocxerPluginSettingTab extends PluginSettingTab {
109126
this.addKofiButton(containerEl)
110127
}
111128

112-
private createFeatureHeading(containerEl: HTMLElement, label: string, description: string, settingsKey: keyof DocxerPluginSettings): Setting {
113-
return new Setting(containerEl)
114-
.setHeading()
115-
.setClass('docxer-settings-heading')
116-
.setName(label)
117-
.setDesc(description)
118-
.addToggle((toggle) =>
119-
toggle
120-
.setTooltip("Requires a reload to take effect.")
121-
.setValue(this.settingsManager.getSetting(settingsKey) as boolean)
122-
.onChange(async (value) => {
123-
await this.settingsManager.setSetting({ [settingsKey]: value })
124-
new Notice("Reload obsidian to apply the changes.")
125-
})
126-
)
127-
}
128-
129129
private addKofiButton(containerEl: HTMLElement) {
130130
const kofiButton = document.createElement('a')
131131
kofiButton.classList.add('kofi-button')

src/utils/file-utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export default class FileUtils {
2323

2424
static toValidFilename(filename: string): string {
2525
// " * / : < > ? \ | + , . ; = [ ] ! @
26-
return filename.replace(/[\/\\:*?"<>|+,.=;!@[\]]/g, "")
26+
return filename.replace(/[\/\\:*?"<>|+,.=;!@[\]\n]/g, "")
2727
}
2828

2929
static async createMissingFolders(app: App, filepath: string) {

0 commit comments

Comments
 (0)