init: 1.0.0a
|
|
@ -1,5 +1,4 @@
|
|||
**/builds
|
||||
node_modules
|
||||
.vscode
|
||||
package-lock.json
|
||||
zotero-cmd.json
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
// 使用 IntelliSense 了解相关属性。
|
||||
// 悬停以查看现有属性的描述。
|
||||
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Restart-Z6",
|
||||
"runtimeExecutable": "npm",
|
||||
"runtimeArgs": [
|
||||
"run",
|
||||
"restart-dev-z6"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Restart-Z7",
|
||||
"runtimeExecutable": "npm",
|
||||
"runtimeArgs": [
|
||||
"run",
|
||||
"restart-dev-z7"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Restart in Prod Mode",
|
||||
"runtimeExecutable": "npm",
|
||||
"runtimeArgs": [
|
||||
"run",
|
||||
"restart-prod"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
{
|
||||
"appendElement - full": {
|
||||
"scope": "javascript,typescript",
|
||||
"prefix": "appendElement",
|
||||
"body": [
|
||||
"appendElement({",
|
||||
"\ttag: '${1:div}',",
|
||||
"\tid: '${2:id}',",
|
||||
"\tnamespace: '${3:html}',",
|
||||
"\tclassList: ['${4:class}'],",
|
||||
"\tstyles: {${5:style}: '$6'},",
|
||||
"\tproperties: {},",
|
||||
"\tattributes: {},",
|
||||
"\t[{ '${7:onload}', (e: Event) => $8, ${9:false} }],",
|
||||
"\tcheckExistanceParent: ${10:HTMLElement},",
|
||||
"\tignoreIfExists: ${11:true},",
|
||||
"\tskipIfExists: ${12:true},",
|
||||
"\tremoveIfExists: ${13:true},",
|
||||
"\tcustomCheck: (doc: Document, options: ElementOptions) => ${14:true},",
|
||||
"\tchildren: [$15]",
|
||||
"}, ${16:container});"
|
||||
]
|
||||
},
|
||||
"appendElement - minimum": {
|
||||
"scope": "javascript,typescript",
|
||||
"prefix": "appendElement",
|
||||
"body": "appendElement({ tag: '$1' }, $2);"
|
||||
},
|
||||
"register Notifier": {
|
||||
"scope": "javascript,typescript",
|
||||
"prefix": "registerObserver",
|
||||
"body": [
|
||||
"registerObserver({",
|
||||
"\t notify: (",
|
||||
"\t\tevent: _ZoteroTypes.Notifier.Event,",
|
||||
"\t\ttype: _ZoteroTypes.Notifier.Type,",
|
||||
"\t\tids: string[],",
|
||||
"\t\textraData: _ZoteroTypes.anyObj",
|
||||
"\t) => {",
|
||||
"\t\t$0",
|
||||
"\t}",
|
||||
"});"
|
||||
]
|
||||
}
|
||||
}
|
||||
149
LICENSE
|
|
@ -1,23 +1,21 @@
|
|||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
The GNU Affero General Public License is a free, copyleft license for
|
||||
software and other kinds of works, specifically designed to ensure
|
||||
cooperation with the community in the case of network server software.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
our General Public Licenses are intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
software for all its users.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
|
|
@ -26,44 +24,34 @@ them if you wish), that you receive source code or can get it if you
|
|||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
Developers that use our General Public Licenses protect your rights
|
||||
with two steps: (1) assert copyright on the software, and (2) offer
|
||||
you this License which gives you legal permission to copy, distribute
|
||||
and/or modify the software.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
A secondary benefit of defending all users' freedom is that
|
||||
improvements made in alternate versions of the program, if they
|
||||
receive widespread use, become available for other developers to
|
||||
incorporate. Many developers of free software are heartened and
|
||||
encouraged by the resulting cooperation. However, in the case of
|
||||
software used on network servers, this result may fail to come about.
|
||||
The GNU General Public License permits making a modified version and
|
||||
letting the public access it on a server without ever releasing its
|
||||
source code to the public.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
The GNU Affero General Public License is designed specifically to
|
||||
ensure that, in such cases, the modified source code becomes available
|
||||
to the community. It requires the operator of a network server to
|
||||
provide the source code of the modified version running there to the
|
||||
users of that server. Therefore, public use of a modified version, on
|
||||
a publicly accessible server, gives the public access to the source
|
||||
code of the modified version.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
An older license, called the Affero General Public License and
|
||||
published by Affero, was designed to accomplish similar goals. This is
|
||||
a different license, not a version of the Affero GPL, but Affero has
|
||||
released a new version of the Affero GPL which permits relicensing under
|
||||
this license.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
|
@ -72,7 +60,7 @@ modification follow.
|
|||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
|
@ -549,35 +537,45 @@ to collect a royalty for further conveying from those to whom you convey
|
|||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, if you modify the
|
||||
Program, your modified version must prominently offer all users
|
||||
interacting with it remotely through a computer network (if your version
|
||||
supports such interaction) an opportunity to receive the Corresponding
|
||||
Source of your version by providing access to the Corresponding Source
|
||||
from a network server at no charge, through some standard or customary
|
||||
means of facilitating copying of software. This Corresponding Source
|
||||
shall include the Corresponding Source for any work covered by version 3
|
||||
of the GNU General Public License that is incorporated pursuant to the
|
||||
following paragraph.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
under version 3 of the GNU General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
but the work with which it is combined will remain governed by version
|
||||
3 of the GNU General Public License.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
the GNU Affero General Public License from time to time. Such new versions
|
||||
will be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Program specifies that a certain numbered version of the GNU Affero General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
GNU Affero General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
versions of the GNU Affero General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
|
|
@ -635,40 +633,29 @@ the "copyright" line and a pointer to where the full notice is found.
|
|||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
If your software can interact with users remotely through a computer
|
||||
network, you should also make sure that it provides a way for users to
|
||||
get its source. For example, if your program is a web application, its
|
||||
interface could display a "Source" link that leads users to an archive
|
||||
of the code. There are many ways you could offer source, and different
|
||||
solutions will be better for different programs; see section 13 for the
|
||||
specific requirements.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
|
|
|||
180
README.md
|
|
@ -1,180 +0,0 @@
|
|||
# Zotero Better Notes
|
||||
|
||||

|
||||
|
||||
Everything about note management. All in Zotero.
|
||||
|
||||
Better Notes Handbook: [EN](https://github.com/windingwind/zotero-better-notes/wiki) | [中文 (provide translation)](https://zotero.yuque.com/staff-gkhviy/better-notes/biigg4?)
|
||||
|
||||
User Guide: [EN](./UserGuide.md) | [中文](./UserGuideCN.md)
|
||||
|
||||
## Introduction
|
||||
|
||||
Better Notes is a plugin for [Zotero](https://zotero.org).
|
||||
|
||||
It streamlines your unordered workflows of metadata analyzing, paper reading, annotating, and note-taking into a closed loop in Zotero.
|
||||
|
||||
Works out of the box and is highly customizable.
|
||||
|
||||
## Contents
|
||||
|
||||
- [Quick start](#quick-start)
|
||||
- [Install](#install)
|
||||
- [Note workspace](#note-workspace)
|
||||
- Connect notes with the [bi-directional links](#bi-directional-link-support)
|
||||
- Automate note generation with the [note templates](#note-templates)
|
||||
- [Export notes](#export) to different formats
|
||||
- Integrate with 3rd-party MarkDown editors seamlessly with [note<->markdown syncing](#syncing-note-markdown)
|
||||
- [Other features](#other-features)
|
||||
|
||||
## Quick Start
|
||||
|
||||
> See [Handbook:Quick Start](https://zotero.yuque.com/staff-gkhviy/better-notes/gw5d7v) for more details.
|
||||
|
||||
New to note-taking? Install and start now!
|
||||
|
||||
Already an Obsidian/Logseq/... user? Forget those complicated integration tools and keep them in sync with MarkDown files with just one click.
|
||||
|
||||
## Install
|
||||
|
||||
- Download the latest release (.xpi file) from the [Releases Page](https://github.com/windingwind/zotero-better-notes/releases)_Note_ If you're using Firefox as your browser, right-click the `.xpi` and select "Save As.."
|
||||
- In Zotero click `Tools` in the top menu bar and then click `Addons`
|
||||
- Go to the Extensions page and then click the gear icon in the top right.
|
||||
- Select `Install Add-on from file`.
|
||||
- Browse to where you downloaded the `.xpi` file and select it.
|
||||
- Restart Zotero, by clicking `restart now` in the extensions list where the plugin is now listed.
|
||||
|
||||
## Note Workspace
|
||||
|
||||
> See [Handbook:Workspace](https://zotero.yuque.com/staff-gkhviy/better-notes/yul2qm) for more details.
|
||||
|
||||
The workspace allows you to focus on the note, as shown in the teaser on top of the README.
|
||||
|
||||
- Note outline(tree view, mindmap, and bubble map)
|
||||
- Note links Preview
|
||||
|
||||
## Bi-directional Link
|
||||
|
||||
> See [Handbook:Bi-directional Link](https://zotero.yuque.com/staff-gkhviy/better-notes/yxpiew) for more details.
|
||||
|
||||
The bi-directional link note(双链笔记) is supported. Link your notes inside Zotero with just one click.
|
||||
|
||||
Export with its' linked sub-notes to Obsidian:
|
||||

|
||||
|
||||
## Note Templates
|
||||
|
||||
> See [Handbook:Note Templates](https://zotero.yuque.com/staff-gkhviy/better-notes/un54wc) for more details.
|
||||
|
||||
Use customized templates to import data from items/notes!
|
||||

|
||||
|
||||
[How to Use Templates](./TemplateUsage.md)
|
||||
|
||||
[How to Write Your Own Template](./TemplateDoc)
|
||||
|
||||
See what templates can do and find templates contributed by the community here: [Note Templates from Community](https://github.com/windingwind/zotero-better-notes/discussions/categories/note-templates)
|
||||
|
||||
## Export
|
||||
|
||||
> See [Handbook:Export](https://zotero.yuque.com/staff-gkhviy/better-notes/nxlngg) for more details.
|
||||
|
||||
- To new note in Zotero
|
||||
- To MarkDown file(embed or link, with images)
|
||||
- To MS Word document(.docx)
|
||||
- To PDF document(.pdf)
|
||||
- To FreeMind file(.mm)
|
||||
|
||||
## Syncing: Note<->MarkDown
|
||||
|
||||
> See [Handbook:Sycn](https://zotero.yuque.com/staff-gkhviy/better-notes/aid2c3) for more details.
|
||||
|
||||
It's painless to Better Notes into your current workflow if you are using software like Obsidian. Keep your notes in sync with external MD files with one click.
|
||||
|
||||
Click 'Auto Sync to Export Path' the first time you export your note. Do not require any third-party tools or complicated setups!
|
||||
|
||||
Any modification in the note or its corresponding MarkDown file will be automatically synced.
|
||||
|
||||

|
||||
|
||||
## Other Features
|
||||
|
||||
> See [Handbook:Other Features](https://zotero.yuque.com/staff-gkhviy/better-notes/sh4v2y) for more details.
|
||||
|
||||
- Quick Note: annotation to note with one click. Support MarkDown comments.
|
||||
<img src="./image/README/markdowncomment.png" width="400px"></img>
|
||||
- Auto-insert new annotations to note. Disabled by default.
|
||||
- Format MarkDown/AsciiDoc in the clipboard.
|
||||
- Quick Cite: cite items in the note with the given cite format.
|
||||
- Image annotation math OCR.
|
||||
- Resize images (right-click on images).
|
||||
- Preview images (double-click/ctrl-click on images).
|
||||
- Customize link actions.
|
||||
- Note editor enhancements.
|
||||
- Quick switch main note.
|
||||
- Copy note link.
|
||||
- Import from MarkDown.
|
||||
- Quick Cite.
|
||||
- ...
|
||||
|
||||
## Development & Contributing
|
||||
|
||||
This addon is built based on the [Zotero Addon Template](https://github.com/windingwind/zotero-addon-template).
|
||||
|
||||
### Build
|
||||
|
||||
```shell
|
||||
# A release-it command: version increase, npm run build, git push, and GitHub release
|
||||
# You need to set the environment variable GITHUB_TOKEN https://github.com/settings/tokens
|
||||
# release-it: https://github.com/release-it/release-it
|
||||
npm run release
|
||||
```
|
||||
|
||||
Alternatively, build it directly using build.js: `npm run build`
|
||||
|
||||
### Debug
|
||||
|
||||
1. Copy the Zotero command line config file. Modify the commands.
|
||||
|
||||
```sh
|
||||
cp zotero-cmd-default.json zotero-cmd.json
|
||||
```
|
||||
|
||||
2. Initialize the addon development environment following this [link](https://www.zotero.org/support/dev/client_coding/plugin_development#setting_up_a_plugin_development_environment).
|
||||
|
||||
3. Build the addon and restart Zotero with this npm command.
|
||||
|
||||
```sh
|
||||
npm run restart
|
||||
```
|
||||
|
||||
You can also debug code in these ways:
|
||||
|
||||
- Test code segments in Tools->Developer->Run Javascript;
|
||||
- Debug output with `Zotero.debug()`. Find the outputs in Help->Debug Output Logging->View Output;
|
||||
- UI debug. Zotero is built on the Firefox XUL framework. Debug XUL UI with software like [XUL Explorer](https://udn.realityripple.com/docs/Archive/Mozilla/XUL_Explorer).
|
||||
> XUL Documents:
|
||||
> https://www.xul.fr/tutorial/
|
||||
> http://www.xulplanet.com/
|
||||
|
||||
## Disclaimer
|
||||
|
||||
Use this code under AGPL (open source required). No warranties are provided. Keep the laws of your locality in mind!
|
||||
|
||||
Part of the code of this repo refers to other open-source projects within the allowed scope.
|
||||
|
||||
- [zotero-pdf-translate](https://github.com/windingwind/zotero-pdf-translate)
|
||||
|
||||
## My Other Zotero Addons
|
||||
|
||||
- [zotero-pdf-preview](https://github.com/windingwind/zotero-pdf-preview) PDF preview for Zotero
|
||||
- [zotero-pdf-translate](https://github.com/windingwind/zotero-pdf-translate) PDF translation for Zotero 6
|
||||
- [zotero-tag](https://github.com/windingwind/zotero-tag) Automatically tag items/Batch tagging
|
||||
|
||||
## Sponsor Me
|
||||
|
||||
I'm windingwind, an active Zotero(https://www.zotero.org) plugin developer. Devoting to making reading papers easier.
|
||||
|
||||
Sponsor me to buy a cup of coffee. I spend more than 24 hours every week coding, debugging, and replying to issues in my plugin repositories. The plugins are open-source and totally free.
|
||||
|
||||
If you sponsor more than $10 a month, you can list your name/logo here and have priority for feature requests/bug fixes!
|
||||
25
Template.md
|
|
@ -1,25 +0,0 @@
|
|||
# Note Template
|
||||
|
||||
## Introduction
|
||||
|
||||
Note Template is an advanced feature that allows users insert anything into the note.
|
||||
|
||||
You can easily reuse your note segments by making them a template.
|
||||
|
||||
Moreover, you can use `Javascript` code to generate custom contents based on the input!
|
||||
|
||||

|
||||
|
||||
## Documentations
|
||||
|
||||
For new users, please see [Usage Documentation](TemplateUsage.md). It will guide you to know how to use templates.
|
||||
|
||||
If you want to write your own template, you can either add a new template from an existing note, or follow this [Template Documentation](TemplateDoc.md).
|
||||
|
||||
## Template Market
|
||||
|
||||
See [[Documentation] Note Templates from Community](https://github.com/windingwind/zotero-better-notes/issues/85)
|
||||
|
||||
Here are templates provided by other Zotero Better Notes users. Search to find one that suits your need!
|
||||
|
||||
If you don't know how to make a template, it is welcomed to discuss or ask for help in a new issue.
|
||||
495
TemplateDoc.md
|
|
@ -1,495 +0,0 @@
|
|||
# How to Write Your Own Template
|
||||
|
||||
This documentation is like a dictionary. For beginners, see [How to Use Templates](./TemplateUsage.md).
|
||||
|
||||
Use `Ctrl+F` to look up what you need and make your own template!
|
||||
|
||||
## Stages
|
||||
|
||||
Some type of templates(Item) support stages.
|
||||
|
||||
Code wrapped inside the stage will be called at a specific time.
|
||||
|
||||
For example, the `beforeloop` stage code:
|
||||
|
||||
```js
|
||||
// @beforeloop-begin
|
||||
code;
|
||||
// @beforeloop-end
|
||||
```
|
||||
|
||||
| stage | calling time |
|
||||
| ---------- | --------------------------------- |
|
||||
| beforeloop | before entering the loop of items |
|
||||
| default | loop of items |
|
||||
| afterloop | after leaving the loop of items |
|
||||
|
||||
In other type of templates, the default stage is called.
|
||||
|
||||
## Variables
|
||||
|
||||
### QuickInsert
|
||||
|
||||
> variables: link: string, subNoteItem, noteItem
|
||||
|
||||
### QuickBackLink
|
||||
|
||||
> variables: subNoteItem, noteItem
|
||||
|
||||
### QuickImport
|
||||
|
||||
> variables: subNoteLines: string[], subNoteItem, noteItem
|
||||
|
||||
### QuickNoteV3
|
||||
|
||||
> variables: annotationItem, topItem
|
||||
|
||||
### ExportMDFileName
|
||||
|
||||
> variables: noteItem
|
||||
|
||||
### ExportMDFileHeader
|
||||
|
||||
> variables: noteItem
|
||||
|
||||
The return value must be a JSON string.
|
||||
|
||||
### Text
|
||||
|
||||
> variables: -
|
||||
|
||||
### Item
|
||||
|
||||
> beforeloop stage: items, copyNoteImage, editor, sharedObj(for temporary variables, shared by all stages)
|
||||
> default stage: topItem, itemNotes, copyNoteImage, editor, sharedObj
|
||||
> afterloop stage: items, copyNoteImage, editor, sharedObj
|
||||
|
||||
### Note
|
||||
|
||||
> Removed after v0.7.15+
|
||||
|
||||
## Formats
|
||||
|
||||
### Line
|
||||
|
||||
Description: Normal line.
|
||||
Template Type: None.
|
||||
Required Variable: None.
|
||||
|
||||
```html
|
||||
<p>Your Line Here</p>
|
||||
```
|
||||
|
||||
### Heading
|
||||
|
||||
Description: From h1 to h6.
|
||||
Template Type: None.
|
||||
Required Variable: None.
|
||||
|
||||
```html
|
||||
<h1>Your Heding 1 Here</h1>
|
||||
<h2>Your Heding 2 Here</h2>
|
||||
```
|
||||
|
||||
### Highlight
|
||||
|
||||
Description: Highlight.
|
||||
Template Type: None.
|
||||
Required Variable: None.
|
||||
|
||||
```html
|
||||
<p style="background-color:#dbeedd;">Text</p>
|
||||
```
|
||||
|
||||
### Strong
|
||||
|
||||
Description: **Strong text**. Put it in a `p`.
|
||||
Template Type: None.
|
||||
Required Variable: None.
|
||||
|
||||
```html
|
||||
<strong>Your Text</strong>
|
||||
```
|
||||
|
||||
### Underline
|
||||
|
||||
Description: Underline text. Put it in a `p`.
|
||||
Template Type: None.
|
||||
Required Variable: None.
|
||||
|
||||
```html
|
||||
<u>Your Text</u>
|
||||
```
|
||||
|
||||
### Deleteline
|
||||
|
||||
Description: Deleteline text. Put it in a `p`.
|
||||
Template Type: None.
|
||||
Required Variable: None.
|
||||
|
||||
```html
|
||||
<span style="text-decoration: line-through">Your Text</span>
|
||||
```
|
||||
|
||||
### Link
|
||||
|
||||
Description: Link. Put it in a `p`.
|
||||
Template Type: None.
|
||||
Required Variable: None.
|
||||
|
||||
```html
|
||||
<a href="zotero://replace/this/link">Link Text</a>
|
||||
```
|
||||
|
||||
### Number List
|
||||
|
||||
Description: Number List.
|
||||
Template Type: None.
|
||||
Required Variable: None.
|
||||
|
||||
```html
|
||||
<ol>
|
||||
<li>First</li>
|
||||
<li>Second</li>
|
||||
<li>...</li>
|
||||
</ol>
|
||||
```
|
||||
|
||||
### Bullet List
|
||||
|
||||
Description: Bullet List.
|
||||
Template Type: None.
|
||||
Required Variable: None.
|
||||
|
||||
```html
|
||||
<ul>
|
||||
<li>First</li>
|
||||
<li>Second</li>
|
||||
<li>...</li>
|
||||
</ul>
|
||||
```
|
||||
|
||||
### Sup & Sub Text
|
||||
|
||||
Description: $\text{The}^{sup} \ and \ \text{the}_{sub}$ text.
|
||||
Template Type: None.
|
||||
Required Variable: None.
|
||||
|
||||
```html
|
||||
<p>The<sup>sup</sup>and the<sub>sub</sub> text</p>
|
||||
```
|
||||
|
||||
### Block Quote
|
||||
|
||||
Description:
|
||||
|
||||
> Block Quote
|
||||
|
||||
Template Type: None.
|
||||
Required Variable: None.
|
||||
|
||||
```html
|
||||
<blockquote>
|
||||
<p>Text</p>
|
||||
</blockquote>
|
||||
```
|
||||
|
||||
### Monospaced
|
||||
|
||||
Description: `Monospaced`
|
||||
Template Type: None.
|
||||
Required Variable: None.
|
||||
|
||||
```html
|
||||
<pre>Text</pre>
|
||||
```
|
||||
|
||||
### Table
|
||||
|
||||
Description:
|
||||
| Table | Column1 | Column2 |
|
||||
| ---- | ---- | ---- |
|
||||
| 00 | 01 | 02 |
|
||||
| 10 | 11 | 12 |
|
||||
|
||||
Template Type: None.
|
||||
Required Variable: None.
|
||||
|
||||
```html
|
||||
<table>
|
||||
<tr>
|
||||
<th style="background-color:#dbeedd;">
|
||||
<p style="text-align: right">Table</p>
|
||||
</th>
|
||||
<th style="background-color:#dbeedd;">Column1</th>
|
||||
<th style="background-color:#dbeedd;">Column2</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>00</td>
|
||||
<td>01</td>
|
||||
<td>02</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>10</td>
|
||||
<td>11</td>
|
||||
<td>12</td>
|
||||
</tr>
|
||||
</table>
|
||||
```
|
||||
|
||||
## General Fields
|
||||
|
||||
### Current Date
|
||||
|
||||
Description: Current Date.
|
||||
Required Variable: None.
|
||||
|
||||
```js
|
||||
<p>${new Date().toLocaleDateString()}</p>
|
||||
```
|
||||
|
||||
### Tags
|
||||
|
||||
Description: `item.getTags()` returns tags in list. Usually use `tags.includes("YourTag") ? do sth : do sth else`.
|
||||
Required Variable: any item/note/annotation.
|
||||
|
||||
```js
|
||||
<p>${topItem.getTags()}</p>
|
||||
```
|
||||
|
||||
## Note Fields
|
||||
|
||||
### Note Title
|
||||
|
||||
Description: Note Title. First line of note.
|
||||
Template Type: Note.
|
||||
Required Variable: noteItem/subNoteItem.
|
||||
|
||||
```js
|
||||
<p>${noteItem.getNoteTitle()}</p>
|
||||
```
|
||||
|
||||
### Note Content
|
||||
|
||||
Description: Note Content.
|
||||
Template Type: Note.
|
||||
Required Variable: noteItem/subNoteItem.
|
||||
|
||||
```js
|
||||
<p>${noteItem.getNote()}</p>
|
||||
```
|
||||
|
||||
### Note Link
|
||||
|
||||
Description: Note Link.
|
||||
Template Type: Note.
|
||||
Required Variable: noteItem/subNoteItem.
|
||||
|
||||
```js
|
||||
<p>
|
||||
<a href="${Zotero.BetterNotes.knowledge.getNoteLink(noteItem)}">
|
||||
${noteItem.key}
|
||||
</a>
|
||||
</p>
|
||||
```
|
||||
|
||||
## Item Fields
|
||||
|
||||
### Title
|
||||
|
||||
Description: Item title.
|
||||
Template Type: Item.
|
||||
Required Variable: topItem.
|
||||
|
||||
```js
|
||||
<p>${topItem.getField("title")}</p>
|
||||
```
|
||||
|
||||
### Publisher/Journal
|
||||
|
||||
Description: Publisher/Journal.
|
||||
Template Type: Item.
|
||||
Required Variable: topItem.
|
||||
|
||||
```js
|
||||
<p>
|
||||
$
|
||||
{(() => {
|
||||
if (topItem.itemType === "conferencePaper") {
|
||||
return;
|
||||
topItem.getField("conferenceName") ||
|
||||
topItem.getField("proceedingsTitle");
|
||||
}
|
||||
if (topItem.itemType === "journalArticle")
|
||||
return topItem.getField("publicationTitle");
|
||||
if (topItem.itemType === "report") return topItem.getField("institution");
|
||||
return topItem.getField("publicationTitle");
|
||||
})()}
|
||||
</p>
|
||||
```
|
||||
|
||||
### Authors
|
||||
|
||||
Description: Authors.
|
||||
Template Type: Item.
|
||||
Required Variable: topItem.
|
||||
|
||||
```js
|
||||
<p>
|
||||
$
|
||||
{topItem
|
||||
.getCreators()
|
||||
.map((v) => v.firstName + " " + v.lastName)
|
||||
.join("; ")}
|
||||
</p>
|
||||
```
|
||||
|
||||
### Pub. date
|
||||
|
||||
Description: Pub. date.
|
||||
Template Type: Item.
|
||||
Required Variable: topItem.
|
||||
|
||||
```js
|
||||
<p>${topItem.getField("date")}</p>
|
||||
```
|
||||
|
||||
### Pub. date
|
||||
|
||||
Description: Publication date/time.
|
||||
Template Type: Item.
|
||||
Required Variable: topItem.
|
||||
|
||||
```js
|
||||
<p>${topItem.getField("date")}</p>
|
||||
```
|
||||
|
||||
### DOI
|
||||
|
||||
Description: DOI.
|
||||
Template Type: Item.
|
||||
Required Variable: topItem.
|
||||
|
||||
```js
|
||||
<p>
|
||||
<a href="https://doi.org/${topItem.getField('DOI')}">
|
||||
${topItem.getField("DOI")}
|
||||
</a>
|
||||
</p>
|
||||
```
|
||||
|
||||
### URL
|
||||
|
||||
Description: URL.
|
||||
Template Type: Item.
|
||||
Required Variable: topItem.
|
||||
|
||||
```js
|
||||
<p>
|
||||
<a href="${topItem.getField('url')}">${topItem.getField("url")}</a>
|
||||
</p>
|
||||
```
|
||||
|
||||
### CitationKey
|
||||
|
||||
Description: CitationKey.
|
||||
Template Type: Item.
|
||||
Required Variable: topItem.
|
||||
|
||||
```js
|
||||
<p>${topItem.citationKey ? topItem.citationKey : ""}</p>
|
||||
```
|
||||
|
||||
### PDF Link
|
||||
|
||||
Description: URL.
|
||||
Template Type: Item.
|
||||
Required Variable: topItem.
|
||||
|
||||
```js
|
||||
<p>
|
||||
$
|
||||
{((topItem) => {
|
||||
const getPDFLink = (_item) => {
|
||||
let libraryID = _item.libraryID;
|
||||
let library = Zotero.Libraries.get(libraryID);
|
||||
let itemKey = _item.key;
|
||||
let itemLink = "";
|
||||
if (library.libraryType === "user") {
|
||||
itemLink = `zotero://open-pdf/library/items/${itemKey}`;
|
||||
} else if (library.libraryType === "group") {
|
||||
itemLink = `zotero://open-pdf/groups/${library.id}/items/${itemKey}`;
|
||||
}
|
||||
return `<a href="${itemLink}">${_item.getFilename()}</a>`;
|
||||
};
|
||||
return Zotero.Items.get(topItem.getAttachments())
|
||||
.filter((i) => i.isPDFAttachment())
|
||||
.map((i) => getPDFLink(i))
|
||||
.join("\n");
|
||||
})(topItem)}
|
||||
</p>
|
||||
```
|
||||
|
||||
### Item Notes
|
||||
|
||||
Description: Walk all sub notes under an item. You can add your code inside the loop.
|
||||
Template Type: Item.
|
||||
Required Variable: topItem.
|
||||
|
||||
```js
|
||||
${itemNotes.map((noteItem)=>{
|
||||
// process each item note
|
||||
const noteLine = `<blockquote>
|
||||
${noteItem.getNote()}
|
||||
</blockquote>`;
|
||||
copyNoteImage(noteItem);
|
||||
return noteLine;
|
||||
}).join("\n")}
|
||||
```
|
||||
|
||||
### About Item Fields
|
||||
|
||||
The `noteItem` and `topItem` is a Zotero Item object. The general data can be accessed using the `getField()` method.
|
||||
|
||||
For example: `topItem.getField('title')` will return the title of the `topItem`.
|
||||
|
||||
```ts
|
||||
// Get Item Fields
|
||||
getField: (void)=>string;
|
||||
|
||||
// Get Authors
|
||||
getCreators: (void)=>{
|
||||
fieldMode: number,
|
||||
firstName: string, // may be empty
|
||||
lastName: string,
|
||||
creatorTypeID: number,
|
||||
}[];
|
||||
```
|
||||
|
||||
Find available fields of the selected item with the code below:
|
||||
|
||||
```js
|
||||
const item = ZoteroPane.getSelectedItems().shift();
|
||||
const usedFields = item.getUsedFields();
|
||||
Zotero.ItemFields.getAll()
|
||||
.filter((e) => usedFields.indexOf(e.id) >= 0)
|
||||
.map((e) => e.name);
|
||||
```
|
||||
|
||||
The result is like this (depending on the item you select):
|
||||
|
||||
```JSON
|
||||
[
|
||||
"0": "title"
|
||||
"1": "date"
|
||||
"2": "language"
|
||||
"3": "shortTitle"
|
||||
"4": "libraryCatalog"
|
||||
"5": "url"
|
||||
"6": "accessDate"
|
||||
"7": "pages"
|
||||
"8": "conferenceName"
|
||||
]
|
||||
```
|
||||
|
||||
or see https://aurimasv.github.io/z2csl/typeMap.xml for the detailed Zotero fields documentation.
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
# How to Use Templates
|
||||
|
||||
This documentation is for beginners.
|
||||
|
||||
If you want to customize your own template, see [How to Write Your Own Template](./TemplateDoc.md). Post an issue if you need help.
|
||||
|
||||
You can find templates under the Workspace Tab/Window->Edit:
|
||||

|
||||
|
||||
## Add a template
|
||||
|
||||
Click the _Edit Templates_ to open the editor.
|
||||

|
||||
|
||||
Create a blank template, or from a note. Here are example templates for the test:
|
||||
|
||||
Copy The Name & template text to a new template and save it!
|
||||
|
||||
**Template Name must include one of these keywords:**
|
||||
|
||||
### Custom Templates
|
||||
|
||||
These templates can have different names. The keyword must be incluede in the template name.
|
||||
|
||||
- Text: indicate it's a normal template
|
||||
- Item: indicate it's an item template. Must select items before inserting
|
||||
|
||||
### System Templates
|
||||
|
||||
Only the template with specific name will be called.
|
||||
|
||||
- QuickInsert: Called when inserting a note link to main note.
|
||||
- QuickBackLink: Called when inserting a note link to main note. The template will be inserted to the end of the sub-note and point to the main note by default.
|
||||
- QuickImport: Called when importing a sub-note to main note.
|
||||
- QuickNoteV3: Called when creating a note from an annotation.
|
||||
> QuickNote is deprecated since v0.6.25
|
||||
- ExportMDFileName: Called when exporting notes to markdown in batch/linked notes to markdown mode. The rendered template will be file name.
|
||||
- ExportMDFileHeader: Called when exporting notes to markdown in batch/linked notes to markdown mode. The rendered template will be YAML header.
|
||||
|
||||
## Template Examples
|
||||
|
||||
Welcome to share & contribute your template! See [Templates From Community](https://github.com/windingwind/zotero-better-notes/discussions/categories/note-templates) for more templates.
|
||||
193
UserGuide.md
|
|
@ -1,193 +0,0 @@
|
|||
# Zotero Better Notes User Guide: Workflow
|
||||
|
||||
Welcome to **Zotero Better Notes** !
|
||||
|
||||
This note helps you quickly learn how to use this addon in 3 min!
|
||||
|
||||
Let's start now.
|
||||
|
||||
## 1 What is Knowledge
|
||||
|
||||
Knowledge is an extension of Zotero's built-in note function.
|
||||
|
||||
Zotero's note is much like a markdown/rich-text editor. You can edit the format with the tools above⬆️.
|
||||
|
||||
### 1.1 Workspace Window
|
||||
|
||||
The knowledge workspace window contains an outline area(left side⬅️), the main note area, and the preview area(right side➡️).
|
||||
|
||||

|
||||
|
||||
Open workspace by clicking the 'Open Workspace' line above the 'My Library' line in Zotero main window.
|
||||
|
||||

|
||||
|
||||
Alternatively, open it with the '🏠home' button on the top-left of note editors.
|
||||
|
||||

|
||||
|
||||
### 1.2 Main note
|
||||
|
||||
This addon uses a Zotero note item as the main note. It will show up on the main area of the workspace window.
|
||||
|
||||
All links will be added to the main note.
|
||||
|
||||
Set a note as the main note in the right-click popup menu.
|
||||
|
||||
### 1.3 Workspace Menu
|
||||
|
||||
**File menu**:
|
||||
|
||||
- Create new main note(Ctrl/Cmd+N)
|
||||
- Open main note(Ctrl/Cmd+O)
|
||||
- Import notes(Ctrl/Cmd+I)
|
||||
- Export notes(Ctrl/Cmd+E)
|
||||
- Close workspace window(Ctrl/Cmd+W)
|
||||
|
||||
**Edit menu**:
|
||||
|
||||
- Insert heading
|
||||
- Decrease selected heading level(Tab)
|
||||
- Increase selected heading level(Shift+Tab)
|
||||
|
||||
**View menu**:
|
||||
|
||||
- Outline Views
|
||||
|
||||
**Help menu**:
|
||||
|
||||
- Open user guide
|
||||
|
||||
## 2 Gather Ideas to Main Note
|
||||
|
||||
### 2.1 From Note
|
||||
|
||||
Select a note outside the workspace window(in Zotero items view or PDF viewer), you may realize a button with the addon's icon on the top of the note editor toolbar.
|
||||
|
||||
Click it, the current note link will be inserted into the main note's cursor position;
|
||||
|
||||
Select a heading, and the note's link will be inserted into the end of this heading.
|
||||
|
||||

|
||||
|
||||
> **💡 Try it now!**
|
||||
>
|
||||
> Open a PDF and open/create a note(in the right side bar of PDF viewer). Add a link below.
|
||||
|
||||
### 2.2 From Annotation (including highlight and picture)
|
||||
|
||||
You can find a button with the addon's icon on every annotation(in the left sidebar of PDF viewer).
|
||||
|
||||

|
||||
|
||||
Click it, and a new note containing this annotation will be created under the PDF item. Annotation tags are copied to the new note.
|
||||
You can then add its link to the main note in the note editor.
|
||||
|
||||
> **💡 Try it now!**
|
||||
>
|
||||
> Open a PDF and open/create an annotation(in the left sidebar of PDF viewer).
|
||||
|
||||
### 2.3 Import Existing Notes
|
||||
|
||||
You can import notes into the main note.
|
||||
|
||||
- In workspace window, File -> Import Notes;
|
||||
- Select notes and confirm.
|
||||
|
||||
Notes' links will be appended to the end of the main note.
|
||||
|
||||
## 3 Check Linked Notes in Workspace Window
|
||||
|
||||
### 3.1 View Linked Notes
|
||||
|
||||
Suppose you have added a lot of links to the main note. Now, it's time to view what you've got.
|
||||
|
||||
Go back to the workspace window.
|
||||
|
||||
Open a note link, the linked note will show up in the preview area(right side➡️).
|
||||
|
||||

|
||||
|
||||
> **💡 Try it now!**
|
||||
>
|
||||
> Open a note link.
|
||||
|
||||
### 3.2 View Linked Note's PDF
|
||||
|
||||
Click the '📄PDF' button on the top-left of the preview area.
|
||||
|
||||

|
||||
|
||||
> **💡 Try it now!**
|
||||
>
|
||||
> Open a linked note's PDF.
|
||||
|
||||
### 3.3 Insert Citation
|
||||
|
||||
Click the "Insert Citation" button in the middle of the preview area.
|
||||
|
||||

|
||||
|
||||
Currently, the quick copy format must be a citation format, otherwise, this function will not work. See Edit->Preferences->Export in Zotero main window.
|
||||
|
||||
### 3.4 Import Linked Note
|
||||
|
||||
Import the selected linked note's context to the main note by clicking the 'import note' button in the link popup.
|
||||
|
||||

|
||||
|
||||
Example:
|
||||
|
||||

|
||||
|
||||
> **💡 Try it now!**
|
||||
>
|
||||
> Import one linked note.
|
||||
|
||||
### 3.5 Update Note Link Text
|
||||
|
||||
Update the link text in main note by clicking the 'update link text' button in the link popup.
|
||||
|
||||
> **💡 Try it now!**
|
||||
>
|
||||
> Modify the first line of a linked note in the preview area. Then update its link text in the main note.
|
||||
|
||||
## 4 Outline Mode
|
||||
|
||||
Switch the outline mode with the '📊mode' button on the bottom of the outline area.
|
||||
|
||||

|
||||
|
||||
> **💡 Try it now!**
|
||||
>
|
||||
> Try different outline modes.
|
||||
|
||||
## 5 Export
|
||||
|
||||
Click the '⬆️export' button on the top-right of the main note area. Choose a format to export, i.e. MarkDown.
|
||||
|
||||

|
||||
|
||||
If you are using MS Word/OneNote, export to clipboard and paste there.
|
||||
|
||||
> **💡 Try it now!**
|
||||
>
|
||||
> Export this main note!
|
||||
|
||||
## 6 Start a New Job
|
||||
|
||||
After the export, you may want to start a new job with a new empty main note.
|
||||
|
||||
Create a note and right-click to set it as the main note, or just create a new main note.
|
||||
|
||||
Open different main note in workspace window->File->Open Main Note.
|
||||
|
||||
> **✨ Hint**
|
||||
>
|
||||
> Create a new collection and save all main notes there is the best way to manage them.
|
||||
>
|
||||
> The user guide should have done this for you.
|
||||
|
||||
Congratulations!
|
||||
|
||||
You can select a new main note and start working with **Zotero Better Notes** now. Have fun!
|
||||
192
UserGuideCN.md
|
|
@ -1,192 +0,0 @@
|
|||
# Zotero Better Notes 用户指引:工作流
|
||||
|
||||
欢迎使用 **Zotero Better Notes** !
|
||||
|
||||
本笔记帮助您在 3 分钟内快速学习如何使用此插件!
|
||||
|
||||
现在开始吧。
|
||||
|
||||
## 1 认识 Knowledge
|
||||
|
||||
Knowledge 是 Zotero 内置 note 功能的扩展。
|
||||
|
||||
Zotero 的 note 很像一个标记/富文本编辑器。您可以使用上方工具编辑格式 ⬆️。
|
||||
|
||||
### 1.1 工作区窗口
|
||||
|
||||
知识工作区窗口包含一个大纲区域(左侧 ⬅️),主笔记区域和预览区域(右侧 ➡️)。
|
||||
|
||||

|
||||
|
||||
在 Zotero 主窗口中单击“我的文库”上方的“Open Workspace”来打开工作区。
|
||||
|
||||

|
||||
|
||||
或者,用笔记编辑器左上角的“🏠 主页”按钮。
|
||||
|
||||

|
||||
|
||||
### 1.2 主笔记
|
||||
|
||||
这个插件使用某一个 Zotero note 作为主笔记。它将显示在工作区窗口的主笔记区域。
|
||||
|
||||
所有链接都将添加到主笔记中。
|
||||
|
||||
从右键菜单中设置当前一个笔记为主笔记:
|
||||
|
||||
### 1.3 工作区菜单栏
|
||||
|
||||
**文件菜单**:
|
||||
|
||||
- 创建新的主笔记(Ctrl/Cmd+N)
|
||||
- 打开主笔记(Ctrl/Cmd+O)
|
||||
- 导入笔记(Ctrl/Cmd+I)
|
||||
- 导出笔记(Ctrl/Cmd+E)
|
||||
- 关闭(Ctrl/Cmd+W)
|
||||
|
||||
**编辑菜单**:
|
||||
|
||||
- 插入标题
|
||||
- 降低标题层级
|
||||
- 提升标题层级
|
||||
|
||||
**查看菜单**:
|
||||
|
||||
- 大纲视图
|
||||
|
||||
**帮助菜单**:
|
||||
|
||||
- 打开用户指引
|
||||
|
||||
## 2 在主笔记中收集想法
|
||||
|
||||
### 2.1 从 Note
|
||||
|
||||
在工作区窗口外选择一个 note(在 Zotero 条目视图或 PDF 阅读器中),您会在笔记编辑器工具栏顶部看到一个带有本插件图标的按钮。
|
||||
|
||||
点击它,当前笔记的链接将插入主笔记的光标位置;
|
||||
|
||||
选择一个标题层级,笔记的链接将插入该标题的末尾。
|
||||
|
||||

|
||||
|
||||
> **💡 尝试一下!**
|
||||
>
|
||||
> 打开 PDF 并打开/创建笔记(在 PDF 阅读器的右侧栏中)。用上面的方法在这条主笔记添加一个链接。
|
||||
|
||||
### 2.2 从 Annotation(高亮批注和图片)
|
||||
|
||||
你可以在每个批注上找到一个带有插件图标的按钮(在 PDF 阅读器的左侧栏中)。
|
||||
|
||||

|
||||
|
||||
单击它,PDF条目下将创建一个带有此批注的新笔记。批注的标签将会复制到新的笔记。
|
||||
|
||||
然后可以在打开的笔记编辑器中将该笔记链接添加到主笔记。
|
||||
|
||||
> **💡 尝试一下!**
|
||||
>
|
||||
> 打开 PDF 并打开/创建批注高亮(在 PDF 阅读器的左侧栏中)。用上面的方法在这条主笔记添加一个链接。
|
||||
|
||||
### 2.3 从功能区导入笔记
|
||||
|
||||
支持从现有的多个笔记批量导入到主笔记中。
|
||||
|
||||
- 在工作区窗口菜单栏文件->导入笔记;
|
||||
- 选择多个笔记并确认。
|
||||
|
||||
## 3 查看工作区窗口中的链接笔记
|
||||
|
||||
### 3.1 查看链接笔记
|
||||
|
||||
假设你已经在主笔记添加了很多的链接。现在,是时候看看你的结果了。
|
||||
|
||||
返回工作区窗口。
|
||||
|
||||
打开笔记链接,链接的笔记将显示在预览区域(右侧 ➡️)。
|
||||
|
||||

|
||||
|
||||
> **💡 尝试一下!**
|
||||
>
|
||||
> 在工作区窗口打开一个笔记链接。
|
||||
|
||||
### 3.2 查看链接笔记的 PDF
|
||||
|
||||
在上一步打开的预览笔记中,点击预览区左上角的“📄PDF”按钮。
|
||||
|
||||

|
||||
|
||||
> **💡 尝试一下!**
|
||||
>
|
||||
> 打开一个链接笔记的 PDF。
|
||||
|
||||
### 3.3 插入当前子笔记父条目文献的引用格式
|
||||
|
||||
在上一步打开的预览笔记中,点击预览区中间的“插入引用”按钮。
|
||||
|
||||

|
||||
|
||||
目前,快速复制格式需要设置为citation format,否则该功能无法正常工作。可在Zotero主窗口的编辑->首选项->导出中设置。
|
||||
|
||||
### 3.4 插入链接的子笔记
|
||||
|
||||
点击链接弹窗中的'import note'来插入选择的笔记内容。
|
||||
|
||||

|
||||
|
||||
插入之后:
|
||||
|
||||

|
||||
|
||||
> **💡 尝试一下!**
|
||||
>
|
||||
> 插入一个链接的笔记
|
||||
|
||||
### 3.5 更新链接文本
|
||||
|
||||
点击链接弹窗中的'update link text'来更新选择的笔记链接文本。
|
||||
|
||||
> **💡 尝试一下!**
|
||||
>
|
||||
> 在预览区域中编辑某个链接笔记的第一行文本,然后在主笔记区域更新它的链接文本。
|
||||
|
||||
## 4 大纲视图
|
||||
|
||||
点击大纲区域左下角的 ‘📊 大纲模式‘ 按钮 来切换大纲视图模式。
|
||||
|
||||

|
||||
|
||||
> **💡 尝试一下!**
|
||||
>
|
||||
> 尝试不同的大纲模式(思维导图)
|
||||
|
||||
## 5 导出
|
||||
|
||||
点击主笔记区域右上角的“⬆️ 导出”按钮,或者工作区窗口菜单的文件->导出主笔记。选择要导出的格式,比如 MarkDown。
|
||||
|
||||

|
||||
|
||||
如果您使用的是 MS Word/OneNote,请导出到剪贴板并粘贴到那里。
|
||||
|
||||
> **💡 尝试一下!**
|
||||
>
|
||||
> 导出这个主笔记!
|
||||
|
||||
## 6 开始新的任务
|
||||
|
||||
导出后,您可能希望使用新的空主笔记开始新任务。
|
||||
|
||||
创建一个笔记,然后在右键菜单中将其设置为主笔记;或者直接创建一个新的主笔记。
|
||||
|
||||
使用工作区窗口菜单的文件->打开主笔记以切换不同的主笔记。
|
||||
|
||||
> **✨ 提示**
|
||||
>
|
||||
> 创建一个新的文件夹并在其中专门保存所有的主笔记——这是管理主笔记的最佳方法。
|
||||
>
|
||||
> 用户指引应该已经为您做到了这一点。
|
||||
|
||||
恭喜!
|
||||
|
||||
你现在可以选择或新建一个主笔记,然后开始使用 **Zotero Better Notes**了。用的开心!
|
||||
|
|
@ -0,0 +1,153 @@
|
|||
/**
|
||||
* Most of this code is from Zotero team's official Make It Red example[1]
|
||||
* or the Zotero 7 documentation[2].
|
||||
* [1] https://github.com/zotero/make-it-red
|
||||
* [2] https://www.zotero.org/support/dev/zotero_7_for_developers
|
||||
*/
|
||||
|
||||
if (typeof Zotero == "undefined") {
|
||||
var Zotero;
|
||||
}
|
||||
|
||||
var chromeHandle;
|
||||
|
||||
// In Zotero 6, bootstrap methods are called before Zotero is initialized, and using include.js
|
||||
// to get the Zotero XPCOM service would risk breaking Zotero startup. Instead, wait for the main
|
||||
// Zotero window to open and get the Zotero object from there.
|
||||
//
|
||||
// In Zotero 7, bootstrap methods are not called until Zotero is initialized, and the 'Zotero' is
|
||||
// automatically made available.
|
||||
async function waitForZotero() {
|
||||
if (typeof Zotero != "undefined") {
|
||||
await Zotero.initializationPromise;
|
||||
}
|
||||
|
||||
var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
var windows = Services.wm.getEnumerator("navigator:browser");
|
||||
var found = false;
|
||||
while (windows.hasMoreElements()) {
|
||||
let win = windows.getNext();
|
||||
if (win.Zotero) {
|
||||
Zotero = win.Zotero;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
await new Promise((resolve) => {
|
||||
var listener = {
|
||||
onOpenWindow: function (aWindow) {
|
||||
// Wait for the window to finish loading
|
||||
let domWindow = aWindow
|
||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowInternal || Ci.nsIDOMWindow);
|
||||
domWindow.addEventListener(
|
||||
"load",
|
||||
function () {
|
||||
domWindow.removeEventListener("load", arguments.callee, false);
|
||||
if (domWindow.Zotero) {
|
||||
Services.wm.removeListener(listener);
|
||||
Zotero = domWindow.Zotero;
|
||||
resolve();
|
||||
}
|
||||
},
|
||||
false
|
||||
);
|
||||
},
|
||||
};
|
||||
Services.wm.addListener(listener);
|
||||
});
|
||||
}
|
||||
await Zotero.initializationPromise;
|
||||
}
|
||||
|
||||
function install(data, reason) {}
|
||||
|
||||
async function startup({ id, version, resourceURI, rootURI }, reason) {
|
||||
await waitForZotero();
|
||||
|
||||
// String 'rootURI' introduced in Zotero 7
|
||||
if (!rootURI) {
|
||||
rootURI = resourceURI.spec;
|
||||
}
|
||||
|
||||
if (Zotero.platformMajorVersion >= 102) {
|
||||
var aomStartup = Components.classes[
|
||||
"@mozilla.org/addons/addon-manager-startup;1"
|
||||
].getService(Components.interfaces.amIAddonManagerStartup);
|
||||
var manifestURI = Services.io.newURI(rootURI + "manifest.json");
|
||||
chromeHandle = aomStartup.registerChrome(manifestURI, [
|
||||
["content", "__addonRef__", rootURI + "chrome/content/"],
|
||||
["locale", "__addonRef__", "en-US", rootURI + "chrome/locale/en-US/"],
|
||||
["locale", "__addonRef__", "zh-CN", rootURI + "chrome/locale/zh-CN/"],
|
||||
]);
|
||||
} else {
|
||||
setDefaultPrefs(rootURI);
|
||||
}
|
||||
|
||||
/**
|
||||
* Global variables for plugin code.
|
||||
* The `_globalThis` is the global root variable of the plugin sandbox environment
|
||||
* and all child variables assigned to it is globally accessible.
|
||||
* See `src/index.ts` for details.
|
||||
*/
|
||||
const ctx = {
|
||||
rootURI,
|
||||
document: Zotero.getMainWindow().document,
|
||||
};
|
||||
ctx._globalThis = ctx;
|
||||
|
||||
Services.scriptloader.loadSubScript(
|
||||
`${rootURI}/chrome/content/scripts/index.js`,
|
||||
ctx
|
||||
);
|
||||
}
|
||||
|
||||
function shutdown({ id, version, resourceURI, rootURI }, reason) {
|
||||
if (reason === APP_SHUTDOWN) {
|
||||
return;
|
||||
}
|
||||
if (typeof Zotero === "undefined") {
|
||||
Zotero = Components.classes["@zotero.org/Zotero;1"].getService(
|
||||
Components.interfaces.nsISupports
|
||||
).wrappedJSObject;
|
||||
}
|
||||
Zotero.__addonInstance__.hooks.onShutdown();
|
||||
|
||||
Cc["@mozilla.org/intl/stringbundle;1"]
|
||||
.getService(Components.interfaces.nsIStringBundleService)
|
||||
.flushBundles();
|
||||
|
||||
Cu.unload(`${rootURI}/chrome/content/scripts/index.js`);
|
||||
|
||||
if (chromeHandle) {
|
||||
chromeHandle.destruct();
|
||||
chromeHandle = null;
|
||||
}
|
||||
}
|
||||
|
||||
function uninstall(data, reason) {}
|
||||
|
||||
// Loads default preferences from defaults/preferences/prefs.js in Zotero 6
|
||||
function setDefaultPrefs(rootURI) {
|
||||
var branch = Services.prefs.getDefaultBranch("");
|
||||
var obj = {
|
||||
pref(pref, value) {
|
||||
switch (typeof value) {
|
||||
case "boolean":
|
||||
branch.setBoolPref(pref, value);
|
||||
break;
|
||||
case "string":
|
||||
branch.setStringPref(pref, value);
|
||||
break;
|
||||
case "number":
|
||||
branch.setIntPref(pref, value);
|
||||
break;
|
||||
default:
|
||||
Zotero.logError(`Invalid type '${typeof value}' for pref '${pref}'`);
|
||||
}
|
||||
},
|
||||
};
|
||||
Zotero.getMainWindow().console.log(rootURI + "prefs.js");
|
||||
Services.scriptloader.loadSubScript(rootURI + "prefs.js", obj);
|
||||
}
|
||||
|
|
@ -1,6 +1,3 @@
|
|||
content __addonRef__ chrome/content/
|
||||
skin __addonRef__ default chrome/skin/default/__addonRef__/
|
||||
locale __addonRef__ en-US chrome/locale/en-US/
|
||||
locale __addonRef__ zh-CN chrome/locale/zh-CN/
|
||||
|
||||
overlay chrome://zotero/content/zoteroPane.xul chrome://__addonRef__/content/overlay.xul
|
||||
|
|
|
|||
|
|
@ -1,40 +0,0 @@
|
|||
<?xml version="1.0"?>
|
||||
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://zotero/skin/about.css" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://zotero/skin/zotero.css" type="text/css"?>
|
||||
|
||||
<!DOCTYPE window [
|
||||
<!ENTITY % AboutDTD SYSTEM "chrome://zotero/locale/about.dtd">
|
||||
%AboutDTD;
|
||||
<!ENTITY % knowledgeDTD SYSTEM "chrome://__addonRef__/locale/overlay.dtd">
|
||||
%knowledgeDTD;
|
||||
]>
|
||||
|
||||
<dialog id="zotero-better-notes-about" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" orient="vertical" buttons="accept" buttonlabelaccept="&zotero.about.close;" ondialogaccept="return true;">
|
||||
<script src="chrome://zotero/content/include.js" />
|
||||
<vbox id="aboutcontent">
|
||||
<hbox>
|
||||
<div xmlns="http://www.w3.org/1999/xhtml">
|
||||
<img id="logo" src="chrome://__addonRef__/skin/favicon.png" />
|
||||
</div>
|
||||
<label id="name" value="__addonName__" style="font-size: 22px; font-weight: bold; color: #f2ac46; padding: 5px;" />
|
||||
</hbox>
|
||||
|
||||
<hbox>
|
||||
<label id="version" value="&zotero.__addonRef__.help.version.label;" />
|
||||
<label id="build" value="&zotero.__addonRef__.help.releasetime.label;" />
|
||||
</hbox>
|
||||
<hbox>
|
||||
<label id="feedback" value="&zotero.__addonRef__.help.feedback.caption.label;" />
|
||||
<hbox style="display: block">
|
||||
<label value="&zotero.__addonRef__.help.feedback.label;" class="zotero-text-link" onclick="Zotero.launchURL('__homepage__');" />
|
||||
</hbox>
|
||||
</hbox>
|
||||
<hbox>
|
||||
<label id="doc" value="&zotero.__addonRef__.help.doc.caption.label;" />
|
||||
<hbox style="display: block">
|
||||
<label value="&zotero.__addonRef__.help.doc.label;" class="zotero-text-link" onclick="Zotero.launchURL('https://zotero.yuque.com/books/share/f3fe159f-956c-4f10-ade3-c87559cacb60/biigg4');" />
|
||||
</hbox>
|
||||
</hbox>
|
||||
</vbox>
|
||||
</dialog>
|
||||
|
|
@ -120,7 +120,12 @@
|
|||
),
|
||||
$(
|
||||
go.TextBlock,
|
||||
{ textAlign: "center", maxSize: new go.Size(100, NaN) },
|
||||
{
|
||||
textAlign: "center",
|
||||
maxSize: new go.Size(100, NaN),
|
||||
editable: true,
|
||||
textEdited: editNode,
|
||||
},
|
||||
new go.Binding("text", "text").makeTwoWay(),
|
||||
new go.Binding("stroke", "stroke")
|
||||
)
|
||||
|
|
@ -191,12 +196,13 @@
|
|||
node.parent.model.id === -1 ? 999 : node.parent.model.id;
|
||||
nodeDataArray.push({
|
||||
key: node.model.id,
|
||||
text: `${node.model.rank === 7 ? "🔗" : ""}${wrapText(
|
||||
text: `${node.model.level === 7 ? "🔗" : ""}${wrapText(
|
||||
node.model.name
|
||||
)}`,
|
||||
stroke: "-moz-DialogText",
|
||||
lineIndex: node.model.lineIndex,
|
||||
noteLink: node.model.rank === 7 ? node.model.link : "",
|
||||
level: node.model.level,
|
||||
noteLink: node.model.level === 7 ? node.model.link : "",
|
||||
});
|
||||
linkDataArray.push({
|
||||
from: parent,
|
||||
|
|
@ -214,7 +220,7 @@
|
|||
var olddata = oldnode.data;
|
||||
if (olddata.noteLink) {
|
||||
window.parent.postMessage(
|
||||
{ type: "jumpNote", link: olddata.noteLink, id: olddata.key },
|
||||
{ type: "openNote", link: olddata.noteLink, id: olddata.key },
|
||||
"*"
|
||||
);
|
||||
} else {
|
||||
|
|
@ -223,17 +229,37 @@
|
|||
type: "jumpNode",
|
||||
lineIndex: olddata.lineIndex,
|
||||
id: olddata.key,
|
||||
workspaceType: window.workspaceType || "tab",
|
||||
},
|
||||
"*"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function editNode(textBlock, previousText, currentText) {
|
||||
const adorn = textBlock.part;
|
||||
const data = adorn.data;
|
||||
if (data.level === 7) {
|
||||
alert("Link cannot be edited in mind map");
|
||||
return false;
|
||||
}
|
||||
window.parent.postMessage(
|
||||
{
|
||||
type: "editNode",
|
||||
lineIndex: data.lineIndex,
|
||||
text: data.text,
|
||||
workspaceType: window.workspaceType || "tab",
|
||||
},
|
||||
"*"
|
||||
);
|
||||
}
|
||||
|
||||
function handler(e) {
|
||||
console.log(e);
|
||||
switch (e.data.type) {
|
||||
case "setMindMapData":
|
||||
setData(e.data.nodes);
|
||||
window.workspaceType = e.data.workspaceType;
|
||||
break;
|
||||
case "saveImage":
|
||||
const imgString = Diagram.makeImageData({
|
||||
|
|
|
|||
|
|
@ -1,97 +0,0 @@
|
|||
<?xml version="1.0"?>
|
||||
<?xml-stylesheet href="chrome://global/skin/"?>
|
||||
<?xml-stylesheet href="chrome://zotero/skin/zotero.css" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://zotero/skin/overlay.css" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://zotero-platform/content/overlay.css"?>
|
||||
<!DOCTYPE window [
|
||||
<!ENTITY % ZoteroDTD SYSTEM "chrome://zotero/locale/zotero.dtd">
|
||||
%ZoteroDTD;
|
||||
<!ENTITY % knowledgeDTD SYSTEM "chrome://__addonRef__/locale/overlay.dtd">
|
||||
%knowledgeDTD;
|
||||
]>
|
||||
|
||||
<dialog id="betternotes-export-dialog" windowtype="betternotes-export"
|
||||
title="&zotero.__addonRef__.export.title;" orient="vertical" width="300" height="300"
|
||||
buttons="cancel,accept" ondialogaccept="Zotero.BetterNotes.NoteExportWindow.doAccept();"
|
||||
onload="Zotero.BetterNotes.NoteExportWindow.doLoad(window);"
|
||||
onunload="Zotero.BetterNotes.NoteExportWindow.doUnload();"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
xmlns:html="http://www.w3.org/1999/xhtml" style="padding:2em"
|
||||
persist="screenX screenY width height">
|
||||
<script src="chrome://zotero/content/include.js" />
|
||||
<vbox flex="1">
|
||||
<groupbox flex="1">
|
||||
<caption label="&zotero.__addonRef__.export.option.label;"></caption>
|
||||
<rows flex="1">
|
||||
<row>
|
||||
<checkbox id="embedLink"
|
||||
tooltiptext="&zotero.__addonRef__.export.cannotworkwith.label;&zotero.__addonRef__.export.singlefile.enable.label;"
|
||||
checked="true"
|
||||
oncommand="Zotero.BetterNotes.NoteExportWindow.doUpdate(event)" />
|
||||
<label value="&zotero.__addonRef__.export.link.enable.label;"
|
||||
tooltiptext="&zotero.__addonRef__.export.cannotworkwith.label;&zotero.__addonRef__.export.singlefile.enable.label;" />
|
||||
</row>
|
||||
<row>
|
||||
<checkbox id="exportNote" checked="false" />
|
||||
<label value="&zotero.__addonRef__.export.note.enable.label;" />
|
||||
</row>
|
||||
</rows>
|
||||
</groupbox>
|
||||
<groupbox flex="1">
|
||||
<caption label="&zotero.__addonRef__.export.markdown.label;"></caption>
|
||||
<rows flex="1">
|
||||
<row>
|
||||
<checkbox id="exportMD" checked="true"
|
||||
oncommand="Zotero.BetterNotes.NoteExportWindow.doUpdate(event)" />
|
||||
<label value="&zotero.__addonRef__.export.file.enable.label;" />
|
||||
</row>
|
||||
<row>
|
||||
<checkbox id="exportSubMD"
|
||||
tooltiptext="&zotero.__addonRef__.export.cannotworkwith.label;&zotero.__addonRef__.export.link.enable.label;"
|
||||
checked="false"
|
||||
oncommand="Zotero.BetterNotes.NoteExportWindow.doUpdate(event)" />
|
||||
<label value="&zotero.__addonRef__.export.singlefile.enable.label;"
|
||||
tooltiptext="&zotero.__addonRef__.export.cannotworkwith.label;&zotero.__addonRef__.export.link.enable.label;" />
|
||||
</row>
|
||||
<row>
|
||||
<checkbox id="exportYAMLHeader"
|
||||
tooltiptext="&zotero.__addonRef__.export.cannotworkwith.label;&zotero.__addonRef__.export.yaml.enable.label;"
|
||||
checked="true"
|
||||
oncommand="Zotero.BetterNotes.NoteExportWindow.doUpdate(event)" />
|
||||
<label value="&zotero.__addonRef__.export.yaml.enable.label;"
|
||||
tooltiptext="&zotero.__addonRef__.export.cannotworkwith.label;&zotero.__addonRef__.export.link.enable.label;" />
|
||||
</row>
|
||||
<row>
|
||||
<checkbox id="exportAutoSync"
|
||||
tooltiptext="&zotero.__addonRef__.export.workwith.label;&zotero.__addonRef__.export.singlefile.enable.label;"
|
||||
checked="false"
|
||||
oncommand="Zotero.BetterNotes.NoteExportWindow.doUpdate(event)" />
|
||||
<label value="&zotero.__addonRef__.export.enableautosync.enable.label;"
|
||||
tooltiptext="Only work with &zotero.__addonRef__.export.singlefile.enable.label;" />
|
||||
</row>
|
||||
</rows>
|
||||
</groupbox>
|
||||
<groupbox flex="1">
|
||||
<caption label="&zotero.__addonRef__.export.document.label;"></caption>
|
||||
<rows flex="1">
|
||||
<row>
|
||||
<checkbox id="exportDocx" checked="false" />
|
||||
<label value="&zotero.__addonRef__.export.docx.enable.label;" />
|
||||
</row>
|
||||
<row>
|
||||
<checkbox id="exportPDF" checked="false" />
|
||||
<label value="&zotero.__addonRef__.export.pdf.enable.label;" />
|
||||
</row>
|
||||
</rows>
|
||||
</groupbox>
|
||||
<groupbox flex="1">
|
||||
<caption label="&zotero.__addonRef__.export.mindmap.label;"></caption>
|
||||
<rows flex="1">
|
||||
<row>
|
||||
<checkbox id="exportFreeMind" checked="false" />
|
||||
<label value="&zotero.__addonRef__.export.freemind.enable.label;" />
|
||||
</row>
|
||||
</rows>
|
||||
</groupbox>
|
||||
</vbox>
|
||||
</dialog>
|
||||
|
Before Width: | Height: | Size: 889 B After Width: | Height: | Size: 889 B |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 53 KiB |
|
|
@ -2,6 +2,7 @@
|
|||
<html lang="en">
|
||||
<head>
|
||||
<title>Image Preview</title>
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
<body>
|
||||
<style>
|
||||
|
|
@ -11,6 +12,7 @@
|
|||
width: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-family: initial;
|
||||
}
|
||||
#image {
|
||||
position: relative;
|
||||
|
|
@ -35,7 +37,7 @@
|
|||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
<link rel="stylesheet" href="chrome://__addonRef__/skin/workspace.css" />
|
||||
<link rel="stylesheet" href="./tooltip.css" />
|
||||
<script>
|
||||
window.addEventListener("DOMContentLoaded", (e) => {
|
||||
document.querySelector(".container").style["line-height"] = `${
|
||||
|
|
|
|||
|
|
@ -50,9 +50,7 @@
|
|||
name: "TEXT",
|
||||
minSize: new go.Size(30, 15),
|
||||
editable: true,
|
||||
textEdited: function (textBlock, previousText, currentText) {
|
||||
console.log("editing heading", textBlock, currentText);
|
||||
},
|
||||
textEdited: editNode,
|
||||
},
|
||||
// remember not only the text string but the scale and the font in the node data
|
||||
new go.Binding("text", "text").makeTwoWay(),
|
||||
|
|
@ -264,13 +262,14 @@
|
|||
node.parent.model.id === -1 ? 999 : node.parent.model.id;
|
||||
data.nodeDataArray.push({
|
||||
key: node.model.id,
|
||||
text: `${node.model.rank === 7 ? "🔗" : ""}${wrapText(
|
||||
text: `${node.model.level === 7 ? "🔗" : ""}${wrapText(
|
||||
node.model.name
|
||||
)}`,
|
||||
stroke: "-moz-DialogText",
|
||||
parent: parent,
|
||||
lineIndex: node.model.lineIndex,
|
||||
noteLink: node.model.rank === 7 ? node.model.link : "",
|
||||
level: node.model.level,
|
||||
noteLink: node.model.level === 7 ? node.model.link : "",
|
||||
brush: go.Brush.randomColor(),
|
||||
});
|
||||
}
|
||||
|
|
@ -283,7 +282,7 @@
|
|||
var olddata = oldnode.data;
|
||||
if (olddata.noteLink) {
|
||||
window.parent.postMessage(
|
||||
{ type: "jumpNote", link: olddata.noteLink, id: olddata.key },
|
||||
{ type: "openNote", link: olddata.noteLink, id: olddata.key },
|
||||
"*"
|
||||
);
|
||||
} else {
|
||||
|
|
@ -292,17 +291,37 @@
|
|||
type: "jumpNode",
|
||||
lineIndex: olddata.lineIndex,
|
||||
id: olddata.key,
|
||||
workspaceType: window.workspaceType || "tab",
|
||||
},
|
||||
"*"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function editNode(textBlock, previousText, currentText) {
|
||||
const adorn = textBlock.part;
|
||||
const data = adorn.data;
|
||||
if (data.level === 7) {
|
||||
alert("Link cannot be edited in mind map");
|
||||
return false;
|
||||
}
|
||||
window.parent.postMessage(
|
||||
{
|
||||
type: "editNode",
|
||||
lineIndex: data.lineIndex,
|
||||
text: data.text,
|
||||
workspaceType: window.workspaceType || "tab",
|
||||
},
|
||||
"*"
|
||||
);
|
||||
}
|
||||
|
||||
function handler(e) {
|
||||
console.log(e);
|
||||
switch (e.data.type) {
|
||||
case "setMindMapData":
|
||||
setData(e.data.nodes);
|
||||
window.workspaceType = e.data.workspaceType;
|
||||
break;
|
||||
case "saveImage":
|
||||
const imgString = Diagram.makeImageData({
|
||||
|
|
|
|||
|
|
@ -1,107 +0,0 @@
|
|||
<?xml version="1.0"?>
|
||||
<?xml-stylesheet href="chrome://__addonRef__/skin/overlay.css" type="text/css"?>
|
||||
|
||||
<!DOCTYPE window [
|
||||
<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
|
||||
%globalDTD;
|
||||
<!ENTITY % standaloneDTD SYSTEM "chrome://zotero/locale/standalone.dtd">
|
||||
%standaloneDTD;
|
||||
<!ENTITY % editMenuOverlayDTD SYSTEM "chrome://zotero/locale/mozilla/editMenuOverlay.dtd">
|
||||
%editMenuOverlayDTD;
|
||||
<!ENTITY % zoteroDTD SYSTEM "chrome://zotero/locale/zotero.dtd">
|
||||
%zoteroDTD;
|
||||
<!ENTITY % knowledgeDTD SYSTEM "chrome://__addonRef__/locale/overlay.dtd">
|
||||
%knowledgeDTD;
|
||||
]>
|
||||
|
||||
<overlay id="__addonRef__" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
<script src="chrome://__addonRef__/content/scripts/index.js" />
|
||||
|
||||
<keyset id="mainKeyset">
|
||||
<key id="key_new_betternotes" class="key-type-betternotes" key="N" modifiers="accel" command="cmd_new_betternotes" />
|
||||
<key id="key_open_betternotes" class="key-type-betternotes" key="O" modifiers="accel" command="cmd_open_betternotes" />
|
||||
<key id="key_export_betternotes" class="key-type-betternotes" key="E" modifiers="accel" command="cmd_export_betternotes" />
|
||||
<key id="key_sync_betternotes" class="key-type-betternotes" key="S" modifiers="accel,shift" command="cmd_sync_betternotes" />
|
||||
</keyset>
|
||||
|
||||
<commandset id="mainCommandSet">
|
||||
<command id="cmd_new_betternotes" oncommand="Zotero.BetterNotes.ZoteroEvents.onEditorEvent({type: 'createWorkspace'});" />
|
||||
<command id="cmd_open_betternotes" oncommand="Zotero.BetterNotes.ZoteroEvents.onEditorEvent({type: 'selectMainNote'});" />
|
||||
<command id="cmd_open_betternotesWindow" oncommand="Zotero.BetterNotes.ZoteroEvents.onEditorEvent({type: 'openWorkspaceInWindow'});" />
|
||||
<command id="cmd_export_betternotes" oncommand="Zotero.BetterNotes.ZoteroEvents.onEditorEvent({type: 'export', content: {editorInstance: {_item: false}}});" />
|
||||
<command id="cmd_sync_betternotes" oncommand="Zotero.BetterNotes.ZoteroEvents.onEditorEvent({type: 'sync'});" />
|
||||
<command id="cmd_sync_manager_betternotes" oncommand="Zotero.BetterNotes.SyncListWindow.openSyncList();" />
|
||||
<command id="cmd_editTemplate_betternotes" oncommand="Zotero.BetterNotes.ZoteroEvents.onEditorEvent({type: 'editTemplate'});" />
|
||||
<!-- <command id="cmd_importlink_betternotes" oncommand="Zotero.BetterNotes.ZoteroEvents.onEditorEvent({type: 'importLink'});" />
|
||||
<command id="cmd_updatelink_betternotes" oncommand="Zotero.BetterNotes.ZoteroEvents.onEditorEvent({type: 'updateLink'});" /> -->
|
||||
<command id="cmd_autoannotation_betternotes" oncommand="Zotero.BetterNotes.ZoteroEvents.onEditorEvent({type: 'updateAutoAnnotation'});" />
|
||||
<command id="cmd_convertmd_betternotes" oncommand="Zotero.BetterNotes.ZoteroEvents.onEditorEvent({type: 'convertMD'});" />
|
||||
<command id="cmd_convertasciidoc_betternotes" oncommand="Zotero.BetterNotes.ZoteroEvents.onEditorEvent({type: 'convertAsciiDoc'});" />
|
||||
<command id="cmd_treeview_betternotes" oncommand="Zotero.BetterNotes.WorkspaceOutline.switchView(1);" />
|
||||
<command id="cmd_mindmap_betternotes" oncommand="Zotero.BetterNotes.WorkspaceOutline.switchView(2);" />
|
||||
<command id="cmd_bubblemap_betternotes" oncommand="Zotero.BetterNotes.WorkspaceOutline.switchView(3);" />
|
||||
<command id="cmd_guide_betternotes" oncommand="Zotero.BetterNotes.ZoteroEvents.onEditorEvent({type: 'openUserGuide'});" />
|
||||
<command id="cmd_about_betternotes" oncommand="Zotero.BetterNotes.ZoteroEvents.onEditorEvent({type: 'openAbout'});" />
|
||||
</commandset>
|
||||
|
||||
<popup id="zotero-itemmenu">
|
||||
<menuseparator />
|
||||
<menuitem id="zotero-itemmenu-__addonRef__-setMainNote" class="menuitem-iconic popup-type-single-note" label="&zotero.__addonRef__.itemmenu.setMainNote.label;" oncommand="Zotero.BetterNotes.ZoteroEvents.onEditorEvent({type: 'setMainNote', content: {params: {itemID: ZoteroPane.getSelectedItems()[0].id}}})" style="list-style-image: url('chrome://__addonRef__/skin/favicon.png');" />
|
||||
<menuitem id="zotero-itemmenu-__addonRef__-exportNote" class="menuitem-iconic popup-type-single" label="&zotero.__addonRef__.itemmenu.exportNote.label;" oncommand="Zotero.BetterNotes.ZoteroEvents.onEditorEvent.bind(Zotero.BetterNotes.ZoteroEvents)({type: 'exportNotes', content: {}})" style="list-style-image: url('chrome://__addonRef__/skin/favicon.png');" />
|
||||
<menuitem id="zotero-itemmenu-__addonRef__-exportNotes" class="menuitem-iconic popup-type-multiple" label="&zotero.__addonRef__.itemmenu.exportNotes.label;" oncommand="Zotero.BetterNotes.ZoteroEvents.onEditorEvent.bind(Zotero.BetterNotes.ZoteroEvents)({type: 'exportNotes', content: {}})" style="list-style-image: url('chrome://__addonRef__/skin/favicon.png');" />
|
||||
</popup>
|
||||
|
||||
<menupopup id="menu_FilePopup">
|
||||
<menuseparator class="menu-type-betternotes menu-betternotes" />
|
||||
<menuitem id="menu_new_betternotes" class="menu-type-betternotes menu-betternotes" label="&zotero.__addonRef__.workspace.menu.new;" key="key_new_betternotes" accesskey="N" command="cmd_new_betternotes" />
|
||||
<menuitem id="menu_open_betternotes" class="menu-type-betternotes menu-betternotes" label="&zotero.__addonRef__.workspace.menu.open;" key="key_open_betternotes" accesskey="O" command="cmd_open_betternotes" />
|
||||
<menuitem id="menu_export_betternotes" class="menu-type-betternotes menu-betternotes" label="&zotero.__addonRef__.workspace.menu.export;" key="key_export_betternotes" accesskey="E" command="cmd_export_betternotes" />
|
||||
<menuitem id="menu_sync_betternotes" class="menu-type-betternotes menu-betternotes" label="&zotero.__addonRef__.workspace.menu.sync;" key="key_sync_betternotes" accesskey="S" command="cmd_sync_betternotes" />
|
||||
<menuitem id="menu_sync_manager_betternotes" class="menu-betternotes" label="&zotero.__addonRef__.workspace.menu.syncmanager;" command="cmd_sync_manager_betternotes" />
|
||||
<menuseparator class="menu-betternotes" />
|
||||
</menupopup>
|
||||
|
||||
<menupopup id="menu_EditPopup">
|
||||
<menu id="menu_insertTextTemplate_betternotes" class="menu-betternotes" label="&zotero.__addonRef__.workspace.menu.insertTextTemplate;">
|
||||
<menupopup id="menu_insertTextTemplatePopup" onpopupshowing="Zotero.BetterNotes.ZoteroViews.updateTemplateMenu(arguments[0], 'Text');" />
|
||||
</menu>
|
||||
<menu id="menu_insertItemTemplate_betternotes" class="menu-betternotes" label="&zotero.__addonRef__.workspace.menu.insertItemTemplate;">
|
||||
<menupopup id="menu_insertItemTemplatePopup" onpopupshowing="Zotero.BetterNotes.ZoteroViews.updateTemplateMenu(arguments[0], 'Item');" />
|
||||
</menu>
|
||||
<menuitem id="menu_editTemplate_betternotes" class="menu-betternotes" label="&zotero.__addonRef__.workspace.menu.editTemplate;" command="cmd_editTemplate_betternotes" />
|
||||
<menu id="menu_citeSetting_betternotes" class="menu-type-betternotes menu-betternotes" label="&zotero.__addonRef__.workspace.menu.citeSetting;">
|
||||
<menupopup id="menu_citeSettingPopup" onpopupshowing="Zotero.BetterNotes.ZoteroViews.updateCitationStyleMenu();" />
|
||||
</menu>
|
||||
<!-- <menuitem id="menu_importlink_betternotes" class="menu-type-betternotes" label="&zotero.__addonRef__.workspace.menu.importLink;" command="cmd_importlink_betternotes" />
|
||||
<menuitem id="menu_updatelink_betternotes" class="menu-type-betternotes" label="&zotero.__addonRef__.workspace.menu.updateLink;" command="cmd_updatelink_betternotes" /> -->
|
||||
<menuseparator class="menu-betternotes" />
|
||||
<menuitem id="menu_autoannotation_betternotes" class="menu-betternotes" type="checkbox" label="&zotero.__addonRef__.workspace.menu.autoannotation;" command="cmd_autoannotation_betternotes" />
|
||||
<menuseparator class="menu-betternotes" />
|
||||
<menuitem id="menu_convertmd_betternotes" class="menu-betternotes" label="&zotero.__addonRef__.workspace.menu.convertmd;" command="cmd_convertmd_betternotes" />
|
||||
<menuitem id="menu_convertasciidoc_betternotes" class="menu-betternotes" label="&zotero.__addonRef__.workspace.menu.convertasciidoc;" command="cmd_convertasciidoc_betternotes" />
|
||||
<menuseparator class="menu-betternotes" />
|
||||
</menupopup>
|
||||
|
||||
<menupopup id="menu_viewPopup">
|
||||
<menuitem id="menu_openWindow_betternotes" class="menu-type-betternotes menu-betternotes" label="&zotero.__addonRef__.workspace.menu.openWindow;" command="cmd_open_betternotesWindow" />
|
||||
<menuseparator class="menu-type-betternotes menu-betternotes" />
|
||||
<menuitem id="menu_treeview" class="menu-type-betternotes menu-betternotes" type="checkbox" label="&zotero.__addonRef__.workspace.menu.treeview;" command="cmd_treeview_betternotes" />
|
||||
<menuitem id="menu_mindmap" class="menu-type-betternotes menu-betternotes" type="checkbox" label="&zotero.__addonRef__.workspace.menu.mindmap;" command="cmd_mindmap_betternotes" />
|
||||
<menuitem id="menu_bubblemap" class="menu-type-betternotes menu-betternotes" type="checkbox" label="&zotero.__addonRef__.workspace.menu.bubblemap;" command="cmd_bubblemap_betternotes" />
|
||||
<menuseparator class="menu-type-betternotes menu-betternotes" />
|
||||
<menuitem id="menu_wordcount_betternotes" class="menu-type-betternotes menu-betternotes" disabled="true" />
|
||||
</menupopup>
|
||||
|
||||
<menupopup id="menu_HelpPopup">
|
||||
<menuseparator class="menu-betternotes" />
|
||||
<menu id="menu_ocrsetting_betternotes" class="menu-betternotes" label="&zotero.__addonRef__.workspace.menu.OCRSetting;">
|
||||
<menupopup id="menu_ocrsettingpopup" onpopupshowing="Zotero.BetterNotes.ZoteroViews.updateOCRStyleMenu();">
|
||||
<menuitem id="menu_ocr_bing_betternotes" class="menu-betternotes" label="&zotero.__addonRef__.workspace.menu.OCRBing;" oncommand="Zotero.BetterNotes.ZoteroEvents.onEditorEvent.bind(Zotero.BetterNotes.ZoteroEvents)({type: 'setOCREngine', content: {params: {engine: 'bing'}}})" type="checkbox" />
|
||||
<menuitem id="menu_ocr_mathpix_betternotes" class="menu-betternotes" label="&zotero.__addonRef__.workspace.menu.OCRMathpix;" oncommand="Zotero.BetterNotes.ZoteroEvents.onEditorEvent.bind(Zotero.BetterNotes.ZoteroEvents)({type: 'setOCREngine', content: {params: {engine: 'mathpix'}}})" type="checkbox" />
|
||||
<menuitem id="menu_ocr_xunfei_betternotes" class="menu-betternotes" label="&zotero.__addonRef__.workspace.menu.OCRXunfei;" oncommand="Zotero.BetterNotes.ZoteroEvents.onEditorEvent.bind(Zotero.BetterNotes.ZoteroEvents)({type: 'setOCREngine', content: {params: {engine: 'xunfei'}}})" type="checkbox" />
|
||||
</menupopup>
|
||||
</menu>
|
||||
<menuitem id="menu_guide_betternotes menu-betternotes" label="&zotero.__addonRef__.workspace.menu.guide;" command="cmd_guide_betternotes" />
|
||||
<menuitem id="menu_about_betternotes menu-betternotes" label="&zotero.__addonRef__.workspace.menu.about;" command="cmd_about_betternotes" />
|
||||
</menupopup>
|
||||
</overlay>
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
href="chrome://__addonRef__/content/lib/css/github-markdown-light.min.css"
|
||||
/>
|
||||
<style>
|
||||
@page {
|
||||
margin: 25mm 20mm 25mm 20mm;
|
||||
size: A4;
|
||||
}
|
||||
@media print {
|
||||
body {
|
||||
height: auto;
|
||||
margin: 0;
|
||||
-webkit-print-color-adjust: exact;
|
||||
color-adjust: exact;
|
||||
}
|
||||
|
||||
@page: footer {
|
||||
display: none;
|
||||
}
|
||||
@page: header {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.markdown-body {
|
||||
box-sizing: border-box;
|
||||
min-width: 200px;
|
||||
max-width: 980px;
|
||||
margin: 0 auto;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
td,
|
||||
th {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.markdown-body {
|
||||
padding: 15px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<script></script>
|
||||
</head>
|
||||
<body>
|
||||
<article class="markdown-body">
|
||||
<h1>Print to PDF</h1>
|
||||
<p>Waiting for data...</p>
|
||||
</article>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
<vbox
|
||||
id="zotero-prefpane-__addonRef__"
|
||||
onload="Zotero.__addonInstance__.hooks.onPrefsEvent('load', {window})"
|
||||
>
|
||||
<groupbox>
|
||||
<label><html:h2>&zotero.__addonRef__.pref.workspace.title;</html:h2></label>
|
||||
<hbox>
|
||||
<html:label for="__addonRef__-workspace-expandLevel"
|
||||
>&zotero.__addonRef__.pref.workspace.expandLevel.label;</html:label
|
||||
>
|
||||
<html:input
|
||||
type="number"
|
||||
min="1"
|
||||
max="6"
|
||||
step="1"
|
||||
id="__addonRef__-workspace-expandLevel"
|
||||
preference="__prefsPrefix__.workspace.outline.expandLevel"
|
||||
></html:input>
|
||||
</hbox>
|
||||
</groupbox>
|
||||
<groupbox>
|
||||
<label><html:h2>&zotero.__addonRef__.pref.sync.title;</html:h2></label>
|
||||
<hbox>
|
||||
<html:label for="__addonRef__-sync-period"
|
||||
>&zotero.__addonRef__.pref.sync.period.label;</html:label
|
||||
>
|
||||
<html:input
|
||||
type="number"
|
||||
min="-1"
|
||||
max="3600"
|
||||
step="1"
|
||||
placeholder="-1 for disable"
|
||||
id="__addonRef__-sync-period"
|
||||
preference="__prefsPrefix__.syncPeriodSeconds"
|
||||
></html:input>
|
||||
</hbox>
|
||||
<hbox>
|
||||
<button
|
||||
onclick="Zotero.__addonInstance__.api.window.showSyncManager()"
|
||||
label="&zotero.__addonRef__.pref.sync.manager.label;"
|
||||
></button>
|
||||
</hbox>
|
||||
</groupbox>
|
||||
<groupbox>
|
||||
<label><html:h2>&zotero.__addonRef__.pref.template.title;</html:h2></label>
|
||||
<hbox>
|
||||
<button
|
||||
onclick="Zotero.__addonInstance__.api.window.showTemplateEditor()"
|
||||
label="&zotero.__addonRef__.pref.template.editor.label;"
|
||||
></button>
|
||||
</hbox>
|
||||
</groupbox>
|
||||
<groupbox>
|
||||
<label
|
||||
><html:h2>&zotero.__addonRef__.pref.annotation.title;</html:h2></label
|
||||
>
|
||||
<checkbox
|
||||
id="__addonRef__-enableAddToNote"
|
||||
label="&zotero.__addonRef__.pref.annotation.autoAnnotation.label;"
|
||||
native="true"
|
||||
preference="__prefsPrefix__.autoAnnotation"
|
||||
/>
|
||||
</groupbox>
|
||||
</vbox>
|
||||
<vbox>
|
||||
<label
|
||||
value="&zotero.__addonRef__.help.version.label; &zotero.__addonRef__.help.releasetime.label;"
|
||||
></label>
|
||||
</vbox>
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
<?xml version="1.0"?>
|
||||
<?xml-stylesheet href="chrome://global/skin/"?>
|
||||
<?xml-stylesheet href="chrome://zotero/skin/zotero.css" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://zotero/skin/overlay.css" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://zotero-platform/content/overlay.css"?>
|
||||
|
||||
<!DOCTYPE window [
|
||||
<!ENTITY % ZoteroDTD SYSTEM "chrome://zotero/locale/zotero.dtd">
|
||||
%ZoteroDTD;
|
||||
<!ENTITY % knowledgeDTD SYSTEM "chrome://__addonRef__/locale/overlay.dtd">
|
||||
%knowledgeDTD;
|
||||
]>
|
||||
|
||||
<dialog id="betternotes-sync-dialog" windowtype="betternotes-sync" title="&zotero.__addonRef__.sync.title;" orient="vertical" width="300" height="300" buttons="cancel,accept,help" ondialogaccept="Zotero.BetterNotes.SyncInfoWindow.doAccept();" ondialoghelp="Zotero.BetterNotes.SyncInfoWindow.doExport();" onload="Zotero.BetterNotes.SyncInfoWindow.doLoad(window);" onunload="Zotero.BetterNotes.SyncInfoWindow.doUnload();" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml" style="padding:2em" persist="screenX screenY width height" buttonlabelhelp="&zotero.__addonRef__.sync.export.label;">
|
||||
<script src="chrome://zotero/content/include.js" />
|
||||
<vbox flex="1">
|
||||
<label value="&zotero.__addonRef__.sync.path.label;" />
|
||||
<label id="__addonRef__-sync-path" />
|
||||
<label value="&zotero.__addonRef__.sync.lastsync.label;" />
|
||||
<label id="__addonRef__-sync-lastsync" />
|
||||
<hbox>
|
||||
<checkbox id="__addonRef__-sync-enable" label="&zotero.__addonRef__.sync.enable.label;" checked="true" />
|
||||
<button flex="0" label="&zotero.__addonRef__.sync.dosync.label;" oncommand="Zotero.BetterNotes.SyncController.doSync(null, true)"></button>
|
||||
<button flex="0" label="&zotero.__addonRef__.sync.synclist.label;" oncommand="Zotero.BetterNotes.SyncListWindow.openSyncList()"></button>
|
||||
</hbox>
|
||||
</vbox>
|
||||
</dialog>
|
||||
|
|
@ -8,7 +8,7 @@
|
|||
type="text/css"
|
||||
href="chrome://__addonRef__/content/lib/css/dx.light.css"
|
||||
/>
|
||||
<link rel="stylesheet" href="chrome://__addonRef__/skin/workspace.css" />
|
||||
<link rel="stylesheet" href="chrome://__addonRef__/content/tooltip.css" />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
<?xml version="1.0"?>
|
||||
<?xml-stylesheet href="chrome://global/skin/"?>
|
||||
<?xml-stylesheet href="chrome://zotero/skin/zotero.css" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://zotero/skin/overlay.css" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://zotero/skin/standalone.css" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://zotero-platform/content/overlay.css"?>
|
||||
|
||||
<!DOCTYPE window [
|
||||
<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
|
||||
%globalDTD;
|
||||
<!ENTITY % standaloneDTD SYSTEM "chrome://zotero/locale/standalone.dtd">
|
||||
%standaloneDTD;
|
||||
<!ENTITY % zoteroDTD SYSTEM "chrome://zotero/locale/zotero.dtd">
|
||||
%zoteroDTD;
|
||||
<!ENTITY % knowledgeDTD SYSTEM "chrome://__addonRef__/locale/overlay.dtd">
|
||||
%knowledgeDTD;
|
||||
]>
|
||||
|
||||
<window id="zotero-knowledge-sync-list" onload="Zotero.BetterNotes.SyncListWindow.doLoad(window);" onresize="Zotero.BetterNotes.SyncListWindow.doUpdate();" orient="vertical" width="600" height="350" title="&zotero.__addonRef__.syncList.title;" persist="screenX screenY width height" windowtype="zotero:knowledgeSyncList" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml">
|
||||
<script src="chrome://zotero/content/include.js" />
|
||||
|
||||
<keyset>
|
||||
<key id="key_close" key="W" modifiers="accel" command="cmd_close" />
|
||||
<key id="key_selectall" key="A" modifiers="accel" command="cmd_selectall" />
|
||||
<key id="key_selectall" keyCode="27" command="cmd_unselectall" />
|
||||
</keyset>
|
||||
<command id="cmd_close" oncommand="window.close();" />
|
||||
<command id="cmd_selectall" oncommand="window.document.getElementById('sync-list').selectAll();" />
|
||||
<command id="cmd_unselectall" oncommand="window.document.getElementById('sync-list').clearSelection();" />
|
||||
|
||||
<vbox flex="1">
|
||||
<listbox id="sync-list" flex="1" seltype="multiple" onselect="Zotero.BetterNotes.SyncListWindow.onSelect();">
|
||||
<listhead>
|
||||
<listheader id="icon" label="#" flex="1" />
|
||||
<listheader id="notename" label="&zotero.__addonRef__.syncList.notename.label;" flex="1" />
|
||||
<listheader id="lastsync" label="&zotero.__addonRef__.syncList.lastsync.label;" flex="1" />
|
||||
<listheader id="syncpath" label="&zotero.__addonRef__.syncList.syncpath.label;" flex="1" />
|
||||
</listhead>
|
||||
<listcols>
|
||||
<listcol flex="1" />
|
||||
</listcols>
|
||||
</listbox>
|
||||
<hbox flex="0">
|
||||
<button id="doupdate" label="&zotero.__addonRef__.syncList.doupdate.label;" oncommand="Zotero.BetterNotes.SyncListWindow.doUpdate();"></button>
|
||||
<button id="changesyncperiod" label="&zotero.__addonRef__.syncList.changesyncperiod.label;" oncommand="Zotero.BetterNotes.SyncListWindow.changeSyncPeriod();"></button>
|
||||
<button id="dosync" label="&zotero.__addonRef__.syncList.dosync.label;" oncommand="Zotero.BetterNotes.SyncListWindow.doSync();"></button>
|
||||
<button id="changesync" label="&zotero.__addonRef__.syncList.changesync.label;" oncommand="Zotero.BetterNotes.SyncListWindow.changeSync();"></button>
|
||||
<button id="removesync" label="&zotero.__addonRef__.syncList.removesync.label;" oncommand="Zotero.BetterNotes.SyncListWindow.removeSync();"></button>
|
||||
</hbox>
|
||||
</vbox>
|
||||
|
||||
</window>
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
<?xml version="1.0"?>
|
||||
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://zotero/skin/zotero.css" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://zotero-platform/content/zotero-react-client.css" type="text/css"?>
|
||||
<!DOCTYPE html>
|
||||
<html
|
||||
lang="en"
|
||||
xmlns="http://www.w3.org/1999/xhtml"
|
||||
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
xmlns:html="http://www.w3.org/1999/xhtml"
|
||||
windowtype="__addonRef__-syncManager"
|
||||
sizemode="normal"
|
||||
scrolling="false"
|
||||
persist="screenX screenY width height sizemode"
|
||||
>
|
||||
<head>
|
||||
<title locale-target="innerHTML">syncManager.title</title>
|
||||
<meta charset="utf-8" />
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", (ev) => {
|
||||
const { Services } = ChromeUtils.import(
|
||||
"resource://gre/modules/Services.jsm"
|
||||
);
|
||||
|
||||
Services.scriptloader.loadSubScript(
|
||||
"chrome://zotero/content/include.js",
|
||||
this
|
||||
);
|
||||
|
||||
Services.scriptloader.loadSubScript(
|
||||
"resource://zotero/require.js",
|
||||
this
|
||||
);
|
||||
window.arguments[0]._initPromise.resolve();
|
||||
});
|
||||
</script>
|
||||
<style>
|
||||
html,
|
||||
body,
|
||||
.viewport {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
word-wrap: break-word;
|
||||
background-color: #f0f0f0;
|
||||
font-family: initial;
|
||||
}
|
||||
.viewport {
|
||||
margin: 0 5px 0 5px;
|
||||
}
|
||||
.viewport-container {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
height: calc(100% - 50px);
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
overflow: hidden;
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
.footer-container {
|
||||
padding: 5px;
|
||||
margin: 0;
|
||||
height: 30px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
overflow: hidden;
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
.list-viewport {
|
||||
width: 100%;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
.tool-button {
|
||||
width: 75px;
|
||||
max-width: 75px;
|
||||
min-width: 75px;
|
||||
padding: 5px;
|
||||
text-align: center;
|
||||
margin: 0 10px 0 10px;
|
||||
}
|
||||
.virtualized-table-body {
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="viewport-container">
|
||||
<div id="list-container" class="viewport list-viewport">
|
||||
<div id="table-container"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="footer-container"
|
||||
style="justify-content: flex-start; padding: 10px"
|
||||
>
|
||||
<button class="tool-button" id="refresh" locale-target="innerHTML">
|
||||
syncManager.refresh
|
||||
</button>
|
||||
<button
|
||||
class="tool-button"
|
||||
id="sync"
|
||||
locale-target="innerHTML,title"
|
||||
title="syncManager.syncDetail"
|
||||
>
|
||||
syncManager.sync
|
||||
</button>
|
||||
<button
|
||||
class="tool-button"
|
||||
id="unSync"
|
||||
locale-target="innerHTML,title"
|
||||
title="syncManager.unSyncDetail"
|
||||
>
|
||||
syncManager.unSync
|
||||
</button>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
<?xml version="1.0"?>
|
||||
<?xml-stylesheet href="chrome://global/skin/"?>
|
||||
<?xml-stylesheet href="chrome://zotero/skin/zotero.css" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://zotero/skin/overlay.css" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://zotero/skin/standalone.css" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://zotero-platform/content/overlay.css"?>
|
||||
|
||||
<!DOCTYPE window [
|
||||
<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
|
||||
%globalDTD;
|
||||
<!ENTITY % standaloneDTD SYSTEM "chrome://zotero/locale/standalone.dtd">
|
||||
%standaloneDTD;
|
||||
<!ENTITY % zoteroDTD SYSTEM "chrome://zotero/locale/zotero.dtd">
|
||||
%zoteroDTD;
|
||||
<!ENTITY % knowledgeDTD SYSTEM "chrome://__addonRef__/locale/overlay.dtd">
|
||||
%knowledgeDTD;
|
||||
]>
|
||||
|
||||
<window id="zotero-knowledge-template-editor" onload="Zotero.BetterNotes.TemplateWindow.initTemplates(window);" orient="vertical" width="600" height="350" title="&zotero.__addonRef__.template.title;" persist="screenX screenY width height" windowtype="zotero:knowledgeTemplateEditor" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml">
|
||||
<script src="chrome://zotero/content/include.js" />
|
||||
|
||||
<keyset>
|
||||
<key id="key_save" key="S" modifiers="accel" command="cmd_save" />
|
||||
<key id="key_help" key="H" modifiers="accel" command="cmd_help" />
|
||||
<key id="key_close" key="W" modifiers="accel" command="cmd_close" />
|
||||
</keyset>
|
||||
<command id="cmd_save" oncommand="Zotero.BetterNotes.TemplateWindow.saveSelectedTemplate();" />
|
||||
<command id="cmd_help" oncommand="Zotero.launchURL('https://github.com/windingwind/zotero-better-notes/blob/master/Template.md');" />
|
||||
<command id="cmd_more" oncommand="Zotero.launchURL('https://github.com/windingwind/zotero-better-notes/discussions/categories/note-templates');" />
|
||||
<command id="cmd_close" oncommand="window.close();" />
|
||||
|
||||
<hbox flex="1">
|
||||
<vbox id="template-container" flex="1" minwidth="400px">
|
||||
<listbox id="template-list" flex="1" seltype="single" onselect="Zotero.BetterNotes.TemplateWindow.updateEditorView();">
|
||||
<listhead>
|
||||
<listheader id="name" label="&zotero.__addonRef__.template.list.label;" flex="1" />
|
||||
</listhead>
|
||||
<listcols>
|
||||
<listcol flex="1" />
|
||||
</listcols>
|
||||
</listbox>
|
||||
<row style="margin: 10px 0 10px 0;">
|
||||
<button id="create-template" label="&zotero.__addonRef__.template.create.label;" oncommand="Zotero.BetterNotes.TemplateWindow.createTemplate();"></button>
|
||||
<button id="import-template" label="&zotero.__addonRef__.template.import.label;" oncommand="Zotero.BetterNotes.TemplateWindow.importNoteTemplate();"></button>
|
||||
<button id="help" label="&helpMenu.label;" command="cmd_help" key="key_help" accesskey="H"></button>
|
||||
<button id="more" label="&zotero.__addonRef__.template.more.label;" command="cmd_more"></button>
|
||||
</row>
|
||||
</vbox>
|
||||
<splitter></splitter>
|
||||
<rows minwidth="400px">
|
||||
<row style="margin: 10px 0 10px 0;">
|
||||
<label value="&zotero.__addonRef__.template.header.label;"></label>
|
||||
<textbox id="editor-name" disabled="true"></textbox>
|
||||
</row>
|
||||
<row flex="1">
|
||||
<textbox id="editor-textbox" flex="1" multiline="true" disabled="true"></textbox>
|
||||
</row>
|
||||
<row style="margin: 10px 0 10px 0;">
|
||||
<button id="save-template" label="&zotero.__addonRef__.template.save.label;" command="cmd_save" key="key_save" accesskey="S"></button>
|
||||
<button id="delete-template" label="&zotero.__addonRef__.template.delete.label;" oncommand="Zotero.BetterNotes.TemplateWindow.deleteSelectedTemplate();"></button>
|
||||
<button id="reset-template" label="&zotero.__addonRef__.template.reset.label;" oncommand="Zotero.BetterNotes.TemplateWindow.resetSelectedTemplate();" hidden="hidden"></button>
|
||||
</row>
|
||||
</rows>
|
||||
</hbox>
|
||||
|
||||
</window>
|
||||
|
|
@ -0,0 +1,201 @@
|
|||
<?xml version="1.0"?>
|
||||
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://zotero/skin/zotero.css" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://zotero-platform/content/zotero-react-client.css" type="text/css"?>
|
||||
<!DOCTYPE html>
|
||||
<html
|
||||
lang="en"
|
||||
xmlns="http://www.w3.org/1999/xhtml"
|
||||
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
xmlns:html="http://www.w3.org/1999/xhtml"
|
||||
windowtype="__addonRef__-templateEditor"
|
||||
sizemode="normal"
|
||||
scrolling="false"
|
||||
persist="screenX screenY width height sizemode"
|
||||
>
|
||||
<head>
|
||||
<title locale-target="innerHTML">templateEditor.title</title>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="referrer" content="no-referrer" />
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", (ev) => {
|
||||
const { Services } = ChromeUtils.import(
|
||||
"resource://gre/modules/Services.jsm"
|
||||
);
|
||||
|
||||
Services.scriptloader.loadSubScript(
|
||||
"chrome://zotero/content/include.js",
|
||||
this
|
||||
);
|
||||
|
||||
Services.scriptloader.loadSubScript(
|
||||
"resource://zotero/require.js",
|
||||
this
|
||||
);
|
||||
window.arguments[0]._initPromise.resolve();
|
||||
});
|
||||
</script>
|
||||
<style>
|
||||
html,
|
||||
body,
|
||||
.viewport {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
word-wrap: break-word;
|
||||
background-color: #f0f0f0;
|
||||
font-family: initial;
|
||||
}
|
||||
.viewport {
|
||||
margin: 0 5px 0 5px;
|
||||
}
|
||||
.viewport-container {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
height: calc(100% - 50px);
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
overflow: hidden;
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
.footer-container {
|
||||
padding: 5px;
|
||||
margin: 0;
|
||||
height: 30px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
overflow: hidden;
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
.list-viewport {
|
||||
width: calc(20% - 10px);
|
||||
background-color: #ffffff;
|
||||
}
|
||||
.editor-viewport {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: calc(40% - 10px);
|
||||
padding: 5px;
|
||||
}
|
||||
.preview-viewport {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: calc(40% - 10px);
|
||||
padding: 5px;
|
||||
}
|
||||
.tool-button {
|
||||
width: 75px;
|
||||
max-width: 75px;
|
||||
min-width: 75px;
|
||||
padding: 5px;
|
||||
text-align: center;
|
||||
margin: 0 10px 0 10px;
|
||||
}
|
||||
.markdown-body {
|
||||
box-sizing: border-box;
|
||||
min-width: 200px;
|
||||
max-width: 980px;
|
||||
margin: 0 auto;
|
||||
padding: 0px;
|
||||
}
|
||||
</style>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
href="chrome://__addonRef__/content/lib/css/github-markdown-light.min.css"
|
||||
/>
|
||||
</head>
|
||||
<body>
|
||||
<div class="viewport-container">
|
||||
<div id="list-container" class="viewport list-viewport">
|
||||
<div id="table-container"></div>
|
||||
</div>
|
||||
<div class="viewport editor-viewport">
|
||||
<div style="display: flex">
|
||||
<div style="flex-shrink: 0" locale-target="innerHTML">
|
||||
templateEditor.templateName
|
||||
</div>
|
||||
<input id="editor-name" type="text" style="width: 100%" />
|
||||
</div>
|
||||
<div style="display: flex; flex-direction: column; height: 100%">
|
||||
<textarea
|
||||
id="editor-textbox"
|
||||
style="height: 100%; resize: none"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="viewport preview-viewport">
|
||||
<div style="display: flex">
|
||||
<div style="flex-shrink: 0" locale-target="innerHTML">
|
||||
templateEditor.previewContainer
|
||||
</div>
|
||||
</div>
|
||||
<article id="preview-container" class="markdown-body"></article>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="footer-container"
|
||||
style="justify-content: flex-start; padding: 10px"
|
||||
>
|
||||
<button
|
||||
class="tool-button"
|
||||
id="create"
|
||||
locale-target="innerHTML,title"
|
||||
title="templateEditor.createDetail"
|
||||
>
|
||||
templateEditor.create
|
||||
</button>
|
||||
<button
|
||||
class="tool-button"
|
||||
id="import"
|
||||
locale-target="innerHTML,title"
|
||||
title="templateEditor.importDetail"
|
||||
>
|
||||
templateEditor.import
|
||||
</button>
|
||||
<button
|
||||
class="tool-button"
|
||||
id="more"
|
||||
locale-target="innerHTML,title"
|
||||
title="templateEditor.moreDetail"
|
||||
>
|
||||
templateEditor.more
|
||||
</button>
|
||||
<button
|
||||
class="tool-button"
|
||||
id="save"
|
||||
locale-target="innerHTML,title"
|
||||
title="templateEditor.saveDetail"
|
||||
>
|
||||
templateEditor.save
|
||||
</button>
|
||||
<button
|
||||
class="tool-button"
|
||||
id="delete"
|
||||
locale-target="innerHTML,title"
|
||||
title="templateEditor.deleteDetail"
|
||||
>
|
||||
templateEditor.delete
|
||||
</button>
|
||||
<button
|
||||
class="tool-button"
|
||||
id="reset"
|
||||
locale-target="innerHTML,title"
|
||||
title="templateEditor.resetDetail"
|
||||
>
|
||||
templateEditor.reset
|
||||
</button>
|
||||
<button
|
||||
class="tool-button"
|
||||
id="help"
|
||||
locale-target="innerHTML,title"
|
||||
title="templateEditor.helpDetail"
|
||||
>
|
||||
templateEditor.help
|
||||
</button>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -84,7 +84,7 @@
|
|||
items,
|
||||
expandNodesRecursive: false,
|
||||
dataStructure: "plain",
|
||||
height: $("window").height() - 100,
|
||||
height: $("window").height(),
|
||||
displayExpr: "name",
|
||||
noDataText: "Outline is Empty",
|
||||
onItemClick: jumpNode,
|
||||
|
|
@ -127,7 +127,7 @@
|
|||
e.dropInsideItem &&
|
||||
toNode !== null &&
|
||||
// !toNode.itemData.isDirectory &&
|
||||
toNode.itemData.rank === 7
|
||||
toNode.itemData.level === 7
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -160,7 +160,11 @@
|
|||
}
|
||||
|
||||
function getTreeView() {
|
||||
try {
|
||||
return $("#treeview").dxTreeView("instance");
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function calculateToIndex(e) {
|
||||
|
|
@ -267,22 +271,44 @@
|
|||
window.parent.postMessage({ type: "getMindMapData" }, "*");
|
||||
}
|
||||
|
||||
function setData(nodes) {
|
||||
function walkNode(node, callback) {
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
callback(node);
|
||||
node.children.forEach((child) => walkNode(child, callback));
|
||||
}
|
||||
|
||||
function setData(nodes, expandLevel) {
|
||||
console.log(nodes);
|
||||
const tree = getTreeView();
|
||||
const expandedStatus = {};
|
||||
if (tree) {
|
||||
const rootNodes = tree.getNodes();
|
||||
console.log(tree, rootNodes);
|
||||
rootNodes.forEach((root) => {
|
||||
walkNode(root, (node) => {
|
||||
expandedStatus[node.itemData.name] = node.itemData.expanded;
|
||||
});
|
||||
});
|
||||
}
|
||||
console.log(expandedStatus);
|
||||
const treeData = [];
|
||||
nodes.map((node) => {
|
||||
treeData.push({
|
||||
id: String(node.model.id),
|
||||
name: node.model.name,
|
||||
rank: node.model.rank,
|
||||
icon: node.model.rank === 7 ? noteIcon : undefined,
|
||||
level: node.model.level,
|
||||
icon: node.model.level === 7 ? noteIcon : undefined,
|
||||
lineIndex: node.model.lineIndex,
|
||||
endIndex: node.model.endIndex,
|
||||
isDirectory: node.children.length > 0,
|
||||
expanded:
|
||||
node.children.filter((n) => n.model.rank < 7).length !== 0,
|
||||
node.model.name in expandedStatus
|
||||
? expandedStatus[node.model.name]
|
||||
: node.model.level < expandLevel,
|
||||
parentId:
|
||||
node.model.rank > 1 ? String(node.parent.model.id) : undefined,
|
||||
node.model.level > 1 ? String(node.parent.model.id) : undefined,
|
||||
});
|
||||
});
|
||||
$(() => {
|
||||
|
|
@ -296,7 +322,7 @@
|
|||
if (itemData.noteLink) {
|
||||
window.parent.postMessage(
|
||||
{
|
||||
type: "jumpNote",
|
||||
type: "openNote",
|
||||
link: itemData.noteLink,
|
||||
id: parseInt(itemData.id),
|
||||
},
|
||||
|
|
@ -308,6 +334,7 @@
|
|||
type: "jumpNode",
|
||||
lineIndex: itemData.lineIndex,
|
||||
id: parseInt(itemData.id),
|
||||
workspaceType: window.workspaceType || "tab",
|
||||
},
|
||||
"*"
|
||||
);
|
||||
|
|
@ -317,7 +344,8 @@
|
|||
function handler(e) {
|
||||
console.log(e);
|
||||
if (e.data.type === "setMindMapData") {
|
||||
setData(e.data.nodes);
|
||||
setData(e.data.nodes, e.data.expandLevel);
|
||||
window.workspaceType = e.data.workspaceType;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,61 +0,0 @@
|
|||
<?xml version="1.0"?>
|
||||
<?xml-stylesheet href="chrome://global/skin/"?>
|
||||
<?xml-stylesheet href="chrome://zotero/skin/zotero.css" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://zotero/skin/overlay.css" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://zotero-platform/content/overlay.css"?>
|
||||
|
||||
<!DOCTYPE window [
|
||||
<!ENTITY % ZoteroDTD SYSTEM "chrome://zotero/locale/zotero.dtd">
|
||||
%ZoteroDTD;
|
||||
<!ENTITY % knowledgeDTD SYSTEM "chrome://__addonRef__/locale/overlay.dtd">
|
||||
%knowledgeDTD;
|
||||
]>
|
||||
|
||||
<wizard xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="knowledge-wizard" windowtype="knowledge-wizard" title="&zotero.__addonRef__.wizard.title;" width="788" height="600" onwizardfinish="Zotero.BetterNotes.WizardWindow.setup()">
|
||||
<script src="chrome://zotero/content/include.js" />
|
||||
|
||||
<wizardpage label="&zotero.__addonRef__.wizard.title;">
|
||||
<image src="chrome://__addonRef__/skin/knowledge-app.png" width="560" height="400"></image>
|
||||
<description style="white-space: pre-wrap; font-size: large; font-weight: bold;">&zotero.__addonRef__.wizard.page1.header;</description>
|
||||
<description style="white-space: pre-wrap;">&zotero.__addonRef__.wizard.page1.description;</description>
|
||||
</wizardpage>
|
||||
<wizardpage label="&zotero.__addonRef__.wizard.open;">
|
||||
<image src="chrome://__addonRef__/skin/open-knowledge.png" width="560" height="400"></image>
|
||||
<description style="white-space: pre-wrap; font-size: large; font-weight: bold;">&zotero.__addonRef__.wizard.page3.header;</description>
|
||||
<description style="white-space: pre-wrap;">&zotero.__addonRef__.wizard.page3.description;</description>
|
||||
</wizardpage>
|
||||
<wizardpage label="&zotero.__addonRef__.wizard.layout;">
|
||||
<image src="chrome://__addonRef__/skin/intro-workspace.png" width="560" height="400"></image>
|
||||
<description style="white-space: pre-wrap; font-size: large; font-weight: bold;">&zotero.__addonRef__.wizard.page4.header;</description>
|
||||
<description style="white-space: pre-wrap;">&zotero.__addonRef__.wizard.page4.description1;</description>
|
||||
<description style="white-space: pre-wrap;">&zotero.__addonRef__.wizard.page4.description2;</description>
|
||||
<description style="white-space: pre-wrap;">&zotero.__addonRef__.wizard.page4.description3;</description>
|
||||
</wizardpage>
|
||||
<wizardpage label="&zotero.__addonRef__.wizard.link;">
|
||||
<image src="chrome://__addonRef__/skin/add-link.gif" width="560" height="400" style="padding-top: 50px;"></image>
|
||||
<description style="white-space: pre-wrap; font-size: large; font-weight: bold;">&zotero.__addonRef__.wizard.page5.header;</description>
|
||||
<description style="white-space: pre-wrap;">&zotero.__addonRef__.wizard.page5.description1;</description>
|
||||
<description style="white-space: pre-wrap;">&zotero.__addonRef__.wizard.page5.description2;</description>
|
||||
</wizardpage>
|
||||
<wizardpage label="&zotero.__addonRef__.wizard.setup;">
|
||||
<description style="white-space: pre-wrap; font-size: large; font-weight: bold;">&zotero.__addonRef__.wizard.page6.header;</description>
|
||||
<description style="white-space: pre-wrap;">&zotero.__addonRef__.wizard.page6.description1;</description>
|
||||
<description style="white-space: pre-wrap; color: red;">&zotero.__addonRef__.wizard.page6.description2;</description>
|
||||
<rows>
|
||||
<row>
|
||||
<checkbox id="__addonRef__-setup-enable" checked="true" oncommand="Zotero.BetterNotes.WizardWindow.changeSetup()" />
|
||||
<label value="&zotero.__addonRef__.wizard.setup.enable.label;" />
|
||||
</row>
|
||||
<row>
|
||||
<checkbox id="__addonRef__-setup-collectionenable" checked="true" oncommand="Zotero.BetterNotes.WizardWindow.updateCollectionSetup()" />
|
||||
<label value="&zotero.__addonRef__.wizard.setup.collectionenable.label;" />
|
||||
<textbox id="__addonRef__-setup-collectionname" value="My Notes" oninput="Zotero.BetterNotes.WizardWindow.updateCollectionSetup()" />
|
||||
</row>
|
||||
<row>
|
||||
<checkbox id="__addonRef__-setup-noteenable" checked="true" oncommand="Zotero.BetterNotes.WizardWindow.updateNoteSetup()" />
|
||||
<label value="&zotero.__addonRef__.wizard.setup.noteenable.label;" />
|
||||
</row>
|
||||
</rows>
|
||||
</wizardpage>
|
||||
<script>Zotero.BetterNotes.WizardWindow.init(document)</script>
|
||||
</wizard>
|
||||
|
|
@ -1,178 +0,0 @@
|
|||
<?xml version="1.0"?>
|
||||
<?xml-stylesheet href="chrome://global/skin/"?>
|
||||
<?xml-stylesheet href="chrome://zotero/skin/zotero.css" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://zotero/skin/overlay.css" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://zotero/skin/standalone.css" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://zotero-platform/content/overlay.css"?>
|
||||
<?xml-stylesheet href="chrome://__addonRef__/skin/workspace.css" type="text/css"?>
|
||||
|
||||
<!DOCTYPE window [
|
||||
<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
|
||||
%globalDTD;
|
||||
<!ENTITY % standaloneDTD SYSTEM "chrome://zotero/locale/standalone.dtd">
|
||||
%standaloneDTD;
|
||||
<!ENTITY % editMenuOverlayDTD SYSTEM "chrome://zotero/locale/mozilla/editMenuOverlay.dtd">
|
||||
%editMenuOverlayDTD;
|
||||
<!ENTITY % zoteroDTD SYSTEM "chrome://zotero/locale/zotero.dtd">
|
||||
%zoteroDTD;
|
||||
<!ENTITY % knowledgeDTD SYSTEM "chrome://__addonRef__/locale/overlay.dtd">
|
||||
%knowledgeDTD;
|
||||
]>
|
||||
|
||||
<window id="zotero-knowledge-workspace" orient="vertical" width="1000" height="350" title="&zotero.__addonRef__.workspace.title;" persist="screenX screenY width height sizemode" windowtype="zotero:knowledgeWorkspace" onload="Zotero.BetterNotes.WorkspaceWindow._workspacePromise.resolve()" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml">
|
||||
|
||||
<script src="chrome://zotero/content/include.js" />
|
||||
|
||||
<keyset>
|
||||
<key id="key_new" key="N" modifiers="accel" command="cmd_new" />
|
||||
<key id="key_open" key="O" modifiers="accel" command="cmd_open" />
|
||||
<key id="key_export" key="E" modifiers="accel" command="cmd_export" />
|
||||
<key id="key_sync_betternotes" key="S" modifiers="accel,shift" command="cmd_sync_betternotes" />
|
||||
<key id="key_close" key="W" modifiers="accel" command="cmd_close" />
|
||||
<!-- <key id="key_insertNotes" key="I" modifiers="accel" command="cmd_insertNotes" /> -->
|
||||
<key id="key_indent" keycode="VK_TAB" command="cmd_indent_betternotes" />
|
||||
<key id="key_unindent" keycode="VK_TAB" modifiers="shift" command="cmd_unindent_betternotes" />
|
||||
</keyset>
|
||||
<command id="cmd_new" oncommand="Zotero.BetterNotes.ZoteroEvents.onEditorEvent({type: 'createWorkspace'});" />
|
||||
<command id="cmd_open" oncommand="Zotero.BetterNotes.ZoteroEvents.onEditorEvent({type: 'selectMainNote'});" />
|
||||
<command id="cmd_openWindow" oncommand="Zotero.BetterNotes.ZoteroEvents.onEditorEvent({type: 'openWorkspaceInWindow'});" />
|
||||
<command id="cmd_export" oncommand="Zotero.BetterNotes.ZoteroEvents.onEditorEvent({type: 'export', content: {editorInstance: {_item: false}}});" />
|
||||
<command id="cmd_sync_betternotes" oncommand="Zotero.BetterNotes.ZoteroEvents.onEditorEvent({type: 'sync'});" />
|
||||
<command id="cmd_sync_manager_betternotes" oncommand="Zotero.BetterNotes.SyncListWindow.openSyncList();" />
|
||||
<command id="cmd_close" oncommand="Zotero.BetterNotes.ZoteroEvents.onEditorEvent({type: 'closeWorkspace'});" />
|
||||
<!-- <command id="cmd_insertNotes" oncommand="Zotero.BetterNotes.ZoteroEvents.onEditorEvent({type: 'insertNotes'});" /> -->
|
||||
<command id="cmd_editTemplate" oncommand="Zotero.BetterNotes.ZoteroEvents.onEditorEvent({type: 'editTemplate'});" />
|
||||
<!-- <command id="cmd_importlink_betternotes" oncommand="Zotero.BetterNotes.ZoteroEvents.onEditorEvent({type: 'importLink'});" />
|
||||
<command id="cmd_updatelink_betternotes" oncommand="Zotero.BetterNotes.ZoteroEvents.onEditorEvent({type: 'updateLink'});" /> -->
|
||||
<command id="cmd_autoannotation_betternotes" oncommand="Zotero.BetterNotes.ZoteroEvents.onEditorEvent({type: 'updateAutoAnnotation', content: {event: event}});" />
|
||||
<command id="cmd_convertmd_betternotes" oncommand="Zotero.BetterNotes.ZoteroEvents.onEditorEvent({type: 'convertMD'});" />
|
||||
<command id="cmd_convertasciidoc_betternotes" oncommand="Zotero.BetterNotes.ZoteroEvents.onEditorEvent({type: 'convertAsciiDoc'});" />
|
||||
<command id="cmd_treeview" oncommand="Zotero.BetterNotes.WorkspaceOutline.switchView(1);" />
|
||||
<command id="cmd_mindmap" oncommand="Zotero.BetterNotes.WorkspaceOutline.switchView(2);" />
|
||||
<command id="cmd_bubblemap" oncommand="Zotero.BetterNotes.WorkspaceOutline.switchView(3);" />
|
||||
<command id="cmd_guide" oncommand="Zotero.BetterNotes.ZoteroEvents.onEditorEvent({type: 'openUserGuide'});" />
|
||||
<command id="cmd_about" oncommand="Zotero.BetterNotes.ZoteroEvents.onEditorEvent({type: 'openAbout'});" />
|
||||
|
||||
<!-- Menu -->
|
||||
<menubar id="better-notes-menu">
|
||||
<menu id="fileMenu" label="&fileMenu.label;" accesskey="&fileMenu.accesskey;">
|
||||
<menupopup id="menu_FilePopup">
|
||||
<menuitem id="menu_new" label="&zotero.__addonRef__.workspace.menu.new;" key="key_new" accesskey="N" command="cmd_new" />
|
||||
<menuitem id="menu_open" label="&zotero.__addonRef__.workspace.menu.open;" key="key_open" accesskey="O" command="cmd_open" />
|
||||
<menuitem id="menu_export" label="&zotero.__addonRef__.workspace.menu.export;" key="key_export" accesskey="E" command="cmd_export" />
|
||||
<menuitem id="menu_sync_betternotes" label="&zotero.__addonRef__.workspace.menu.sync;" key="key_sync_betternotes" accesskey="S" command="cmd_sync_betternotes" />
|
||||
<menuitem id="menu_sync_manager_betternotes" label="&zotero.__addonRef__.workspace.menu.syncmanager;" command="cmd_sync_manager_betternotes" />
|
||||
<menuitem id="menu_close" label="&closeCmd.label;" key="key_close" accesskey="&closeCmd.accesskey;" command="cmd_close" />
|
||||
</menupopup>
|
||||
</menu>
|
||||
|
||||
<menu id="menu_edit" label="&editMenu.label;" accesskey="&editMenu.accesskey;">
|
||||
<menupopup id="menu_EditPopup">
|
||||
<!-- <menuitem id="menu_insertNotes" label="&zotero.__addonRef__.workspace.menu.insertNotes;" key="key_insertNotes" accesskey="I" command="cmd_insertNotes" /> -->
|
||||
<menu id="menu_insertTextTemplate" label="&zotero.__addonRef__.workspace.menu.insertTextTemplate;">
|
||||
<menupopup id="menu_insertTextTemplatePopup" onpopupshowing="Zotero.BetterNotes.ZoteroViews.updateTemplateMenu(arguments[0], 'Text');" />
|
||||
</menu>
|
||||
<menu id="menu_insertItemTemplate" label="&zotero.__addonRef__.workspace.menu.insertItemTemplate;">
|
||||
<menupopup id="menu_insertItemTemplatePopup" onpopupshowing="Zotero.BetterNotes.ZoteroViews.updateTemplateMenu(arguments[0], 'Item');" />
|
||||
</menu>
|
||||
<menuitem id="menu_editTemplate" label="&zotero.__addonRef__.workspace.menu.editTemplate;" command="cmd_editTemplate" />
|
||||
<menu id="menu_citeSetting_betternotes" class="menu-type-betternotes" label="&zotero.__addonRef__.workspace.menu.citeSetting;">
|
||||
<menupopup id="menu_citeSettingPopup" onpopupshowing="Zotero.BetterNotes.ZoteroViews.updateCitationStyleMenu();" />
|
||||
</menu>
|
||||
<!-- <menuitem id="menu_importlink_betternotes" class="menu-type-betternotes" label="&zotero.__addonRef__.workspace.menu.importLink;" command="cmd_importlink_betternotes" />
|
||||
<menuitem id="menu_updatelink_betternotes" class="menu-type-betternotes" label="&zotero.__addonRef__.workspace.menu.updateLink;" command="cmd_updatelink_betternotes" /> -->
|
||||
<menuseparator />
|
||||
<menuitem id="menu_autoannotation_betternotes" type="checkbox" label="&zotero.__addonRef__.workspace.menu.autoannotation;" command="cmd_autoannotation_betternotes" />
|
||||
<menuseparator />
|
||||
<menuitem id="menu_convertmd_betternotes" label="&zotero.__addonRef__.workspace.menu.convertmd;" command="cmd_convertmd_betternotes" />
|
||||
<menuitem id="menu_convertasciidoc_betternotes" class="menu-betternotes" label="&zotero.__addonRef__.workspace.menu.convertasciidoc;" command="cmd_convertasciidoc_betternotes" />
|
||||
</menupopup>
|
||||
</menu>
|
||||
|
||||
<menu id="view-menu" label="&viewMenu.label;" accesskey="&viewMenu.accesskey;" onpopupshowing="Zotero.BetterNotes.WorkspaceMenu.updateViewMenu();">
|
||||
<menupopup id="menu_ViewPopup">
|
||||
<menuitem id="menu_openWindow" label="&zotero.__addonRef__.workspace.menu.openWindow;" command="cmd_openWindow" />
|
||||
<menuseparator />
|
||||
<menuitem id="menu_treeview" type="checkbox" label="&zotero.__addonRef__.workspace.menu.treeview;" command="cmd_treeview" />
|
||||
<menuitem id="menu_mindmap" type="checkbox" label="&zotero.__addonRef__.workspace.menu.mindmap;" command="cmd_mindmap" />
|
||||
<menuitem id="menu_bubblemap" type="checkbox" label="&zotero.__addonRef__.workspace.menu.bubblemap;" command="cmd_bubblemap" />
|
||||
<menuseparator />
|
||||
<menu id="note-font-size-menu" label="¬eFontSize.label;">
|
||||
<menupopup oncommand="Zotero.Prefs.set('note.fontSize', event.originalTarget.getAttribute('label'));">
|
||||
<menuitem label="11" type="checkbox" />
|
||||
<menuitem label="12" type="checkbox" />
|
||||
<menuitem label="13" type="checkbox" />
|
||||
<menuitem label="14" type="checkbox" />
|
||||
<menuitem label="18" type="checkbox" />
|
||||
<menuitem label="24" type="checkbox" />
|
||||
<menuitem label="36" type="checkbox" />
|
||||
<menuitem label="48" type="checkbox" />
|
||||
<menuitem label="64" type="checkbox" />
|
||||
<menuitem label="72" type="checkbox" />
|
||||
<menuitem label="96" type="checkbox" />
|
||||
<menuseparator />
|
||||
<menuitem id="view-menuitem-note-font-size-reset" label="&zotero.general.reset;" oncommand="Zotero.Prefs.clear('note.fontSize');" />
|
||||
</menupopup>
|
||||
</menu>
|
||||
<menuseparator />
|
||||
<menuitem id="menu_wordcount_betternotes" disabled="true" />
|
||||
</menupopup>
|
||||
</menu>
|
||||
|
||||
<menu id="helpMenu" label="&helpMenu.label;" accesskey="&helpMenu.accesskey;">
|
||||
<menupopup id="menu_HelpPopup">
|
||||
<menuitem id="menu_guide" label="&zotero.__addonRef__.workspace.menu.guide;" command="cmd_guide" />
|
||||
<menuitem id="menu_about" label="&zotero.__addonRef__.workspace.menu.about;" command="cmd_about" />
|
||||
</menupopup>
|
||||
</menu>
|
||||
</menubar>
|
||||
|
||||
|
||||
<hbox flex="1">
|
||||
<vbox id="zotero-knowledge-outline" flex="1" width="330" minwidth="300" style="overflow: hidden;">
|
||||
<html:div id="mindmap-container">
|
||||
<!-- Inser Here -->
|
||||
</html:div>
|
||||
<html:div id="outline-tools" height="50" maxheight="50" minheight="50" style="display: flex; flex-flex-direction: row; justify-content: space-between; margin: 0px 20px 0px 20px;">
|
||||
<div class="tooltip">
|
||||
<html:button id="outline-switchview" class="tool-button">
|
||||
<svg t="1652006549395" class="icon tool-icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="14217" width="18" height="18">
|
||||
<path d="M928 637c-8.8 0-16-7.2-16-16v-77c0-53-43-96-96-96H560c-8.8 0-16-7.2-16-16v-32c0-8.8 7.2-16 16-16h80c53 0 96-43 96-96V144c0-53-43-96-96-96H384c-53 0-96 43-96 96v144c0 53 43 96 96 96h80c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H208c-53 0-96 43-96 96v77c0 8.8-7.2 16-16 16-53 0-96 43-96 96v131c0 53 43 96 96 96h96c53 0 96-43 96-96V733c0-53-43-96-96-96-8.8 0-16-7.2-16-16v-77c0-17.7 14.3-32 32-32h256c8.8 0 16 7.2 16 16v94c0 8.3-6.7 15-15 15h-1c-53 0-96 43-96 96v131c0 53 43 96 96 96h96c53 0 96-43 96-96V733c0-53-43-96-96-96h-1c-8.3 0-15-6.7-15-15v-94c0-8.8 7.2-16 16-16h256c17.7 0 32 14.3 32 32v77c0 8.8-7.2 16-16 16-53 0-96 43-96 96v131c0 53 43 96 96 96h96c53 0 96-43 96-96V733c0-53-43-96-96-96z m-736 64c17.6 0 32 14.4 32 32v131c0 17.6-14.4 32-32 32H96c-17.6 0-32-14.4-32-32V733c0-17.6 14.4-32 32-32h96z m368 0c17.6 0 32 14.4 32 32v131c0 17.6-14.4 32-32 32h-96c-17.6 0-32-14.4-32-32V733c0-17.6 14.4-32 32-32h96zM384 320c-17.6 0-32-14.4-32-32V144c0-17.6 14.4-32 32-32h256c17.6 0 32 14.4 32 32v144c0 17.6-14.4 32-32 32H384z m576 544c0 17.6-14.4 32-32 32h-96c-17.6 0-32-14.4-32-32V733c0-17.6 14.4-32 32-32h96c17.6 0 32 14.4 32 32v131z" p-id="14218"></path>
|
||||
</svg>
|
||||
</html:button>
|
||||
<span class="tooltiptext">Switch Outline Mode</span>
|
||||
</div>
|
||||
<div>
|
||||
<div class="tooltip">
|
||||
<html:button id="outline-saveImage" class="tool-button">
|
||||
<svg t="1668585252314" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6557" width="18" height="18">
|
||||
<path d="M828.1 896.7H199.9c-73.2 0-132.8-59.6-132.8-132.8V514.5c0-17.7 14.3-32 32-32s32 14.3 32 32v249.4c0 37.9 30.9 68.8 68.8 68.8h628.2c37.9 0 68.8-30.9 68.8-68.8V514.5c0-17.7 14.3-32 32-32s32 14.3 32 32v249.4c0 73.2-59.6 132.8-132.8 132.8z" p-id="6558"></path>
|
||||
<path d="M512.4 740.7c-17.7 0-32-14.3-32-32V151.8c0-17.7 14.3-32 32-32s32 14.3 32 32v556.9c0 17.7-14.3 32-32 32z" p-id="6559"></path>
|
||||
<path d="M518.5 737.4c-8.2 0-16.4-3.1-22.6-9.4-12.5-12.5-12.5-32.8 0-45.3l156.9-156.9c12.5-12.5 32.8-12.5 45.3 0s12.5 32.8 0 45.3L541.1 728c-6.2 6.3-14.4 9.4-22.6 9.4z" p-id="6560"></path>
|
||||
<path d="M506.2 737.2c-8.2 0-16.4-3.1-22.6-9.4L326.7 570.9c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0l156.9 156.9c12.5 12.5 12.5 32.8 0 45.3-6.3 6.3-14.5 9.4-22.7 9.4z" p-id="6561"></path>
|
||||
</svg>
|
||||
</html:button>
|
||||
<span class="tooltiptext">Save As Image</span>
|
||||
</div>
|
||||
<div class="tooltip">
|
||||
<html:button id="outline-saveFreeMind" class="tool-button">
|
||||
<svg t="1668587671777" class="icon" viewBox="0 0 1025 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1778" width="18" height="18">
|
||||
<path d="M903.526 542.788c-11.476-109.267-69.113-209.266-157.91-273.966 1.775-42.203-8.091-85.392-30.916-124.607C651.078 34.909 510.893-2.126 401.587 61.495c-75.837 44.141-116.869 125.136-113.649 207.104-69.606 50.492-123.007 124.277-147.063 213.792a391.809 391.809 0 0 0-11.196 59.809c-37.458 19.563-69.947 49.713-92.506 89.106-62.851 109.751-24.832 249.672 84.919 312.523 76.13 43.597 166.769 38.645 236.139-5.102a391.907 391.907 0 0 0 57.362 20.29c89.516 24.056 180.13 14.833 258.713-20.088 35.68 22.675 78.051 35.746 123.464 35.587 126.472-0.444 228.638-103.331 228.194-229.803-0.31-87.709-49.894-163.714-122.438-201.925zM343.497 234.956c7.264-51.226 37.068-98.744 85.254-126.791 83.531-48.619 190.659-20.317 239.278 63.214 11.79 20.256 19.044 41.901 22.096 63.762a175.057 175.057 0 0 1-2.102 60.33c-9.855 46.729-38.683 89.27-83.209 115.186-83.531 48.619-190.659 20.317-239.278-63.214-9.688-16.645-16.312-34.228-20.063-52.097a174.622 174.622 0 0 1-1.976-60.39z m9.101 640.606a174.59 174.59 0 0 1-51.354 31.888c-47.97 19.273-103.973 17.204-152.318-10.482-83.87-48.03-112.925-154.957-64.894-238.828 11.641-20.328 26.749-37.424 44.142-50.994a175.094 175.094 0 0 1 53.336-28.371c45.394-14.826 96.646-11.13 141.349 14.471 83.871 48.031 112.925 154.957 64.894 238.828-9.586 16.742-21.526 31.292-35.155 43.488z m77.009 31.305a339.803 339.803 0 0 1-24.38-7.545 229.776 229.776 0 0 0 29.388-40.412c62.851-109.751 24.832-249.672-84.919-312.523-51.188-29.314-108.937-36.677-162.371-25.063a339.779 339.779 0 0 1 5.7-24.919c18.21-67.764 55.959-125.048 105.538-167.454a229.77 229.77 0 0 0 20.304 45.656c63.621 109.306 203.806 146.341 313.112 82.72 51.001-29.685 86.26-76.04 102.911-128.148 57.693 49.633 96.988 117.799 111.025 192.597a229.761 229.761 0 0 0-49.756-5.258c-126.472 0.444-228.638 103.331-228.194 229.803 0.207 58.987 22.705 112.68 59.48 153.148-61.556 21.657-130.06 25.612-197.838 7.398z m367.972 13.649c-23.467 0.082-45.866-4.47-66.346-12.777a175.093 175.093 0 0 1-51.198-32.023c-35.495-31.897-57.89-78.104-58.07-129.584-0.34-96.649 77.735-175.274 174.384-175.614 19.296-0.068 37.87 2.998 55.249 8.705a174.572 174.572 0 0 1 53.254 28.504c40.693 31.906 66.915 81.451 67.111 137.175 0.34 96.649-77.735 175.274-174.384 175.614z" p-id="1779"></path>
|
||||
</svg>
|
||||
</html:button>
|
||||
<span class="tooltiptext">Save As FreeMind</span>
|
||||
</div>
|
||||
</div>
|
||||
</html:div>
|
||||
</vbox>
|
||||
<splitter id="outline-splitter" collapse="before">
|
||||
<grippy></grippy>
|
||||
</splitter>
|
||||
<zoteronoteeditor id="zotero-note-editor-main" flex="1" width="700" onerror="return;onError()" />
|
||||
<splitter id="preview-splitter" collapse="after" state="collapsed">
|
||||
<grippy></grippy>
|
||||
</splitter>
|
||||
<zoteronoteeditor id="zotero-note-editor-preview" flex="1" width="500" onerror="return;onError()" />
|
||||
</hbox>
|
||||
|
||||
</window>
|
||||
|
|
@ -1,22 +1,88 @@
|
|||
library.workspace=Workspace
|
||||
library.openWorkspace=Open Workspace
|
||||
library.newMainNote=New Main Note
|
||||
library.newMainNote.confirmHead=Will create a new note under collection
|
||||
library.newMainNote.confirmTail=and set it the main note. Continue?
|
||||
library.importMD=Import MarkDown as Note
|
||||
editor.mainNote=Main Note
|
||||
editor.recentMainNotes=Set Main Note from Recent
|
||||
editor.refreshEditor=Refresh Editor
|
||||
editor.previewInWorkspace=Preview in Workspace
|
||||
editor.copyNoteLink=Copy Note Link
|
||||
editor.copyNoteLinkOfLine=Copy Note Link of Current Line
|
||||
editor.importMD=Import from MarkDown
|
||||
editor.resizeImage=Resize Image
|
||||
editor.resizeImage.prompt=Enter new width (px):
|
||||
editor.previewImage=Preview Image
|
||||
editor.insertAnchor=Insert Position Preview
|
||||
editor.endOfNote=End of Note
|
||||
menu.insertTextTemplate=Insert Note Template [Text]
|
||||
menu.insertItemTemplate=Insert Note Template [Item]
|
||||
export.withLinkedNotes.confirm=Export linked notes to markdown files?
|
||||
export.withYAML.confirm=With YAML Front Matter?
|
||||
pref.title=BNotes
|
||||
|
||||
menuItem.setMainNote=As Workspace Note
|
||||
menuItem.exportNote=Export Note
|
||||
|
||||
menuEdit.templatePicker=Insert Template to Workspace Note
|
||||
menuEdit.templateEditor=Template Editor
|
||||
|
||||
menuTools.syncManager=Sync Manager
|
||||
|
||||
templateEditor.title=Template Editor
|
||||
templateEditor.templateName=Template Name
|
||||
templateEditor.previewContainer=Preview
|
||||
templateEditor.create=New
|
||||
templateEditor.import=Import Note
|
||||
templateEditor.more=More Templates
|
||||
templateEditor.save=Save
|
||||
templateEditor.delete=Delete
|
||||
templateEditor.reset=Reset
|
||||
templateEditor.help=Help
|
||||
|
||||
tab.name=Note Workspace
|
||||
|
||||
export.title=Export Notes
|
||||
export.options.linkMode=Linked Notes Mode
|
||||
export.options.MD=MarkDown(.md)
|
||||
export.options.Docx=MS Word(.docx)
|
||||
export.options.PDF=PDF(.pdf)
|
||||
export.options.mm=Mind Map
|
||||
export.options.note=Zotero Note
|
||||
export.embedLink=All Embeded in One Export
|
||||
export.standaloneLink=Each Converted to Standalone Exports
|
||||
export.keepLink=Keep Zotero Links(zotero://note/)
|
||||
export.exportMD=Export MD File(s)
|
||||
export.setAutoSync=Set Auto-Sync
|
||||
export.withYAMLHeader=With YAML Header
|
||||
export.exportDocx=Export Docx File
|
||||
export.exportPDF=Export PDF File
|
||||
export.exportFreeMind=Export FreeMind File
|
||||
export.exportNote=Export to New Zotero Note Item
|
||||
export.confirm=Export
|
||||
export.cancel=Close
|
||||
export.target=Target
|
||||
|
||||
syncManager.title=Sync Manager
|
||||
syncManager.refresh=Refresh
|
||||
syncManager.sync=Sync
|
||||
syncManager.syncDetail=Check and sync immedietly
|
||||
syncManager.unSync=UnSync
|
||||
syncManager.unSyncDetail=Remove selected notes from syncing list
|
||||
syncManager.noteName=Note Name
|
||||
syncManager.lastSync=Last Sync
|
||||
syncManager.noteStatus=MarkDown Path
|
||||
|
||||
syncInfo.syncTo=MarkDown Path
|
||||
syncInfo.lastSync=Last Sync
|
||||
syncInfo.sync=Sync
|
||||
syncInfo.unSync=UnSync
|
||||
syncInfo.reveal=Show in Folder
|
||||
syncInfo.manager=Sync Manager
|
||||
syncInfo.export=Export As...
|
||||
syncInfo.cancel=Close
|
||||
|
||||
fileInterface.sync=Sync to
|
||||
|
||||
sync.start.hint=Note Auto-Sync is enabled every
|
||||
sync.stop.hint=Note Auto-Sync is disabled
|
||||
sync.running.hint.title=Note Syncing
|
||||
sync.running.hint.check=Check Status
|
||||
sync.running.hint.updateMD=Update MarkDown
|
||||
sync.running.hint.updateNote=Update Note
|
||||
sync.running.hint.diff=Confirm Merge
|
||||
sync.running.hint.finish=Finish
|
||||
sync.running.hint.synced=Synced
|
||||
sync.running.hint.upToDate=Up To Date
|
||||
|
||||
workspace.notesPane.hint=PDF NotePane is not accesible if no PDF files are opened.
|
||||
workspace.switchOutline=Swith Outline Mode
|
||||
workspace.saveOutlineImage=Save Image
|
||||
workspace.saveOutlineFreeMind=Save MindMap
|
||||
|
||||
editor.toolbar.main=Workspace Note
|
||||
editor.toolbar.settings.title=Workspace Settings
|
||||
editor.toolbar.settings.openWorkspace=Open Note Workspace
|
||||
editor.toolbar.settings.setWorkspace=Set as Workspace Note
|
||||
editor.toolbar.settings.insertTemplate=Insert Template to Cursor Line
|
||||
editor.toolbar.settings.copyLink=Copy Note Link
|
||||
editor.toolbar.export.title=Export Note...
|
||||
|
|
|
|||
|
|
@ -1,112 +1,12 @@
|
|||
<!ENTITY zotero.__addonRef__.workspace.title "Workspace">
|
||||
<!ENTITY zotero.__addonRef__.pref.workspace.title "Workspace">
|
||||
<!ENTITY zotero.__addonRef__.pref.workspace.expandLevel.label "Outline expand to heading level">
|
||||
<!ENTITY zotero.__addonRef__.pref.sync.title "Sync">
|
||||
<!ENTITY zotero.__addonRef__.pref.sync.period.label "Auto-sync period (seconds)">
|
||||
<!ENTITY zotero.__addonRef__.pref.sync.manager.label "Open Sync Manager">
|
||||
<!ENTITY zotero.__addonRef__.pref.template.title "Template">
|
||||
<!ENTITY zotero.__addonRef__.pref.template.editor.label "Open Template Editor">
|
||||
<!ENTITY zotero.__addonRef__.pref.annotation.title "PDF Annotation">
|
||||
<!ENTITY zotero.__addonRef__.pref.annotation.autoAnnotation.label "Automatically add new annotations to workspace note">
|
||||
|
||||
<!ENTITY zotero.__addonRef__.workspace.menu.new "Create New Main Note">
|
||||
<!ENTITY zotero.__addonRef__.workspace.menu.open "Open Main Note...">
|
||||
<!ENTITY zotero.__addonRef__.workspace.menu.openWindow "Open Workspace in Standalone Window">
|
||||
<!ENTITY zotero.__addonRef__.workspace.menu.export "Export Main Note...">
|
||||
<!ENTITY zotero.__addonRef__.workspace.menu.sync "Sync Main Note">
|
||||
<!ENTITY zotero.__addonRef__.workspace.menu.syncmanager "Better Notes Sync Manager">
|
||||
<!ENTITY zotero.__addonRef__.workspace.menu.insertNotes "Insert Notes...">
|
||||
<!ENTITY zotero.__addonRef__.workspace.menu.insertTextTemplate "Insert Note Template [Text]">
|
||||
<!ENTITY zotero.__addonRef__.workspace.menu.insertItemTemplate "Insert Note Template [Item]">
|
||||
<!ENTITY zotero.__addonRef__.workspace.menu.editTemplate "Note Template Editor">
|
||||
<!ENTITY zotero.__addonRef__.workspace.menu.citeSetting "Citation Style in Notes">
|
||||
<!ENTITY zotero.__addonRef__.workspace.menu.OCRSetting "Annotation Math OCR">
|
||||
<!ENTITY zotero.__addonRef__.workspace.menu.OCRBing "Bing">
|
||||
<!ENTITY zotero.__addonRef__.workspace.menu.OCRMathpix "Mathpix">
|
||||
<!ENTITY zotero.__addonRef__.workspace.menu.OCRXunfei "Xunfei">
|
||||
<!ENTITY zotero.__addonRef__.workspace.menu.addheading "Insert Heading...">
|
||||
<!ENTITY zotero.__addonRef__.workspace.menu.indent "Decrease Heading Level">
|
||||
<!ENTITY zotero.__addonRef__.workspace.menu.unindent "Increase Heading Level">
|
||||
<!ENTITY zotero.__addonRef__.workspace.menu.importLink "Import Selected Note Link Content">
|
||||
<!ENTITY zotero.__addonRef__.workspace.menu.updateLink "Update Selected Note Link Text">
|
||||
<!ENTITY zotero.__addonRef__.workspace.menu.autoannotation "Auto Insert New Annotations to Note">
|
||||
<!ENTITY zotero.__addonRef__.workspace.menu.convertmd "Convert MarkDown in Clipboard">
|
||||
<!ENTITY zotero.__addonRef__.workspace.menu.convertasciidoc "Convert AsciiDoc in Clipboard">
|
||||
<!ENTITY zotero.__addonRef__.workspace.menu.treeview "Outline: Tree View">
|
||||
<!ENTITY zotero.__addonRef__.workspace.menu.mindmap "Outline: Mind Map">
|
||||
<!ENTITY zotero.__addonRef__.workspace.menu.bubblemap "Outline: Bubble Map">
|
||||
<!ENTITY zotero.__addonRef__.workspace.menu.guide "Better Notes User Guide">
|
||||
<!ENTITY zotero.__addonRef__.workspace.menu.about "About Better Notes">
|
||||
|
||||
<!ENTITY zotero.__addonRef__.itemmenu.setMainNote.label "Set Main Note">
|
||||
<!ENTITY zotero.__addonRef__.itemmenu.exportNote.label "Export Note and SubNotes">
|
||||
<!ENTITY zotero.__addonRef__.itemmenu.exportNotes.label "Export Notes and SubNotes to Markdown">
|
||||
|
||||
<!ENTITY zotero.__addonRef__.export.title "Export">
|
||||
<!ENTITY zotero.__addonRef__.export.cannotworkwith.label "Cannot work with ">
|
||||
<!ENTITY zotero.__addonRef__.export.workwith.label "Only work with ">
|
||||
<!ENTITY zotero.__addonRef__.export.option.label "General">
|
||||
<!ENTITY zotero.__addonRef__.export.link.enable.label "Embed Linked Notes">
|
||||
<!ENTITY zotero.__addonRef__.export.note.enable.label "Export to new note">
|
||||
<!ENTITY zotero.__addonRef__.export.markdown.label "MarkDown">
|
||||
<!ENTITY zotero.__addonRef__.export.file.enable.label "Export to MarkDown File">
|
||||
<!ENTITY zotero.__addonRef__.export.singlefile.enable.label "Export Linked Notes to MarkDown File">
|
||||
<!ENTITY zotero.__addonRef__.export.enableautosync.enable.label "Auto Sync to Export Path">
|
||||
<!ENTITY zotero.__addonRef__.export.yaml.enable.label "YAML Front Matter">
|
||||
<!ENTITY zotero.__addonRef__.export.document.label "Document">
|
||||
<!ENTITY zotero.__addonRef__.export.docx.enable.label "Export to MS Word Document">
|
||||
<!ENTITY zotero.__addonRef__.export.pdf.enable.label "Export to PDF Document">
|
||||
<!ENTITY zotero.__addonRef__.export.mindmap.label "Mind Map">
|
||||
<!ENTITY zotero.__addonRef__.export.freemind.enable.label "FreeMind">
|
||||
|
||||
<!ENTITY zotero.__addonRef__.sync.title "Sync Status">
|
||||
<!ENTITY zotero.__addonRef__.sync.export.label "Export to...">
|
||||
<!ENTITY zotero.__addonRef__.sync.path.label "Sync to:">
|
||||
<!ENTITY zotero.__addonRef__.sync.changesync.label "Change Folder">
|
||||
<!ENTITY zotero.__addonRef__.sync.lastsync.label "Last sync:">
|
||||
<!ENTITY zotero.__addonRef__.sync.enable.label "Keep in sync">
|
||||
<!ENTITY zotero.__addonRef__.sync.dosync.label "Sync Now">
|
||||
<!ENTITY zotero.__addonRef__.sync.synclist.label "Sync Manager">
|
||||
|
||||
<!ENTITY zotero.__addonRef__.syncList.title "Sync Manager">
|
||||
<!ENTITY zotero.__addonRef__.syncList.notename.label "Name">
|
||||
<!ENTITY zotero.__addonRef__.syncList.syncpath.label "Sync To">
|
||||
<!ENTITY zotero.__addonRef__.syncList.lastsync.label "Last Sync">
|
||||
<!ENTITY zotero.__addonRef__.syncList.doupdate.label "Refresh">
|
||||
<!ENTITY zotero.__addonRef__.syncList.changesyncperiod.label "Sync Period:">
|
||||
<!ENTITY zotero.__addonRef__.syncList.dosync.label "Sync">
|
||||
<!ENTITY zotero.__addonRef__.syncList.changesync.label "Change Folder">
|
||||
<!ENTITY zotero.__addonRef__.syncList.removesync.label "Unsync">
|
||||
<!ENTITY zotero.__addonRef__.syncList.related.label "Apply to Related Notes">
|
||||
|
||||
<!ENTITY zotero.__addonRef__.wizard.title "Welcomed to Zotero Better Notes">
|
||||
<!ENTITY zotero.__addonRef__.wizard.page1.header "Zotero Better Notes User Guide">
|
||||
<!ENTITY zotero.__addonRef__.wizard.page1.description "Click Next to Continue.">
|
||||
<!ENTITY zotero.__addonRef__.wizard.open "Knowledge Workspace">
|
||||
<!ENTITY zotero.__addonRef__.wizard.page3.header "Knowledge Workspace">
|
||||
<!ENTITY zotero.__addonRef__.wizard.page3.description "Click the link above 'My Library' or the home button in the note editor to open or focus the workspace.">
|
||||
<!ENTITY zotero.__addonRef__.wizard.layout "Knowledge Workspace Layout">
|
||||
<!ENTITY zotero.__addonRef__.wizard.page4.header "Outline/Main Note/Linked Note Preview">
|
||||
<!ENTITY zotero.__addonRef__.wizard.page4.description1 "Modify/jump to headings inside main Note with the Outline;">
|
||||
<!ENTITY zotero.__addonRef__.wizard.page4.description2 "Add links to the main note while reading. Export to MarkDown/HTML with full text supported;">
|
||||
<!ENTITY zotero.__addonRef__.wizard.page4.description3 "Open and edit linked notes in the preview.">
|
||||
<!ENTITY zotero.__addonRef__.wizard.link "Add Note Link">
|
||||
<!ENTITY zotero.__addonRef__.wizard.page5.header "Note as you go, and open the links as your will.">
|
||||
<!ENTITY zotero.__addonRef__.wizard.page5.description1 "Click to insert the current note link after the current line in main note.">
|
||||
<!ENTITY zotero.__addonRef__.wizard.page5.description2 "Jump to notes even from other Apps via a note link!">
|
||||
<!ENTITY zotero.__addonRef__.wizard.setup "Setup Workspace">
|
||||
<!ENTITY zotero.__addonRef__.wizard.page6.header "Almost there. Please follow the user guide before starting.">
|
||||
<!ENTITY zotero.__addonRef__.wizard.page6.description1 "You can come back later from the knowledge workspace.">
|
||||
<!ENTITY zotero.__addonRef__.wizard.page6.description2 "WARNING If main Note is not set, this addon will not work properly.">
|
||||
|
||||
<!ENTITY zotero.__addonRef__.wizard.setup.enable.label "Enable Auto Setup">
|
||||
<!ENTITY zotero.__addonRef__.wizard.setup.collectionenable.label "Create a new collection for main notes:">
|
||||
<!ENTITY zotero.__addonRef__.wizard.setup.noteenable.label "Create a 'Zotero Better Notes User Guide: Workflow' note">
|
||||
|
||||
<!ENTITY zotero.__addonRef__.template.title "Template Editor">
|
||||
<!ENTITY zotero.__addonRef__.template.header.label "Template Name">
|
||||
<!ENTITY zotero.__addonRef__.template.list.label "Template List">
|
||||
<!ENTITY zotero.__addonRef__.template.create.label "Create">
|
||||
<!ENTITY zotero.__addonRef__.template.import.label "Create from Note">
|
||||
<!ENTITY zotero.__addonRef__.template.save.label "Save">
|
||||
<!ENTITY zotero.__addonRef__.template.delete.label "Delete">
|
||||
<!ENTITY zotero.__addonRef__.template.reset.label "Reset">
|
||||
<!ENTITY zotero.__addonRef__.template.more.label "Get More">
|
||||
|
||||
<!ENTITY zotero.__addonRef__.help.feedback.caption.label "User Guide and Feedback">
|
||||
<!ENTITY zotero.__addonRef__.help.feedback.label "GitHub">
|
||||
<!ENTITY zotero.__addonRef__.help.doc.caption.label "Documentations">
|
||||
<!ENTITY zotero.__addonRef__.help.doc.label "Better Notes Handbook(Chinese, provide translation)">
|
||||
<!ENTITY zotero.__addonRef__.help.version.label "VERSION __buildVersion__">
|
||||
<!ENTITY zotero.__addonRef__.help.version.label "__addonName__ VERSION __buildVersion__">
|
||||
<!ENTITY zotero.__addonRef__.help.releasetime.label "Build __buildTime__">
|
||||
|
|
@ -1,22 +1,88 @@
|
|||
library.workspace=工作区
|
||||
library.openWorkspace=打开工作区
|
||||
library.newMainNote=新建笔记并设为主笔记
|
||||
library.newMainNote.confirmHead=将在当前分类
|
||||
library.newMainNote.confirmTail=创建新笔记并将它设为主笔记。确认继续吗?
|
||||
library.importMD=导入Markdown为笔记
|
||||
editor.mainNote=主笔记
|
||||
editor.recentMainNotes=从最近笔记设置主笔记
|
||||
editor.refreshEditor=刷新编辑器
|
||||
editor.previewInWorkspace=在工作区中预览
|
||||
editor.copyNoteLink=复制笔记链接
|
||||
editor.copyNoteLinkOfLine=复制当前行的笔记链接
|
||||
editor.importMD=从Markdown导入
|
||||
editor.resizeImage=调整图片大小
|
||||
editor.resizeImage.prompt=输入新的宽度(单位:像素):
|
||||
editor.previewImage=预览图片
|
||||
editor.insertAnchor=插入位置预览
|
||||
editor.endOfNote=笔记结尾
|
||||
menu.insertTextTemplate=插入笔记模板[文本/Text]
|
||||
menu.insertItemTemplate=插入笔记模板[条目/Item]
|
||||
export.withLinkedNotes.confirm=导出链接的子笔记到Markdown?
|
||||
export.withYAML.confirm=导出带有YAML Front Matter?
|
||||
pref.title=BNotes
|
||||
|
||||
menuItem.setMainNote=设为工作区主笔记
|
||||
menuItem.exportNote=导出笔记
|
||||
|
||||
menuEdit.templatePicker=插入模板到工作区笔记
|
||||
menuEdit.templateEditor=模板编辑器
|
||||
|
||||
menuTools.syncManager=同步管理器
|
||||
|
||||
templateEditor.title=模板编辑器
|
||||
templateEditor.templateName=模板名称
|
||||
templateEditor.previewContainer=预览
|
||||
templateEditor.create=新建
|
||||
templateEditor.import=导入笔记
|
||||
templateEditor.more=更多模板
|
||||
templateEditor.save=保存
|
||||
templateEditor.delete=删除
|
||||
templateEditor.reset=重置
|
||||
templateEditor.help=帮助
|
||||
|
||||
tab.name=笔记工作区
|
||||
|
||||
export.title=导出笔记
|
||||
export.options.linkMode=链接笔记模式
|
||||
export.options.MD=MarkDown(.md)
|
||||
export.options.Docx=MS Word(.docx)
|
||||
export.options.PDF=PDF(.pdf)
|
||||
export.options.mm=思维导图
|
||||
export.options.note=Zotero笔记
|
||||
export.embedLink=全部嵌入为一个导出
|
||||
export.standaloneLink=分别单独导出
|
||||
export.keepLink=保留Zotero链接(zotero://note/)
|
||||
export.exportMD=导出MD文件
|
||||
export.setAutoSync=设置自动同步
|
||||
export.withYAMLHeader=带有YAML头
|
||||
export.exportDocx=导出Word文件
|
||||
export.exportPDF=导出PDF文件
|
||||
export.exportFreeMind=导出FreeMind文件
|
||||
export.exportNote=导出为Zotero笔记条目
|
||||
export.confirm=导出
|
||||
export.cancel=关闭
|
||||
export.target=目标
|
||||
|
||||
syncManager.title=同步管理
|
||||
syncManager.refresh=刷新
|
||||
syncManager.sync=同步
|
||||
syncManager.syncDetail=检查并立即同步
|
||||
syncManager.unSync=取消同步
|
||||
syncManager.unSyncDetail=从同步列表中移除选中的笔记
|
||||
syncManager.noteName=笔记名称
|
||||
syncManager.lastSync=最近同步
|
||||
syncManager.filePath=MarkDown路径
|
||||
|
||||
syncInfo.syncTo=MarkDown路径
|
||||
syncInfo.lastSync=最近同步
|
||||
syncInfo.sync=同步
|
||||
syncInfo.unSync=取消同步
|
||||
syncInfo.reveal=在文件夹中显示
|
||||
syncInfo.manager=同步管理
|
||||
syncInfo.export=导出为...
|
||||
syncInfo.cancel=关闭
|
||||
|
||||
sync.start.hint=自动同步已启用, 间隔
|
||||
sync.stop.hint=自动同步已停止
|
||||
sync.running.hint.title=笔记同步
|
||||
sync.running.hint.check=检查状态
|
||||
sync.running.hint.updateMD=更新MarkDown
|
||||
sync.running.hint.updateNote=更新笔记
|
||||
sync.running.hint.diff=确认合并
|
||||
sync.running.hint.finish=同步完成
|
||||
sync.running.hint.synced=已同步
|
||||
sync.running.hint.upToDate=已最新
|
||||
|
||||
fileInterface.sync=同步到
|
||||
|
||||
workspace.notesPane.hint=PDF笔记侧栏在没有PDF文件打开时不可访问.
|
||||
workspace.switchOutline=切换大纲模式
|
||||
workspace.saveOutlineImage=保存图片
|
||||
workspace.saveOutlineFreeMind=保存思维导图
|
||||
|
||||
editor.toolbar.main=工作区笔记
|
||||
editor.toolbar.settings.title=工作区选项
|
||||
editor.toolbar.settings.openWorkspace=打开笔记工作区
|
||||
editor.toolbar.settings.setWorkspace=设为工作区笔记
|
||||
editor.toolbar.settings.insertTemplate=插入模板到光标行
|
||||
editor.toolbar.settings.copyLink=复制笔记链接
|
||||
editor.toolbar.export.title=导出笔记...
|
||||
|
|
|
|||
|
|
@ -1,112 +1,12 @@
|
|||
<!ENTITY zotero.__addonRef__.workspace.title "工作区">
|
||||
<!ENTITY zotero.__addonRef__.pref.workspace.title "工作区">
|
||||
<!ENTITY zotero.__addonRef__.pref.workspace.expandLevel.label "大纲展开至标题层级">
|
||||
<!ENTITY zotero.__addonRef__.pref.sync.title "同步">
|
||||
<!ENTITY zotero.__addonRef__.pref.sync.period.label "自动同步周期 (秒)">
|
||||
<!ENTITY zotero.__addonRef__.pref.sync.manager.label "打开同步管理器">
|
||||
<!ENTITY zotero.__addonRef__.pref.template.title "模板">
|
||||
<!ENTITY zotero.__addonRef__.pref.template.editor.label "打开模板编辑器">
|
||||
<!ENTITY zotero.__addonRef__.pref.annotation.title "PDF批注">
|
||||
<!ENTITY zotero.__addonRef__.pref.annotation.autoAnnotation.label "自动添加新批注到工作区笔记">
|
||||
|
||||
<!ENTITY zotero.__addonRef__.workspace.menu.new "创建新主笔记">
|
||||
<!ENTITY zotero.__addonRef__.workspace.menu.open "打开主笔记...">
|
||||
<!ENTITY zotero.__addonRef__.workspace.menu.openWindow "在独立窗口中打开工作区">
|
||||
<!ENTITY zotero.__addonRef__.workspace.menu.export "导出主笔记...">
|
||||
<!ENTITY zotero.__addonRef__.workspace.menu.sync "同步主笔记">
|
||||
<!ENTITY zotero.__addonRef__.workspace.menu.syncmanager "Better Notes同步管理">
|
||||
<!ENTITY zotero.__addonRef__.workspace.menu.insertNotes "插入多条笔记...">
|
||||
<!ENTITY zotero.__addonRef__.workspace.menu.insertTextTemplate "插入笔记模板[文本/Text]">
|
||||
<!ENTITY zotero.__addonRef__.workspace.menu.insertItemTemplate "插入笔记模板[条目/Item]">
|
||||
<!ENTITY zotero.__addonRef__.workspace.menu.editTemplate "笔记模板编辑器">
|
||||
<!ENTITY zotero.__addonRef__.workspace.menu.citeSetting "笔记引用格式">
|
||||
<!ENTITY zotero.__addonRef__.workspace.menu.OCRSetting "注释公式识别">
|
||||
<!ENTITY zotero.__addonRef__.workspace.menu.OCRBing "Bing">
|
||||
<!ENTITY zotero.__addonRef__.workspace.menu.OCRMathpix "Mathpix">
|
||||
<!ENTITY zotero.__addonRef__.workspace.menu.OCRXunfei "讯飞">
|
||||
<!ENTITY zotero.__addonRef__.workspace.menu.addheading "插入标题...">
|
||||
<!ENTITY zotero.__addonRef__.workspace.menu.indent "降低标题层级">
|
||||
<!ENTITY zotero.__addonRef__.workspace.menu.unindent "提升标题层级">
|
||||
<!ENTITY zotero.__addonRef__.workspace.menu.importLink "导入选中的笔记链接内容">
|
||||
<!ENTITY zotero.__addonRef__.workspace.menu.updateLink "更新选中的笔记链接文本">
|
||||
<!ENTITY zotero.__addonRef__.workspace.menu.autoannotation "自动插入新注释到笔记">
|
||||
<!ENTITY zotero.__addonRef__.workspace.menu.convertmd "转换剪贴板中的MarkDown内容">
|
||||
<!ENTITY zotero.__addonRef__.workspace.menu.convertasciidoc "转换剪贴板中的AsciiDoc内容">
|
||||
<!ENTITY zotero.__addonRef__.workspace.menu.treeview "大纲: 树视图">
|
||||
<!ENTITY zotero.__addonRef__.workspace.menu.mindmap "大纲: 思维导图">
|
||||
<!ENTITY zotero.__addonRef__.workspace.menu.bubblemap "大纲: 气泡导图">
|
||||
<!ENTITY zotero.__addonRef__.workspace.menu.guide "Better Notes用户指引">
|
||||
<!ENTITY zotero.__addonRef__.workspace.menu.about "关于Better Notes">
|
||||
|
||||
<!ENTITY zotero.__addonRef__.itemmenu.setMainNote.label "设置为主笔记">
|
||||
<!ENTITY zotero.__addonRef__.itemmenu.exportNote.label "导出笔记及条目子笔记">
|
||||
<!ENTITY zotero.__addonRef__.itemmenu.exportNotes.label "导出笔记及条目子笔记为Markdown">
|
||||
|
||||
<!ENTITY zotero.__addonRef__.export.title "导出笔记">
|
||||
<!ENTITY zotero.__addonRef__.export.cannotworkwith.label "无法一起使用:">
|
||||
<!ENTITY zotero.__addonRef__.export.workwith.label "只在此时有效:">
|
||||
<!ENTITY zotero.__addonRef__.export.option.label "通用">
|
||||
<!ENTITY zotero.__addonRef__.export.link.enable.label "嵌入链接的子笔记">
|
||||
<!ENTITY zotero.__addonRef__.export.note.enable.label "导出到新笔记">
|
||||
<!ENTITY zotero.__addonRef__.export.markdown.label "MarkDown">
|
||||
<!ENTITY zotero.__addonRef__.export.file.enable.label "导出为MarkDown文件">
|
||||
<!ENTITY zotero.__addonRef__.export.singlefile.enable.label "导出链接的子笔记为MarkDown文件">
|
||||
<!ENTITY zotero.__addonRef__.export.enableautosync.enable.label "修改时自动同步到导出路径">
|
||||
<!ENTITY zotero.__addonRef__.export.yaml.enable.label "YAML Front Matter">
|
||||
<!ENTITY zotero.__addonRef__.export.document.label "文档">
|
||||
<!ENTITY zotero.__addonRef__.export.docx.enable.label "导出到Word文档">
|
||||
<!ENTITY zotero.__addonRef__.export.pdf.enable.label "导出到PDF文档">
|
||||
<!ENTITY zotero.__addonRef__.export.mindmap.label "思维导图">
|
||||
<!ENTITY zotero.__addonRef__.export.freemind.enable.label "FreeMind">
|
||||
|
||||
<!ENTITY zotero.__addonRef__.sync.title "同步状态">
|
||||
<!ENTITY zotero.__addonRef__.sync.export.label "导出为...">
|
||||
<!ENTITY zotero.__addonRef__.sync.path.label "同步到:">
|
||||
<!ENTITY zotero.__addonRef__.sync.changesync.label "修改路径">
|
||||
<!ENTITY zotero.__addonRef__.sync.lastsync.label "最近同步:">
|
||||
<!ENTITY zotero.__addonRef__.sync.enable.label "保持同步">
|
||||
<!ENTITY zotero.__addonRef__.sync.dosync.label "立刻同步">
|
||||
<!ENTITY zotero.__addonRef__.sync.synclist.label "同步管理">
|
||||
|
||||
<!ENTITY zotero.__addonRef__.syncList.title "同步管理">
|
||||
<!ENTITY zotero.__addonRef__.syncList.notename.label "名称">
|
||||
<!ENTITY zotero.__addonRef__.syncList.syncpath.label "同步到">
|
||||
<!ENTITY zotero.__addonRef__.syncList.lastsync.label "最近同步">
|
||||
<!ENTITY zotero.__addonRef__.syncList.doupdate.label "刷新">
|
||||
<!ENTITY zotero.__addonRef__.syncList.changesyncperiod.label "同步周期:">
|
||||
<!ENTITY zotero.__addonRef__.syncList.dosync.label "同步">
|
||||
<!ENTITY zotero.__addonRef__.syncList.changesync.label "修改路径">
|
||||
<!ENTITY zotero.__addonRef__.syncList.removesync.label "取消同步">
|
||||
<!ENTITY zotero.__addonRef__.syncList.related.label "应用于关联笔记">
|
||||
|
||||
<!ENTITY zotero.__addonRef__.wizard.title "欢迎使用 Zotero Better Notes">
|
||||
<!ENTITY zotero.__addonRef__.wizard.page1.header "Zotero Better Notes 用户指引">
|
||||
<!ENTITY zotero.__addonRef__.wizard.page1.description "单击下一步以继续。">
|
||||
<!ENTITY zotero.__addonRef__.wizard.open "Knowledge工作区">
|
||||
<!ENTITY zotero.__addonRef__.wizard.page3.header "Knowledge工作区">
|
||||
<!ENTITY zotero.__addonRef__.wizard.page3.description "单击“我的文库”上方链接或笔记编辑器中的“主页”按钮,来打开或聚焦工作区。">
|
||||
<!ENTITY zotero.__addonRef__.wizard.layout "Knowledge工作区布局">
|
||||
<!ENTITY zotero.__addonRef__.wizard.page4.header "大纲/主笔记/链接笔记预览">
|
||||
<!ENTITY zotero.__addonRef__.wizard.page4.description1 "用大纲修改/跳转主笔记的标题;">
|
||||
<!ENTITY zotero.__addonRef__.wizard.page4.description2 "在阅读时可以从其他笔记添加链接到主笔记。支持导出包含链接全文的MarkDown/HTML。">
|
||||
<!ENTITY zotero.__addonRef__.wizard.page4.description3 "在预览中打开并编辑链接的笔记。">
|
||||
<!ENTITY zotero.__addonRef__.wizard.link "添加笔记链接">
|
||||
<!ENTITY zotero.__addonRef__.wizard.page5.header "随写随记,并在任何地方打开笔记链接。">
|
||||
<!ENTITY zotero.__addonRef__.wizard.page5.description1 "单击按钮,在主笔记中当前行插入当前笔记的链接。">
|
||||
<!ENTITY zotero.__addonRef__.wizard.page5.description2 "通过笔记链接,甚至可以从其他应用程序跳转到笔记!">
|
||||
<!ENTITY zotero.__addonRef__.wizard.setup "配置工作区">
|
||||
<!ENTITY zotero.__addonRef__.wizard.page6.header "即将完成。在开始之前,建议按照指引快速配置并上手。">
|
||||
<!ENTITY zotero.__addonRef__.wizard.page6.description1 "您可以稍后从工作区左侧'💡'返回这里。">
|
||||
<!ENTITY zotero.__addonRef__.wizard.page6.description2 "提示:如果未设置主笔记,此插件将无法正常工作。">
|
||||
|
||||
<!ENTITY zotero.__addonRef__.wizard.setup.enable.label "启用快速配置">
|
||||
<!ENTITY zotero.__addonRef__.wizard.setup.collectionenable.label "创建新的分类用来存放主笔记:">
|
||||
<!ENTITY zotero.__addonRef__.wizard.setup.noteenable.label "创建 'Zotero Better Notes 用户指引:工作流' 笔记">
|
||||
|
||||
<!ENTITY zotero.__addonRef__.template.title "模板编辑器">
|
||||
<!ENTITY zotero.__addonRef__.template.header.label "模板名称">
|
||||
<!ENTITY zotero.__addonRef__.template.list.label "模板列表">
|
||||
<!ENTITY zotero.__addonRef__.template.create.label "新建">
|
||||
<!ENTITY zotero.__addonRef__.template.import.label "从笔记新建...">
|
||||
<!ENTITY zotero.__addonRef__.template.save.label "保存">
|
||||
<!ENTITY zotero.__addonRef__.template.delete.label "删除">
|
||||
<!ENTITY zotero.__addonRef__.template.reset.label "重置">
|
||||
<!ENTITY zotero.__addonRef__.template.more.label "获取更多模板">
|
||||
|
||||
<!ENTITY zotero.__addonRef__.help.feedback.caption.label "用户指引和反馈">
|
||||
<!ENTITY zotero.__addonRef__.help.feedback.label "GitHub">
|
||||
<!ENTITY zotero.__addonRef__.help.doc.caption.label "使用文档">
|
||||
<!ENTITY zotero.__addonRef__.help.doc.label "Better Notes手册(中文)">
|
||||
<!ENTITY zotero.__addonRef__.help.version.label "VERSION __buildVersion__">
|
||||
<!ENTITY zotero.__addonRef__.help.version.label "__addonName__ 版本 __buildVersion__">
|
||||
<!ENTITY zotero.__addonRef__.help.releasetime.label "Build __buildTime__">
|
||||
|
Before Width: | Height: | Size: 293 KiB |
|
Before Width: | Height: | Size: 224 KiB |
|
Before Width: | Height: | Size: 166 KiB |
|
|
@ -1,26 +0,0 @@
|
|||
pref("extensions.zotero.Knowledge4Zotero.recentMainNoteIds", "");
|
||||
pref("extensions.zotero.Knowledge4Zotero.syncNoteIds", "");
|
||||
pref("extensions.zotero.Knowledge4Zotero.syncPeriod", 30000);
|
||||
pref("extensions.zotero.Knowledge4Zotero.autoAnnotation", false);
|
||||
pref("extensions.zotero.Knowledge4Zotero.exportMD", true);
|
||||
pref("extensions.zotero.Knowledge4Zotero.exportSubMD", false);
|
||||
pref("extensions.zotero.Knowledge4Zotero.exportAutoSync", false);
|
||||
pref("extensions.zotero.Knowledge4Zotero.exportYAMLHeader", true);
|
||||
pref("extensions.zotero.Knowledge4Zotero.embedLink", true);
|
||||
pref("extensions.zotero.Knowledge4Zotero.exportNote", false);
|
||||
pref("extensions.zotero.Knowledge4Zotero.exportDocx", false);
|
||||
pref("extensions.zotero.Knowledge4Zotero.exportPDF", false);
|
||||
pref("extensions.zotero.Knowledge4Zotero.exportFreeMind", false);
|
||||
pref("extensions.zotero.Knowledge4Zotero.OCREngine", "bing");
|
||||
pref("extensions.zotero.Knowledge4Zotero.OCRMathpix.Appid", "");
|
||||
pref("extensions.zotero.Knowledge4Zotero.OCRMathpix.Appkey", "");
|
||||
pref("extensions.zotero.Knowledge4Zotero.OCRXunfei.APPID", "");
|
||||
pref("extensions.zotero.Knowledge4Zotero.OCRMathpix.APISecret", "");
|
||||
pref("extensions.zotero.Knowledge4Zotero.OCRMathpix.APIKey", "");
|
||||
pref("extensions.zotero.Knowledge4Zotero.linkAction.click", "");
|
||||
pref("extensions.zotero.Knowledge4Zotero.linkAction.shiftclick", "");
|
||||
pref("extensions.zotero.Knowledge4Zotero.linkAction.ctrlclick", "");
|
||||
pref("extensions.zotero.Knowledge4Zotero.linkAction.altclick", "");
|
||||
pref("extensions.zotero.Knowledge4Zotero.linkAction.preview", true);
|
||||
pref("extensions.zotero.Knowledge4Zotero.imagePreview.ctrlclick", true);
|
||||
pref("extensions.zotero.Knowledge4Zotero.imagePreview.dblclick", true);
|
||||
|
|
@ -12,21 +12,22 @@
|
|||
em:creator="__author__"
|
||||
em:description="__description__"
|
||||
em:homepageURL="__homepage__"
|
||||
em:iconURL="chrome://__addonRef__/skin/favicon.png"
|
||||
em:updateURL="https://raw.githubusercontent.com/windingwind/zotero-better-notes/master/update.rdf">>
|
||||
<em:type>2</em:type>
|
||||
<em:targetApplication RDF:resource="rdf:#$x61SL3"/>
|
||||
em:iconURL="chrome://__addonRef__/content/icons/favicon.png"
|
||||
em:optionsURL="chrome://__addonRef__/content/preferences.xul"
|
||||
em:updateURL="__updaterdf__"
|
||||
em:multiprocessCompatible="true"
|
||||
em:bootstrap="true">
|
||||
<em:targetApplication>
|
||||
<Description>
|
||||
<em:id>zotero@chnm.gmu.edu</em:id>
|
||||
<em:minVersion>6.0.14-beta</em:minVersion>
|
||||
<em:minVersion>5.0</em:minVersion>
|
||||
<em:maxVersion>*</em:maxVersion>
|
||||
</Description>
|
||||
</em:targetApplication>
|
||||
<em:targetApplication>
|
||||
<Description>
|
||||
<em:id>juris-m@juris-m.github.io</em:id>
|
||||
<em:minVersion>6.0.14-beta</em:minVersion>
|
||||
<em:minVersion>5.0</em:minVersion>
|
||||
<em:maxVersion>*</em:maxVersion>
|
||||
</Description>
|
||||
</em:targetApplication>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"manifest_version": 2,
|
||||
"name": "__addonName__",
|
||||
"version": "__buildVersion__",
|
||||
"description": "__description__",
|
||||
"author": "__author__",
|
||||
"icons": {
|
||||
"48": "chrome/content/icons/favicon@0.5x.png",
|
||||
"96": "chrome/content/icons/favicon.png"
|
||||
},
|
||||
"applications": {
|
||||
"zotero": {
|
||||
"id": "__addonID__",
|
||||
"update_url": "__updaterdf__",
|
||||
"strict_min_version": "6.999",
|
||||
"strict_max_version": "7.0.*"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
pref("__prefsPrefix__.recentMainNoteIds", "");
|
||||
|
||||
pref("__prefsPrefix__.syncNoteIds", "");
|
||||
pref("__prefsPrefix__.syncPeriodSeconds", 30);
|
||||
|
||||
pref("__prefsPrefix__.autoAnnotation", false);
|
||||
|
||||
pref("__prefsPrefix__.embedLink", true);
|
||||
pref("__prefsPrefix__.standaloneLink", false);
|
||||
pref("__prefsPrefix__.keepLink", true);
|
||||
pref("__prefsPrefix__.exportMD", true);
|
||||
pref("__prefsPrefix__.setAutoSync", false);
|
||||
pref("__prefsPrefix__.withYAMLHeader", false);
|
||||
pref("__prefsPrefix__.exportDocx", false);
|
||||
pref("__prefsPrefix__.exportPDF", false);
|
||||
pref("__prefsPrefix__.exportFreeMind", false);
|
||||
pref("__prefsPrefix__.exportNote", false);
|
||||
|
||||
pref("__prefsPrefix__.OCREngine", "bing");
|
||||
pref("__prefsPrefix__.OCRMathpix.Appid", "");
|
||||
pref("__prefsPrefix__.OCRMathpix.Appkey", "");
|
||||
pref("__prefsPrefix__.OCRXunfei.APPID", "");
|
||||
pref("__prefsPrefix__.OCRMathpix.APISecret", "");
|
||||
pref("__prefsPrefix__.OCRMathpix.APIKey", "");
|
||||
|
||||
pref("__prefsPrefix__.workspace.outline.expandLevel", 2);
|
||||
|
Before Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 762 KiB |
|
Before Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 438 KiB |
|
Before Width: | Height: | Size: 792 KiB |
|
Before Width: | Height: | Size: 86 KiB |
60
package.json
|
|
@ -1,18 +1,30 @@
|
|||
{
|
||||
"name": "zotero-better-notes",
|
||||
"version": "0.8.8",
|
||||
"description": "Everything about note management. All in Zotero.",
|
||||
"config": {
|
||||
"addonName": "Zotero Better Notes",
|
||||
"addonID": "Knowledge4Zotero@windingwind.com",
|
||||
"addonRef": "Knowledge4Zotero",
|
||||
"version": "0.8.9",
|
||||
"description": "Everything about note management. All in Zotero.",
|
||||
"main": "src/index.js",
|
||||
"addonRef": "BetterNotes",
|
||||
"prefsPrefix": "extensions.zotero.Knowledge4Zotero",
|
||||
"addonInstance": "BetterNotes",
|
||||
"updaterdf": "https://raw.githubusercontent.com/windingwind/zotero-better-notes/bootstrap/update.json"
|
||||
},
|
||||
"main": "src/index.ts",
|
||||
"scripts": {
|
||||
"build-dev": "cross-env NODE_ENV=development node build.js",
|
||||
"build": "cross-env NODE_ENV=production node build.js",
|
||||
"start": "node start.js",
|
||||
"stop": "node stop.js",
|
||||
"prerestart": "npm run build-dev",
|
||||
"restart": "node restart.js",
|
||||
"build-dev": "cross-env NODE_ENV=development node scripts/build.js",
|
||||
"build-prod": "cross-env NODE_ENV=production node scripts/build.js",
|
||||
"build": "concurrently -c auto npm:build-prod npm:tsc",
|
||||
"tsc": "tsc --noEmit",
|
||||
"start-z6": "node scripts/start.js --z 6",
|
||||
"start-z7": "node scripts/start.js --z 7",
|
||||
"start": "node scripts/start.js",
|
||||
"stop": "node scripts/stop.js",
|
||||
"restart-dev-z6": "npm run build-dev && npm run stop && npm run start-z6",
|
||||
"restart-dev-z7": "npm run build-dev && npm run stop && npm run start-z7",
|
||||
"restart-dev": "npm run build-dev && npm run stop && npm run start",
|
||||
"restart-prod": "npm run build-prod && npm run stop && npm run start",
|
||||
"restart": "npm run restart-dev",
|
||||
"release": "release-it",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
|
|
@ -35,8 +47,6 @@
|
|||
"hast-util-to-text": "^3.1.1",
|
||||
"hastscript": "^7.1.0",
|
||||
"html-docx-js-typescript": "^0.1.5",
|
||||
"prosemirror-model": "^1.18.3",
|
||||
"prosemirror-transform": "^1.7.0",
|
||||
"rehype-format": "^4.0.1",
|
||||
"rehype-parse": "^8.0.4",
|
||||
"rehype-remark": "^9.1.2",
|
||||
|
|
@ -53,16 +63,26 @@
|
|||
"unist-util-visit": "^4.1.1",
|
||||
"unist-util-visit-parents": "^5.1.1",
|
||||
"yamljs": "^0.3.0",
|
||||
"zotero-plugin-toolkit": "^0.1.1"
|
||||
"zotero-plugin-toolkit": "file:../zotero-plugin-toolkit"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/diff": "^5.0.2",
|
||||
"@types/jquery": "^3.5.14",
|
||||
"@types/node": "^17.0.31",
|
||||
"compressing": "^1.5.1",
|
||||
"@types/browser-or-node": "^1.3.0",
|
||||
"@types/diff": "^5.0.3",
|
||||
"@types/node": "^18.11.17",
|
||||
"@types/seedrandom": "^3.0.5",
|
||||
"@types/yamljs": "^0.2.31",
|
||||
"compressing": "^1.6.3",
|
||||
"concurrently": "^7.6.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"esbuild": "^0.14.34",
|
||||
"release-it": "^14.14.0",
|
||||
"zotero-types": "^1.0.9"
|
||||
"esbuild": "^0.17.4",
|
||||
"minimist": "^1.2.7",
|
||||
"prosemirror-model": "^1.18.3",
|
||||
"prosemirror-state": "^1.4.2",
|
||||
"prosemirror-transform": "^1.7.1",
|
||||
"prosemirror-view": "^1.30.2",
|
||||
"release-it": "^15.6.0",
|
||||
"replace-in-file": "^6.3.5",
|
||||
"typescript": "^4.9.4",
|
||||
"zotero-types": "^1.0.12"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
const { execSync } = require("child_process");
|
||||
const { killZotero, startZotero } = require("./zotero-cmd.json");
|
||||
|
||||
try {
|
||||
execSync(killZotero);
|
||||
} catch (e) {}
|
||||
|
||||
execSync(startZotero);
|
||||
|
|
@ -9,11 +9,9 @@ const {
|
|||
author,
|
||||
description,
|
||||
homepage,
|
||||
addonName,
|
||||
addonID,
|
||||
addonRef,
|
||||
version,
|
||||
} = require("./package.json");
|
||||
config,
|
||||
} = require("../package.json");
|
||||
|
||||
function copyFileSync(source, target) {
|
||||
var targetFile = target;
|
||||
|
|
@ -87,63 +85,72 @@ async function main() {
|
|||
const buildDir = "builds";
|
||||
|
||||
console.log(
|
||||
`[Build] BUILD_DIR=${buildDir}, VERSION=${version}, BUILD_TIME=${buildTime}`
|
||||
`[Build] BUILD_DIR=${buildDir}, VERSION=${version}, BUILD_TIME=${buildTime}, ENV=${[
|
||||
process.env.NODE_ENV,
|
||||
]}`
|
||||
);
|
||||
|
||||
clearFolder(buildDir);
|
||||
|
||||
copyFolderRecursiveSync("addon", buildDir);
|
||||
|
||||
const buildStartTime = new Date().getTime();
|
||||
copyFileSync("update-template.json", "update.json");
|
||||
copyFileSync("update-template.rdf", "update.rdf");
|
||||
|
||||
await esbuild
|
||||
.build({
|
||||
entryPoints: ["src/index.ts"],
|
||||
define: {
|
||||
__env__: process.env.NODE_ENV,
|
||||
__env__: `"${process.env.NODE_ENV}"`,
|
||||
},
|
||||
bundle: true,
|
||||
// Entry should be the same as addon/chrome/content/overlay.xul
|
||||
outfile: path.join(buildDir, "addon/chrome/content/scripts/index.js"),
|
||||
// Don't turn minify on
|
||||
// minify: true,
|
||||
target: ["firefox60"],
|
||||
})
|
||||
.catch(() => process.exit(1));
|
||||
|
||||
console.log(
|
||||
`[Build] Run esbuild OK in ${
|
||||
(new Date().getTime() - buildStartTime) / 1000
|
||||
} s.`
|
||||
);
|
||||
|
||||
await esbuild
|
||||
.build({
|
||||
entryPoints: ["src/editor/editorScript.ts"],
|
||||
entryPoints: ["src/extras/editorScript.ts"],
|
||||
bundle: true,
|
||||
outfile: path.join(buildDir, "temp/editorScript.js"),
|
||||
// minify: true,
|
||||
outfile: path.join(
|
||||
buildDir,
|
||||
"addon/chrome/content/scripts/editorScript.js"
|
||||
),
|
||||
target: ["firefox60"],
|
||||
})
|
||||
.catch(() => process.exit(1));
|
||||
|
||||
const optionsScript = {
|
||||
files: [path.join(buildDir, "addon/chrome/content/scripts/index.js")],
|
||||
from: [/__placeholder:editorScript.js__/g],
|
||||
to: [
|
||||
fs
|
||||
.readFileSync(path.join(buildDir, "temp/editorScript.js"))
|
||||
.toString()
|
||||
.replace(/\\/g, "\\\\"),
|
||||
],
|
||||
countMatches: true,
|
||||
};
|
||||
await esbuild
|
||||
.build({
|
||||
entryPoints: ["src/extras/docxWorker.ts"],
|
||||
bundle: true,
|
||||
outfile: path.join(
|
||||
buildDir,
|
||||
"addon/chrome/content/scripts/docxWorker.js"
|
||||
),
|
||||
target: ["firefox60"],
|
||||
})
|
||||
.catch(() => process.exit(1));
|
||||
|
||||
_ = replace.sync(optionsScript);
|
||||
console.log(
|
||||
"[Build] Run replace in ",
|
||||
_.filter((f) => f.hasChanged).map(
|
||||
(f) => `${f.file} : ${f.numReplacements} / ${f.numMatches}`
|
||||
)
|
||||
console.log("[Build] Run esbuild OK");
|
||||
|
||||
const replaceFrom = [
|
||||
/__author__/g,
|
||||
/__description__/g,
|
||||
/__homepage__/g,
|
||||
/__buildVersion__/g,
|
||||
/__buildTime__/g,
|
||||
];
|
||||
|
||||
const replaceTo = [author, description, homepage, version, buildTime];
|
||||
|
||||
replaceFrom.push(
|
||||
...Object.keys(config).map((k) => new RegExp(`__${k}__`, "g"))
|
||||
);
|
||||
replaceTo.push(...Object.values(config));
|
||||
|
||||
const optionsAddon = {
|
||||
files: [
|
||||
|
|
@ -151,31 +158,17 @@ async function main() {
|
|||
path.join(buildDir, "**/*.dtd"),
|
||||
path.join(buildDir, "**/*.xul"),
|
||||
path.join(buildDir, "**/*.html"),
|
||||
path.join(buildDir, "**/*.manifest"),
|
||||
path.join(buildDir, "**/*.xhtml"),
|
||||
path.join(buildDir, "**/*.json"),
|
||||
path.join(buildDir, "addon/prefs.js"),
|
||||
path.join(buildDir, "addon/chrome.manifest"),
|
||||
path.join(buildDir, "addon/manifest.json"),
|
||||
path.join(buildDir, "addon/bootstrap.js"),
|
||||
"update.json",
|
||||
"update.rdf",
|
||||
],
|
||||
from: [
|
||||
/__author__/g,
|
||||
/__description__/g,
|
||||
/__homepage__/g,
|
||||
/__addonName__/g,
|
||||
/__addonID__/g,
|
||||
/__addonRef__/g,
|
||||
/__buildVersion__/g,
|
||||
/__buildTime__/g,
|
||||
/<em:version>\S*<\/em:version>/g,
|
||||
],
|
||||
to: [
|
||||
author,
|
||||
description,
|
||||
homepage,
|
||||
addonName,
|
||||
addonID,
|
||||
addonRef,
|
||||
version,
|
||||
buildTime,
|
||||
`<em:version>${version}</em:version>`,
|
||||
],
|
||||
from: replaceFrom,
|
||||
to: replaceTo,
|
||||
countMatches: true,
|
||||
};
|
||||
|
||||
|
|
@ -205,4 +198,7 @@ async function main() {
|
|||
);
|
||||
}
|
||||
|
||||
main();
|
||||
main().catch((err) => {
|
||||
console.log(err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
const { execSync } = require("child_process");
|
||||
const { exit } = require("process");
|
||||
const { exec } = require("./zotero-cmd.json");
|
||||
|
||||
// Run node start.js -h for help
|
||||
const args = require("minimist")(process.argv.slice(2));
|
||||
|
||||
if (args.help || args.h) {
|
||||
console.log("Start Zotero Args:");
|
||||
console.log(
|
||||
"--zotero(-z): Zotero exec key in zotero-cmd.json. Default the first one."
|
||||
);
|
||||
console.log("--profile(-p): Zotero profile name.");
|
||||
exit(0);
|
||||
}
|
||||
|
||||
const zoteroPath = exec[args.zotero || args.z || Object.keys(exec)[0]];
|
||||
const profile = args.profile || args.p;
|
||||
|
||||
const startZotero = `${zoteroPath} --debugger --purgecaches ${
|
||||
profile ? `-p ${profile}` : ""
|
||||
}`;
|
||||
|
||||
execSync(startZotero);
|
||||
exit(0);
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
const { execSync } = require("child_process");
|
||||
const { killZoteroWindows, killZoteroUnix } = require("./zotero-cmd.json");
|
||||
|
||||
try {
|
||||
if (process.platform === "win32") {
|
||||
execSync(killZoteroWindows);
|
||||
} else {
|
||||
execSync(killZoteroUnix);
|
||||
}
|
||||
} catch (e) {}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"usage": "Copy and rename this file to zotero-cmd.json. Edit the cmd.",
|
||||
"killZoteroWindows": "taskkill /f /im zotero.exe",
|
||||
"killZoteroUnix": "kill -9 $(ps -x | grep zotero)",
|
||||
"exec": {
|
||||
"6": "/path/to/zotero6.exe",
|
||||
"7": "/path/to/zotero7.exe"
|
||||
}
|
||||
}
|
||||
269
src/addon.ts
|
|
@ -1,102 +1,179 @@
|
|||
/*
|
||||
* This file defines the plugin's structure.
|
||||
*/
|
||||
import ZoteroToolkit from "zotero-plugin-toolkit/dist/index";
|
||||
import {
|
||||
ColumnOptions,
|
||||
VirtualizedTableHelper,
|
||||
} from "zotero-plugin-toolkit/dist/helpers/virtualizedTable";
|
||||
import hooks from "./hooks";
|
||||
import api from "./api";
|
||||
|
||||
import ZoteroEvents from "./zotero/events";
|
||||
import ZoteroNotifies from "./zotero/notifies";
|
||||
import ZoteroViews from "./zotero/views";
|
||||
import ReaderViews from "./reader/readerViews";
|
||||
import WizardWindow from "./wizard/wizardWindow";
|
||||
import { TemplateController, TemplateAPI } from "./template/templateController";
|
||||
import SyncInfoWindow from "./sync/syncInfoWindow";
|
||||
import SyncListWindow from "./sync/syncListWindow";
|
||||
import SyncController from "./sync/syncController";
|
||||
import WorkspaceWindow from "./workspace/workspaceWindow";
|
||||
import WorkspaceOutline from "./workspace/workspaceOutline";
|
||||
import WorkspaceMenu from "./workspace/workspaceMenu";
|
||||
import NoteUtils from "./note/noteUtils";
|
||||
import NoteParse from "./note/noteParse";
|
||||
import NoteExportWindow from "./note/noteExportWindow";
|
||||
import NoteExport from "./note/noteExportController";
|
||||
import NoteImport from "./note/noteImportController";
|
||||
import SyncDiffWindow from "./sync/syncDiffWindow";
|
||||
import EditorViews from "./editor/editorViews";
|
||||
import EditorController from "./editor/editorController";
|
||||
import EditorImageViewer from "./editor/imageViewerWindow";
|
||||
import TemplateWindow from "./template/templateWindow";
|
||||
import { SyncUtils } from "./sync/syncUtils";
|
||||
import ZoteroToolkit from "zotero-plugin-toolkit";
|
||||
import AddonLocale from "./zotero/locale";
|
||||
|
||||
class BetterNotes {
|
||||
public env: "development" | "production";
|
||||
public Locale: AddonLocale;
|
||||
public ZoteroEvents: ZoteroEvents;
|
||||
public ZoteroNotifies: ZoteroNotifies;
|
||||
// Zotero UI
|
||||
public ZoteroViews: ZoteroViews;
|
||||
// Reader UI
|
||||
public ReaderViews: ReaderViews;
|
||||
// Workspace UI
|
||||
public WorkspaceOutline: WorkspaceOutline;
|
||||
public WorkspaceWindow: WorkspaceWindow;
|
||||
public WorkspaceMenu: WorkspaceMenu;
|
||||
// First-run wizard
|
||||
public WizardWindow: WizardWindow;
|
||||
// Sync tools
|
||||
public SyncUtils: SyncUtils;
|
||||
public SyncInfoWindow: SyncInfoWindow;
|
||||
public SyncListWindow: SyncListWindow;
|
||||
public SyncController: SyncController;
|
||||
// Template
|
||||
public TemplateWindow: TemplateWindow;
|
||||
public TemplateController: TemplateController;
|
||||
// Just for template API consistency
|
||||
public knowledge: TemplateAPI;
|
||||
// Note tools
|
||||
public NoteUtils: NoteUtils;
|
||||
public NoteExport: NoteExport;
|
||||
public NoteImport: NoteImport;
|
||||
public SyncDiffWindow: SyncDiffWindow;
|
||||
public NoteExportWindow: NoteExportWindow;
|
||||
public NoteParse: NoteParse;
|
||||
public EditorViews: EditorViews;
|
||||
public EditorController: EditorController;
|
||||
public EditorImageViewer: EditorImageViewer;
|
||||
|
||||
public toolkit: ZoteroToolkit;
|
||||
class Addon {
|
||||
public data: {
|
||||
alive: boolean;
|
||||
// Env type, see build.js
|
||||
env: "development" | "production";
|
||||
// ztoolkit: MyToolkit;
|
||||
ztoolkit: ZoteroToolkit;
|
||||
locale?: {
|
||||
stringBundle: any;
|
||||
};
|
||||
prefs?: {
|
||||
window: Window;
|
||||
columns: Array<ColumnOptions>;
|
||||
rows: Array<{ [dataKey: string]: string }>;
|
||||
};
|
||||
export: {
|
||||
pdf: { promise?: _ZoteroTypes.PromiseObject };
|
||||
docx: { worker?: HTMLIFrameElement };
|
||||
};
|
||||
sync: {
|
||||
lock: boolean;
|
||||
manager: {
|
||||
window?: Window;
|
||||
tableHelper?: VirtualizedTableHelper;
|
||||
data: {
|
||||
noteId: number;
|
||||
noteName: string;
|
||||
lastSync: string;
|
||||
filePath: string;
|
||||
}[];
|
||||
};
|
||||
diff: {
|
||||
window?: Window;
|
||||
};
|
||||
};
|
||||
notify: Array<Parameters<_ZoteroTypes.Notifier.Notify>>;
|
||||
workspace: {
|
||||
mainId: number;
|
||||
previewId: number;
|
||||
tab: {
|
||||
active: boolean;
|
||||
id?: string;
|
||||
container?: XUL.Box;
|
||||
};
|
||||
window: {
|
||||
active: boolean;
|
||||
window?: Window;
|
||||
container?: XUL.Box;
|
||||
};
|
||||
outline: OutlineType;
|
||||
};
|
||||
imageViewer: {
|
||||
window?: Window;
|
||||
srcList: string[];
|
||||
idx: number;
|
||||
scaling: number;
|
||||
title: string;
|
||||
pined: boolean;
|
||||
anchorPosition?: {
|
||||
left: number;
|
||||
top: number;
|
||||
};
|
||||
};
|
||||
templateEditor: {
|
||||
window?: Window;
|
||||
tableHelper?: VirtualizedTableHelper;
|
||||
templates: { name: string }[];
|
||||
};
|
||||
templatePicker: {
|
||||
noteId?: number;
|
||||
lineIndex?: number;
|
||||
};
|
||||
prompt?: Prompt;
|
||||
} = {
|
||||
alive: true,
|
||||
env: __env__,
|
||||
// ztoolkit: new MyToolkit(),
|
||||
ztoolkit: new ZoteroToolkit(),
|
||||
export: {
|
||||
pdf: { promise: undefined },
|
||||
docx: { worker: undefined },
|
||||
},
|
||||
sync: {
|
||||
lock: false,
|
||||
manager: {
|
||||
data: [],
|
||||
},
|
||||
diff: {},
|
||||
},
|
||||
notify: [],
|
||||
workspace: {
|
||||
get mainId(): number {
|
||||
return parseInt(getPref("mainKnowledgeID") as string);
|
||||
},
|
||||
set mainId(id: number) {
|
||||
setPref("mainKnowledgeID", id);
|
||||
},
|
||||
previewId: -1,
|
||||
tab: {
|
||||
active: false,
|
||||
},
|
||||
window: {
|
||||
active: false,
|
||||
},
|
||||
outline: OutlineType.treeView,
|
||||
},
|
||||
imageViewer: {
|
||||
window: undefined,
|
||||
srcList: [],
|
||||
idx: -1,
|
||||
scaling: 1,
|
||||
title: "Note",
|
||||
pined: false,
|
||||
anchorPosition: undefined,
|
||||
},
|
||||
templateEditor: {
|
||||
window: undefined,
|
||||
tableHelper: undefined,
|
||||
templates: [],
|
||||
},
|
||||
templatePicker: {},
|
||||
};
|
||||
// Lifecycle hooks
|
||||
public hooks: typeof hooks;
|
||||
// APIs
|
||||
public api: typeof api;
|
||||
|
||||
constructor() {
|
||||
this.Locale = new AddonLocale(this);
|
||||
this.ZoteroEvents = new ZoteroEvents(this);
|
||||
this.ZoteroNotifies = new ZoteroNotifies(this);
|
||||
this.ZoteroViews = new ZoteroViews(this);
|
||||
this.ReaderViews = new ReaderViews(this);
|
||||
this.WorkspaceOutline = new WorkspaceOutline(this);
|
||||
this.WorkspaceWindow = new WorkspaceWindow(this);
|
||||
this.WorkspaceMenu = new WorkspaceMenu(this);
|
||||
this.EditorViews = new EditorViews(this);
|
||||
this.EditorController = new EditorController(this);
|
||||
this.EditorImageViewer = new EditorImageViewer(this);
|
||||
this.WizardWindow = new WizardWindow(this);
|
||||
this.SyncUtils = new SyncUtils(this);
|
||||
this.SyncInfoWindow = new SyncInfoWindow(this);
|
||||
this.SyncListWindow = new SyncListWindow(this);
|
||||
this.SyncController = new SyncController(this);
|
||||
this.SyncDiffWindow = new SyncDiffWindow(this);
|
||||
this.TemplateWindow = new TemplateWindow(this);
|
||||
this.TemplateController = new TemplateController(this);
|
||||
this.NoteUtils = new NoteUtils(this);
|
||||
this.NoteExport = new NoteExport(this);
|
||||
this.NoteImport = new NoteImport(this);
|
||||
this.NoteExportWindow = new NoteExportWindow(this);
|
||||
this.NoteParse = new NoteParse(this);
|
||||
this.knowledge = new TemplateAPI(this);
|
||||
|
||||
this.toolkit = new ZoteroToolkit();
|
||||
// Disable since we are still using overlay
|
||||
this.toolkit.UI.enableElementRecordGlobal = false;
|
||||
this.hooks = hooks;
|
||||
this.api = api;
|
||||
}
|
||||
}
|
||||
|
||||
export default BetterNotes;
|
||||
/**
|
||||
* Alternatively, import toolkit modules you use to minify the plugin size.
|
||||
*
|
||||
* Steps to replace the default `ztoolkit: ZoteroToolkit` with your `ztoolkit: MyToolkit`:
|
||||
*
|
||||
* 1. Uncomment this file's line 30: `ztoolkit: new MyToolkit(),`
|
||||
* and comment line 31: `ztoolkit: new ZoteroToolkit(),`.
|
||||
* 2. Uncomment this file's line 10: `ztoolkit: MyToolkit;` in this file
|
||||
* and comment line 11: `ztoolkit: ZoteroToolkit;`.
|
||||
* 3. Uncomment `./typing/global.d.ts` line 12: `declare const ztoolkit: import("../src/addon").MyToolkit;`
|
||||
* and comment line 13: `declare const ztoolkit: import("zotero-plugin-toolkit").ZoteroToolkit;`.
|
||||
*
|
||||
* You can now add the modules under the `MyToolkit` class.
|
||||
*/
|
||||
|
||||
import { BasicTool, unregister } from "zotero-plugin-toolkit/dist/basic";
|
||||
import { UITool } from "zotero-plugin-toolkit/dist/tools/ui";
|
||||
import { PreferencePaneManager } from "zotero-plugin-toolkit/dist/managers/preferencePane";
|
||||
import { getPref, setPref } from "./utils/prefs";
|
||||
import { OutlineType } from "./utils/workspace";
|
||||
import { Prompt } from "zotero-plugin-toolkit/dist/managers/prompt";
|
||||
|
||||
export class MyToolkit extends BasicTool {
|
||||
UI: UITool;
|
||||
PreferencePane: PreferencePaneManager;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.UI = new UITool(this);
|
||||
this.PreferencePane = new PreferencePaneManager(this);
|
||||
}
|
||||
|
||||
unregisterAll() {
|
||||
unregister(this);
|
||||
}
|
||||
}
|
||||
|
||||
export default Addon;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,106 @@
|
|||
// workspace
|
||||
import {
|
||||
getWorkspaceEditor,
|
||||
initWorkspace,
|
||||
toggleNotesPane,
|
||||
toggleOutlinePane,
|
||||
togglePreviewPane,
|
||||
} from "./modules/workspace/content";
|
||||
|
||||
const workspace = {
|
||||
getWorkspaceEditor,
|
||||
initWorkspace,
|
||||
toggleNotesPane,
|
||||
toggleOutlinePane,
|
||||
togglePreviewPane,
|
||||
};
|
||||
|
||||
// sync
|
||||
import sync = require("./modules/sync/api");
|
||||
|
||||
// convert
|
||||
import convert = require("./modules/convert/api");
|
||||
|
||||
// template
|
||||
import { runTemplate, runItemTemplate } from "./modules/template/api";
|
||||
import {
|
||||
SYSTEM_TEMPLATE_NAMES,
|
||||
DEFAULT_TEMPLATES,
|
||||
} from "./modules/template/data";
|
||||
import {
|
||||
getTemplateKeys,
|
||||
getTemplateText,
|
||||
setTemplate,
|
||||
initTemplates,
|
||||
removeTemplate,
|
||||
} from "./modules/template/controller";
|
||||
import { renderTemplatePreview } from "./modules/template/preview";
|
||||
import {
|
||||
showTemplatePicker,
|
||||
updateTemplatePicker,
|
||||
} from "./modules/template/picker";
|
||||
|
||||
const template = {
|
||||
SYSTEM_TEMPLATE_NAMES,
|
||||
DEFAULT_TEMPLATES,
|
||||
runTemplate,
|
||||
runItemTemplate,
|
||||
getTemplateKeys,
|
||||
getTemplateText,
|
||||
setTemplate,
|
||||
initTemplates,
|
||||
removeTemplate,
|
||||
renderTemplatePreview,
|
||||
updateTemplatePicker,
|
||||
};
|
||||
|
||||
// export
|
||||
import { exportNotes } from "./modules/export/api";
|
||||
import { saveMD, syncMDBatch } from "./modules/export/markdown";
|
||||
import { saveDocx } from "./modules/export/docx";
|
||||
import { saveFreeMind } from "./modules/export/freemind";
|
||||
|
||||
const _export = {
|
||||
exportNotes,
|
||||
saveMD,
|
||||
syncMDBatch,
|
||||
saveFreeMind,
|
||||
saveDocx,
|
||||
savePDF,
|
||||
};
|
||||
|
||||
// import
|
||||
import { fromMD } from "./modules/import/markdown";
|
||||
|
||||
const _import = {
|
||||
fromMD,
|
||||
};
|
||||
|
||||
// window
|
||||
import { showImageViewer } from "./modules/imageViewer";
|
||||
import { showSyncInfo } from "./modules/sync/infoWindow";
|
||||
import { showExportNoteOptions } from "./modules/export/exportWindow";
|
||||
import { showTemplateEditor } from "./modules/template/editorWindow";
|
||||
import { showSyncManager } from "./modules/sync/managerWindow";
|
||||
import { showSyncDiff } from "./modules/sync/diffWindow";
|
||||
import { savePDF } from "./modules/export/pdf";
|
||||
|
||||
const window = {
|
||||
showImageViewer,
|
||||
showExportNoteOptions,
|
||||
showSyncInfo,
|
||||
showSyncManager,
|
||||
showSyncDiff,
|
||||
showTemplateEditor,
|
||||
showTemplatePicker,
|
||||
};
|
||||
|
||||
export default {
|
||||
workspace,
|
||||
_export,
|
||||
_import,
|
||||
window,
|
||||
sync,
|
||||
convert,
|
||||
template,
|
||||
};
|
||||
|
|
@ -1,73 +0,0 @@
|
|||
/*
|
||||
* This file realizes editor watch.
|
||||
*/
|
||||
|
||||
import BetterNotes from "../addon";
|
||||
import AddonBase from "../module";
|
||||
|
||||
class EditorController extends AddonBase {
|
||||
editorHistory: Array<{
|
||||
instance: Zotero.EditorInstance;
|
||||
time: number;
|
||||
}>;
|
||||
editorPromise: _ZoteroTypes.PromiseObject;
|
||||
activeEditor: Zotero.EditorInstance;
|
||||
|
||||
constructor(parent: BetterNotes) {
|
||||
super(parent);
|
||||
this.editorHistory = [];
|
||||
}
|
||||
|
||||
startWaiting() {
|
||||
this.editorPromise = Zotero.Promise.defer();
|
||||
}
|
||||
|
||||
async waitForEditor() {
|
||||
await this.editorPromise.promise;
|
||||
}
|
||||
|
||||
injectScripts(_window: Window) {
|
||||
if (!_window.document.getElementById("betternotes-script")) {
|
||||
const messageScript = this._Addon.toolkit.UI.createElement(
|
||||
_window.document,
|
||||
"script"
|
||||
) as HTMLScriptElement;
|
||||
messageScript.id = "betternotes-script";
|
||||
messageScript.innerHTML = `__placeholder:editorScript.js__`;
|
||||
_window.document.head.append(messageScript);
|
||||
}
|
||||
_window.addEventListener("BNMessage", (e: CustomEvent) => {
|
||||
this._Addon.toolkit.Tool.log("note editor event", e.detail);
|
||||
switch (e.detail.type) {
|
||||
case "exportPDFDone":
|
||||
this._Addon.NoteExport._pdfPrintPromise.resolve();
|
||||
break;
|
||||
case "exportDocxDone":
|
||||
this._Addon.NoteExport._docxBlob = e.detail.docxBlob;
|
||||
this._Addon.NoteExport._docxPromise.resolve();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
recordEditor(instance: Zotero.EditorInstance) {
|
||||
this.editorHistory.push({
|
||||
instance: instance,
|
||||
time: new Date().getTime(),
|
||||
});
|
||||
const aliveInstances = Zotero.Notes._editorInstances.map(
|
||||
(_i) => _i.instanceID
|
||||
);
|
||||
this.editorHistory = this.editorHistory.filter((obj) =>
|
||||
aliveInstances.includes(obj.instance.instanceID)
|
||||
);
|
||||
|
||||
if (this.editorPromise) {
|
||||
this.editorPromise.resolve();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default EditorController;
|
||||
|
|
@ -1,225 +0,0 @@
|
|||
// DO NOT USE BACKTICK IN THIS FILE
|
||||
const { Fragment, Slice } = require("prosemirror-model");
|
||||
const { Step, StepResult } = require("prosemirror-transform");
|
||||
import { asBlob } from "html-docx-js-typescript";
|
||||
|
||||
class SetAttrsStep extends Step {
|
||||
// :: (number, Object | null)
|
||||
constructor(pos, attrs) {
|
||||
super();
|
||||
this.pos = pos;
|
||||
this.attrs = attrs;
|
||||
}
|
||||
|
||||
apply(doc) {
|
||||
let target = doc.nodeAt(this.pos);
|
||||
if (!target) return StepResult.fail("No node at given position");
|
||||
let newNode = target.type.create(this.attrs, Fragment.emtpy, target.marks);
|
||||
let slice = new Slice(Fragment.from(newNode), 0, target.isLeaf ? 0 : 1);
|
||||
return StepResult.fromReplace(doc, this.pos, this.pos + 1, slice);
|
||||
}
|
||||
|
||||
invert(doc) {
|
||||
let target = doc.nodeAt(this.pos);
|
||||
return new SetAttrsStep(this.pos, target ? target.attrs : null);
|
||||
}
|
||||
|
||||
map(mapping) {
|
||||
let pos = mapping.mapResult(this.pos, 1);
|
||||
return pos.deleted ? null : new SetAttrsStep(pos.pos, this.attrs);
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return { stepType: "setAttrs", pos: this.pos, attrs: this.attrs };
|
||||
}
|
||||
|
||||
static fromJSON(schema, json) {
|
||||
if (
|
||||
typeof json.pos != "number" ||
|
||||
(json.attrs != null && typeof json.attrs != "object")
|
||||
)
|
||||
throw new RangeError("Invalid input for SetAttrsStep.fromJSON");
|
||||
return new SetAttrsStep(json.pos, json.attrs);
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
window.SetAttrsStep = SetAttrsStep;
|
||||
|
||||
// @ts-ignore
|
||||
window.updateImageDimensions = function (
|
||||
nodeID,
|
||||
width,
|
||||
height,
|
||||
state,
|
||||
dispatch
|
||||
) {
|
||||
let { tr } = state;
|
||||
console.log(nodeID, width, height, state, dispatch);
|
||||
state.doc.descendants((node, pos) => {
|
||||
if (node.type.name === "image" && node.attrs.nodeID === nodeID) {
|
||||
tr.step(new SetAttrsStep(pos, { ...node.attrs, width, height }));
|
||||
tr.setMeta("addToHistory", false);
|
||||
tr.setMeta("system", true);
|
||||
dispatch(tr);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
window.addEventListener(
|
||||
"message",
|
||||
async (e) => {
|
||||
console.log(e);
|
||||
const editNode = document.querySelector(".primary-editor") as HTMLElement;
|
||||
let t = 0;
|
||||
let imageFlag = false;
|
||||
while (!imageFlag && t < 500) {
|
||||
await new Promise(function (resolve) {
|
||||
setTimeout(resolve, 10);
|
||||
});
|
||||
imageFlag = !Array.prototype.find.call(
|
||||
document.querySelectorAll("img"),
|
||||
(e) => !e.getAttribute("src") || e.style.display === "none"
|
||||
);
|
||||
t += 1;
|
||||
}
|
||||
switch (e.data.type) {
|
||||
case "exportPDF":
|
||||
console.log("exportPDF");
|
||||
const container = document.getElementById(
|
||||
"editor-container"
|
||||
) as HTMLElement;
|
||||
container.style.display = "none";
|
||||
|
||||
const fullPageStyle = document.createElement("style");
|
||||
fullPageStyle.innerHTML =
|
||||
"@page { margin: 0; } @media print{ body { height : auto; -webkit-print-color-adjust: exact; color-adjust: exact; }}";
|
||||
document.body.append(fullPageStyle);
|
||||
|
||||
const printNode = editNode.cloneNode(true) as HTMLElement;
|
||||
printNode.style.padding = "20px";
|
||||
document.body.append(printNode);
|
||||
|
||||
let printFlag = false;
|
||||
window.onafterprint = (_e) => {
|
||||
console.log("Print Dialog Closed..");
|
||||
printFlag = true;
|
||||
// document.title = "Printed";
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("BNMessage", {
|
||||
detail: {
|
||||
type: "exportPDFDone",
|
||||
},
|
||||
})
|
||||
);
|
||||
};
|
||||
window.onmouseover = (e) => {
|
||||
if (printFlag) {
|
||||
document.title = "Printed";
|
||||
printNode.remove();
|
||||
container.style.removeProperty("display");
|
||||
}
|
||||
};
|
||||
document.title = (printNode?.firstChild as HTMLElement).innerText;
|
||||
console.log(document.title);
|
||||
window.print();
|
||||
break;
|
||||
case "exportDocx":
|
||||
// @ts-ignore
|
||||
const docxBlob: Blob = await window.getDocx(editNode);
|
||||
console.log(docxBlob);
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("BNMessage", {
|
||||
detail: {
|
||||
type: "exportDocxDone",
|
||||
docxBlob: docxBlob,
|
||||
},
|
||||
})
|
||||
);
|
||||
break;
|
||||
case "resizeImage":
|
||||
console.log("resizeImage");
|
||||
// @ts-ignore
|
||||
window.updateImageDimensions(
|
||||
// @ts-ignore
|
||||
_currentEditorInstance._editorCore.view.state.selection.node.attrs
|
||||
.nodeID,
|
||||
e.data.width,
|
||||
undefined,
|
||||
// @ts-ignore
|
||||
_currentEditorInstance._editorCore.view.state,
|
||||
// @ts-ignore
|
||||
_currentEditorInstance._editorCore.view.dispatch
|
||||
);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
// @ts-ignore
|
||||
window.getDocx = async (
|
||||
dom: HTMLElement,
|
||||
config: object,
|
||||
{ title = document.title, width = undefined } = {}
|
||||
) => {
|
||||
if (!dom) return;
|
||||
config = config || {};
|
||||
let copyDom = document.createElement("span");
|
||||
// const styleDom = document.querySelectorAll('style, link, meta')
|
||||
const titleDom = document.createElement("title");
|
||||
titleDom.innerText = title;
|
||||
|
||||
copyDom.appendChild(titleDom);
|
||||
// Array.from(styleDom).forEach(item => {
|
||||
// copyDom.appendChild(item.cloneNode(true))
|
||||
// })
|
||||
const cloneDom = dom.cloneNode(true) as HTMLElement;
|
||||
if (width) {
|
||||
const domTables = cloneDom.getElementsByTagName("table");
|
||||
if (domTables.length) {
|
||||
for (const table of domTables) {
|
||||
table.style.width = width + "px";
|
||||
}
|
||||
}
|
||||
}
|
||||
copyDom.appendChild(cloneDom);
|
||||
|
||||
const htmlTemp = copyDom.innerHTML;
|
||||
copyDom = null;
|
||||
const iframeDom = document.createElement("iframe");
|
||||
const attrObj = {
|
||||
height: 0,
|
||||
width: 0,
|
||||
border: 0,
|
||||
wmode: "Opaque",
|
||||
};
|
||||
const styleObj = {
|
||||
position: "absolute",
|
||||
top: "-999px",
|
||||
left: "-999px",
|
||||
};
|
||||
Object.entries(attrObj).forEach(([key, value]) => {
|
||||
iframeDom.setAttribute(key, String(value));
|
||||
});
|
||||
Object.entries(styleObj).forEach(([key, value]) => {
|
||||
iframeDom.style[key] = value;
|
||||
});
|
||||
document.body.insertBefore(iframeDom, document.body.children[0]);
|
||||
const iframeWin = iframeDom.contentWindow;
|
||||
const iframeDocs = iframeWin.document;
|
||||
iframeDocs.write("<!doctype html>");
|
||||
iframeDocs.write(htmlTemp);
|
||||
|
||||
let htmlDoc =
|
||||
'<!DOCTYPE html>\n<html lang="en"><head><meta charset="UTF-8"></head>\n';
|
||||
htmlDoc += iframeDocs.documentElement.innerHTML;
|
||||
htmlDoc += "\n</html>";
|
||||
var converted = await asBlob(htmlDoc, config);
|
||||
console.log(converted);
|
||||
document.body.removeChild(iframeDom);
|
||||
return converted;
|
||||
};
|
||||
|
|
@ -1,293 +0,0 @@
|
|||
/*
|
||||
* This file contains image viewer for note editor.
|
||||
*/
|
||||
|
||||
import BetterNotes from "../addon";
|
||||
import AddonBase from "../module";
|
||||
|
||||
class EditorImageViewer extends AddonBase {
|
||||
_window: Window;
|
||||
scaling: number;
|
||||
srcList: string[];
|
||||
idx: number;
|
||||
title: string;
|
||||
pined: boolean;
|
||||
anchorPosition: {
|
||||
left: number;
|
||||
top: number;
|
||||
};
|
||||
icons: any;
|
||||
constructor(parent: BetterNotes) {
|
||||
super(parent);
|
||||
this.scaling = 1;
|
||||
this.title = "Note";
|
||||
this.pined = false;
|
||||
this.icons = {
|
||||
pin: `<svg t="1668685819555" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1445" width="18" height="18"><path d="M631.637333 178.432a64 64 0 0 1 19.84 13.504l167.616 167.786667a64 64 0 0 1-19.370666 103.744l-59.392 26.304-111.424 111.552-8.832 122.709333a64 64 0 0 1-109.098667 40.64l-108.202667-108.309333-184.384 185.237333-45.354666-45.162667 184.490666-185.344-111.936-112.021333a64 64 0 0 1 40.512-109.056l126.208-9.429333 109.44-109.568 25.706667-59.306667a64 64 0 0 1 84.181333-33.28z m-25.450666 58.730667l-30.549334 70.464-134.826666 135.04-149.973334 11.157333 265.408 265.6 10.538667-146.474667 136.704-136.874666 70.336-31.146667-167.637333-167.765333z" p-id="1446"></path></svg>`,
|
||||
pined: `<svg t="1668685864340" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1624" width="18" height="18"><path d="M631.637333 178.432a64 64 0 0 1 19.84 13.504l167.616 167.786667a64 64 0 0 1-19.370666 103.744l-59.392 26.304-111.424 111.552-8.832 122.709333a64 64 0 0 1-109.098667 40.64l-108.202667-108.309333-184.384 185.237333-45.354666-45.162667 184.490666-185.344-111.936-112.021333a64 64 0 0 1 40.512-109.056l126.208-9.429333 109.44-109.568 25.706667-59.306667a64 64 0 0 1 84.181333-33.28z" p-id="1625"></path></svg>`,
|
||||
};
|
||||
}
|
||||
|
||||
async onInit(srcs: string[], idx: number, title: string, pined: boolean) {
|
||||
if (!this._window || this._window.closed) {
|
||||
this._window = window.open(
|
||||
"chrome://Knowledge4Zotero/content/imageViewer.html",
|
||||
"betternotes-note-imagepreview",
|
||||
`chrome,centerscreen,resizable,status,width=500,height=550${
|
||||
pined ? ",alwaysRaised=yes" : ""
|
||||
}`
|
||||
);
|
||||
let t = 0;
|
||||
// Wait for window
|
||||
while (t < 500 && this._window.document.readyState !== "complete") {
|
||||
await Zotero.Promise.delay(10);
|
||||
t += 1;
|
||||
}
|
||||
const container = this._window.document.querySelector(
|
||||
".container"
|
||||
) as HTMLDivElement;
|
||||
const img = this._window.document.querySelector(
|
||||
"#image"
|
||||
) as HTMLImageElement;
|
||||
|
||||
this._window.document
|
||||
.querySelector("#left")
|
||||
.addEventListener("click", (e) => {
|
||||
this.setIndex("left");
|
||||
});
|
||||
this._window.document
|
||||
.querySelector("#bigger")
|
||||
.addEventListener("click", (e) => {
|
||||
this.anchorPosition = {
|
||||
left: img.scrollWidth / 2 - container.scrollLeft / 2,
|
||||
top: img.scrollHeight / 2 - container.scrollLeft / 2,
|
||||
};
|
||||
this.setScale(this.scaling * 1.1);
|
||||
});
|
||||
this._window.document
|
||||
.querySelector("#smaller")
|
||||
.addEventListener("click", (e) => {
|
||||
this.anchorPosition = {
|
||||
left: img.scrollWidth / 2 - container.scrollLeft / 2,
|
||||
top: img.scrollHeight / 2 - container.scrollLeft / 2,
|
||||
};
|
||||
this.setScale(this.scaling / 1.1);
|
||||
});
|
||||
this._window.document
|
||||
.querySelector("#resetwidth")
|
||||
.addEventListener("click", (e) => {
|
||||
this.setScale(1);
|
||||
});
|
||||
this._window.document
|
||||
.querySelector("#right")
|
||||
.addEventListener("click", (e) => {
|
||||
this.setIndex("right");
|
||||
});
|
||||
this._window.document
|
||||
.querySelector("#copy")
|
||||
.addEventListener("click", (e) => {
|
||||
this._Addon.toolkit.Tool.getCopyHelper()
|
||||
.addImage(this.srcList[this.idx])
|
||||
.copy();
|
||||
this._Addon.ZoteroViews.showProgressWindow(
|
||||
"Better Notes",
|
||||
"Image Copied."
|
||||
);
|
||||
});
|
||||
this._window.document
|
||||
.querySelector("#save")
|
||||
.addEventListener("click", async (e) => {
|
||||
let parts = this.srcList[this.idx].split(",");
|
||||
if (!parts[0].includes("base64")) {
|
||||
return;
|
||||
}
|
||||
let mime = parts[0].match(/:(.*?);/)[1];
|
||||
let bstr = atob(parts[1]);
|
||||
let n = bstr.length;
|
||||
let u8arr = new Uint8Array(n);
|
||||
while (n--) {
|
||||
u8arr[n] = bstr.charCodeAt(n);
|
||||
}
|
||||
let ext = Zotero.MIME.getPrimaryExtension(mime, "");
|
||||
const filename = await this._Addon.toolkit.Tool.openFilePicker(
|
||||
Zotero.getString("noteEditor.saveImageAs"),
|
||||
"save",
|
||||
[[`Image(*.${ext})`, `*.${ext}`]],
|
||||
`${Zotero.getString("fileTypes.image").toLowerCase()}.${ext}`
|
||||
);
|
||||
if (filename) {
|
||||
await OS.File.writeAtomic(
|
||||
this._Addon.NoteUtils.formatPath(filename),
|
||||
u8arr
|
||||
);
|
||||
}
|
||||
this._Addon.ZoteroViews.showProgressWindow(
|
||||
"Better Notes",
|
||||
`Image Saved to ${filename}`
|
||||
);
|
||||
});
|
||||
this._window.document.querySelector("#pin").innerHTML =
|
||||
this.icons[pined ? "pined" : "pin"];
|
||||
this._window.document.querySelector("#pin-tooltip").innerHTML = pined
|
||||
? "Unpin"
|
||||
: "Pin";
|
||||
this._window.document
|
||||
.querySelector("#pin")
|
||||
.addEventListener("click", (e) => {
|
||||
this.setPin();
|
||||
});
|
||||
this._window.addEventListener("keydown", (e) => {
|
||||
// ctrl+w or esc
|
||||
if ((e.key === "w" && e.ctrlKey) || e.keyCode === 27) {
|
||||
this._window.close();
|
||||
}
|
||||
this.anchorPosition = {
|
||||
left: img.scrollWidth / 2 - container.scrollLeft / 2,
|
||||
top: img.scrollHeight / 2 - container.scrollLeft / 2,
|
||||
};
|
||||
if (e.keyCode === 37 || e.keyCode === 40) {
|
||||
this.setIndex("left");
|
||||
}
|
||||
if (e.keyCode === 38 || e.keyCode === 39) {
|
||||
this.setIndex("right");
|
||||
}
|
||||
if (e.key === "0") {
|
||||
this.setScale(1);
|
||||
} else if (e.keyCode === 107 || e.keyCode === 187 || e.key === "=") {
|
||||
this.setScale(this.scaling * 1.1);
|
||||
} else if (e.key === "-") {
|
||||
this.setScale(this.scaling / 1.1);
|
||||
}
|
||||
});
|
||||
this._window.addEventListener("wheel", async (e) => {
|
||||
this.anchorPosition = {
|
||||
left: e.pageX - container.offsetLeft,
|
||||
top: e.pageY - container.offsetTop,
|
||||
};
|
||||
function normalizeWheelEventDirection(evt) {
|
||||
let delta = Math.hypot(evt.deltaX, evt.deltaY);
|
||||
const angle = Math.atan2(evt.deltaY, evt.deltaX);
|
||||
if (-0.25 * Math.PI < angle && angle < 0.75 * Math.PI) {
|
||||
// All that is left-up oriented has to change the sign.
|
||||
delta = -delta;
|
||||
}
|
||||
return delta;
|
||||
}
|
||||
const delta = normalizeWheelEventDirection(e);
|
||||
if (e.ctrlKey) {
|
||||
this.setScale(
|
||||
this.scaling *
|
||||
Math.pow(delta > 0 ? 1.1 : 1 / 1.1, Math.round(Math.abs(delta)))
|
||||
);
|
||||
} else if (e.shiftKey) {
|
||||
container.scrollLeft -= delta * 10;
|
||||
} else {
|
||||
container.scrollLeft += e.deltaX * 10;
|
||||
container.scrollTop += e.deltaY * 10;
|
||||
}
|
||||
});
|
||||
img.addEventListener("mousedown", (e) => {
|
||||
e.preventDefault();
|
||||
// if (this.scaling <= 1) {
|
||||
// return;
|
||||
// }
|
||||
img.onmousemove = (e) => {
|
||||
e.preventDefault();
|
||||
container.scrollLeft -= e.movementX;
|
||||
container.scrollTop -= e.movementY;
|
||||
};
|
||||
img.onmouseleave = () => {
|
||||
img.onmousemove = null;
|
||||
img.onmouseup = null;
|
||||
};
|
||||
img.onmouseup = () => {
|
||||
img.onmousemove = null;
|
||||
img.onmouseup = null;
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
this.srcList = srcs;
|
||||
this.idx = idx;
|
||||
this.title = title || "Note";
|
||||
this.pined = pined;
|
||||
this.setImage();
|
||||
this.setScale(1);
|
||||
this._window.focus();
|
||||
}
|
||||
|
||||
private setImage() {
|
||||
(this._window.document.querySelector("#image") as HTMLImageElement).src =
|
||||
this.srcList[this.idx];
|
||||
this.setTitle();
|
||||
(
|
||||
this._window.document.querySelector(
|
||||
"#left-container"
|
||||
) as HTMLButtonElement
|
||||
).style.opacity = this.idx === 0 ? "0.5" : "1";
|
||||
(
|
||||
this._window.document.querySelector(
|
||||
"#right-container"
|
||||
) as HTMLButtonElement
|
||||
).style.opacity = this.idx === this.srcList.length - 1 ? "0.5" : "1";
|
||||
}
|
||||
|
||||
private setIndex(type: "left" | "right") {
|
||||
if (type === "left") {
|
||||
this.idx > 0 ? (this.idx -= 1) : undefined;
|
||||
}
|
||||
if (type === "right") {
|
||||
this.idx < this.srcList.length - 1 ? (this.idx += 1) : undefined;
|
||||
}
|
||||
this.setImage();
|
||||
}
|
||||
|
||||
private setScale(scaling: number) {
|
||||
const oldScale = this.scaling;
|
||||
this.scaling = scaling;
|
||||
if (this.scaling > 10) {
|
||||
this.scaling = 10;
|
||||
}
|
||||
if (this.scaling < 0.1) {
|
||||
this.scaling = 0.1;
|
||||
}
|
||||
const container = this._window.document.querySelector(
|
||||
".container"
|
||||
) as HTMLDivElement;
|
||||
(
|
||||
this._window.document.querySelector("#image") as HTMLImageElement
|
||||
).style.width = `calc(100% * ${this.scaling})`;
|
||||
if (this.scaling > 1) {
|
||||
container.scrollLeft +=
|
||||
this.anchorPosition.left * (this.scaling - oldScale);
|
||||
container.scrollTop +=
|
||||
this.anchorPosition.top * (this.scaling - oldScale);
|
||||
}
|
||||
(
|
||||
this._window.document.querySelector(
|
||||
"#bigger-container"
|
||||
) as HTMLButtonElement
|
||||
).style.opacity = this.scaling === 10 ? "0.5" : "1";
|
||||
(
|
||||
this._window.document.querySelector(
|
||||
"#smaller-container"
|
||||
) as HTMLButtonElement
|
||||
).style.opacity = this.scaling === 0.1 ? "0.5" : "1";
|
||||
// (
|
||||
// this._window.document.querySelector("#image") as HTMLImageElement
|
||||
// ).style.cursor = this.scaling <= 1 ? "default" : "move";
|
||||
}
|
||||
|
||||
private setTitle() {
|
||||
this._window.document.querySelector("title").innerText = `${this.idx + 1}/${
|
||||
this.srcList.length
|
||||
}:${this.title}`;
|
||||
}
|
||||
|
||||
private setPin() {
|
||||
this._window.close();
|
||||
this.onInit(this.srcList, this.idx, this.title, !this.pined);
|
||||
}
|
||||
}
|
||||
|
||||
export default EditorImageViewer;
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
import { asBlob } from "html-docx-js-typescript";
|
||||
|
||||
// this runs in a webworker. accept input message
|
||||
// and return output message
|
||||
onmessage = ({ data: { type, jobId, message } }) => {
|
||||
if (type === "parseDocx") {
|
||||
console.log("DOCX Worker", type, jobId, message);
|
||||
asBlob(message)
|
||||
.then((blob) => {
|
||||
postMessage({ type: "parseDocxReturn", jobId, message: blob }, "*");
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,339 @@
|
|||
// The prosemirror imports are only for type hint
|
||||
import { Node, NodeType } from "prosemirror-model";
|
||||
import { Mark, MarkType, ResolvedPos, Attrs } from "prosemirror-model";
|
||||
import { EditorState, TextSelection } from "prosemirror-state";
|
||||
import { EditorView } from "prosemirror-view";
|
||||
|
||||
declare const _currentEditorInstance: {
|
||||
_editorCore: EditorCore;
|
||||
};
|
||||
|
||||
// Code from https://github.com/ueberdosis/tiptap/tree/main/packages/core/src/helpers
|
||||
|
||||
function objectIncludes(object1: any, object2: any) {
|
||||
const keys = Object.keys(object2);
|
||||
|
||||
if (!keys.length) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return !!keys.filter((key) => object2[key] === object1[key]).length;
|
||||
}
|
||||
|
||||
function findMarkInSet(
|
||||
marks: readonly Mark[],
|
||||
type: MarkType,
|
||||
attributes = {}
|
||||
) {
|
||||
return marks.find((item) => {
|
||||
return item.type === type && objectIncludes(item.attrs, attributes);
|
||||
});
|
||||
}
|
||||
|
||||
function isMarkInSet(marks: readonly Mark[], type: MarkType, attributes = {}) {
|
||||
return !!findMarkInSet(marks, type, attributes);
|
||||
}
|
||||
|
||||
function getMarkRange($pos: ResolvedPos, type: MarkType, attributes = {}) {
|
||||
if (!$pos || !type) {
|
||||
return;
|
||||
}
|
||||
|
||||
const start = $pos.parent.childAfter($pos.parentOffset);
|
||||
|
||||
if (!start.node) {
|
||||
return;
|
||||
}
|
||||
|
||||
const mark = findMarkInSet(start.node.marks, type, attributes);
|
||||
|
||||
if (!mark) {
|
||||
return;
|
||||
}
|
||||
|
||||
let startIndex = $pos.index();
|
||||
let startPos = $pos.start() + start.offset;
|
||||
let endIndex = startIndex + 1;
|
||||
let endPos = startPos + start.node.nodeSize;
|
||||
|
||||
findMarkInSet(start.node.marks, type, attributes);
|
||||
|
||||
while (
|
||||
startIndex > 0 &&
|
||||
mark.isInSet($pos.parent.child(startIndex - 1).marks)
|
||||
) {
|
||||
startIndex -= 1;
|
||||
startPos -= $pos.parent.child(startIndex).nodeSize;
|
||||
}
|
||||
|
||||
while (
|
||||
endIndex < $pos.parent.childCount &&
|
||||
isMarkInSet($pos.parent.child(endIndex).marks, type, attributes)
|
||||
) {
|
||||
endPos += $pos.parent.child(endIndex).nodeSize;
|
||||
endIndex += 1;
|
||||
}
|
||||
|
||||
return {
|
||||
from: startPos,
|
||||
to: endPos,
|
||||
};
|
||||
}
|
||||
|
||||
function getMarkRangeAtCursor(state: EditorState, type: MarkType) {
|
||||
const { selection } = state;
|
||||
const { $from, empty } = selection;
|
||||
|
||||
if (empty) {
|
||||
const start = $from.parent.childAfter($from.parentOffset);
|
||||
if (start.node) {
|
||||
const mark = start.node.marks.find(
|
||||
(mark) => mark.type.name === type.name
|
||||
);
|
||||
if (mark) {
|
||||
return getMarkRange($from, type, mark.attrs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function deleteRange(from: number, to: number) {
|
||||
return (state: EditorState, dispatch: EditorView["dispatch"]) => {
|
||||
const { tr } = state;
|
||||
console.log("Delete Node", from, to);
|
||||
tr.delete(from, to);
|
||||
dispatch(tr);
|
||||
};
|
||||
}
|
||||
|
||||
function deleteRangeAtCursor(searchType: MarkType) {
|
||||
return (state: EditorState, dispatch: EditorView["dispatch"]) => {
|
||||
const range = getMarkRangeAtCursor(state, searchType);
|
||||
if (range) {
|
||||
const from = range.from;
|
||||
const to = range.to;
|
||||
return deleteRange(from, to)(state, dispatch);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function replaceRange(
|
||||
from: number,
|
||||
to: number,
|
||||
text: string | undefined,
|
||||
type: MarkType,
|
||||
attrs: Attrs | string
|
||||
) {
|
||||
return (state: EditorState, dispatch: EditorView["dispatch"]) => {
|
||||
const { tr } = state;
|
||||
if (typeof attrs === "string") {
|
||||
attrs = JSON.parse(attrs) as Attrs;
|
||||
}
|
||||
|
||||
const node = state.schema.text(text || state.doc.textBetween(from, to), [
|
||||
type.create(attrs),
|
||||
]);
|
||||
console.log("Replace Node", from, to, node);
|
||||
tr.replaceWith(from, to, node);
|
||||
dispatch(tr);
|
||||
};
|
||||
}
|
||||
|
||||
function replaceRangeNode(
|
||||
from: number,
|
||||
to: number,
|
||||
text: string | undefined,
|
||||
nodeType: NodeType,
|
||||
nodeAttrs: Attrs | string,
|
||||
markType?: MarkType,
|
||||
markAttrs?: Attrs | string
|
||||
) {
|
||||
return (state: EditorState, dispatch: EditorView["dispatch"]) => {
|
||||
const { tr } = state;
|
||||
if (typeof nodeAttrs === "string") {
|
||||
nodeAttrs = JSON.parse(nodeAttrs) as Attrs;
|
||||
}
|
||||
if (typeof markAttrs === "string") {
|
||||
markAttrs = JSON.parse(markAttrs) as Attrs;
|
||||
}
|
||||
|
||||
const node = nodeType.create(
|
||||
nodeAttrs,
|
||||
state.schema.text(text || state.doc.textBetween(from, to)),
|
||||
markType ? [markType.create(markAttrs)] : []
|
||||
);
|
||||
console.log("Replace Node", from, to, node);
|
||||
tr.replaceWith(from, to, node);
|
||||
dispatch(tr);
|
||||
};
|
||||
}
|
||||
|
||||
function replaceRangeAtCursor(
|
||||
text: string | undefined,
|
||||
type: MarkType,
|
||||
attrs: Attrs | string,
|
||||
searchType: MarkType
|
||||
) {
|
||||
return (state: EditorState, dispatch: EditorView["dispatch"]) => {
|
||||
const range = getMarkRangeAtCursor(state, searchType);
|
||||
if (range) {
|
||||
const from = range.from;
|
||||
const to = range.to;
|
||||
return replaceRange(from, to, text, type, attrs)(state, dispatch);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function moveRange(from: number, to: number, delta: number) {
|
||||
return (state: EditorState, dispatch: EditorView["dispatch"]) => {
|
||||
const { tr, selection } = state;
|
||||
const _TextSelection =
|
||||
selection.constructor as unknown as typeof TextSelection;
|
||||
const slice = state.doc.slice(from, to);
|
||||
console.log("Move Node", from, to, delta, slice);
|
||||
tr.delete(from, to);
|
||||
tr.insert(from + delta, slice.content);
|
||||
tr.setSelection(_TextSelection.create(tr.doc, from + delta));
|
||||
tr.scrollIntoView();
|
||||
dispatch(tr);
|
||||
};
|
||||
}
|
||||
|
||||
function updateMarkRangeAtCursor(type: MarkType, attrs: Attrs) {
|
||||
return (state: EditorState, dispatch: EditorView["dispatch"]) => {
|
||||
const { tr, selection, doc } = state;
|
||||
let { from, to } = selection;
|
||||
const { $from, empty } = selection;
|
||||
|
||||
if (empty) {
|
||||
const range = getMarkRangeAtCursor(state, type);
|
||||
if (range) {
|
||||
from = range.from;
|
||||
to = range.to;
|
||||
}
|
||||
}
|
||||
|
||||
const hasMark = doc.rangeHasMark(from, to, type);
|
||||
|
||||
if (hasMark) {
|
||||
tr.removeMark(from, to, type);
|
||||
}
|
||||
|
||||
tr.addStoredMark(type.create(attrs));
|
||||
|
||||
if (to > from) {
|
||||
tr.addMark(from, to, type.create(attrs));
|
||||
}
|
||||
|
||||
dispatch(tr);
|
||||
};
|
||||
}
|
||||
|
||||
function removeMarkRangeAtCursor(type: MarkType) {
|
||||
return (state: EditorState, dispatch: EditorView["dispatch"]) => {
|
||||
const { tr, selection } = state;
|
||||
let { from, to } = selection;
|
||||
const { $from, empty } = selection;
|
||||
|
||||
if (empty) {
|
||||
const range = getMarkRangeAtCursor(state, type);
|
||||
if (range) {
|
||||
from = range.from;
|
||||
to = range.to;
|
||||
}
|
||||
}
|
||||
|
||||
tr.ensureMarks([]);
|
||||
if (to > from) {
|
||||
tr.removeMark(from, to, type);
|
||||
}
|
||||
dispatch(tr);
|
||||
};
|
||||
}
|
||||
|
||||
function getHeadingLevelInRange(from: number, to: number) {
|
||||
return (state: EditorState) => {
|
||||
let level = -1;
|
||||
state.doc.nodesBetween(from, to, (node, pos) => {
|
||||
if (node.type.name === "heading") {
|
||||
level = node.attrs.level;
|
||||
}
|
||||
});
|
||||
return level;
|
||||
};
|
||||
}
|
||||
|
||||
function updateHeadingsInRange(from: number, to: number, levelOffset: number) {
|
||||
return (state: EditorState, dispatch: EditorView["dispatch"]) => {
|
||||
let { tr } = state;
|
||||
state.doc.nodesBetween(from, to, (node, pos) => {
|
||||
if (node.type.name === "heading") {
|
||||
tr = tr.setNodeMarkup(pos, state.schema.nodes.heading, {
|
||||
level: node.attrs.level + levelOffset,
|
||||
});
|
||||
}
|
||||
});
|
||||
dispatch(tr);
|
||||
};
|
||||
}
|
||||
|
||||
function refocusEditor(callback: Function) {
|
||||
let scrollTop = document.querySelector(".editor-core")!.scrollTop;
|
||||
let input = document.createElement("input");
|
||||
input.style.position = "absolute";
|
||||
input.style.opacity = "0";
|
||||
document.body.append(input);
|
||||
input.focus();
|
||||
input.offsetTop;
|
||||
setTimeout(() => {
|
||||
(document.querySelector(".primary-editor") as any).focus();
|
||||
input.remove();
|
||||
document.querySelector(".editor-core")!.scrollTop = scrollTop;
|
||||
setTimeout(callback, 0);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
function updateImageDimensions(
|
||||
nodeID: string,
|
||||
width: number,
|
||||
height: number | undefined,
|
||||
state: EditorState,
|
||||
dispatch: EditorView["dispatch"]
|
||||
) {
|
||||
let { tr } = state;
|
||||
state.doc.descendants((node: Node, pos: number) => {
|
||||
if (node.type.name === "image" && node.attrs.nodeID === nodeID) {
|
||||
// tr.step(new SetAttrsStep(pos, { ...node.attrs, width, height }));
|
||||
// tr.setMeta("addToHistory", false);
|
||||
// tr.setMeta("system", true);
|
||||
tr.setNodeMarkup(
|
||||
pos,
|
||||
node.type,
|
||||
{ ...node.attrs, width, height },
|
||||
node.marks
|
||||
);
|
||||
dispatch(tr);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export const BetterNotesEditorAPI = {
|
||||
deleteRange,
|
||||
deleteRangeAtCursor,
|
||||
replaceRange,
|
||||
replaceRangeNode,
|
||||
replaceRangeAtCursor,
|
||||
moveRange,
|
||||
updateMarkRangeAtCursor,
|
||||
removeMarkRangeAtCursor,
|
||||
refocusEditor,
|
||||
updateImageDimensions,
|
||||
getHeadingLevelInRange,
|
||||
updateHeadingsInRange,
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
window.BetterNotesEditorAPI = BetterNotesEditorAPI;
|
||||
|
|
@ -0,0 +1,236 @@
|
|||
import { config } from "../package.json";
|
||||
import { initLocale } from "./utils/locale";
|
||||
import { registerPrefsWindow } from "./modules/preferenceWindow";
|
||||
import { registerNoteLinkProxyHandler } from "./modules/noteLink";
|
||||
import { registerEditorInstanceHook } from "./modules/editor/initalize";
|
||||
import { initTemplates } from "./modules/template/controller";
|
||||
import { registerMenus } from "./modules/menu";
|
||||
import {
|
||||
activateWorkspaceTab,
|
||||
deActivateWorkspaceTab,
|
||||
registerWorkspaceTab,
|
||||
TAB_TYPE,
|
||||
} from "./modules/workspace/tab";
|
||||
import {
|
||||
initWorkspaceEditor,
|
||||
togglePreviewPane,
|
||||
updateOutline,
|
||||
} from "./modules/workspace/content";
|
||||
import { registerNotify } from "./modules/notify";
|
||||
import { showWorkspaceWindow } from "./modules/workspace/window";
|
||||
import {
|
||||
checkReaderAnnotationButton,
|
||||
registerReaderInitializer,
|
||||
unregisterReaderInitializer,
|
||||
} from "./modules/reader";
|
||||
|
||||
async function onStartup() {
|
||||
await Promise.all([
|
||||
Zotero.initializationPromise,
|
||||
Zotero.unlockPromise,
|
||||
Zotero.uiReadyPromise,
|
||||
]);
|
||||
initLocale();
|
||||
ztoolkit.ProgressWindow.setIconURI(
|
||||
"default",
|
||||
`chrome://${config.addonRef}/content/icons/favicon.png`
|
||||
);
|
||||
|
||||
registerNoteLinkProxyHandler();
|
||||
|
||||
registerNotify(["tab", "item"]);
|
||||
|
||||
registerEditorInstanceHook();
|
||||
|
||||
initTemplates();
|
||||
|
||||
registerMenus();
|
||||
|
||||
registerWorkspaceTab();
|
||||
|
||||
registerReaderInitializer();
|
||||
|
||||
registerPrefsWindow();
|
||||
|
||||
addon.api.sync.setSync();
|
||||
}
|
||||
|
||||
function onShutdown(): void {
|
||||
ztoolkit.unregisterAll();
|
||||
unregisterReaderInitializer();
|
||||
// Remove addon object
|
||||
addon.data.alive = false;
|
||||
delete Zotero[config.addonInstance];
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is just an example of dispatcher for Notify events.
|
||||
* Any operations should be placed in a function to keep this funcion clear.
|
||||
*/
|
||||
function onNotify(
|
||||
event: string,
|
||||
type: string,
|
||||
ids: number[] | string[],
|
||||
extraData: { [key: string]: any }
|
||||
) {
|
||||
// Workspace tab select/unselect callback
|
||||
if (event === "select" && type === "tab") {
|
||||
if (extraData[ids[0]].type == TAB_TYPE) {
|
||||
activateWorkspaceTab();
|
||||
} else {
|
||||
deActivateWorkspaceTab();
|
||||
}
|
||||
}
|
||||
// Workspace main note update
|
||||
if (event === "modify" && type === "item") {
|
||||
if ((ids as number[]).includes(addon.data.workspace.mainId)) {
|
||||
addon.data.workspace.tab.active &&
|
||||
updateOutline(addon.data.workspace.tab.container!);
|
||||
addon.data.workspace.window.active &&
|
||||
updateOutline(addon.data.workspace.window.container!);
|
||||
}
|
||||
}
|
||||
if (event === "modify" && type === "item") {
|
||||
const modifiedNotes = Zotero.Items.get(ids).filter((item) => item.isNote());
|
||||
if (modifiedNotes.length) {
|
||||
addon.api.sync.doSync(modifiedNotes, {
|
||||
quiet: true,
|
||||
skipActive: true,
|
||||
reason: "item-modify",
|
||||
});
|
||||
}
|
||||
}
|
||||
// Reader annotation buttons update
|
||||
if (event === "add" && type === "item") {
|
||||
const annotationItems = Zotero.Items.get(ids as number[]).filter((item) =>
|
||||
item.isAnnotation()
|
||||
);
|
||||
if (annotationItems.length === 0) {
|
||||
checkReaderAnnotationButton(annotationItems);
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is just an example of dispatcher for Preference UI events.
|
||||
* Any operations should be placed in a function to keep this funcion clear.
|
||||
* @param type event type
|
||||
* @param data event data
|
||||
*/
|
||||
async function onPrefsEvent(type: string, data: { [key: string]: any }) {
|
||||
switch (type) {
|
||||
case "load":
|
||||
// registerPrefsScripts(data.window);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
function onOpenNote(
|
||||
noteId: number,
|
||||
mode: "auto" | "preview" | "workspace" | "standalone" = "auto",
|
||||
options: {
|
||||
lineIndex?: number;
|
||||
} = {}
|
||||
) {
|
||||
const noteItem = Zotero.Items.get(noteId);
|
||||
if (!noteItem?.isNote()) {
|
||||
ztoolkit.log(`onOpenNote: ${noteId} is not a note.`);
|
||||
return;
|
||||
}
|
||||
if (mode === "auto") {
|
||||
if (noteId === addon.data.workspace.mainId) {
|
||||
mode = "workspace";
|
||||
} else if (
|
||||
addon.data.workspace.tab.active ||
|
||||
addon.data.workspace.window.active
|
||||
) {
|
||||
mode = "preview";
|
||||
} else {
|
||||
mode = "standalone";
|
||||
}
|
||||
}
|
||||
switch (mode) {
|
||||
case "preview":
|
||||
addon.hooks.onSetWorkspaceNote(noteId, "preview", options);
|
||||
break;
|
||||
case "workspace":
|
||||
addon.hooks.onSetWorkspaceNote(noteId, "main", options);
|
||||
case "standalone":
|
||||
ZoteroPane.openNoteWindow(noteId);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function onSetWorkspaceNote(
|
||||
noteId: number,
|
||||
type: "main" | "preview" = "main",
|
||||
options: {
|
||||
lineIndex?: number;
|
||||
} = {}
|
||||
) {
|
||||
if (type === "main") {
|
||||
addon.data.workspace.mainId = noteId;
|
||||
addon.data.workspace.tab.active &&
|
||||
updateOutline(addon.data.workspace.tab.container!);
|
||||
addon.data.workspace.window.active &&
|
||||
updateOutline(addon.data.workspace.window.container!);
|
||||
}
|
||||
if (addon.data.workspace.window.active) {
|
||||
initWorkspaceEditor(
|
||||
addon.data.workspace.window.container,
|
||||
type,
|
||||
noteId,
|
||||
options
|
||||
);
|
||||
type === "preview" &&
|
||||
togglePreviewPane(addon.data.workspace.window.container, "open");
|
||||
addon.data.workspace.window.window?.focus();
|
||||
}
|
||||
if (addon.data.workspace.tab.active) {
|
||||
initWorkspaceEditor(
|
||||
addon.data.workspace.tab.container,
|
||||
type,
|
||||
noteId,
|
||||
options
|
||||
);
|
||||
type === "preview" &&
|
||||
togglePreviewPane(addon.data.workspace.tab.container, "open");
|
||||
Zotero_Tabs.select(addon.data.workspace.tab.id!);
|
||||
}
|
||||
}
|
||||
|
||||
function onOpenWorkspace(type: "tab" | "window" = "tab") {
|
||||
if (type === "window") {
|
||||
if (addon.data.workspace.window.active) {
|
||||
addon.data.workspace.window.window?.focus();
|
||||
return;
|
||||
}
|
||||
showWorkspaceWindow();
|
||||
return;
|
||||
}
|
||||
if (type === "tab") {
|
||||
// selecting tab will auto load the workspace content
|
||||
Zotero_Tabs.select(addon.data.workspace.tab.id!);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Add your hooks here. For element click, etc.
|
||||
// Keep in mind hooks only do dispatch. Don't add code that does real jobs in hooks.
|
||||
// Otherwise the code would be hard to read and maintian.
|
||||
|
||||
export default {
|
||||
onStartup,
|
||||
onShutdown,
|
||||
onNotify,
|
||||
onPrefsEvent,
|
||||
onOpenNote,
|
||||
onSetWorkspaceNote,
|
||||
onOpenWorkspace,
|
||||
};
|
||||
42
src/index.ts
|
|
@ -1,18 +1,28 @@
|
|||
/*
|
||||
* This file is the esbuild entrance.
|
||||
*/
|
||||
import { BasicTool } from "zotero-plugin-toolkit/dist/basic";
|
||||
import Addon from "./addon";
|
||||
import { config } from "../package.json";
|
||||
|
||||
import BetterNotes from "./addon";
|
||||
const basicTool = new BasicTool();
|
||||
|
||||
const addon = new BetterNotes();
|
||||
Zotero.BetterNotes = addon;
|
||||
// For compatibility
|
||||
Zotero.Knowledge4Zotero = addon;
|
||||
|
||||
window.addEventListener(
|
||||
"load",
|
||||
async function (e) {
|
||||
Zotero.BetterNotes.ZoteroEvents.onInit();
|
||||
},
|
||||
false
|
||||
);
|
||||
if (!basicTool.getGlobal("Zotero")[config.addonInstance]) {
|
||||
// Set global variables
|
||||
_globalThis.Zotero = basicTool.getGlobal("Zotero");
|
||||
_globalThis.ZoteroPane = basicTool.getGlobal("ZoteroPane");
|
||||
_globalThis.Zotero_Tabs = basicTool.getGlobal("Zotero_Tabs");
|
||||
_globalThis.window = basicTool.getGlobal("window");
|
||||
_globalThis.document = basicTool.getGlobal("document");
|
||||
_globalThis.OS = basicTool.getGlobal("OS") as typeof OS;
|
||||
_globalThis.addon = new Addon();
|
||||
_globalThis.ztoolkit = addon.data.ztoolkit;
|
||||
ztoolkit.basicOptions.log.prefix = `[${config.addonName}]`;
|
||||
ztoolkit.basicOptions.log.disableConsole = addon.data.env === "production";
|
||||
ztoolkit.UI.basicOptions.ui.enableElementJSONLog =
|
||||
addon.data.env === "development";
|
||||
ztoolkit.UI.basicOptions.ui.enableElementDOMLog =
|
||||
addon.data.env === "development";
|
||||
ztoolkit.basicOptions.debug.disableDebugBridgePassword =
|
||||
addon.data.env === "development";
|
||||
Zotero[config.addonInstance] = addon;
|
||||
// Trigger addon hook for initialization
|
||||
addon.hooks.onStartup();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +0,0 @@
|
|||
/*
|
||||
* This file defines the modules' base class.
|
||||
*/
|
||||
|
||||
|
||||
import BetterNotes from "./addon";
|
||||
|
||||
class AddonBase {
|
||||
protected _Addon: BetterNotes;
|
||||
constructor(parent: BetterNotes) {
|
||||
this._Addon = parent;
|
||||
}
|
||||
}
|
||||
|
||||
export default AddonBase;
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
import { getPref } from "../../utils/prefs";
|
||||
|
||||
export function initEditorImagePreviewer(editor: Zotero.EditorInstance) {
|
||||
const openPreview = (e: MouseEvent) => {
|
||||
const imgs = editor._iframeWindow.document
|
||||
.querySelector(".primary-editor")
|
||||
?.querySelectorAll("img");
|
||||
if (!imgs) {
|
||||
return;
|
||||
}
|
||||
const imageList = Array.from(imgs);
|
||||
addon.api.window.showImageViewer(
|
||||
imageList.map((elem) => elem.src),
|
||||
imageList.indexOf(e.target as HTMLImageElement),
|
||||
editor._item.getNoteTitle()
|
||||
);
|
||||
};
|
||||
editor._iframeWindow.document.addEventListener("dblclick", (e) => {
|
||||
if ((e.target as HTMLElement).tagName === "IMG") {
|
||||
openPreview(e);
|
||||
}
|
||||
});
|
||||
editor._iframeWindow.document.addEventListener("click", (e) => {
|
||||
if ((e.target as HTMLElement).tagName === "IMG" && e.ctrlKey) {
|
||||
openPreview(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
import { initEditorImagePreviewer } from "./image";
|
||||
import { injectEditorCSS, injectEditorScripts } from "./inject";
|
||||
import { initEditorPopup } from "./popup";
|
||||
import { initEditorToolbar } from "./toolbar";
|
||||
|
||||
export function registerEditorInstanceHook() {
|
||||
Zotero.Notes.registerEditorInstance = new Proxy(
|
||||
Zotero.Notes.registerEditorInstance,
|
||||
{
|
||||
apply: (
|
||||
target,
|
||||
thisArg,
|
||||
argumentsList: [instance: Zotero.EditorInstance]
|
||||
) => {
|
||||
target.apply(thisArg, argumentsList);
|
||||
argumentsList.forEach(onEditorInstanceCreated);
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async function onEditorInstanceCreated(editor: Zotero.EditorInstance) {
|
||||
await editor._initPromise;
|
||||
|
||||
// item.getNote may not be initialized yet
|
||||
if (Zotero.ItemTypes.getID("note") !== editor._item.itemTypeID) {
|
||||
return;
|
||||
}
|
||||
await injectEditorScripts(editor._iframeWindow);
|
||||
injectEditorCSS(editor._iframeWindow);
|
||||
initEditorImagePreviewer(editor);
|
||||
await initEditorToolbar(editor);
|
||||
initEditorPopup(editor);
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
import { getFileContent } from "../../utils/str";
|
||||
|
||||
export async function injectEditorScripts(win: Window) {
|
||||
ztoolkit.UI.appendElement(
|
||||
{
|
||||
tag: "script",
|
||||
id: "betternotes-script",
|
||||
properties: {
|
||||
innerHTML: await getFileContent(
|
||||
rootURI + "chrome/content/scripts/editorScript.js"
|
||||
),
|
||||
},
|
||||
ignoreIfExists: true,
|
||||
},
|
||||
win.document.head
|
||||
);
|
||||
}
|
||||
|
||||
export function injectEditorCSS(win: Window) {
|
||||
ztoolkit.UI.appendElement(
|
||||
{
|
||||
tag: "style",
|
||||
id: "betternotes-style",
|
||||
properties: {
|
||||
innerHTML: `
|
||||
.primary-editor > h1::before {
|
||||
margin-left: -64px !important;
|
||||
padding-left: 40px !important;
|
||||
content: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%2218px%22%20height%3D%2218px%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2015.56%22%3E%3Cdefs%3E%3Cstyle%3E.cls-1%7Bfill%3A%23666%3B%7D%3C%2Fstyle%3E%3C%2Fdefs%3E%3Ctitle%3E%E6%9C%AA%E6%A0%87%E9%A2%98-1%3C%2Ftitle%3E%3Cpath%20class%3D%22cls-1%22%20d%3D%22M12.29%2C16.8H11.14V12.33H6.07V16.8H4.92V7H6.07v4.3h5.07V7h1.15Z%22%20transform%3D%22translate(0%20-4.22)%22%2F%3E%3Cpath%20class%3D%22cls-1%22%20d%3D%22M18.05%2C16.8H16.93V8.41a4%2C4%2C0%2C0%2C1-.9.53%2C6.52%2C6.52%2C0%2C0%2C1-1.14.44l-.32-1a8.2%2C8.2%2C0%2C0%2C0%2C1.67-.67%2C6.31%2C6.31%2C0%2C0%2C0%2C1.39-1h.42Z%22%20transform%3D%22translate(0%20-4.22)%22%2F%3E%3Cpath%20class%3D%22cls-1%22%20d%3D%22M21%2C5a2.25%2C2.25%2C0%2C0%2C1%2C2.25%2C2.25v9.56A2.25%2C2.25%2C0%2C0%2C1%2C21%2C19H3A2.25%2C2.25%2C0%2C0%2C1%2C.75%2C16.78V7.22A2.25%2C2.25%2C0%2C0%2C1%2C3%2C5H21m0-.75H3a3%2C3%2C0%2C0%2C0-3%2C3v9.56a3%2C3%2C0%2C0%2C0%2C3%2C3H21a3%2C3%2C0%2C0%2C0%2C3-3V7.22a3%2C3%2C0%2C0%2C0-3-3Z%22%20transform%3D%22translate(0%20-4.22)%22%2F%3E%3C%2Fsvg%3E") !important;
|
||||
}
|
||||
.primary-editor > h2::before {
|
||||
margin-left: -64px !important;
|
||||
padding-left: 40px !important;
|
||||
content: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%2218px%22%20height%3D%2218px%22%20%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2015.56%22%3E%3Cdefs%3E%3Cstyle%3E.a%7Bfill%3A%23666%3B%7D%3C%2Fstyle%3E%3C%2Fdefs%3E%3Cpath%20class%3D%22a%22%20d%3D%22M11.17%2C16.8H10V12.33H5V16.8H3.8V7H5v4.3H10V7h1.15Z%22%20transform%3D%22translate(0%20-4.22)%22%2F%3E%3Cpath%20class%3D%22a%22%20d%3D%22M14.14%2C16.8v-.48a4.1%2C4.1%2C0%2C0%2C1%2C.14-1.11%2C2.86%2C2.86%2C0%2C0%2C1%2C.45-.91%2C5.49%2C5.49%2C0%2C0%2C1%2C.83-.86c.33-.29.75-.61%2C1.24-1a7.43%2C7.43%2C0%2C0%2C0%2C.9-.73%2C3.9%2C3.9%2C0%2C0%2C0%2C.57-.7%2C2.22%2C2.22%2C0%2C0%2C0%2C.3-.66%2C2.87%2C2.87%2C0%2C0%2C0%2C.11-.77%2C1.89%2C1.89%2C0%2C0%2C0-.47-1.32%2C1.66%2C1.66%2C0%2C0%2C0-1.28-.5A3.17%2C3.17%2C0%2C0%2C0%2C15.7%2C8a3.49%2C3.49%2C0%2C0%2C0-1.08.76l-.68-.65a4.26%2C4.26%2C0%2C0%2C1%2C1.39-1A4%2C4%2C0%2C0%2C1%2C17%2C6.84a2.62%2C2.62%2C0%2C0%2C1%2C2.83%2C2.67%2C3.58%2C3.58%2C0%2C0%2C1-.15%2C1%2C3.09%2C3.09%2C0%2C0%2C1-.41.9%2C5.53%2C5.53%2C0%2C0%2C1-.67.81%2C9%2C9%2C0%2C0%2C1-.95.79c-.46.32-.84.59-1.13.82a4.68%2C4.68%2C0%2C0%2C0-.71.64%2C2%2C2%2C0%2C0%2C0-.38.6%2C2.08%2C2.08%2C0%2C0%2C0-.11.69h4.88v1Z%22%20transform%3D%22translate(0%20-4.22)%22%2F%3E%3Cpath%20class%3D%22a%22%20d%3D%22M21%2C5a2.25%2C2.25%2C0%2C0%2C1%2C2.25%2C2.25v9.56A2.25%2C2.25%2C0%2C0%2C1%2C21%2C19H3A2.25%2C2.25%2C0%2C0%2C1%2C.75%2C16.78V7.22A2.25%2C2.25%2C0%2C0%2C1%2C3%2C5H21m0-.75H3a3%2C3%2C0%2C0%2C0-3%2C3v9.56a3%2C3%2C0%2C0%2C0%2C3%2C3H21a3%2C3%2C0%2C0%2C0%2C3-3V7.22a3%2C3%2C0%2C0%2C0-3-3Z%22%20transform%3D%22translate(0%20-4.22)%22%2F%3E%3C%2Fsvg%3E") !important;
|
||||
}
|
||||
.primary-editor > h3::before {
|
||||
margin-left: -64px !important;
|
||||
padding-left: 40px !important;
|
||||
content: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%2218px%22%20height%3D%2218px%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2015.56%22%3E%3Cdefs%3E%3Cstyle%3E.cls-1%7Bfill%3A%23666%3B%7D%3C%2Fstyle%3E%3C%2Fdefs%3E%3Cpath%20class%3D%22cls-1%22%20d%3D%22M11.17%2C16.8H10V12.33H5V16.8H3.8V7H5v4.3H10V7h1.15Z%22%20transform%3D%22translate(0%20-4.22)%22%2F%3E%3Cpath%20class%3D%22cls-1%22%20d%3D%22M14%2C16.14l.51-.8a4.75%2C4.75%2C0%2C0%2C0%2C1.1.52%2C4.27%2C4.27%2C0%2C0%2C0%2C1.12.16%2C2.29%2C2.29%2C0%2C0%2C0%2C1.64-.52A1.77%2C1.77%2C0%2C0%2C0%2C19%2C14.17a1.7%2C1.7%2C0%2C0%2C0-.68-1.48%2C3.6%2C3.6%2C0%2C0%2C0-2.06-.48H15.4v-1h.77A3%2C3%2C0%2C0%2C0%2C18%2C10.81a1.65%2C1.65%2C0%2C0%2C0%2C.6-1.41%2C1.47%2C1.47%2C0%2C0%2C0-.47-1.19A1.67%2C1.67%2C0%2C0%2C0%2C17%2C7.79a3.33%2C3.33%2C0%2C0%2C0-2.08.73l-.59-.75a4.4%2C4.4%2C0%2C0%2C1%2C1.28-.71A4.35%2C4.35%2C0%2C0%2C1%2C17%2C6.84a2.84%2C2.84%2C0%2C0%2C1%2C2%2C.65%2C2.21%2C2.21%2C0%2C0%2C1%2C.74%2C1.78%2C2.35%2C2.35%2C0%2C0%2C1-.49%2C1.5%2C2.7%2C2.7%2C0%2C0%2C1-1.46.89v0a2.74%2C2.74%2C0%2C0%2C1%2C1.65.74%2C2.15%2C2.15%2C0%2C0%2C1%2C.66%2C1.65%2C2.64%2C2.64%2C0%2C0%2C1-.9%2C2.12%2C3.44%2C3.44%2C0%2C0%2C1-2.34.78%2C5.3%2C5.3%2C0%2C0%2C1-1.48-.2A5%2C5%2C0%2C0%2C1%2C14%2C16.14Z%22%20transform%3D%22translate(0%20-4.22)%22%2F%3E%3Cpath%20class%3D%22cls-1%22%20d%3D%22M21%2C5a2.25%2C2.25%2C0%2C0%2C1%2C2.25%2C2.25v9.56A2.25%2C2.25%2C0%2C0%2C1%2C21%2C19H3A2.25%2C2.25%2C0%2C0%2C1%2C.75%2C16.78V7.22A2.25%2C2.25%2C0%2C0%2C1%2C3%2C5H21m0-.75H3a3%2C3%2C0%2C0%2C0-3%2C3v9.56a3%2C3%2C0%2C0%2C0%2C3%2C3H21a3%2C3%2C0%2C0%2C0%2C3-3V7.22a3%2C3%2C0%2C0%2C0-3-3Z%22%20transform%3D%22translate(0%20-4.22)%22%2F%3E%3C%2Fsvg%3E") !important;
|
||||
}
|
||||
.primary-editor > h4::before {
|
||||
margin-left: -64px !important;
|
||||
padding-left: 40px !important;
|
||||
content: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%2218px%22%20height%3D%2218px%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2015.56%22%3E%3Cdefs%3E%3Cstyle%3E.cls-1%7Bfill%3A%23666%3B%7D%3C%2Fstyle%3E%3C%2Fdefs%3E%3Cpath%20class%3D%22cls-1%22%20d%3D%22M11.17%2C16.8H10V12.33H5V16.8H3.8V7H5v4.3H10V7h1.15Z%22%20transform%3D%22translate(0%20-4.22)%22%2F%3E%3Cpath%20class%3D%22cls-1%22%20d%3D%22M19.43%2C6.92v6.59h1.05v1.05H19.43V16.9H18.31V14.56H13.66v-1c.43-.49.87-1%2C1.31-1.57s.87-1.13%2C1.27-1.7S17%2C9.14%2C17.36%2C8.57a16.51%2C16.51%2C0%2C0%2C0%2C.86-1.65Zm-4.49%2C6.59h3.37V8.63c-.34.61-.67%2C1.15-1%2C1.63s-.6.91-.87%2C1.3-.56.74-.81%2C1Z%22%20transform%3D%22translate(0%20-4.22)%22%2F%3E%3Cpath%20class%3D%22cls-1%22%20d%3D%22M21%2C5a2.25%2C2.25%2C0%2C0%2C1%2C2.25%2C2.25v9.56A2.25%2C2.25%2C0%2C0%2C1%2C21%2C19H3A2.25%2C2.25%2C0%2C0%2C1%2C.75%2C16.78V7.22A2.25%2C2.25%2C0%2C0%2C1%2C3%2C5H21m0-.75H3a3%2C3%2C0%2C0%2C0-3%2C3v9.56a3%2C3%2C0%2C0%2C0%2C3%2C3H21a3%2C3%2C0%2C0%2C0%2C3-3V7.22a3%2C3%2C0%2C0%2C0-3-3Z%22%20transform%3D%22translate(0%20-4.22)%22%2F%3E%3C%2Fsvg%3E") !important;
|
||||
}
|
||||
.primary-editor > h5::before {
|
||||
margin-left: -64px !important;
|
||||
padding-left: 40px !important;
|
||||
content: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%2218px%22%20height%3D%2218px%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2015.56%22%3E%3Cdefs%3E%3Cstyle%3E.cls-1%7Bfill%3A%23666%3B%7D%3C%2Fstyle%3E%3C%2Fdefs%3E%3Cpath%20class%3D%22cls-1%22%20d%3D%22M11.17%2C16.8H10V12.33H5V16.8H3.8V7H5v4.3H10V7h1.15Z%22%20transform%3D%22translate(0%20-4.22)%22%2F%3E%3Cpath%20class%3D%22cls-1%22%20d%3D%22M14%2C16l.58-.76a3.67%2C3.67%2C0%2C0%2C0%2C1%2C.58A3.44%2C3.44%2C0%2C0%2C0%2C16.8%2C16a2.17%2C2.17%2C0%2C0%2C0%2C1.58-.6A2%2C2%2C0%2C0%2C0%2C19%2C13.88a1.85%2C1.85%2C0%2C0%2C0-.64-1.5%2C2.83%2C2.83%2C0%2C0%2C0-1.86-.54c-.27%2C0-.55%2C0-.86%2C0s-.58%2C0-.81.06L15.17%2C7H19.7V8H16.14l-.2%2C2.88.47%2C0h.43a3.5%2C3.5%2C0%2C0%2C1%2C2.43.79%2C2.74%2C2.74%2C0%2C0%2C1%2C.88%2C2.16%2C3%2C3%2C0%2C0%2C1-.94%2C2.3%2C3.41%2C3.41%2C0%2C0%2C1-2.4.87%2C4.45%2C4.45%2C0%2C0%2C1-1.5-.24A4.81%2C4.81%2C0%2C0%2C1%2C14%2C16Z%22%20transform%3D%22translate(0%20-4.22)%22%2F%3E%3Cpath%20class%3D%22cls-1%22%20d%3D%22M21%2C5a2.25%2C2.25%2C0%2C0%2C1%2C2.25%2C2.25v9.56A2.25%2C2.25%2C0%2C0%2C1%2C21%2C19H3A2.25%2C2.25%2C0%2C0%2C1%2C.75%2C16.78V7.22A2.25%2C2.25%2C0%2C0%2C1%2C3%2C5H21m0-.75H3a3%2C3%2C0%2C0%2C0-3%2C3v9.56a3%2C3%2C0%2C0%2C0%2C3%2C3H21a3%2C3%2C0%2C0%2C0%2C3-3V7.22a3%2C3%2C0%2C0%2C0-3-3Z%22%20transform%3D%22translate(0%20-4.22)%22%2F%3E%3C%2Fsvg%3E") !important;
|
||||
}
|
||||
.primary-editor > h6::before {
|
||||
margin-left: -64px !important;
|
||||
padding-left: 40px !important;
|
||||
content: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%2218px%22%20height%3D%2218px%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2015.56%22%3E%3Cdefs%3E%3Cstyle%3E.cls-1%7Bfill%3A%23666%3B%7D%3C%2Fstyle%3E%3C%2Fdefs%3E%3Cpath%20class%3D%22cls-1%22%20d%3D%22M11.17%2C16.8H10V12.33H5V16.8H3.8V7H5v4.3H10V7h1.15Z%22%20transform%3D%22translate(0%20-4.22)%22%2F%3E%3Cpath%20class%3D%22cls-1%22%20d%3D%22M20.18%2C13.7a3.24%2C3.24%2C0%2C0%2C1-.88%2C2.38%2C2.94%2C2.94%2C0%2C0%2C1-2.2.9%2C2.69%2C2.69%2C0%2C0%2C1-2.31-1.17A5.59%2C5.59%2C0%2C0%2C1%2C14%2C12.49a12.18%2C12.18%2C0%2C0%2C1%2C.2-2.14%2C5.16%2C5.16%2C0%2C0%2C1%2C.84-2A3.65%2C3.65%2C0%2C0%2C1%2C16.27%2C7.2%2C3.71%2C3.71%2C0%2C0%2C1%2C18%2C6.84%2C3.14%2C3.14%2C0%2C0%2C1%2C19%2C7a3.59%2C3.59%2C0%2C0%2C1%2C1%2C.5l-.56.77a2.3%2C2.3%2C0%2C0%2C0-1.49-.48A2.3%2C2.3%2C0%2C0%2C0%2C16.79%2C8a3%2C3%2C0%2C0%2C0-.92.85%2C3.79%2C3.79%2C0%2C0%2C0-.56%2C1.25%2C6.56%2C6.56%2C0%2C0%2C0-.19%2C1.65h0a2.61%2C2.61%2C0%2C0%2C1%2C1-.84%2C2.91%2C2.91%2C0%2C0%2C1%2C1.23-.28%2C2.63%2C2.63%2C0%2C0%2C1%2C2%2C.85A3.09%2C3.09%2C0%2C0%2C1%2C20.18%2C13.7ZM19%2C13.78a2.28%2C2.28%2C0%2C0%2C0-.5-1.62%2C1.67%2C1.67%2C0%2C0%2C0-1.29-.54%2C2%2C2%2C0%2C0%2C0-1.5.58%2C2%2C2%2C0%2C0%2C0-.56%2C1.4%2C2.65%2C2.65%2C0%2C0%2C0%2C.55%2C1.74%2C1.85%2C1.85%2C0%2C0%2C0%2C2.78.1A2.38%2C2.38%2C0%2C0%2C0%2C19%2C13.78Z%22%20transform%3D%22translate(0%20-4.22)%22%2F%3E%3Cpath%20class%3D%22cls-1%22%20d%3D%22M21%2C5a2.25%2C2.25%2C0%2C0%2C1%2C2.25%2C2.25v9.56A2.25%2C2.25%2C0%2C0%2C1%2C21%2C19H3A2.25%2C2.25%2C0%2C0%2C1%2C.75%2C16.78V7.22A2.25%2C2.25%2C0%2C0%2C1%2C3%2C5H21m0-.75H3a3%2C3%2C0%2C0%2C0-3%2C3v9.56a3%2C3%2C0%2C0%2C0%2C3%2C3H21a3%2C3%2C0%2C0%2C0%2C3-3V7.22a3%2C3%2C0%2C0%2C0-3-3Z%22%20transform%3D%22translate(0%20-4.22)%22%2F%3E%3C%2Fsvg%3E") !important;
|
||||
}
|
||||
.primary-editor > p, .primary-editor h1, .primary-editor h2, .primary-editor h3, .primary-editor h4, .primary-editor h5, .primary-editor h6, .primary-editor pre, .primary-editor blockquote, .primary-editor table, .primary-editor ul, .primary-editor ol, .primary-editor hr{
|
||||
max-width: unset
|
||||
}
|
||||
`,
|
||||
},
|
||||
ignoreIfExists: true,
|
||||
},
|
||||
win.document.head
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,257 @@
|
|||
import { ICONS } from "../../utils/config";
|
||||
import {
|
||||
del,
|
||||
getEditorCore,
|
||||
getLineAtCursor,
|
||||
getPositionAtLine,
|
||||
getURLAtCursor,
|
||||
insert,
|
||||
updateImageDimensionsAtCursor,
|
||||
updateURLAtCursor,
|
||||
} from "../../utils/editor";
|
||||
import { getNoteLink, getNoteLinkParams } from "../../utils/link";
|
||||
import { getString } from "../../utils/locale";
|
||||
|
||||
export function initEditorPopup(editor: Zotero.EditorInstance) {
|
||||
const ob = new (ztoolkit.getGlobal("MutationObserver"))((muts) => {
|
||||
for (const mut of muts) {
|
||||
ztoolkit.log(mut);
|
||||
if (
|
||||
(mut.addedNodes.length &&
|
||||
(mut.addedNodes[0] as HTMLElement).querySelector(".link-popup")) ||
|
||||
(mut.attributeName === "href" &&
|
||||
mut.target.parentElement?.classList.contains("link"))
|
||||
) {
|
||||
updateEditorLinkPopup(editor);
|
||||
} else if (
|
||||
mut.addedNodes.length &&
|
||||
(mut.addedNodes[0] as HTMLElement).querySelector(".image-popup")
|
||||
) {
|
||||
updateEditorImagePopup(editor);
|
||||
}
|
||||
}
|
||||
});
|
||||
ob.observe(
|
||||
editor._iframeWindow.document.querySelector(".relative-container")!,
|
||||
{
|
||||
subtree: true,
|
||||
childList: true,
|
||||
attributes: true,
|
||||
attributeFilter: ["href"],
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async function updateEditorLinkPopup(editor: Zotero.EditorInstance) {
|
||||
const _window = editor._iframeWindow;
|
||||
const link = getURLAtCursor(editor);
|
||||
const linkParams = getNoteLinkParams(link);
|
||||
const linkNote = linkParams.noteItem;
|
||||
const editorNote = editor._item;
|
||||
// If the note is invalid, we remove the buttons
|
||||
if (linkNote) {
|
||||
const insertButton = ztoolkit.UI.createElement(_window.document, "button", {
|
||||
id: "link-popup-insert",
|
||||
properties: {
|
||||
title: `Import Linked Note: ${linkNote.getNoteTitle()}`,
|
||||
innerHTML: ICONS["embedLinkContent"],
|
||||
},
|
||||
classList: ["link-popup-extra"],
|
||||
removeIfExists: true,
|
||||
listeners: [
|
||||
{
|
||||
type: "click",
|
||||
listener: async (e) => {
|
||||
if (!linkParams.ignore) {
|
||||
const templateText = await addon.api.template.runTemplate(
|
||||
"[QuickImportV2]",
|
||||
"link, noteItem",
|
||||
[link, editorNote]
|
||||
);
|
||||
// auto insert to anchor position
|
||||
updateURLAtCursor(
|
||||
editor,
|
||||
undefined,
|
||||
getNoteLink(
|
||||
linkNote,
|
||||
Object.assign({}, linkParams, { ignore: true })
|
||||
)!
|
||||
);
|
||||
insert(editor, templateText);
|
||||
} else {
|
||||
updateURLAtCursor(
|
||||
editor,
|
||||
undefined,
|
||||
getNoteLink(
|
||||
linkNote,
|
||||
Object.assign({}, linkParams, { ignore: null })
|
||||
)!
|
||||
);
|
||||
const lineIndex = getLineAtCursor(editor);
|
||||
del(
|
||||
editor,
|
||||
getPositionAtLine(editor, lineIndex),
|
||||
getPositionAtLine(editor, lineIndex + 1)
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const updateButton = ztoolkit.UI.createElement(_window.document, "button", {
|
||||
id: "link-popup-update",
|
||||
properties: {
|
||||
title: `Update Link Text: ${linkNote.getNoteTitle()}`,
|
||||
innerHTML: ICONS["updateLinkText"],
|
||||
},
|
||||
classList: ["link-popup-extra"],
|
||||
removeIfExists: true,
|
||||
listeners: [
|
||||
{
|
||||
type: "click",
|
||||
listener: async (e) => {
|
||||
updateURLAtCursor(
|
||||
editor,
|
||||
linkNote.getNoteTitle(),
|
||||
getURLAtCursor(editor)
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const openButton = ztoolkit.UI.createElement(_window.document, "button", {
|
||||
id: "link-popup-open",
|
||||
properties: {
|
||||
title: "Open in new window",
|
||||
innerHTML: ICONS["openInNewWindow"],
|
||||
},
|
||||
classList: ["link-popup-extra"],
|
||||
removeIfExists: true,
|
||||
listeners: [
|
||||
{
|
||||
type: "click",
|
||||
listener: async (e) => {
|
||||
ZoteroPane.openNoteWindow(linkNote.id);
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
_window.document
|
||||
.querySelector(".link-popup")
|
||||
?.append(insertButton, updateButton, openButton);
|
||||
// if (linkPopup) {
|
||||
// if (Zotero.Prefs.get("Knowledge4Zotero.linkAction.preview") as boolean) {
|
||||
// let previewContainer =
|
||||
// _window.document.getElementById("note-link-preview");
|
||||
// if (previewContainer) {
|
||||
// previewContainer.remove();
|
||||
// }
|
||||
// previewContainer = ztoolkit.UI.createElement(
|
||||
// _window.document,
|
||||
// "div"
|
||||
// ) as HTMLDivElement;
|
||||
// previewContainer.id = "note-link-preview";
|
||||
// previewContainer.className = "ProseMirror primary-editor";
|
||||
// previewContainer.innerHTML =
|
||||
// await this._Addon.NoteParse.parseNoteStyleHTML(linkNote);
|
||||
// previewContainer.addEventListener("click", (e) => {
|
||||
// this._Addon.WorkspaceWindow.setWorkspaceNote("preview", linkNote);
|
||||
// });
|
||||
// linkPopup.append(previewContainer);
|
||||
// previewContainer.setAttribute(
|
||||
// "style",
|
||||
// `width: 98%;height: ${
|
||||
// linkPopup ? Math.min(linkPopup.offsetTop, 300) : 300
|
||||
// }px;position: absolute;background: white;bottom: 36px;overflow: hidden;box-shadow: 0 0 5px 5px rgba(0,0,0,0.2);border-radius: 5px;cursor: pointer;opacity: 0.9;`
|
||||
// );
|
||||
// previewContainer
|
||||
// .querySelector("div[data-schema-version]")
|
||||
// ?.childNodes.forEach((node) => {
|
||||
// if ((node as Element).setAttribute) {
|
||||
// (node as Element).setAttribute("style", "margin: 0");
|
||||
// } else {
|
||||
// node.remove();
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
} else {
|
||||
Array.from(_window.document.querySelectorAll(".link-popup-extra")).forEach(
|
||||
(elem) => elem.remove()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function updateEditorImagePopup(editor: Zotero.EditorInstance) {
|
||||
ztoolkit.UI.appendElement(
|
||||
{
|
||||
tag: "fragment",
|
||||
children: [
|
||||
{
|
||||
tag: "button",
|
||||
id: "image-popup-preview",
|
||||
properties: {
|
||||
innerHTML: ICONS.previewImage,
|
||||
title: getString("editor.previewImage.title"),
|
||||
},
|
||||
removeIfExists: true,
|
||||
listeners: [
|
||||
{
|
||||
type: "click",
|
||||
listener: (e) => {
|
||||
const imgs = editor._iframeWindow.document
|
||||
.querySelector(".primary-editor")
|
||||
?.querySelectorAll("img");
|
||||
if (!imgs) {
|
||||
return;
|
||||
}
|
||||
const imageList = Array.from(imgs);
|
||||
addon.api.window.showImageViewer(
|
||||
imageList.map((elem) => elem.src),
|
||||
imageList.indexOf(
|
||||
editor._iframeWindow.document
|
||||
.querySelector(".primary-editor")
|
||||
?.querySelector(".selected")
|
||||
?.querySelector("img") as HTMLImageElement
|
||||
),
|
||||
editor._item.getNoteTitle()
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
tag: "button",
|
||||
id: "image-popup-resize",
|
||||
properties: {
|
||||
innerHTML: ICONS.resizeImage,
|
||||
title: getString("editor.resizeImage.title"),
|
||||
},
|
||||
removeIfExists: true,
|
||||
listeners: [
|
||||
{
|
||||
type: "click",
|
||||
listener: (e) => {
|
||||
const newWidth = parseFloat(
|
||||
editor._iframeWindow.prompt(
|
||||
getString("editor.resizeImage.prompt"),
|
||||
// @ts-ignore
|
||||
getEditorCore(editor).view.state.selection.node?.attrs
|
||||
?.width
|
||||
) || ""
|
||||
);
|
||||
if (newWidth && newWidth > 10) {
|
||||
updateImageDimensionsAtCursor(editor, newWidth);
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
editor._iframeWindow.document.querySelector(".image-popup")!
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,405 @@
|
|||
import { config } from "../../../package.json";
|
||||
import { ICONS } from "../../utils/config";
|
||||
import { getLineAtCursor } from "../../utils/editor";
|
||||
import { showHint } from "../../utils/hint";
|
||||
import { getNoteLink, getNoteLinkParams } from "../../utils/link";
|
||||
import { getString } from "../../utils/locale";
|
||||
import {
|
||||
addLineToNote,
|
||||
getNoteTreeFlattened,
|
||||
getNoteType,
|
||||
} from "../../utils/note";
|
||||
import { getPref } from "../../utils/prefs";
|
||||
import { slice } from "../../utils/str";
|
||||
|
||||
export async function initEditorToolbar(editor: Zotero.EditorInstance) {
|
||||
const noteItem = editor._item;
|
||||
const noteType = getNoteType(noteItem.id);
|
||||
const toolbar = await registerEditorToolbar(editor, makeId("toolbar"));
|
||||
|
||||
// Settings
|
||||
const settingsButton = await registerEditorToolbarDropdown(
|
||||
editor,
|
||||
toolbar,
|
||||
makeId("settings"),
|
||||
ICONS.settings,
|
||||
getString("editor.toolbar.settings.title"),
|
||||
"start",
|
||||
(e) => {}
|
||||
);
|
||||
|
||||
settingsButton.addEventListener("mouseenter", (ev) => {
|
||||
const settingsMenu: PopupData[] = [
|
||||
{
|
||||
id: makeId("settings-openWorkspace"),
|
||||
text: getString("editor.toolbar.settings.openWorkspace"),
|
||||
callback: (e) => {
|
||||
addon.hooks.onOpenWorkspace("tab");
|
||||
},
|
||||
},
|
||||
{
|
||||
id: makeId("settings-setWorkspace"),
|
||||
text: getString("editor.toolbar.settings.setWorkspace"),
|
||||
callback: (e) => {
|
||||
addon.hooks.onSetWorkspaceNote(e.editor._item.id, "main");
|
||||
},
|
||||
},
|
||||
{
|
||||
id: makeId("settings-insertTemplate"),
|
||||
text: getString("editor.toolbar.settings.insertTemplate"),
|
||||
callback: (e) => {
|
||||
addon.api.window.showTemplatePicker(
|
||||
e.editor._item.id,
|
||||
getLineAtCursor(e.editor)
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: makeId("settings-copyLink"),
|
||||
text: getString("editor.toolbar.settings.copyLink"),
|
||||
callback: (e) => {
|
||||
const link =
|
||||
getNoteLink(e.editor._item, {
|
||||
lineIndex: getLineAtCursor(e.editor),
|
||||
}) || "";
|
||||
new ztoolkit.Clipboard()
|
||||
.addText(link, "text/unicode")
|
||||
.addText(
|
||||
`<a href="${link}">${
|
||||
e.editor._item.getNoteTitle().trim() || link
|
||||
}</a>`,
|
||||
"text/html"
|
||||
)
|
||||
.copy();
|
||||
showHint(`Link ${link} copied`);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const settingsPopup = registerEditorToolbarPopup(
|
||||
editor,
|
||||
settingsButton,
|
||||
`${config.addonRef}-settings-popup`,
|
||||
"left",
|
||||
settingsMenu
|
||||
);
|
||||
});
|
||||
settingsButton.addEventListener("mouseleave", (ev) => {
|
||||
editor._iframeWindow.document
|
||||
.querySelector(`#${makeId("settings-popup")}`)
|
||||
?.remove();
|
||||
});
|
||||
settingsButton.addEventListener("click", (ev) => {
|
||||
editor._iframeWindow.document
|
||||
.querySelector(`#${makeId("settings-popup")}`)
|
||||
?.remove();
|
||||
});
|
||||
|
||||
// Center button
|
||||
if (noteType === "main") {
|
||||
registerEditorToolbarElement(
|
||||
editor,
|
||||
toolbar,
|
||||
"middle",
|
||||
ztoolkit.UI.createElement(editor._iframeWindow.document, "div", {
|
||||
properties: { innerHTML: getString("editor.toolbar.main") },
|
||||
})
|
||||
);
|
||||
} else {
|
||||
const onTriggerMenu = (ev: MouseEvent) => {
|
||||
editor._iframeWindow.focus();
|
||||
const linkMenu: PopupData[] = getLinkMenuData(editor);
|
||||
editor._iframeWindow.document
|
||||
.querySelector(`#${makeId("link")}`)!
|
||||
.querySelector(".toolbar-button")!.innerHTML = ICONS.linkAfter;
|
||||
|
||||
const popup = registerEditorToolbarPopup(
|
||||
editor,
|
||||
linkButton,
|
||||
`${config.addonRef}-link-popup`,
|
||||
"middle",
|
||||
linkMenu
|
||||
);
|
||||
};
|
||||
|
||||
const onExitMenu = (ev: MouseEvent) => {
|
||||
editor._iframeWindow.document
|
||||
.querySelector(`#${makeId("link-popup")}`)
|
||||
?.remove();
|
||||
editor._iframeWindow.document
|
||||
.querySelector(`#${makeId("link")}`)!
|
||||
.querySelector(".toolbar-button")!.innerHTML = ICONS.addon;
|
||||
};
|
||||
|
||||
const onClickMenu = async (ev: MouseEvent) => {
|
||||
const mainNote = Zotero.Items.get(addon.data.workspace.mainId) || null;
|
||||
if (!mainNote?.isNote()) {
|
||||
return;
|
||||
}
|
||||
const lineIndex = parseInt(
|
||||
(ev.target as HTMLDivElement).id.split("-").pop() || "-1"
|
||||
);
|
||||
const forwardLink = getNoteLink(noteItem);
|
||||
const backLink = getNoteLink(mainNote, { ignore: true, lineIndex });
|
||||
addLineToNote(
|
||||
mainNote,
|
||||
await addon.api.template.runTemplate(
|
||||
"[QuickInsertV2]",
|
||||
"link, linkText, subNoteItem, noteItem",
|
||||
[
|
||||
forwardLink,
|
||||
noteItem.getNoteTitle().trim() || forwardLink,
|
||||
noteItem,
|
||||
mainNote,
|
||||
]
|
||||
),
|
||||
lineIndex
|
||||
);
|
||||
addLineToNote(
|
||||
noteItem,
|
||||
await addon.api.template.runTemplate(
|
||||
"[QuickBackLinkV2]",
|
||||
"link, linkText, subNoteItem, noteItem",
|
||||
[
|
||||
backLink,
|
||||
mainNote.getNoteTitle().trim() || "Workspace Note",
|
||||
noteItem,
|
||||
mainNote,
|
||||
"",
|
||||
]
|
||||
)
|
||||
);
|
||||
onExitMenu(ev);
|
||||
ev.stopPropagation();
|
||||
};
|
||||
|
||||
const linkButton = await registerEditorToolbarDropdown(
|
||||
editor,
|
||||
toolbar,
|
||||
makeId("link"),
|
||||
ICONS.addon,
|
||||
getString("editor.toolbar.link.title"),
|
||||
"middle",
|
||||
onClickMenu
|
||||
);
|
||||
|
||||
linkButton.addEventListener("mouseenter", onTriggerMenu);
|
||||
linkButton.addEventListener("mouseleave", onExitMenu);
|
||||
linkButton.addEventListener("mouseleave", onExitMenu);
|
||||
linkButton.addEventListener("click", (ev) => {
|
||||
if ((ev.target as HTMLElement).classList.contains("option")) {
|
||||
onClickMenu(ev);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Export
|
||||
const exportButton = await registerEditorToolbarDropdown(
|
||||
editor,
|
||||
toolbar,
|
||||
makeId("export"),
|
||||
ICONS.export,
|
||||
getString("editor.toolbar.export.title"),
|
||||
"end",
|
||||
(e) => {
|
||||
if (addon.api.sync.isSyncNote(noteItem.id)) {
|
||||
addon.api.window.showSyncInfo(noteItem.id);
|
||||
} else {
|
||||
addon.api.window.showExportNoteOptions([noteItem.id]);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function getLinkMenuData(editor: Zotero.EditorInstance): PopupData[] {
|
||||
const workspaceNote = Zotero.Items.get(addon.data.workspace.mainId) || null;
|
||||
const currentNote = editor._item;
|
||||
if (!workspaceNote?.isNote()) {
|
||||
return [
|
||||
{
|
||||
id: makeId("link-popup-nodata"),
|
||||
text: getString("editor.toolbar.link.popup.nodata"),
|
||||
},
|
||||
];
|
||||
}
|
||||
const nodes = getNoteTreeFlattened(workspaceNote, {
|
||||
keepLink: true,
|
||||
});
|
||||
const menuData: PopupData[] = [];
|
||||
for (const node of nodes) {
|
||||
if (node.model.level === 7) {
|
||||
const lastMenu =
|
||||
menuData.length > 0 ? menuData[menuData.length - 1] : null;
|
||||
const linkNote = getNoteLinkParams(node.model.link).noteItem;
|
||||
if (linkNote && linkNote.id === currentNote.id && lastMenu) {
|
||||
lastMenu.suffix = "🔗";
|
||||
}
|
||||
continue;
|
||||
}
|
||||
menuData.push({
|
||||
id: makeId(
|
||||
`link-popup-${
|
||||
getPref("editor.link.insertPosition")
|
||||
? node.model.lineIndex - 1
|
||||
: node.model.endIndex
|
||||
}`
|
||||
),
|
||||
text: node.model.name,
|
||||
prefix: "·".repeat(node.model.level - 1),
|
||||
});
|
||||
}
|
||||
return menuData;
|
||||
}
|
||||
|
||||
async function registerEditorToolbar(
|
||||
editor: Zotero.EditorInstance,
|
||||
id: string
|
||||
) {
|
||||
await editor._initPromise;
|
||||
const _document = editor._iframeWindow.document;
|
||||
const toolbar = ztoolkit.UI.createElement(_document, "div", {
|
||||
attributes: {
|
||||
id,
|
||||
},
|
||||
classList: ["toolbar"],
|
||||
children: [
|
||||
{
|
||||
tag: "div",
|
||||
classList: ["start"],
|
||||
},
|
||||
{
|
||||
tag: "div",
|
||||
classList: ["middle"],
|
||||
},
|
||||
{
|
||||
tag: "div",
|
||||
classList: ["end"],
|
||||
},
|
||||
],
|
||||
ignoreIfExists: true,
|
||||
}) as HTMLDivElement;
|
||||
_document.querySelector(".editor")?.childNodes[0].before(toolbar);
|
||||
return toolbar;
|
||||
}
|
||||
|
||||
async function registerEditorToolbarDropdown(
|
||||
editor: Zotero.EditorInstance,
|
||||
toolbar: HTMLDivElement,
|
||||
id: string,
|
||||
icon: string,
|
||||
title: string,
|
||||
position: "start" | "middle" | "end",
|
||||
callback: (e: MouseEvent & { editor: Zotero.EditorInstance }) => any
|
||||
) {
|
||||
await editor._initPromise;
|
||||
const _document = editor._iframeWindow.document;
|
||||
const dropdown = ztoolkit.UI.createElement(_document, "div", {
|
||||
attributes: {
|
||||
id,
|
||||
title,
|
||||
},
|
||||
classList: ["dropdown", "more-dropdown"],
|
||||
children: [
|
||||
{
|
||||
tag: "button",
|
||||
attributes: {
|
||||
title,
|
||||
},
|
||||
properties: {
|
||||
innerHTML: icon,
|
||||
},
|
||||
classList: ["toolbar-button"],
|
||||
listeners: [
|
||||
{
|
||||
type: "click",
|
||||
listener: (e) => {
|
||||
Object.assign(e, { editor });
|
||||
if (callback) {
|
||||
callback(
|
||||
e as any as MouseEvent & { editor: Zotero.EditorInstance }
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
skipIfExists: true,
|
||||
});
|
||||
toolbar.querySelector(`.${position}`)?.append(dropdown);
|
||||
return dropdown;
|
||||
}
|
||||
|
||||
declare interface PopupData {
|
||||
id: string;
|
||||
text: string;
|
||||
prefix?: string;
|
||||
suffix?: string;
|
||||
callback?: (e: MouseEvent & { editor: Zotero.EditorInstance }) => any;
|
||||
}
|
||||
|
||||
async function registerEditorToolbarPopup(
|
||||
editor: Zotero.EditorInstance,
|
||||
dropdown: HTMLDivElement,
|
||||
id: string,
|
||||
align: "middle" | "left" | "right",
|
||||
popupLines: PopupData[]
|
||||
) {
|
||||
await editor._initPromise;
|
||||
const popup = ztoolkit.UI.appendElement(
|
||||
{
|
||||
tag: "div",
|
||||
classList: ["popup"],
|
||||
id,
|
||||
children: popupLines.map((props) => ({
|
||||
tag: "button",
|
||||
classList: ["option"],
|
||||
properties: {
|
||||
id: props.id,
|
||||
innerHTML:
|
||||
slice((props.prefix || "") + props.text, 30) + (props.suffix || ""),
|
||||
title: "",
|
||||
},
|
||||
listeners: [
|
||||
{
|
||||
type: "click",
|
||||
listener: (e) => {
|
||||
Object.assign(e, { editor });
|
||||
props.callback &&
|
||||
props.callback(
|
||||
e as any as MouseEvent & { editor: Zotero.EditorInstance }
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
})),
|
||||
removeIfExists: true,
|
||||
},
|
||||
dropdown
|
||||
) as HTMLDivElement;
|
||||
let style: string = "";
|
||||
if (align === "middle") {
|
||||
style = `right: -${popup.offsetWidth / 2 - 15}px;`;
|
||||
} else if (align === "left") {
|
||||
style = "left: 0; right: auto;";
|
||||
} else if (align === "right") {
|
||||
style = "right: 0;";
|
||||
}
|
||||
popup.setAttribute("style", style);
|
||||
return popup;
|
||||
}
|
||||
|
||||
async function registerEditorToolbarElement(
|
||||
editor: Zotero.EditorInstance,
|
||||
toolbar: HTMLDivElement,
|
||||
position: "start" | "middle" | "end",
|
||||
elem: HTMLElement
|
||||
) {
|
||||
await editor._initPromise;
|
||||
toolbar.querySelector(`.${position}`)?.append(elem);
|
||||
return elem;
|
||||
}
|
||||
|
||||
function makeId(key: string) {
|
||||
return `${config.addonRef}-${key}`;
|
||||
}
|
||||
|
|
@ -0,0 +1,218 @@
|
|||
import {
|
||||
getLinkedNotesRecursively,
|
||||
getNoteLink,
|
||||
getNoteLinkParams,
|
||||
} from "../../utils/link";
|
||||
import { getString } from "../../utils/locale";
|
||||
import { getLinesInNote } from "../../utils/note";
|
||||
import { formatPath } from "../../utils/str";
|
||||
|
||||
export { exportNotes };
|
||||
|
||||
async function exportNotes(
|
||||
noteItems: Zotero.Item[],
|
||||
options: {
|
||||
embedLink?: boolean;
|
||||
standaloneLink?: boolean;
|
||||
exportNote?: boolean;
|
||||
exportMD?: boolean;
|
||||
setAutoSync?: boolean;
|
||||
syncDir?: string;
|
||||
withYAMLHeader?: boolean;
|
||||
exportDocx?: boolean;
|
||||
exportPDF?: boolean;
|
||||
exportFreeMind?: boolean;
|
||||
}
|
||||
) {
|
||||
let inputNoteItems = noteItems;
|
||||
// If embedLink or exportNote, create a new note item
|
||||
if ((options.embedLink || options.exportNote) && !options.setAutoSync) {
|
||||
inputNoteItems = [];
|
||||
for (const noteItem of noteItems) {
|
||||
const noteID = await ZoteroPane.newNote();
|
||||
const newNote = Zotero.Items.get(noteID);
|
||||
newNote.setNote(noteItem.getNote());
|
||||
await newNote.saveTx({
|
||||
skipSelect: true,
|
||||
skipNotifier: true,
|
||||
skipSyncedUpdate: true,
|
||||
});
|
||||
await Zotero.DB.executeTransaction(async () => {
|
||||
await Zotero.Notes.copyEmbeddedImages(noteItem, newNote);
|
||||
});
|
||||
if (options.embedLink) {
|
||||
newNote.setNote(await embedLinkedNotes(newNote));
|
||||
}
|
||||
await newNote.saveTx();
|
||||
inputNoteItems.push(newNote);
|
||||
}
|
||||
}
|
||||
let linkedNoteItems = [] as Zotero.Item[];
|
||||
if (options.standaloneLink) {
|
||||
const linkedNoteIds = [] as number[];
|
||||
for (const noteItem of inputNoteItems) {
|
||||
let linkedIds: number[] = getLinkedNotesRecursively(
|
||||
getNoteLink(noteItem) || "",
|
||||
linkedNoteIds
|
||||
);
|
||||
linkedNoteIds.push(...linkedIds);
|
||||
}
|
||||
const targetNoteItemIds = inputNoteItems.map((item) => item.id);
|
||||
linkedNoteItems = Zotero.Items.get(
|
||||
linkedNoteIds.filter((id) => !targetNoteItemIds.includes(id))
|
||||
);
|
||||
}
|
||||
|
||||
const allNoteItems = Array.from(
|
||||
new Set(inputNoteItems.concat(linkedNoteItems))
|
||||
);
|
||||
if (options.exportMD) {
|
||||
if (options.setAutoSync) {
|
||||
const raw = await new ztoolkit.FilePicker(
|
||||
`${getString("fileInterface.sync")} MarkDown File`,
|
||||
"folder"
|
||||
).open();
|
||||
if (raw) {
|
||||
const syncDir = formatPath(raw);
|
||||
// Hard reset sync status for input notes
|
||||
for (const noteItem of inputNoteItems) {
|
||||
await toSync(noteItem, syncDir, true);
|
||||
}
|
||||
|
||||
// Find linked notes that are not synced and include them in sync
|
||||
for (const noteItem of linkedNoteItems) {
|
||||
await toSync(noteItem, syncDir, false);
|
||||
}
|
||||
|
||||
await addon.api.sync.doSync(allNoteItems, {
|
||||
quiet: true,
|
||||
skipActive: false,
|
||||
reason: "export",
|
||||
});
|
||||
}
|
||||
} else {
|
||||
for (const noteItem of allNoteItems) {
|
||||
await toMD(noteItem, {
|
||||
withYAMLHeader: options.withYAMLHeader,
|
||||
keepNoteLink: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
if (options.exportDocx) {
|
||||
for (const noteItem of allNoteItems) {
|
||||
await toDocx(noteItem);
|
||||
}
|
||||
}
|
||||
if (options.exportFreeMind) {
|
||||
for (const noteItem of allNoteItems) {
|
||||
await toFreeMind(noteItem);
|
||||
}
|
||||
}
|
||||
if (options.embedLink && !options.exportNote) {
|
||||
// If not exportNote, delete temp notes
|
||||
for (const noteItem of allNoteItems) {
|
||||
const _w: Window = ZoteroPane.findNoteWindow(noteItem.id);
|
||||
if (_w) {
|
||||
_w.close();
|
||||
}
|
||||
await Zotero.Items.erase(noteItem.id);
|
||||
}
|
||||
} else if (options.exportPDF) {
|
||||
for (const noteItem of allNoteItems) {
|
||||
await addon.api._export.savePDF(noteItem.id);
|
||||
}
|
||||
} else if (options.exportNote) {
|
||||
for (const noteItem of allNoteItems) {
|
||||
ZoteroPane.openNoteWindow(noteItem.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function toMD(
|
||||
noteItem: Zotero.Item,
|
||||
options: {
|
||||
filename?: string;
|
||||
keepNoteLink?: boolean;
|
||||
withYAMLHeader?: boolean;
|
||||
} = {}
|
||||
) {
|
||||
let filename = options.filename;
|
||||
if (!filename) {
|
||||
const raw = await new ztoolkit.FilePicker(
|
||||
`${Zotero.getString("fileInterface.export")} MarkDown File`,
|
||||
"save",
|
||||
[["MarkDown File(*.md)", "*.md"]],
|
||||
`${noteItem.getNoteTitle()}.md`
|
||||
).open();
|
||||
if (!raw) return;
|
||||
filename = formatPath(raw, ".md");
|
||||
}
|
||||
await addon.api._export.saveMD(filename, noteItem.id, options);
|
||||
}
|
||||
|
||||
async function toSync(
|
||||
noteItem: Zotero.Item,
|
||||
syncDir: string,
|
||||
overwrite: boolean = false
|
||||
) {
|
||||
if (!overwrite && addon.api.sync.isSyncNote(noteItem.id)) {
|
||||
return;
|
||||
}
|
||||
addon.api.sync.updateSyncStatus(noteItem.id, {
|
||||
path: syncDir,
|
||||
filename: await addon.api.sync.getMDFileName(noteItem.id, syncDir),
|
||||
md5: "",
|
||||
noteMd5: Zotero.Utilities.Internal.md5(noteItem.getNote(), false),
|
||||
lastsync: 0,
|
||||
itemID: noteItem.id,
|
||||
});
|
||||
}
|
||||
|
||||
async function toDocx(noteItem: Zotero.Item) {
|
||||
const raw = await new ztoolkit.FilePicker(
|
||||
`${Zotero.getString("fileInterface.export")} MS Word Docx`,
|
||||
"save",
|
||||
[["MS Word Docx File(*.docx)", "*.docx"]],
|
||||
`${noteItem.getNoteTitle()}.docx`
|
||||
).open();
|
||||
if (!raw) return;
|
||||
const filename = formatPath(raw, ".docx");
|
||||
await addon.api._export.saveDocx(filename, noteItem.id);
|
||||
}
|
||||
|
||||
async function toFreeMind(noteItem: Zotero.Item) {
|
||||
const raw = await new ztoolkit.FilePicker(
|
||||
`${Zotero.getString("fileInterface.export")} FreeMind XML`,
|
||||
"save",
|
||||
[["FreeMind XML File(*.mm)", "*.mm"]],
|
||||
`${noteItem.getNoteTitle()}.mm`
|
||||
).open();
|
||||
if (!raw) return;
|
||||
const filename = formatPath(raw, ".mm");
|
||||
await addon.api._export.saveFreeMind(filename, noteItem.id);
|
||||
}
|
||||
|
||||
async function embedLinkedNotes(noteItem: Zotero.Item): Promise<string> {
|
||||
const parser = ztoolkit.getDOMParser();
|
||||
|
||||
let newLines: string[] = [];
|
||||
const noteLines = getLinesInNote(noteItem);
|
||||
for (let i in noteLines) {
|
||||
newLines.push(noteLines[i]);
|
||||
const doc = parser.parseFromString(noteLines[i], "text/html");
|
||||
const linkParams = Array.from(doc.querySelectorAll("a"))
|
||||
.filter((a) => a.href.startsWith("zotero://note/"))
|
||||
.map((a) => getNoteLinkParams(a.href))
|
||||
.filter((p) => p.noteItem && !p.ignore);
|
||||
for (const linkParam of linkParams) {
|
||||
const html = await addon.api.template.runTemplate(
|
||||
"[QuickImportV2]",
|
||||
"link, noteItem",
|
||||
[linkParam.link, noteItem]
|
||||
);
|
||||
newLines.push(html);
|
||||
}
|
||||
}
|
||||
return newLines.join("\n");
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
import { showHintWithLink } from "../../utils/hint";
|
||||
import { renderNoteHTML } from "../../utils/note";
|
||||
import { getFileContent, randomString } from "../../utils/str";
|
||||
import { waitUtilAsync } from "../../utils/wait";
|
||||
|
||||
export async function saveDocx(filename: string, noteId: number) {
|
||||
const noteItem = Zotero.Items.get(noteId);
|
||||
await Zotero.File.putContentsAsync(filename, await note2docx(noteItem));
|
||||
showHintWithLink(`Note Saved to ${filename}`, "Show in Folder", (ev) => {
|
||||
Zotero.File.reveal(filename);
|
||||
});
|
||||
}
|
||||
|
||||
async function note2docx(noteItem: Zotero.Item) {
|
||||
const renderedContent = await renderNoteHTML(noteItem);
|
||||
let htmlDoc =
|
||||
'<!DOCTYPE html>\n<html lang="en"><head><meta charset="UTF-8"></head>\n';
|
||||
htmlDoc += renderedContent;
|
||||
htmlDoc += "\n</html>";
|
||||
|
||||
let blob: ArrayBufferLike;
|
||||
const lock = Zotero.Promise.defer();
|
||||
const jobId = randomString(6, new Date().toUTCString());
|
||||
const listener = (ev: MessageEvent) => {
|
||||
if (ev.data.type === "parseDocxReturn" && ev.data.jobId === jobId) {
|
||||
blob = ev.data.message;
|
||||
lock.resolve();
|
||||
}
|
||||
};
|
||||
const worker = await getWorker();
|
||||
worker.contentWindow?.addEventListener("message", listener);
|
||||
worker.contentWindow?.postMessage(
|
||||
{
|
||||
type: "parseDocx",
|
||||
jobId,
|
||||
message: htmlDoc,
|
||||
},
|
||||
"*"
|
||||
);
|
||||
await lock.promise;
|
||||
worker.contentWindow?.removeEventListener("message", listener);
|
||||
return blob!;
|
||||
}
|
||||
|
||||
async function getWorker() {
|
||||
if (addon.data.export.docx.worker) {
|
||||
return addon.data.export.docx.worker;
|
||||
}
|
||||
const worker = Zotero.Browser.createHiddenBrowser(
|
||||
window
|
||||
) as HTMLIFrameElement;
|
||||
await waitUtilAsync(() => worker.contentDocument?.readyState === "complete");
|
||||
|
||||
const doc = worker.contentDocument;
|
||||
ztoolkit.UI.appendElement(
|
||||
{
|
||||
tag: "script",
|
||||
properties: {
|
||||
innerHTML: await getFileContent(
|
||||
rootURI + "chrome/content/scripts/docxWorker.js"
|
||||
),
|
||||
},
|
||||
},
|
||||
doc?.head!
|
||||
);
|
||||
addon.data.export.docx.worker = worker;
|
||||
return worker;
|
||||
}
|
||||
|
|
@ -0,0 +1,227 @@
|
|||
import { getString } from "../../utils/locale";
|
||||
import { getPref, setPref } from "../../utils/prefs";
|
||||
import { fill, slice } from "../../utils/str";
|
||||
|
||||
enum OPTIONS {
|
||||
"embedLink",
|
||||
"standaloneLink",
|
||||
"keepLink",
|
||||
"exportMD",
|
||||
"setAutoSync",
|
||||
"withYAMLHeader",
|
||||
"exportDocx",
|
||||
"exportPDF",
|
||||
"exportFreeMind",
|
||||
"exportNote",
|
||||
}
|
||||
|
||||
export async function showExportNoteOptions(noteIds: number[]) {
|
||||
const items = Zotero.Items.get(noteIds);
|
||||
const noteItems: Zotero.Item[] = [];
|
||||
items.forEach((item) => {
|
||||
if (item.isNote()) {
|
||||
noteItems.push(item);
|
||||
}
|
||||
if (item.isRegularItem()) {
|
||||
noteItems.splice(0, 0, ...Zotero.Items.get(item.getNotes()));
|
||||
}
|
||||
});
|
||||
if (noteItems.length === 0) {
|
||||
return;
|
||||
}
|
||||
const dataKeys = Object.keys(OPTIONS).filter(
|
||||
(value) => typeof value === "string"
|
||||
);
|
||||
const data = dataKeys.reduce((acc, key) => {
|
||||
acc[key] = getPref(`export.${key}`) as boolean;
|
||||
return acc;
|
||||
}, {} as Record<string, any>);
|
||||
|
||||
data.loadCallback = () => {
|
||||
const doc = dialog.window.document;
|
||||
const standaloneLinkRadio = doc.querySelector(
|
||||
"#standaloneLink"
|
||||
) as HTMLInputElement;
|
||||
const autoSyncRadio = doc.querySelector("#setAutoSync") as HTMLInputElement;
|
||||
function updateSyncCheckbox() {
|
||||
const standaloneLinkEnabled = standaloneLinkRadio.checked;
|
||||
if (!standaloneLinkEnabled) {
|
||||
autoSyncRadio.checked = false;
|
||||
autoSyncRadio.disabled = true;
|
||||
} else {
|
||||
autoSyncRadio.disabled = false;
|
||||
}
|
||||
}
|
||||
Array.from(doc.querySelectorAll('input[name="linkMode"]')).forEach((elem) =>
|
||||
elem.addEventListener("change", updateSyncCheckbox)
|
||||
);
|
||||
updateSyncCheckbox();
|
||||
};
|
||||
|
||||
const dialog = new ztoolkit.Dialog(17, 1)
|
||||
.setDialogData(data)
|
||||
.addCell(0, 0, {
|
||||
tag: "div",
|
||||
styles: {
|
||||
display: "grid",
|
||||
gridTemplateColumns: "1fr 20px",
|
||||
rowGap: "10px",
|
||||
columnGap: "5px",
|
||||
},
|
||||
children: [
|
||||
{
|
||||
tag: "label",
|
||||
properties: {
|
||||
innerHTML: `${getString("export.target")}: ${fill(
|
||||
slice(noteItems[0].getNoteTitle(), 40),
|
||||
40
|
||||
)}${
|
||||
noteItems.length > 1 ? ` and ${noteItems.length - 1} more` : ""
|
||||
}`,
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
.addCell(1, 0, makeHeadingLine(getString("export.options.linkMode")))
|
||||
.addCell(2, 0, makeRadioLine("embedLink", "linkMode"))
|
||||
.addCell(3, 0, makeRadioLine("standaloneLink", "linkMode"))
|
||||
.addCell(4, 0, makeRadioLine("keepLink", "linkMode"))
|
||||
.addCell(5, 0, makeHeadingLine(getString("export.options.MD")))
|
||||
.addCell(6, 0, makeCheckboxLine("exportMD"))
|
||||
.addCell(7, 0, makeCheckboxLine("setAutoSync"))
|
||||
.addCell(8, 0, makeCheckboxLine("withYAMLHeader"))
|
||||
.addCell(9, 0, makeHeadingLine(getString("export.options.Docx")))
|
||||
.addCell(10, 0, makeCheckboxLine("exportDocx"))
|
||||
.addCell(11, 0, makeHeadingLine(getString("export.options.PDF")))
|
||||
.addCell(12, 0, makeCheckboxLine("exportPDF"))
|
||||
.addCell(13, 0, makeHeadingLine(getString("export.options.mm")))
|
||||
.addCell(14, 0, makeCheckboxLine("exportFreeMind"))
|
||||
.addCell(15, 0, makeHeadingLine(getString("export.options.note")))
|
||||
.addCell(16, 0, makeCheckboxLine("exportNote"))
|
||||
.addButton(getString("export.confirm"), "confirm")
|
||||
.addButton(getString("export.cancel"), "cancel")
|
||||
.open(getString("export.title"), {
|
||||
resizable: true,
|
||||
centerscreen: true,
|
||||
fitContent: true,
|
||||
noDialogMode: true,
|
||||
});
|
||||
|
||||
await data.unloadLock?.promise;
|
||||
if (data._lastButtonId === "confirm") {
|
||||
addon.api._export.exportNotes(noteItems, data as Record<string, boolean>);
|
||||
dataKeys.forEach((key) => {
|
||||
setPref(`export.${key}`, Boolean(data[key]));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function makeHeadingLine(text: string) {
|
||||
return {
|
||||
tag: "div",
|
||||
styles: {
|
||||
display: "grid",
|
||||
gridTemplateColumns: "1fr 20px",
|
||||
rowGap: "10px",
|
||||
columnGap: "5px",
|
||||
},
|
||||
children: [
|
||||
{
|
||||
tag: "h3",
|
||||
properties: {
|
||||
innerHTML: text,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
function makeCheckboxLine(dataKey: string, callback?: (ev: Event) => void) {
|
||||
return {
|
||||
tag: "div",
|
||||
styles: {
|
||||
display: "grid",
|
||||
gridTemplateColumns: "1fr 20px",
|
||||
rowGap: "10px",
|
||||
columnGap: "5px",
|
||||
},
|
||||
children: [
|
||||
{
|
||||
tag: "label",
|
||||
attributes: {
|
||||
for: dataKey,
|
||||
},
|
||||
properties: {
|
||||
innerHTML: getString(`export.${dataKey}`),
|
||||
},
|
||||
},
|
||||
{
|
||||
tag: "input",
|
||||
id: dataKey,
|
||||
attributes: {
|
||||
"data-bind": dataKey,
|
||||
"data-prop": "checked",
|
||||
},
|
||||
properties: {
|
||||
type: "checkbox",
|
||||
},
|
||||
listeners: callback
|
||||
? [
|
||||
{
|
||||
type: "change",
|
||||
listener: callback,
|
||||
},
|
||||
]
|
||||
: [],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
function makeRadioLine(
|
||||
dataKey: string,
|
||||
radioName: string,
|
||||
callback?: (ev: Event) => void
|
||||
) {
|
||||
return {
|
||||
tag: "div",
|
||||
styles: {
|
||||
display: "grid",
|
||||
gridTemplateColumns: "1fr 20px",
|
||||
rowGap: "10px",
|
||||
columnGap: "5px",
|
||||
},
|
||||
children: [
|
||||
{
|
||||
tag: "label",
|
||||
attributes: {
|
||||
for: dataKey,
|
||||
},
|
||||
properties: {
|
||||
innerHTML: getString(`export.${dataKey}`),
|
||||
},
|
||||
},
|
||||
{
|
||||
tag: "input",
|
||||
id: dataKey,
|
||||
attributes: {
|
||||
"data-bind": dataKey,
|
||||
"data-prop": "checked",
|
||||
},
|
||||
properties: {
|
||||
type: "radio",
|
||||
name: radioName,
|
||||
value: dataKey,
|
||||
},
|
||||
listeners: callback
|
||||
? [
|
||||
{
|
||||
type: "change",
|
||||
listener: callback,
|
||||
},
|
||||
]
|
||||
: [],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
import TreeModel = require("tree-model");
|
||||
import { showHintWithLink } from "../../utils/hint";
|
||||
import { getNoteTree, parseHTMLLines, renderNoteHTML } from "../../utils/note";
|
||||
|
||||
export async function saveFreeMind(filename: string, noteId: number) {
|
||||
const noteItem = Zotero.Items.get(noteId);
|
||||
await Zotero.File.putContentsAsync(filename, await note2mm(noteItem));
|
||||
showHintWithLink(`Note Saved to ${filename}`, "Show in Folder", (ev) => {
|
||||
Zotero.File.reveal(filename);
|
||||
});
|
||||
}
|
||||
|
||||
async function note2mm(
|
||||
noteItem: Zotero.Item,
|
||||
options: { withContent?: boolean } = { withContent: true }
|
||||
) {
|
||||
const root = getNoteTree(noteItem, false);
|
||||
const textNodeForEach = (e: Node, callbackfn: Function) => {
|
||||
if (e.nodeType === document.TEXT_NODE) {
|
||||
callbackfn(e);
|
||||
return;
|
||||
}
|
||||
e.childNodes.forEach((_e) => textNodeForEach(_e, callbackfn));
|
||||
};
|
||||
const html2Escape = (sHtml: string) => {
|
||||
return sHtml.replace(/[<>&"]/g, function (c) {
|
||||
return { "<": "<", ">": ">", "&": "&", '"': """ }[c]!;
|
||||
});
|
||||
};
|
||||
let lines: string[] = [];
|
||||
if (options.withContent) {
|
||||
const doc = ztoolkit
|
||||
.getDOMParser()
|
||||
.parseFromString(await renderNoteHTML(noteItem), "text/html");
|
||||
textNodeForEach(doc.body, (e: Text) => {
|
||||
e.data = html2Escape(e.data);
|
||||
});
|
||||
lines = parseHTMLLines(doc.body.innerHTML);
|
||||
}
|
||||
const convertClosingTags = (htmlStr: string) => {
|
||||
const regConfs = [
|
||||
{
|
||||
reg: /<br[^>]*?>/g,
|
||||
cbk: (str: string) => "<br></br>",
|
||||
},
|
||||
{
|
||||
reg: /<img[^>]*?>/g,
|
||||
cbk: (str: string) => {
|
||||
return `<img ${str.match(/src="[^"]+"/g)}></img>`;
|
||||
},
|
||||
},
|
||||
];
|
||||
for (const regConf of regConfs) {
|
||||
htmlStr = htmlStr.replace(regConf.reg, regConf.cbk);
|
||||
}
|
||||
return htmlStr;
|
||||
};
|
||||
const convertNode = (node: TreeModel.Node<NoteNodeData>) => {
|
||||
mmXML += `<node ID="${node.model.id}" TEXT="${html2Escape(
|
||||
node.model.name || noteItem.getNoteTitle()
|
||||
)}"><hook NAME="AlwaysUnfoldedNode" />`;
|
||||
if (
|
||||
options.withContent &&
|
||||
node.model.lineIndex >= 0 &&
|
||||
node.model.endIndex >= 0
|
||||
) {
|
||||
mmXML += `<richcontent TYPE="NOTE" CONTENT-TYPE="xml/"><html><head></head><body>${convertClosingTags(
|
||||
lines
|
||||
.slice(
|
||||
node.model.lineIndex,
|
||||
node.hasChildren()
|
||||
? node.children[0].model.lineIndex
|
||||
: node.model.endIndex + 1
|
||||
)
|
||||
.join("\n")
|
||||
)}</body></html></richcontent>`;
|
||||
}
|
||||
if (node.hasChildren()) {
|
||||
node.children.forEach((child: TreeModel.Node<NoteNodeData>) => {
|
||||
convertNode(child);
|
||||
});
|
||||
}
|
||||
mmXML += "</node>";
|
||||
};
|
||||
let mmXML = '<map version="freeplane 1.9.0">';
|
||||
convertNode(root);
|
||||
mmXML += "</map>";
|
||||
ztoolkit.log(mmXML);
|
||||
return mmXML;
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
import { showHintWithLink } from "../../utils/hint";
|
||||
import { formatPath } from "../../utils/str";
|
||||
|
||||
export async function saveMD(
|
||||
filename: string,
|
||||
noteId: number,
|
||||
options: {
|
||||
keepNoteLink?: boolean;
|
||||
withYAMLHeader?: boolean;
|
||||
}
|
||||
) {
|
||||
const noteItem = Zotero.Items.get(noteId);
|
||||
const dir = OS.Path.join(
|
||||
...OS.Path.split(formatPath(filename)).components.slice(0, -1)
|
||||
);
|
||||
const hasImage = noteItem.getNote().includes("<img");
|
||||
if (hasImage) {
|
||||
await Zotero.File.createDirectoryIfMissingAsync(dir);
|
||||
}
|
||||
await Zotero.File.putContentsAsync(
|
||||
filename,
|
||||
await addon.api.convert.note2md(noteItem, dir, options)
|
||||
);
|
||||
|
||||
showHintWithLink(`Note Saved to ${filename}`, "Show in Folder", (ev) => {
|
||||
Zotero.File.reveal(filename);
|
||||
});
|
||||
}
|
||||
|
||||
export async function syncMDBatch(saveDir: string, noteIds: number[]) {
|
||||
const noteItems = Zotero.Items.get(noteIds);
|
||||
await Zotero.File.createDirectoryIfMissingAsync(saveDir);
|
||||
const attachmentsDir = formatPath(OS.Path.join(saveDir, "attachments"));
|
||||
const hasImage = noteItems.some((noteItem) =>
|
||||
noteItem.getNote().includes("<img")
|
||||
);
|
||||
if (hasImage) {
|
||||
await Zotero.File.createDirectoryIfMissingAsync(attachmentsDir);
|
||||
}
|
||||
for (const noteItem of noteItems) {
|
||||
const filename = await addon.api.sync.getMDFileName(noteItem.id, saveDir);
|
||||
const filePath = OS.Path.join(saveDir, filename);
|
||||
const content = await addon.api.convert.note2md(noteItem, saveDir, {
|
||||
keepNoteLink: false,
|
||||
withYAMLHeader: true,
|
||||
});
|
||||
await Zotero.File.putContentsAsync(filePath, content);
|
||||
addon.api.sync.updateSyncStatus(noteItem.id, {
|
||||
path: saveDir,
|
||||
filename,
|
||||
itemID: noteItem.id,
|
||||
md5: Zotero.Utilities.Internal.md5(
|
||||
addon.api.sync.getMDStatusFromContent(content).content,
|
||||
false
|
||||
),
|
||||
noteMd5: Zotero.Utilities.Internal.md5(noteItem.getNote(), false),
|
||||
lastsync: new Date().getTime(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
import { config } from "../../../package.json";
|
||||
import { showHint } from "../../utils/hint";
|
||||
import { renderNoteHTML } from "../../utils/note";
|
||||
import { waitUtilAsync } from "../../utils/wait";
|
||||
|
||||
export async function savePDF(noteId: number) {
|
||||
const html = await renderNoteHTML(Zotero.Items.get(noteId));
|
||||
const win = window.openDialog(
|
||||
`chrome://${config.addonRef}/content/pdfPrinter.html`,
|
||||
`${config.addonRef}-imageViewer`,
|
||||
`chrome,centerscreen,resizable,status,width=900,height=650,dialog=no`
|
||||
)!;
|
||||
await waitUtilAsync(() => win.document.readyState === "complete");
|
||||
win.document.querySelector(".markdown-body")!.innerHTML = html;
|
||||
const printPromise = Zotero.Promise.defer();
|
||||
disablePrintFooterHeader();
|
||||
win.addEventListener("mouseover", (ev) => {
|
||||
win.close();
|
||||
printPromise.resolve();
|
||||
});
|
||||
win.print();
|
||||
await printPromise.promise;
|
||||
showHint("Note Saved as PDF");
|
||||
}
|
||||
|
||||
function disablePrintFooterHeader() {
|
||||
// @ts-ignore
|
||||
Zotero.Prefs.resetBranch([], "print");
|
||||
Zotero.Prefs.set("print.print_footercenter", "", true);
|
||||
Zotero.Prefs.set("print.print_footerleft", "", true);
|
||||
Zotero.Prefs.set("print.print_footerright", "", true);
|
||||
Zotero.Prefs.set("print.print_headercenter", "", true);
|
||||
Zotero.Prefs.set("print.print_headerleft", "", true);
|
||||
Zotero.Prefs.set("print.print_headerright", "", true);
|
||||
}
|
||||
|
|
@ -0,0 +1,293 @@
|
|||
import { config } from "../../package.json";
|
||||
import { ICONS } from "../utils/config";
|
||||
import { showHint, showHintWithLink } from "../utils/hint";
|
||||
import { formatPath } from "../utils/str";
|
||||
import { waitUtilAsync } from "../utils/wait";
|
||||
|
||||
export async function showImageViewer(
|
||||
srcList: string[],
|
||||
idx: number,
|
||||
title: string
|
||||
) {
|
||||
if (
|
||||
!addon.data.imageViewer.window ||
|
||||
Components.utils.isDeadWrapper(addon.data.imageViewer.window) ||
|
||||
addon.data.imageViewer.window.closed
|
||||
) {
|
||||
addon.data.imageViewer.window = window.openDialog(
|
||||
`chrome://${config.addonRef}/content/imageViewer.html`,
|
||||
`${config.addonRef}-imageViewer`,
|
||||
`chrome,centerscreen,resizable,status,width=500,height=550,dialog=no${
|
||||
addon.data.imageViewer.pined ? ",alwaysRaised=yes" : ""
|
||||
}`
|
||||
)!;
|
||||
await waitUtilAsync(
|
||||
() => addon.data.imageViewer.window?.document.readyState === "complete"
|
||||
);
|
||||
const container = addon.data.imageViewer.window.document.querySelector(
|
||||
".container"
|
||||
) as HTMLDivElement;
|
||||
const img = addon.data.imageViewer.window.document.querySelector(
|
||||
"#image"
|
||||
) as HTMLImageElement;
|
||||
|
||||
addon.data.imageViewer.window.document
|
||||
.querySelector("#left")
|
||||
?.addEventListener("click", (e) => {
|
||||
setIndex("left");
|
||||
});
|
||||
addon.data.imageViewer.window.document
|
||||
.querySelector("#bigger")
|
||||
?.addEventListener("click", (e) => {
|
||||
addon.data.imageViewer.anchorPosition = {
|
||||
left: img.scrollWidth / 2 - container.scrollLeft / 2,
|
||||
top: img.scrollHeight / 2 - container.scrollLeft / 2,
|
||||
};
|
||||
setScale(addon.data.imageViewer.scaling * 1.1);
|
||||
});
|
||||
addon.data.imageViewer.window.document
|
||||
.querySelector("#smaller")
|
||||
?.addEventListener("click", (e) => {
|
||||
addon.data.imageViewer.anchorPosition = {
|
||||
left: img.scrollWidth / 2 - container.scrollLeft / 2,
|
||||
top: img.scrollHeight / 2 - container.scrollLeft / 2,
|
||||
};
|
||||
setScale(addon.data.imageViewer.scaling / 1.1);
|
||||
});
|
||||
addon.data.imageViewer.window.document
|
||||
.querySelector("#resetwidth")
|
||||
?.addEventListener("click", (e) => {
|
||||
setScale(1);
|
||||
});
|
||||
addon.data.imageViewer.window.document
|
||||
.querySelector("#right")
|
||||
?.addEventListener("click", (e) => {
|
||||
setIndex("right");
|
||||
});
|
||||
addon.data.imageViewer.window.document
|
||||
.querySelector("#copy")
|
||||
?.addEventListener("click", (e) => {
|
||||
new ztoolkit.Clipboard()
|
||||
.addImage(addon.data.imageViewer.srcList[addon.data.imageViewer.idx])
|
||||
.copy();
|
||||
showHint("Image Copied.");
|
||||
});
|
||||
addon.data.imageViewer.window.document
|
||||
.querySelector("#save")
|
||||
?.addEventListener("click", async (e) => {
|
||||
let parts =
|
||||
addon.data.imageViewer.srcList[addon.data.imageViewer.idx].split(",");
|
||||
if (!parts[0].includes("base64")) {
|
||||
return;
|
||||
}
|
||||
let mime = parts[0].match(/:(.*?);/)![1];
|
||||
let bstr = addon.data.imageViewer.window?.atob(parts[1])!;
|
||||
let n = bstr.length;
|
||||
let u8arr = new Uint8Array(n);
|
||||
while (n--) {
|
||||
u8arr[n] = bstr.charCodeAt(n);
|
||||
}
|
||||
let ext = Zotero.MIME.getPrimaryExtension(mime, "");
|
||||
const filename = await new ztoolkit.FilePicker(
|
||||
Zotero.getString("noteEditor.saveImageAs"),
|
||||
"save",
|
||||
[[`Image(*.${ext})`, `*.${ext}`]],
|
||||
`${Zotero.getString("fileTypes.image").toLowerCase()}.${ext}`,
|
||||
addon.data.imageViewer.window,
|
||||
"images"
|
||||
).open();
|
||||
if (filename) {
|
||||
await OS.File.writeAtomic(formatPath(filename), u8arr);
|
||||
showHintWithLink(
|
||||
`Image Saved to ${filename}`,
|
||||
"Show in Folder",
|
||||
(ev) => {
|
||||
Zotero.File.reveal(filename);
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
addon.data.imageViewer.window.document.querySelector("#pin")!.innerHTML =
|
||||
addon.data.imageViewer.pined
|
||||
? ICONS.imageViewerPined
|
||||
: ICONS.imageViewerPin;
|
||||
addon.data.imageViewer.window.document.querySelector(
|
||||
"#pin-tooltip"
|
||||
)!.innerHTML = addon.data.imageViewer.pined ? "Unpin" : "Pin";
|
||||
addon.data.imageViewer.window.document
|
||||
.querySelector("#pin")
|
||||
?.addEventListener("click", (e) => {
|
||||
setPin();
|
||||
});
|
||||
addon.data.imageViewer.window.addEventListener("keydown", (e) => {
|
||||
// ctrl+w or esc
|
||||
if ((e.key === "w" && e.ctrlKey) || e.keyCode === 27) {
|
||||
addon.data.imageViewer.window?.close();
|
||||
}
|
||||
addon.data.imageViewer.anchorPosition = {
|
||||
left: img.scrollWidth / 2 - container.scrollLeft / 2,
|
||||
top: img.scrollHeight / 2 - container.scrollLeft / 2,
|
||||
};
|
||||
if (e.keyCode === 37 || e.keyCode === 40) {
|
||||
setIndex("left");
|
||||
}
|
||||
if (e.keyCode === 38 || e.keyCode === 39) {
|
||||
setIndex("right");
|
||||
}
|
||||
if (e.key === "0") {
|
||||
setScale(1);
|
||||
} else if (e.keyCode === 107 || e.keyCode === 187 || e.key === "=") {
|
||||
setScale(addon.data.imageViewer.scaling * 1.1);
|
||||
} else if (e.key === "-") {
|
||||
setScale(addon.data.imageViewer.scaling / 1.1);
|
||||
}
|
||||
});
|
||||
addon.data.imageViewer.window.addEventListener("wheel", async (e) => {
|
||||
addon.data.imageViewer.anchorPosition = {
|
||||
left: e.pageX - container.offsetLeft,
|
||||
top: e.pageY - container.offsetTop,
|
||||
};
|
||||
function normalizeWheelEventDirection(evt: WheelEvent) {
|
||||
let delta = Math.hypot(evt.deltaX, evt.deltaY);
|
||||
const angle = Math.atan2(evt.deltaY, evt.deltaX);
|
||||
if (-0.25 * Math.PI < angle && angle < 0.75 * Math.PI) {
|
||||
// All that is left-up oriented has to change the sign.
|
||||
delta = -delta;
|
||||
}
|
||||
return delta;
|
||||
}
|
||||
const delta = normalizeWheelEventDirection(e);
|
||||
if (e.ctrlKey) {
|
||||
setScale(
|
||||
addon.data.imageViewer.scaling *
|
||||
Math.pow(delta > 0 ? 1.1 : 1 / 1.1, Math.round(Math.abs(delta)))
|
||||
);
|
||||
} else if (e.shiftKey) {
|
||||
container.scrollLeft -= delta * 10;
|
||||
} else {
|
||||
container.scrollLeft += e.deltaX * 10;
|
||||
container.scrollTop += e.deltaY * 10;
|
||||
}
|
||||
});
|
||||
img.addEventListener("mousedown", (e) => {
|
||||
e.preventDefault();
|
||||
// if (addon.data.imageViewer.scaling <= 1) {
|
||||
// return;
|
||||
// }
|
||||
img.onmousemove = (e) => {
|
||||
e.preventDefault();
|
||||
container.scrollLeft -= e.movementX;
|
||||
container.scrollTop -= e.movementY;
|
||||
};
|
||||
img.onmouseleave = () => {
|
||||
img.onmousemove = null;
|
||||
img.onmouseup = null;
|
||||
};
|
||||
img.onmouseup = () => {
|
||||
img.onmousemove = null;
|
||||
img.onmouseup = null;
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
addon.data.imageViewer.srcList = srcList;
|
||||
addon.data.imageViewer.idx = idx;
|
||||
addon.data.imageViewer.title = title || "Note";
|
||||
setImage();
|
||||
setScale(1);
|
||||
addon.data.imageViewer.window.focus();
|
||||
}
|
||||
|
||||
function setImage() {
|
||||
(
|
||||
addon.data.imageViewer.window?.document.querySelector(
|
||||
"#image"
|
||||
) as HTMLImageElement
|
||||
).src = addon.data.imageViewer.srcList[addon.data.imageViewer.idx];
|
||||
setTitle();
|
||||
(
|
||||
addon.data.imageViewer.window?.document.querySelector(
|
||||
"#left-container"
|
||||
) as HTMLButtonElement
|
||||
).style.opacity = addon.data.imageViewer.idx === 0 ? "0.5" : "1";
|
||||
(
|
||||
addon.data.imageViewer.window?.document.querySelector(
|
||||
"#right-container"
|
||||
) as HTMLButtonElement
|
||||
).style.opacity =
|
||||
addon.data.imageViewer.idx === addon.data.imageViewer.srcList.length - 1
|
||||
? "0.5"
|
||||
: "1";
|
||||
}
|
||||
|
||||
function setIndex(type: "left" | "right") {
|
||||
if (type === "left") {
|
||||
addon.data.imageViewer.idx > 0
|
||||
? (addon.data.imageViewer.idx -= 1)
|
||||
: undefined;
|
||||
}
|
||||
if (type === "right") {
|
||||
addon.data.imageViewer.idx < addon.data.imageViewer.srcList.length - 1
|
||||
? (addon.data.imageViewer.idx += 1)
|
||||
: undefined;
|
||||
}
|
||||
setImage();
|
||||
}
|
||||
|
||||
function setScale(scaling: number) {
|
||||
const oldScale = addon.data.imageViewer.scaling;
|
||||
addon.data.imageViewer.scaling = scaling;
|
||||
if (addon.data.imageViewer.scaling > 10) {
|
||||
addon.data.imageViewer.scaling = 10;
|
||||
}
|
||||
if (addon.data.imageViewer.scaling < 0.1) {
|
||||
addon.data.imageViewer.scaling = 0.1;
|
||||
}
|
||||
const container = addon.data.imageViewer.window?.document.querySelector(
|
||||
".container"
|
||||
) as HTMLDivElement;
|
||||
(
|
||||
addon.data.imageViewer.window?.document.querySelector(
|
||||
"#image"
|
||||
) as HTMLImageElement
|
||||
).style.width = `calc(100% * ${addon.data.imageViewer.scaling})`;
|
||||
if (addon.data.imageViewer.scaling > 1) {
|
||||
container.scrollLeft +=
|
||||
addon.data.imageViewer.anchorPosition?.left! *
|
||||
(addon.data.imageViewer.scaling - oldScale);
|
||||
container.scrollTop +=
|
||||
addon.data.imageViewer.anchorPosition?.top! *
|
||||
(addon.data.imageViewer.scaling - oldScale);
|
||||
}
|
||||
(
|
||||
addon.data.imageViewer.window?.document.querySelector(
|
||||
"#bigger-container"
|
||||
) as HTMLButtonElement
|
||||
).style.opacity = addon.data.imageViewer.scaling === 10 ? "0.5" : "1";
|
||||
(
|
||||
addon.data.imageViewer.window?.document.querySelector(
|
||||
"#smaller-container"
|
||||
) as HTMLButtonElement
|
||||
).style.opacity = addon.data.imageViewer.scaling === 0.1 ? "0.5" : "1";
|
||||
// (
|
||||
// addon.data.imageViewer.window.document.querySelector("#image") as HTMLImageElement
|
||||
// ).style.cursor = addon.data.imageViewer.scaling <= 1 ? "default" : "move";
|
||||
}
|
||||
|
||||
function setTitle() {
|
||||
addon.data.imageViewer.window!.document.querySelector(
|
||||
"title"
|
||||
)!.innerText! = `${addon.data.imageViewer.idx + 1}/${
|
||||
addon.data.imageViewer.srcList.length
|
||||
}:${addon.data.imageViewer.title}`;
|
||||
}
|
||||
|
||||
function setPin() {
|
||||
addon.data.imageViewer.window?.close();
|
||||
addon.data.imageViewer.pined = !addon.data.imageViewer.pined;
|
||||
showImageViewer(
|
||||
addon.data.imageViewer.srcList,
|
||||
addon.data.imageViewer.idx,
|
||||
addon.data.imageViewer.title
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
import { addLineToNote } from "../../utils/note";
|
||||
|
||||
export async function fromMD(
|
||||
filepath: string,
|
||||
options: {
|
||||
noteId?: number;
|
||||
ignoreVersion?: boolean;
|
||||
append?: boolean;
|
||||
appendLineIndex?: number;
|
||||
} = {}
|
||||
) {
|
||||
let mdStatus: MDStatus;
|
||||
try {
|
||||
mdStatus = await addon.api.sync.getMDStatus(filepath);
|
||||
} catch (e) {
|
||||
ztoolkit.log(`Import Error: ${String(e)}`);
|
||||
return;
|
||||
}
|
||||
let noteItem = options.noteId ? Zotero.Items.get(options.noteId) : undefined;
|
||||
if (
|
||||
!options.ignoreVersion &&
|
||||
typeof mdStatus.meta?.version === "number" &&
|
||||
typeof noteItem?.version === "number" &&
|
||||
mdStatus.meta?.version < noteItem?.version
|
||||
) {
|
||||
if (
|
||||
!window.confirm(
|
||||
`The target note seems to be newer than the file ${filepath}. Are you sure you want to import it anyway?`
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
const noteStatus = noteItem
|
||||
? addon.api.sync.getNoteStatus(noteItem.id)
|
||||
: {
|
||||
meta: '<div data-schema-version="9">',
|
||||
content: "",
|
||||
tail: "</div>",
|
||||
};
|
||||
|
||||
if (!noteItem) {
|
||||
noteItem = new Zotero.Item("note");
|
||||
noteItem.libraryID = ZoteroPane.getSelectedLibraryID();
|
||||
if (ZoteroPane.getCollectionTreeRow()?.isCollection()) {
|
||||
noteItem.addToCollection(ZoteroPane.getCollectionTreeRow()?.ref.id);
|
||||
}
|
||||
await noteItem.saveTx({
|
||||
notifierData: {
|
||||
autoSyncDelay: Zotero.Notes.AUTO_SYNC_DELAY,
|
||||
},
|
||||
});
|
||||
}
|
||||
const parsedContent = await addon.api.convert.md2note(mdStatus, noteItem, {
|
||||
isImport: true,
|
||||
});
|
||||
ztoolkit.log("import", noteStatus);
|
||||
|
||||
if (options.append) {
|
||||
await addLineToNote(noteItem, parsedContent, options.appendLineIndex || -1);
|
||||
} else {
|
||||
noteItem.setNote(noteStatus!.meta + parsedContent + noteStatus!.tail);
|
||||
await noteItem.saveTx({
|
||||
notifierData: {
|
||||
autoSyncDelay: Zotero.Notes.AUTO_SYNC_DELAY,
|
||||
},
|
||||
});
|
||||
}
|
||||
return noteItem;
|
||||
}
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
import { config } from "../../package.json";
|
||||
import { getString } from "../utils/locale";
|
||||
|
||||
export function registerMenus() {
|
||||
// item
|
||||
ztoolkit.Menu.register("item", { tag: "menuseparator" });
|
||||
ztoolkit.Menu.register("item", {
|
||||
tag: "menuitem",
|
||||
label: getString("menuItem.exportNote"),
|
||||
icon: `chrome://${config.addonRef}/content/icons/favicon.png`,
|
||||
commandListener: (ev) => {
|
||||
addon.api.window.showExportNoteOptions(
|
||||
ZoteroPane.getSelectedItems().map((item) => item.id)
|
||||
);
|
||||
},
|
||||
});
|
||||
ztoolkit.Menu.register("item", {
|
||||
tag: "menuitem",
|
||||
label: getString("menuItem.setMainNote"),
|
||||
icon: `chrome://${config.addonRef}/content/icons/favicon.png`,
|
||||
commandListener: (ev) => {
|
||||
addon.hooks.onSetWorkspaceNote(ZoteroPane.getSelectedItems()[0].id);
|
||||
},
|
||||
getVisibility: (elem, ev) => {
|
||||
const items = ZoteroPane.getSelectedItems();
|
||||
return (
|
||||
items.length == 1 &&
|
||||
items[0].isNote() &&
|
||||
items[0].id !== addon.data.workspace.mainId
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
// menuEdit
|
||||
const menuEditAnchor = document.querySelector(
|
||||
"#menu_preferences"
|
||||
) as XUL.MenuItem;
|
||||
ztoolkit.Menu.register(
|
||||
"menuEdit",
|
||||
{
|
||||
tag: "menuitem",
|
||||
label: getString("menuEdit.templatePicker"),
|
||||
icon: `chrome://${config.addonRef}/content/icons/favicon.png`,
|
||||
commandListener: (ev) => {
|
||||
addon.api.window.showTemplatePicker();
|
||||
},
|
||||
},
|
||||
"before",
|
||||
menuEditAnchor
|
||||
);
|
||||
ztoolkit.Menu.register(
|
||||
"menuEdit",
|
||||
{
|
||||
tag: "menuitem",
|
||||
label: getString("menuEdit.templateEditor"),
|
||||
icon: `chrome://${config.addonRef}/content/icons/favicon.png`,
|
||||
commandListener: (ev) => {
|
||||
addon.api.window.showTemplateEditor();
|
||||
},
|
||||
},
|
||||
"before",
|
||||
menuEditAnchor
|
||||
);
|
||||
ztoolkit.Menu.register(
|
||||
"menuEdit",
|
||||
{ tag: "menuseparator" },
|
||||
"before",
|
||||
menuEditAnchor
|
||||
);
|
||||
|
||||
// menuTools
|
||||
ztoolkit.Menu.register("menuTools", { tag: "menuseparator" });
|
||||
ztoolkit.Menu.register("menuTools", {
|
||||
tag: "menuitem",
|
||||
label: getString("menuTools.syncManager"),
|
||||
icon: `chrome://${config.addonRef}/content/icons/favicon.png`,
|
||||
commandListener: (ev) => {
|
||||
addon.api.window.showSyncManager();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
import { getNoteLinkParams } from "../utils/link";
|
||||
|
||||
export function registerNoteLinkProxyHandler() {
|
||||
const openNoteExtension = {
|
||||
noContent: true,
|
||||
doAction: async (uri: any) => {
|
||||
const linkParams = getNoteLinkParams(uri.spec);
|
||||
if (linkParams.noteItem) {
|
||||
addon.hooks.onOpenNote(linkParams.noteItem.id, "auto", {
|
||||
lineIndex: linkParams.lineIndex || undefined,
|
||||
});
|
||||
}
|
||||
},
|
||||
newChannel: function (uri: any) {
|
||||
this.doAction(uri);
|
||||
},
|
||||
};
|
||||
Services.io.getProtocolHandler("zotero").wrappedJSObject._extensions[
|
||||
"zotero://note"
|
||||
] = openNoteExtension;
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
export function registerNotify(types: _ZoteroTypes.Notifier.Type[]) {
|
||||
const callback = {
|
||||
notify: async (...data: Parameters<_ZoteroTypes.Notifier.Notify>) => {
|
||||
if (!addon?.data.alive) {
|
||||
unregisterNotify(notifyID);
|
||||
return;
|
||||
}
|
||||
addon.hooks.onNotify(...data);
|
||||
},
|
||||
};
|
||||
|
||||
// Register the callback in Zotero as an item observer
|
||||
const notifyID = Zotero.Notifier.registerObserver(callback, types);
|
||||
|
||||
// Unregister callback when the window closes (important to avoid a memory leak)
|
||||
window.addEventListener(
|
||||
"unload",
|
||||
(e: Event) => {
|
||||
unregisterNotify(notifyID);
|
||||
},
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
function unregisterNotify(notifyID: string) {
|
||||
Zotero.Notifier.unregisterObserver(notifyID);
|
||||
}
|
||||
|
|
@ -0,0 +1,145 @@
|
|||
import { config } from "../../package.json";
|
||||
import { getString } from "../utils/locale";
|
||||
|
||||
export function registerPrefsWindow() {
|
||||
ztoolkit.PreferencePane.register({
|
||||
pluginID: config.addonID,
|
||||
src: rootURI + "chrome/content/preferences.xhtml",
|
||||
label: getString("pref.title"),
|
||||
image: `chrome://${config.addonRef}/content/icons/favicon.png`,
|
||||
extraDTD: [`chrome://${config.addonRef}/locale/overlay.dtd`],
|
||||
defaultXUL: true,
|
||||
});
|
||||
}
|
||||
|
||||
export function registerPrefsScripts(_window: Window) {
|
||||
// This function is called when the prefs window is opened
|
||||
// See addon/chrome/content/preferences.xul onpaneload
|
||||
if (!addon.data.prefs) {
|
||||
addon.data.prefs = {
|
||||
window: _window,
|
||||
columns: [
|
||||
{
|
||||
dataKey: "title",
|
||||
label: "prefs.table.title",
|
||||
fixedWidth: true,
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
dataKey: "detail",
|
||||
label: "prefs.table.detail",
|
||||
},
|
||||
],
|
||||
rows: [
|
||||
{
|
||||
title: "Orange",
|
||||
detail: "It's juicy",
|
||||
},
|
||||
{
|
||||
title: "Banana",
|
||||
detail: "It's sweet",
|
||||
},
|
||||
{
|
||||
title: "Apple",
|
||||
detail: "I mean the fruit APPLE",
|
||||
},
|
||||
],
|
||||
};
|
||||
} else {
|
||||
addon.data.prefs.window = _window;
|
||||
}
|
||||
updatePrefsUI();
|
||||
bindPrefEvents();
|
||||
}
|
||||
|
||||
async function updatePrefsUI() {
|
||||
// You can initialize some UI elements on prefs window
|
||||
// with addon.data.prefs.window.document
|
||||
// Or bind some events to the elements
|
||||
const renderLock = ztoolkit.getGlobal("Zotero").Promise.defer();
|
||||
const tableHelper = new ztoolkit.VirtualizedTable(addon.data.prefs?.window!)
|
||||
.setContainerId(`${config.addonRef}-table-container`)
|
||||
.setProp({
|
||||
id: `${config.addonRef}-prefs-table`,
|
||||
// Do not use setLocale, as it modifies the Zotero.Intl.strings
|
||||
// Set locales directly to columns
|
||||
columns: addon.data.prefs?.columns.map((column) =>
|
||||
Object.assign(column, {
|
||||
label: getString(column.label) || column.label,
|
||||
})
|
||||
),
|
||||
showHeader: true,
|
||||
multiSelect: true,
|
||||
staticColumns: true,
|
||||
disableFontSizeScaling: true,
|
||||
})
|
||||
.setProp("getRowCount", () => addon.data.prefs?.rows.length || 0)
|
||||
.setProp(
|
||||
"getRowData",
|
||||
(index) =>
|
||||
addon.data.prefs?.rows[index] || {
|
||||
title: "no data",
|
||||
detail: "no data",
|
||||
}
|
||||
)
|
||||
// Show a progress window when selection changes
|
||||
.setProp("onSelectionChange", (selection) => {
|
||||
new ztoolkit.ProgressWindow(config.addonName)
|
||||
.createLine({
|
||||
text: `Selected line: ${addon.data.prefs?.rows
|
||||
.filter((v, i) => selection.isSelected(i))
|
||||
.map((row) => row.title)
|
||||
.join(",")}`,
|
||||
progress: 100,
|
||||
})
|
||||
.show();
|
||||
})
|
||||
// When pressing delete, delete selected line and refresh table.
|
||||
// Returning false to prevent default event.
|
||||
.setProp("onKeyDown", (event: KeyboardEvent) => {
|
||||
if (event.key == "Delete" || (Zotero.isMac && event.key == "Backspace")) {
|
||||
addon.data.prefs!.rows =
|
||||
addon.data.prefs?.rows.filter(
|
||||
(v, i) => !tableHelper.treeInstance.selection.isSelected(i)
|
||||
) || [];
|
||||
tableHelper.render();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
// For find-as-you-type
|
||||
.setProp(
|
||||
"getRowString",
|
||||
(index) => addon.data.prefs?.rows[index].title || ""
|
||||
)
|
||||
// Render the table.
|
||||
.render(-1, () => {
|
||||
renderLock.resolve();
|
||||
});
|
||||
await renderLock.promise;
|
||||
ztoolkit.log("Preference table rendered!");
|
||||
}
|
||||
|
||||
function bindPrefEvents() {
|
||||
addon.data
|
||||
.prefs!.window.document.querySelector(
|
||||
`#zotero-prefpane-${config.addonRef}-enable`
|
||||
)
|
||||
?.addEventListener("command", (e) => {
|
||||
ztoolkit.log(e);
|
||||
addon.data.prefs!.window.alert(
|
||||
`Successfully changed to ${(e.target as XUL.Checkbox).checked}!`
|
||||
);
|
||||
});
|
||||
|
||||
addon.data
|
||||
.prefs!!.window.document.querySelector(
|
||||
`#zotero-prefpane-${config.addonRef}-input`
|
||||
)
|
||||
?.addEventListener("change", (e) => {
|
||||
ztoolkit.log(e);
|
||||
addon.data.prefs!.window.alert(
|
||||
`Successfully changed to ${(e.target as HTMLInputElement).value}!`
|
||||
);
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,391 @@
|
|||
import { TagElementProps } from "zotero-plugin-toolkit/dist/tools/ui";
|
||||
import { config } from "../../package.json";
|
||||
import { ICONS } from "../utils/config";
|
||||
import { getNoteLink, getNoteLinkParams } from "../utils/link";
|
||||
import { addLineToNote } from "../utils/note";
|
||||
|
||||
export function registerReaderInitializer() {
|
||||
ztoolkit.ReaderInstance.register(
|
||||
"initialized",
|
||||
`${config.addonRef}-annotationButtons`,
|
||||
initializeReaderAnnotationButton
|
||||
);
|
||||
// Force re-initialize
|
||||
Zotero.Reader._readers.forEach((r) => {
|
||||
initializeReaderAnnotationButton(r);
|
||||
});
|
||||
}
|
||||
|
||||
export function unregisterReaderInitializer() {
|
||||
Zotero.Reader._readers.forEach((r) => {
|
||||
unInitializeReaderAnnotationButton(r);
|
||||
});
|
||||
}
|
||||
|
||||
export async function checkReaderAnnotationButton(items: Zotero.Item[]) {
|
||||
const hitSet = new Set<number>();
|
||||
let t = 0;
|
||||
const period = 100;
|
||||
const wait = 5000;
|
||||
while (items.length > hitSet.size && t < wait) {
|
||||
for (const instance of Zotero.Reader._readers) {
|
||||
const hitItems = await initializeReaderAnnotationButton(instance);
|
||||
hitItems.forEach((item) => hitSet.add(item.id));
|
||||
}
|
||||
await Zotero.Promise.delay(period);
|
||||
t += period;
|
||||
}
|
||||
}
|
||||
|
||||
async function initializeReaderAnnotationButton(
|
||||
instance: _ZoteroTypes.ReaderInstance
|
||||
): Promise<Zotero.Item[]> {
|
||||
if (!instance) {
|
||||
return [];
|
||||
}
|
||||
await instance._initPromise;
|
||||
await instance._waitForReader();
|
||||
const _document = instance._iframeWindow?.document;
|
||||
if (!_document) {
|
||||
return [];
|
||||
}
|
||||
const hitItems: Zotero.Item[] = [];
|
||||
for (const moreButton of _document.querySelectorAll(".more")) {
|
||||
if (moreButton.getAttribute("_betternotesInitialized") === "true") {
|
||||
continue;
|
||||
}
|
||||
moreButton.setAttribute("_betternotesInitialized", "true");
|
||||
|
||||
let annotationWrapper = moreButton;
|
||||
while (!annotationWrapper.getAttribute("data-sidebar-annotation-id")) {
|
||||
annotationWrapper = annotationWrapper.parentElement!;
|
||||
}
|
||||
const itemKey =
|
||||
annotationWrapper.getAttribute("data-sidebar-annotation-id") || "";
|
||||
if (!instance.itemID) {
|
||||
continue;
|
||||
}
|
||||
const libraryID = Zotero.Items.get(instance.itemID).libraryID;
|
||||
const annotationItem = (await Zotero.Items.getByLibraryAndKeyAsync(
|
||||
libraryID,
|
||||
itemKey
|
||||
)) as Zotero.Item;
|
||||
|
||||
if (!annotationItem) {
|
||||
continue;
|
||||
}
|
||||
|
||||
hitItems.push(annotationItem);
|
||||
|
||||
const annotationButtons: TagElementProps[] = [
|
||||
{
|
||||
tag: "div",
|
||||
classList: ["icon"],
|
||||
properties: {
|
||||
innerHTML: ICONS.readerQuickNote,
|
||||
},
|
||||
listeners: [
|
||||
{
|
||||
type: "click",
|
||||
listener: (e) => {
|
||||
createNoteFromAnnotation(
|
||||
annotationItem,
|
||||
(e as MouseEvent).shiftKey ? "standalone" : "auto"
|
||||
);
|
||||
e.preventDefault();
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "mouseover",
|
||||
listener: (e) => {
|
||||
(e.target as HTMLElement).style.backgroundColor = "#F0F0F0";
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "mouseout",
|
||||
listener: (e) => {
|
||||
(e.target as HTMLElement).style.removeProperty(
|
||||
"background-color"
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
enableElementRecord: true,
|
||||
},
|
||||
];
|
||||
|
||||
if (annotationItem.annotationType === "image") {
|
||||
annotationButtons.push({
|
||||
tag: "div",
|
||||
classList: ["icon"],
|
||||
properties: {
|
||||
innerHTML: ICONS.readerOCR,
|
||||
},
|
||||
listeners: [
|
||||
{
|
||||
type: "click",
|
||||
listener: (e) => {
|
||||
// TODO: OCR
|
||||
e.preventDefault();
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "mouseover",
|
||||
listener: (e) => {
|
||||
(e.target as HTMLElement).style.backgroundColor = "#F0F0F0";
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "mouseout",
|
||||
listener: (e) => {
|
||||
(e.target as HTMLElement).style.removeProperty(
|
||||
"background-color"
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
enableElementRecord: true,
|
||||
});
|
||||
}
|
||||
|
||||
ztoolkit.UI.insertElementBefore(
|
||||
{
|
||||
tag: "fragment",
|
||||
children: annotationButtons,
|
||||
},
|
||||
moreButton
|
||||
);
|
||||
}
|
||||
return hitItems;
|
||||
}
|
||||
|
||||
async function unInitializeReaderAnnotationButton(
|
||||
instance: _ZoteroTypes.ReaderInstance
|
||||
): Promise<void> {
|
||||
if (!instance) {
|
||||
return;
|
||||
}
|
||||
await instance._initPromise;
|
||||
await instance._waitForReader();
|
||||
const _document = instance._iframeWindow?.document;
|
||||
if (!_document) {
|
||||
return;
|
||||
}
|
||||
for (const moreButton of _document.querySelectorAll(".more")) {
|
||||
if (moreButton.getAttribute("_betternotesInitialized") === "true") {
|
||||
moreButton.removeAttribute("_betternotesInitialized");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function createNoteFromAnnotation(
|
||||
annotationItem: Zotero.Item,
|
||||
openMode: "standalone" | "auto" = "auto"
|
||||
) {
|
||||
const annotationTags = annotationItem.getTags().map((_) => _.tag);
|
||||
const linkRegex = new RegExp("^zotero://note/(.*)$");
|
||||
for (const tag of annotationTags) {
|
||||
if (linkRegex.test(tag)) {
|
||||
const linkParams = getNoteLinkParams(tag);
|
||||
if (linkParams.noteItem) {
|
||||
addon.hooks.onOpenNote(linkParams.noteItem.id, openMode, {
|
||||
lineIndex: linkParams.lineIndex || undefined,
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
annotationItem.removeTag(tag);
|
||||
await annotationItem.saveTx();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const note: Zotero.Item = new Zotero.Item("note");
|
||||
note.libraryID = annotationItem.libraryID;
|
||||
note.parentID = annotationItem.parentItem!.parentID;
|
||||
await note.saveTx();
|
||||
|
||||
// await waitUtilAsync(() => Boolean(getEditorInstance(note.id)));
|
||||
|
||||
const renderredTemplate = await addon.api.template.runTemplate(
|
||||
"[QuickNoteV5]",
|
||||
"annotationItem, topItem, noteItem",
|
||||
[annotationItem, annotationItem.parentItem!.parentItem, note]
|
||||
);
|
||||
await addLineToNote(note, renderredTemplate);
|
||||
|
||||
const tags = annotationItem.getTags();
|
||||
for (const tag of tags) {
|
||||
note.addTag(tag.tag, tag.type);
|
||||
}
|
||||
await note.saveTx();
|
||||
|
||||
ZoteroPane.openNoteWindow(note.id);
|
||||
|
||||
annotationItem.addTag(getNoteLink(note)!);
|
||||
await annotationItem.saveTx();
|
||||
}
|
||||
|
||||
// async function OCRImageAnnotation(src: string, annotationItem: Zotero.Item) {
|
||||
// /*
|
||||
// message.content = {
|
||||
// params: { src: string, annotationItem: Zotero.Item }
|
||||
// }
|
||||
// */
|
||||
// let result: string;
|
||||
// let success: boolean;
|
||||
// const engine = Zotero.Prefs.get("Knowledge4Zotero.OCREngine");
|
||||
// if (engine === "mathpix") {
|
||||
// const xhr = await Zotero.HTTP.request(
|
||||
// "POST",
|
||||
// "https://api.mathpix.com/v3/text",
|
||||
// {
|
||||
// headers: {
|
||||
// "Content-Type": "application/json; charset=utf-8",
|
||||
// app_id: Zotero.Prefs.get("Knowledge4Zotero.OCRMathpix.Appid"),
|
||||
// app_key: Zotero.Prefs.get("Knowledge4Zotero.OCRMathpix.Appkey"),
|
||||
// },
|
||||
// body: JSON.stringify({
|
||||
// src: src,
|
||||
// math_inline_delimiters: ["$", "$"],
|
||||
// math_display_delimiters: ["$$", "$$"],
|
||||
// rm_spaces: true,
|
||||
// }),
|
||||
// responseType: "json",
|
||||
// }
|
||||
// );
|
||||
// this._Addon.toolkit.Tool.log(xhr);
|
||||
// if (xhr && xhr.status && xhr.status === 200 && xhr.response.text) {
|
||||
// result = xhr.response.text;
|
||||
// success = true;
|
||||
// } else {
|
||||
// result = xhr.status === 200 ? xhr.response.error : `${xhr.status} Error`;
|
||||
// success = false;
|
||||
// }
|
||||
// } else if (engine === "xunfei") {
|
||||
// /**
|
||||
// * 1.Doc:https://www.xfyun.cn/doc/words/formula-discern/API.html
|
||||
// * 2.Error code:https://www.xfyun.cn/document/error-code
|
||||
// * @author iflytek
|
||||
// */
|
||||
|
||||
// const config = {
|
||||
// hostUrl: "https://rest-api.xfyun.cn/v2/itr",
|
||||
// host: "rest-api.xfyun.cn",
|
||||
// appid: Zotero.Prefs.get("Knowledge4Zotero.OCRXunfei.APPID"),
|
||||
// apiSecret: Zotero.Prefs.get("Knowledge4Zotero.OCRXunfei.APISecret"),
|
||||
// apiKey: Zotero.Prefs.get("Knowledge4Zotero.OCRXunfei.APIKey"),
|
||||
// uri: "/v2/itr",
|
||||
// };
|
||||
|
||||
// let date = new Date().toUTCString();
|
||||
// let postBody = getPostBody();
|
||||
// let digest = getDigest(postBody);
|
||||
|
||||
// const xhr = await Zotero.HTTP.request("POST", config.hostUrl, {
|
||||
// headers: {
|
||||
// "Content-Type": "application/json",
|
||||
// Accept: "application/json,version=1.0",
|
||||
// Host: config.host,
|
||||
// Date: date,
|
||||
// Digest: digest,
|
||||
// Authorization: getAuthStr(date, digest),
|
||||
// },
|
||||
// body: JSON.stringify(postBody),
|
||||
// responseType: "json",
|
||||
// });
|
||||
|
||||
// if (xhr?.response?.code === 0) {
|
||||
// result = xhr.response.data.region
|
||||
// .filter((r) => r.type === "text")
|
||||
// .map((r) => r.recog.content)
|
||||
// .join(" ")
|
||||
// .replace(/ifly-latex-(begin)?(end)?/g, "$");
|
||||
// this._Addon.toolkit.Tool.log(xhr);
|
||||
// success = true;
|
||||
// } else {
|
||||
// result =
|
||||
// xhr.status === 200
|
||||
// ? `${xhr.response.code} ${xhr.response.message}`
|
||||
// : `${xhr.status} Error`;
|
||||
// success = false;
|
||||
// }
|
||||
|
||||
// function getPostBody() {
|
||||
// let digestObj = {
|
||||
// common: {
|
||||
// app_id: config.appid,
|
||||
// },
|
||||
// business: {
|
||||
// ent: "teach-photo-print",
|
||||
// aue: "raw",
|
||||
// },
|
||||
// data: {
|
||||
// image: src.split(",").pop(),
|
||||
// },
|
||||
// };
|
||||
// return digestObj;
|
||||
// }
|
||||
|
||||
// function getDigest(body) {
|
||||
// return (
|
||||
// "SHA-256=" +
|
||||
// CryptoJS.enc.Base64.stringify(CryptoJS.SHA256(JSON.stringify(body)))
|
||||
// );
|
||||
// }
|
||||
|
||||
// function getAuthStr(date, digest) {
|
||||
// let signatureOrigin = `host: ${config.host}\ndate: ${date}\nPOST ${config.uri} HTTP/1.1\ndigest: ${digest}`;
|
||||
// let signatureSha = CryptoJS.HmacSHA256(signatureOrigin, config.apiSecret);
|
||||
// let signature = CryptoJS.enc.Base64.stringify(signatureSha);
|
||||
// let authorizationOrigin = `api_key="${config.apiKey}", algorithm="hmac-sha256", headers="host date request-line digest", signature="${signature}"`;
|
||||
// return authorizationOrigin;
|
||||
// }
|
||||
// } else if (engine === "bing") {
|
||||
// const xhr = await Zotero.HTTP.request(
|
||||
// "POST",
|
||||
// "https://www.bing.com/cameraexp/api/v1/getlatex",
|
||||
// {
|
||||
// headers: {
|
||||
// "Content-Type": "application/json",
|
||||
// },
|
||||
// body: JSON.stringify({
|
||||
// data: src.split(",").pop(),
|
||||
// inputForm: "Image",
|
||||
// clientInfo: { platform: "edge" },
|
||||
// }),
|
||||
// responseType: "json",
|
||||
// }
|
||||
// );
|
||||
// if (xhr && xhr.status && xhr.status === 200 && !xhr.response.isError) {
|
||||
// result = xhr.response.latex
|
||||
// ? `$${xhr.response.latex}$`
|
||||
// : xhr.response.ocrText;
|
||||
// success = true;
|
||||
// } else {
|
||||
// result =
|
||||
// xhr.status === 200 ? xhr.response.errorMessage : `${xhr.status} Error`;
|
||||
// success = false;
|
||||
// }
|
||||
// } else {
|
||||
// result = "OCR Engine Not Found";
|
||||
// success = false;
|
||||
// }
|
||||
// if (success) {
|
||||
// annotationItem.annotationComment = `${
|
||||
// annotationItem.annotationComment
|
||||
// ? `${annotationItem.annotationComment}\n`
|
||||
// : ""
|
||||
// }${result}`;
|
||||
// await annotationItem.saveTx();
|
||||
// this._Addon.ZoteroViews.showProgressWindow(
|
||||
// "Better Notes OCR",
|
||||
// `OCR Result: ${result}`
|
||||
// );
|
||||
// } else {
|
||||
// this._Addon.ZoteroViews.showProgressWindow(
|
||||
// "Better Notes OCR",
|
||||
// result,
|
||||
// "fail"
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
|
@ -0,0 +1,434 @@
|
|||
import YAML = require("yamljs");
|
||||
import { showHint } from "../../utils/hint";
|
||||
import { getNoteLinkParams } from "../../utils/link";
|
||||
import { clearPref, getPref, setPref } from "../../utils/prefs";
|
||||
import { getString } from "../../utils/locale";
|
||||
|
||||
export {
|
||||
getRelatedNoteIds,
|
||||
removeSyncNote,
|
||||
isSyncNote,
|
||||
getSyncNoteIds,
|
||||
addSyncNote,
|
||||
updateSyncStatus,
|
||||
doSync,
|
||||
setSync,
|
||||
getSyncStatus,
|
||||
getNoteStatus,
|
||||
getMDStatus,
|
||||
getMDStatusFromContent,
|
||||
getMDFileName,
|
||||
};
|
||||
|
||||
function getSyncNoteIds(): number[] {
|
||||
const ids = getPref("syncNoteIds") as string;
|
||||
return Zotero.Items.get(ids.split(",").map((id: string) => Number(id)))
|
||||
.filter((item) => item.isNote())
|
||||
.map((item) => item.id);
|
||||
}
|
||||
|
||||
function isSyncNote(noteId: number): boolean {
|
||||
const syncNoteIds = getSyncNoteIds();
|
||||
return syncNoteIds.includes(noteId);
|
||||
}
|
||||
|
||||
async function getRelatedNoteIds(noteId: number): Promise<number[]> {
|
||||
let allNoteIds: number[] = [noteId];
|
||||
const note = Zotero.Items.get(noteId);
|
||||
const linkMatches = note.getNote().match(/zotero:\/\/note\/\w+\/\w+\//g);
|
||||
if (!linkMatches) {
|
||||
return allNoteIds;
|
||||
}
|
||||
const subNoteIds = (
|
||||
await Promise.all(
|
||||
linkMatches.map(async (link) => getNoteLinkParams(link).noteItem)
|
||||
)
|
||||
)
|
||||
.filter((item) => item && item.isNote())
|
||||
.map((item) => (item as Zotero.Item).id);
|
||||
allNoteIds = allNoteIds.concat(subNoteIds);
|
||||
allNoteIds = new Array(...new Set(allNoteIds));
|
||||
return allNoteIds;
|
||||
}
|
||||
|
||||
async function getRelatedNoteIdsFromNotes(
|
||||
noteIds: number[]
|
||||
): Promise<number[]> {
|
||||
let allNoteIds: number[] = [];
|
||||
for (const noteId of noteIds) {
|
||||
allNoteIds = allNoteIds.concat(await getRelatedNoteIds(noteId));
|
||||
}
|
||||
return allNoteIds;
|
||||
}
|
||||
|
||||
function addSyncNote(noteId: number) {
|
||||
const ids = getSyncNoteIds();
|
||||
if (ids.includes(noteId)) {
|
||||
return;
|
||||
}
|
||||
ids.push(noteId);
|
||||
setPref("syncNoteIds", ids.join(","));
|
||||
}
|
||||
|
||||
function removeSyncNote(noteId: number) {
|
||||
const ids = getSyncNoteIds();
|
||||
setPref("syncNoteIds", ids.filter((id) => id !== noteId).join(","));
|
||||
clearPref(`syncDetail-${noteId}`);
|
||||
}
|
||||
|
||||
function updateSyncStatus(noteId: number, status: SyncStatus) {
|
||||
addSyncNote(noteId);
|
||||
setPref(`syncDetail-${noteId}`, JSON.stringify(status));
|
||||
}
|
||||
function getNoteStatus(noteId: number) {
|
||||
const noteItem = Zotero.Items.get(noteId);
|
||||
if (!noteItem?.isNote()) {
|
||||
return;
|
||||
}
|
||||
const fullContent = noteItem.getNote();
|
||||
const ret = {
|
||||
meta: "",
|
||||
content: "",
|
||||
tail: "</div>",
|
||||
lastmodify: Zotero.Date.sqlToDate(noteItem.dateModified, true),
|
||||
};
|
||||
const metaRegex = /"?data-schema-version"?="[0-9]*">/;
|
||||
const match = fullContent?.match(metaRegex);
|
||||
if (!match || match.length == 0) {
|
||||
ret.meta = '<div "data-schema-version"="9">';
|
||||
ret.content = fullContent || "";
|
||||
return ret;
|
||||
}
|
||||
const idx = fullContent.search(metaRegex);
|
||||
if (idx != -1) {
|
||||
ret.content = fullContent.substring(
|
||||
idx + match[0].length,
|
||||
fullContent.length - ret.tail.length
|
||||
);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
function getSyncStatus(noteId?: number): SyncStatus {
|
||||
const defaultStatus = JSON.stringify({
|
||||
path: "",
|
||||
filename: "",
|
||||
md5: "",
|
||||
noteMd5: "",
|
||||
lastsync: new Date().getTime(),
|
||||
itemID: -1,
|
||||
});
|
||||
return JSON.parse(
|
||||
(getPref(`syncDetail-${noteId}`) as string) || defaultStatus
|
||||
);
|
||||
}
|
||||
|
||||
function getMDStatusFromContent(contentRaw: string): MDStatus {
|
||||
const result = contentRaw.match(/^---([\s\S]*)---\n/);
|
||||
const ret: MDStatus = {
|
||||
meta: { version: -1 },
|
||||
content: contentRaw,
|
||||
filedir: "",
|
||||
filename: "",
|
||||
lastmodify: new Date(0),
|
||||
};
|
||||
if (result) {
|
||||
const yaml = result[0].replace(/---/g, "");
|
||||
ret.content = contentRaw.slice(result[0].length);
|
||||
try {
|
||||
ret.meta = YAML.parse(yaml);
|
||||
} catch (e) {
|
||||
ztoolkit.log(e);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
async function getMDStatus(
|
||||
source: Zotero.Item | number | string
|
||||
): Promise<MDStatus> {
|
||||
let ret: MDStatus = {
|
||||
meta: null,
|
||||
content: "",
|
||||
filedir: "",
|
||||
filename: "",
|
||||
lastmodify: new Date(0),
|
||||
};
|
||||
try {
|
||||
let filepath = "";
|
||||
if (typeof source === "string") {
|
||||
filepath = source;
|
||||
} else if (typeof source === "number") {
|
||||
const syncStatus = getSyncStatus(source);
|
||||
filepath = `${syncStatus.path}/${syncStatus.filename}`;
|
||||
} else if (source.isNote && source.isNote()) {
|
||||
const syncStatus = getSyncStatus(source.id);
|
||||
filepath = `${syncStatus.path}/${syncStatus.filename}`;
|
||||
}
|
||||
filepath = Zotero.File.normalizeToUnix(filepath);
|
||||
if (await OS.File.exists(filepath)) {
|
||||
let contentRaw = (await OS.File.read(filepath, {
|
||||
encoding: "utf-8",
|
||||
})) as string;
|
||||
ret = getMDStatusFromContent(contentRaw);
|
||||
const pathSplit = filepath.split("/");
|
||||
ret.filedir = Zotero.File.normalizeToUnix(
|
||||
pathSplit.slice(0, -1).join("/")
|
||||
);
|
||||
ret.filename = filepath.split("/").pop() || "";
|
||||
const stat = await OS.File.stat(filepath);
|
||||
ret.lastmodify = stat.lastModificationDate;
|
||||
}
|
||||
} catch (e) {
|
||||
ztoolkit.log(e);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
async function getMDFileName(noteId: number, searchDir?: string) {
|
||||
const noteItem = Zotero.Items.get(noteId);
|
||||
if (searchDir !== undefined && (await OS.File.exists(searchDir))) {
|
||||
const mdRegex = /\.(md|MD|Md|mD)$/;
|
||||
let matchedFileName = null;
|
||||
let matchedDate = new Date(0);
|
||||
await Zotero.File.iterateDirectory(
|
||||
searchDir,
|
||||
async (entry: OS.File.Entry) => {
|
||||
if (entry.isDir) return;
|
||||
if (mdRegex.test(entry.name)) {
|
||||
if (
|
||||
entry.name.split(".").shift()?.split("-").pop() === noteItem.key
|
||||
) {
|
||||
const stat = await OS.File.stat(entry.path);
|
||||
if (stat.lastModificationDate > matchedDate) {
|
||||
matchedFileName = entry.name;
|
||||
matchedDate = stat.lastModificationDate;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
if (matchedFileName) {
|
||||
return matchedFileName;
|
||||
}
|
||||
}
|
||||
return await addon.api.template.runTemplate(
|
||||
"[ExportMDFileNameV2]",
|
||||
"noteItem",
|
||||
[noteItem]
|
||||
);
|
||||
}
|
||||
|
||||
function setSync() {
|
||||
const syncPeriod = getPref("syncPeriodSeconds") as number;
|
||||
if (syncPeriod > 0) {
|
||||
showHint(`${getString("sync.start.hint")} ${syncPeriod} s`);
|
||||
const timer = ztoolkit.getGlobal("setInterval")(() => {
|
||||
if (!addon.data.alive) {
|
||||
showHint(getString("sync.stop.hint"));
|
||||
ztoolkit.getGlobal("clearInterval")(timer);
|
||||
}
|
||||
// Only when Zotero is active and focused
|
||||
if (document.hasFocus() && (getPref("syncPeriodSeconds") as number) > 0) {
|
||||
doSync(undefined, { quiet: true, skipActive: true, reason: "auto" });
|
||||
}
|
||||
}, Number(syncPeriod) * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
async function doCompare(noteItem: Zotero.Item): Promise<SyncCode> {
|
||||
const syncStatus = getSyncStatus(noteItem.id);
|
||||
const MDStatus = await getMDStatus(noteItem.id);
|
||||
// No file found
|
||||
if (!MDStatus.meta) {
|
||||
return SyncCode.NoteAhead;
|
||||
}
|
||||
// File meta is unavailable
|
||||
if (MDStatus.meta.version < 0) {
|
||||
return SyncCode.NeedDiff;
|
||||
}
|
||||
let MDAhead = false;
|
||||
let noteAhead = false;
|
||||
const md5 = Zotero.Utilities.Internal.md5(MDStatus.content, false);
|
||||
const noteMd5 = Zotero.Utilities.Internal.md5(noteItem.getNote(), false);
|
||||
// MD5 doesn't match (md side change)
|
||||
if (md5 !== syncStatus.md5) {
|
||||
MDAhead = true;
|
||||
}
|
||||
// MD5 doesn't match (note side change)
|
||||
if (noteMd5 !== syncStatus.noteMd5) {
|
||||
noteAhead = true;
|
||||
}
|
||||
// Note version doesn't match (note side change)
|
||||
// This might be unreliable when Zotero account is not login
|
||||
if (Number(MDStatus.meta.version) !== noteItem.version) {
|
||||
noteAhead = true;
|
||||
}
|
||||
if (noteAhead && MDAhead) {
|
||||
return SyncCode.NeedDiff;
|
||||
} else if (noteAhead) {
|
||||
return SyncCode.NoteAhead;
|
||||
} else if (MDAhead) {
|
||||
return SyncCode.MDAhead;
|
||||
} else {
|
||||
return SyncCode.UpToDate;
|
||||
}
|
||||
}
|
||||
|
||||
async function doSync(
|
||||
items: Zotero.Item[] = [],
|
||||
{ quiet, skipActive, reason } = {
|
||||
quiet: true,
|
||||
skipActive: true,
|
||||
reason: "unknown",
|
||||
}
|
||||
) {
|
||||
// Always log in development mode
|
||||
if (addon.data.env === "development") {
|
||||
quiet = false;
|
||||
}
|
||||
if (addon.data.sync.lock) {
|
||||
// Only allow one task
|
||||
return;
|
||||
}
|
||||
let progress;
|
||||
// Wrap the code in try...catch so that the lock can be released anyway
|
||||
try {
|
||||
addon.data.sync.lock = true;
|
||||
let skippedCount = 0;
|
||||
if (!items || !items.length) {
|
||||
items = Zotero.Items.get(getSyncNoteIds());
|
||||
}
|
||||
if (skipActive) {
|
||||
// Skip active note editors' targets
|
||||
const activeNoteIds = Zotero.Notes._editorInstances
|
||||
.filter((editor) => editor._iframeWindow.document.hasFocus())
|
||||
.map((editor) => editor._item.id);
|
||||
const filteredItems = items.filter(
|
||||
(item) => !activeNoteIds.includes(item.id)
|
||||
);
|
||||
skippedCount = items.length - filteredItems.length;
|
||||
items = filteredItems;
|
||||
}
|
||||
ztoolkit.log("sync start", reason, items, skippedCount);
|
||||
|
||||
if (!quiet) {
|
||||
progress = new ztoolkit.ProgressWindow(
|
||||
`[${getString("sync.running.hint.title")}] ${
|
||||
addon.data.env === "development" ? reason : "Better Notes"
|
||||
}`
|
||||
)
|
||||
.createLine({
|
||||
text: `[${getString("sync.running.hint.check")}] 0/${
|
||||
items.length
|
||||
} ...`,
|
||||
type: "default",
|
||||
progress: 1,
|
||||
})
|
||||
.show(-1);
|
||||
}
|
||||
// Export items of same dir in batch
|
||||
const toExport = {} as Record<string, number[]>;
|
||||
const toImport: SyncStatus[] = [];
|
||||
const toDiff: SyncStatus[] = [];
|
||||
let i = 1;
|
||||
for (const item of items) {
|
||||
const syncStatus = getSyncStatus(item.id);
|
||||
const filepath = syncStatus.path;
|
||||
let compareResult = await doCompare(item);
|
||||
switch (compareResult) {
|
||||
case SyncCode.NoteAhead:
|
||||
if (Object.keys(toExport).includes(filepath)) {
|
||||
toExport[filepath].push(item.id);
|
||||
} else {
|
||||
toExport[filepath] = [item.id];
|
||||
}
|
||||
break;
|
||||
case SyncCode.MDAhead:
|
||||
toImport.push(syncStatus);
|
||||
break;
|
||||
case SyncCode.NeedDiff:
|
||||
toDiff.push(syncStatus);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
progress?.changeLine({
|
||||
text: `[${getString("sync.running.hint.check")}] ${i}/${
|
||||
items.length
|
||||
} ...`,
|
||||
progress: ((i - 1) / items.length) * 100,
|
||||
});
|
||||
i += 1;
|
||||
}
|
||||
ztoolkit.log("will be synced:", toExport, toImport, toDiff);
|
||||
i = 1;
|
||||
let totalCount = Object.keys(toExport).length;
|
||||
for (const filepath of Object.keys(toExport)) {
|
||||
progress?.changeLine({
|
||||
text: `[${getString("sync.running.hint.updateMD")}] ${i}/${
|
||||
items.length
|
||||
} ...`,
|
||||
progress: ((i - 1) / items.length) * 100,
|
||||
});
|
||||
await addon.api._export.syncMDBatch(filepath, toExport[filepath]);
|
||||
i += 1;
|
||||
}
|
||||
i = 1;
|
||||
totalCount = toImport.length;
|
||||
for (const syncStatus of toImport) {
|
||||
progress?.changeLine({
|
||||
text: `[${getString(
|
||||
"sync.running.hint.updateNote"
|
||||
)}] ${i}/${totalCount}, ${toDiff.length} queuing...`,
|
||||
progress: ((i - 1) / totalCount) * 100,
|
||||
});
|
||||
const item = Zotero.Items.get(syncStatus.itemID);
|
||||
const filepath = OS.Path.join(syncStatus.path, syncStatus.filename);
|
||||
await addon.api._import.fromMD(filepath, { noteId: item.id });
|
||||
// Update md file to keep the metadata synced
|
||||
await addon.api._export.syncMDBatch(syncStatus.path, [item.id]);
|
||||
i += 1;
|
||||
}
|
||||
i = 1;
|
||||
totalCount = toDiff.length;
|
||||
for (const syncStatus of toDiff) {
|
||||
progress?.changeLine({
|
||||
text: `[${getString("sync.running.hint.diff")}] ${i}/${totalCount}...`,
|
||||
progress: ((i - 1) / totalCount) * 100,
|
||||
});
|
||||
|
||||
await addon.api.window.showSyncDiff(
|
||||
syncStatus.itemID,
|
||||
OS.Path.join(syncStatus.path, syncStatus.filename)
|
||||
);
|
||||
i += 1;
|
||||
}
|
||||
const syncCount =
|
||||
Object.keys(toExport).length + toImport.length + toDiff.length;
|
||||
progress?.changeLine({
|
||||
text:
|
||||
(syncCount
|
||||
? `[${getString(
|
||||
"sync.running.hint.finish"
|
||||
)}] ${syncCount} ${getString("sync.running.hint.synced")}`
|
||||
: `[${getString("sync.running.hint.finish")}] ${getString(
|
||||
"sync.running.hint.upToDate"
|
||||
)}`) + (skippedCount ? `, ${skippedCount} skipped.` : ""),
|
||||
progress: 100,
|
||||
});
|
||||
} catch (e) {
|
||||
ztoolkit.log(e);
|
||||
showHint(`Sync Error: ${String(e)}`);
|
||||
} finally {
|
||||
progress?.startCloseTimer(5000);
|
||||
}
|
||||
addon.data.sync.lock = false;
|
||||
}
|
||||
|
||||
enum SyncCode {
|
||||
UpToDate = 0,
|
||||
NoteAhead,
|
||||
MDAhead,
|
||||
NeedDiff,
|
||||
}
|
||||