Skip to content

Commit

Permalink
Added support for importing comments.
Browse files Browse the repository at this point in the history
  • Loading branch information
Developer-Mike committed Jun 27, 2024
1 parent 771fba2 commit 836ea36
Show file tree
Hide file tree
Showing 9 changed files with 270 additions and 28 deletions.
42 changes: 41 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"devDependencies": {
"@types/mime-types": "^2.1.4",
"@types/node": "^16.11.6",
"@types/turndown": "^5.0.4",
"@typescript-eslint/eslint-plugin": "5.29.0",
"@typescript-eslint/parser": "5.29.0",
"builtin-modules": "3.3.0",
Expand All @@ -20,6 +21,7 @@
"mammoth": "^1.6.0",
"mime-types": "^2.1.35",
"monkey-around": "^2.3.0",
"sass": "^1.70.0"
"sass": "^1.70.0",
"turndown": "^7.2.0"
}
}
69 changes: 63 additions & 6 deletions src/convertable-file-views/docx.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import * as mammoth from "mammoth"
import { renderAsync } from 'docx-preview'
import { htmlToMarkdown } from "obsidian"
import ConvertableFileView from "src/core/convertable-file-view"
import ConvertibleFileView from "src/core/convertible-file-view"
import FileUtils from "src/utils/file-utils"
import { extensions } from "mime-types"
import ObsidianTurndown from "src/utils/obsidian-turndown"
import { htmlToMarkdown } from "obsidian"

export default class DocxFileView extends ConvertableFileView {
export default class DocxFileView extends ConvertibleFileView {
static readonly VIEW_TYPE_ID = "docx-view"

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

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

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

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

// Convert HTML to Markdown
const markdown = htmlToMarkdown(html.value)
let markdown
if (!this.plugin.settings.getSetting("importComments")) {
markdown = htmlToMarkdown(html.value)
} else {
const turndownService = ObsidianTurndown.getService()

turndownService.addRule('comments-sup', {
filter: ['sup'],
replacement: function (content) {
// [[MS2]](#comment-1) -> MS
const author = content.match(/\[\[(\D+)\d*\]/)?.[1] ?? "Unknown Author"
// [[MS2]](#comment-1) -> 2
const commentNumber = content.match(/(\d+)/)?.[1] ?? "1"
// [[MS2]](#comment-1) -> comment-1
const commentId = content.match(/#([^\)]+)/)?.[1] ?? "comment-0"

return ` ([[#^${commentId}|Comment ${author} ${commentNumber}]])`
}
})

turndownService.addRule('comments-description-list', {
filter: ['dl'],
replacement: function (content) {
console.log(content)
/*
Comment [MS1]
Hey [↑](#comment-ref-0)
Comment [AD2]
Test comment 2 [↑](#comment-ref-1)
*/
const comments = content.match(/Comment \[(\D+)\d+\]\n\n[\s\S]+? \[.\]\(#comment-ref-(\d+)\)/g)
if (!comments) return content

const commentsCallouts = comments.map((comment) => {
const author = comment.match(/Comment \[(\D+)\d+\]/)?.[1] ?? "Unknown Author"
const number = comment.match(/Comment \[\D+(\d+)\]/)?.[1] ?? "1"
const id = comment.match(/Comment \[\D+\d+\]\n\n[\s\S]+? \[.\]\(#comment-ref-(\d+)\)/)?.[1] ?? "0"
const content = comment.match(/Comment \[\D+\d+\]\n\n([\s\S]+?) \[.\]\(#comment-ref-\d+\)/)?.[1] ?? ""

return (
`>[!QUOTE] **Comment ${author} ${number}**\n`
+ `> ${content}\n`
+ `^comment-${id}`
)
})

return "---" + "\n\n" + commentsCallouts.join("\n\n")
}
})

markdown = turndownService.turndown(html.value)
}

return markdown
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,12 @@ export default abstract class ConvertableFileView extends TextFileView {
private async convertFile() {
if (!this.file) return

const convertedFilePath = FileUtils.toUnixPath(this.file.path).replace(/\.[^\.]*$/, ".md")
if (this.app.vault.getAbstractFileByPath(convertedFilePath)) {
new Notice("A file with the same name already exists.")
return
}

// Get the directory where the attachments will be saved
const attachmentsDirectory = {
"vault": "",
Expand All @@ -88,7 +94,6 @@ export default abstract class ConvertableFileView extends TextFileView {
}

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

Expand Down
2 changes: 1 addition & 1 deletion src/main.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import DocxFileView from "./convertable-file-views/docx"
import ConvertableFileView from "./core/convertable-file-view"
import ConvertableFileView from "./core/convertible-file-view"
import SettingsManager from "./settings"
import { Plugin, WorkspaceLeaf } from "obsidian"

Expand Down
34 changes: 17 additions & 17 deletions src/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ import DocxerPlugin from "./main"

export interface DocxerPluginSettings {
deleteFileAfterConversion: boolean
importComments: boolean
fallbackAttachmentName: string
attachmentsFolder: "vault" | "custom" | "same" | "subfolder"
customAttachmentsFolder: string
}

export const DEFAULT_SETTINGS: Partial<DocxerPluginSettings> = {
deleteFileAfterConversion: false,
importComments: false,
fallbackAttachmentName: "Attachment",
attachmentsFolder: "subfolder",
customAttachmentsFolder: "Attachments"
Expand Down Expand Up @@ -72,6 +74,21 @@ export class DocxerPluginSettingTab extends PluginSettingTab {
.onChange(async (value) => await this.settingsManager.setSetting({ deleteFileAfterConversion: value }))
)

new Setting(containerEl)
.setName("Import docx comments")
.setDesc("Import comments from docx files using reference links. Comments will be placed at the end of the markdown file.")
.addToggle((toggle) =>
toggle
.setValue(this.settingsManager.getSetting('importComments'))
.onChange(async (value) => await this.settingsManager.setSetting({ importComments: value }))
)

new Setting(containerEl)
.setHeading()
.setClass('docxer-settings-heading')
.setName("Attachments")
.setDesc("Settings related to attachments extracted during file conversion.")

new Setting(containerEl)
.setName("Fallback attachment name")
.setDesc("Fallback name if the attachment file has no alt text or is written using only invalid characters.")
Expand Down Expand Up @@ -109,23 +126,6 @@ export class DocxerPluginSettingTab extends PluginSettingTab {
this.addKofiButton(containerEl)
}

private createFeatureHeading(containerEl: HTMLElement, label: string, description: string, settingsKey: keyof DocxerPluginSettings): Setting {
return new Setting(containerEl)
.setHeading()
.setClass('docxer-settings-heading')
.setName(label)
.setDesc(description)
.addToggle((toggle) =>
toggle
.setTooltip("Requires a reload to take effect.")
.setValue(this.settingsManager.getSetting(settingsKey) as boolean)
.onChange(async (value) => {
await this.settingsManager.setSetting({ [settingsKey]: value })
new Notice("Reload obsidian to apply the changes.")
})
)
}

private addKofiButton(containerEl: HTMLElement) {
const kofiButton = document.createElement('a')
kofiButton.classList.add('kofi-button')
Expand Down
2 changes: 1 addition & 1 deletion src/utils/file-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export default class FileUtils {

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

static async createMissingFolders(app: App, filepath: string) {
Expand Down
Loading

0 comments on commit 836ea36

Please sign in to comment.