Skip to content

Commit

Permalink
fix(rtl): support better rtl in view mode (julianpoy#1236)
Browse files Browse the repository at this point in the history
  • Loading branch information
levyitay committed May 26, 2024
1 parent b7702fe commit 9a60788
Show file tree
Hide file tree
Showing 6 changed files with 110 additions and 12 deletions.
8 changes: 7 additions & 1 deletion packages/frontend/src/app/pages/home/home.page.html
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,14 @@
*ngIf="!preferences[preferenceKeys.ShowImages]"
class="no-image-padding"
></div>
<h2 [title]="recipe.title">{{ recipe.title }}</h2>
<h2
[style.text-align]="isRtlText(recipe.title) ? 'right' : 'left'"
[title]="recipe.title"
>
{{ recipe.title }}
</h2>
<p
[style.text-align]="isRtlText(recipe.description) ? 'right' : 'left'"
*ngIf="recipe.description?.length && preferences[preferenceKeys.ShowRecipeDescription]"
[title]="recipe.description"
>
Expand Down
5 changes: 5 additions & 0 deletions packages/frontend/src/app/pages/home/home.page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { PreferencesService } from "~/services/preferences.service";
import {
MyRecipesPreferenceKey,
GlobalPreferenceKey,
isRtlText,
} from "@recipesage/util/shared";
import { HomePopoverPage } from "~/pages/home-popover/home-popover.page";
import { HomeSearchFilterPopoverPage } from "~/pages/home-search-popover/home-search-filter-popover.page";
Expand Down Expand Up @@ -498,6 +499,10 @@ export class HomePage {
this.selectedRecipeIds = [];
}

isRtlText(text: string, firstWordOnly = false): boolean {
return isRtlText(text, firstWordOnly);
}

async addLabelToSelectedRecipes() {
const header = await this.translate
.get("pages.home.addLabel.header")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
<img src="{{ sortedRecipeImages()[0].image.location }}" />
</ion-thumbnail>

<ion-label>
<ion-label [dir]="isTitleRtl ? 'rtl' : 'ltr'">
<h2 class="recipeTitle ion-text-wrap">{{ recipe.title }}</h2>

<ion-grid
Expand Down Expand Up @@ -91,7 +91,12 @@ <h2 class="recipeTitle ion-text-wrap">{{ recipe.title }}</h2>
</ion-col>
</ion-row>
</ion-grid>
<p class="ion-text-wrap">{{ recipe.description }}</p>
<p
[style.text-align]="isDescriptionRtl ? 'right' : 'left'"
class="ion-text-wrap"
>
{{ recipe.description }}
</p>
</ion-label>
</ion-item>

Expand Down Expand Up @@ -170,10 +175,11 @@ <h2 class="recipeTitle ion-text-wrap">{{ recipe.title }}</h2>
</ion-label>
</ion-item>
<ion-item class="ingredients ion-text-wrap" lines="none">
<ion-label>
<ion-label [dir]="isIngredientsRtl ? 'rtl' : 'ltr'">
<p
*ngFor="let ingredient of ingredients; index as ingredientIdx"
[innerHTML]="ingredient.content"
[style.text-align]="ingredient.isRtl ? 'right' : 'left'"
class="ingredient ion-text-wrap"
[ngClass]="{ ingredientContent: !ingredient.isHeader, complete: getIngredientComplete(ingredientIdx) }"
(click)="ingredientClicked($event, ingredient, ingredientIdx)"
Expand All @@ -199,24 +205,28 @@ <h2 class="recipeTitle ion-text-wrap">{{ recipe.title }}</h2>
</ion-label>
</ion-item>
<ion-item class="instructions ion-text-wrap" lines="none">
<ion-label>
<ion-label [dir]="isInstructionRtl ? 'rtl' : 'ltr'">
<p
*ngFor="let instruction of instructions; index as instructionIdx"
class="instruction ion-text-wrap"
[style.text-align]="instruction.isRtl ? 'right' : 'left'"
>
<span *ngIf="instruction.isHeader"
><b class="sectionHeader">{{instruction.content}}</b></span
>
<span *ngIf="instruction.isHeader">
<b class="sectionHeader">{{instruction.content}}</b>
</span>
<span
*ngIf="!instruction.isHeader"
(click)="instructionClicked($event, instruction, instructionIdx)"
[ngClass]="{ complete: getInstructionComplete(instructionIdx) }"
[style.text-align]="instruction.isRtl ? 'right' : 'left'"
>
<b>{{instruction.count}} &nbsp;</b>
<span
class="instructionContent"
[innerHTML]="instruction.content"
[style.text-align]="instruction.isRtl ? 'right' : 'left'"
></span>
<!-- <b *ngIf="instruction.isRtl">&nbsp; {{instruction.count}}</b> -->
</span>
</p>
<p *ngIf="!instructions || instructions.length === 0">
Expand All @@ -235,9 +245,10 @@ <h2 class="recipeTitle ion-text-wrap">{{ recipe.title }}</h2>
</ion-label>
</ion-item>
<ion-item lines="none" class="ion-text-wrap">
<ion-label>
<ion-label [dir]="isNotesRtl ? 'rtl' : 'ltr'">
<p
*ngFor="let note of notes; index as noteIdx"
[style.text-align]="note.isRtl ? 'right' : 'left'"
[innerHTML]="note.content"
[ngClass]="{ header: note.isHeader }"
class="ion-text-wrap"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import { UtilService, RouteMap } from "~/services/util.service";
import { CapabilitiesService } from "~/services/capabilities.service";
import { WakeLockService } from "~/services/wakelock.service";
import { PreferencesService } from "~/services/preferences.service";
import { RecipeDetailsPreferenceKey } from "@recipesage/util/shared";
import { RecipeDetailsPreferenceKey, isRtlText } from "@recipesage/util/shared";
import { RecipeCompletionTrackerService } from "~/services/recipe-completion-tracker.service";

import { AddRecipeToShoppingListModalPage } from "../add-recipe-to-shopping-list-modal/add-recipe-to-shopping-list-modal.page";
Expand Down Expand Up @@ -63,7 +63,11 @@ export class RecipePage {
ingredients?: ParsedIngredient[];
instructions?: ParsedInstruction[];
notes?: ParsedNote[];

isInstructionRtl?: boolean;
isIngredientsRtl?: boolean;
isTitleRtl?: boolean;
isDescriptionRtl?: boolean;
isNotesRtl?: boolean;
scale = 1;

labelGroupIds: string[] = [];
Expand Down Expand Up @@ -156,7 +160,10 @@ export class RecipePage {
if (!response) return;

this.recipe = response;

this.isTitleRtl =
this.recipe.title != "" && isRtlText(this.recipe.title, true);
this.isDescriptionRtl =
this.recipe.description != "" && isRtlText(this.recipe.description, true);
if (this.recipe.url && !this.recipe.url.trim().startsWith("http")) {
this.recipe.url = "http://" + this.recipe.url.trim();
}
Expand All @@ -165,6 +172,13 @@ export class RecipePage {
this.instructions = this.recipeService.parseInstructions(
this.recipe.instructions,
);
let rtlCount = 0;
this.instructions.forEach((instruction) => {
if (instruction.isRtl) {
rtlCount++;
}
});
this.isInstructionRtl = rtlCount > this.instructions.length / 2;
}

if (this.recipe.notes && this.recipe.notes.length > 0) {
Expand All @@ -174,6 +188,13 @@ export class RecipePage {
...note,
content: linkifyStr(note.content),
}));
let count = 0;
this.notes.forEach((note) => {
if (note.isRtl) {
count++;
}
});
this.isNotesRtl = count > this.notes.length / 2;
}

const groupIdsSet = new Set<string>();
Expand Down Expand Up @@ -343,6 +364,13 @@ export class RecipePage {
this.scale,
true,
);
let rtlCount = 0;
this.ingredients.forEach((ingredient) => {
if (ingredient.isRtl) {
rtlCount++;
}
});
this.isIngredientsRtl = rtlCount > this.ingredients.length / 2;
}

editRecipe() {
Expand Down
3 changes: 3 additions & 0 deletions packages/frontend/src/app/services/recipe.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,18 +49,21 @@ export interface ParsedIngredient {
originalContent: string;
isHeader: boolean;
complete: boolean;
isRtl: boolean;
}

export interface ParsedInstruction {
content: string;
isHeader: boolean;
complete: boolean;
count: number;
isRtl: boolean;
}

export interface ParsedNote {
content: string;
isHeader: boolean;
isRtl: boolean;
}

export enum ExportFormat {
Expand Down
45 changes: 45 additions & 0 deletions packages/util/shared/src/parsers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ export const parseIngredients = (
originalContent: string;
complete: boolean;
isHeader: boolean;
isRtl: boolean;
}[] => {
if (!ingredients) return [];

Expand All @@ -147,6 +148,7 @@ export const parseIngredients = (
originalContent: match,
complete: false,
isHeader: false,
isRtl: isRtlText(match),
})) || [];

for (let i = 0; i < lines.length; i++) {
Expand Down Expand Up @@ -226,8 +228,10 @@ export const parseIngredients = (
acc + ingredientPart + (ingredientPartDelimiters[idx] || ""),
"",
);
lines[i].isRtl = isRtlText(lines[i].originalContent);
} else {
lines[i].content = updatedIngredientParts.join(" + ");
lines[i].isRtl = isRtlText(lines[i].originalContent);
}

lines[i].isHeader = false;
Expand All @@ -244,6 +248,7 @@ export const parseInstructions = (
isHeader: boolean;
count: number;
complete: boolean;
isRtl: boolean;
}[] => {
instructions = replaceFractionsInText(instructions);

Expand All @@ -269,13 +274,15 @@ export const parseInstructions = (
isHeader: true,
count: 0,
complete: false,
isRtl: isRtlText(headerContent, true),
};
} else {
return {
content: line,
isHeader: false,
count: stepCount++,
complete: false,
isRtl: isRtlText(line, true),
};
}
});
Expand All @@ -286,6 +293,7 @@ export const parseNotes = (
): {
content: string;
isHeader: boolean;
isRtl: boolean;
}[] => {
// Starts with [, anything inbetween, ends with ]
const headerRegexp = /^\[.*\]$/;
Expand All @@ -301,12 +309,49 @@ export const parseNotes = (
return {
content: headerContent,
isHeader: true,
isRtl: isRtlText(headerContent),
};
} else {
return {
content: line,
isHeader: false,
isRtl: isRtlText(line),
};
}
});
};

/* eslint-disable no-control-regex */
export const isRtlText = (
text: string,
onlyFirstWord: boolean = true,
): boolean => {
const rtlChars = new RegExp("[\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC]");
const ltrChars = new RegExp(
"[\u0000-\u0590\u2000-\u202E\u202A-\u202E\uFB00-\uFB4F]",
);
const aToZ = new RegExp("[a-zA-Z]");
let rtlCount = 0;
let ltrCount = 0;
if (onlyFirstWord) {
const splits = text.split(" ");
for (let i = 0; i < splits.length; i++) {
if (
rtlChars.test(splits[i].charAt(0)) ||
aToZ.test(splits[i].charAt(0))
) {
text = splits[i];
break;
}
}
}
for (let i = 0; i < text.length; i++) {
if (rtlChars.test(text[i])) {
rtlCount++;
} else if (ltrChars.test(text[i])) {
ltrCount++;
}
}

return rtlCount > ltrCount;
};

0 comments on commit 9a60788

Please sign in to comment.