Allow chosing fractions in quantities. Prettier config.

This commit is contained in:
Roger Veciana 2024-03-22 22:28:10 +01:00
parent 74b37280e3
commit e990c31399
7 changed files with 205 additions and 152 deletions

3
.gitignore vendored
View file

@ -1,6 +1,3 @@
# vscode
.vscode
# Intellij
*.iml
.idea

5
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,5 @@
{
"[svelte]": {
"editor.defaultFormatter": "svelte.svelte-vscode"
},
}

View file

@ -1,29 +1,31 @@
<script lang="ts">
export let data:string;
export let onChange: (newData: string) => void;
$: onChange(data)
export let data: string;
export let onChange: (newData: string) => void;
$: onChange(data);
</script>
<div>
<div class="content" contenteditable="true" bind:textContent={data} on:keydown={(e)=>{
if (e.key === 'Enter') {
document.execCommand('insertLineBreak')
e.preventDefault()
}}}>{data}</div>
<div
class="content"
contenteditable="true"
bind:textContent={data}
on:keydown={(e) => {
if (e.key === 'Enter') {
document.execCommand('insertLineBreak');
e.preventDefault();
}
}}
>
{data}
</div>
</div>
<style>
.content:empty:not(:focus):before {
content: "Type the recipe here";
opacity: 0.6;
}
.content{
white-space: pre-line;
}
.content:empty:not(:focus):before {
content: 'Type the recipe here';
opacity: 0.6;
}
.content {
white-space: pre-line;
}
</style>

View file

@ -69,6 +69,17 @@ export class Settings extends PluginSettingTab {
this.plugin.saveData(this.plugin.settings);
this.plugin.reloadPluginViews();
}));
new Setting(containerEl)
.setName('Show quantities as fractions')
.setDesc('Show the ingredient quantities as fractions instead of decimals, if possible')
.addToggle(toggle => toggle
.setValue(this.plugin.settings.showFractionsInQuantities)
.onChange((value: boolean) => {
this.plugin.settings.showFractionsInQuantities = value;
this.plugin.saveData(this.plugin.settings);
this.plugin.reloadPluginViews();
}));
}}
@ -80,6 +91,7 @@ export interface CookLangSettings {
showIngredientList: boolean;
showCookwareList: boolean;
showQuantitiesInline: boolean;
showFractionsInQuantities: boolean;
}
export const DEFAULT_SETTINGS: CookLangSettings = {
@ -88,4 +100,5 @@ export const DEFAULT_SETTINGS: CookLangSettings = {
showIngredientList: true,
showCookwareList: true,
showQuantitiesInline: false,
showFractionsInQuantities: true,
};

View file

@ -1,148 +1,157 @@
<script lang="ts">
import { Parser, type ParseResult } from '@cooklang/cooklang-ts';
import { tooltip } from '@svelte-plugins/tooltips';
import i18n from '../lang/i18n';
import { Parser, type ParseResult } from '@cooklang/cooklang-ts';
import { tooltip } from '@svelte-plugins/tooltips';
import i18n from '../lang/i18n';
import { DEFAULT_SETTINGS, type CookLangSettings } from './Settings';
import { formatNumber } from './utils';
export let data: string;
export let images: Record<string, string> = {};
export let settings: CookLangSettings = DEFAULT_SETTINGS;
export let data:string;
export let images: Record<string, string> = {}
export let settings: CookLangSettings = DEFAULT_SETTINGS
const translateIngredientsQuantity = (quantity: string | number) =>
quantity === 'some'
? $i18n.t('some')
: formatNumber(quantity, settings.showFractionsInQuantities);
const translateIngredientsQuantity = (quantity:string|number)=>quantity==='some'?$i18n.t('some'):quantity;
let recipe: ParseResult;
let recipe: ParseResult;
$: recipe = new Parser().parse(data);
$: recipe = new Parser().parse(data);
</script>
<div>
{#if data.length === 0}
<p>{$i18n.t('empty')}</p>
{:else}
{#if settings.showImages && images.recipe}
<img class="image-main" src={images.recipe} alt="Final result" />
{/if}
{#if settings.showIngredientList && recipe.ingredients.length > 0}
<section class="section">
<h3 class="section-title">{$i18n.t('ingredients')}</h3>
{#if recipe.metadata.servings}
<p class="servings">Servings: {recipe.metadata.servings}</p>
{/if}
<ul class="ingredients">
{#each recipe.ingredients as ingredient}
<li>{translateIngredientsQuantity(ingredient.quantity)} {ingredient.units} {ingredient.name}</li>
{/each}
</ul>
</section>
{/if}
{#if settings.showCookwareList && recipe.cookwares.length > 0}
<section class="section">
<h3 class="section-title">{$i18n.t('cookware')}</h3>
<ul class="cookware">
{#each recipe.cookwares as cookware}
<li>{cookware.name}</li>
{/each}
</ul>
</section>
{/if}
<h2 class="H2">{$i18n.t('method')}:</h2>
<div>
{#each recipe.steps as step, i}
<h3>{$i18n.t('step')} {i+1}</h3>
{#if settings.showImages && images[i]}
<img src={images[i]} alt="Final result" />
{/if}
<p>
{#each step as stepPart}
{#if stepPart.type === 'text'}
<span>{stepPart.value}</span>
{:else if stepPart.type === 'ingredient'}
<span class="ingredient" use:tooltip={{ content: `${translateIngredientsQuantity(stepPart.quantity)} ${stepPart.units} ${stepPart.name}`, action:'hover', autoPosition:true, arrow: false }}>{settings.showQuantitiesInline?`${translateIngredientsQuantity(stepPart.quantity)} ${stepPart.units} ${stepPart.name}`: `${stepPart.name}`}</span>
{:else if stepPart.type === 'cookware'}
<span class="cookware" >{stepPart.name}</span>
{:else if stepPart.type === 'timer'}
<span class="timer">{stepPart.quantity} {stepPart.units}</span>
{/if}
{/each}
</p>
{/each}
</div>
{/if}
{#if data.length === 0}
<p>{$i18n.t('empty')}</p>
{:else}
{#if settings.showImages && images.recipe}
<img class="image-main" src={images.recipe} alt="Final result" />
{/if}
{#if settings.showIngredientList && recipe.ingredients.length > 0}
<section class="section">
<h3 class="section-title">{$i18n.t('ingredients')}</h3>
{#if recipe.metadata.servings}
<p class="servings">Servings: {recipe.metadata.servings}</p>
{/if}
{#if recipe.metadata.source}
<hr />
<p>
{$i18n.t('source')}: <a href={recipe.metadata.source}>{recipe.metadata.source}</a>
</p>
{/if}
<ul class="ingredients">
{#each recipe.ingredients as ingredient}
<li>
{translateIngredientsQuantity(ingredient.quantity)}
{ingredient.units}
{ingredient.name}
</li>
{/each}
</ul>
</section>
{/if}
{#if settings.showCookwareList && recipe.cookwares.length > 0}
<section class="section">
<h3 class="section-title">{$i18n.t('cookware')}</h3>
<ul class="cookware">
{#each recipe.cookwares as cookware}
<li>{cookware.name}</li>
{/each}
</ul>
</section>
{/if}
<h2 class="H2">{$i18n.t('method')}:</h2>
<div>
{#each recipe.steps as step, i}
<h3>{$i18n.t('step')} {i + 1}</h3>
{#if settings.showImages && images[i]}
<img src={images[i]} alt="Final result" />
{/if}
<p>
{#each step as stepPart}
{#if stepPart.type === 'text'}
<span>{stepPart.value}</span>
{:else if stepPart.type === 'ingredient'}
<span
class="ingredient"
use:tooltip={{
content: `${translateIngredientsQuantity(stepPart.quantity)} ${stepPart.units} ${stepPart.name}`,
action: 'hover',
autoPosition: true,
arrow: false
}}
>{settings.showQuantitiesInline
? `${translateIngredientsQuantity(stepPart.quantity)} ${stepPart.units} ${stepPart.name}`
: `${stepPart.name}`}</span
>
{:else if stepPart.type === 'cookware'}
<span class="cookware">{stepPart.name}</span>
{:else if stepPart.type === 'timer'}
<span class="timer">{stepPart.quantity} {stepPart.units}</span>
{/if}
{/each}
</p>
{/each}
</div>
{/if}
{#if recipe.metadata.source}
<hr />
<p>
{$i18n.t('source')}: <a href={recipe.metadata.source}>{recipe.metadata.source}</a>
</p>
{/if}
</div>
<style>
.image-main{
width: 100%;}
section {
border: 1px solid #ccc;
border-radius: 0.25rem;
padding: 10px;
margin-top: 10px;
position: relative;
}
.section-title {
display: inline-block;
left: 5px;
top: -1.5rem;
position: absolute;
.image-main {
width: 100%;
}
section {
border: 1px solid #ccc;
border-radius: 0.25rem;
padding: 10px;
margin-top: 10px;
position: relative;
}
.section-title {
display: inline-block;
left: 5px;
top: -1.5rem;
position: absolute;
font-size: 0.75rem;
font-weight: 700;
text-transform: uppercase;
padding-left: 0.75rem;
padding-right: 0.75rem;
font-size: 0.75rem;
font-weight: 700;
text-transform: uppercase;
padding-left: 0.75rem;
padding-right: 0.75rem;
--tw-bg-opacity: 1;
background-color:var(--background-primary);
}
--tw-bg-opacity: 1;
background-color: var(--background-primary);
}
.servings {
width: fit-content;
}
.servings {
width: fit-content;
}
ul.ingredients{
column-count: 2;
}
ul.ingredients {
column-count: 2;
}
ul.cookware{
column-count: 2;
}
ul.cookware {
column-count: 2;
}
span.cookware {
font-weight: 600;
}
span.cookware {
font-weight: 600;
}
span.ingredient {
font-weight: 600;
}
span.ingredient {
font-weight: 600;
}
span.timer {
font-weight: 600;
}
:global(.theme-light .tooltip) {
--tooltip-background-color: rgb(235, 232, 233);
--tooltip-color: black;
--tooltip-box-shadow: 0 1px 8px rgb(125, 123, 123);
}
span.timer {
font-weight: 600;
}
:global(.theme-light .tooltip) {
--tooltip-background-color: rgb(235, 232, 233);
--tooltip-color: black;
--tooltip-box-shadow: 0 1px 8px rgb(125, 123, 123);
}
</style>

View file

@ -38,3 +38,30 @@ export const getI18n = (data:string) => {
}
export const formatNumber = (num: number|string, useFraction:boolean=false): string => {
if(typeof num === 'string') return num;
const epsilon = 0.0001;
if(Math.abs(num - 1/2) < epsilon) return useFraction?"1/2":"0.5";
else if(Math.abs(num - 1/3) < epsilon) return useFraction?"1/3":"0.33";
else if(Math.abs(num - 2/3) < epsilon) return useFraction?"2/3":"0.66";
else if(Math.abs(num - 1/4) < epsilon) return useFraction?"1/4":"0.25";
else if(Math.abs(num - 3/4) < epsilon) return useFraction?"3/4":"0.75";
else if(Math.abs(num - 1/5) < epsilon) return useFraction?"1/5":"0.2";
else if(Math.abs(num - 2/5) < epsilon) return useFraction?"2/5":"0.4";
else if(Math.abs(num - 3/5) < epsilon) return useFraction?"3/5":"0.6";
else if(Math.abs(num - 4/5) < epsilon) return useFraction?"4/5":"0.8";
else if(Math.abs(num - 1/6) < epsilon) return useFraction?"1/6":"0.16";
else if(Math.abs(num - 5/6) < epsilon) return useFraction?"5/6":"0.83";
else if(Math.abs(num - 1/7) < epsilon) return useFraction?"1/7":"0.14";
else if(Math.abs(num - 1/8) < epsilon) return useFraction?"1/8":"0.125";
else if(Math.abs(num - 3/8) < epsilon) return useFraction?"3/8":"0.375";
else if(Math.abs(num - 5/8) < epsilon) return useFraction?"5/8":"0.625";
else if(Math.abs(num - 7/8) < epsilon) return useFraction?"7/8":"0.875";
else if(Math.abs(num - 1/9) < epsilon) return useFraction?"1/9":"0.11";
else if(Math.abs(num - 1/10) < epsilon) return useFraction?"1/10":"0.1";
else if(Math.abs(num - 1/12) < epsilon) return useFraction?"1/12":"0.08";
else if(Math.abs(num - 1/16) < epsilon) return useFraction?"1/16":"0.06";
else if(Math.abs(num - 1/32) < epsilon) return useFraction?"1/32":"0.03";
else return num.toString();
};

View file

@ -1,4 +1,4 @@
.content.svelte-1x7gjtx:empty:not(:focus):before{content:"Type the recipe here";opacity:0.6}.content.svelte-1x7gjtx{white-space:pre-line}:root{--tooltip-arrow-size:10px;--tooltip-background-color:rgba(0, 0, 0, 0.9);--tooltip-border-radius:4px;--tooltip-box-shadow:0 1px 20px rgba(0, 0, 0, 0.25);--tooltip-font-family:-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
.content.svelte-1bdsa1h:empty:not(:focus):before{content:'Type the recipe here';opacity:0.6}.content.svelte-1bdsa1h{white-space:pre-line}:root{--tooltip-arrow-size:10px;--tooltip-background-color:rgba(0, 0, 0, 0.9);--tooltip-border-radius:4px;--tooltip-box-shadow:0 1px 20px rgba(0, 0, 0, 0.25);--tooltip-font-family:-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif;--tooltip-font-size:14px;--tooltip-font-weight:500;--tooltip-line-height:1.25rem;--tooltip-color:#fff;--tooltip-offset-x:0px;--tooltip-offset-y:0px;--tooltip-padding:12px;--tooltip-pointer-events:none;--tooltip-white-space-hidden:nowrap;--tooltip-white-space-shown:normal;--tooltip-z-index:100}.tooltip.svelte-1xktviv{background-color:var(--tooltip-background-color);box-shadow:var(--tooltip-box-shadow);border-radius:var(--tooltip-border-radius);color:var(--tooltip-color);opacity:0;font-family:var(--tooltip-font-family);font-size:var(--tooltip-font-size);font-style:normal;font-weight:var(--tooltip-font-weight);line-height:var(--tooltip-line-height);padding:var(--tooltip-padding);pointer-events:var(--tooltip-pointer-events);position:absolute;text-align:left;visibility:hidden;white-space:var(--tooltip-white-space-hidden);z-index:var(--tooltip-z-index)}.tooltip.show.svelte-1xktviv{opacity:1;visibility:visible;white-space:var(--tooltip-white-space-shown)}.tooltip.bottom.svelte-1xktviv:after,.tooltip.left.svelte-1xktviv:after,.tooltip.right.svelte-1xktviv:after,.tooltip.top.svelte-1xktviv:after{border:var(--tooltip-arrow-size) solid var(--tooltip-background-color);content:' ';position:absolute}.tooltip.arrowless.svelte-1xktviv:after{border:0 !important}.tooltip.bottom.svelte-1xktviv,.tooltip.top.svelte-1xktviv{--tooltip-offset-x:0px;--tooltip-offset-y:12px}.tooltip.left.svelte-1xktviv,.tooltip.right.svelte-1xktviv{--tooltip-offset-x:12px;--tooltip-offset-y:0px}.tooltip.bottom.svelte-1xktviv{bottom:0;left:50%;transform:translate(
calc(-50% + var(--tooltip-offset-x)),
calc(100% + var(--tooltip-offset-y))
@ -190,4 +190,4 @@
calc(-50% + var(--tooltip-offset-x)),
calc(100% + var(--tooltip-offset-y))
)
scale(1, 1)}.image-main.svelte-1ewzew3{width:100%}section.svelte-1ewzew3{border:1px solid #ccc;border-radius:0.25rem;padding:10px;margin-top:10px;position:relative}.section-title.svelte-1ewzew3{display:inline-block;left:5px;top:-1.5rem;position:absolute;font-size:0.75rem;font-weight:700;text-transform:uppercase;padding-left:0.75rem;padding-right:0.75rem;--tw-bg-opacity:1;background-color:var(--background-primary)}.servings.svelte-1ewzew3{width:fit-content}ul.ingredients.svelte-1ewzew3{column-count:2}ul.cookware.svelte-1ewzew3{column-count:2}span.cookware.svelte-1ewzew3{font-weight:600}span.ingredient.svelte-1ewzew3{font-weight:600}span.timer.svelte-1ewzew3{font-weight:600}.theme-light .tooltip{--tooltip-background-color:rgb(235, 232, 233);--tooltip-color:black;--tooltip-box-shadow:0 1px 8px rgb(125, 123, 123)}
scale(1, 1)}.image-main.svelte-13gknjv{width:100%}section.svelte-13gknjv{border:1px solid #ccc;border-radius:0.25rem;padding:10px;margin-top:10px;position:relative}.section-title.svelte-13gknjv{display:inline-block;left:5px;top:-1.5rem;position:absolute;font-size:0.75rem;font-weight:700;text-transform:uppercase;padding-left:0.75rem;padding-right:0.75rem;--tw-bg-opacity:1;background-color:var(--background-primary)}.servings.svelte-13gknjv{width:fit-content}ul.ingredients.svelte-13gknjv{column-count:2}ul.cookware.svelte-13gknjv{column-count:2}span.cookware.svelte-13gknjv{font-weight:600}span.ingredient.svelte-13gknjv{font-weight:600}span.timer.svelte-13gknjv{font-weight:600}.theme-light .tooltip{--tooltip-background-color:rgb(235, 232, 233);--tooltip-color:black;--tooltip-box-shadow:0 1px 8px rgb(125, 123, 123)}