Compare commits

...

18 Commits

Author SHA1 Message Date
Tobias Manske 2d45296b14
Merge pull request #23 from gugutu/master
continuous-integration/drone/tag Build is passing Details
2024-03-05 00:56:47 +01:00
gugutu def0045819
Fix the issue of incorrect JS file names introduced under Anki 23.12.1 2024-03-05 03:47:12 +08:00
gugutu 6acf2d5bb5
Fix for Anki version 24.04+ 2024-03-01 16:30:31 +08:00
gugutu d6034c6dcf
Fix for Anki version 24.04+ 2024-03-01 16:19:38 +08:00
gugutu 65cc6c0b12
Modify the implementation method of "avoiding secondary refresh"
The previous implementation may encounter issues in specific scenarios and could potentially become invalid after Anki updates. Now, the approach has been modified to check whether the note content has been modified, which is a simpler and more understandable method.
2024-02-25 05:11:17 +08:00
gugutu 8487bb86d0
Increase the setSizes parameter
If the parameter given to setSizes is very small and smaller than QSplitter, the sub windows within QSplitter will be stretched. It is advisable to set a larger parameter for setSizes as much as possible.
2024-02-02 09:12:49 +08:00
gugutu 74d12576cd
Fix memory leakage caused by hook not releasing
Due to Anki not providing a hook when closing the editor, refactoring to use set filter to check which editors are not active and avoid creating a large number of hooks. By traversing the set, it is determined whether the current editor needs to be refreshed.
2024-02-02 08:50:17 +08:00
gugutu deca35908a
Improve compatibility with other plugins
Wrap a widget on the outer layer of the webview so that other plugins can continue to modify the layout.
2024-02-02 07:28:16 +08:00
gugutu 2023cea60c
Fix second refresh after loading note 2024-02-02 07:10:47 +08:00
Tobias Manske cefa65eadf
Merge pull request #20 from spacelord47/fix/use_qt6_enums_properly
continuous-integration/drone/tag Build is passing Details
Fixes: #19 
Fixes: #18
2023-11-05 16:02:06 +01:00
spacelord47 766d246f46
fix: use Qt6 enums properly
New Anki version(23.10) dropped compatibility for Qt5: https://forums.ankiweb.net/t/porting-tips-for-anki-23-10/35916#enumerations-6
2023-11-05 13:53:17 +00:00
Tobias Manske 23f9c0cb68
Patch version check for new versioning scheme
I haven't taken a look at anything in the new version yet.
But that closes #16.
2023-09-25 11:50:44 +02:00
Tobias Manske ab68523be6
Merge PR #10 2023-03-24 18:16:25 +01:00
Tobias Manske af073e0498
Build addon automatically on push
continuous-integration/drone/tag Build is passing Details
Signed-off-by: Tobias Manske <tobias.manske@mailbox.org>
2023-03-24 18:02:29 +01:00
Tobias Manske 4c966c1f5f
Format
Signed-off-by: Tobias Manske <tobias.manske@mailbox.org>
2023-03-24 18:02:28 +01:00
Tobias Manske 8e2815bc87
Add option to split side-by-side view. Also fixes split ratio handling
Signed-off-by: Tobias Manske <tobias.manske@mailbox.org>
2023-03-24 18:02:25 +01:00
Premysl Bednarek 734f24646e Remove the usage of deprecated function.
mv.pm.night_mode() is deprecated (see
1ed2cce648/qt/aqt/profiles.py (L537)).
Furthermore, on anki Version ⁨2.1.54, Python 3.9.10 Qt 6.3.1 PyQt 6.3.1, this function returns false, even when the dark mode is set.
2022-12-14 08:36:12 +01:00
Tobias Manske 58cd3cec42
Build changes for PR #7 2022-10-10 18:59:30 +02:00
6 changed files with 135 additions and 38 deletions

31
.drone.yml Normal file
View File

@ -0,0 +1,31 @@
---
kind: pipeline
type: docker
name: Build Anki Plugin
trigger:
event:
include:
- tag
steps:
- name: Build Archive
image: debian:bookworm
pull: always
commands:
- apt-get update && apt-get install -y zip
- ./build.sh
- name: Upload Artifact to Gitea
depends_on:
- Build Archive
image: plugins/gitea-release
settings:
api_key:
from_secret: gitea_api_token
checksum: sha256
base_url: https://git.tobiasmanske.de
files: editor-preview.ankiaddon
image_pull_secrets:
- registry

4
.gitignore vendored
View File

@ -117,6 +117,7 @@ venv/
ENV/
env.bak/
venv.bak/
.idea
# Spyder project settings
.spyderproject
@ -166,3 +167,6 @@ tags
# End of https://www.toptal.com/developers/gitignore/api/vim,python
*.ankiaddon
.DS_Store
# Stores actual addon config if the src directory is symlinked into an anki installation during development
src/meta.json

View File

@ -6,6 +6,6 @@ if [ -d src/__pycache__ ]; then
fi
cd src
zip -r ../editor-preview.ankiaddon *
zip -r ../editor-preview.ankiaddon --exclude meta.json -- *
cd ..
unzip -l editor-preview.ankiaddon

View File

@ -1,4 +1,5 @@
import json
from typing import Set
from anki import hooks, buildinfo
from aqt import editor, gui_hooks, mw
@ -8,8 +9,10 @@ from aqt.webview import AnkiWebView
config = mw.addonManager.getConfig(__name__)
class EditorPreview(object):
js=[
editors: Set[editor.Editor] = set()
js = [
"js/mathjax.js",
"js/vendor/mathjax/tex-chtml.js",
"js/reviewer.js",
@ -18,7 +21,18 @@ class EditorPreview(object):
def __init__(self):
gui_hooks.editor_did_init.append(self.editor_init_hook)
gui_hooks.editor_did_init_buttons.append(self.editor_init_button_hook)
if int(buildinfo.version.split(".")[2]) < 45: # < 2.1.45
gui_hooks.editor_did_load_note.append(self.editor_note_hook)
gui_hooks.editor_did_fire_typing_timer.append(self.onedit_hook)
buildversion = buildinfo.version.split(".")
# Anki changed their versioning scheme in 2023 to year.month(.patch), causing things to explode here.
if int(buildversion[0]) >= 24 or (int(buildversion[0]) == 23 and int(buildversion[1]) == 12 and 2 < len(buildversion) and int(buildversion[2])) >= 1: # >= 23.12.1
self.js = [
"js/mathjax.js",
"js/vendor/mathjax/tex-chtml-full.js",
"js/reviewer.js",
]
elif int(buildversion[0]) < 23 and int(buildversion[2]) < 45: # < 2.1.45
self.js = [
"js/vendor/jquery.min.js",
"js/vendor/css_browser_selector.min.js",
@ -27,71 +41,112 @@ class EditorPreview(object):
"js/reviewer.js",
]
def editor_init_hook(self, ed: editor.Editor):
ed.webview = AnkiWebView(title="editor_preview")
ed.editor_preview = AnkiWebView(title="editor_preview")
# This is taken out of clayout.py
ed.webview.stdHtml(
ed.editor_preview.stdHtml(
ed.mw.reviewer.revHtml(),
css=["css/reviewer.css"],
js=self.js,
context=ed,
)
if not config['showPreviewAutomatically']:
ed.webview.hide()
if not config["showPreviewAutomatically"]:
ed.editor_preview.hide()
self._inject_splitter(ed)
gui_hooks.editor_did_fire_typing_timer.append(lambda o: self.onedit_hook(ed, o))
gui_hooks.editor_did_load_note.append(lambda o: None if o != ed else self.editor_note_hook(o))
def _get_splitter(self, editor):
mainR, editorR = [int(r) * 10000 for r in config["splitRatio"].split(":")]
location = config["location"]
split = QSplitter()
if location == "above":
split.setOrientation(Qt.Orientation.Vertical)
split.addWidget(editor.editor_preview)
split.addWidget(editor.wrapped_web)
sizes = [editorR, mainR]
elif location == "below":
split.setOrientation(Qt.Orientation.Vertical)
split.addWidget(editor.wrapped_web)
split.addWidget(editor.editor_preview)
sizes = [mainR, editorR]
elif location == "left":
split.setOrientation(Qt.Orientation.Horizontal)
split.addWidget(editor.editor_preview)
split.addWidget(editor.wrapped_web)
sizes = [editorR, mainR]
elif location == "right":
split.setOrientation(Qt.Orientation.Horizontal)
split.addWidget(editor.wrapped_web)
split.addWidget(editor.editor_preview)
sizes = [mainR, editorR]
else:
raise ValueError("Invalid value for config key location")
split.setSizes(sizes)
return split
def _inject_splitter(self, editor: editor.Editor):
layout = editor.outerLayout
split = QSplitter()
split.setOrientation(Qt.Vertical)
layout = editor.web.parentWidget().layout()
if layout is None:
layout = QVBoxLayout()
editor.web.parentWidget().setLayout(layout)
web_index = layout.indexOf(editor.web)
layout.removeWidget(editor.web)
split.addWidget(editor.web)
split.addWidget(editor.webview)
splitRatio = config['splitRatio']
upperR, lowerR = [int(r) for r in splitRatio.split(":")]
split.setStretchFactor(0, upperR)
split.setStretchFactor(1, lowerR)
# Wrap a widget on the outer layer of the webview
# So that other plugins can continue to modify the layout
editor.wrapped_web = QWidget()
wrapLayout = QHBoxLayout()
editor.wrapped_web.setLayout(wrapLayout)
wrapLayout.addWidget(editor.web)
split = self._get_splitter(editor)
layout.insertWidget(web_index, split)
def editor_note_hook(self, editor):
self.onedit_hook(editor, editor.note)
self.editors = set(filter(lambda it: it.note is not None, self.editors))
self.editors.add(editor)
# The initial loading of notes will also trigger an editing event
# which will cause a second refresh
# Caching the content of notes here will be used to determine if the content has changed
editor.cached_fields = list(editor.note.fields)
self.refresh(editor)
def editor_init_button_hook(self, buttons, editor):
addon_path = os.path.dirname(__file__)
icons_dir = os.path.join(addon_path, 'icons')
b = editor.addButton(icon=os.path.join(icons_dir, 'file.svg'), cmd="_editor_toggle_preview", tip='Toggle Live Preview',
func=lambda o=editor: self.onEditorPreviewButton(o), disables=False
)
icons_dir = os.path.join(addon_path, "icons")
b = editor.addButton(
icon=os.path.join(icons_dir, "file.svg"),
cmd="_editor_toggle_preview",
tip="Toggle Live Preview",
func=lambda o=editor: self.onEditorPreviewButton(o),
disables=False,
)
buttons.append(b)
def onEditorPreviewButton(self, origin: editor.Editor):
if origin.webview.isHidden():
origin.webview.show()
if origin.editor_preview.isHidden():
origin.editor_preview.show()
else:
origin.webview.hide()
origin.editor_preview.hide()
def _obtainCardText(self, note):
c = note.ephemeral_card()
a = mw.prepare_card_text_for_display(c.answer())
a = gui_hooks.card_will_show(a, c, "clayoutAnswer")
if theme_manager.night_mode:
bodyclass = theme_manager.body_classes_for_card_ord(c.ord, mw.pm.night_mode())
else:
bodyclass = theme_manager.body_classes_for_card_ord(c.ord)
bodyclass = theme_manager.body_classes_for_card_ord(c.ord, theme_manager.night_mode)
bodyclass += " editor-preview"
return f"_showAnswer({json.dumps(a)},'{bodyclass}');"
def onedit_hook(self, editor, origin):
if editor.note == origin:
editor.webview.eval(self._obtainCardText(editor.note))
def onedit_hook(self, note):
for editor in self.editors:
if editor.note == note and editor.cached_fields != note.fields:
editor.cached_fields = list(note.fields)
self.refresh(editor)
def refresh(self, editor):
editor.editor_preview.eval(self._obtainCardText(editor.note))
eprev = EditorPreview()

View File

@ -1 +1,5 @@
{"showPreviewAutomatically": true, "splitRatio": "4:1"}
{
"showPreviewAutomatically": true,
"splitRatio": "1:1",
"location": "below"
}

View File

@ -2,5 +2,8 @@
\- `showPreviewAutomatically` [boolean (true | false)]:<br/>
&nbsp;&nbsp;&nbsp;Defines if the preview window should show up automatically as you enter the Editor (default: true)<br/><br/>
\- `splitRatio` [int:int]:<br/>
&nbsp;&nbsp;&nbsp;Defines the default split ratio of the main view and preview view (default: 4:1)
&nbsp;&nbsp;&nbsp;Defines the default split ratio of the main view and preview view (default: 1:1)<br/>
<br/>
\- `location` [string (above | below | left | right)]:<br/>
&nbsp;&nbsp;&nbsp;Defines where to render the preview (default: below)
<br/>