17
17
# You should have received a copy of the GNU General Public License along with
18
18
# Chinese Support Redux. If not, see <https://www.gnu.org/licenses/>.
19
19
20
+ import re
21
+ import json
22
+
23
+ import anki .buildinfo
20
24
from anki .hooks import addHook
25
+ from aqt .utils import showWarning
21
26
from aqt import mw
27
+ from aqt .editor import Editor
22
28
23
29
from .behavior import update_fields
24
30
from .main import config
@@ -80,12 +86,95 @@ def onFocusLost(self, _, note, index):
80
86
return False
81
87
82
88
83
- def append_tone_styling (editor ):
84
- js = 'var css = document.styleSheets[0];'
89
+ TONE_CSS_RULE = re .compile ("(\\ .tone\\ d) *\\ {([^}]*)\\ }" )
90
+
91
+ # append_tone_styling(editor: Editor)
92
+ #
93
+ # Extracts the CSS rules for tones (i.e. matching TONE_CSS_RULE) from the
94
+ # user defined CSS style sheet. For the sake of simplicity, a tone CSS rule
95
+ # must be a one liner.
96
+ #
97
+ # IMPLEMENTATION NOTES:
98
+ #
99
+ # The code makes heavily use of internal APIs in Anki that may change in
100
+ # future releases. Hopefully, these notes are useful to adapt the code to
101
+ # new releases in case of breaking.
102
+ #
103
+ # The solution is based on Anki 2.1.54.
104
+ #
105
+ # The Javascript code being evaluated in the QWebView executes the following steps:
106
+ # 1. Wait until the UI has been loaded. The code for that is based on [1].
107
+ # 2. Loop through all RichTextInput Svelte component instances. They are
108
+ # reachable via "require" because they have been registered before here [2].
109
+ # Unfortunately, this method is only available since 2.1.54.
110
+ # 3. Using the RichTextInputAPI [3], we can query the associated CustomStyles
111
+ # instance. A CustomStyles instance has a `styleMap` [4] that contains an
112
+ # "userBase" entry, which wraps a <style> HTML element. This style element's
113
+ # intended function is to apply color, font family, font size, etc. [5,6].
114
+ # It is the perfect place to add our own CSS tone rules.
115
+ #
116
+ # [1] https://github.com/ankitects/anki/blob/2.1.54/qt/aqt/editor.py#L184
117
+ # [2] https://github.com/ankitects/anki/blob/2.1.54/ts/editor/rich-text-input/RichTextInput.svelte#L40
118
+ # [3] https://github.com/ankitects/anki/blob/2.1.54/ts/editor/rich-text-input/RichTextInput.svelte#L21
119
+ # [4] https://github.com/ankitects/anki/blob/2.1.54/ts/editor/rich-text-input/CustomStyles.svelte#L37
120
+ # [5] https://github.com/ankitects/anki/blob/2.1.54/ts/editor/rich-text-input/RichTextStyles.svelte#L17
121
+ # [6] https://github.com/ankitects/anki/blob/2.1.54/ts/editor/rich-text-input/RichTextStyles.svelte#L33
122
+
123
+ def append_tone_styling_anki2_1_54 (editor : Editor ):
124
+ rules = []
125
+ for line in editor .note .note_type ()['css' ].split ('\n ' ):
126
+ if '.tone' in line :
127
+ m = TONE_CSS_RULE .search (line )
128
+ if m :
129
+ rules .append (line )
130
+ else :
131
+ showWarning ("WARN: could not parse CSS tone rule. "
132
+ "Currently, tone CSS rules need to be one liners." )
133
+
134
+ js = f"var CSSRULES = { json .dumps (rules )} ;"
135
+ js += """
136
+ require("anki/ui").loaded.then(() =>
137
+ require("anki/RichTextInput").instances.forEach(inst =>
138
+ inst.customStyles.then(styles => {
139
+ var sheet = styles.styleMap.get("userBase").element.sheet;
140
+ CSSRULES.forEach(rule =>
141
+ sheet.insertRule(rule)
142
+ );
143
+ })
144
+ )
145
+ );
146
+ """
147
+ editor .web .eval (js )
85
148
86
- for line in editor .note .model ()['css' ].split ('\n ' ):
149
+ def append_tone_styling_anki2_1_49 (editor ):
150
+ rules = []
151
+ for line in editor .note .note_type ()['css' ].split ('\n ' ):
87
152
if line .startswith ('.tone' ):
88
- js += 'css.insertRule("{}", css.cssRules.length);' .format (
89
- line .rstrip ())
153
+ m = TONE_CSS_RULE .search (line )
154
+ if m :
155
+ rules .append ((m .group (1 ), m .group (2 )))
156
+ else :
157
+ showWarning ("WARN: could not parse CSS tone rule. "
158
+ "Currently, tone CSS rules need to be one liners." )
159
+
160
+ inner_js = ""
161
+ for rulename , ruledef in rules :
162
+ for part in ruledef .split (';' ):
163
+ if ':' in part :
164
+ [property , value ] = part .split (':' , 1 )
165
+ inner_js += f"jQuery('{ rulename .strip ()} ', this.shadowRoot).css('{ property .strip ()} ', '{ value .strip ()} ');\n "
166
+ js = "jQuery('div.field').each(function () {\n %s})" % inner_js
90
167
91
168
editor .web .eval (js )
169
+
170
+
171
+ __version = [int (x ) for x in anki .buildinfo .version .split ('.' )]
172
+ if __version < [2 ,1 ,50 ]:
173
+ append_tone_styling = append_tone_styling_anki2_1_49
174
+ elif __version >= [2 ,1 ,54 ]:
175
+ append_tone_styling = append_tone_styling_anki2_1_54
176
+ else :
177
+ showWarning ("Chinese tone styling has not been implemented for your current Anki version. "
178
+ "Supported versions are Anki 2.1.49 as well as 2.1.54 and later." )
179
+ def append_tone_styling (editor ):
180
+ pass
0 commit comments