mirror of
https://github.com/rveciana/obsidian-cooklang.git
synced 2026-07-22 09:40:31 +00:00
Compare commits
32 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ca6e05a6cd | ||
|
|
c7da48fec5 | ||
|
|
cdeb211ae2 | ||
|
|
39834741d4 | ||
|
|
dc97fc2660 | ||
|
|
f0a1af420f | ||
|
|
c956ed667a | ||
|
|
298ba05ba9 | ||
|
|
3a2ca394ee | ||
|
|
eb0aec8aff | ||
|
|
2d6569dbe2 | ||
|
|
b062235885 | ||
|
|
8dc675918d | ||
|
|
b810c5cefe | ||
|
|
e990c31399 | ||
|
|
74b37280e3 | ||
|
|
d9aba4ae96 | ||
|
|
9ba7bd21fe | ||
|
|
d64a3e73cb | ||
|
|
933d99b9c5 | ||
|
|
33715d8cbb | ||
|
|
1a24d61721 | ||
|
|
29821bcf1b | ||
|
|
4d05d3bb1e | ||
|
|
801e131f51 | ||
|
|
e4f09873da | ||
|
|
d9bd5e738a | ||
|
|
84bfe98a45 | ||
|
|
0c507a9390 | ||
|
|
bbc6ee3000 | ||
|
|
d00fd1772b | ||
|
|
0f8cf51d12 |
25 changed files with 5981 additions and 3521 deletions
|
|
@ -1,2 +0,0 @@
|
|||
npm node_modules
|
||||
build
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -1,6 +1,3 @@
|
|||
# vscode
|
||||
.vscode
|
||||
|
||||
# Intellij
|
||||
*.iml
|
||||
.idea
|
||||
|
|
@ -11,6 +8,7 @@ node_modules
|
|||
# Don't include the compiled main.js file in the repo.
|
||||
# They should be uploaded to GitHub releases instead.
|
||||
main.js
|
||||
styles.css
|
||||
|
||||
# Exclude sourcemaps
|
||||
*.map
|
||||
|
|
|
|||
1
.husky/pre-commit
Normal file
1
.husky/pre-commit
Normal file
|
|
@ -0,0 +1 @@
|
|||
npm run set-date
|
||||
5
.vscode/settings.json
vendored
Normal file
5
.vscode/settings.json
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"[svelte]": {
|
||||
"editor.defaultFormatter": "svelte.svelte-vscode"
|
||||
},
|
||||
}
|
||||
2
LICENSE
2
LICENSE
|
|
@ -1,6 +1,6 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2024 rveciana
|
||||
Copyright (c) 2025 Roger Veciana i Rovira
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
|
|||
18
README.md
18
README.md
|
|
@ -1,3 +1,6 @@
|
|||
[](https://opensource.org/licenses/MIT)
|
||||
|
||||
|
||||
# Cooklang
|
||||
|
||||
This plugin allows you to edit and view recipes written in the Cooklang format.
|
||||
|
|
@ -13,11 +16,22 @@ This plugin allows you to edit and view recipes written in the Cooklang format.
|
|||
|
||||
- Multi language: The titles for the sections (i.e. *ingredients*, *cookware* and so on) will be translated into the recipe language or the one configured in the Obsidian settings
|
||||
|
||||
- Scale quantities: The slider in the ingredients part allows multiplying or dividing the amounts
|
||||
## Searching
|
||||
|
||||
Obsidian won't search files that aren't of the extension *.md by default. Since cooklang has the .cook extension, any search will gnore the files, which is really impractical. Fortuntely, there's a workaround:
|
||||
|
||||
- Install the `omnisearch` plugin
|
||||
- Open its settings
|
||||
- Add the word `cook` at the section `Additional TEXT files to index`
|
||||
|
||||
The search has to be done from the sidebar search icon, but it works perfectly.
|
||||
|
||||
## Changelog
|
||||
|
||||
- 0.0.1: Support for *source* and *servings* metadata.
|
||||
- 0.0.0: Initial version. Can edit, view, and select the language.
|
||||
- 0.0.10: Choose quantities as fractions or decimals
|
||||
- 0.0.9: Support webp images, add languages
|
||||
- 0.0.8: Initial version. Can edit, view, and select the language
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
|
|
|
|||
58
eslint.config.ts
Normal file
58
eslint.config.ts
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
import js from '@eslint/js';
|
||||
import tseslint from 'typescript-eslint';
|
||||
import svelte from 'eslint-plugin-svelte';
|
||||
import svelteParser from 'svelte-eslint-parser';
|
||||
import type { Linter } from 'eslint';
|
||||
|
||||
import tsParser from '@typescript-eslint/parser';
|
||||
|
||||
const svelteRecommendedWithRules = svelte.configs.recommended.find(
|
||||
(config) => 'rules' in config
|
||||
);
|
||||
const svelteRules = (svelteRecommendedWithRules?.rules ?? {}) as Linter.RulesRecord;
|
||||
export default [
|
||||
// ⛔ Ignore files/folders
|
||||
{
|
||||
ignores: ['node_modules', 'build', 'main.js', 'vite.config.mjs', 'version-bump.mjs'],
|
||||
},
|
||||
|
||||
// Base JS rules
|
||||
js.configs.recommended,
|
||||
|
||||
// TypeScript rules
|
||||
...tseslint.configs.recommended,
|
||||
|
||||
// Svelte-specific configuration
|
||||
{
|
||||
files: ['**/*.svelte'],
|
||||
plugins: {
|
||||
svelte,
|
||||
},
|
||||
languageOptions: {
|
||||
globals: { document: 'readonly',console: 'readonly',
|
||||
window: 'readonly', },
|
||||
parser: svelteParser,
|
||||
parserOptions: {
|
||||
parser: tsParser,
|
||||
ecmaVersion: 2020,
|
||||
sourceType: 'module',
|
||||
extraFileExtensions: ['.svelte'],
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
...svelteRules
|
||||
},
|
||||
},
|
||||
|
||||
// Global rules for all file
|
||||
{
|
||||
rules: {
|
||||
'no-unused-vars': 'off',
|
||||
'@typescript-eslint/no-unused-vars': ['error', { args: 'none' }],
|
||||
'@typescript-eslint/ban-ts-comment': 'off',
|
||||
'no-prototype-builtins': 'off',
|
||||
'@typescript-eslint/no-empty-function': 'off',
|
||||
},
|
||||
},
|
||||
|
||||
];
|
||||
BIN
header.png
BIN
header.png
Binary file not shown.
|
Before Width: | Height: | Size: 2.2 MiB After Width: | Height: | Size: 2.2 MiB |
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "cooklang-viewer-and-editor",
|
||||
"name": "Cooklang",
|
||||
"version": "0.0.3",
|
||||
"version": "0.1.0",
|
||||
"minAppVersion": "0.12.0",
|
||||
"description": "Display and edit recipes written in the Cooklang format.",
|
||||
"author": "Roger Veciana i Rovira",
|
||||
|
|
|
|||
7216
package-lock.json
generated
7216
package-lock.json
generated
File diff suppressed because it is too large
Load diff
83
package.json
83
package.json
|
|
@ -1,36 +1,51 @@
|
|||
{
|
||||
"name": "obsidian-svelte-starter",
|
||||
"version": "0.0.4",
|
||||
"description": "A plugin development template for devs who want to use svelte in Obsidian.",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"lint": "eslint . --ext .ts",
|
||||
"dev": "npm run lint && vite build --watch",
|
||||
"build": "npx svelte-check && vite build",
|
||||
"bumpversion": "node version-bump.mjs && git add manifest.json versions.json",
|
||||
"postversion": "npm run bumpversion"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "Roger Veciana i Rovira",
|
||||
"devDependencies": {
|
||||
"@cooklang/cooklang-ts": "^1.2.5",
|
||||
"@svelte-plugins/tooltips": "^3.0.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^1.0.1",
|
||||
"@tsconfig/svelte": "^3.0.0",
|
||||
"@types/node": "^16.11.6",
|
||||
"@typescript-eslint/eslint-plugin": "6.19.0",
|
||||
"@typescript-eslint/parser": "6.19.0",
|
||||
"builtin-modules": "3.3.0",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-plugin-svelte": "^2.35.1",
|
||||
"franc": "^6.2.0",
|
||||
"obsidian": "0.15.9",
|
||||
"svelte": "^3.49.0",
|
||||
"svelte-check": "^2.8.0",
|
||||
"svelte-i18next": "^2.2.2",
|
||||
"svelte-preprocess": "^5.1.3",
|
||||
"tslib": "2.4.0",
|
||||
"typescript": "5.3.3",
|
||||
"vite": "^3.0.0"
|
||||
}
|
||||
"name": "cooklang-viewer-and-editor",
|
||||
"version": "0.1.0",
|
||||
"description": "A plugin development template for devs who want to use svelte in Obsidian.",
|
||||
"main": "main.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"lint": "eslint . --ext .ts",
|
||||
"dev": "npm run lint && vite build --watch",
|
||||
"test": "vitest",
|
||||
"build": "npx svelte-check && vite build",
|
||||
"bumpversion": "node version-bump.mjs && git add manifest.json versions.json && git commit -m 'bump version'",
|
||||
"postversion": "npm run bumpversion",
|
||||
"prepare": "husky",
|
||||
"set-date": "year=`date +%Y` && cat LICENSE|sed \"3s/.*/Copyright (c) $year Roger Veciana i Rovira/\">LICENSE.tmp && mv LICENSE.tmp LICENSE"
|
||||
},
|
||||
"keywords": [
|
||||
"cooklang",
|
||||
"cooking"
|
||||
],
|
||||
"author": "Roger Veciana i Rovira",
|
||||
"devDependencies": {
|
||||
"@cooklang/cooklang-ts": "^1.2.7",
|
||||
"@svelte-plugins/tooltips": "^3.0.3",
|
||||
"@sveltejs/vite-plugin-svelte": "^5.1.0",
|
||||
"@tsconfig/svelte": "^5.0.4",
|
||||
"@types/node": "^22.15.29",
|
||||
"@typescript-eslint/parser": "^8.33.1",
|
||||
"builtin-modules": "^5.0.0",
|
||||
"esbuild-svelte": "^0.9.3",
|
||||
"eslint": "^9.28.0",
|
||||
"eslint-plugin-svelte": "^3.9.0",
|
||||
"franc": "^6.2.0",
|
||||
"husky": "^9.1.7",
|
||||
"jiti": "^2.4.2",
|
||||
"obsidian": "^1.8.7",
|
||||
"svelte": "^5.33.14",
|
||||
"svelte-check": "^4.1.6",
|
||||
"svelte-i18next": "^2.2.2",
|
||||
"svelte-preprocess": "^6.0.3",
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "~5.8.3",
|
||||
"typescript-eslint": "^8.33.0",
|
||||
"vite": "^6.3.5",
|
||||
"vite-plugin-static-copy": "^3.0.0",
|
||||
"vitest": "^3.2.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"svelte-range-slider-pips": "^4.0.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import i18next from "i18next";
|
||||
import { createI18nStore } from "svelte-i18next";
|
||||
import { createI18nStore } from "svelte-i18next/i18n.js";
|
||||
|
||||
|
||||
export const i18n = createI18nStore(i18next);
|
||||
|
|
|
|||
|
|
@ -1,35 +1,122 @@
|
|||
export const resources= {
|
||||
en: {
|
||||
translation: {
|
||||
"empty": "Empty recipe. Edit it using the pencil icon.",
|
||||
"method": "Method",
|
||||
"ingredients": "Ingredients",
|
||||
"cookware": "Cookware",
|
||||
"step": "Step",
|
||||
"some": "Some",
|
||||
"source": "Source",
|
||||
}
|
||||
},
|
||||
es: {
|
||||
translation: {
|
||||
empty: "Receta vacía. Edítala usando el icono de lápiz.",
|
||||
method: "Método",
|
||||
ingredients: "Ingredientes",
|
||||
cookware: "Utensilios de cocina",
|
||||
step: "Paso",
|
||||
some: "Algunos",
|
||||
source: "Fuente",
|
||||
}
|
||||
},
|
||||
ca: {
|
||||
translation: {
|
||||
empty: "Recepta buida. Edita-la utilitzant la icona del llapis.",
|
||||
method: "Mètode",
|
||||
ingredients: "Ingredients",
|
||||
cookware: "Estris de cuina",
|
||||
step: "Pas",
|
||||
some: "Alguns",
|
||||
source: "Font",
|
||||
}
|
||||
export const resources = {
|
||||
en: {
|
||||
translation: {
|
||||
"empty": "Empty recipe. Edit it using the pencil icon.",
|
||||
"method": "Method",
|
||||
"ingredients": "Ingredients",
|
||||
"cookware": "Cookware",
|
||||
"step": "Step",
|
||||
"some": "Some",
|
||||
"source": "Source",
|
||||
"servings": "Servings",
|
||||
}
|
||||
}
|
||||
},
|
||||
es: {
|
||||
translation: {
|
||||
"empty": "Receta vacía. Edítala usando el icono de lápiz.",
|
||||
"method": "Método",
|
||||
"ingredients": "Ingredientes",
|
||||
"cookware": "Utensilios de cocina",
|
||||
"step": "Paso",
|
||||
"some": "Algunos",
|
||||
"source": "Fuente",
|
||||
"servings": "Porciones",
|
||||
}
|
||||
},
|
||||
ca: {
|
||||
translation: {
|
||||
"empty": "Recepta buida. Edita-la utilitzant la icona del llapis.",
|
||||
"method": "Mètode",
|
||||
"ingredients": "Ingredients",
|
||||
"cookware": "Estris de cuina",
|
||||
"step": "Pas",
|
||||
"some": "Alguns",
|
||||
"source": "Font",
|
||||
"servings": "Racions",
|
||||
}
|
||||
},
|
||||
de: {
|
||||
translation: {
|
||||
"empty": "Leeres Rezept. Bearbeite es mit dem Bleistift-Symbol.",
|
||||
"method": "Methode",
|
||||
"ingredients": "Zutaten",
|
||||
"cookware": "Kochgeschirr",
|
||||
"step": "Schritt",
|
||||
"some": "Einige",
|
||||
"source": "Quelle",
|
||||
"servings": "Portionen",
|
||||
}
|
||||
},
|
||||
fr: {
|
||||
translation: {
|
||||
"empty": "Recette vide. Modifiez-la à l'aide de l'icône du crayon.",
|
||||
"method": "Méthode",
|
||||
"ingredients": "Ingrédients",
|
||||
"cookware": "Ustensiles de cuisine",
|
||||
"step": "Étape",
|
||||
"some": "Certains",
|
||||
"source": "Source",
|
||||
"servings": "Portions",
|
||||
}
|
||||
},
|
||||
pt: {
|
||||
translation: {
|
||||
"empty": "Receita vazia. Edite usando o ícone de lápis.",
|
||||
"method": "Método",
|
||||
"ingredients": "Ingredientes",
|
||||
"cookware": "Utensílios de cozinha",
|
||||
"step": "Passo",
|
||||
"some": "Alguns",
|
||||
"source": "Fonte",
|
||||
"servings": "Porções",
|
||||
}
|
||||
},
|
||||
it: {
|
||||
translation: {
|
||||
"empty": "Ricetta vuota. Modificala usando l'icona della matita.",
|
||||
"method": "Metodo",
|
||||
"ingredients": "Ingredienti",
|
||||
"cookware": "Utensili da cucina",
|
||||
"step": "Passo",
|
||||
"some": "Alcuni",
|
||||
"source": "Fonte",
|
||||
"servings": "Porzioni",
|
||||
}
|
||||
},
|
||||
nl: {
|
||||
translation: {
|
||||
"empty": "Leeg recept. Bewerk het met het potloodicoon.",
|
||||
"method": "Methode",
|
||||
"ingredients": "Ingrediënten",
|
||||
"cookware": "Kookgerei",
|
||||
"step": "Stap",
|
||||
"some": "Sommige",
|
||||
"source": "Bron",
|
||||
"servings": "Porties",
|
||||
}
|
||||
},
|
||||
tr: {
|
||||
translation: {
|
||||
"empty": "Boş tarif. Kalem simgesini kullanarak düzenleyin.",
|
||||
"method": "Yöntem",
|
||||
"ingredients": "Malzemeler",
|
||||
"cookware": "Mutfak gereçleri",
|
||||
"step": "Adım",
|
||||
"some": "Bazı",
|
||||
"source": "Kaynak",
|
||||
"servings": "Porsiyonlar",
|
||||
}
|
||||
},
|
||||
el: {
|
||||
translation: {
|
||||
"empty": "Άδεια συνταγή. Επεξεργαστείτε τη χρησιμοποιώντας το εικονίδιο του μολυβιού.",
|
||||
"method": "Μέθοδος",
|
||||
"ingredients": "Συστατικά",
|
||||
"cookware": "Σκεύη μαγειρικής",
|
||||
"step": "Βήμα",
|
||||
"some": "Κάποια",
|
||||
"source": "Πηγή",
|
||||
"servings": "Μερίδες",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,17 +2,21 @@ import {
|
|||
Plugin,
|
||||
TFile,
|
||||
TextFileView,
|
||||
WorkspaceLeaf,
|
||||
type WorkspaceLeaf,
|
||||
setIcon
|
||||
} from "obsidian";
|
||||
|
||||
|
||||
import i18next from "i18next";
|
||||
import { resources } from "./lang/resources";
|
||||
import { resources } from "./lang/resources.js";
|
||||
|
||||
import Edit from "./ui/Edit.svelte";
|
||||
import View from "./ui/View.svelte";
|
||||
import { DEFAULT_SETTINGS, Settings, type CookLangSettings } from "./ui/Settings";
|
||||
import { getI18n, isTFile } from "./ui/utils";
|
||||
|
||||
import { DEFAULT_SETTINGS, Settings, type CookLangSettings } from "./ui/Settings.js";
|
||||
import { getI18n, isTFile } from "./ui/utils.js";
|
||||
import { mount, unmount } from "svelte";
|
||||
import { Parser, type ParseResult } from "@cooklang/cooklang-ts";
|
||||
|
||||
const VIEW_TYPE = "svelte-cooklang";
|
||||
|
||||
|
|
@ -31,16 +35,19 @@ i18next.init({
|
|||
const DEFAULT_DATA = "";
|
||||
|
||||
class CooklangSvelteView extends TextFileView {
|
||||
view: View | Edit;
|
||||
view!: View | Edit;
|
||||
mode: "source" | "preview" = "preview";
|
||||
changeModeButton: HTMLElement;
|
||||
changeModeButton!: HTMLElement;
|
||||
data: string = DEFAULT_DATA;
|
||||
images: Record<string, string> = {};
|
||||
settings: CookLangSettings;
|
||||
props = $state({data:DEFAULT_DATA, images:{}, settings: DEFAULT_SETTINGS});
|
||||
|
||||
|
||||
constructor(leaf: WorkspaceLeaf, settings: CookLangSettings) {
|
||||
super(leaf);
|
||||
this.settings = settings;
|
||||
|
||||
}
|
||||
|
||||
getViewType(): string {
|
||||
|
|
@ -53,7 +60,7 @@ class CooklangSvelteView extends TextFileView {
|
|||
}
|
||||
|
||||
getIcon(): string {
|
||||
return "microphone";
|
||||
return "chef-hat";
|
||||
}
|
||||
|
||||
async onOpen(): Promise<void> {
|
||||
|
|
@ -62,30 +69,34 @@ class CooklangSvelteView extends TextFileView {
|
|||
this.changeModeButton = this.addAction(
|
||||
"pencil",
|
||||
"Preview (Ctrl+Click to open in new pane)",
|
||||
(e) => { this.switchMode(e.metaKey || e.ctrlKey);},
|
||||
17
|
||||
(e) => { this.switchMode(e.metaKey || e.ctrlKey);}
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
renderPreview(newTab=false){
|
||||
|
||||
const container = this.contentEl.createEl("div");
|
||||
const newElement = this.mode === "preview"
|
||||
? new View({
|
||||
target: container,
|
||||
props: { data: this.data, images: this.images, settings: this.settings },
|
||||
})
|
||||
: new Edit({
|
||||
target: container,
|
||||
props: {
|
||||
data: this.data,
|
||||
onChange: (newData:string) => (this.data = newData),
|
||||
},
|
||||
});
|
||||
if(newTab){
|
||||
|
||||
const newElement = this.mode === "preview"?
|
||||
mount(View , {target: container, props: this.props})
|
||||
:
|
||||
mount(Edit, {target: container,
|
||||
props: {data: this.props.data,
|
||||
onChange: (newData:string) => {
|
||||
this.props.data = newData;
|
||||
this.data = newData;
|
||||
}}
|
||||
})
|
||||
|
||||
|
||||
if(newTab && this.file){
|
||||
const newTab = this.app.workspace.getLeaf(true);
|
||||
newTab.openFile(this.file);
|
||||
} else {
|
||||
if (this.view) {
|
||||
unmount(this.view);
|
||||
}
|
||||
this.view = newElement;
|
||||
}
|
||||
|
||||
|
|
@ -94,14 +105,15 @@ class CooklangSvelteView extends TextFileView {
|
|||
getViewData(): string {
|
||||
return this.data;
|
||||
}
|
||||
|
||||
setViewData(data: string): void {
|
||||
|
||||
const images = (
|
||||
this.file.parent.children.filter(isTFile).filter(
|
||||
(f) => (f.basename === this.file.basename ||
|
||||
f.basename.startsWith(this.file.basename + ".")) &&
|
||||
f.name != this.file.name &&
|
||||
["png", "jpg", "jpeg", "gif"].includes(f.extension)
|
||||
this.file?.parent?.children.filter(isTFile).filter(
|
||||
(f) => (f.basename === this.file?.basename ||
|
||||
f.basename.startsWith(this.file?.basename + ".")) &&
|
||||
f.name != this.file?.name &&
|
||||
["png", "jpg", "jpeg", "gif","webp"].includes(f.extension)
|
||||
) as TFile[]
|
||||
).reduce((acc, f) => {
|
||||
const split = f.basename.split(".");
|
||||
|
|
@ -114,15 +126,22 @@ class CooklangSvelteView extends TextFileView {
|
|||
return acc;
|
||||
}, {} as Record<string, string>);
|
||||
this.images = images;
|
||||
|
||||
if(this.settings.autoLanguage){
|
||||
const lang = getI18n(data);
|
||||
|
||||
const recipe: ParseResult = new Parser().parse(data);
|
||||
if(recipe.metadata.locale){
|
||||
const lang = recipe.metadata.locale.split("_")[0]
|
||||
i18next.changeLanguage(lang);
|
||||
} else if(this.settings.autoLanguage){
|
||||
const lang = getI18n(data);
|
||||
i18next.changeLanguage(lang);
|
||||
}
|
||||
|
||||
this.data = data;
|
||||
|
||||
this.view.$set({ data, images });
|
||||
this.props.data = data;
|
||||
this.props.images = images;
|
||||
|
||||
this.renderPreview(false)
|
||||
|
||||
}
|
||||
clear(): void {
|
||||
|
|
@ -141,8 +160,7 @@ class CooklangSvelteView extends TextFileView {
|
|||
}
|
||||
|
||||
export default class CooklangPlugin extends Plugin {
|
||||
private view: CooklangSvelteView;
|
||||
settings: CookLangSettings;
|
||||
settings: CookLangSettings = DEFAULT_SETTINGS;
|
||||
|
||||
|
||||
async onload() {
|
||||
|
|
@ -151,7 +169,7 @@ export default class CooklangPlugin extends Plugin {
|
|||
this.registerView(
|
||||
VIEW_TYPE,
|
||||
(leaf: WorkspaceLeaf) =>
|
||||
(this.view = new CooklangSvelteView(leaf, this.settings))
|
||||
(new CooklangSvelteView(leaf, this.settings))
|
||||
);
|
||||
|
||||
this.app.workspace.onLayoutReady(this.onLayoutReady.bind(this));
|
||||
|
|
@ -207,7 +225,7 @@ export default class CooklangPlugin extends Plugin {
|
|||
if (this.app.workspace.getLeavesOfType(VIEW_TYPE).length) {
|
||||
return;
|
||||
}
|
||||
this.app.workspace.getRightLeaf(false).setViewState({
|
||||
this.app.workspace.getRightLeaf(false)?.setViewState({
|
||||
type: VIEW_TYPE,
|
||||
});
|
||||
}
|
||||
|
|
@ -1,29 +1,38 @@
|
|||
<script lang="ts">
|
||||
export let data:string;
|
||||
export let onChange: (newData: string) => void;
|
||||
$: onChange(data)
|
||||
let { data, onChange }: { data: string; onChange: (newData: string) => void } = $props();
|
||||
|
||||
$effect(() => {
|
||||
if (data !== undefined) {
|
||||
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"
|
||||
role="textbox"
|
||||
tabindex="0"
|
||||
aria-multiline="true"
|
||||
bind:textContent={data}
|
||||
onkeydown={(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>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { App, PluginSettingTab, Setting } from "obsidian";
|
||||
import type CooklangPlugin from "src/starterIndex";
|
||||
import { type App, PluginSettingTab, Setting } from "obsidian";
|
||||
import type CooklangPlugin from "src/starterIndex.svelte";
|
||||
|
||||
export class Settings extends PluginSettingTab {
|
||||
plugin: CooklangPlugin;
|
||||
|
|
@ -13,10 +13,6 @@ export class Settings extends PluginSettingTab {
|
|||
const { containerEl } = this;
|
||||
|
||||
containerEl.empty();
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Preview Options')
|
||||
.setHeading();
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Find recipe language automatically')
|
||||
|
|
@ -73,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();
|
||||
}));
|
||||
|
||||
|
||||
}}
|
||||
|
|
@ -84,6 +91,7 @@ export interface CookLangSettings {
|
|||
showIngredientList: boolean;
|
||||
showCookwareList: boolean;
|
||||
showQuantitiesInline: boolean;
|
||||
showFractionsInQuantities: boolean;
|
||||
}
|
||||
|
||||
export const DEFAULT_SETTINGS: CookLangSettings = {
|
||||
|
|
@ -92,4 +100,5 @@ export const DEFAULT_SETTINGS: CookLangSettings = {
|
|||
showIngredientList: true,
|
||||
showCookwareList: true,
|
||||
showQuantitiesInline: false,
|
||||
showFractionsInQuantities: true,
|
||||
};
|
||||
|
|
@ -1,149 +1,166 @@
|
|||
<script lang="ts">
|
||||
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 { Parser, type ParseResult } from '@cooklang/cooklang-ts';
|
||||
import { tooltip } from '@svelte-plugins/tooltips';
|
||||
import i18n from '../lang/i18n.js';
|
||||
import { DEFAULT_SETTINGS, type CookLangSettings } from './Settings.js';
|
||||
import { formatNumber } from './utils.js';
|
||||
import RangeSlider from 'svelte-range-slider-pips';
|
||||
|
||||
let {
|
||||
data,
|
||||
images = {},
|
||||
settings = DEFAULT_SETTINGS
|
||||
}: { data: string; images: Record<string, string>; settings: CookLangSettings } = $props();
|
||||
|
||||
export let data:string;
|
||||
export let images: Record<string, string> = {}
|
||||
export let settings: CookLangSettings = DEFAULT_SETTINGS
|
||||
const scales = [0.25, 0.5, 1, 1.5, 2, 3, 4];
|
||||
let scaleIdx = $state(2);
|
||||
let scale = $derived(scales[scaleIdx]);
|
||||
|
||||
const translateIngredientsQuantity = (quantity:string|number)=>quantity==='some'?$i18n.t('some'):quantity;
|
||||
const translateIngredientsQuantity = (quantity: string | number, scale: number = 1) =>
|
||||
quantity === 'some'
|
||||
? $i18n.t('some')
|
||||
: formatNumber(quantity, settings.showFractionsInQuantities, scale);
|
||||
|
||||
let recipe: ParseResult;
|
||||
|
||||
$: recipe = new Parser().parse(data);
|
||||
$: console.log(recipe);
|
||||
|
||||
const recipe: ParseResult = 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">
|
||||
{$i18n.t('servings')}: {scale * parseInt(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, scale)}
|
||||
{ingredient.units}
|
||||
{ingredient.name}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
|
||||
<RangeSlider bind:value={scaleIdx} min={0} max={6} />
|
||||
</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 {
|
||||
max-width: 300px;
|
||||
}
|
||||
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>
|
||||
|
|
|
|||
36
src/ui/utils.test.ts
Normal file
36
src/ui/utils.test.ts
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { formatNumber } from './utils.js';
|
||||
|
||||
|
||||
|
||||
describe('formatNumber', () => {
|
||||
|
||||
it('Should parse a simple string with a number only', () => {
|
||||
expect(formatNumber(2)).toBe("2");
|
||||
});
|
||||
it('Should parse a simple string with a string only', () => {
|
||||
expect(formatNumber("hello")).toBe("hello");
|
||||
});
|
||||
it('Should parse a string with a number and a string', () => {
|
||||
expect(formatNumber("2 kilos")).toBe("2 kilos");
|
||||
expect(formatNumber("about 2 kilos")).toBe("about 2 kilos");
|
||||
});
|
||||
it('Should parse a string with a scaled number and a string', () => {
|
||||
expect(formatNumber("2 kilos", true, 2)).toBe("4 kilos");
|
||||
expect(formatNumber("2 kilos", true, 0.5)).toBe("1 kilos");
|
||||
});
|
||||
it('Should parse a string with a scaled number with decimals', () => {
|
||||
expect(formatNumber("2.5 kilos", true, 2)).toBe("5 kilos");
|
||||
});
|
||||
it('Should parse a string with a scaled number with decimals, rounding to une decimal', () => {
|
||||
expect(formatNumber("2.55 kilos")).toBe("2.5 kilos");
|
||||
expect(formatNumber("1 kilos", false, 0.3333333)).toBe("0.3 kilos");
|
||||
});
|
||||
it('Should convert to fractions', () => {
|
||||
expect(formatNumber(0.33333, true)).toBe("1/3");
|
||||
expect(formatNumber(0.5, true)).toBe("1/2");
|
||||
expect(formatNumber(0.2, true)).toBe("1/5");
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import type { Ingredient, Cookware, Timer, Text } from "@cooklang/cooklang-ts";
|
||||
import { franc } from "franc";
|
||||
import { iso6393To1 } from "./langCodes";
|
||||
import { TAbstractFile, TFile } from "obsidian";
|
||||
import { iso6393To1 } from "./langCodes.js";
|
||||
import { type TAbstractFile, TFile } from "obsidian";
|
||||
|
||||
export const isText = (
|
||||
step: Ingredient | Cookware | Timer | Text
|
||||
|
|
@ -38,3 +38,38 @@ export const getI18n = (data:string) => {
|
|||
|
||||
|
||||
}
|
||||
|
||||
const formatFraction = (num:number, useFraction:boolean=false) =>{
|
||||
if(!useFraction) return +num.toFixed( 1 );
|
||||
const epsilon = 0.0001;
|
||||
if (Math.abs(num - 1 / 2) < epsilon) return "1/2";
|
||||
else if (Math.abs(num - 1 / 3) < epsilon) return "1/3";
|
||||
else if (Math.abs(num - 2 / 3) < epsilon) return "2/3";
|
||||
else if (Math.abs(num - 1 / 4) < epsilon) return "1/4";
|
||||
else if (Math.abs(num - 3 / 4) < epsilon) return "3/4";
|
||||
else if (Math.abs(num - 1 / 5) < epsilon) return "1/5";
|
||||
else if (Math.abs(num - 2 / 5) < epsilon) return "2/5";
|
||||
else if (Math.abs(num - 3 / 5) < epsilon) return "3/5";
|
||||
else if (Math.abs(num - 4 / 5) < epsilon) return "4/5";
|
||||
else if (Math.abs(num - 1 / 6) < epsilon) return "1/6";
|
||||
else if (Math.abs(num - 5 / 6) < epsilon) return "5/6";
|
||||
else if (Math.abs(num - 1 / 7) < epsilon) return "1/7";
|
||||
else if (Math.abs(num - 1 / 8) < epsilon) return "1/8";
|
||||
else if (Math.abs(num - 3 / 8) < epsilon) return "3/8";
|
||||
else if (Math.abs(num - 5 / 8) < epsilon) return "5/8";
|
||||
else if (Math.abs(num - 7 / 8) < epsilon) return "7/8";
|
||||
else if (Math.abs(num - 1 / 9) < epsilon) return "1/9";
|
||||
else if (Math.abs(num - 1 / 10) < epsilon) return "1/10";
|
||||
else if (Math.abs(num - 1 / 12) < epsilon) return "1/12";
|
||||
else if (Math.abs(num - 1 / 16) < epsilon) return "1/16";
|
||||
else if (Math.abs(num - 1 / 32) < epsilon)return "1/32";
|
||||
|
||||
// If no fraction matches, just return
|
||||
return +num.toFixed( 1 );
|
||||
}
|
||||
export const formatNumber = (num: number|string, useFraction:boolean=false, scale=1): string => {
|
||||
const str = String(num);
|
||||
const parts = str.match(/\d+(\.\d+)?|[a-zA-Z']+/g);
|
||||
if (!parts) return str;
|
||||
return parts.map(part => /^\d+(\.\d+)?$/.test(part) ? formatFraction(Number(part)*scale, useFraction) : part).join(" ");
|
||||
};
|
||||
1338
styles.css
1338
styles.css
File diff suppressed because it is too large
Load diff
7
test/__mocks__/obsidian.ts
Normal file
7
test/__mocks__/obsidian.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
// test/__mocks__/obsidian.ts
|
||||
export class TFile {
|
||||
path: string = "";
|
||||
name: string = "";
|
||||
}
|
||||
|
||||
export type TAbstractFile = TFile;
|
||||
|
|
@ -1,21 +1,28 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"inlineSources": true,
|
||||
"module": "ESNext",
|
||||
"target": "ES6",
|
||||
"allowJs": true,
|
||||
"noImplicitAny": true,
|
||||
"moduleResolution": "node",
|
||||
"importHelpers": true,
|
||||
"isolatedModules": true,
|
||||
"strictNullChecks": true,
|
||||
"lib": [
|
||||
"DOM",
|
||||
"ES5",
|
||||
"ES6",
|
||||
"ES7"
|
||||
]
|
||||
"verbatimModuleSyntax": true,
|
||||
"types": ["svelte"],
|
||||
"skipLibCheck": true,
|
||||
"baseUrl": ".",
|
||||
"inlineSources": true,
|
||||
"allowJs": true,
|
||||
"noImplicitAny": true,
|
||||
"moduleResolution": "node16",
|
||||
"module": "node16",
|
||||
"target": "ES2022",
|
||||
"esModuleInterop": true,
|
||||
"resolveJsonModule": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"importHelpers": true,
|
||||
"isolatedModules": true,
|
||||
"strictNullChecks": true,
|
||||
"lib": [
|
||||
"DOM",
|
||||
"ES5",
|
||||
"ES6",
|
||||
"ES7"
|
||||
],
|
||||
|
||||
},
|
||||
"extends": "@tsconfig/svelte/tsconfig.json",
|
||||
"include": [
|
||||
|
|
|
|||
|
|
@ -1,5 +1,15 @@
|
|||
{
|
||||
"0.0.1": "0.12.0",
|
||||
"0.0.2": "0.12.0",
|
||||
"0.0.3": "0.12.0"
|
||||
"0.0.3": "0.12.0",
|
||||
"0.0.4": "0.12.0",
|
||||
"0.0.5": "0.12.0",
|
||||
"0.0.6": "0.12.0",
|
||||
"0.0.7": "0.12.0",
|
||||
"0.0.8": "0.12.0",
|
||||
"0.0.9": "0.12.0",
|
||||
"0.0.10": "0.12.0",
|
||||
"0.0.11": "0.12.0",
|
||||
"0.0.12": "0.12.0",
|
||||
"0.1.0": "0.12.0"
|
||||
}
|
||||
|
|
@ -1,17 +1,38 @@
|
|||
import path from 'path';
|
||||
import { defineConfig } from 'vite';
|
||||
import { svelte } from '@sveltejs/vite-plugin-svelte';
|
||||
import autoPreprocess from 'svelte-preprocess';
|
||||
|
||||
import esbuildSvelte from 'esbuild-svelte';
|
||||
import sveltePreprocess from 'svelte-preprocess';
|
||||
import builtins from 'builtin-modules';
|
||||
import { viteStaticCopy } from 'vite-plugin-static-copy';
|
||||
import { svelte } from '@sveltejs/vite-plugin-svelte';
|
||||
|
||||
|
||||
const prod = (process.argv[2] === 'production');
|
||||
|
||||
export default defineConfig(() => {
|
||||
return {
|
||||
plugins: [
|
||||
svelte({
|
||||
preprocess: autoPreprocess()
|
||||
})
|
||||
svelte({
|
||||
preprocess: sveltePreprocess({ typescript: true,
|
||||
}),
|
||||
}),
|
||||
esbuildSvelte({
|
||||
compilerOptions: { css: 'injected' },
|
||||
}),
|
||||
viteStaticCopy({
|
||||
targets: [
|
||||
{
|
||||
src: 'build/main.js',
|
||||
dest: '..',
|
||||
},
|
||||
{
|
||||
src: 'build/styles.css',
|
||||
dest: '..',
|
||||
}
|
||||
],
|
||||
watch: false,
|
||||
}),
|
||||
],
|
||||
watch: !prod,
|
||||
build: {
|
||||
|
|
@ -22,7 +43,7 @@ export default defineConfig(() => {
|
|||
ignoreTryCatch: false,
|
||||
},
|
||||
lib: {
|
||||
entry: path.resolve(__dirname, './src/starterIndex.ts'),
|
||||
entry: path.resolve(__dirname, './src/starterIndex.svelte.ts'),
|
||||
formats: ['cjs'],
|
||||
},
|
||||
css: {},
|
||||
|
|
@ -32,6 +53,7 @@ export default defineConfig(() => {
|
|||
entryFileNames: 'main.js',
|
||||
assetFileNames: 'styles.css',
|
||||
},
|
||||
|
||||
external: ['obsidian',
|
||||
'electron',
|
||||
"codemirror",
|
||||
|
|
@ -63,8 +85,8 @@ export default defineConfig(() => {
|
|||
],
|
||||
},
|
||||
// Use root as the output dir
|
||||
emptyOutDir: false,
|
||||
outDir: '.',
|
||||
outDir: './build',
|
||||
emptyOutDir: false,
|
||||
},
|
||||
}
|
||||
});
|
||||
14
vitest.config.ts
Normal file
14
vitest.config.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import { defineConfig } from 'vitest/config';
|
||||
import path from 'path';
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
environment: 'node',
|
||||
globals: true,
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
obsidian: path.resolve(__dirname, 'test/__mocks__/obsidian.ts'),
|
||||
},
|
||||
},
|
||||
});
|
||||
Loading…
Reference in a new issue