Skip to content

Commit

Permalink
add pandoc based conversion for imports, fiduswriter/fiduswriter#1277
Browse files Browse the repository at this point in the history
  • Loading branch information
johanneswilm committed Nov 15, 2024
1 parent 827fe1b commit cc622a0
Show file tree
Hide file tree
Showing 9 changed files with 165 additions and 15 deletions.
14 changes: 14 additions & 0 deletions fiduswriter/pandoc/static/js/modules/pandoc/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export const formats = [
"docx",
"odt",
"doc",
"latex",
"markdown",
"mediawiki",
"org",
"rst",
"textile",
"html",
"json",
"zip"
]
55 changes: 55 additions & 0 deletions fiduswriter/pandoc/static/js/modules/pandoc/document_overview.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import {getJson} from "../common"

import {formats} from "./constants"
import {PandocConversionImporter} from "./importer"

export class DocumentOverviewPandoc {
constructor(documentOverview) {
this.documentOverview = documentOverview

this.pandocAvailable = null
}

init() {
this.checkPandoc().then(() => {
if (this.pandocAvailable) {
this.modifyMenu()
this.modifyExternalImporter()
}
})
}

checkPandoc() {
if (this.pandocAvailable !== null) {
return Promise.resolve(this.pandocAvailable)
}

return getJson("/api/pandoc/available/").then(({available}) => {
this.pandocAvailable = available
})
}

modifyMenu() {
const menu = this.documentOverview.menu
let menuItem = menu.model.content.find(
menuItem => menuItem.id === "import_external"
)
if (!menuItem) {
menuItem = {
id: "import_external"
}
menu.model.contents.push(menuItem)
}
menuItem.title = gettext("Import document")
menuItem.description = gettext("Import a document in another format")
menuItem.icon = "file"
menu.update()
}

modifyExternalImporter() {
const actions = this.documentOverview.mod.actions
actions.externalFormats = actions.externalFormats.concat(formats)
actions.externalFormatsTitle = gettext("Import Pandoc JSON/ZIP file")
actions.externalFileImporter = PandocConversionImporter
}
}
22 changes: 22 additions & 0 deletions fiduswriter/pandoc/static/js/modules/pandoc/editor.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,31 @@
import {getJson} from "../common"

export class EditorPandoc {
constructor(editor) {
this.editor = editor

this.pandocAvailable = null
}

init() {
this.checkPandoc().then(() => {
if (this.pandocAvailable) {
this.modifyMenu()
}
})
}

checkPandoc() {
if (this.pandocAvailable !== null) {
return Promise.resolve(this.pandocAvailable)
}

return getJson("/api/pandoc/available/").then(({available}) => {
this.pandocAvailable = available
})
}

modifyMenu() {
const exportMenu = this.editor.menu.headerbarModel.content.find(
menu => menu.id === "export"
)
Expand Down
17 changes: 4 additions & 13 deletions fiduswriter/pandoc/static/js/modules/pandoc/exporter.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import download from "downloadjs"
import {addAlert, get, jsonPost} from "../common"
import {PandocExporter} from "../exporter/pandoc"
import {createSlug} from "../exporter/tools/file"
import {fileToBase64} from "./helpers"

export class PandocConversionExporter extends PandocExporter {
constructor(
Expand All @@ -26,20 +27,10 @@ export class PandocConversionExporter extends PandocExporter {
this.httpFiles.map(binaryFile =>
get(binaryFile.url)
.then(response => response.blob())
.then(
blob =>
new Promise((resolve, reject) => {
const reader = new window.FileReader()
reader.onerror = reject
reader.onload = () => {
resolve(reader.result)
}
reader.readAsDataURL(blob)
})
)
.then(base64Object =>
.then(blob => fileToBase64(blob))
.then(base64String =>
Promise.resolve({
contents: base64Object.split("base64,")[1],
contents: base64String,
filename: binaryFile.filename
})
)
Expand Down
7 changes: 7 additions & 0 deletions fiduswriter/pandoc/static/js/modules/pandoc/helpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export const fileToBase64 = file =>
new Promise((resolve, reject) => {
const reader = new window.FileReader()
reader.onerror = reject
reader.onload = () => resolve(reader.result.split("base64,")[1])
reader.readAsDataURL(file)
})
43 changes: 43 additions & 0 deletions fiduswriter/pandoc/static/js/modules/pandoc/importer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import {jsonPost} from "../common"
import {PandocImporter} from "../importer/pandoc"

import {formats} from "./constants"
import {fileToBase64} from "./helpers"

export class PandocConversionImporter extends PandocImporter {
init() {
return this.getTemplate().then(() => {
if (this.file.type === "application/json") {
return this.importJSON()
} else if (this.file.type === "application/zip") {
return this.importZip()
} else if (formats.includes(this.file.name.split(".").pop())) {
return this.convertAndImport()
} else {
this.output.statusText = gettext("Unknown file type")
return Promise.resolve(this.output)
}
})
}

convertAndImport() {
const from = this.file.name.split(".").pop()
return fileToBase64(this.file)
.then(base64String => {
return jsonPost("/api/pandoc/export/", {
from,
to: "json",
standalone: true,
text: base64String
})
})
.then(response => response.json())
.then(json => {
if (json.error) {
this.output.statusText = json.error
return this.output
}
return this.handlePandocJson(json.output)
})
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export {DocumentOverviewPandoc} from "../../modules/pandoc/document_overview"
1 change: 1 addition & 0 deletions fiduswriter/pandoc/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@

urlpatterns = [
re_path("^export/$", views.export, name="export"),
re_path("^available/$", views.available, name="available"),
]
20 changes: 18 additions & 2 deletions fiduswriter/pandoc/views.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
from httpx import AsyncClient
from httpx import AsyncClient, HTTPError
from asgiref.sync import async_to_sync, sync_to_async

from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.views.decorators.http import require_POST
from django.http import HttpResponse
from django.http import JsonResponse, HttpResponse

PANDOC_URL = "http://localhost:3030/"
if hasattr(settings, "PANDOC_URL"):
PANDOC_URL = settings.PANDOC_URL
# Add backslash if missing
if not PANDOC_URL.endswith("/"):
PANDOC_URL += "/"


@sync_to_async
Expand All @@ -32,3 +35,16 @@ async def export(request):
headers={"Content-Type": "application/json"},
status=response.status_code,
)


@login_required
async def available(request):
"""Return whether pandoc service is available"""
try:
async with AsyncClient() as client:
response = await client.get(f"{PANDOC_URL}version")
if response.status_code == 200:
return JsonResponse({"available": True})
return JsonResponse({"available": False})
except HTTPError:
return JsonResponse({"available": False})

0 comments on commit cc622a0

Please sign in to comment.