Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show Anki card flags #1571

Merged
merged 20 commits into from
Nov 6, 2024
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ext/css/material.css
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,7 @@ body {
.icon[data-icon=clipboard] { --icon-image: url(/images/clipboard.svg); }
.icon[data-icon=key] { --icon-image: url(/images/key.svg); }
.icon[data-icon=tag] { --icon-image: url(/images/tag.svg); }
.icon[data-icon=flag] { --icon-image: url(/images/flag.svg); }
.icon[data-icon=accessibility] { --icon-image: url(/images/accessibility.svg); }
.icon[data-icon=connection] { --icon-image: url(/images/connection.svg); }
.icon[data-icon=external-link] { --icon-image: url(/images/external-link.svg); }
Expand Down
1 change: 1 addition & 0 deletions ext/images/flag.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
23 changes: 22 additions & 1 deletion ext/js/background/backend.js
Original file line number Diff line number Diff line change
Expand Up @@ -613,7 +613,7 @@ export class Backend {
}

const noteIds = isDuplicate ? duplicateNoteIds[originalIndices.indexOf(i)] : null;
const noteInfos = (fetchAdditionalInfo && noteIds !== null && noteIds.length > 0) ? await this._anki.notesInfo(noteIds) : [];
const noteInfos = (fetchAdditionalInfo && noteIds !== null && noteIds.length > 0) ? await this._notesCardsInfo(noteIds) : [];

const info = {
canAdd: valid,
Expand All @@ -628,6 +628,27 @@ export class Backend {
return results;
}

/**
* @param {number[]} noteIds
* @returns {Promise<(?import('anki').NoteInfo)[]>}
*/
async _notesCardsInfo(noteIds) {
const notesInfo = await this._anki.notesInfo(noteIds);
/** @type {number[]} */
// @ts-expect-error - ts is not smart enough to realize that filtering !!x removes null and undefined
const cardIds = notesInfo.flatMap((x) => x?.cards).filter((x) => !!x);
const cardsInfo = await this._anki.cardsInfo(cardIds);
for (let i = 0; i < notesInfo.length; i++) {
if (notesInfo[i] !== null) {
const cardInfo = cardsInfo.find((x) => x?.noteId === notesInfo[i]?.noteId);
if (cardInfo) {
notesInfo[i]?.cardsInfo.push(cardInfo);
}
}
}
return notesInfo;
}

/** @type {import('api').ApiHandler<'injectAnkiNoteMedia'>} */
async _onApiInjectAnkiNoteMedia({timestamp, definitionDetails, audioDetails, screenshotDetails, clipboardDetails, dictionaryMediaDetails}) {
return await this._injectAnkNoteMedia(
Expand Down
50 changes: 50 additions & 0 deletions ext/js/comm/anki-connect.js
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,17 @@ export class AnkiConnect {
return this._normalizeNoteInfoArray(result);
}

/**
* @param {import('anki').CardId[]} cardIds
* @returns {Promise<(?import('anki').CardInfo)[]>}
*/
async cardsInfo(cardIds) {
if (!this._enabled) { return []; }
await this._checkVersion();
const result = await this._invoke('cardsInfo', {cards: cardIds});
return this._normalizeCardInfoArray(result);
}

/**
* @returns {Promise<string[]>}
*/
Expand Down Expand Up @@ -655,6 +666,45 @@ export class AnkiConnect {
fields: fields2,
modelName,
cards: cards2,
cardsInfo: [],
};
result2.push(item2);
}
return result2;
}

/**
Kuuuube marked this conversation as resolved.
Show resolved Hide resolved
* @param {unknown} result
* @returns {(?import('anki').CardInfo)[]}
* @throws {Error}
*/
_normalizeCardInfoArray(result) {
if (!Array.isArray(result)) {
throw this._createUnexpectedResultError('array', result, '');
}
/** @type {(?import('anki').CardInfo)[]} */
const result2 = [];
for (let i = 0, ii = result.length; i < ii; ++i) {
const item = /** @type {unknown} */ (result[i]);
if (item === null || typeof item !== 'object') {
throw this._createError(`Unexpected result type at index ${i}: expected Cards.CardInfo, received ${this._getTypeName(item)}`, result);
}
const {cardId} = /** @type {{[key: string]: unknown}} */ (item);
if (typeof cardId !== 'number') {
result2.push(null);
continue;
}
const {note, flags} = /** @type {{[key: string]: unknown}} */ (item);
if (typeof note !== 'number') {
result2.push(null);
continue;
}

/** @type {import('anki').CardInfo} */
const item2 = {
noteId: note,
cardId,
flags: typeof flags === 'number' ? flags : 0,
};
result2.push(item2);
}
Expand Down
125 changes: 125 additions & 0 deletions ext/js/display/display-anki.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ export class DisplayAnki {
this._errorNotificationEventListeners = null;
/** @type {?import('./display-notification.js').DisplayNotification} */
this._tagsNotification = null;
/** @type {?import('./display-notification.js').DisplayNotification} */
this._flagsNotification = null;
/** @type {?Promise<void>} */
this._updateSaveButtonsPromise = null;
/** @type {?import('core').TokenObject} */
Expand Down Expand Up @@ -103,6 +105,8 @@ export class DisplayAnki {
/** @type {(event: MouseEvent) => void} */
this._onShowTagsBind = this._onShowTags.bind(this);
/** @type {(event: MouseEvent) => void} */
this._onShowFlagsBind = this._onShowFlags.bind(this);
/** @type {(event: MouseEvent) => void} */
this._onNoteSaveBind = this._onNoteSave.bind(this);
/** @type {(event: MouseEvent) => void} */
this._onViewNotesButtonClickBind = this._onViewNotesButtonClick.bind(this);
Expand Down Expand Up @@ -260,6 +264,9 @@ export class DisplayAnki {
for (const node of element.querySelectorAll('.action-button[data-action=view-tags]')) {
eventListeners.addEventListener(node, 'click', this._onShowTagsBind);
}
for (const node of element.querySelectorAll('.action-button[data-action=view-flags]')) {
eventListeners.addEventListener(node, 'click', this._onShowFlagsBind);
}
for (const node of element.querySelectorAll('.action-button[data-action=save-note]')) {
eventListeners.addEventListener(node, 'click', this._onNoteSaveBind);
}
Expand Down Expand Up @@ -304,6 +311,16 @@ export class DisplayAnki {
this._showTagsNotification(tags);
}

/**
* @param {MouseEvent} e
*/
_onShowFlags(e) {
e.preventDefault();
const element = /** @type {HTMLElement} */ (e.currentTarget);
const flags = element.title;
this._showFlagsNotification(flags);
}

/**
* @param {number} index
* @param {import('display-anki').CreateMode} mode
Expand All @@ -323,6 +340,15 @@ export class DisplayAnki {
return entry !== null ? entry.querySelector('.action-button[data-action=view-tags]') : null;
}

/**
* @param {number} index
* @returns {?HTMLButtonElement}
*/
_flagsIndicatorFind(index) {
const entry = this._getEntry(index);
return entry !== null ? entry.querySelector('.action-button[data-action=view-flags]') : null;
}

/**
* @param {number} index
* @returns {?HTMLElement}
Expand Down Expand Up @@ -459,6 +485,7 @@ export class DisplayAnki {

if (displayTags !== 'never' && Array.isArray(noteInfos)) {
this._setupTagsIndicator(i, noteInfos);
this._setupFlagsIndicator(i, noteInfos);
}
}

Expand Down Expand Up @@ -508,6 +535,104 @@ export class DisplayAnki {
this._tagsNotification.open();
}

/**
* @param {number} i
* @param {(?import('anki').NoteInfo)[]} noteInfos
*/
_setupFlagsIndicator(i, noteInfos) {
const flagsIndicator = this._flagsIndicatorFind(i);
if (flagsIndicator === null) {
return;
}

/** @type {Set<string>} */
const displayFlags = new Set();
for (const item of noteInfos) {
if (item === null) { continue; }
for (const cardInfo of item.cardsInfo) {
if (cardInfo.flags !== 0) {
displayFlags.add(this._getFlagName(cardInfo.flags));
}
}
}

if (displayFlags.size > 0) {
flagsIndicator.disabled = false;
flagsIndicator.hidden = false;
flagsIndicator.title = `Card flags: ${[...displayFlags].join(', ')}`;
/** @type {HTMLElement | null} */
const flagsIndicatorIcon = flagsIndicator.querySelector('.action-icon');
if (flagsIndicatorIcon !== null && flagsIndicator instanceof HTMLElement) {
flagsIndicatorIcon.style.background = this._getFlagColor(displayFlags);
}
}
}

/**
* @param {number} flag
* @returns {string}
*/
_getFlagName(flag) {
/** @type {Record<number, string>} */
const flagNamesDict = {
1: 'Red',
2: 'Orange',
3: 'Green',
4: 'Blue',
5: 'Pink',
6: 'Turquoise',
7: 'Purple',
};
if (flag in flagNamesDict) {
return flagNamesDict[flag];
}
return '';
}

/**
* @param {Set<string>} flags
* @returns {string}
*/
_getFlagColor(flags) {
/** @type {Record<string, import('display-anki').RGB>} */
const flagColorsDict = {
Red: {red: 248, green: 113, blue: 113},
Orange: {red: 253, green: 186, blue: 116},
Green: {red: 134, green: 239, blue: 172},
Blue: {red: 96, green: 165, blue: 250},
Pink: {red: 240, green: 171, blue: 252},
Turquoise: {red: 94, green: 234, blue: 212},
Purple: {red: 192, green: 132, blue: 252},
};

const gradientSliceSize = 100 / flags.size;
let currentGradientPercent = 0;

const gradientSlices = [];
for (const flag of flags) {
const flagColor = flagColorsDict[flag];
gradientSlices.push(
'rgb(' + flagColor.red + ',' + flagColor.green + ',' + flagColor.blue + ') ' + currentGradientPercent + '%',
'rgb(' + flagColor.red + ',' + flagColor.green + ',' + flagColor.blue + ') ' + (currentGradientPercent + gradientSliceSize) + '%',
);
currentGradientPercent += gradientSliceSize;
}

return 'linear-gradient(to right,' + gradientSlices.join(',') + ')';
}

/**
* @param {string} message
*/
_showFlagsNotification(message) {
if (this._flagsNotification === null) {
this._flagsNotification = this._display.createNotification(true);
}

this._flagsNotification.setContent(message);
this._flagsNotification.open();
}

/**
* @param {import('display-anki').CreateMode} mode
*/
Expand Down
8 changes: 4 additions & 4 deletions ext/settings.html
Original file line number Diff line number Diff line change
Expand Up @@ -1960,7 +1960,7 @@ <h1>Yomitan Settings</h1>
<div class="settings-item-inner">
<div class="settings-item-left">
<div class="settings-item-label">
Show card tags
Show card tags and flags
jamesmaa marked this conversation as resolved.
Show resolved Hide resolved
<a tabindex="0" class="more-toggle more-only" data-parent-distance="4">(?)</a>
</div>
</div>
Expand All @@ -1974,10 +1974,10 @@ <h1>Yomitan Settings</h1>
</div>
<div class="settings-item-children more" hidden>
<p>
When coming across a word that is already in an Anki deck, a button will appear that shows
the tags the card has. If set to <em>Non-standard</em>, all tags that are included in the
When coming across a word that is already in an Anki deck, two buttons can appear that show
the tags and flags the card has. If set to <em>Non-standard</em>, all tags that are included in the
<em>Card tags</em> option will be filtered out from the list. If no tags remain after filtering,
then the button will not be shown.
then the tags button will not be shown. If no flags are found, the flags button will not be shown.
</p>
<p>
<a tabindex="0" class="more-toggle" data-parent-distance="3">Less&hellip;</a>
Expand Down
3 changes: 3 additions & 0 deletions ext/templates-display.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
<div class="entry-current-indicator" title="Current entry"><span class="entry-current-indicator-inner"></span></div>
<div class="entry-header">
<div class="actions">
<button type="button" class="action-button action-button-collapsible" data-action="view-flags" hidden disabled>
<span class="action-icon icon" data-icon="flag"></span>
</button>
<button type="button" class="action-button action-button-collapsible" data-action="view-tags" hidden disabled>
<span class="action-icon icon" data-icon="tag"></span>
</button>
Expand Down
7 changes: 7 additions & 0 deletions types/ext/anki.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,20 @@ export type NoteInfo = {
fields: {[key: string]: NoteFieldInfo};
modelName: string;
cards: CardId[];
cardsInfo: CardInfo[];
};

export type NoteFieldInfo = {
value: string;
order: number;
};

export type CardInfo = {
noteId: NoteId;
cardId: CardId;
flags: number;
};

export type ApiReflectResult = {
scopes: string[];
actions: string[];
Expand Down
6 changes: 6 additions & 0 deletions types/ext/display-anki.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,9 @@ export type CreateNoteResult = {
errors: Error[];
requirements: AnkiNoteBuilder.Requirement[];
};

export type RGB = {
red: number;
green: number;
blue: number;
};