vault backup: 2025-04-10 14:05:11

This commit is contained in:
SeedList
2025-04-10 14:05:11 +08:00
parent 71d12f5275
commit 6a125902cd
88 changed files with 0 additions and 27937 deletions

View File

@ -1,13 +0,0 @@
# EditorConfig is awesome: https://EditorConfig.org
# top-most EditorConfig file
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = false
insert_final_newline = false
max_line_length = 140

View File

@ -1,3 +0,0 @@
# export to obsidian plugin directory directly?
OUT_DIR="./dist"

View File

@ -1 +0,0 @@
NODE_ENV=development

View File

@ -1,52 +0,0 @@
{
"env": {
"browser": true,
"commonjs": true,
"es2021": true
},
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": "latest"
},
"plugins": [
"@typescript-eslint"
],
"rules": {
"indent": [
"error",
2,
{
"SwitchCase": 1
}
],
"linebreak-style": [
"error",
"unix"
],
"quotes": [
"error",
"single",
{
"avoidEscape": true
}
],
"semi": [
"error",
"always"
],
"no-prototype-builtins": "off",
"no-constant-condition": [
"error",
{
"checkLoops": false
}
]
},
"ignorePatterns": [
"dist/*"
]
}

View File

@ -1 +0,0 @@
* text=auto eol=lf

View File

@ -1,20 +0,0 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "npm" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "weekly"
time: "13:00"
open-pull-requests-limit: 10
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: weekly
time: "13:00"
open-pull-requests-limit: 10

View File

@ -1,119 +0,0 @@
name: Release Obsidian plugin
on:
workflow_dispatch:
inputs:
version:
description: 'version'
required: true
env:
PLUGIN_NAME: obsidian-enhancing-export
jobs:
bump:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false # otherwise, the token used is the GITHUB_TOKEN, instead of your personal access token.
fetch-depth: 0 # otherwise, there would be errors pushing refs to the destination repository.
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: 20
- name: Create local changes
run: |
npm run version ${{ github.event.inputs.version }}
- name: Commit files
run: |
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git commit -a -m "🔖 Bump version number to ${{ github.event.inputs.version }}"
git tag -a ${{ github.event.inputs.version }} -m ""
- name: Push changes
uses: ad-m/github-push-action@master
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
branch: ${{ github.ref }}
tags: true
build:
runs-on: ubuntu-latest
needs: [ "bump"]
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.version }}
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: 18
- uses: pnpm/action-setup@v4
with:
version: 9
- name: Build
id: build
run: |
pnpm install
npm run build
mkdir ${{ env.PLUGIN_NAME }}
cp dist/* ${{ env.PLUGIN_NAME }}
zip -r ${{ env.PLUGIN_NAME }}.zip ${{ env.PLUGIN_NAME }}
ls
- name: Create Release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VERSION: ${{ github.event.inputs.version }}
with:
tag_name: ${{ github.event.inputs.version }}
release_name: ${{ github.event.inputs.version }}
draft: false
prerelease: false
- name: Upload zip file
id: upload-zip
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./${{ env.PLUGIN_NAME }}.zip
asset_name: ${{ env.PLUGIN_NAME }}-${{ steps.build.outputs.tag_name }}.zip
asset_content_type: application/zip
- name: Upload main.js
id: upload-main
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./dist/main.js
asset_name: main.js
asset_content_type: text/javascript
- name: Upload manifest.json
id: upload-manifest
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./dist/manifest.json
asset_name: manifest.json
asset_content_type: application/json
- name: Upload styles.css
id: upload-css
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./dist/styles.css
asset_name: styles.css
asset_content_type: text/css

View File

@ -1,34 +0,0 @@
name: Test
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [20.x]
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- uses: pnpm/action-setup@v4
with:
version: 9
- name: Install Pandoc
run: |
wget https://github.com/jgm/pandoc/releases/download/3.1.11.1/pandoc-3.1.11.1-1-amd64.deb
sudo dpkg -i pandoc-3.1.11.1-1-amd64.deb
- name: Install Dependencies
run: pnpm install
- name: Test
run: npm test

View File

@ -1,6 +0,0 @@
/dist
/node_modules
/coverage
/lua/main.lua
/.env.local
/.idea

View File

@ -1 +0,0 @@
tag-version-prefix=""

View File

@ -1,8 +0,0 @@
tabWidth: 2
useTabs: false
semi: true
singleQuote: true
quoteProps: preserve
trailingComma: es5
arrowParens: avoid

View File

@ -1,63 +0,0 @@
# Contributing to Obsidian Enhancing Export
First, thank you for your willingness to contribute to this project.
## Simple guide
1. Environment Preparing
- Install the `nodejs`
[https://nodejs.org/en/download](https://nodejs.org/en/download)
- Install the `pnpm`
```shell
npm install -g pnpm
```
- Clone the repository
```shell'
git clone https://github.com/mokeyish/obsidian-enhancing-export.git
```
- Install the dependencies
```shell
cd obsidian-enhancing-export
pnpm install
```
2. Development & debugging (Recommend [VsCode](https://code.visualstudio.com/))
- Add `.env.local` to project root with following content
```shell
# export to obsidian plugin directory directly
OUT_DIR="path/to/.obsidian/plugins/obsidian-enhancing-export"
```
- Enable `dev-mode `
To enable dev-mode in the obsidian, use the shortcut `Ctrl+Shift+I` or the `<F12>` key to open DevTools. and run following commands in the Console Tab of DevTools.
```shell
localStorage.setItem('debug-plugin', '1')
```
- Build the code for debugging
```shell
npm run dev
```
More debug tips please see: [How to debug TypeScript in Chrome](https://blog.logrocket.com/how-to-debug-typescript-chrome/)
3. Building for Production
```shell
npm run build
```
4. Other commands please see `sciprts` of `package.json` in the project root.

View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2022 YISH
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,67 +0,0 @@
# Obsidian Enhancing Export Plugin
![GitHub release (latest by date including pre-releases)](https://img.shields.io/github/v/release/mokeyish/obsidian-enhancing-export?display_name=tag&include_prereleases)
![Obsidian Downloads](https://img.shields.io/badge/dynamic/json?logo=obsidian&color=%23483699&label=downloads&query=%24%5B%27obsidian-enhancing-export%27%5D.downloads&url=https%3A%2F%2Fraw.githubusercontent.com%2Fobsidianmd%2Fobsidian-releases%2Fmaster%2Fcommunity-plugin-stats.json)
English | [中文](https://github.com/mokeyish/obsidian-enhancing-export/blob/master/README_zh-CN.md)
This is an enhancing export plugin base on `Pandoc` for Obsidian ([https://obsidian.md/](https://obsidian.md/)). It's allow you to export to formats like `Markdown`,`Markdown (Hugo)`,`HTML`,`docx`,`Latex` etc.
Where `Markdown`,`Markdown (Hugo)`,`HTML` will export and its media resource together.
**Note** `Markdown`,`Markdown (Hugo)`,`HTML` are tested in Mac OS, Windows, and Linux as I used it for myself, others are not tested well.
**Ads**: You might like my other plugins 🤪
- [Obsidian Code Emitter](https://github.com/mokeyish/obsidian-code-emitter)
## Screen shot
- Export viewclick on `Export to...` on file menu.
![](https://raw.githubusercontent.com/mokeyish/obsidian-enhancing-export/master/screenshot/exportview_en-US.png)
- Setting view
![](https://raw.githubusercontent.com/mokeyish/obsidian-enhancing-export/master/screenshot/settingview_en-US.png)
## Installation
1. First install the latest `pandoc` (3.1.9+), and then add `pandoc` path to environment variable `PATH` or set absolute path of `pandoc` in the plugin setting view.
See more details in [https://pandoc.org/installing.html](https://pandoc.org/installing.html)。
2. Search `obsidian-enhancing-export` in the community plugins of obsidian, and install it.
## Customize export commands
You can customize your export command by yourself, click `add` in the plugin setting view and then choose template `custom` to add new custom configuration.
## Variables
You can use `${variables}` in custom export command, their values are:
| Key | Value |
| ------------------------- | ------------------------------------------------------------ |
| `${outputPath}` | Output file path after export. For example if your export to location `/User/aaa/Documents/test.pdf`, then `${outputDir}` will be replace that path. |
| `${outputDir}` | Output directory of saved exported fileIt will be `/User/aaa/Documents` in above case. |
| `${outputFileName}` | File name (without extension) of the saved exported file. It will be `test` in above case. |
| `${outputFileFullName}` | File name (with extension) of the saved exported file. It will be `test.pdf` in above case. |
| `${currentPath}` | Path of currently edited file. For example, if your are editing `/User/aaa/Documents/readme.md`, the the value will be `/User/aaa/Documents/readme.md`. |
| `${currentDir}` | Current directory of currently edited file, It will be`/User/aaa/Documents` in above case. |
| `${currentFileName}` | Filename without extension of currently edited file, It will be `readme` in above case. |
| `${currentFileFullName}` | Filename with extension of currently edited file. It will be `readme.md` in above case. |
| `${vaultDir}` | The obsidian current vaultDir. |
| `${attachmentFolderPath}` | The `attachmentFolderPath` of Obsidian. |
| Others variables | You can use `keyword: value` in [YAML Front Matter](https://jekyllrb.com/docs/front-matter/), then use `${metadata.keyword}` |
## Related resources
- **Tutorial**: [Obsidian Tutorial for Academic Writing](https://betterhumans.pub/obsidian-tutorial-for-academic-writing-87b038060522) - tutorial on how to setup this plugin and use it for academic writing (export to `.docx`, `.pdf`, `.tex`, `.bib`)
- **A collection of lua filters for pandoc**: [https://github.com/pandoc-ext](https://github.com/pandoc-ext) - Filters and other goodies to get the most out of pandoc, the universal document converter.
- **Math latex editor**: [https://math.yish.org/](https://math.yish.org/)
## Finally
- Welcome to provide more command templates to [here](src/export_templates.ts).
- Feel free to file an issue for any questions.

View File

@ -1,52 +0,0 @@
# Obsidian Enhancing Export Plugin
[English](https://github.com/mokeyish/obsidian-enhancing-export/blob/master/README.md) | 中文
这是一个基于 Pandoc 的 Obsidian 加强版导出插件。提供了基本的导出格式Markdown 、MarkdownHugo [https://gohugo.io/](https://gohugo.io/)、Html、docx、Latex等。
其中 Markdown 、MarkdownHugo、Html 会把媒体资源一并导出。
**注意:** 目前自用的就是 Markdown 、MarkdownHugo、Html在 Mac OS、Windows、Linux 可正常使用,其他未经严格测试。
## 界面截图
- 导出界面,在文件菜单上点击 `导出为......`
![](https://raw.githubusercontent.com/mokeyish/obsidian-enhancing-export/master/screenshot/exportview_zh-CN.png)
- 设置界面
![](https://raw.githubusercontent.com/mokeyish/obsidian-enhancing-export/master/screenshot/settingview_zh-CN.png)
## 安装
1. 需要先安装最新的 `pandoc`(3.1.9+),最好配置到 PATH 环境变量,或者设置界面指定路径。
参考地址:[https://pandoc.org/installing.html](https://pandoc.org/installing.html)
2. 在 Obsidian 插件市场,搜索 `obsidian-enhancing-export` 进行安装。
## 自定义命令
本插件是支持自定义导出命令的,在设置界面,点击添加按钮,选择 `Custom` 作为模板,即可新增一个自定义导出的配置了。
### 变量
你可以使用 `${variable}` 在自定义导出的命令中。它们的值是:
| 变量名 | 值 |
| -- | -- |
| `${outputPath}` |导出路径,例如,你的导出位置是:`/User/aaa/Documents/test.pdf` ,则 `${outputDir}` 会替换为那个路径。|
| `${outputDir}` | 导出目录,按上面的例子,它会被替换为 `/User/aaa/Documents`。 |
| `${outputFileName}` | 没有扩展名的文件名,按上面的例子,它会被替换为 `test`。 |
| `${outputFileFullName}` | 文件的全名,按上面的例子,它会被替换为 `test.pdf`。 |
| `${currentPath}` | 当前文件路径,例如当前的文件位置是 `/User/aaa/Documents/readme.md`,那么它会被替换为这个文件的位置。 |
| `${currentDir}` | 当前文件所在目录,按上面的例子,值为 `/User/aaa/Documents`。 |
| `${currentFileName}` | 当前文件不带扩展名的名字,值是 `readme` |
| `${currentFileFullName}` | 当前文件全名,值是 `readme.md`。 |
| `${vaultDir}` | Obsidian 当前的 vaultDir. |
| `${attachmentFolderPath}`| Obsidian 的附件目录 |
| 其他变量 | 你可以在 [YAML Front Matter](https://jekyllrb.com/docs/front-matter/) 中定义 `keyword: value` 变量,然后以 `${metadata.keyword}`引用它。 |
## Related resources
- **Pandoc 的 lua filters 集合**: [https://github.com/pandoc-ext](https://github.com/pandoc-ext)
- **Latex 数学公式编辑器**: [https://math.yish.org/](https://math.yish.org/)
## 最后
- 欢迎提供更多命令模板到[这里](src/export_templates.ts).。
- 有问题可以提交 Issue 给我。

View File

@ -1,5 +0,0 @@
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node'
};

View File

@ -1,6 +0,0 @@
-- credits to tarleb — StackExchange: https://tex.stackexchange.com/questions/392070/pandoc-markdown-create-self-contained-bib-file-from-cited-references
function Pandoc(d)
d.meta.references = pandoc.utils.references(d)
d.meta.bibliography = nil
return d
end

View File

@ -1,5 +0,0 @@
package.path=package.path..";" ..debug.getinfo(1).source:match("(.*[/\\])"):sub(2) .. "?.lua"
Mode='hugo'
require('markdown')

View File

@ -1,237 +0,0 @@
package.path=debug.getinfo(1).source:gsub('@',''):sub(0):match('(.*[/\\])'):sub(0) .. '?.lua' .. ';' .. package.path
require("polyfill")
local url = require('url')
local pandoc=pandoc
local PANDOC_STATE=PANDOC_STATE
PANDOC_VERSION:must_be_at_least '3.1.7'
os.text = pandoc.text
local PATH = pandoc.path
local doc_dir = nil
local media_dir = nil
if Mode == nil then
Mode = 'default'
end
-- print("Mode: "..Mode)
if PANDOC_STATE.output_file then
local output_file = PANDOC_STATE.output_file
doc_dir = PATH.directory(output_file)
if PANDOC_WRITER_OPTIONS.variables["media_dir"] then
media_dir = tostring(PANDOC_WRITER_OPTIONS.variables["media_dir"])
else
media_dir = PATH.split_extension(output_file)
if Mode ~= 'hugo' then
media_dir = media_dir .. '-media'
end
end
end
assert(doc_dir, "doc_dir is nil")
assert(media_dir, "media_dir is nil")
local function get_absolute_path(file_path)
if PATH.is_absolute(file_path) then
return file_path
end
for _, dir in pairs(PANDOC_STATE.resource_path) do
local full_path = PATH.join({dir, file_path})
if os.exists(full_path) then
return full_path
end
end
for _, file in pairs(PANDOC_STATE.input_files) do
if not PATH.is_absolute(file) then
file = PATH.join({pandoc.system.get_working_directory(), file_path})
end
local dir = PATH.directory(file)
local full_path = PATH.join({dir, file_path})
if os.exists(full_path) then
return full_path
end
end
return nil
end
local function get_output_file(file_path)
if media_dir then
local new_file_name = pandoc.utils.sha1(file_path)
local _, new_file_ext = PATH.split_extension(file_path)
file_path = new_file_name .. new_file_ext
local full_path = PATH.join({media_dir, file_path})
return full_path
else
return nil
end
end
local function extract_media(file_path)
os.mkdir(media_dir)
file_path = url.decode(file_path)
local abs_path = get_absolute_path(file_path)
local file = get_output_file(file_path)
if abs_path and file then
if not os.exists(file) then
os.copy(abs_path, file)
end
local rel_path = PATH.make_relative(file, doc_dir, false)
local parts = PATH.split(rel_path)
for i,v in ipairs(parts) do
parts[i] = url.encode(v)
end
local encoded_rel_path = table.concat(parts, "/")
if Mode == 'hugo' then
encoded_rel_path = '../' .. encoded_rel_path
end
return encoded_rel_path
end
end
local function raw(s)
return pandoc.RawInline('markdown', s)
end
function Image(el)
local src = extract_media(el.src)
if src then
el.src = src
end
return el
end
function Space()
return raw(' ')
end
function SoftBreak()
return raw('\n')
end
function RawInline(el)
if el.format == "html" then
el.format = 'markdown'
el.text = string.gsub(el.text, '<img[^>]+>', function(img)
return string.gsub(img, 'src="([^"]+)"', function(url)
if string.find(url, '^[Hh][Tt][Tt][Pp][Ss]?://') == nil then
local extract_media_url = extract_media(url)
if extract_media_url then
return 'src="' .. extract_media_url .. '"'
end
return '123'
end
return 'src="' .. url .. '"'
end)
end)
end
return el
end
function RawBlock(el)
if el.format == "html" then
el.format = 'markdown'
end
return el
end
function Math(el)
if Mode == 'hugo' then
if el.mathtype == 'DisplayMath' then
return raw('{{< mathjax >}}\n$$' .. el.text .. '$$\n{{</mathjax >}}')
else
el.text = string.gsub(el.text, '\\[\\{\\}]', function (v)
return '\\' .. v
end)
el.text = string.gsub(el.text, '_', function (v)
return '\\' .. v
end)
end
end
return el
end
local function headerLink(input)
-- github style section link
return "#"..input:gsub(' ', '-')
end
local function insertLink(content, linkDescription)
local descriptionText = table.concat(linkDescription, "")
if string.find(descriptionText, '|') then
local target, desc = descriptionText:match("(.*)|(.*)")
table.insert(content, pandoc.Link(desc, headerLink(target)))
else
table.insert(content, pandoc.Link(descriptionText, headerLink(descriptionText)))
end
end
function Para(el)
local content = el.content
content = ProcessMath(content)
content = ProcessInternalLinks(content)
el.content = content
return el
end
function ProcessMath(elements)
local content = {}
local in_display_math = false
for _, item in pairs(elements) do
if item.t == 'Str'and item.text == "$$" then
in_display_math = not in_display_math
else
if in_display_math then
if item.t == 'RawInline' and item.format == 'tex' then
local n = pandoc.Math('DisplayMath', '\n' .. item.text .. '\n')
table.insert(content, Math(n))
else
table.insert(content, item)
end
else
table.insert(content, item)
end
end
end
return content
end
function ProcessInternalLinks(elements)
local content = {}
local in_section_link = false
local linkDescription = {}
for _, item in pairs(elements) do
if item.t == 'Str' and string.starts_with(item.text, '[[#') then
in_section_link = true
table.insert(linkDescription, string.sub(item.text, 4))
elseif in_section_link then
if string.ends_with(item.text, ']]') then
table.insert(linkDescription, string.sub(item.text, 1, -3))
insertLink(content, linkDescription)
in_section_link = false
linkDescription = {}
else
table.insert(linkDescription, item.text)
end
else
table.insert(content, item)
end
end
return content
end
function Plain(el)
el.content = ProcessInternalLinks(el.content)
return el
end
function Pandoc(el)
return el
end

View File

@ -1,68 +0,0 @@
traverse = 'topdown'
math_block_text = nil
function process(el)
-- MathBlock start or end
if el.t == 'Str' and el.text == '$$' then
if math_block_text == nil then -- start
math_block_text = ''
else -- end
local math_block = pandoc.Math('DisplayMath', '\n' .. math_block_text .. '\n')
math_block_text = nil
return math_block
end
return {}
end
if math_block_text then
if (el.t == 'RawInline' or el.t == 'RawBlock') and el.format == 'tex' then
math_block_text = math_block_text .. el.text
return {}
elseif el.t == 'Str' then
math_block_text = math_block_text .. el.text
return {}
elseif el.t == 'SoftBreak' or el.t == 'BulletList' then
return {}
end
end
return el
end
function RawInline(el)
return process(el)
end
function RawBlock(el)
return process(el)
end
function Str(el)
return process(el)
end
function SoftBreak(el)
return process(el)
end
function Header(el)
return process(el)
end
function Para(el)
return process(el)
end
function Plain(el)
return process(el)
end
function BulletList(el)
return process(el)
end

View File

@ -1,12 +0,0 @@
return {
{
Math = function (elem)
if elem.text:find("^%s*\\begin{") ~= nil then
return pandoc.RawInline('tex', elem.text)
else
return elem
end
end,
}
}

View File

@ -1,61 +0,0 @@
os.platform = nil
if os.platform == nil then
local libExt = package.cpath:match("%p[\\|/]?\\.%p(%a+)")
if libExt == 'dll' then
os.platform = "Windows"
elseif libExt == 'so' then
os.platform = "Linux"
elseif libExt == 'dylib' then
os.platform = "MacOS"
end
end
os.copy = function(src, dest)
if os.platform == "Windows" then
src = string.gsub(src, "/", "\\")
src = os.text.toencoding(src)
dest = os.text.toencoding(dest)
os.execute('copy "' .. src .. '" "' .. dest .. '" >NUL')
else
os.execute('cp "' .. src .. '" "' .. dest .. '"')
end
end
os.mkdir = function(dir)
if os.exists(dir) then
return
end
if os.platform == "Windows" then
dir = os.text.toencoding(dir)
os.execute('mkdir "' .. dir .. '"')
else
os.execute('mkdir -p "' .. dir .. '"')
end
end
os.exists = function(path)
if os.platform == "Windows" then
path = string.gsub(path, "/", "\\")
path = os.text.toencoding(path)
local _, _, code = os.execute('if exist "' .. path .. '" (exit 0) else (exit 1)')
return code == 0
else
local _, _, code = os.execute('test -e "' .. path .. '"')
return code == 0
end
end
string.starts_with = function(str, start)
return str:sub(1, #start) == start
end
string.ends_with = function(str, ending)
return ending == "" or str:sub(-#ending) == ending
end
return {
os = os,
string = string
}

View File

@ -1,18 +0,0 @@
local function encode (str)
str = string.gsub (str, "([^0-9a-zA-Z !'()*._~-])", -- locale independent
function (c) return string.format ("%%%02X", string.byte(c)) end)
str = string.gsub (str, " ", "%%20")
return str
end
local function decode (str)
str = string.gsub (str, "%%20", " ")
str = string.gsub (str, "%%(%x%x)", function(h) return string.char(tonumber(h,16)) end)
return str
end
return {
encode = encode,
decode = decode
}

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +0,0 @@
{
"id": "obsidian-enhancing-export",
"name": "Enhancing Export",
"version": "1.10.7",
"minAppVersion": "1.6.3",
"description": "This is a enhancing export plugin for Obsidian. It allows to export to formats like Html, DOCX, ePub and PDF or Markdown(Hugo) etc.",
"author": "YISH",
"authorUrl": "https://github.com/mokeyish",
"isDesktopOnly": true
}

View File

@ -1,46 +0,0 @@
{
"name": "obsidian-enhancing-export",
"version": "1.10.7",
"description": "This is a enhancing export plugin for Obsidian. It allows to export to formats like Html, DOCX, ePub and PDF or Markdown(Hugo) etc.",
"main": "dist/main.js",
"scripts": {
"dev": "vite build -w -m development",
"build": "vite build",
"version": "node version-bump.mjs",
"lint": "eslint --ext .ts,.js src",
"lint-fix": "eslint --fix --ext .ts,.js src",
"format-check": "prettier --check \"src/**/*.ts\"",
"format-fix": "prettier --write \"src/**/*.ts\"",
"test": "jest"
},
"keywords": [],
"author": "YISH",
"license": "MIT",
"repository": "https://github.com/mokeyish/obsidian-enhancing-export",
"type": "module",
"devDependencies": {
"@types/jest": "^29.5.1",
"@types/node": "^20.14.8",
"@types/semver": "^7.5.0",
"@types/yargs-parser": "^21.0.3",
"@typescript-eslint/eslint-plugin": "^5.59.7",
"@typescript-eslint/parser": "^5.59.7",
"builtin-modules": "^3.3.0",
"dotenv": "^16.0.3",
"eslint": "^8.41.0",
"jest": "^29.5.0",
"obsidian": "latest",
"prettier": "^3.3.3",
"ts-jest": "^29.1.0",
"tslib": "2.6.3",
"typescript": "5.0.4",
"vite": "^5.2.13",
"vite-plugin-solid": "^2.10.2",
"vite-plugin-static-copy": "^1.0.5"
},
"dependencies": {
"semver": "^7.5.1",
"solid-js": "^1.8.20",
"yargs-parser": "^21.1.1"
}
}

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

View File

@ -1,141 +0,0 @@
import type { ExportSetting } from './settings';
/*
* Variables
* - ${attachmentFolderPath} --> obsidian' settings.
*
* /User/aaa/Documents/test.pdf
* - ${outputDir} --> /User/aaa/Documents/
* - ${outputPath} --> /User/aaa/Documents/test.pdf
* - ${outputFileName} --> test
* - ${outputFileFullName} --> test.pdf
*
* /User/aaa/Documents/test.pdf
* - ${currentDir} --> /User/aaa/Documents/
* - ${currentPath} --> /User/aaa/Documents/test.pdf
* - ${currentFileName} --> test
* - ${CurrentFileFullName} --> test.pdf
*/
export default {
'Markdown': {
name: 'Markdown',
type: 'pandoc',
arguments:
'-f ${fromFormat} --resource-path="${currentDir}" --resource-path="${attachmentFolderPath}" --lua-filter="${luaDir}/markdown.lua" -s -o "${outputPath}" -t commonmark_x-attributes',
extension: '.md',
},
'Markdown (Hugo)': {
name: 'Markdown (Hugo)',
type: 'pandoc',
arguments:
'-f ${fromFormat} --resource-path="${currentDir}" --resource-path="${attachmentFolderPath}" --lua-filter="${luaDir}/markdown+hugo.lua" -s -o "${outputPath}" -t commonmark_x-attributes',
extension: '.md',
},
'Html': {
name: 'Html',
type: 'pandoc',
arguments:
'-f ${fromFormat} --resource-path="${currentDir}" --resource-path="${attachmentFolderPath}" --lua-filter="${luaDir}/math_block.lua" --embed-resources --standalone --metadata title="${currentFileName}" -s -o "${outputPath}" -t html',
customArguments: '--mathjax="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-svg-full.js"',
extension: '.html',
},
'TextBundle': {
name: 'TextBundle',
type: 'pandoc',
arguments:
'-f ${fromFormat} --resource-path="${currentDir}" --resource-path="${attachmentFolderPath}" --lua-filter="${luaDir}/markdown.lua" -V media_dir="${outputDir}/${outputFileName}.textbundle/assets" -s -o "${outputDir}/${outputFileName}.textbundle/text.md" -t commonmark_x-attributes',
extension: '.md',
},
'Typst': {
name: 'Typst',
type: 'pandoc',
arguments:
'-f ${fromFormat} --resource-path="${currentDir}" --resource-path="${attachmentFolderPath}" --lua-filter="${luaDir}/markdown.lua" -s -o "${outputPath}" -t typst',
extension: '.typ',
},
'PDF': {
name: 'PDF',
type: 'pandoc',
arguments:
'-f ${fromFormat} --resource-path="${currentDir}" --resource-path="${attachmentFolderPath}" --lua-filter="${luaDir}/pdf.lua" ${ options.textemplate ? `--resource-path="${pluginDir}/textemplate" --template="${options.textemplate}"` : ` ` } -o "${outputPath}" -t pdf',
customArguments: '--pdf-engine=pdflatex',
optionsMeta: {
'textemplate': 'preset:textemplate', // reference from `PresetOptionsMeta` in `src/settings.ts`
},
extension: '.pdf',
},
'Word (.docx)': {
name: 'Word (.docx)',
type: 'pandoc',
arguments: '-f ${fromFormat} --resource-path="${currentDir}" --resource-path="${attachmentFolderPath}" -o "${outputPath}" -t docx',
extension: '.docx',
},
'OpenOffice': {
name: 'OpenOffice',
type: 'pandoc',
arguments: '-f ${fromFormat} --resource-path="${currentDir}" --resource-path="${attachmentFolderPath}" -o "${outputPath}" -t odt',
extension: '.odt',
},
'RTF': {
name: 'RTF',
type: 'pandoc',
arguments: '-f ${fromFormat} --resource-path="${currentDir}" --resource-path="${attachmentFolderPath}" -s -o "${outputPath}" -t rtf',
extension: '.rtf',
},
'Epub': {
name: 'Epub',
type: 'pandoc',
arguments: '-f ${fromFormat} --resource-path="${currentDir}" --resource-path="${attachmentFolderPath}" -o "${outputPath}" -t epub',
extension: '.epub',
},
'Latex': {
name: 'Latex',
type: 'pandoc',
arguments:
'-f ${fromFormat} --resource-path="${currentDir}" --resource-path="${attachmentFolderPath}" ${ options.textemplate ? `--resource-path="${pluginDir}/textemplate" --template="${options.textemplate}"` : ` ` } --extract-media="${outputDir}" -s -o "${outputPath}" -t latex',
optionsMeta: {
'textemplate': 'preset:textemplate', // reference from `PresetOptionsMeta` in `src/settings.ts`
},
extension: '.tex',
},
'Media Wiki': {
name: 'Media Wiki',
type: 'pandoc',
arguments:
'-f ${fromFormat} --resource-path="${currentDir}" --resource-path="${attachmentFolderPath}" -s -o "${outputPath}" -t mediawiki',
extension: '.mediawiki',
},
'reStructuredText': {
name: 'reStructuredText',
type: 'pandoc',
arguments: '-f ${fromFormat} --resource-path="${currentDir}" --resource-path="${attachmentFolderPath}" -s -o "${outputPath}" -t rst',
extension: '.rst',
},
'Textile': {
name: 'Textile',
type: 'pandoc',
arguments:
'-f ${fromFormat} --resource-path="${currentDir}" --resource-path="${attachmentFolderPath}" -s -o "${outputPath}" -t textile',
extension: '.textile',
},
'OPML': {
name: 'OPML',
type: 'pandoc',
arguments: '-f ${fromFormat} --resource-path="${currentDir}" --resource-path="${attachmentFolderPath}" -s -o "${outputPath}" -t opml',
extension: '.opml',
},
'Bibliography (.bib)': {
name: 'Bibliography',
type: 'pandoc',
arguments:
'-f ${fromFormat} --resource-path="${currentDir}" --resource-path="${attachmentFolderPath}" --lua-filter="${luaDir}/citefilter.lua" -o "${outputPath}" --to=bibtex "${currentPath}"',
extension: '.bib',
},
'Custom': {
name: 'Custom',
type: 'custom',
command: 'your command',
targetFileExtensions: '.ext',
},
} satisfies Record<string, ExportSetting> as Record<string, ExportSetting>;

View File

@ -1,234 +0,0 @@
import * as ct from 'electron';
import * as fs from 'fs';
import process from 'process';
import path from 'path';
import argsParser from 'yargs-parser';
import { Variables, ExportSetting, extractDefaultExtension as extractExtension, createEnv } from './settings';
import { MessageBox } from './ui/message_box';
import { Notice, TFile } from 'obsidian';
import { exec, renderTemplate, getPlatformValue, trimQuotes } from './utils';
import ProgressBar from './ui/components/ProgressBar';
import type ExportPlugin from './main';
import pandoc from './pandoc';
export async function exportToOo(
plugin: ExportPlugin,
currentFile: TFile,
candidateOutputDirectory: string,
candidateOutputFileName: string | undefined,
setting: ExportSetting,
showOverwriteConfirmation?: boolean,
options?: unknown,
onSuccess?: () => void,
onFailure?: () => void,
beforeExport?: () => void
) {
const {
settings: globalSetting,
lang,
manifest,
app: {
vault: { adapter, config: obsidianConfig },
metadataCache,
},
} = plugin;
if (!candidateOutputFileName) {
const extension = extractExtension(setting);
candidateOutputFileName = `${currentFile.basename}${extension}`;
}
if (showOverwriteConfirmation == undefined) {
showOverwriteConfirmation = globalSetting.showOverwriteConfirmation;
}
const showExportProgressBar = globalSetting.showExportProgressBar;
/* Variables
* /User/aaa/Documents/test.pdf
* - ${outputDir} --> /User/aaa/Documents/
* - ${outputPath} --> /User/aaa/Documents/test.pdf
* - ${outputFileName} --> test
* - ${outputFileFullName} --> test.pdf
*
* /User/aaa/Documents/test.pdf
* - ${currentDir} --> /User/aaa/Documents/
* - ${currentPath} --> /User/aaa/Documents/test.pdf
* - ${CurrentFileName} --> test
* - ${CurrentFileFullName} --> test.pdf
*/
const vaultDir = adapter.getBasePath();
const pluginDir = `${vaultDir}/${manifest.dir}`;
const luaDir = `${pluginDir}/lua`;
const outputDir = candidateOutputDirectory;
const outputPath = `${outputDir}/${candidateOutputFileName}`;
const outputFileName = candidateOutputFileName.substring(0, candidateOutputFileName.lastIndexOf('.'));
const outputFileFullName = candidateOutputFileName;
const currentPath = adapter.getFullPath(currentFile.path);
const currentDir = path.dirname(currentPath);
const currentFileName = currentFile.basename;
const currentFileFullName = currentFile.name;
let attachmentFolderPath = obsidianConfig.attachmentFolderPath ?? '/';
if (attachmentFolderPath === '/') {
attachmentFolderPath = vaultDir;
} else if (attachmentFolderPath.startsWith('.')) {
attachmentFolderPath = path.join(currentDir, attachmentFolderPath.substring(1));
} else {
attachmentFolderPath = path.join(vaultDir, attachmentFolderPath);
}
let frontMatter: unknown = null;
try {
frontMatter = metadataCache.getCache(currentFile.path).frontmatter;
} catch (e) {
console.error(e);
}
const variables: Variables = {
pluginDir,
luaDir,
outputDir,
outputPath,
outputFileName,
outputFileFullName,
currentDir,
currentPath,
currentFileName,
currentFileFullName,
attachmentFolderPath,
vaultDir,
// date: new Date(currentFile.stat.ctime),
// lastMod: new Date(currentFile.stat.mtime),
// now: new Date()
metadata: frontMatter,
options,
fromFormat: app.vault.config.useMarkdownLinks ? 'markdown' : 'markdown+wikilinks_title_after_pipe',
};
const showCommandLineOutput = setting.type === 'custom' && setting.showCommandOutput;
const openExportedFileLocation = setting.openExportedFileLocation ?? globalSetting.openExportedFileLocation;
const openExportedFile = setting.openExportedFile ?? globalSetting.openExportedFile;
if (showOverwriteConfirmation && fs.existsSync(outputPath)) {
// const msgBox = new MessageBox(this.app, {
// message: lang.overwriteConfirmationDialog.message(outputDir),
// title: lang.overwriteConfirmationDialog.title(outputFileFullName),
// buttons: 'OkCancel',
// buttonsLabel: {
// ok: lang.overwriteConfirmationDialog.replace,
// },
// buttonsClass: {
// ok: 'mod-warning'
// },
// callback: {
// ok: () => doExport()
// }
// });
// msgBox.open();
const result = await ct.remote.dialog.showSaveDialog({
title: lang.overwriteConfirmationDialog.title(outputFileFullName),
defaultPath: outputPath,
properties: ['showOverwriteConfirmation', 'createDirectory'],
});
if (result.canceled) {
return;
}
variables.outputPath = result.filePath;
variables.outputDir = path.dirname(variables.outputPath);
variables.outputFileFullName = path.basename(variables.outputPath);
variables.outputFileName = path.basename(variables.outputFileFullName, path.extname(variables.outputFileFullName));
}
// show progress
let progressBarHide: (() => void) | undefined = undefined;
if (showExportProgressBar) {
beforeExport?.();
progressBarHide = ProgressBar.show(lang.preparing(variables.outputFileFullName));
}
// process Environment variables..
const env = (variables.env = createEnv(getPlatformValue(globalSetting.env) ?? {}, variables));
let pandocPath = pandoc.normalizePath(getPlatformValue(globalSetting.pandocPath));
if (process.platform === 'win32') {
// https://github.com/mokeyish/obsidian-enhancing-export/issues/153
pandocPath = pandocPath.replaceAll('\\', '/');
const pathKeys: Array<keyof Variables> = [
'pluginDir',
'luaDir',
'outputDir',
'outputPath',
'currentDir',
'currentPath',
'attachmentFolderPath',
'vaultDir',
];
for (const pathKey of pathKeys) {
const path = variables[pathKey] as string;
variables[pathKey] = path.replaceAll('\\', '/');
}
}
const cmdTpl =
setting.type === 'pandoc'
? `${pandocPath} "\${currentPath}" ${setting.arguments ?? ''} ${setting.customArguments ?? ''}`
: setting.command;
const cmd = renderTemplate(cmdTpl, variables);
const args = argsParser(cmd.match(/(?:[^\s"]+|"[^"]*")+/g), {
alias: {
output: ['o'],
},
});
const actualOutputPath = path.normalize(trimQuotes(args.output));
const actualOutputDir = path.dirname(actualOutputPath);
if (!fs.existsSync(actualOutputDir)) {
fs.mkdirSync(actualOutputDir);
}
try {
console.log(`[${plugin.manifest.name}]: export command and options:`, {
cmd,
options: { cwd: variables.currentDir, env },
});
await exec(cmd, { cwd: variables.currentDir, env });
progressBarHide?.();
const next = async () => {
if (openExportedFileLocation) {
setTimeout(() => {
ct.remote.shell.showItemInFolder(actualOutputPath);
}, 1000);
}
if (openExportedFile) {
await ct.remote.shell.openPath(actualOutputPath);
}
if (setting.type === 'pandoc' && setting.runCommand === true && setting.command) {
const extCmd = renderTemplate(setting.command, variables);
await exec(extCmd, { cwd: variables.currentDir, env });
}
// success
onSuccess && onSuccess();
};
if (showCommandLineOutput) {
const box = new MessageBox(app, lang.exportCommandOutputMessage(cmd));
box.onClose = next;
box.open();
} else {
new Notice(lang.exportSuccessNotice(variables.outputFileFullName), 1500);
await next();
}
} catch (err) {
progressBarHide?.();
new MessageBox(app, lang.exportErrorOutputMessage(cmd, err)).open();
onFailure && onFailure();
}
}

View File

@ -1,79 +0,0 @@
import type { Plugin } from 'obsidian';
import { debounce, Platform } from 'obsidian';
import { normalize, join } from 'path';
declare global {
interface HmrOptions {
watchFiles?: Array<'main.js' | 'manifest.json' | 'styles.css'> | string[];
}
interface Window {
hmr(plugin: Plugin, options?: HmrOptions): void;
}
}
Window.prototype.hmr = function (plugin: Plugin, options?: HmrOptions): void {
if (Platform.isMobile) {
return;
}
console.log(`[hmr: ${plugin.manifest.name}]`, new Date());
options ??= {};
options.watchFiles ??= ['main.js', 'manifest.json', 'styles.css'];
const { watchFiles } = options;
const {
app: {
vault: { adapter },
plugins,
},
manifest: { dir: pluginDir, id },
} = plugin;
const {
app: { vault },
} = plugin;
const restartPlugin = async () => {
const dbgKey = 'debug-plugin';
const oldDebug = localStorage.getItem(dbgKey);
try {
localStorage.setItem(dbgKey, '1');
await plugins.disablePlugin(id);
await plugins.enablePlugin(id);
} finally {
if (oldDebug) {
localStorage.setItem(dbgKey, oldDebug);
} else {
localStorage.removeItem(dbgKey);
}
}
};
const entry = normalize(join(pluginDir, 'main.js'));
const onChange = debounce(
async (file: string) => {
if (file.startsWith(pluginDir)) {
if (!(await adapter.exists(entry))) {
return;
}
if (file === pluginDir) {
// reload
} else if (watchFiles?.length > 0) {
if (!watchFiles.some(o => file.endsWith(o))) {
return;
}
}
await restartPlugin();
}
},
500,
true
);
plugin.registerEvent(vault.on('raw', onChange));
plugin.register(() => adapter.stopWatchPath(pluginDir));
adapter.startWatchPath(pluginDir);
};
export {};

View File

@ -1,71 +0,0 @@
import { strTpl } from '../utils';
import type { Lang } from '.';
export default {
exportToOo: 'Export to...',
exportSuccessNotice: strTpl`Export der Datei ${0} erfolgreich!`,
exportCommandOutputMessage: strTpl`Command: ${0}`,
exportErrorOutputMessage: strTpl`Command: ${0}Fehler:${1}`,
exportWithPrevious: 'Exportiere mit Vorherigem',
pleaseOpenFile: 'Bitte öffne zunächst eine Datei.',
preparing: strTpl`generating "${0}"...`,
exportDialog: {
exportTo: 'Exportiere nach',
fileName: 'Dateiname',
title: strTpl`Export to ${0}`,
export: 'Export',
selectExportFolder: 'Zielordner auswählen',
overwriteConfirmation: 'Überschreibe den Zielordner',
type: 'Typ',
},
messageBox: {
yes: 'Ja',
no: 'Nein',
ok: 'Ok',
cancel: 'Abbrechen',
},
overwriteConfirmationDialog: {
replace: 'Ersetze',
title: strTpl`"${0}" existiert bereits. Soll er ersetzt werden?`,
message: strTpl`Eine Datei oder ein Ordner mit dem gleichen Namen existiert bereits im Ordner "${0}". Das Ersetzen wird die jetzigen Inhalte überschreiben.`,
},
settingTab: {
general: 'Allgemein',
name: 'Name',
title: 'Export-Einstellungen',
pandocVersion: strTpl`Version: ${0}`,
pandocVersionWithWarning: strTpl`Version: ${0}, please upgrade version to ${1}`,
pandocNotFound:
'Pandoc.exe wurde nicht gefunden. Bitte geben Sie den Pfad zur Pandoc.exe ein oder fügen Sie ihn den Window Systemumgebungsvariablen hinzu.',
defaultFolderForExportedFile: 'Standardordner für exportierte Dateien',
openExportedFileLocation: 'Speicherort der exportierten Datei öffnen',
openExportedFile: 'Exportierte Datei öffnen',
pandocPath: 'Pfad zur Datei Pandoc.exe',
pandocPathPlaceholder: '(Automatische Erkennung)',
editCommandTemplate: 'Befehlsvorlage bearbeiten',
chooseCommandTemplate: 'Vorlage auswählen',
customLocation: 'Benutzerdefinierter Speicherort',
template: 'Vorlage',
command: 'Befehl',
reset: 'Zurücksetzen',
auto: 'Auto',
add: 'Hinzufügen',
remove: 'Entfernen',
rename: 'Umbenennen',
sameFolderWithCurrentFile: 'Der gleiche Ordner mit der aktuellen Datei',
afterExport: 'Nach dem Export',
targetFileExtensions: 'Dateinamenserweiterung der Zieldatei',
targetFileExtensionsTip: '(Mit Leerzeichen getrennt)',
showCommandOutput: 'Zeige die Ausgabe des Befehls',
runCommand: 'Starte den Befehl',
extraArguments: 'Zusätzliche Parameter',
save: 'Speichern',
new: 'Neu',
arguments: 'Parameter',
advanced: 'Advanced',
environmentVariables: 'Environment Variables',
environmentVariablesDesc: 'Define the Environment Variables for exporting.',
ShowExportProgressBar: 'Show export progressBar',
},
} satisfies Lang;

View File

@ -1,69 +0,0 @@
import { strTpl } from '../utils';
export default {
exportToOo: 'Export to...',
exportSuccessNotice: strTpl`Export file ${0} success!`,
exportCommandOutputMessage: strTpl`Command: ${0}`,
exportErrorOutputMessage: strTpl`Command: ${0}Error:${1}`,
exportWithPrevious: 'Export with Previous',
pleaseOpenFile: 'Please open a file first.',
preparing: strTpl`generating "${0}"...`,
exportDialog: {
exportTo: 'Export to',
fileName: 'File Name',
title: strTpl`Export to ${0}`,
export: 'Export',
selectExportFolder: 'Please select an export folder.',
overwriteConfirmation: 'Overwrite confirmation',
type: 'Type',
},
messageBox: {
yes: 'Yes',
no: 'No',
ok: 'Ok',
cancel: 'Cancel',
},
overwriteConfirmationDialog: {
replace: 'Replace',
title: strTpl`"${0}" already exists. Do you want to replace it?`,
message: strTpl`A file or folder with the same name already exists in the folder "${0}". Replacing it will overwrite its current contents.`,
},
settingTab: {
general: 'General',
name: 'Name',
title: 'Export Settings',
pandocVersion: strTpl`Version: ${0}`,
pandocVersionWithWarning: strTpl`Version: ${0}, please upgrade version to ${1}`,
pandocNotFound: 'Pandoc not found, please fill in the Pandoc file path, or add it to the system environment variables.',
defaultFolderForExportedFile: 'Default Folder for Exported File',
openExportedFileLocation: 'Open exported file location',
ShowExportProgressBar: 'Show export progress bar',
openExportedFile: 'Open exported file',
pandocPath: 'Pandoc path',
pandocPathPlaceholder: '(Auto Detect)',
editCommandTemplate: 'Edit Command Template',
chooseCommandTemplate: 'Choose template',
customLocation: 'Custom location',
template: 'Template',
command: 'Command',
reset: 'Reset',
auto: 'Auto',
add: 'Add',
remove: 'Remove',
rename: 'Rename',
sameFolderWithCurrentFile: 'Same folder with current file',
afterExport: 'After Export',
targetFileExtensions: 'Target file extensions',
targetFileExtensionsTip: '(Separated by whitespace)',
showCommandOutput: 'Show command output',
runCommand: 'Run command',
extraArguments: 'Extra arguments',
save: 'Save',
new: 'New',
arguments: 'Arguments',
advanced: 'Advanced',
environmentVariables: 'Environment Variables',
environmentVariablesDesc: 'Define the Environment Variables for exporting.',
},
};

View File

@ -1,27 +0,0 @@
import zhCN from './zh-CN';
import enUS from './en-US';
import deDE from './de-DE';
import { moment } from 'obsidian';
export type Lang = typeof enUS;
export default {
'de-DE': deDE,
'en-US': enUS,
'zh-CN': zhCN,
get current() {
const langIds = Object.keys(this);
const locale = moment.locale().toLowerCase();
let langId = langIds.find(id => id.toLowerCase() === locale.toLowerCase());
if (langId) {
return this[langId];
}
const localePrefix = locale.split('-')[0];
langId = langIds.find(id => id.toLowerCase().startsWith(localePrefix));
if (langId) {
return this[langId];
}
return this['en-US'];
},
};

View File

@ -1,70 +0,0 @@
import { strTpl } from '../utils';
import type { Lang } from '.';
export default {
exportToOo: '导出为......',
exportWithPrevious: '使用上一次设置导出',
exportSuccessNotice: strTpl`导出文件 ${0} 成功!`,
exportCommandOutputMessage: strTpl`命令:${0}`,
exportErrorOutputMessage: strTpl`命令 ${0},错误:${1}`,
pleaseOpenFile: '请打开一个文件先。',
preparing: strTpl`正在生成 "${0}" ......`,
exportDialog: {
fileName: '文件名',
type: '类型',
exportTo: '导出到',
title: strTpl`导出为 ${0}`,
export: '导出',
selectExportFolder: '请选择导出文件夹',
overwriteConfirmation: '覆盖提示',
},
messageBox: {
yes: '是',
no: '否',
ok: '确认',
cancel: '取消',
},
overwriteConfirmationDialog: {
replace: '替换',
title: strTpl`"${0}" 已经存在。您要替换它吗?`,
message: strTpl`"${0}" 文件夹中已有相同的文件或文件夹,若替换,则会覆盖其当前内容。`,
},
settingTab: {
title: '导出设置',
general: '通用',
name: '名称',
customLocation: '自定义',
pandocVersion: strTpl`版本: ${0}`,
pandocVersionWithWarning: strTpl`Version: ${0}, 请升级版本到 ${1}`,
pandocNotFound: '找不到 Pandoc请填写 Pandoc 文件路径,或者将其添加到系统环境变量中。',
pandocPath: 'Pandoc 路径',
defaultFolderForExportedFile: '默认的导出文件夹',
openExportedFileLocation: '打开导出文件所在目录',
sameFolderWithCurrentFile: '与原文件同一目录下',
openExportedFile: '打开导出文件',
pandocPathPlaceholder: '(自动检测)',
editCommandTemplate: '编辑命令模板',
chooseCommandTemplate: '选择模板',
afterExport: '导出后',
command: '命令',
arguments: '参数',
auto: '自动',
reset: '重置',
add: '添加',
remove: '移除',
rename: '重命名',
targetFileExtensions: '目标文件扩展名',
targetFileExtensionsTip: '(用空格分开)',
showCommandOutput: '显示命令行输出',
runCommand: '运行自定义命令',
extraArguments: '自定义参数',
save: '保存',
new: '新建',
template: '模板',
advanced: '高级',
environmentVariables: '环境变量',
environmentVariablesDesc: '定义导出的环境变量.',
ShowExportProgressBar: '显示导出进度条',
},
} satisfies Lang;

View File

@ -1,153 +0,0 @@
import { App, Menu, Plugin, PluginManifest, TFile, Notice, debounce } from 'obsidian';
import { UniversalExportPluginSettings, ExportSetting, DEFAULT_SETTINGS, DEFAULT_ENV } from './settings';
// import { ExportSettingTab, ExportDialog } from './ui/legacy';
import { ExportSettingTab, ExportDialog } from './ui';
import { exportToOo } from './exporto0o';
import { getPlatformValue, PlatformKey } from './utils';
import lang, { Lang } from './lang';
import path from 'path';
import resources from './resources';
import './styles.css';
export default class UniversalExportPlugin extends Plugin {
settings: UniversalExportPluginSettings;
lang: Lang;
constructor(app: App, manifest: PluginManifest) {
super(app, manifest);
this.lang = lang.current;
this.saveSettings = debounce(this.saveSettings.bind(this), 1000, true) as unknown as typeof this.saveSettings;
}
async onload() {
await this.releaseResources();
await this.loadSettings();
const { lang } = this;
this.addSettingTab(new ExportSettingTab(this));
this.addCommand({
id: 'obsidian-enhancing-export:export',
name: lang.exportToOo,
icon: 'document',
callback: () => {
const file = this.app.workspace.getActiveFile();
if (file) {
ExportDialog.show(this, file);
} else {
new Notice(lang.pleaseOpenFile, 2000);
}
},
});
this.addCommand({
id: 'obsidian-enhancing-export:export-with-previous',
name: lang.exportWithPrevious,
icon: 'document',
callback: async () => {
const file = this.app.workspace.getActiveFile();
if (file) {
if (this.settings.lastExportType && this.settings.lastExportDirectory) {
const setting = this.settings.items.find(s => s.name === this.settings.lastExportType);
if (setting) {
await exportToOo(this, file, getPlatformValue(this.settings.lastExportDirectory), undefined, setting);
return;
}
}
ExportDialog.show(this, file);
} else {
new Notice(lang.pleaseOpenFile, 2000);
}
},
});
this.registerEvent(
this.app.workspace.on('file-menu', (menu: Menu, file) => {
if (file instanceof TFile) {
menu
.addItem(item => {
item
.setTitle(lang.exportToOo)
.setIcon('document')
.onClick((): void => {
ExportDialog.show(this, file);
});
})
.addSeparator();
}
})
);
// this.downloadLuaScripts().then();
if (import.meta.env.DEV) {
window.hmr && window.hmr(this);
}
}
public async resetSettings(): Promise<void> {
this.settings = {
...JSON.parse(JSON.stringify(DEFAULT_SETTINGS)),
lastExportDirectory: this.settings.lastExportDirectory,
};
await this.saveSettings();
}
public async loadSettings(): Promise<void> {
const settings: UniversalExportPluginSettings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
settings.items.forEach(v => {
Object.assign(v, Object.assign({}, DEFAULT_SETTINGS.items.find(o => o.name === v.name) ?? {}, v));
});
for (const item of DEFAULT_SETTINGS.items) {
if (settings.items.every(o => o.name !== item.name)) {
settings.items.push(item);
}
}
this.settings = settings;
}
public async saveSettings(): Promise<void> {
console.log('[obsidian-enhancing-export] saveSettings', this.settings);
const settings: UniversalExportPluginSettings = JSON.parse(JSON.stringify(this.settings));
settings.items.forEach(v => {
const def = DEFAULT_SETTINGS.items.find(o => o.name === v.name);
if (def) {
Object.keys(v).forEach((k: keyof ExportSetting) => {
if (k !== 'name' && JSON.stringify(v[k]) === JSON.stringify(def[k])) {
delete v[k];
}
});
}
});
if (settings.env) {
for (const platform of Object.keys(settings.env) as PlatformKey[]) {
const env = settings.env[platform];
if (JSON.stringify(env) === JSON.stringify(DEFAULT_ENV[platform])) {
delete settings.env[platform];
continue;
}
const refEnv = getPlatformValue(DEFAULT_ENV, platform);
for (const [name, value] of Object.entries(env)) {
if (value === refEnv[name]) {
delete env[name];
}
}
if (Object.keys(env).length === 0) {
delete settings.env[platform];
}
}
}
await this.saveData(settings);
}
async releaseResources(): Promise<void> {
const { adapter } = this.app.vault;
for (const [dir, res] of resources) {
const resDir = path.join(this.manifest.dir, dir);
await adapter.mkdir(resDir);
for (const [fileName, bytes] of res) {
const filePath = path.join(resDir, fileName);
await adapter.writeBinary(filePath, bytes);
}
}
resources.length = 0;
}
}

View File

@ -1,28 +0,0 @@
import { exec } from './utils';
import semver from 'semver/preload';
export const normalizePandocPath = (path?: string) => (path?.includes(' ') ? `"${path}"` : `${path ?? 'pandoc'}`);
export async function getPandocVersion(path?: string, env?: Record<string, string>) {
path = normalizePandocPath(path);
let version = await exec(`${path} --version`, { env });
version = version.substring(0, version.indexOf('\n')).replace('pandoc.exe', '').replace('pandoc', '').trim();
let dotCount = [...version].filter(c => c === '.').length;
if (dotCount === 1) {
version = `${version}.0`;
} else {
while (dotCount > 2) {
version = version.substring(0, version.lastIndexOf('.'));
dotCount -= 1;
}
}
return semver.parse(version, true);
}
export const PANDOC_REQUIRED_VERSION = '3.1.7';
export default {
normalizePath: normalizePandocPath,
getVersion: getPandocVersion,
requiredVersion: PANDOC_REQUIRED_VERSION,
};

View File

@ -1,9 +0,0 @@
const embed = (dir: string, res: Record<string, { default: Uint8Array }>) =>
[dir, Object.entries(res).map(([k, m]) => [k.substring(dir.length + 3), m.default] as const)] as const;
// The embedded resource
export default [
// For other file types, the Loader must be configured in the <root>/vite.config.ts.
embed('lua', import.meta.glob<{ default: Uint8Array }>('../lua/*.lua', { eager: true })),
embed('textemplate', import.meta.glob<{ default: Uint8Array }>('../textemplate/*.{tex,sty}', { eager: true })),
];

View File

@ -1,172 +0,0 @@
import export_templates from './export_templates';
import { setPlatformValue, PlatformValue, renderTemplate, getPlatformValue } from './utils';
import type { PropertyGridMeta } from './ui/components/PropertyGrid';
/*
* Variables
* /User/aaa/Documents/test.pdf
* - ${outputDir} --> /User/aaa/Documents/
* - ${outputPath} --> /User/aaa/Documents/test.pdf
* - ${outputFileName} --> test
* - ${outputFileFullName} --> test.pdf
*
* /User/aaa/Documents/test.pdf
* - ${currentDir} --> /User/aaa/Documents/
* - ${currentPath} --> /User/aaa/Documents/test.pdf
* - ${CurrentFileName} --> test
* - ${CurrentFileFullName} --> test.pdf
*/
export interface Variables extends Record<string, unknown> {
attachmentFolderPath: string;
pluginDir: string;
luaDir: string;
outputDir: string;
outputPath: string;
outputFileName: string;
outputFileFullName: string;
currentDir: string;
currentPath: string;
currentFileName: string;
currentFileFullName: string;
vaultDir: string;
// date: new Date(currentFile.stat.ctime),
// lastMod: new Date(currentFile.stat.mtime),
// now: new Date()
metadata?: unknown;
options?: unknown;
env?: Record<string, string>;
}
export interface UniversalExportPluginSettings {
version?: string;
pandocPath?: PlatformValue<string>;
showOverwriteConfirmation?: boolean;
defaultExportDirectoryMode: 'Auto' | 'Same' | 'Custom';
customDefaultExportDirectory?: PlatformValue<string>;
env: PlatformValue<Record<string, string>>;
items: ExportSetting[];
openExportedFile?: boolean; // open exported file after export
openExportedFileLocation?: boolean; // open exported file location after export
lastEditName?: string;
lastExportDirectory?: PlatformValue<string>;
lastExportType?: string;
showExportProgressBar?: boolean;
}
export type OptionsMeta = {
[optionsName: string]: PropertyGridMeta[string] | `preset:${keyof typeof PRESET_OPTIONS_META}`;
};
interface CommonExportSetting {
name: string;
openExportedFileLocation?: boolean; // open exported file location after export
openExportedFile?: boolean; // open exported file after export
optionsMeta?: OptionsMeta;
}
export interface PandocExportSetting extends CommonExportSetting {
type: 'pandoc';
arguments: string;
customArguments?: string;
extension: string;
runCommand?: boolean; // run command after export
command?: string; // command to run after export
}
export interface CustomExportSetting extends CommonExportSetting {
type: 'custom';
command: string;
targetFileExtensions?: string;
showCommandOutput?: boolean; // show command output in console after export
}
export type ExportSetting = PandocExportSetting | CustomExportSetting;
export const PRESET_OPTIONS_META: PropertyGridMeta = {
'textemplate': {
title: 'Latex Template',
type: 'dropdown',
options: [
{ name: 'None', value: null },
{ name: 'Dissertation', value: 'dissertation.tex' },
{ name: 'Academic Paper', value: 'neurips.tex' },
],
},
};
export const DEFAULT_ENV = (() => {
let env: PlatformValue<Record<string, string>> = {};
env = setPlatformValue(
env,
{
'HOME': '${HOME}',
'PATH': '${PATH}',
'TEXINPUTS': '${pluginDir}/textemplate/:', // It is necessary to **append** to the current TEXINPUTS wtih ":" - NOT REPLACE. TEXINPUTS contains the basic latex classes.
},
'*' // available for all platforms.
);
env = setPlatformValue(
env,
{
'TEXINPUTS': '${pluginDir}/textemplate/;', // Windows uses ; rather than : for appending
'PATH': '${HOME}\\AppData\\Local\\Pandoc;${PATH}',
},
'win32' // available for windows only.
);
env = setPlatformValue(
env,
{
'PATH': '/opt/homebrew/bin:/usr/local/bin:/Library/TeX/texbin:${PATH}', // Add HomebrewBin and TexBin. see: https://docs.brew.sh/Installation
},
'darwin' // for MacOS only.
);
return env;
})();
export const DEFAULT_SETTINGS: UniversalExportPluginSettings = {
items: Object.values(export_templates).filter(o => o.type !== 'custom'),
pandocPath: undefined,
defaultExportDirectoryMode: 'Auto',
openExportedFile: true,
env: DEFAULT_ENV,
showExportProgressBar: true,
};
export function extractDefaultExtension(s: ExportSetting): string {
if (s.type === 'pandoc') {
return s.extension;
} else if (s.type === 'custom') {
return s.targetFileExtensions?.split(',')[0];
}
return '';
}
export function createEnv(env: Record<string, string>, envVars?: Record<string, unknown>) {
env = Object.assign({}, getPlatformValue(DEFAULT_ENV), env);
envVars = Object.assign({ HOME: process.env['HOME'] ?? process.env['USERPROFILE'] }, process.env, envVars ?? {});
return Object.fromEntries(Object.entries(env).map(([n, v]) => [n, renderTemplate(v, envVars)]));
}
export function finalizeOptionsMeta(meta?: OptionsMeta): PropertyGridMeta {
if (meta) {
return Object.fromEntries(
Object.entries(meta).map(([optionsName, optionsMetaOrPresetName]) => [
optionsName,
typeof optionsMetaOrPresetName === 'string'
? PRESET_OPTIONS_META[optionsMetaOrPresetName.startsWith('preset:') ? optionsMetaOrPresetName.substring(7) : '']
: optionsMetaOrPresetName,
])
);
}
return {};
}

View File

@ -1,8 +0,0 @@
.setting-item.ex-setting-item {
border-top: unset;
padding-top: 0;
}
*[hidden] {
display: none;
}

View File

@ -1,141 +0,0 @@
import * as ct from 'electron';
import { TFile } from 'obsidian';
import { createSignal, createRoot, onCleanup, createMemo, untrack, createEffect, Show } from 'solid-js';
import { insert } from 'solid-js/web';
import type UniversalExportPlugin from '../main';
import { extractDefaultExtension as extractExtension, finalizeOptionsMeta } from '../settings';
import { setPlatformValue, getPlatformValue, } from '../utils';
import { exportToOo } from '../exporto0o';
import Modal from './components/Modal';
import Button from './components/Button';
import PropertyGrid, { createDefaultObject } from './components/PropertyGrid';
import Setting, {Text, DropDown, ExtraButton, Toggle} from './components/Setting';
const Dialog = (props: { plugin: UniversalExportPlugin, currentFile: TFile, onClose?: () => void }) => {
const { plugin: { app, settings: globalSetting, lang }, currentFile } = props;
const [hidden, setHidden] = createSignal(false);
const [showOverwriteConfirmation, setShowOverwriteConfirmation] = createSignal(globalSetting.showOverwriteConfirmation);
const [exportType, setExportType] = createSignal(globalSetting.lastExportType ?? globalSetting.items.first()?.name);
const [options, setOptions] = createSignal({});
const setting = createMemo(() => globalSetting.items.find(o => o.name === exportType()));
const extension = createMemo(() => extractExtension(setting()));
const title = createMemo(() => lang.exportDialog.title(setting().name));
const optionsMeta = createMemo(() => finalizeOptionsMeta(setting().optionsMeta));
const [candidateOutputDirectory, setCandidateOutputDirectory] = createSignal(`${getPlatformValue(globalSetting.lastExportDirectory) ?? ct.remote.app.getPath('documents')}`);
const [candidateOutputFileName, setCandidateOutputFileName] = createSignal(`${currentFile.basename}${extension()}`);
createEffect(() => {
const meta = optionsMeta();
setOptions(meta ? createDefaultObject(meta) : {});
});
createEffect(() => {
let fileName = untrack(candidateOutputFileName);
fileName = fileName.includes('.') ? fileName.substring(0, fileName.lastIndexOf('.')) : fileName;
setCandidateOutputFileName(`${fileName}${extension()}`);
});
const exportTypes = globalSetting.items.map(o => ({ name: o.name, value: o.name }));
if (globalSetting.defaultExportDirectoryMode === 'Same') {
const path = currentFile.vault.adapter.getBasePath() + '/' + currentFile.parent.path;
setCandidateOutputDirectory(path);
} else if (globalSetting.defaultExportDirectoryMode === 'Custom') {
setCandidateOutputDirectory(getPlatformValue(globalSetting.customDefaultExportDirectory));
}
const chooseFolder = async () => {
const retval = await ct.remote.dialog.showOpenDialog({
title: lang.exportDialog.selectExportFolder,
defaultPath: candidateOutputDirectory(),
properties: ['createDirectory', 'openDirectory'],
});
if (!retval.canceled && retval.filePaths?.length > 0) {
setCandidateOutputDirectory(retval.filePaths[0]);
}
};
const doExport = async () => {
const plugin = props.plugin;
setHidden(true);
await exportToOo(
plugin,
currentFile,
untrack(candidateOutputDirectory),
untrack(candidateOutputFileName),
untrack(setting),
untrack(showOverwriteConfirmation),
options(),
async () => {
globalSetting.showOverwriteConfirmation = untrack(showOverwriteConfirmation);
globalSetting.lastExportDirectory = setPlatformValue(globalSetting.lastExportDirectory, untrack(candidateOutputDirectory));
globalSetting.lastExportType = untrack(setting).name;
await plugin.saveSettings();
props.onClose && props.onClose();
},
() => {
setHidden(false);
}
);
};
return <>
<Modal app={app} title={title()} hidden={hidden()} onClose={props.onClose} >
<Setting name={lang.exportDialog.type}>
<DropDown options={exportTypes} onChange={(typ) => setExportType(typ)} selected={exportType()}/>
</Setting>
<Setting name={lang.exportDialog.fileName}>
<Text
title={candidateOutputFileName()}
value={candidateOutputFileName()}
onChange={(value) => setCandidateOutputFileName(value)}
/>
</Setting>
<Show when={optionsMeta()}>
<PropertyGrid meta={optionsMeta()} value={options()} onChange={ (o) => setOptions(o)}/>
</Show>
<Setting name={lang.exportDialog.exportTo}>
<Text title={candidateOutputDirectory()} value={candidateOutputDirectory()} disabled />
<ExtraButton icon='folder' onClick={chooseFolder} />
</Setting>
<Setting name={lang.exportDialog.overwriteConfirmation} class="mod-toggle">
<Toggle checked={showOverwriteConfirmation()} onChange={setShowOverwriteConfirmation} />
</Setting>
<div class="modal-button-container">
<Button cta={true} onClick={doExport}>{lang.exportDialog.export}</Button>
</div>
</Modal>
</>;
};
const show = (plugin: UniversalExportPlugin, currentFile: TFile) => createRoot(dispose => {
let disposed = false;
const cleanup = () => {
if (disposed) {
return;
}
disposed = true;
dispose();
};
const el = insert(document.body, () => <Dialog onClose={cleanup} plugin={plugin} currentFile={currentFile} />);
onCleanup(() => {
el instanceof Node && document.body.contains(el) && document.body.removeChild(el);
});
return cleanup;
});
export default {
show
};

View File

@ -1,362 +0,0 @@
import * as ct from 'electron';
import process from 'process';
import { PluginSettingTab } from 'obsidian';
import type { SemVer } from 'semver'
import type UniversalExportPlugin from '../main';
import {
CustomExportSetting,
ExportSetting,
PandocExportSetting,
createEnv,
DEFAULT_ENV
} from '../settings';
import { setPlatformValue, getPlatformValue } from '../utils';
import { createSignal, createRoot, onCleanup, createMemo, createEffect, Show, batch, Match, Switch, JSX } from 'solid-js';
import { createStore, produce } from 'solid-js/store';
import { insert, Dynamic } from 'solid-js/web';
import type { Lang } from '../lang';
import pandoc from '../pandoc';
import Modal from './components/Modal';
import Button from './components/Button';
import Setting, { Text, Toggle, ExtraButton, DropDown, TextArea } from './components/Setting';
import export_templates from '../export_templates';
const SettingTab = (props: { lang: Lang, plugin: UniversalExportPlugin }) => {
const { plugin, lang } = props;
const [settings, setSettings0] = createStore(plugin.settings);
const [pandocVersion, setPandocVersion] = createSignal<SemVer>();
const envVars = createMemo(() => Object.entries(Object.assign({}, getPlatformValue(DEFAULT_ENV), getPlatformValue(settings.env) ?? {})).map(([n, v]) => `${n}="${v}"`).join('\n'));
const setSettings: typeof setSettings0 = (...args: unknown[]) => {
(setSettings0 as ((...args: unknown[]) => void))(...args);
plugin.saveSettings();
};
const setEnvVars = (envItems: string) => {
try {
const env: Record<string, string> = {};
for (let line of envItems.split('\n')) {
line = line.trim();
const sepIdx = line.indexOf('=');
if (sepIdx > 0) {
const name = line.substring(0, sepIdx);
let value = line.substring(sepIdx + 1).trim();
if (value.startsWith('"') && value.endsWith('"')) {
value = value.substring(1, value.length - 1);
}
env[name] = value;
}
}
setSettings('env', setPlatformValue(settings.env ?? {}, env));
} catch (e) {
alert(e);
}
};
const currentCommandTemplate = createMemo(() => settings.items.find(v => v.name === settings.lastEditName) ?? settings.items.first());
const currentEditCommandTemplate = <T extends 'custom' | 'pandoc'>(type?: T) => {
const template = currentCommandTemplate();
return (type === undefined || type === template.type ? template : undefined) as T extends 'custom' ? CustomExportSetting : T extends 'pandoc' ? PandocExportSetting : ExportSetting;
};
const customDefaultExportDirectory = createMemo(() => getPlatformValue(settings.customDefaultExportDirectory));
const updateCurrentEditCommandTemplate = (update: (prev: Partial<ExportSetting>) => void) => {
const idx = settings.items.findIndex(v => v.name === settings.lastEditName);
setSettings('items', idx === -1 ? 0 : idx, produce(item => {
update(item);
return item;
}));
};
const pandocDescription = createMemo(() => {
const version = pandocVersion();
if (version) {
if (app.vault.config.useMarkdownLinks && version.compare(pandoc.requiredVersion) === -1) {
return lang.settingTab.pandocVersionWithWarning(pandoc.requiredVersion)
}
return lang.settingTab.pandocVersion(version)
}
return lang.settingTab.pandocNotFound;
});
const [modal, setModal] = createSignal<() => JSX.Element>();
const AddCommandTemplateModal = () => {
type TemplateKey = keyof typeof export_templates;
const [templateName, setTemplateName] = createSignal(Object.keys(export_templates)[0] as TemplateKey);
const [name, setName] = createSignal<string>();
const doAdd = () => {
const template = JSON.parse(JSON.stringify(export_templates[templateName()]));
template.name = name();
batch(() => {
setSettings('items', items => [...items, template]);
setSettings('lastEditName', template.name);
});
setModal(undefined);
};
return <>
<Modal app={app} title={lang.settingTab.new} onClose={() => setModal(undefined)}>
<Setting name={lang.settingTab.template}>
<DropDown
options={Object.entries(export_templates).map(([k, v]) => ({ name: v.name, value: k }))}
selected={name() ?? templateName()}
onChange={(v: TemplateKey) => setTemplateName(v)}
/>
</Setting>
<Setting name={lang.settingTab.name}>
<Text value={name() ?? ''} onChange={(value) => setName(value)} />
</Setting>
<div class="modal-button-container">
<Button cta={true} onClick={doAdd}>{lang.settingTab.save}</Button>
</div>
</Modal>
</>;
};
const RenameCommandTemplateModal = () => {
const [name, setName] = createSignal(currentEditCommandTemplate().name);
const doRename = () => {
batch(() => {
updateCurrentEditCommandTemplate((v) => v.name = name());
setSettings('lastEditName', name());
});
setModal(undefined);
};
return <>
<Modal app={app} title={lang.settingTab.rename} onClose={() => setModal(undefined)}>
<Setting name={lang.settingTab.name}>
<Text value={name() ?? ''} onChange={(value) => setName(value)} />
</Setting>
<div class="modal-button-container">
<Button cta={true} onClick={doRename}>{lang.settingTab.add}</Button>
</div>
</Modal>
</>;
};
const PandocCommandTempateEditBlock = () => {
const template = () => currentEditCommandTemplate('pandoc');
const updateTemplate = (update: (prev: Partial<PandocExportSetting>) => void) => {
updateCurrentEditCommandTemplate(prev => prev.type === 'pandoc' ? update(prev) : undefined);
};
return <>
<Setting name={lang.settingTab.arguments}>
<Text style="width: 100%" value={template().arguments ?? ''} onChange={(value) => updateTemplate(v => v.arguments = value)} />
</Setting>
<Setting name={lang.settingTab.extraArguments}>
<Text style="width: 100%" value={template().customArguments ?? ''} title={template().customArguments} onChange={(value) => updateTemplate(v => v.customArguments = value)} />
</Setting>
<Setting name={lang.settingTab.afterExport} heading={true} />
<Setting name={lang.settingTab.openExportedFileLocation}>
<Toggle checked={template().openExportedFileLocation ?? false} onChange={(checked) => updateTemplate(v => v.openExportedFileLocation = checked)} />
</Setting>
<Setting name={lang.settingTab.openExportedFile}>
<Toggle checked={template().openExportedFile ?? false} onChange={(checked) => updateTemplate(v => v.openExportedFile = checked)} />
</Setting>
<Setting name={lang.settingTab.runCommand}>
<Toggle checked={template().runCommand} onChange={(checked) => updateTemplate(v => v.runCommand = checked)} />
</Setting>
<Show when={template().runCommand}>
<Setting>
<Text style="width: 100%" value={template().command ?? ''} onChange={(value) => updateTemplate(v => v.command = value)} />
</Setting>
</Show>
</>;
};
const CustomCommandTempateEditBlock = () => {
const template = () => currentEditCommandTemplate('custom');
const updateTemplate = (update: (prev: Partial<CustomExportSetting>) => void) => {
updateCurrentEditCommandTemplate(prev => prev.type === 'custom' ? update(prev) : undefined);
};
return <>
<Setting name={lang.settingTab.command}>
<Text style="width: 100%" value={template().command} onChange={(value) => updateTemplate(v => v.command = value)} />
</Setting>
<Setting name={lang.settingTab.targetFileExtensions}>
<Text value={template().targetFileExtensions ?? ''} onChange={(value) => updateTemplate(v => v.targetFileExtensions = value)} />
</Setting>
<Setting name={lang.settingTab.afterExport} heading={true} />
<Setting name={lang.settingTab.showCommandOutput} >
<Toggle checked={template().showCommandOutput ?? false} onChange={(checked) => updateTemplate(v => v.showCommandOutput = checked)} />
</Setting>
<Setting name={lang.settingTab.openExportedFileLocation}>
<Toggle checked={template().openExportedFileLocation ?? false} onChange={(checked) => updateTemplate(v => v.openExportedFileLocation = checked)} />
</Setting>
<Setting name={lang.settingTab.openExportedFile}>
<Toggle checked={template().openExportedFile ?? false} onChange={(checked) => updateTemplate(v => v.openExportedFile = checked)} />
</Setting>
</>;
};
const resetSettings = async () => {
await plugin.resetSettings();
setSettings(plugin.settings);
};
const chooseCustomDefaultExportDirectory = async () => {
const retval = await ct.remote.dialog.showOpenDialog({
defaultPath: customDefaultExportDirectory() ?? ct.remote.app.getPath('documents'),
properties: ['createDirectory', 'openDirectory'],
});
if (!retval.canceled && retval.filePaths.length > 0) {
setSettings('customDefaultExportDirectory', v => setPlatformValue(v, retval.filePaths[0]));
}
};
const choosePandocPath = async () => {
const retval = await ct.remote.dialog.showOpenDialog({
filters: process.platform == 'win32' ? [{ extensions: ['exe'], name: 'pandoc' }]: undefined,
properties: ['openFile'],
});
if (!retval.canceled && retval.filePaths.length > 0) {
setSettings('pandocPath', (v) => setPlatformValue(v, retval.filePaths[0]));
}
};
createEffect(async () => {
try {
const env = createEnv(getPlatformValue(settings.env) ?? {});
setPandocVersion(await pandoc.getVersion(getPlatformValue(settings.pandocPath), env));
} catch {
setPandocVersion(undefined);
}
});
return <>
<Setting name={lang.settingTab.general} heading={true}>
<ExtraButton icon='reset' onClick={resetSettings} />
</Setting>
<Setting name={lang.settingTab.pandocPath} description={pandocDescription()}>
<Text
placeholder={lang.settingTab.pandocPathPlaceholder}
value={getPlatformValue(settings.pandocPath) ?? ''}
onChange={(value) => setSettings('pandocPath', (v) => setPlatformValue(v, value))}
/>
<ExtraButton icon="folder" onClick={choosePandocPath} />
</Setting>
<Setting name={lang.settingTab.defaultFolderForExportedFile}>
<DropDown options={[
{ name: lang.settingTab.auto, value: 'Auto' },
{ name: lang.settingTab.sameFolderWithCurrentFile, value: 'Same' },
{ name: lang.settingTab.customLocation, value: 'Custom' }
]} selected={settings.defaultExportDirectoryMode} onChange={(v: 'Auto' | 'Same' | 'Custom') => setSettings('defaultExportDirectoryMode', v)} />
</Setting>
<Show when={settings.defaultExportDirectoryMode === 'Custom'}>
<Setting>
<Text value={customDefaultExportDirectory() ?? ''} title={customDefaultExportDirectory()} />
<ExtraButton icon="folder" onClick={chooseCustomDefaultExportDirectory} />
</Setting>
</Show>
<Setting name={lang.settingTab.openExportedFileLocation}>
<Toggle
checked={settings.openExportedFileLocation}
onChange={(v) => setSettings('openExportedFileLocation', v)}
/>
</Setting>
<Setting name={lang.settingTab.openExportedFile} >
<Toggle
checked={settings.openExportedFile}
onChange={(v) => setSettings('openExportedFile', v)} />
</Setting>
<Setting name={lang.settingTab.ShowExportProgressBar}>
<Toggle
checked={settings.showExportProgressBar}
onChange={(v) => setSettings('showExportProgressBar', v)}
/>
</Setting>
<Setting name={lang.settingTab.editCommandTemplate} heading={true} />
<Setting name={lang.settingTab.chooseCommandTemplate}>
<DropDown
options={settings.items.map(o => ({ name: o.name, value: o.name }))}
selected={settings.lastEditName}
onChange={(v) => setSettings('lastEditName', v)}
/>
<ExtraButton
icon="plus"
tooltip={lang.settingTab.add}
onClick={() => setModal(() => AddCommandTemplateModal)} />
<ExtraButton
icon="pencil"
tooltip={lang.settingTab.rename}
onClick={() => setModal(() => RenameCommandTemplateModal)} />
<ExtraButton
icon="trash"
tooltip={lang.settingTab.remove}
onClick={() => batch(() => {
setSettings('items', (items) => items.filter(n => n.name !== currentEditCommandTemplate()?.name));
setSettings('lastEditName', settings.items.first()?.name);
})} />
</Setting>
<Switch>
<Match when={currentEditCommandTemplate('pandoc')}>
<PandocCommandTempateEditBlock />
</Match>
<Match when={currentEditCommandTemplate('custom')}>
<CustomCommandTempateEditBlock />
</Match>
</Switch>
<Setting name={lang.settingTab.advanced} heading={true} />
{/* TODO:// optimize UI as https://www.jetbrains.com/help/idea/absolute-path-variables.html */}
<Setting name={lang.settingTab.environmentVariables} description={lang.settingTab.environmentVariablesDesc}>
<TextArea
style='width: 100%;height: 5em'
value={envVars()}
onChange={setEnvVars}
/>
</Setting>
<Show when={modal()}>
<Dynamic component={modal()} ref={(el: Node) => document.body.appendChild(el)} />
</Show>
</>;
};
export default class extends PluginSettingTab {
plugin: UniversalExportPlugin;
#dispose?: () => void;
public get lang() {
return this.plugin.lang;
}
constructor(plugin: UniversalExportPlugin) {
super(plugin.app, plugin);
this.plugin = plugin;
this.name = this.plugin.lang.settingTab.title;
}
display() {
this.#dispose = createRoot(dispose => {
insert(this.containerEl, <SettingTab plugin={this.plugin} lang={this.lang} />);
onCleanup(() => {
this.containerEl.empty();
});
return dispose;
});
}
hide() {
this.#dispose();
}
}

View File

@ -1,5 +0,0 @@
import type { ParentProps } from 'solid-js/types';
export default (props: ParentProps<{ cta?: boolean, onClick?: () => void}> ) => {
return <button classList={{'mod-cta': props.cta}} onClick={props.onClick}>{props.children}</button>;
};

View File

@ -1,12 +0,0 @@
import { setIcon } from 'obsidian';
import type { JSX } from 'solid-js/jsx-runtime';
export default (props: { name: string, title?: string, class?: string, onClick?: JSX.EventHandlerUnion<HTMLDivElement, MouseEvent> }) => {
return <div
ref={(el) => setIcon(el, props.name)}
class={props.class}
classList={{ 'clickable-icon': !!props.onClick }}
onClick={props.onClick}
title={props.title}
/>;
};

View File

@ -1,53 +0,0 @@
import { App, Modal } from 'obsidian';
import { JSX, createEffect, onCleanup, onMount } from 'solid-js';
import { insert } from 'solid-js/web';
export default (props: {
app: App,
title?: JSX.Element,
children: JSX.Element,
classList?: {
[k: string]: boolean;
},
hidden?: boolean,
onClose?: () => void
}) => {
const modal = new Modal(props.app);
let classes: string[] = [];
let clean = false;
createEffect(() => {
insert(modal.titleEl, () => props.title);
});
createEffect(() => {
insert(modal.contentEl, () => props.children);
});
createEffect(() => {
const newClasses = Object.entries(props.classList ?? {}).filter(([, v]) => v).map(([k,]) => k);
if (classes.length > 0) {
modal.containerEl.removeClasses(classes);
}
if (newClasses.length > 0) {
modal.containerEl.addClasses(newClasses);
}
classes = newClasses;
});
createEffect(() => {
modal.containerEl.style.display = props.hidden ? 'None' : '';
});
modal.onClose = () => {
if (clean) return;
clean = true;
props.onClose();
};
onMount(() => modal.open());
onCleanup(() => {
if (!clean) {
modal.close();
}
});
return document.createTextNode('');
};

View File

@ -1,36 +0,0 @@
import { createRoot, onCleanup } from 'solid-js';
import { insert } from 'solid-js/web';
const ProgressBar = (props: { message: string, ref: HTMLDivElement }) => {
return <>
<div ref={props.ref} class="progress-bar">
<div class="progress-bar-message u-center-text">{props.message}</div>
<div class="progress-bar-indicator">
<div class="progress-bar-line"></div>
<div class="progress-bar-subline" style="display: none;"></div>
<div class="progress-bar-subline mod-increase"></div>
<div class="progress-bar-subline mod-decrease"></div>
</div>
</div>
</>
}
const show = (message: string) => createRoot(dispose => {
let disposed = false;
const cleanup = () => {
if (disposed) {
return;
}
disposed = true;
dispose();
};
let el: HTMLDivElement;
insert(document.body, () => <ProgressBar ref={el} message={message} />);
onCleanup(() => {
el instanceof Node && document.body.contains(el) && document.body.removeChild(el);
});
return cleanup;
});
export default { show }

View File

@ -1,155 +0,0 @@
import { FileFilter, remote } from 'electron';
import { For, JSX, createEffect, createSignal, untrack } from 'solid-js';
import Setting, { Toggle, DropDown, Text, ExtraButton } from './Setting';
const editors = {
checkbox: (props: { meta: CheckboxMeta, onChange?: (value: unknown) => void }) => {
return <>
<Setting name={props.meta.title} description={props.meta.description}>
<Toggle checked={getDefaultValue(props.meta)} onChange={props.onChange} />
</Setting>
</>;
},
textInput: (props: { meta: TextInputMeta, onChange?: (value: unknown) => void }) => {
return <>
<Setting name={props.meta.title} description={props.meta.description}>
<Text value={getDefaultValue(props.meta)} onChange={props.onChange} />
</Setting>
</>;
},
dropdown: (props: { meta: DropDownMeta, onChange?: (value: unknown) => void }) => {
return <>
<Setting name={props.meta.title} description={props.meta.description}>
<DropDown selected={getDefaultValue(props.meta)} options={props.meta.options} onChange={(v) => props.onChange(v)} />
</Setting>
</>;
},
fileSelectDialog: (props: { meta: FileSelectDialogMeta, onChange?: (value: unknown) => void }) => {
const [filePath, setFilePath] = createSignal<string>(getDefaultValue(props.meta));
const chooseFile = async () => {
const retval = await remote.dialog.showOpenDialog({
properties: ['openFile'],
filters: props.meta.filters
});
if (!retval.canceled && retval.filePaths.length > 0) {
setFilePath(retval.filePaths[0]);
props.onChange && props.onChange(untrack(filePath));
}
};
return <>
<Setting name={props.meta.title} description={props.meta.description}>
<Text value={filePath() ?? ''} readOnly={true} />
<ExtraButton icon='folder' onClick={chooseFile} />
</Setting>
</>;
}
};
const getdefaultEditor = (meta: AnyPropertyGridControl, onChange?: (value: unknown) => void) => {
switch (meta.type) {
case 'checkbox': {
const E = editors[meta.type];
return <E meta={meta} onChange={onChange} />;
}
case 'dropdown': {
const E = editors[meta.type];
return <E meta={meta} onChange={onChange} />;
}
case 'textInput': {
const E = editors[meta.type];
return <E meta={meta} onChange={onChange} />;
}
case 'fileSelectDialog': {
const E = editors[meta.type];
return <E meta={meta} onChange={onChange} />;
}
default:
return <div>Unsupported {JSON.stringify(meta)} </div>;
}
};
export interface PropertyGridControlMeta<T = unknown> {
title: string,
description?: string,
default?: T | (() => T)
}
export interface FileSelectDialogMeta extends PropertyGridControlMeta<string> {
type: 'fileSelectDialog',
filters?: FileFilter[]
}
export interface DropDownMeta extends PropertyGridControlMeta<string> {
type: 'dropdown',
options: {
name?: string,
value: string
}[]
}
export interface CheckboxMeta extends PropertyGridControlMeta<boolean> {
type: 'checkbox'
}
export interface TextInputMeta extends PropertyGridControlMeta<string> {
type: 'textInput'
}
export type AnyPropertyGridControl = DropDownMeta | CheckboxMeta | TextInputMeta | FileSelectDialogMeta;
export type PropertyGridMeta = {
[k: string]: AnyPropertyGridControl
}
export type PropertyGridProps = {
meta: PropertyGridMeta,
value?: Record<string, unknown>,
customEditor?: (meta: AnyPropertyGridControl, onChange?: (value: unknown) => void) => JSX.Element | undefined,
onChange?: (value: Record<string, unknown>, key: string) => void
}
export default (props: PropertyGridProps) => {
let obj: Record<string, unknown> = {};
createEffect(() => obj = props.value ?? createDefaultObject(props.meta));
const onChange = (key: string, value: unknown) => {
obj[key] = value;
props.onChange && props.onChange(obj, key);
};
const createEditor = (key: string, meta: AnyPropertyGridControl) => {
const onValueChange = (value: unknown) => onChange(key, value);
let editor: JSX.Element | undefined = undefined;
if (props.customEditor) {
editor = props.customEditor(meta, onValueChange);
if (editor) {
return editor;
}
}
return getdefaultEditor(meta, onValueChange);
};
return <>
<For each={Object.entries(props.meta)}>
{([key, meta]) => createEditor(key, meta)}
</For>
</>;
};
export const createDefaultObject = (meta: PropertyGridMeta): Record<string, unknown> => {
return Object.fromEntries(Object.entries(meta).map(([k, m]) => [k, getDefaultValue(m)]));
};
const getDefaultValue = <T, M extends PropertyGridControlMeta<T>>(meta: M) => {
if (meta.default) {
return meta.default instanceof Function ? meta.default() : meta.default;
}
};

View File

@ -1,124 +0,0 @@
import { For, JSX, createContext, onCleanup, onMount, useContext } from 'solid-js';
import * as Ob from 'obsidian';
type SettingContext = {
settingEl: HTMLDivElement
}
const Context = createContext<SettingContext>();
const useSetting = () => useContext(Context);
export default (props: {
name?: string,
description?: string,
class?: string,
heading?: boolean,
disabled?: boolean,
noInfo?: boolean,
children?: JSX.Element
}) => {
const context: SettingContext = {
settingEl: null
};
return <>
<Context.Provider value={context}>
<div
ref={(el) => context.settingEl = el}
class={`setting-item ${props.class ?? ''}`.trimEnd()}
classList={{
'setting-item-heading': props.heading,
'is-disable': props.disabled
}}>
<div class="setting-item-info">
<div class="setting-item-name">{props.name}</div>
<div class="setting-item-description">{props.description}</div>
</div>
<div class="setting-item-control">
{props.children}
</div>
</div>
</Context.Provider>
</>;
};
export const Toggle = (props: { checked?: boolean, onChange?: (checked: boolean) => void }) => {
const setting = useSetting();
onMount(() => {
setting.settingEl.addClass('mod-toggle');
});
onCleanup(() => {
setting.settingEl.removeClass('mod-toggle');
});
return <>
<div class="checkbox-container" classList={{ 'is-enabled': props.checked }} onClick={() => props.onChange && props.onChange(!props.checked)} >
<input type="checkbox" />
</div>
</>;
};
export const ExtraButton = (props: { icon?: string, onClick?: () => void, tooltip?: string }) => {
return <div
ref={(el) => props.icon && Ob.setIcon(el, props.icon)}
class="setting-editor-extra-setting-button"
classList={{ 'clickable-icon': props.icon && !!props.onClick }}
aria-label={props.tooltip}
onClick={props.onClick}
/>;
};
export const Text = (props: { placeholder?: string,
title?: string,
value?: string,
style?: string,
disabled?: boolean,
readOnly?: boolean,
spellcheck?: boolean,
onChange?: (value: string) => void }) => {
return <input
type="text"
title={props.title}
readOnly={props.readOnly}
placeholder={props.placeholder}
spellcheck={props.spellcheck ?? false}
style={props.style}
value={props.value}
onChange={(e) => props.onChange?.(e.target.value)}
disabled={props.disabled}
/>;
};
export const TextArea = (props: { placeholder?: string,
title?: string,
value?: string,
style?: string,
disabled?: boolean,
spellcheck?: boolean,
onChange?: (value: string) => void }) => {
return <textarea
placeholder={props.placeholder}
spellcheck={props.spellcheck ?? false}
style={props.style}
value={props.value}
onChange={(e) => props.onChange?.(e.target.value)}
disabled={props.disabled}
/>;
};
export const DropDown = (props: {
options: { name?: string, value: string }[],
selected?: string,
onChange?: (value: string, index: number) => void
}) => {
return <>
<select class="dropdown" onChange={(e) => props.onChange?.(e.target.value, e.target.selectedIndex)} autofocus={true}>
<For each={props.options}>
{(item) => <option value={item.value} selected={item.value === props.selected}>{item.name ?? item.value}</option>}
</For>
</select>
</>;
};

View File

@ -1,6 +0,0 @@
import ExportDialog from './ExportDialog';
import ExportSettingTab from './SettingTab';
export {
ExportDialog, ExportSettingTab
};

View File

@ -1,158 +0,0 @@
import { App, Modal, Setting, TFile, TextComponent } from 'obsidian';
import * as ct from 'electron';
import { extractDefaultExtension as extractExtension } from '../../settings';
import { setPlatformValue, getPlatformValue } from '../../utils';
import { exportToOo } from '../../exporto0o';
import { setTooltip, setVisible } from './setting_tab';
import type UniversalExportPlugin from '../../main';
export class ExportDialog extends Modal {
readonly plugin: UniversalExportPlugin;
readonly currentFile: TFile;
get lang() {
return this.plugin.lang;
}
constructor(app: App, plugin: UniversalExportPlugin, currentFile: TFile) {
super(app);
this.plugin = plugin;
this.currentFile = currentFile;
}
onOpen() {
const {
titleEl,
contentEl,
currentFile,
plugin: { settings: globalSetting },
lang,
} = this;
const exportDirectoryMode = globalSetting.defaultExportDirectoryMode;
let exportType = globalSetting.lastExportType ?? globalSetting.items.first()?.name;
let setting = globalSetting.items.find(o => o.name === exportType);
let extension = extractExtension(setting);
let showOverwriteConfirmation = globalSetting.showOverwriteConfirmation;
let candidateOutputDirectory = `${getPlatformValue(globalSetting.lastExportDirectory) ?? ct.remote.app.getPath('documents')}`;
let candidateOutputFileName = `${currentFile.basename}${extension}`;
// eslint-disable-next-line prefer-const
let candidateOutputFileNameSetting: Setting;
if (exportDirectoryMode === 'Same') {
const fullPath: string = this.app.vault.adapter.getFullPath(currentFile.path);
candidateOutputDirectory = fullPath.substring(0, fullPath.length - currentFile.name.length - 1);
} else if (exportDirectoryMode === 'Custom') {
candidateOutputDirectory = getPlatformValue(globalSetting.customDefaultExportDirectory);
}
titleEl.setText(lang.exportDialog.title(setting.name));
new Setting(contentEl).setName(lang.exportDialog.type).addDropdown(cb => {
cb.addOptions(Object.fromEntries(globalSetting.items.map(o => [o.name, o.name])))
.onChange(v => {
exportType = v;
setting = globalSetting.items.find(o => o.name === exportType);
titleEl.setText(lang.exportDialog.title(setting.name));
extension = extractExtension(setting);
if (candidateOutputFileName.includes('.')) {
candidateOutputFileName = candidateOutputFileName.substring(0, candidateOutputFileName.lastIndexOf('.')) + extension;
} else {
candidateOutputFileName = candidateOutputFileName + extension;
}
(candidateOutputFileNameSetting.components.first() as TextComponent)
?.setValue(candidateOutputFileName)
.inputEl.setAttribute('title', candidateOutputFileName);
})
.setValue(exportType);
});
candidateOutputFileNameSetting = new Setting(contentEl).setName(lang.exportDialog.fileName).addText(cb => {
cb.setValue(candidateOutputFileName)
.onChange(v => {
candidateOutputFileName = v;
setTooltip(cb.inputEl, v);
})
.inputEl.setAttribute('title', candidateOutputFileName);
});
const candidateOutputDirectorySetting = new Setting(contentEl)
.setName(lang.exportDialog.exportTo)
.addText(cb => {
cb.setValue(candidateOutputDirectory).onChange(v => {
candidateOutputDirectory = v;
setTooltip(cb.inputEl, candidateOutputDirectory);
});
cb.setDisabled(true);
setTooltip(cb.inputEl, candidateOutputDirectory);
})
.addExtraButton(cb => {
cb.setIcon('folder').onClick(async () => {
const retval = await ct.remote.dialog.showOpenDialog({
title: lang.exportDialog.selectExportFolder,
defaultPath: candidateOutputDirectory,
properties: ['createDirectory', 'openDirectory'],
});
if (!retval.canceled && retval.filePaths?.length > 0) {
candidateOutputDirectory = retval.filePaths[0];
(candidateOutputDirectorySetting.components.first() as TextComponent)
?.setValue(candidateOutputDirectory)
.inputEl.setAttribute('title', candidateOutputDirectory);
}
});
});
new Setting(contentEl).setName(lang.exportDialog.overwriteConfirmation).addToggle(cb => {
cb.setValue(showOverwriteConfirmation).onChange(v => (showOverwriteConfirmation = v));
});
contentEl.createEl('div', { cls: ['modal-button-container'], parent: contentEl }, el => {
el.createEl('button', {
text: lang.exportDialog.export,
cls: ['mod-cta'],
parent: el,
}).onclick = async () => {
await exportToOo(
this.plugin,
currentFile,
candidateOutputDirectory,
candidateOutputFileName,
setting,
showOverwriteConfirmation,
async () => {
globalSetting.showOverwriteConfirmation = showOverwriteConfirmation;
globalSetting.lastExportDirectory = setPlatformValue(globalSetting.lastExportDirectory, candidateOutputDirectory);
globalSetting.lastExportType = setting.name;
await this.plugin.saveSettings();
this.close();
},
() => {
setVisible(this.containerEl, true);
},
() => {
setVisible(this.containerEl, false);
}
);
};
});
}
onClose() {
const { contentEl } = this;
contentEl.empty();
}
}
const show = (plugin: UniversalExportPlugin, currentFile: TFile) => {
const dialog = new ExportDialog(plugin.app, plugin, currentFile);
dialog.open();
return () => dialog.close();
};
export default {
show,
};

View File

@ -1,4 +0,0 @@
import ExportDialog from './export_dialog';
import ExportSettingTab from './setting_tab';
export { ExportDialog, ExportSettingTab };

View File

@ -1,573 +0,0 @@
import { App, PluginSettingTab, Setting, TextComponent } from 'obsidian';
import * as ct from 'electron';
import { CustomExportSetting, ExportSetting, PandocExportSetting, UniversalExportPluginSettings } from '../../settings';
import { setPlatformValue, getPlatformValue } from '../../utils';
import pandoc from '../../pandoc';
import { Modal } from 'obsidian';
import export_command_templates from '../../export_templates';
import type ExportSettingTab from './setting_tab';
import type UniversalExportPlugin from '../../main';
export default class extends PluginSettingTab {
plugin: UniversalExportPlugin;
public get lang() {
return this.plugin.lang;
}
constructor(plugin: UniversalExportPlugin) {
super(plugin.app, plugin);
this.plugin = plugin;
this.name = this.plugin.lang.settingTab.title;
}
hide() {
const { containerEl } = this;
containerEl.empty();
}
display(): void {
const { containerEl, lang, plugin } = this;
containerEl.empty();
const validateCallback = (v: unknown, k: keyof typeof t, t: unknown): boolean => {
const sv = t[k];
if (v === sv) {
return false;
}
// noinspection RedundantIfStatementJS
if (k !== 'lastEditName' && sv === undefined && (v === false || v === '')) {
return false;
}
return true;
};
const changedCallback = async (v: unknown, k: keyof typeof t, t: unknown) => {
if (v !== undefined) {
if (v === false || (typeof v === 'string' && v.trim() === '')) {
delete t[k];
}
}
await plugin.saveSettings();
};
const globalSettingWatcher = new Watcher<UniversalExportPluginSettings>({
onChangingCallback: validateCallback,
onChangedCallback: changedCallback,
});
const settingWatcher = new Watcher<ExportSetting>({
onChangingCallback: validateCallback,
onChangedCallback: changedCallback,
});
const pandocSettingWatcher = settingWatcher.as<PandocExportSetting>();
const customSettingWatcher = settingWatcher.as<CustomExportSetting>();
let globalSetting = new Proxy(plugin.settings, globalSettingWatcher);
let current = new Proxy(
globalSetting.items.find(v => v.name === globalSetting.lastEditName) ?? globalSetting.items.first(),
settingWatcher
);
const changeEditExportSetting = (name: string) => {
const newSetting = globalSetting.items.find(v => v.name === name) ?? globalSetting.items.first();
if (globalSetting.lastEditName !== newSetting.name) {
globalSetting.lastEditName = newSetting.name;
}
if (newSetting) {
current = new Proxy(newSetting, settingWatcher);
settingWatcher.fireChanged(current);
}
};
containerEl.createEl('h2', { text: lang.settingTab.title });
// General
new Setting(containerEl)
.setName(lang.settingTab.general)
.addExtraButton(cb => {
cb.setIcon('reset')
.setTooltip(lang.settingTab.reset)
.onClick(async () => {
await this.plugin.resetSettings();
globalSetting = new Proxy(plugin.settings, globalSettingWatcher);
globalSettingWatcher.fireChanged(globalSetting);
changeEditExportSetting(globalSetting.lastEditName);
});
})
.setHeading();
const pandocPathSetting = new Setting(containerEl);
pandoc
.getVersion(getPlatformValue(globalSetting.pandocPath))
.then(ver => {
pandocPathSetting.setDesc(lang.settingTab.pandocVersion(ver.version));
})
.catch(() => {
pandocPathSetting.setDesc(lang.settingTab.pandocNotFound);
});
pandocPathSetting.setName(lang.settingTab.pandocPath).addText(cb => {
cb.setPlaceholder(lang.settingTab.pandocPathPlaceholder).onChange(v => {
if (globalSetting.pandocPath !== v) {
globalSetting.pandocPath = setPlatformValue(globalSetting.pandocPath, v);
pandoc
.getVersion(getPlatformValue(globalSetting.pandocPath))
.then(ver => {
pandocPathSetting.setDesc(lang.settingTab.pandocVersion(ver.version));
})
.catch(() => {
pandocPathSetting.setDesc(lang.settingTab.pandocNotFound);
});
}
});
globalSettingWatcher.watchOnChanged('pandocPath', value => {
cb.setValue(getPlatformValue(value) ?? '');
});
});
new Setting(containerEl).setName(lang.settingTab.defaultFolderForExportedFile).addDropdown(cb => {
cb.addOptions({
'Auto': lang.settingTab.auto,
'Same': lang.settingTab.sameFolderWithCurrentFile,
'Custom': lang.settingTab.customLocation,
}).onChange((v: 'Auto' | 'Same' | 'Custom') => {
if (globalSetting.defaultExportDirectoryMode !== v) {
globalSetting.defaultExportDirectoryMode = v;
}
});
globalSettingWatcher.watchOnChanged('defaultExportDirectoryMode', (value: 'Auto' | 'Same' | 'Custom') => {
cb.setValue(value);
});
});
const customDefaultExportDirectorySetting = new Setting(containerEl)
.addText(cb => {
globalSettingWatcher.watchOnChanged('customDefaultExportDirectory', (value?) => {
const val = getPlatformValue(value);
cb.setValue(val ?? '');
setTooltip(cb.inputEl, val);
});
})
.setClass('ex-setting-item')
.addExtraButton(cb => {
cb.setIcon('folder').onClick(async () => {
const retval = await ct.remote.dialog.showOpenDialog({
defaultPath: getPlatformValue(globalSetting.customDefaultExportDirectory) ?? ct.remote.app.getPath('documents'),
properties: ['createDirectory', 'openDirectory'],
});
if (!retval.canceled && retval.filePaths.length > 0) {
globalSetting.customDefaultExportDirectory = setPlatformValue(globalSetting.customDefaultExportDirectory, retval.filePaths[0]);
}
});
globalSettingWatcher.watchOnChanged('customDefaultExportDirectory', value => {
const text = customDefaultExportDirectorySetting.components.first() as TextComponent;
const val = getPlatformValue(value);
text.setValue(val ?? '');
setTooltip(text.inputEl, val);
});
});
globalSettingWatcher.watchOnChanged('defaultExportDirectoryMode', value => {
setVisible(customDefaultExportDirectorySetting.settingEl, value === 'Custom');
});
new Setting(containerEl).setName(lang.settingTab.openExportedFileLocation).addToggle(cb => {
cb.onChange(v => {
if (globalSetting.openExportedFileLocation !== v) {
globalSetting.openExportedFileLocation = v;
}
});
globalSettingWatcher.watchOnChanged('openExportedFileLocation', v => {
cb.setValue(v);
});
});
new Setting(containerEl).setName(lang.settingTab.openExportedFile).addToggle(cb => {
cb.onChange(v => {
if (globalSetting.openExportedFile !== v) {
globalSetting.openExportedFile = v;
}
});
globalSettingWatcher.watchOnChanged('openExportedFile', v => {
cb.setValue(v);
});
});
// settings for export type
new Setting(containerEl).setName(lang.settingTab.editCommandTemplate).setHeading();
new Setting(containerEl)
.setName(lang.settingTab.chooseCommandTemplate)
.addDropdown(cb => {
cb.onChange(v => {
if (globalSetting.lastEditName !== v) {
changeEditExportSetting(v);
}
});
globalSettingWatcher.watchOnChanged('items', (value: ExportSetting[]) => {
cb.selectEl.empty();
cb.addOptions(Object.fromEntries(value.map(o => [o.name, o.name])));
cb.setValue(globalSetting.lastEditName ?? globalSetting.items.first()?.name);
});
globalSettingWatcher.watchOnChanged('lastEditName', value => {
cb.setValue(value);
});
})
.addExtraButton(button => {
button.setTooltip(lang.settingTab.add);
button.setIcon('plus');
button.onClick(() => {
new AddNewModal(this.app, this, s => {
globalSetting.items = [...globalSetting.items, s];
changeEditExportSetting(s.name);
}).open();
});
})
.addExtraButton(button => {
button.setTooltip(lang.settingTab.rename);
button.setIcon('pencil');
button.onClick(() => {
new RenemeModal(this.app, this, current, n => {
current.name = n;
globalSetting.items = [...globalSetting.items];
changeEditExportSetting(n);
}).open();
});
})
.addExtraButton(button => {
button.setTooltip(lang.settingTab.remove);
button.setIcon('trash');
button.onClick(() => {
globalSetting.items = globalSetting.items.filter(o => o.name != current.name);
changeEditExportSetting(globalSetting.items.first()?.name);
});
});
const commandSetting = new Setting(containerEl).setName(lang.settingTab.command).addText(cb => {
cb.setDisabled(true);
cb.onChange(v => {
if (current.type === 'custom' && current.command !== v) {
current.command = v;
}
});
customSettingWatcher.watchOnChanged('command', value => {
cb.setValue(value);
});
settingWatcher.watchOnChanged('type', value => {
cb.setDisabled(value !== 'custom');
});
});
settingWatcher.watchOnChanged('type', value => {
setVisible(commandSetting.settingEl, value === 'custom');
});
const argumentsSetting = new Setting(containerEl).setName(lang.settingTab.arguments).addText(cb => {
cb.setDisabled(true);
cb.onChange(v => {
if (current.type === 'pandoc' && current.arguments !== v) {
current.arguments = v;
setTooltip(cb.inputEl, current.arguments);
}
});
pandocSettingWatcher.watchOnChanged('arguments', value => {
cb.setValue(value ?? '');
setTooltip(cb.inputEl, value);
});
settingWatcher.watchOnChanged('type', value => {
cb.setDisabled(value !== 'custom');
});
});
settingWatcher.watchOnChanged('type', value => {
setVisible(argumentsSetting.settingEl, value === 'pandoc');
});
const targetFileExtensionsSetting = new Setting(containerEl).setName(lang.settingTab.targetFileExtensions).addText(cb => {
cb.onChange(v => {
if (current.type === 'custom' && current.targetFileExtensions !== v) {
current.targetFileExtensions = v;
}
});
customSettingWatcher.watchOnChanged('targetFileExtensions', value => {
cb.setValue(value ?? '');
});
});
settingWatcher.watchOnChanged('type', value => {
setVisible(targetFileExtensionsSetting.settingEl, value === 'custom');
});
const customArgumentsSetting = new Setting(containerEl).setName(lang.settingTab.extraArguments).addText(cb => {
cb.onChange(v => {
if (current.type === 'pandoc' && current.customArguments !== v) {
current.customArguments = v;
}
});
pandocSettingWatcher.watchOnChanged('customArguments', value => {
cb.setValue(value ?? '');
setTooltip(cb.inputEl, value);
});
});
settingWatcher.watchOnChanged('type', value => {
setVisible(customArgumentsSetting.settingEl, value === 'pandoc');
});
new Setting(containerEl).setName(lang.settingTab.afterExport).setHeading();
const showCommandOutputSetting = new Setting(containerEl).setName(lang.settingTab.showCommandOutput).addToggle(cb => {
if (current.type === 'custom') {
cb.setValue(current.showCommandOutput);
}
cb.onChange(v => {
if (current.type === 'custom' && current.showCommandOutput !== v) {
current.showCommandOutput = v;
}
});
});
settingWatcher.watchOnChanged('type', value => {
setVisible(showCommandOutputSetting.settingEl, value === 'custom');
});
new Setting(containerEl).setName(lang.settingTab.openExportedFileLocation).addToggle(cb => {
cb.onChange(v => {
if (current.openExportedFileLocation !== v) {
current.openExportedFileLocation = v;
}
});
settingWatcher.watchOnChanged('openExportedFileLocation', value => {
cb.setValue(value);
});
});
new Setting(containerEl).setName(lang.settingTab.runCommand).addToggle(cb => {
cb.onChange(v => {
if (current.type === 'pandoc' && current.runCommand !== v) {
current.runCommand = v;
}
});
pandocSettingWatcher.watchOnChanged('runCommand', value => {
cb.setValue(value);
});
});
const commandAfterExportSetting = new Setting(containerEl).addText(cb => {
cb.onChange(v => {
if (current.command !== v) {
current.command = v;
}
});
pandocSettingWatcher.watchOnChanged('command', value => {
cb.setValue(value);
});
pandocSettingWatcher.watchOnChanged('runCommand', (value, _, target) => {
setVisible(commandAfterExportSetting.settingEl, target.type === 'pandoc' && value);
cb.setValue(current.command);
});
});
globalSettingWatcher.fireChanged(globalSetting);
settingWatcher.fireChanged(current);
}
}
class AddNewModal extends Modal {
readonly settingTab: ExportSettingTab;
readonly callback: (setting: ExportSetting) => void;
get lang() {
return this.settingTab.lang;
}
constructor(app: App, settingTab: ExportSettingTab, callback: (setting: ExportSetting) => void) {
super(app);
this.settingTab = settingTab;
this.callback = callback;
}
onOpen() {
const { contentEl, titleEl, lang, callback } = this;
titleEl.setText(lang.settingTab.new);
let tpl = Object.values(export_command_templates).first();
let tplName = tpl.name;
let name = tpl.name;
// eslint-disable-next-line prefer-const
let nameSetting: Setting;
new Setting(contentEl).setName(lang.settingTab.template).addDropdown(cb => {
cb.addOptions(Object.fromEntries(Object.values(export_command_templates).map(o => [o.name, o.name])))
.setValue(tplName)
.onChange(v => {
tplName = v;
name = v;
(nameSetting.components.first() as TextComponent)?.setValue(name);
});
});
nameSetting = new Setting(contentEl).setName(lang.settingTab.name).addText(cb => {
cb.setValue(name).onChange(v => (name = v));
});
contentEl.createEl('div', { cls: ['modal-button-container'], parent: contentEl }, el => {
el.createEl('button', {
text: lang.settingTab.add,
cls: ['mod-cta'],
parent: el,
}).onclick = async () => {
tpl = JSON.parse(JSON.stringify(export_command_templates[tplName]));
tpl.name = name;
callback(tpl);
this.close();
};
});
}
onClose() {
const { contentEl } = this;
contentEl.empty();
}
}
class RenemeModal extends Modal {
private readonly settingTab: ExportSettingTab;
private readonly setting: ExportSetting;
private readonly callback: (name: string) => void;
get lang() {
return this.settingTab.lang;
}
constructor(app: App, settingTab: ExportSettingTab, setting: ExportSetting, callback: (name: string) => void) {
super(app);
this.settingTab = settingTab;
this.setting = setting;
this.callback = callback;
}
onOpen() {
const { contentEl, titleEl, lang, setting } = this;
titleEl.setText(lang.settingTab.rename);
let name = setting.name;
new Setting(contentEl).setName(lang.settingTab.name).addText(cb => {
cb.setValue(setting.name).onChange(v => (name = v));
});
contentEl.createEl('div', { cls: ['modal-button-container'], parent: contentEl }, el => {
el.createEl('button', {
text: lang.settingTab.save,
cls: ['mod-cta'],
parent: el,
}).onclick = async () => {
// success
this.callback(name);
this.close();
};
});
}
onClose() {
const { contentEl } = this;
contentEl.empty();
}
}
type TOnChangingHandler<T extends object, K extends keyof T> = (value: T[K], key: K, target: T) => boolean;
type TOnChangedHandler<T extends object, K extends keyof T> = (value: T[K], key: K, target: T) => void;
export class Watcher<T extends object> {
onChanging: { [k in keyof T]?: TOnChangingHandler<T, keyof T>[] };
onChanged: { [k in keyof T]?: TOnChangedHandler<T, keyof T>[] };
private readonly _onChangingCallback: TOnChangingHandler<T, keyof T>;
private readonly _onChangedCallback: TOnChangedHandler<T, keyof T>;
constructor(options?: { onChangingCallback?: TOnChangingHandler<T, keyof T>; onChangedCallback?: TOnChangedHandler<T, keyof T> }) {
this.onChanging = {};
this.onChanged = {};
this._onChangingCallback = options?.onChangingCallback ?? (() => true);
this._onChangedCallback = options?.onChangedCallback ?? (() => void 0);
}
as<T extends object>(): Watcher<T> {
return this as unknown as Watcher<T>;
}
watchOnChanging<K extends keyof T>(key: K, handler: TOnChangingHandler<T, K>): void {
(this.onChanging[key] ?? (this.onChanging[key] = [])).push(handler);
}
watchOnChanged<K extends keyof T>(key: K, handler: TOnChangedHandler<T, K>): void {
(this.onChanged[key] ?? (this.onChanged[key] = [])).push(handler);
}
set<K extends keyof T>(target: T, key: K, value: T[K]): boolean {
if (this._onChangingCallback && this._onChangingCallback(value, key, target) === false) {
return false;
}
const onChangingHandlers = this.onChanging[key];
if (onChangingHandlers) {
let invalid = false;
for (const h of onChangingHandlers) {
if (!h(value, key, target)) {
invalid = true;
}
}
if (invalid) {
return false;
}
}
// The default behavior to store the value
target[key] = value;
const onChangedHandlers = this.onChanged[key];
if (onChangedHandlers) {
for (const h of onChangedHandlers) {
try {
h(value, key, target);
} catch (e) {
console.error(e);
}
}
}
if (this._onChangedCallback) {
this._onChangedCallback(value, key, target);
}
// Indicate success
return true;
}
fireChanged(target: T) {
for (const key of Object.keys(this.onChanged)) {
const k = key as keyof T;
const onChangedHandlers = this.onChanged[k];
if (onChangedHandlers) {
for (const h of onChangedHandlers) {
try {
h(target[k], k, target);
} catch (e) {
console.error(e);
}
}
}
}
}
}
export function setVisible(el: Element, visible: boolean) {
if (visible) {
el.removeAttribute('hidden');
} else {
el.setAttribute('hidden', '');
}
return el;
}
export function setTooltip(el: Element, tooltip?: string) {
if (tooltip && tooltip.trim() != '') {
el.setAttribute('title', tooltip);
} else {
el.removeAttribute('title');
}
return el;
}

View File

@ -1,110 +0,0 @@
import { App, Modal } from 'obsidian';
import lang, { Lang } from '../lang';
export interface MessageBoxOptions {
message: string;
title?: string;
buttons: 'Yes' | 'YesNo' | 'Ok' | 'OkCancel';
buttonsLabel?: {
yes?: string;
no?: string;
ok?: string;
cancel?: string;
};
buttonsClass?: {
yes?: string;
no?: string;
ok?: string;
cancel?: string;
};
callback?: {
yes?: () => void;
no?: () => void;
ok?: () => void;
cancel?: () => void;
};
}
export class MessageBox extends Modal {
readonly options: MessageBoxOptions;
readonly lang: Lang;
constructor(app: App, message: string);
constructor(app: App, message: string, title?: string);
constructor(app: App, options: MessageBoxOptions);
constructor(app: App, options: MessageBoxOptions | string, title?: string) {
super(app);
this.options = typeof options === 'string' ? { message: options, buttons: 'Ok', title } : options;
this.lang = lang.current;
}
onOpen(): void {
const {
titleEl,
contentEl,
lang,
options: { message, title, buttons, callback, buttonsLabel: label, buttonsClass },
} = this;
if (title) {
titleEl.setText(title);
}
contentEl.createDiv({ text: message });
switch (buttons) {
case 'Yes':
contentEl.createEl('div', { cls: ['modal-button-container'], parent: contentEl }, el => {
el.createEl('button', {
text: label?.yes ?? lang.messageBox.yes,
cls: ['mod-cta', buttonsClass?.yes],
parent: el,
}).onclick = () => this.call(callback?.yes);
});
break;
case 'YesNo':
contentEl.createEl('div', { cls: ['modal-button-container'], parent: contentEl }, el => {
el.createEl('button', {
text: label?.yes ?? lang.messageBox.yes,
cls: ['mod-cta', buttonsClass?.yes],
parent: el,
}).onclick = () => this.call(callback?.yes);
el.createEl('button', {
text: label?.no ?? lang.messageBox.no,
cls: ['mod-cta', buttonsClass?.no],
parent: el,
}).onclick = () => this.call(callback?.no);
});
break;
case 'Ok':
contentEl.createEl('div', { cls: ['modal-button-container'], parent: contentEl }, el => {
el.createEl('button', {
text: label?.ok ?? lang.messageBox.ok,
cls: ['mod-cta', buttonsClass?.no],
parent: el,
}).onclick = () => this.call(callback?.ok);
});
break;
case 'OkCancel':
contentEl.createEl('div', { cls: ['modal-button-container'], parent: contentEl }, el => {
el.createEl('button', {
text: label?.ok ?? lang.messageBox.ok,
cls: ['mod-cta', buttonsClass?.ok],
parent: el,
}).onclick = () => this.call(callback?.ok);
el.createEl('button', {
text: label?.cancel ?? lang.messageBox.cancel,
cls: ['mod-cta', buttonsClass?.cancel],
parent: el,
}).onclick = () => this.call(callback?.cancel);
});
break;
}
}
private call(callback?: () => void): void {
if (callback) {
callback();
}
this.close();
}
onClose() {
const { contentEl } = this;
contentEl.empty();
}
}

View File

@ -1,127 +0,0 @@
import { ExecOptions, exec as node_exec } from 'child_process';
import process from 'process';
export type PlatformKey = typeof process.platform | '*';
export type PlatformValue<T> = { [k in PlatformKey]?: T };
export function setPlatformValue<T>(obj: PlatformValue<T>, value: T, platform?: PlatformKey | PlatformKey[]): PlatformValue<T> {
if (typeof value === 'string' && value.trim() === '') {
value = undefined;
}
if (platform instanceof Array) {
return platform.reduce((o, p) => setPlatformValue(o, value, p), obj);
}
platform ??= process.platform;
return {
...(obj ?? {}),
[platform]: value,
};
}
export function getPlatformValue<T>(obj: PlatformValue<T>, platform?: PlatformKey): T {
obj ??= {};
const val = obj[platform ?? process.platform];
const all = obj['*'];
if (all && typeof all === 'object') {
return Object.assign({}, all, val);
}
return val ?? all;
}
// eslint-disable-next-line
export function strTpl(strings: TemplateStringsArray, ...keys: number[]): (...values: any[]) => string {
return function (...values) {
const dict = values[values.length - 1] || {};
const result = [strings[0]];
keys.forEach(function (key, i) {
const value = Number.isInteger(key) ? values[key] : dict[key];
result.push(value, strings[i + 1]);
});
return result.join('');
};
}
export function exec(cmd: string, options?: ExecOptions): Promise<string> {
options = options ?? {};
return new Promise((resolve, reject) => {
node_exec(cmd, options, (error, stdout, stderr) => {
if (error) {
reject(error);
console.error(stdout, error);
return;
}
if (stderr && stderr !== '') {
reject(stderr);
console.error(stdout, error);
return;
}
if (stdout?.trim().length === 0 && '1' === localStorage.getItem('debug-plugin')) {
console.log(stdout);
}
resolve(stdout);
});
});
}
export function joinEnvPath(...paths: string[]) {
switch (process.platform) {
case 'win32':
return paths.join(';');
default:
return paths.join(':');
}
}
export function trimQuotes(s: string) {
return (s.startsWith('"') && s.endsWith('"')) || (s.startsWith("'") && s.endsWith("'")) ? s.substring(1, s.length - 1) : s;
}
/**
* render template
* @example
* renderTemplate('Hi, ${name}', { name: 'John' }) // returns 'Hi, John'
* @param template
* @param variables
* @returns {string}
*/
export function renderTemplate(template: string, variables: Record<string, unknown> = {}): string {
while (true) {
try {
const keys = Object.keys(variables).filter(isVarName) as Array<keyof typeof variables>;
const values = keys.map(k => variables[k]);
return new Function(...keys, `{ return \`${template.replaceAll('\\', '\\\\')}\` }`).bind(variables)(...values);
} catch (e: unknown) {
if (e instanceof ReferenceError && e.message.endsWith('is not defined')) {
const name = e.message.substring(0, e.message.indexOf(' '));
const value =
Object.keys(variables)
.filter(n => n.toLowerCase() === name.toLowerCase())
.map(n => variables[n])[0] ?? `\${${name}}`;
variables[name] = value;
} else {
throw e;
}
}
}
}
const isVarName = (str: string) => {
if (typeof str !== 'string') {
return false;
}
if (str.trim() !== str) {
return false;
}
try {
new Function(str, 'var ' + str);
} catch {
return false;
}
return true;
};

View File

@ -1 +0,0 @@
/// <reference types="vite/client" />

View File

@ -1,38 +0,0 @@
import os from 'os';
import { exec as execSync, ExecException } from 'child_process';
import { readFile } from 'fs/promises';
export async function exec(cmd: string, options: { lineSeparator: '\n' | '\r\n' | '\r' }): Promise<string> {
function lineSeparator(s?: string, ls?: '\n' | '\r\n' | '\r') {
if (!s || os.EOL === ls || !ls) {
return s;
}
return s.replaceAll(os.EOL, ls);
}
return await new Promise((resolve, reject) => {
execSync(cmd, { encoding: 'utf-8', cwd: module.path }, (e: ExecException, stdout: string, stderr: string) => {
if (!e) {
resolve(lineSeparator(stdout, options?.lineSeparator));
} else {
reject(lineSeparator(stderr, options?.lineSeparator));
}
});
});
}
export const testConversion = async (name: string, filter?: string) => {
process.chdir(module.path);
const input_file = `./markdowns/${name}.md`;
const expect_out = `./markdowns/${name}.out`;
let pandoc: string;
if (filter) {
const lua_script = `../lua/${filter}.lua`;
pandoc = `pandoc -s -L ${lua_script} -t native -f markdown "${input_file}" -o -`;
} else {
pandoc = `pandoc -s -t native -f markdown "${input_file}" -o -`;
}
const ret = await exec(pandoc, { lineSeparator: '\n'});
expect(ret).toBe(await readFile(expect_out, { encoding: 'utf-8', flag: 'r' }));
};

View File

@ -1,15 +0,0 @@
import { testConversion } from './common';
test('test basic internal link block parsing', async () => {
await testConversion('internal-link-basic', 'markdown');
});
test('test complex internal link block parsing', async () => {
await testConversion('internal-link-bullet', 'markdown');
});
test('test basic internal link with description', async () => {
await testConversion('internal-link-described', 'markdown');
});

View File

@ -1 +0,0 @@
[[#Some complex section]]

View File

@ -1,14 +0,0 @@
Pandoc
Meta { unMeta = fromList [] }
[ Para
[ Link
( "" , [] , [] )
[ Str "Some"
, Space
, Str "complex"
, Space
, Str "section"
]
( "#Some-complex-section" , "" )
]
]

View File

@ -1,3 +0,0 @@
- potato
- [[#more complex vegetables]]
- cucumber

View File

@ -1,19 +0,0 @@
Pandoc
Meta { unMeta = fromList [] }
[ BulletList
[ [ Plain [ Str "potato" ] ]
, [ Plain
[ Link
( "" , [] , [] )
[ Str "more"
, Space
, Str "complex"
, Space
, Str "vegetables"
]
( "#more-complex-vegetables" , "" )
]
]
, [ Plain [ Str "cucumber" ] ]
]
]

View File

@ -1 +0,0 @@
[[#Some complex section|with custom description]]

View File

@ -1,14 +0,0 @@
Pandoc
Meta { unMeta = fromList [] }
[ Para
[ Link
( "" , [] , [] )
[ Str "with"
, Space
, Str "custom"
, Space
, Str "description"
]
( "#Some-complex-section" , "" )
]
]

View File

@ -1,41 +0,0 @@
$$
\left(\begin{array}{c}
\hat{F}_{1,i,j}\\ \hat{F}_{2,i,j} \\ \vdots \\ \hat{F}_{C-1,i,j} \\ \hat{F}_{C,i,j}
\end{array}\right)
=
\begin{pmatrix}
\frac{\gamma_1}{\sqrt{\hat{\sigma}^2_1}+\epsilon} & 0 & \cdots & &0
\\
0 && \frac{\gamma_2}{\sqrt{\hat{\sigma}^2_2}+\epsilon} & & & &
\\
\vdots && \ddots && \vdots
\\
&&& \frac{\gamma_{C-1}}{\sqrt{\hat{\sigma}^2_{C-1}+\epsilon}} & 0
\\
0 && \cdots &0 & \frac{\gamma_C}{\sqrt{\hat{\sigma}^2_{C}+\epsilon}}
\end{pmatrix}
\cdot
\begin{pmatrix}
F_{1,i,j}
\\
F_{2,i,j}
\\
\vdots
\\
F_{C-1,i,j}
\\
F_{C,i,j}
\end{pmatrix}
+
\begin{pmatrix}
\beta_1-\gamma_1\frac{\hat{\mu}_1}{\sqrt{\hat{\sigma}^2_1+\epsilon}}
\\
\beta_2-\gamma_2\frac{\hat{\mu}_2}{\sqrt{\hat{\sigma}^2_2+\epsilon}}
\\
\vdots
\\
\beta_{C-1}-\gamma_{C-1}\frac{\hat{\mu}_{C-1}}{\sqrt{\hat{\sigma}^2_{C-1}+\epsilon}}
\\
\beta_C-\gamma_C\frac{\hat{\mu}_C}{\sqrt{\hat{\sigma}^2_C+\epsilon}}
\end{pmatrix}
$$

View File

@ -1,8 +0,0 @@
Pandoc
Meta { unMeta = fromList [] }
[ Para
[ Math
DisplayMath
"\n\\left(\\begin{array}{c}\n\\hat{F}_{1,i,j}\\\\ \\hat{F}_{2,i,j} \\\\ \\vdots \\\\ \\hat{F}_{C-1,i,j} \\\\ \\hat{F}_{C,i,j}\n\\end{array}\\right)\n=\n\\begin{pmatrix}\n\\frac{\\gamma_1}{\\sqrt{\\hat{\\sigma}^2_1}+\\epsilon} & 0 & \\cdots & &0\n\\\\\n0 && \\frac{\\gamma_2}{\\sqrt{\\hat{\\sigma}^2_2}+\\epsilon} & & & &\n\\\\\n\\vdots && \\ddots && \\vdots\n\\\\\n&&& \\frac{\\gamma_{C-1}}{\\sqrt{\\hat{\\sigma}^2_{C-1}+\\epsilon}} & 0\n\\\\\n0 && \\cdots &0 & \\frac{\\gamma_C}{\\sqrt{\\hat{\\sigma}^2_{C}+\\epsilon}}\n\\end{pmatrix}\n\\cdot\n\\begin{pmatrix}\nF_{1,i,j}\n\\\\\nF_{2,i,j}\n\\\\\n\\vdots\n\\\\\nF_{C-1,i,j}\n\\\\\nF_{C,i,j}\n\\end{pmatrix}\n+\n\\begin{pmatrix}\n\\beta_1-\\gamma_1\\frac{\\hat{\\mu}_1}{\\sqrt{\\hat{\\sigma}^2_1+\\epsilon}}\n\\\\\n\\beta_2-\\gamma_2\\frac{\\hat{\\mu}_2}{\\sqrt{\\hat{\\sigma}^2_2+\\epsilon}}\n\\\\\n\\vdots\n\\\\\n\\beta_{C-1}-\\gamma_{C-1}\\frac{\\hat{\\mu}_{C-1}}{\\sqrt{\\hat{\\sigma}^2_{C-1}+\\epsilon}}\n\\\\\n\\beta_C-\\gamma_C\\frac{\\hat{\\mu}_C}{\\sqrt{\\hat{\\sigma}^2_C+\\epsilon}}\n\\end{pmatrix} \n"
]
]

View File

@ -1,51 +0,0 @@
$$
\left(\begin{array}{c}
\hat{F}_{1,i,j}\\ \hat{F}_{2,i,j} \\ \vdots \\ \hat{F}_{C-1,i,j} \\ \hat{F}_{C,i,j}
\end{array}\right)
=
\begin{pmatrix}
\frac{\gamma_1}{\sqrt{\hat{\sigma}^2_1}+\epsilon} & 0 & \cdots & &0
\\
0 && \frac{\gamma_2}{\sqrt{\hat{\sigma}^2_2}+\epsilon} & & & &
\\
\vdots && \ddots && \vdots
\\
&&& \frac{\gamma_{C-1}}{\sqrt{\hat{\sigma}^2_{C-1}+\epsilon}} & 0
\\
0 && \cdots &0 & \frac{\gamma_C}{\sqrt{\hat{\sigma}^2_{C}+\epsilon}}
\end{pmatrix}
\cdot
\begin{pmatrix}
F_{1,i,j}
\\
F_{2,i,j}
\\
\vdots
\\
F_{C-1,i,j}
\\
F_{C,i,j}
\end{pmatrix}
+
\begin{pmatrix}
\beta_1-\gamma_1\frac{\hat{\mu}_1}{\sqrt{\hat{\sigma}^2_1+\epsilon}}
\\
\beta_2-\gamma_2\frac{\hat{\mu}_2}{\sqrt{\hat{\sigma}^2_2+\epsilon}}
\\
\vdots
\\
\beta_{C-1}-\gamma_{C-1}\frac{\hat{\mu}_{C-1}}{\sqrt{\hat{\sigma}^2_{C-1}+\epsilon}}
\\
\beta_C-\gamma_C\frac{\hat{\mu}_C}{\sqrt{\hat{\sigma}^2_C+\epsilon}}
\end{pmatrix}
$$

View File

@ -1,28 +0,0 @@
Pandoc
Meta { unMeta = fromList [] }
[ Plain
[ Str "$$"
, SoftBreak
, RawInline (Format "tex") "\\left"
, Str "("
]
, RawBlock
(Format "tex")
"\\begin{array}{c}\n\\hat{F}_{1,i,j}\\\\ \\hat{F}_{2,i,j} \\\\ \\vdots \\\\ \\hat{F}_{C-1,i,j} \\\\ \\hat{F}_{C,i,j}\n\\end{array}"
, Header
1
( "section" , [] , [] )
[ RawInline (Format "tex") "\\right" , Str ")" ]
, RawBlock
(Format "tex")
"\\begin{pmatrix}\n\\frac{\\gamma_1}{\\sqrt{\\hat{\\sigma}^2_1}+\\epsilon} & 0 & \\cdots & &0\n\\\\\n0 && \\frac{\\gamma_2}{\\sqrt{\\hat{\\sigma}^2_2}+\\epsilon} & & & &\n\\\\\n\\vdots && \\ddots && \\vdots\n\\\\\n&&& \\frac{\\gamma_{C-1}}{\\sqrt{\\hat{\\sigma}^2_{C-1}+\\epsilon}} & 0\n\\\\\n0 && \\cdots &0 & \\frac{\\gamma_C}{\\sqrt{\\hat{\\sigma}^2_{C}+\\epsilon}}\n\n\\end{pmatrix}"
, RawBlock (Format "tex") "\\cdot"
, RawBlock
(Format "tex")
"\\begin{pmatrix}\nF_{1,i,j}\n\\\\\nF_{2,i,j}\n\\\\\n\\vdots\n\\\\\nF_{C-1,i,j}\n\\\\\n\nF_{C,i,j}\n\\end{pmatrix}"
, BulletList [ [] ]
, RawBlock
(Format "tex")
"\\begin{pmatrix}\n\\beta_1-\\gamma_1\\frac{\\hat{\\mu}_1}{\\sqrt{\\hat{\\sigma}^2_1+\\epsilon}}\n\\\\\n\\beta_2-\\gamma_2\\frac{\\hat{\\mu}_2}{\\sqrt{\\hat{\\sigma}^2_2+\\epsilon}}\n\\\\\n\\vdots\n\\\\\n\\beta_{C-1}-\\gamma_{C-1}\\frac{\\hat{\\mu}_{C-1}}{\\sqrt{\\hat{\\sigma}^2_{C-1}+\\epsilon}}\n\\\\\n\\beta_C-\\gamma_C\\frac{\\hat{\\mu}_C}{\\sqrt{\\hat{\\sigma}^2_C+\\epsilon}}\n\n\\end{pmatrix}"
, Para [ Str "$$" ]
]

View File

@ -1,11 +0,0 @@
Pandoc
Meta { unMeta = fromList [] }
[ Plain []
, Para []
, Para []
, Para
[ Math
DisplayMath
"\n\\left(\\begin{array}{c}\n\\hat{F}_{1,i,j}\\\\ \\hat{F}_{2,i,j} \\\\ \\vdots \\\\ \\hat{F}_{C-1,i,j} \\\\ \\hat{F}_{C,i,j}\n\\end{array}\\right)=\\begin{pmatrix}\n\\frac{\\gamma_1}{\\sqrt{\\hat{\\sigma}^2_1}+\\epsilon} & 0 & \\cdots & &0\n\\\\\n0 && \\frac{\\gamma_2}{\\sqrt{\\hat{\\sigma}^2_2}+\\epsilon} & & & &\n\\\\\n\\vdots && \\ddots && \\vdots\n\\\\\n&&& \\frac{\\gamma_{C-1}}{\\sqrt{\\hat{\\sigma}^2_{C-1}+\\epsilon}} & 0\n\\\\\n0 && \\cdots &0 & \\frac{\\gamma_C}{\\sqrt{\\hat{\\sigma}^2_{C}+\\epsilon}}\n\n\\end{pmatrix}\\cdot\\begin{pmatrix}\nF_{1,i,j}\n\\\\\nF_{2,i,j}\n\\\\\n\\vdots\n\\\\\nF_{C-1,i,j}\n\\\\\n\nF_{C,i,j}\n\\end{pmatrix}\\begin{pmatrix}\n\\beta_1-\\gamma_1\\frac{\\hat{\\mu}_1}{\\sqrt{\\hat{\\sigma}^2_1+\\epsilon}}\n\\\\\n\\beta_2-\\gamma_2\\frac{\\hat{\\mu}_2}{\\sqrt{\\hat{\\sigma}^2_2+\\epsilon}}\n\\\\\n\\vdots\n\\\\\n\\beta_{C-1}-\\gamma_{C-1}\\frac{\\hat{\\mu}_{C-1}}{\\sqrt{\\hat{\\sigma}^2_{C-1}+\\epsilon}}\n\\\\\n\\beta_C-\\gamma_C\\frac{\\hat{\\mu}_C}{\\sqrt{\\hat{\\sigma}^2_C+\\epsilon}}\n\n\\end{pmatrix}\n"
]
]

View File

@ -1,13 +0,0 @@
$$
\begin{align*}
\begin{rcases}
\lambda_{1}(a_{11}^{*}, \ldots, a_{22}^{*}) < 0 \\
\lambda_{2}(a_{11}^{*}, \ldots, a_{22}^{*}) < 0 \\
\end{rcases} & \Rightarrow \text{stable knot} \\
\begin{rcases}
\lambda_{1}(a_{11}^{*}, \ldots, a_{22}^{*}) > 0 \\
\lambda_{2}(a_{11}^{*}, \ldots, a_{22}^{*}) < 0 \\
\end{rcases} & \Rightarrow \text{saddle}
\end{align*}
$$

View File

@ -1,12 +0,0 @@
Pandoc
Meta { unMeta = fromList [] }
[ Para
[ Str "$$"
, SoftBreak
, RawInline
(Format "tex")
"\\begin{align*}\n\\begin{rcases}\n\\lambda_{1}(a_{11}^{*}, \\ldots, a_{22}^{*}) < 0 \\\\\n\\lambda_{2}(a_{11}^{*}, \\ldots, a_{22}^{*}) < 0 \\\\\n\\end{rcases} & \\Rightarrow \\text{stable knot} \\\\\n\n \\begin{rcases}\n \\lambda_{1}(a_{11}^{*}, \\ldots, a_{22}^{*}) > 0 \\\\\n \\lambda_{2}(a_{11}^{*}, \\ldots, a_{22}^{*}) < 0 \\\\\n \\end{rcases} & \\Rightarrow \\text{saddle}\n\\end{align*}"
, SoftBreak
, Str "$$"
]
]

View File

@ -1,8 +0,0 @@
Pandoc
Meta { unMeta = fromList [] }
[ Para
[ Math
DisplayMath
"\n\\begin{align*}\n\\begin{rcases}\n\\lambda_{1}(a_{11}^{*}, \\ldots, a_{22}^{*}) < 0 \\\\\n\\lambda_{2}(a_{11}^{*}, \\ldots, a_{22}^{*}) < 0 \\\\\n\\end{rcases} & \\Rightarrow \\text{stable knot} \\\\\n\n \\begin{rcases}\n \\lambda_{1}(a_{11}^{*}, \\ldots, a_{22}^{*}) > 0 \\\\\n \\lambda_{2}(a_{11}^{*}, \\ldots, a_{22}^{*}) < 0 \\\\\n \\end{rcases} & \\Rightarrow \\text{saddle}\n\\end{align*}\n"
]
]

View File

@ -1,23 +0,0 @@
import { testConversion } from './common';
test('test math-block parsing', async () => {
await testConversion('math-block', 'math_block');
});
test('test math-block-01 parsing', async () => {
await testConversion('math-block-01', 'math_block');
});
test('test math-block-01-no-empty-lines parsing', async () => {
await testConversion('math-block-01-no-empty-lines', 'math_block');
});
test('test math-block-01-no-empty-lines parsing filter off', async () => {
await testConversion('math-block-01-no-empty-lines');
});

View File

@ -1,7 +0,0 @@
import pandoc from '../src/pandoc';
test('test get pandoc version', async () => {
const out = await pandoc.getVersion();
expect(out.compare('3.1.5')).toBe(1);
});

View File

@ -1,26 +0,0 @@
import { getPlatformValue, setPlatformValue } from '../src/utils';
test('test get set platformValue 1', async () => {
const val = setPlatformValue({}, 'abc');
expect(getPlatformValue(val)).toBe('abc');
});
test('test get set platformValue 2', async () => {
const val = setPlatformValue({}, 'abc', '*');
expect(getPlatformValue(val)).toBe('abc');
});
test('test get set platformValue 3', async () => {
let val = setPlatformValue<Record<string, string>>({}, { 'a': 'x' }, '*');
val = setPlatformValue(val, { 'b': 'y' });
expect(getPlatformValue(val)).toStrictEqual({ 'a': 'x', 'b': 'y' });
});
test('test set platformValue on multi platform', async () => {
const val = setPlatformValue<Record<string, string>>({}, { 'a': 'x' }, ['win32', 'darwin']);
expect(val).toStrictEqual({
'win32': { 'a': 'x' },
'darwin': { 'a': 'x' }
});
});

View File

@ -1,32 +0,0 @@
import { renderTemplate } from '../src/utils';
test('test Template rendering', async () => {
const out = renderTemplate('s${luaDir}e', { luaDir: 'w123' });
expect(out).toBe('sw123e');
});
test('test Template rendering 2', async () => {
const out = renderTemplate('${HOME}', {
'HOME': 'C:\\Users\\Admin',
'CommonProgramFiles(x86)': 'C:\\Program Files (x86)\\Common Files',
});
expect(out).toBe('C:\\Users\\Admin');
});
test('test Template rendering options.textemplate', async () => {
expect(renderTemplate('pandoc ${ options.textemplate ? `--template="${options.textemplate}"` : `` }',
{ options: { textemplate: 'dissertation.tex' } }))
.toBe('pandoc --template="dissertation.tex"');
expect(renderTemplate('pandoc ${ options.textemplate ? `--template="${options.textemplate}"` : `` }',
{ options: { textemplate: null } }))
.toBe('pandoc ');
});
test('test Template rendering with undefined variable', async () => {
expect(renderTemplate('Hi ${user}', { }))
.toBe('Hi ${user}');
});

View File

@ -1,373 +0,0 @@
% partial rewrite of the LaTeX2e package for submissions to the
% Conference on Neural Information Processing Systems (NeurIPS):
%
% - uses more LaTeX conventions
% - line numbers at submission time replaced with aligned numbers from
% lineno package
% - \nipsfinalcopy replaced with [final] package option
% - automatically loads times package for authors
% - loads natbib automatically; this can be suppressed with the
% [nonatbib] package option
% - adds foot line to first page identifying the conference
% - adds preprint option for submission to e.g. arXiv
% - conference acronym modified
%
% Roman Garnett (garnett@wustl.edu) and the many authors of
% nips15submit_e.sty, including MK and drstrip@sandia
%
% last revision: March 2023
\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{neurips}[2023/03/31 NeurIPS 2023 submission/camera-ready style file]
% declare final option, which creates camera-ready copy
\newif\if@neuripsfinal\@neuripsfinalfalse
\DeclareOption{final}{
\@neuripsfinaltrue
}
% declare nonatbib option, which does not load natbib in case of
% package clash (users can pass options to natbib via
% \PassOptionsToPackage)
\newif\if@natbib\@natbibtrue
\DeclareOption{nonatbib}{
\@natbibfalse
}
% declare preprint option, which creates a preprint version ready for
% upload to, e.g., arXiv
\newif\if@preprint\@preprintfalse
\DeclareOption{preprint}{
\@preprinttrue
}
\ProcessOptions\relax
% determine whether this is an anonymized submission
\newif\if@submission\@submissiontrue
\if@neuripsfinal\@submissionfalse\fi
\if@preprint\@submissionfalse\fi
% fonts
\renewcommand{\rmdefault}{ptm}
\renewcommand{\sfdefault}{phv}
% change this every year for notice string at bottom
\newcommand{\@neuripsordinal}{}
\newcommand{\@neuripsyear}{\the\year}
\newcommand{\@neuripslocation}{}
% acknowledgments
\usepackage{environ}
\newcommand{\acksection}{\section*{Acknowledgments and Disclosure of Funding}}
\NewEnviron{ack}{%
\acksection
\BODY
}
% load natbib unless told otherwise
\if@natbib
\RequirePackage{natbib}
\fi
% set page geometry
\usepackage[verbose=true,letterpaper]{geometry}
\AtBeginDocument{
\newgeometry{
textheight=9in,
textwidth=5.5in,
top=1in,
headheight=12pt,
headsep=25pt,
footskip=30pt
}
\@ifpackageloaded{fullpage}
{\PackageWarning{neurips_2023}{fullpage package not allowed! Overwriting formatting.}}
{}
}
\widowpenalty=10000
\clubpenalty=10000
\flushbottom
\sloppy
% font sizes with reduced leading
\renewcommand{\normalsize}{%
\@setfontsize\normalsize\@xpt\@xipt
\abovedisplayskip 7\p@ \@plus 2\p@ \@minus 5\p@
\abovedisplayshortskip \z@ \@plus 3\p@
\belowdisplayskip \abovedisplayskip
\belowdisplayshortskip 4\p@ \@plus 3\p@ \@minus 3\p@
}
\normalsize
\renewcommand{\small}{%
\@setfontsize\small\@ixpt\@xpt
\abovedisplayskip 6\p@ \@plus 1.5\p@ \@minus 4\p@
\abovedisplayshortskip \z@ \@plus 2\p@
\belowdisplayskip \abovedisplayskip
\belowdisplayshortskip 3\p@ \@plus 2\p@ \@minus 2\p@
}
\renewcommand{\footnotesize}{\@setfontsize\footnotesize\@ixpt\@xpt}
\renewcommand{\scriptsize}{\@setfontsize\scriptsize\@viipt\@viiipt}
\renewcommand{\tiny}{\@setfontsize\tiny\@vipt\@viipt}
\renewcommand{\large}{\@setfontsize\large\@xiipt{14}}
\renewcommand{\Large}{\@setfontsize\Large\@xivpt{16}}
\renewcommand{\LARGE}{\@setfontsize\LARGE\@xviipt{20}}
\renewcommand{\huge}{\@setfontsize\huge\@xxpt{23}}
\renewcommand{\Huge}{\@setfontsize\Huge\@xxvpt{28}}
% sections with less space
\providecommand{\section}{}
\renewcommand{\section}{%
\@startsection{section}{1}{\z@}%
{-2.0ex \@plus -0.5ex \@minus -0.2ex}%
{ 1.5ex \@plus 0.3ex \@minus 0.2ex}%
{\large\bf\raggedright}%
}
\providecommand{\subsection}{}
\renewcommand{\subsection}{%
\@startsection{subsection}{2}{\z@}%
{-1.8ex \@plus -0.5ex \@minus -0.2ex}%
{ 0.8ex \@plus 0.2ex}%
{\normalsize\bf\raggedright}%
}
\providecommand{\subsubsection}{}
\renewcommand{\subsubsection}{%
\@startsection{subsubsection}{3}{\z@}%
{-1.5ex \@plus -0.5ex \@minus -0.2ex}%
{ 0.5ex \@plus 0.2ex}%
{\normalsize\bf\raggedright}%
}
\providecommand{\paragraph}{}
\renewcommand{\paragraph}{%
\@startsection{paragraph}{4}{\z@}%
{1.5ex \@plus 0.5ex \@minus 0.2ex}%
{-1em}%
{\normalsize\bf}%
}
\providecommand{\subparagraph}{}
\renewcommand{\subparagraph}{%
\@startsection{subparagraph}{5}{\z@}%
{1.5ex \@plus 0.5ex \@minus 0.2ex}%
{-1em}%
{\normalsize\bf}%
}
\providecommand{\subsubsubsection}{}
\renewcommand{\subsubsubsection}{%
\vskip5pt{\noindent\normalsize\rm\raggedright}%
}
% float placement
\renewcommand{\topfraction }{0.85}
\renewcommand{\bottomfraction }{0.4}
\renewcommand{\textfraction }{0.1}
\renewcommand{\floatpagefraction}{0.7}
\newlength{\@neuripsabovecaptionskip}\setlength{\@neuripsabovecaptionskip}{7\p@}
\newlength{\@neuripsbelowcaptionskip}\setlength{\@neuripsbelowcaptionskip}{\z@}
\setlength{\abovecaptionskip}{\@neuripsabovecaptionskip}
\setlength{\belowcaptionskip}{\@neuripsbelowcaptionskip}
% swap above/belowcaptionskip lengths for tables
\renewenvironment{table}
{\setlength{\abovecaptionskip}{\@neuripsbelowcaptionskip}%
\setlength{\belowcaptionskip}{\@neuripsabovecaptionskip}%
\@float{table}}
{\end@float}
% footnote formatting
\setlength{\footnotesep }{6.65\p@}
\setlength{\skip\footins}{9\p@ \@plus 4\p@ \@minus 2\p@}
\renewcommand{\footnoterule}{\kern-3\p@ \hrule width 12pc \kern 2.6\p@}
\setcounter{footnote}{0}
% paragraph formatting
\setlength{\parindent}{\z@}
\setlength{\parskip }{5.5\p@}
% list formatting
\setlength{\topsep }{4\p@ \@plus 1\p@ \@minus 2\p@}
\setlength{\partopsep }{1\p@ \@plus 0.5\p@ \@minus 0.5\p@}
\setlength{\itemsep }{2\p@ \@plus 1\p@ \@minus 0.5\p@}
\setlength{\parsep }{2\p@ \@plus 1\p@ \@minus 0.5\p@}
\setlength{\leftmargin }{3pc}
\setlength{\leftmargini }{\leftmargin}
\setlength{\leftmarginii }{2em}
\setlength{\leftmarginiii}{1.5em}
\setlength{\leftmarginiv }{1.0em}
\setlength{\leftmarginv }{0.5em}
\def\@listi {\leftmargin\leftmargini}
\def\@listii {\leftmargin\leftmarginii
\labelwidth\leftmarginii
\advance\labelwidth-\labelsep
\topsep 2\p@ \@plus 1\p@ \@minus 0.5\p@
\parsep 1\p@ \@plus 0.5\p@ \@minus 0.5\p@
\itemsep \parsep}
\def\@listiii{\leftmargin\leftmarginiii
\labelwidth\leftmarginiii
\advance\labelwidth-\labelsep
\topsep 1\p@ \@plus 0.5\p@ \@minus 0.5\p@
\parsep \z@
\partopsep 0.5\p@ \@plus 0\p@ \@minus 0.5\p@
\itemsep \topsep}
\def\@listiv {\leftmargin\leftmarginiv
\labelwidth\leftmarginiv
\advance\labelwidth-\labelsep}
\def\@listv {\leftmargin\leftmarginv
\labelwidth\leftmarginv
\advance\labelwidth-\labelsep}
\def\@listvi {\leftmargin\leftmarginvi
\labelwidth\leftmarginvi
\advance\labelwidth-\labelsep}
% create title
\providecommand{\maketitle}{}
\renewcommand{\maketitle}{%
\par
\begingroup
\renewcommand{\thefootnote}{\fnsymbol{footnote}}
% for perfect author name centering
\renewcommand{\@makefnmark}{\hbox to \z@{$^{\@thefnmark}$\hss}}
% The footnote-mark was overlapping the footnote-text,
% added the following to fix this problem (MK)
\long\def\@makefntext##1{%
\parindent 1em\noindent
\hbox to 1.8em{\hss $\m@th ^{\@thefnmark}$}##1
}
\thispagestyle{empty}
\@maketitle
\@thanks
\@notice
\endgroup
\let\maketitle\relax
\let\thanks\relax
}
% rules for title box at top of first page
\newcommand{\@toptitlebar}{
\hrule height 4\p@
\vskip 0.25in
\vskip -\parskip%
}
\newcommand{\@bottomtitlebar}{
\vskip 0.29in
\vskip -\parskip
\hrule height 1\p@
\vskip 0.09in%
}
% create title (includes both anonymized and non-anonymized versions)
\providecommand{\@maketitle}{}
\renewcommand{\@maketitle}{%
\vbox{%
\hsize\textwidth
\linewidth\hsize
\vskip 0.1in
\@toptitlebar
\centering
{\LARGE\bf \@title\par}
\@bottomtitlebar
\if@submission
\begin{tabular}[t]{c}\bf\rule{\z@}{24\p@}
Anonymous Author(s) \\
Affiliation \\
Address \\
\texttt{email} \\
\end{tabular}%
\else
\def\And{%
\end{tabular}\hfil\linebreak[0]\hfil%
\begin{tabular}[t]{c}\bf\rule{\z@}{24\p@}\ignorespaces%
}
\def\AND{%
\end{tabular}\hfil\linebreak[4]\hfil%
\begin{tabular}[t]{c}\bf\rule{\z@}{24\p@}\ignorespaces%
}
\begin{tabular}[t]{c}\bf\rule{\z@}{24\p@}\@author\end{tabular}%
\fi
\vskip 0.3in \@minus 0.1in
}
}
% add conference notice to bottom of first page
\newcommand{\ftype@noticebox}{8}
\newcommand{\@notice}{%
% give a bit of extra room back to authors on first page
\enlargethispage{2\baselineskip}%
\@float{noticebox}[b]%
\footnotesize\@noticestring%
\end@float%
}
% abstract styling
\renewenvironment{abstract}%
{%
\vskip 0.075in%
\centerline%
{\large\bf Abstract}%
\vspace{0.5ex}%
\begin{quote}%
}
{
\par%
\end{quote}%
\vskip 1ex%
}
% handle tweaks for camera-ready copy vs. submission copy
\if@preprint
\newcommand{\@noticestring}{%
Preprint. Under review.%
}
\else
\if@neuripsfinal
\newcommand{\@noticestring}{%
(\@neuripsyear) \@title
}
\else
\newcommand{\@noticestring}{%
(\@neuripsyear) \@title %
}
% hide the acknowledgements
\NewEnviron{hide}{}
\let\ack\hide
\let\endack\endhide
% line numbers for submission
\RequirePackage{lineno}
\linenumbers
% fix incompatibilities between lineno and amsmath, if required, by
% transparently wrapping linenomath environments around amsmath
% environments
\AtBeginDocument{%
\@ifpackageloaded{amsmath}{%
\newcommand*\patchAmsMathEnvironmentForLineno[1]{%
\expandafter\let\csname old#1\expandafter\endcsname\csname #1\endcsname
\expandafter\let\csname oldend#1\expandafter\endcsname\csname end#1\endcsname
\renewenvironment{#1}%
{\linenomath\csname old#1\endcsname}%
{\csname oldend#1\endcsname\endlinenomath}%
}%
\newcommand*\patchBothAmsMathEnvironmentsForLineno[1]{%
\patchAmsMathEnvironmentForLineno{#1}%
\patchAmsMathEnvironmentForLineno{#1*}%
}%
\patchBothAmsMathEnvironmentsForLineno{equation}%
\patchBothAmsMathEnvironmentsForLineno{align}%
\patchBothAmsMathEnvironmentsForLineno{flalign}%
\patchBothAmsMathEnvironmentsForLineno{alignat}%
\patchBothAmsMathEnvironmentsForLineno{gather}%
\patchBothAmsMathEnvironmentsForLineno{multline}%
}
{}
}
\fi
\fi
\endinput

View File

@ -1,159 +0,0 @@
\documentclass{article}
% if you need to pass options to natbib, use, e.g.:
% \PassOptionsToPackage{numbers, compress}{natbib}
% before loading neurips_2023
% ready for submission
\usepackage[final]{neurips}
% to compile a preprint version, e.g., for submission to arXiv, add add the
% [preprint] option:
% \usepackage[preprint]{neurips_2023}
% to compile a camera-ready version, add the [final] option, e.g.:
% \usepackage[final]{neurips_2023}
% to avoid loading the natbib package, add option nonatbib:
% \usepackage[nonatbib]{neurips_2023}
\usepackage[utf8]{inputenc} % allow utf-8 input
\usepackage[T1]{fontenc} % use 8-bit T1 fonts
\usepackage{hyperref} % hyperlinks
\usepackage{url} % simple URL typesetting
\usepackage{booktabs} % professional-quality tables
\usepackage{amsfonts} % blackboard math symbols
\usepackage{nicefrac} % compact symbols for 1/2, etc.
\usepackage{microtype} % microtypography
\usepackage{xcolor} % colors
\usepackage{graphicx}
\makeatletter
\def\maxwidth{\ifdim\Gin@nat@width>\linewidth\linewidth\else\Gin@nat@width\fi}
\def\maxheight{\ifdim\Gin@nat@height>\textheight\textheight\else\Gin@nat@height\fi}
\makeatother
% Scale images if necessary, so that they will not overflow the page
% margins by default, and it is still possible to overwrite the defaults
% using explicit options in \includegraphics[width, height, ...]{}
\setkeys{Gin}{width=\maxwidth,height=\maxheight,keepaspectratio}
% Set default figure placement to htbp
\makeatletter
\def\fps@figure{htbp}
\makeatother
$if(csl-refs)$
\newlength{\cslhangindent}
\setlength{\cslhangindent}{1.5em}
\newlength{\csllabelwidth}
\setlength{\csllabelwidth}{3em}
\newlength{\cslentryspacingunit} % times entry-spacing
\setlength{\cslentryspacingunit}{\parskip}
\newenvironment{CSLReferences}[2] % #1 hanging-ident, #2 entry spacing
{% don't indent paragraphs
\setlength{\parindent}{0pt}
% turn on hanging indent if param 1 is 1
\ifodd #1
\let\oldpar\par
\def\par{\hangindent=\cslhangindent\oldpar}
\fi
% set entry spacing
\setlength{\parskip}{#2\cslentryspacingunit}
}%
{}
\usepackage{calc}
\newcommand{\CSLBlock}[1]{#1\hfill\break}
\newcommand{\CSLLeftMargin}[1]{\parbox[t]{\csllabelwidth}{#1}}
\newcommand{\CSLRightInline}[1]{\parbox[t]{\linewidth - \csllabelwidth}{#1}\break}
\newcommand{\CSLIndent}[1]{\hspace{\cslhangindent}#1}
$endif$
\providecommand{\tightlist}{%
\setlength{\itemsep}{0pt}\setlength{\parskip}{0pt}}
\title{$title$}
% Iterate through the authors except last to add \And.
\author{%
$for(authors/allbutlast)$
$authors.name$\\$authors.affiliation$\\$authors.institution$\\$authors.email$\\$authors.address$ \And
$endfor$
$for(authors/last)$
$authors.name$\\$authors.affiliation$\\$authors.institution$\\$authors.email$\\$authors.address$
$endfor$
}
% \author{%
% David S.~Hippocampus \\
% Department of Computer Science\\
% Cranberry-Lemon University\\
% Pittsburgh, PA 15213 \\
% \texttt{hippo@cs.cranberry-lemon.edu} \\
% % examples of more authors
% % \And
% % Coauthor \\
% % Affiliation \\
% % Address \\
% % \texttt{email} \\
% % \AND
% % Coauthor \\
% % Affiliation \\
% % Address \\
% % \texttt{email} \\
% % \And
% % Coauthor \\
% % Affiliation \\
% % Address \\
% % \texttt{email} \\
% % \And
% % Coauthor \\
% % Affiliation \\
% % Address \\
% % \texttt{email} \\
% }
\begin{document}
\maketitle
\begin{abstract}
$if(abstract)$
$abstract$
$else$
Add your abstract at the beginning of your markdown file like this
\begin{verbatim}
---
title: "Your Title"
abstract: "your abstract here"
authors:
- name: Leonardo V. Castorina
affiliation: School of Informatics
institution: University of Edinburgh
email: justanemail@domain.ext
address: Edinburgh
- name: Coauthor
affiliation: Affiliation
institution: Institution
email: coauthor@example.com
address: Address
---
\end{verbatim}
This is called YAML frontmatter. If you set your abstract correctly you should not see this message.
$endif$
\end{abstract}
$body$
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\end{document}

View File

@ -1,29 +0,0 @@
{
"compilerOptions": {
"baseUrl": ".",
"inlineSourceMap": true,
"inlineSources": true,
"module": "ESNext",
"target": "ES2021",
"allowJs": true,
"noImplicitAny": true,
"moduleResolution": "node",
"esModuleInterop": true,
"importHelpers": true,
"jsx": "preserve",
"jsxImportSource": "solid-js",
"noEmit": true,
"isolatedModules": true,
"lib": [
"DOM",
"ES5",
"ES6",
"ES7",
"ES2021"
],
"typeRoots": [ "./typings", "./node_modules/@types"]
},
"include": [
"**/*.ts", "**/*.tsx"
]
}

File diff suppressed because it is too large Load Diff

View File

@ -1,34 +0,0 @@
import 'obsidian';
import type { EventRef } from 'obsidian';
declare module 'obsidian' {
export interface DataAdapter {
getBasePath(): string;
getFullPath(path: string): string;
startWatchPath(path: string): void;
stopWatchPath(path: string): void;
}
export interface PluginSettingTab {
name: string;
}
export interface App {
readonly loadProgress: { show(): void; hide(): void; setMessage(msg: string): void; };
plugins: {
enablePlugin(id: string): Promise<void>;
disablePlugin(id: string): Promise<void>;
}
}
export interface Vault {
config: {
attachmentFolderPath: string,
useMarkdownLinks: boolean,
}
on(name: 'raw', callback: (file: string) => void, ctx?: any): EventRef;
}
}

View File

@ -1,29 +0,0 @@
import { readFileSync, writeFileSync } from 'fs';
import { exec } from 'child_process';
import process from 'process';
const pkg = JSON.parse(readFileSync('package.json', 'utf8'));
const version = process.argv.at(2) ?? pkg.version;
if (version != pkg.version) {
pkg.version = version;
writeFileSync('package.json', `${JSON.stringify(pkg, null, 2)}\n`);
exec('git add package.json');
}
// read minAppVersion from manifest.json and bump version to target version
let manifest = JSON.parse(readFileSync('manifest.json', 'utf8'));
const { minAppVersion } = manifest;
manifest.version = pkg.version;
manifest.description = pkg.description;
writeFileSync('manifest.json', JSON.stringify(manifest, null, 2));
// update versions.json with target version and minAppVersion from manifest.json
let versions = JSON.parse(readFileSync('versions.json', 'utf8'));
versions[pkg.version] = minAppVersion;
writeFileSync('versions.json', JSON.stringify(versions, null, '\t'));
exec('git add manifest.json versions.json');

View File

@ -1,53 +0,0 @@
{
"1.0.4": "0.12.0",
"1.0.5": "0.12.0",
"1.0.6": "0.12.0",
"1.0.7": "0.12.0",
"1.0.8": "0.12.0",
"1.0.9": "0.12.0",
"1.0.10": "0.12.0",
"1.0.11": "0.12.0",
"1.1.0": "0.12.0",
"1.1.1": "0.12.0",
"1.1.2": "0.12.0",
"1.1.3": "0.12.0",
"1.1.4": "0.12.0",
"1.1.5": "0.12.0",
"1.1.6": "0.12.0",
"1.1.7": "0.12.0",
"1.1.8": "0.12.0",
"1.1.9": "0.12.0",
"1.1.10": "0.12.0",
"1.2.0": "0.12.0",
"1.2.1": "0.12.0",
"1.3.0": "0.12.0",
"1.3.1": "0.12.0",
"1.4.1": "0.12.0",
"1.5.2": "0.12.0",
"1.5.3": "0.12.0",
"1.5.4": "0.12.0",
"1.6.1": "0.12.0",
"1.7.1": "0.12.0",
"1.7.2": "0.12.0",
"1.8.1": "0.12.0",
"1.8.2": "0.12.0",
"1.8.3": "0.12.0",
"1.8.4": "0.12.0",
"1.8.5": "0.12.0",
"1.8.6": "0.12.0",
"1.8.7": "0.12.0",
"1.8.8": "0.12.0",
"1.9.1": "0.12.0",
"1.9.2": "0.12.0",
"1.9.3": "0.12.0",
"1.9.4": "0.12.0",
"1.9.5": "0.12.0",
"1.9.6": "0.12.0",
"1.10.1": "0.12.0",
"1.10.2": "0.12.0",
"1.10.3": "0.12.0",
"1.10.4": "0.12.0",
"1.10.5": "0.12.0",
"1.10.6": "1.6.3",
"1.10.7": "1.6.3"
}

View File

@ -1,143 +0,0 @@
import { exec } from 'child_process';
import { defineConfig, loadEnv, Plugin, UserConfig } from 'vite';
import { viteStaticCopy } from 'vite-plugin-static-copy';
import solidPlugin from 'vite-plugin-solid';
import builtins from 'builtin-modules';
import path from 'path';
import * as fsp from 'fs/promises';
const banner = `
/*!
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
if you want to view the source, please visit the github repository https://github.com/mokeyish/obsidian-enhancing-export .
*/
`;
export default defineConfig(async ({ mode }) => {
const { normalize } = path;
const { rm } = fsp;
const prod = mode === 'production';
let { OUT_DIR } = loadEnv(mode, process.cwd(), ['OUT_']);
OUT_DIR = normalize(OUT_DIR);
if (OUT_DIR != 'dist' && OUT_DIR != path.join(process.cwd(), 'dist')) {
await rm('dist', { recursive: true });
exec(process.platform === 'win32' ? `mklink /J dist ${OUT_DIR}` : `ln -s ${OUT_DIR} dist`);
}
return {
plugins: [
solidPlugin(),
viteStaticCopy({
targets: [{
src: 'manifest.json',
dest: '.'
}]
}),
loader({
'.lua': 'binary',
'.tex': 'binary',
'.sty': 'binary'
}), // src/resources.ts
prod ? undefined : inject(['src/hmr.ts']),
],
build: {
lib: {
entry: 'src/main.ts',
name: 'main',
fileName: () => 'main.js',
formats: ['cjs'],
},
minify: prod,
sourcemap: prod ? false : 'inline',
cssCodeSplit: false,
emptyOutDir: false,
// outDir: '',
rollupOptions: {
output: {
exports: 'named',
assetFileNames: (v) => v.name === 'style.css' ? 'styles.css' : v.name,
banner,
},
external: [
'obsidian',
'electron',
'@codemirror/autocomplete',
'@codemirror/closebrackets',
'@codemirror/collab',
'@codemirror/commands',
'@codemirror/comment',
'@codemirror/fold',
'@codemirror/gutter',
'@codemirror/highlight',
'@codemirror/history',
'@codemirror/language',
'@codemirror/lint',
'@codemirror/matchbrackets',
'@codemirror/panel',
'@codemirror/rangeset',
'@codemirror/rectangular-selection',
'@codemirror/search',
'@codemirror/state',
'@codemirror/stream-parser',
'@codemirror/text',
'@codemirror/tooltip',
'@codemirror/view',
'@lezer/common',
'@lezer/highlight',
'@lezer/lr',
...builtins
],
},
}
} as UserConfig;
});
const loader = (config: { [extention: string]: 'binary' }): Plugin => {
const { extname } = path;
return {
name: 'binary-boader',
enforce: 'pre',
async load(id) {
const format = config[extname(id)];
if (format) {
if (format === 'binary') {
const buffer = await fsp.readFile(id);
return {
code: `export default Uint8Array.from(atob('${buffer.toString('base64')}'), c => c.charCodeAt(0));`
};
}
}
},
};
};
const inject = (files: string[]): Plugin => {
if (files && files.length > 0) {
return {
name: 'inject-code',
async load(this, id) {
const info = this.getModuleInfo(id);
if (info.isEntry) {
const code = await fsp.readFile(id, 'utf-8');
const { relative, dirname, basename, extname, join } = path;
const dir = dirname(id);
const inject_code = files
.map(v => relative(dir, v))
.map(p => join('./', basename(p, extname(p))))
.map(p => `import './${p}'`).join(';');
return `
${inject_code};
${code}
`;
}
},
};
}
};