Skip to content

Commit 58c5b11

Browse files
committed
some improvements to the note actions buttons
1 parent 620ad22 commit 58c5b11

5 files changed

Lines changed: 191 additions & 88 deletions

File tree

crates/wordbase-cli/src/query.rs

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,18 +33,15 @@ pub async fn render(engine: &Engine, profile: &Profile, text: &str) -> Result<()
3333
s_add_note: "Add Note".into(),
3434
s_view_note: "View note in Anki".into(),
3535
s_add_duplicate_note: "Add duplicate note".into(),
36-
fn_note_exists: "
37-
<js_callback>(window.wordbase.note_exists({
36+
fn_num_existing_notes: "
37+
/* <js_callback>(window.wordbase.note_exists({
3838
headword: <js_headword>,
3939
reading: <js_reading>,
40-
}))"
41-
.into(),
42-
fn_add_note: "
43-
window.wordbase.add_note({
44-
headword: <js_headword>,
45-
reading: <js_reading>,
46-
})"
40+
})) */ <js_callback>(4)
41+
"
4742
.into(),
43+
fn_add_new_note: "<js_callback>()".into(),
44+
fn_add_duplicate_note: "<js_callback>()".into(),
4845
fn_view_note: "
4946
window.wordbase.view_note({
5047
headword: <js_headword>,

crates/wordbase/src/anki.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,9 @@ impl Engine {
4343
};
4444
let glossaries = glossaries(&entries);
4545

46-
let key = term_part(term.headword());
4746
Ok(TermNote {
4847
fields: [
49-
("Expression", key.clone()),
48+
("Expression", term_part(term.headword())),
5049
("ExpressionReading", term_part(term.reading())),
5150
("ExpressionFurigana", term_ruby_plain(term)),
5251
(
@@ -75,7 +74,6 @@ impl Engine {
7574
.map(|audio| ("ExpressionAudio".to_string(), NoteField::Audio(audio))),
7675
)
7776
.collect::<HashMap<_, _>>(),
78-
key,
7977
})
8078
}
8179

@@ -113,7 +111,6 @@ impl Engine {
113111
#[derive(Debug, Serialize)]
114112
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
115113
pub struct TermNote {
116-
pub key: String,
117114
pub fields: HashMap<String, NoteField>,
118115
}
119116

crates/wordbase/src/records.html

Lines changed: 118 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -192,10 +192,33 @@
192192
font-weight: var(--caption-font-weight);
193193
}
194194

195+
.dot-badge {
196+
position: absolute;
197+
top: 0;
198+
left: 0;
199+
transform: translate(-50%, -50%);
200+
201+
height: 1.5em;
202+
padding: 0 0.3em;
203+
min-width: calc(1.5em - 0.3em * 2);
204+
205+
color: var(--fg-color);
206+
background: color-mix(in srgb, var(--bg-color), var(--fg-color) 10%);
207+
border-radius: 9999px;
208+
border: 3px solid var(--bg-color);
209+
210+
justify-content: center;
211+
align-items: center;
212+
white-space: nowrap;
213+
line-height: 1;
214+
user-select: none;
215+
}
216+
195217
.header {
196218
display: flex;
197219
flex-direction: row;
198220
align-items: center;
221+
199222
position: sticky;
200223
z-index: 1000;
201224
top: 0;
@@ -249,16 +272,37 @@
249272
}
250273
</style>
251274
{% endmacro style %}
252-
</html></title></head>
275+
253276
<!-- {# content #} -->
254277

255278
{{ self::style() }}
256279

257280
<script>
281+
function for_el_with_class(script, class_name, f) {
282+
function do_internal(element, class_name) {
283+
if (element.classList && element.classList.contains(class_name)) {
284+
f(element);
285+
}
286+
Array.from(element.children).forEach((child) =>
287+
do_internal(child, class_name),
288+
);
289+
}
290+
291+
Array.from(script.parentNode.children).forEach((el) =>
292+
do_internal(el, class_name),
293+
);
294+
}
295+
258296
function show_with_class(script, class_name) {
259-
Array.from(script.parentNode.children)
260-
.filter(el => el.classList && el.classList.contains(class_name))
261-
.forEach(el => el.style.display = "flex");
297+
for_el_with_class(script, class_name, (el) => (el.style.display = "flex"));
298+
}
299+
300+
function hide_with_class(script, class_name) {
301+
for_el_with_class(
302+
script,
303+
class_name,
304+
(el) => (el.style.display = "hidden"),
305+
);
262306
}
263307
</script>
264308

@@ -279,26 +323,32 @@
279323
xmlns="http://www.w3.org/2000/svg"
280324
viewBox="0 0 16 16"
281325
>
282-
<path d="m 5.019531 0 c -1.332031 0 -2.273437 0.816406 -2.644531 1.554688 c -0.371094 0.738281 -0.355469 1.445312 -0.355469 1.445312 l 0.03125 12.472656 l 5.996094 -2.402344 l 6.003906 2.402344 l -0.03125 -12.476562 c 0 -0.003906 0.011719 -0.707032 -0.355469 -1.441406 c -0.371093 -0.738282 -1.3125 -1.554688 -2.644531 -1.554688 z m 0 2 h 6 c 0.695313 0.011719 1.003907 0.367188 1 1 l 0.023438 9.519531 l -3.996094 -1.601562 l -4.003906 1.601562 l -0.023438 -9.523437 c -0.007812 -0.648438 0.398438 -0.996094 1 -0.996094 z m 0 0"
283-
fill="currentColor"/>
326+
<path
327+
d="m 5.019531 0 c -1.332031 0 -2.273437 0.816406 -2.644531 1.554688 c -0.371094 0.738281 -0.355469 1.445312 -0.355469 1.445312 l 0.03125 12.472656 l 5.996094 -2.402344 l 6.003906 2.402344 l -0.03125 -12.476562 c 0 -0.003906 0.011719 -0.707032 -0.355469 -1.441406 c -0.371093 -0.738282 -1.3125 -1.554688 -2.644531 -1.554688 z m 0 2 h 6 c 0.695313 0.011719 1.003907 0.367188 1 1 l 0.023438 9.519531 l -3.996094 -1.601562 l -4.003906 1.601562 l -0.023438 -9.523437 c -0.007812 -0.648438 0.398438 -0.996094 1 -0.996094 z m 0 0"
328+
fill="currentColor"
329+
/>
284330
</svg>
285331

286332
<svg
287333
id="bookmark-filled-symbolic"
288334
xmlns="http://www.w3.org/2000/svg"
289335
viewBox="0 0 16 16"
290336
>
291-
<path d="m 5.019531 0 c -1.332031 0 -2.273437 0.816406 -2.644531 1.554688 c -0.371094 0.738281 -0.355469 1.445312 -0.355469 1.445312 l 0.03125 12.472656 l 5.996094 -2.402344 l 6.003906 2.402344 l -0.03125 -12.476562 c 0 -0.003906 0.011719 -0.707032 -0.355469 -1.441406 c -0.371093 -0.738282 -1.3125 -1.554688 -2.644531 -1.554688 z m 0 0"
292-
fill="currentColor"/>
337+
<path
338+
d="m 5.019531 0 c -1.332031 0 -2.273437 0.816406 -2.644531 1.554688 c -0.371094 0.738281 -0.355469 1.445312 -0.355469 1.445312 l 0.03125 12.472656 l 5.996094 -2.402344 l 6.003906 2.402344 l -0.03125 -12.476562 c 0 -0.003906 0.011719 -0.707032 -0.355469 -1.441406 c -0.371093 -0.738282 -1.3125 -1.554688 -2.644531 -1.554688 z m 0 0"
339+
fill="currentColor"
340+
/>
293341
</svg>
294342

295343
<svg
296344
id="copy-symbolic"
297345
xmlns="http://www.w3.org/2000/svg"
298346
viewBox="0 0 16 16"
299347
>
300-
<path d="m 0 3 c 0 -1.644531 1.355469 -3 3 -3 h 5 c 1.644531 0 3 1.355469 3 3 c 0 0.550781 -0.449219 1 -1 1 s -1 -0.449219 -1 -1 c 0 -0.570312 -0.429688 -1 -1 -1 h -5 c -0.570312 0 -1 0.429688 -1 1 v 5 c 0 0.570312 0.429688 1 1 1 c 0.550781 0 1 0.449219 1 1 s -0.449219 1 -1 1 c -1.644531 0 -3 -1.355469 -3 -3 z m 5 5 c 0 -1.644531 1.355469 -3 3 -3 h 5 c 1.644531 0 3 1.355469 3 3 v 5 c 0 1.644531 -1.355469 3 -3 3 h -5 c -1.644531 0 -3 -1.355469 -3 -3 z m 2 0 v 5 c 0 0.570312 0.429688 1 1 1 h 5 c 0.570312 0 1 -0.429688 1 -1 v -5 c 0 -0.570312 -0.429688 -1 -1 -1 h -5 c -0.570312 0 -1 0.429688 -1 1 z m 0 0"
301-
fill="currentColor"/>
348+
<path
349+
d="m 0 3 c 0 -1.644531 1.355469 -3 3 -3 h 5 c 1.644531 0 3 1.355469 3 3 c 0 0.550781 -0.449219 1 -1 1 s -1 -0.449219 -1 -1 c 0 -0.570312 -0.429688 -1 -1 -1 h -5 c -0.570312 0 -1 0.429688 -1 1 v 5 c 0 0.570312 0.429688 1 1 1 c 0.550781 0 1 0.449219 1 1 s -0.449219 1 -1 1 c -1.644531 0 -3 -1.355469 -3 -3 z m 5 5 c 0 -1.644531 1.355469 -3 3 -3 h 5 c 1.644531 0 3 1.355469 3 3 v 5 c 0 1.644531 -1.355469 3 -3 3 h -5 c -1.644531 0 -3 -1.355469 -3 -3 z m 2 0 v 5 c 0 0.570312 0.429688 1 1 1 h 5 c 0.570312 0 1 -0.429688 1 -1 v -5 c 0 -0.570312 -0.429688 -1 -1 -1 h -5 c -0.570312 0 -1 0.429688 -1 1 z m 0 0"
350+
fill="currentColor"
351+
/>
302352
</svg>
303353
</div>
304354

@@ -308,17 +358,30 @@
308358
<div class="header">
309359
<div
310360
class="content"
311-
style="width: 100%; display: flex; flex-direction: row; gap: 16px; align-items: center"
361+
style="
362+
width: 100%;
363+
display: flex;
364+
flex-direction: row;
365+
gap: 16px;
366+
align-items: center;
367+
"
312368
>
313-
<div style="padding: 16px 0; font-size: 2em; flex: 1">
369+
<div style="padding: 8px 0; font-size: 2em; flex: 1">
314370
<ruby>
315371
{%- for part in group.furigana_parts -%}
316372
<span>{{- part[0] -}}</span><rt>{{- part[1] -}}</rt>
317373
{%- endfor -%}
318374
</ruby>
319375
</div>
320376

321-
<div style="display: flex; flex-direction: column; gap: 8px">
377+
<div
378+
style="
379+
padding: 1.5em 0;
380+
display: flex;
381+
flex-direction: column;
382+
gap: 8px;
383+
"
384+
>
322385
<!--
323386
{% if group.term.headword %}
324387
{% set js_headword = group.term.headword | addslashes %}
@@ -334,15 +397,24 @@
334397
{% set js_reading = "null" %}
335398
{% endif %}
336399
337-
{% set fn_note_exists = config.fn_note_exists | replace(from="<js_headword>", to=js_headword) | replace(from="<js_reading>", to=js_reading) %}
338-
{% set fn_add_note = config.fn_add_note | replace(from="<js_headword>", to=js_headword) | replace(from="<js_reading>", to=js_reading) %}
400+
{% set fn_num_existing_notes = config.fn_num_existing_notes | replace(from="<js_headword>", to=js_headword) | replace(from="<js_reading>", to=js_reading) %}
401+
{% set fn_add_new_note = config.fn_add_new_note | replace(from="<js_headword>", to=js_headword) | replace(from="<js_reading>", to=js_reading) %}
402+
{% set fn_add_duplicate_note = config.fn_add_duplicate_note | replace(from="<js_headword>", to=js_headword) | replace(from="<js_reading>", to=js_reading) %}
339403
{% set fn_view_note = config.fn_view_note | replace(from="<js_headword>", to=js_headword) | replace(from="<js_reading>", to=js_reading) %}
340404
-->
341405

342406
<div hidden class="add-note">
343407
<button
344408
class="suggested-action"
345-
onclick="{{ fn_add_note | safe }}"
409+
onclick="
410+
{
411+
const on_add_note = () => {
412+
413+
};
414+
}
415+
416+
{{ fn_add_new_note | safe }}
417+
"
346418
>
347419
<svg class="icon">
348420
<use href="#bookmark-outline-symbolic"></use>
@@ -353,6 +425,20 @@
353425

354426
<div hidden class="note-actions">
355427
<div style="display: flex; flex-direction: row; gap: 8px">
428+
<div style="flex: 1; position: relative">
429+
<button
430+
title="{{ config.s_add_duplicate_note }}"
431+
onclick="{{ fn_add_duplicate_note | safe }}"
432+
style="flex: 1; position: relative"
433+
>
434+
<svg class="icon">
435+
<use href="#copy-symbolic"></use>
436+
</svg>
437+
</button>
438+
439+
<div hidden class="multiple-notes dot-badge">?</div>
440+
</div>
441+
356442
<button
357443
title="{{ config.s_view_note }}"
358444
onclick="{{ fn_view_note | safe }}"
@@ -362,32 +448,33 @@
362448
<use href="#bookmark-filled-symbolic"></use>
363449
</svg>
364450
</button>
365-
366-
<button
367-
title="{{ config.s_add_duplicate_note }}"
368-
onclick="{{ fn_add_note | safe }}"
369-
style="flex: 1"
370-
>
371-
<svg class="icon">
372-
<use href="#copy-symbolic"></use>
373-
</svg>
374-
</button>
375451
</div>
376452
</div>
377453

378454
<script>
379455
{
380456
const script = document.currentScript;
381457

382-
const note_exists_callback = exists => {
383-
if (exists) {
384-
show_with_class(script, 'note-actions')
458+
const on_num_existing_notes = (num) => {
459+
hide_with_class(script, "add-note");
460+
hide_with_class(script, "note-actions");
461+
hide_with_class(script, "multiple-notes");
462+
463+
if (num > 0) {
464+
show_with_class(script, "note-actions");
465+
if (num > 1) {
466+
for_el_with_class(script, "multiple-notes", (el) => {
467+
el.style.display = "flex";
468+
el.innerHTML = `${num}`;
469+
});
470+
}
385471
} else {
386-
show_with_class(script, 'add-note')
472+
show_with_class(script, "add-note");
387473
}
388474
};
389475

390-
{{ fn_note_exists | replace(from="<js_callback>", to="note_exists_callback") | safe }};
476+
// prettier-ignore
477+
{{ fn_num_existing_notes | replace(from="<js_callback>", to="on_num_existing_notes") | safe }}
391478
}
392479
</script>
393480
</div>

crates/wordbase/src/render.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -68,30 +68,34 @@ pub struct RenderConfig {
6868
pub s_add_note: String,
6969
pub s_view_note: String,
7070
pub s_add_duplicate_note: String,
71-
/// Template for calling a JS function to check if an Anki note exists for a
72-
/// given term.
71+
/// Template for calling a JS function to get how many notes in Anki already
72+
/// exist for a given term.
7373
///
7474
/// # Examples
7575
///
7676
/// ```text
77-
/// Wordbase.note_exists(<js_headword>, <js_reading>)
77+
/// const num_notes = Wordbase.num_existing_notes(<js_headword>, <js_reading>);
78+
/// <js_callback>(num_notes);
7879
/// ```
79-
pub fn_note_exists: String,
80+
pub fn_num_existing_notes: String,
8081
/// Template for calling a JS function to add an Anki note for a given term.
8182
///
8283
/// # Examples
8384
///
8485
/// ```text
85-
/// Wordbase.add_note(<js_headword>, <js_reading>)
86+
/// Wordbase.add_note(<js_headword>, <js_reading>);
87+
/// <js_callback>();
8688
/// ```
8789
///
8890
/// ```text
8991
/// window.wordbase.callNative(
9092
/// 'add_note',
9193
/// { headword: <js_headword>, reading: <js_reading> },
94+
/// result => <js_callback>(JSON.parse(result)),
9295
/// )
9396
/// ```
94-
pub fn_add_note: String,
97+
pub fn_add_new_note: String,
98+
pub fn_add_duplicate_note: String,
9599
pub fn_view_note: String,
96100
}
97101

0 commit comments

Comments
 (0)