Browse Source

add markdown to docx

master
godo 6 months ago
parent
commit
a655fa9dee
  1. 2
      README.md
  2. 1
      frontend/public/markdown/index.html
  3. 13216
      frontend/public/markdown/scripts/html-docx.js
  4. 25
      frontend/public/markdown/scripts/index.js
  5. 341
      godo/convert/find.go
  6. 42
      godo/convert/xlsx.go
  7. BIN
      godo/deps/darwin/goconv/pdf/pdftohtml
  8. BIN
      godo/deps/linux/goconv/pdf/pdftohtml
  9. BIN
      godo/deps/windows/goconv/pdf/pdftohtml.exe

2
README.md

@ -18,7 +18,7 @@
- 新增ai模型管理,可下载管理ollama模型
- 新增ai助手,可控制整个系统的prompt
- word新增ai优化/续写/纠错/翻译/总结,生成大纲,根据大纲一键创建文章
- markdown更换为更实用的cherry-markdown,支持draw.io绘图,支持导出为思维导图/pdf/长图/md/html格式
- markdown更换为更实用的cherry-markdown,支持draw.io绘图,支持导出为思维导图/pdf/长图/docx/md/html格式
- 修复截图/截屏路径
- 修复更换存储将路径后系统不重置的问题
- 新增文件密码箱(系统设置里),可根据不同文件进行加密存储

1
frontend/public/markdown/index.html

@ -44,6 +44,7 @@
<!--<script src="https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js" integrity="sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4" crossorigin="anonymous"></script>-->
<script src="./dist/cherry-markdown.min.js"></script>
<script src="./scripts/pinyin/pinyin_dist.js"></script>
<script src="./scripts/html-docx.js"></script>
<script src="./scripts/modal.js"></script>
<script src="./scripts/index.js"></script>
</body>

13216
frontend/public/markdown/scripts/html-docx.js

File diff suppressed because it is too large

25
frontend/public/markdown/scripts/index.js

@ -103,6 +103,29 @@ var exportDataHook = Cherry.createMenuHook('导出', {
cherry.export('markdown');
}
},
{
noIcon: true,
name: 'word',
onclick: () => {
if (!markdownTitle || markdownTitle == "") {
markdownTitle = window.prompt("请输入文稿标题");
}
const content = cherry.getHtml()
const converted = htmlDocx.asBlob(content);
// 创建一个隐藏的 <a> 元素
const a = document.createElement('a');
a.style.display = 'none';
document.body.appendChild(a);
// 设置 <a> 元素的 href 属性为 Blob URL
a.href = URL.createObjectURL(converted);
a.download = `${markdownTitle}.docx`;
// 触发点击事件
a.click();
// 清理
document.body.removeChild(a);
URL.revokeObjectURL(a.href);
}
},
{
noIcon: true,
name: 'html',
@ -324,7 +347,7 @@ class AiDialogClass {
action: 'creation_leader'
}, '*')
that.openDialog('creation_builder')
}else{
} else {
showModal({
titleText: '提示',
contentText: "请输入标题",

341
godo/convert/find.go

@ -1,29 +1,336 @@
/*
* GodoOS - A lightweight cloud desktop
* Copyright (C) 2024 https://godoos.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package convert
import (
"archive/zip"
"encoding/xml"
"fmt"
"io"
"log"
"os"
"path/filepath"
"strings"
"godo/libs"
)
// 常见图片扩展名列表
var imageExtensions = []string{".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp", ".tif", ".tiff"}
// 直接遍历ZIP并查找可能的图片文件
func findImagesInZip(zr *zip.Reader) ([]string, error) {
var images []string
cacheDir := libs.GetCacheDir()
for _, f := range zr.File {
if isImageFile(f.Name) {
images = append(images, f.Name)
if err := extractImageToCache(zr, f.Name, cacheDir); err != nil {
log.Printf("Error extracting image %s to cache: %v", f.Name, err)
}
}
}
return images, nil
}
// 判断文件是否为图片文件
func isImageFile(fileName string) bool {
ext := strings.ToLower(filepath.Ext(fileName))
for _, imgExt := range imageExtensions {
if ext == imgExt {
return true
}
}
return false
}
// 从zip中提取图片到缓存目录
func extractImageToCache(zr *zip.Reader, imageName, cacheDir string) error {
fileInZip, err := getFileByName(zr.File, imageName)
if err != nil {
return err
}
rc, err := fileInZip.Open()
if err != nil {
return fmt.Errorf("failed to open file %s in zip: %w", imageName, err)
}
defer rc.Close()
justFileName := filepath.Base(imageName)
outFilePath := filepath.Join(cacheDir, justFileName)
outFile, err := os.Create(outFilePath)
if err != nil {
return fmt.Errorf("failed to create file %s: %w", outFilePath, err)
}
defer outFile.Close()
_, err = io.Copy(outFile, rc)
if err != nil {
return fmt.Errorf("failed to copy image content to %s: %w", outFilePath, err)
}
// 提取图片周围的文本内容
textContent, err := getSurroundingTextForOffice(zr, imageName)
if err != nil {
log.Printf("Error getting surrounding text for image %s: %v", imageName, err)
} else {
textFilePath := filepath.Join(cacheDir, strings.TrimSuffix(justFileName, filepath.Ext(justFileName))+".txt")
if err := saveTextToFile(textContent, textFilePath); err != nil {
log.Printf("Error saving text to file %s: %v", textFilePath, err)
}
}
return nil
}
// 从zip.File数组中根据文件名查找并返回对应的文件
func getFileByName(files []*zip.File, name string) (*zip.File, error) {
for _, file := range files {
if file.Name == name {
return file, nil
}
}
return nil, fmt.Errorf("file %s not found in zip archive", name)
}
// 获取 .pptx, .xlsx 或 .docx 文件中图片周围的文本内容
func getSurroundingTextForOffice(zr *zip.Reader, imageName string) (string, error) {
imageDir := filepath.Dir(imageName)
xmlFiles, err := findRelevantXMLFiles(zr, imageDir)
if err != nil {
return "", err
}
for _, xmlFile := range xmlFiles {
fileInZip, err := getFileByName(zr.File, xmlFile)
if err != nil {
continue
}
rc, err := fileInZip.Open()
if err != nil {
continue
}
defer rc.Close()
doc, err := parseXMLDocument(rc, imageDir)
if err != nil {
continue
}
surroundingText := getSurroundingText(doc, filepath.Base(imageName))
if surroundingText != "" {
return truncateText(surroundingText), nil
}
}
return "", fmt.Errorf("no surrounding text found for image %s", imageName)
}
// 查找相关的XML文件
func findRelevantXMLFiles(zr *zip.Reader, imageDir string) ([]string, error) {
switch {
case strings.Contains(imageDir, "ppt/media"):
return findFilesByPattern(zr, "ppt/slides/slide*.xml"), nil
case strings.Contains(imageDir, "xl/media"):
return findFilesByPattern(zr, "xl/worksheets/sheet*.xml"), nil
case strings.Contains(imageDir, "word/media"):
return []string{"word/document.xml"}, nil
default:
return nil, fmt.Errorf("unknown image directory %s", imageDir)
}
}
// 解析XML文档
func parseXMLDocument(rc io.ReadCloser, imageDir string) (interface{}, error) {
var doc interface{}
switch {
case strings.Contains(imageDir, "ppt/media"):
doc = &PPTXDocument{}
case strings.Contains(imageDir, "xl/media"):
doc = &XLSXDocument{}
case strings.Contains(imageDir, "word/media"):
doc = &DOCXDocument{}
default:
return nil, fmt.Errorf("unknown image directory %s", imageDir)
}
if err := xml.NewDecoder(rc).Decode(doc); err != nil {
return nil, err
}
return doc, nil
}
// 获取图片周围的文本内容
func getSurroundingText(doc interface{}, imagePath string) string {
switch d := doc.(type) {
case *PPTXDocument:
for _, slide := range d.Slides {
for _, shape := range slide.Shapes {
if shape.Type == "pic" && shape.ImagePath == imagePath {
return getTextFromSlide(slide)
}
}
}
case *XLSXDocument:
for _, sheet := range d.Sheets {
for _, drawing := range sheet.Drawings {
for _, image := range drawing.Images {
if image.ImagePath == imagePath {
return getTextFromSheet(sheet)
}
}
}
}
case *DOCXDocument:
for _, paragraph := range d.Body.Paragraphs {
for _, run := range paragraph.Runs {
for _, pic := range run.Pictures {
if pic.ImagePath == imagePath {
return getTextFromParagraph(paragraph)
}
}
}
}
}
return ""
}
// 查找符合模式的文件
func findFilesByPattern(zr *zip.Reader, pattern string) []string {
var files []string
for _, f := range zr.File {
if matched, _ := filepath.Match(pattern, f.Name); matched {
files = append(files, f.Name)
}
}
return files
}
// 将文本内容保存到文件
func saveTextToFile(text, filePath string) error {
return os.WriteFile(filePath, []byte(text), 0644)
}
// 截断文本,确保不超过80个字符
func truncateText(text string) string {
if len(text) > 80 {
return text[:80]
}
return text
}
// PPTXDocument 结构体定义
type PPTXDocument struct {
Slides []Slide `xml:"p:sld"`
}
type Slide struct {
Shapes []Shape `xml:"p:cSld>p:spTree>p:sp"`
}
type Shape struct {
Type string `xml:"p:pic"`
ImagePath string `xml:"p:pic>p:blipFill>a:blip/@r:embed"`
Elements []Element `xml:"p:txBody>a:p>a:r"`
}
type Element struct {
Type string `xml:"a:t"`
Value string `xml:",chardata"`
}
// XLSXDocument 结构体定义
type XLSXDocument struct {
Sheets []Sheet `xml:"worksheet"`
}
type Sheet struct {
Rows []Row `xml:"sheetData>row"`
Drawings []Drawing `xml:"drawing"`
}
type Row struct {
Cells []Cell `xml:"c"`
}
type Cell struct {
Value string `xml:"v"`
}
type Drawing struct {
Images []Image `xml:"xdr:pic"`
}
type Image struct {
ImagePath string `xml:"xdr:pic>xdr:blipFill>a:blip/@r:embed"`
}
// DOCXDocument 结构体定义
type DOCXDocument struct {
Body struct {
Paragraphs []Paragraph `xml:"w:p"`
} `xml:"w:body"`
}
type Paragraph struct {
Runs []Run `xml:"w:r"`
}
type Run struct {
Pictures []Picture `xml:"w:drawing"`
Text []Text `xml:"w:t"`
}
type Text struct {
Value string `xml:",chardata"`
}
type Picture struct {
ImagePath string `xml:"wp:docPr/@name"`
}
// 从幻灯片中提取文本
func getTextFromSlide(slide Slide) string {
var text string
for _, shape := range slide.Shapes {
if shape.Type != "pic" {
text += getTextFromShape(shape)
}
}
return text
}
// 从形状中提取文本
func getTextFromShape(shape Shape) string {
var text string
for _, element := range shape.Elements {
text += element.Value
}
return text
}
// 从工作表中提取文本
func getTextFromSheet(sheet Sheet) string {
var text string
for _, row := range sheet.Rows {
for _, cell := range row.Cells {
text += cell.Value
}
}
return text
}
// 从段落中提取文本
func getTextFromParagraph(paragraph Paragraph) string {
var text string
for _, run := range paragraph.Runs {
for _, t := range run.Text {
text += t.Value
}
}
return text
}

42
godo/convert/xlsx.go

@ -29,27 +29,57 @@ import (
_ "github.com/pbnjay/grate/xlsx"
)
// 返回行索引 列索引
func ConvertXlsx(r io.Reader) (string, error) {
absFileFrom, tmpfromfile, err := libs.GetTempFile(r, "prefix-xlsx-from")
if err != nil {
return "", err
}
text := ""
textByRow := ""
textByColumn := ""
wb, _ := grate.Open(absFileFrom) // open the file
sheets, _ := wb.List() // list available sheets
for _, s := range sheets { // enumerate each sheet name
// 用于存储每一列的内容
columns := make([][]string, 0)
for _, s := range sheets { // enumerate each sheet name
sheet, _ := wb.Get(s) // open the sheet
for sheet.Next() { // enumerate each row of data
maxColumns := 0
for sheet.Next() { // enumerate each row of data
row := sheet.Strings() // get the row's content as []string
//fmt.Println(strings.Join(row, "\t"))
// 更新最大列数
if len(row) > maxColumns {
maxColumns = len(row)
}
// 跳过空记录
if len(row) == 0 {
continue
}
text += strings.Join(row, "\t") + "\n"
textByRow += strings.Join(row, "\t") + "\n"
// 初始化列切片
if len(columns) < maxColumns {
columns = make([][]string, maxColumns)
}
// 将每一列的内容添加到对应的列切片中
for i, cell := range row {
columns[i] = append(columns[i], cell)
}
}
}
// 拼接每一列的内容
for _, col := range columns {
textByColumn += strings.Join(col, "\n") + "\n"
}
wb.Close()
libs.CloseTempFile(tmpfromfile)
return text, nil
return textByRow + "\n\n" + textByColumn, nil
}

BIN
godo/deps/darwin/goconv/pdf/pdftohtml

Binary file not shown.

BIN
godo/deps/linux/goconv/pdf/pdftohtml

Binary file not shown.

BIN
godo/deps/windows/goconv/pdf/pdftohtml.exe

Binary file not shown.
Loading…
Cancel
Save