Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
13 changes: 12 additions & 1 deletion AnkiDroid/src/main/assets/scripts/js-api.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ class AnkiDroidJS {
Object.keys(jsApiList).forEach(method => {
if (method === "ankiAddTagToNote") {
AnkiDroidJS.prototype[method] = async function (noteId, tag) {
console.warn("ankiAddTagToNote is deprecated. Use ankiSetNoteTags instead.");
console.warn("ankiAddTagToNote is deprecated. Use ankiSetNoteTags instead");
const endpoint = jsApiList[method];
const data = JSON.stringify({ noteId, tag });
return await this.handleRequest(endpoint, data);
Expand All @@ -130,6 +130,17 @@ Object.keys(jsApiList).forEach(method => {
}
if (method === "ankiSetNoteTags") {
AnkiDroidJS.prototype[method] = async function (noteId, tags) {
let hasSpaces = false;
for (let i = 0; i < tags.length; i++) {
tags[i] = tags[i].trim();
if (tags[i].includes(" ") || tags[i].includes("\u3000")) {
tags[i] = tags[i].replace(" ", "_").replace("\u3000", "_");
hasSpaces = true;
}
}
if (hasSpaces) {
console.warn("Spaces in tags have been converted to underscores");
}
const endpoint = jsApiList[method];
const data = JSON.stringify({ noteId, tags });
return await this.handleRequest(endpoint, data);
Expand Down
8 changes: 7 additions & 1 deletion AnkiDroid/src/main/java/com/ichi2/anki/AnkiDroidJsAPI.kt
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ open class AnkiDroidJsAPI(
* @param returnDefaultValues `true` if default values should be returned (if non-[Reviewer])
* @return
*/
@NeedsTest("setNoteTags: Test that tags are set for all edge cases")
open suspend fun handleJsApiRequest(
methodName: String,
bytes: ByteArray,
Expand Down Expand Up @@ -383,7 +384,12 @@ open class AnkiDroidJsAPI(
val tags = jsonObject.getJSONArray("tags")
withCol {
fun Note.setTagsFromList(tagList: List<String>) {
val tagsAsString = this@withCol.tags.join(tagList)
val sanitizedTags = tagList.map { it.trim() }
val spaces = "\\s|\u3000".toRegex()
if (sanitizedTags.any { it.contains(spaces) }) {
throw IllegalArgumentException("Tags cannot contain spaces")
}
val tagsAsString = this@withCol.tags.join(sanitizedTags)
setTagsFromStr(this@withCol, tagsAsString)
}

Expand Down
2 changes: 1 addition & 1 deletion AnkiDroid/src/main/res/layout/activity_shared_decks.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
android:layout_height="wrap_content"
android:minHeight="?attr/actionBarSize"
android:theme="@style/ActionBarStyle"
android:background="?attr/colorPrimary"
android:background="?attr/appBarColor"
app:popupTheme="@style/ActionBar.Popup"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
Expand Down
51 changes: 33 additions & 18 deletions AnkiDroid/src/main/res/layout/gesture_display.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,33 +25,41 @@
<ImageView
android:id="@+id/top_left"
style="@style/binding_gesture_tap_button"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
app:layout_constraintHorizontal_bias="0.047"
app:layout_constraintVertical_bias="0.047"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<ImageView
android:id="@+id/top_center"
style="@style/binding_gesture_tap_button"
android:layout_marginTop="16dp"
app:layout_constraintVertical_bias="0.047"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<ImageView
android:id="@+id/top_right"
style="@style/binding_gesture_tap_button"
app:layout_constraintEnd_toEndOf="@+id/right"
app:layout_constraintTop_toTopOf="@+id/top_center" />
app:layout_constraintHorizontal_bias="0.953"
app:layout_constraintVertical_bias="0.047"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />


<ImageView
android:id="@+id/left"
style="@style/binding_gesture_tap_button"
android:layout_marginStart="16dp"
app:layout_constraintBottom_toTopOf="parent"
app:layout_constraintHorizontal_bias="0.047"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="parent" />
app:layout_constraintTop_toTopOf="parent" />

<ImageView
android:id="@+id/center"
Expand All @@ -64,33 +72,40 @@
<ImageView
android:id="@+id/right"
style="@style/binding_gesture_tap_button"
android:layout_marginEnd="16dp"
app:layout_constraintBottom_toTopOf="parent"
app:layout_constraintHorizontal_bias="0.953"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="parent" />
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<ImageView
android:id="@+id/bottom_left"
android:layout_marginBottom="16dp"
style="@style/binding_gesture_tap_button"
app:layout_constraintHorizontal_bias="0.047"
app:layout_constraintVertical_bias="0.953"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="@+id/left" />
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<ImageView
android:id="@+id/bottom_center"
style="@style/binding_gesture_tap_button"
android:layout_marginBottom="16dp"
app:layout_constraintVertical_bias="0.953"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<ImageView
android:id="@+id/bottom_right"
style="@style/binding_gesture_tap_button"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
app:layout_constraintHorizontal_bias="0.953"
app:layout_constraintVertical_bias="0.953"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<ImageView
android:id="@+id/swipe_select"
Expand Down
42 changes: 42 additions & 0 deletions AnkiDroid/src/test/java/com/ichi2/anki/AnkiDroidJsAPITest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import com.ichi2.utils.BASIC_MODEL_NAME
import net.ankiweb.rsdroid.withoutUnicodeIsolation
import org.hamcrest.CoreMatchers.equalTo
import org.hamcrest.MatcherAssert.assertThat
import org.json.JSONArray
import org.json.JSONObject
import org.junit.Ignore
import org.junit.Test
Expand Down Expand Up @@ -418,6 +419,31 @@ class AnkiDroidJsAPITest : RobolectricTest() {
assertEquals(CardType.New, cardAfterReset.type, "Card type after reset")
}

@Test
fun ankiGetNoteTagsTest() =
runTest {
val n =
addBasicNote("Front", "Back").update {
tags = mutableListOf("tag1", "tag2", "tag3")
}

val reviewer: Reviewer = startReviewer()
waitForAsyncTasksToComplete()

val jsapi = reviewer.jsApi

// test get tags for note
val expectedTags = n.tags
val response = getDataFromRequest("getNoteTags", jsapi, jsonObjectOf("noteId" to n.id))
val jsonResponse = JSONObject(response)
val actualTags = JSONArray(jsonResponse.getString("value"))

assertEquals(expectedTags.size, actualTags.length())
for (i in 0 until actualTags.length()) {
assertEquals(expectedTags[i], actualTags.getString(i))
}
}

companion object {
fun jsApiContract(data: String = ""): ByteArray =
JSONObject()
Expand Down Expand Up @@ -451,5 +477,21 @@ class AnkiDroidJsAPITest : RobolectricTest() {
jsAPI
.handleJsApiRequest(methodName, jsApiContract(apiData), false)
.decodeToString()

suspend fun getDataFromRequest(
methodName: String,
jsAPI: AnkiDroidJsAPI,
apiData: JSONObject,
): String =
jsAPI
.handleJsApiRequest(methodName, jsApiContract(apiData.toString()), false)
.decodeToString()
}
}

private fun jsonObjectOf(vararg pairs: Pair<String, Any>): JSONObject =
JSONObject().apply {
for ((key, value) in pairs) {
put(key, value)
}
}
7 changes: 7 additions & 0 deletions AnkiDroid/src/test/java/com/ichi2/testutils/TestClass.kt
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,13 @@ interface TestClass {
col.decks.save(deckConfig)
}

/** Helper method to update a note */
fun Note.update(block: Note.() -> Unit): Note {
block(this)
col.updateNote(this)
return this
}

/** Helper method to all cards of a note */
fun Note.updateCards(update: Card.() -> Unit): Note {
cards().forEach { it.update(update) }
Expand Down
Loading