Update memo layout and library import examples
All checks were successful
Deploy Sokko G / deploy (push) Successful in 16s

This commit is contained in:
Shinuwa 2026-08-25 19:17:19 +02:00
parent 6125336338
commit 91d99d6ca4
6 changed files with 86 additions and 28 deletions

View file

@ -307,6 +307,7 @@ Regles UI :
- cartes compactes, non imbriquees, avec accent couleur localise ; - cartes compactes, non imbriquees, avec accent couleur localise ;
- edition directe du texte court ; - edition directe du texte court ;
- zone de texte en hauteur automatique, sans resize manuel ni scrollbar interne ;
- palette de couleurs compacte reprenant les swatches du bloc note ; - palette de couleurs compacte reprenant les swatches du bloc note ;
- la creation de memo est masquee par defaut et s'ouvre via le bouton `+` du header d'outil ; - la creation de memo est masquee par defaut et s'ouvre via le bouton `+` du header d'outil ;
- réorganisation des cartes par poignée de drag & drop compacte ; - réorganisation des cartes par poignée de drag & drop compacte ;

View file

@ -118,7 +118,7 @@
], ],
"importFormat": { "importFormat": {
"text": "Chaque ligne crée un mémo. Le suffixe color=#f6c453 conserve la couleur de la carte.", "text": "Chaque ligne crée un mémo. Le suffixe color=#f6c453 conserve la couleur de la carte.",
"example": "- Sauvegarder avant le boss | color=#f6c453\n- Acheter des potions | color=#22d3ee" "example": "- Pour la reproduction, combine Pierre Stase + Nœud Destin pour transmettre nature et IV. | color=#f6c453\n- Les IV obtenus par Hyper Training ne sont pas transmis aux oeufs. | color=#8b5cf6\n- Pour une équipe Distorsion, vise 0 IV en Vitesse sur les Pokémon lents. | color=#22d3ee\n- Utilise les objets Pouvoir pour cibler précisément les EV à entraîner. | color=#d946ef"
} }
}, },
"notepad": { "notepad": {
@ -262,7 +262,7 @@
], ],
"importFormat": { "importFormat": {
"text": "Le Bloc Note utilise des pages avec blocs HTML nettoyés, puis une ligne dessins en JSON compact si nécessaire.", "text": "Le Bloc Note utilise des pages avec blocs HTML nettoyés, puis une ligne dessins en JSON compact si nécessaire.",
"example": "@notepad\n@page: Page 1\n@html\n<h3>Obtenir Mew :</h3><div><ul><li>Obtenir la CS <span style=\"color: #f6c453\">Coupe </span>à l'intérieur du bateau le <span style=\"color: #d946ef\">S.S. Anne</span>.</li><li>Faire exprès de perdre un combat pour réapparaître au Centre Pokémon sans faire partir le navire.</li><li>Revenir sur les quais de <span style=\"color: #d946ef\">Carmin-sur-Mer</span> après avoir appris <span style=\"color: #f6c453\">Surf</span>.</li></ul></div>\n@endhtml\n@drawingMode: permanent\n@drawings: {\"strokes\":[...]}" "example": "@notepad\n@page: Infos: Obtenir Mew\n@html\n<h3><span style=\"color: #f5f7ff\">Obtenir Mew :</span></h3><div><ul><li>Obtenir la CS <span style=\"color: #f6c453\">Coupe </span>à l'intérieur du bateau le <span style=\"color: #d946ef\">S.S. Anne</span>.</li><li>Faire exprès de perdre un combat (tomber K.O.) pour réapparaître au Centre Pokémon sans faire partir le navire.</li><li>Revenir sur les quais de <span style=\"color: #d946ef\">Carmin-sur-Mer</span> après avoir débloqué et appris la compétence <span style=\"color: #f6c453\">Surf</span>.</li><li>Naviguer sur l'eau pour atteindre la zone cachée où repose le véhicule.</li></ul><div><br></div></div><p></p>\n@endhtml\n@drawingMode: permanent\n@drawings: {\"strokes\":[...]}\n@page: Infos: Obtenir Celebi\n@html\n<h3><span style=\"color: #f5f7ff\">Obtenir Celebi :</span></h3><p></p><ul><li>Avoir battu la <span style=\"color: #d946ef\">Ligue Pokémon</span> au moins une fois et posséder le Pokédex National.</li><li>Mettre un Pokémon de type Plante (comme <span style=\"color: #22d3ee\">Florizarre</span> ou <span style=\"color: #22d3ee\">Meganium</span>) au niveau 100 en première position de votre équipe.</li><li>Avoir un Pokémon connaissant la CS <span style=\"color: #f6c453\">Flash</span> et un autre connaissant la CS <span style=\"color: #f6c453\">Coupe</span>.</li><li>Se rendre devant l'autel du <span style=\"color: #d946ef\">Bois aux Chênes</span> la nuit (le jeu doit être dans sa phase nocturne).</li><li>Utiliser la capacité <span style=\"color: #f6c453\">Flash</span> juste devant l'autel pour éclairer l'écran.</li><li>Appuyer sur A face à l'autel</li></ul><div><span style=\"color: #b4bdd3\"><br></span></div><div><span style=\"color: #b4bdd3\"><br></span></div>\n@endhtml\n@drawingMode: permanent\n@drawings: {\"strokes\":[...]}"
} }
}, },
"checklist": { "checklist": {
@ -618,7 +618,7 @@
], ],
"importFormat": { "importFormat": {
"text": "Le format contient l'image en base64, puis les marqueurs et les dessins permanents.", "text": "Le format contient l'image en base64, puis les marqueurs et les dessins permanents.",
"example": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABA...\n@marker: 63.85,49.46,Niv. 1-20\n@marker: 47.65,31.93,Niv. 10-20\n@marker: 55.92,41.03,Niv. 20-30\n@drawings: {\"strokes\":[...]}" "example": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABA...\n@marker: 63.849429339290076,49.45757170456234,Niv. 1-20\n@marker: 47.654532128023455,31.930025688385026,Niv. 10-20\n@marker: 55.91951415308366,41.03347982081821,Niv. 20-30\n@marker: 68.87543192209696,28.397341995202,Niv. 30-40\n@marker: 54.6909357439531,19.97325011145787,Niv. 30-50\n@marker: 40.95319535094761,50.136933953251386,Niv. 30-50\n@marker: 42.51684059893198,25.136403201494595,Niv. 50-60\n@marker: 32.46483543331821,70.51780141392268,Niv. 60-70\n@marker: 46.425953718892885,80.70823514425832,Niv. 60-70\n@marker: 36.59732644584831,9.91868883086003,Niv. 70-80\n@drawings: {\"strokes\":[...]}"
} }
}, },
"links": { "links": {
@ -689,6 +689,16 @@
"icon": "copy" "icon": "copy"
} }
}, },
{
"group": "Formule",
"icon": "parent",
"title": "Parent",
"text": "Insère la valeur du parent dans la formule.",
"preview": {
"kind": "iconButton",
"icon": "parent"
}
},
{ {
"group": "Contrôles", "group": "Contrôles",
"icon": "edit", "icon": "edit",
@ -740,7 +750,7 @@
], ],
"importFormat": { "importFormat": {
"text": "Une ligne par résultat. L'indentation de deux espaces conserve les liens parent/enfant.", "text": "Une ligne par résultat. L'indentation de deux espaces conserve les liens parent/enfant.",
"example": "Lingot de champignite: 100\n Lingot de chlorophyte: 100\n Champignon luisant: 1500\nLingot de chlorophyte: 100\n Minerai de chlorophyte: 500" "example": "Lingot de champignite: 100\n Lingot de chlorophyte: 100 | formula=base\n Champignon luisant: 1500 | formula=base*15\nLingot de chlorophyte: 100\n Minerai de chlorophyte: 500 | formula=base*5"
} }
}, },
"goalTree": { "goalTree": {
@ -773,6 +783,16 @@
"icon": "edit" "icon": "edit"
} }
}, },
{
"group": "Formule",
"icon": "parent",
"title": "Parent",
"text": "Insère la valeur du parent dans une quantité ou formule.",
"preview": {
"kind": "iconButton",
"icon": "parent"
}
},
{ {
"group": "Objectifs", "group": "Objectifs",
"icon": "trash", "icon": "trash",
@ -815,7 +835,7 @@
], ],
"importFormat": { "importFormat": {
"text": "Une ligne par nœud avec indentation de deux espaces. Utilisez xN pour une quantité simple ou = base * N pour une formule.", "text": "Une ligne par nœud avec indentation de deux espaces. Utilisez xN pour une quantité simple ou = base * N pour une formule.",
"example": "- Breuvage de rage x10\n - Élixir de destruction = base * 3\n - Poudre de flamme x5\n - Sève de cèdre x7\n - Catalyseur magique x10" "example": "@lastResetAt: 1787263200000\n- Potion d'invincibilité\n - Potion de vie\n - Ingredients | kind=checklist | items=%5B%7B%22id%22%3A%22xn2okr5%22%2C%22label%22%3A%22Herbe%20magique%22%2C%22formula%22%3A%22base%22%2C%22qtyTarget%22%3A10%2C%22qtyCurrent%22%3A7%7D%2C%7B%22id%22%3A%22xm5h5uu%22%2C%22label%22%3A%22Eau%20rajeunissante%22%2C%22formula%22%3A%22base%22%2C%22qtyTarget%22%3A10%2C%22qtyCurrent%22%3A5%7D%5D\n - Potion de résistance\n - Ingredients supp. | kind=checklist | items=%5B%7B%22id%22%3A%22x3mlygm%22%2C%22label%22%3A%22Poudre%20magique%22%2C%22formula%22%3A%22base%22%2C%22qtyTarget%22%3A10%7D%2C%7B%22id%22%3A%22x1dqx6w%22%2C%22label%22%3A%22Herbe%20magique%22%2C%22formula%22%3A%22base*2%22%2C%22qtyTarget%22%3A20%7D%5D\n- Parler a l'alchimiste | collapsed=true\n - Acheter une station d'alchimie | collapsed=true\n - Apprendre la recette de potion d'invicibilité | collapsed=true\n - S'inscrire à l'academie d'alchimie | collapsed=true"
} }
}, },
"table": { "table": {
@ -1026,7 +1046,7 @@
], ],
"importFormat": { "importFormat": {
"text": "Les étapes et comptes à rebours sont exportés en lignes lisibles. Les échéances sont recalculées à l'import.", "text": "Les étapes et comptes à rebours sont exportés en lignes lisibles. Les échéances sont recalculées à l'import.",
"example": "@activeTab: countdown\nlap | label=Fosse 110 | elapsedMs=1046475\nlap | label=Fosse 55 | elapsedMs=50019\ncountdown | label=Fin de vague infernale | type=time_pattern | alertMode=off | pattern=X:55:X\ncountdown | label=Boss Mondial | type=daily_time | alertMode=off | time=14:00:00\ncountdown | label=Legion | type=interval | alertMode=off | autoRefresh=true | intervalMs=1800000 | startMode=now\ncountdown | label=Durée | type=duration | alertMode=off | durationMs=1800000" "example": "@activeTab: countdown\nlap | label=Fosse 110 | elapsedMs=1046475\nlap | label=Fosse 55 | elapsedMs=50019\ncountdown | label=Fin de vague infernale | type=time_pattern | pattern=X:55:X\ncountdown | label=Boss Mondial | type=daily_time | time=14:00:00\ncountdown | label=Legion | type=interval | autoRefresh=true | intervalMs=1800000 | startMode=now\ncountdown | label=Durée | type=duration | durationMs=1800000"
} }
}, },
"calendar": { "calendar": {
@ -1193,7 +1213,7 @@
], ],
"importFormat": { "importFormat": {
"text": "Les catégories commencent par #. Les tâches utilisent leur type et l'indentation de deux espaces crée les sous-tâches. Les états cochés ne sont pas repris.", "text": "Les catégories commencent par #. Les tâches utilisent leur type et l'indentation de deux espaces crée les sous-tâches. Les états cochés ne sont pas repris.",
"example": "@hideCompleted: true\n# To craft\n- unique Essence de liqueur\n- unique Bière\n- unique Vinaigre\n- weekly Faire les boss du Sanctuaire Noir (Groupe)\n @weeklyResetDay: 0\n- weekly Faire les boss du Sanctuaire Noir (Solo)\n @weeklyResetDay: 0\n- unique Ameliorer le bateau\n - daily Faire du troc\n @prerequisite: true" "example": "@hideCompleted: true\n# To craft\n- unique Essence de liqueur\n- unique Bière\n- unique Vinaigre\n- weekly Faire les boss du Sanctuaire Noir (Groupe)\n @weeklyResetDay: 0\n- weekly Faire les boss du Sanctuaire Noir (Solo)\n @weeklyResetDay: 0\n- weekly Faire la fosse aux immortels\n- unique Ameliorer le bateau\n - daily Faire du troc\n @prerequisite: true"
} }
}, },
"combos": { "combos": {
@ -1375,7 +1395,7 @@
], ],
"importFormat": { "importFormat": {
"text": "Le format est découpé par type, équipement et sous-sections Stats, Sockets et Craft.", "text": "Le format est découpé par type, équipement et sous-sections Stats, Sockets et Craft.",
"example": "# Casque | icon=helmet\n## Nu Udra γ | icon=helmet | active=true\nStats\n | shield | Défense | 68\n | shield | Resistance au feu | 5\n | shield | Resistance a l'eau | -4\nSockets\nJoyau antivirus | shape=jewel | color=violet\n bonus | | sword | Antivirus | 1\nCraft\n@obtain: Avoir un certificat Alpha supreme\n## Nu Udra γ | icon=chest-armor | active=true\nStats\n | shield | Défense | 68\nSockets\nJoyau Attaque | shape=jewel | color=red\n bonus | | sword | Attaque | 2" "example": "# Casque | icon=helmet\n## Nu Udra γ | icon=helmet | active=true\nStats\n | shield | Défense | 68\n | shield | Resistance au feu | 5\n | shield | Resistance a l'eau | -4\n | shield | Resistance à la foudre | 0\n | shield | Resistance à la glace | 1\n | shield | Resistance au dragon | 0\nSockets\nJoyau antivirus | shape=jewel | color=violet\n bonus | | sword | Antivirus | 1\nJoyau antivirus | shape=jewel | color=violet\n bonus | | sword | Antivirus | 1\nJoyau antivirus | shape=jewel | color=violet\n bonus | | sword | Antivirus | 1\nCraft\n@obtain: Avoir un certificat Alpha supreme\n## Nu Udra γ | icon=chest-armor | active=true\nStats\n | shield | Défense | 68\n | shield | Resistance au feu | 5\n | shield | Resistance a l'eau | -4\n | shield | Resistance à la foudre | 0\n | shield | Resistance à la glace | 1\n | shield | Resistance au dragon | 0\nSockets\nJoyau Mise a mort | shape=jewel | color=indigo\n bonus | | sword | Mise a mort | 1\nJoyau Attaque | shape=jewel | color=red\n bonus | | sword | Attaque | 2"
} }
} }
}, },
@ -1568,28 +1588,34 @@
"toolsLibraryLink": "Voir la librairie d'outils", "toolsLibraryLink": "Voir la librairie d'outils",
"toolGroups": [ "toolGroups": [
{ {
"icon": "notepad", "icon": "memos",
"title": "Notes et suivi", "title": "Notes et suivi",
"text": "Écrire des rappels, suivre des objectifs, organiser une routine ou préparer une liste de choses à faire.", "text": "Écrire des rappels courts, garder un bloc note multi-pages, suivre des objectifs simples ou compter des ressources.",
"examples": ["Notes", "Checklists", "Tâches"] "examples": ["Memos", "Bloc Note", "Checklist", "Compteurs"]
}, },
{ {
"icon": "picture", "icon": "picture",
"title": "Médias et repères visuels", "title": "Médias et repères visuels",
"text": "Conserver des captures, cartes, images de référence ou annotations visibles pendant la session.", "text": "Conserver des captures, cartes, images annotées ou liens utiles visibles pendant la session.",
"examples": ["Images", "Annotations", "Cartes"] "examples": ["Images", "Annotation d'images", "Liens"]
}, },
{ {
"icon": "table", "icon": "table",
"title": "Données et calculs", "title": "Données et calculs",
"text": "Comparer des valeurs, compter des ressources, préparer des tableaux ou calculer des besoins de craft.", "text": "Comparer des valeurs, préparer des tableaux, calculer des besoins de craft ou structurer des objectifs.",
"examples": ["Compteurs", "Tableaux", "Calculs"] "examples": ["Calculateur", "Tableau", "Arbre d'objectifs"]
}, },
{ {
"icon": "clock", "icon": "clock",
"title": "Temps et actions", "title": "Temps et actions",
"text": "Gérer des timers, routines, rotations, commandes ou séquences à reproduire sans perdre le fil.", "text": "Gérer des timers, calendriers, routines et tâches récurrentes sans perdre le fil.",
"examples": ["Timers", "Combos", "Routines"] "examples": ["Timer", "Calendrier", "Planificateur de tâches"]
},
{
"icon": "controller",
"title": "Builds et commandes",
"text": "Documenter des builds, organiser des équipements et conserver des séquences de commandes.",
"examples": ["Combos", "Planificateur d'équipements"]
} }
], ],
"contribute": { "contribute": {
@ -1785,7 +1811,7 @@
"tickerLabel": "Mémos", "tickerLabel": "Mémos",
"hideTickerTitle": "Masquer la bande", "hideTickerTitle": "Masquer la bande",
"showTickerTitle": "Afficher la bande", "showTickerTitle": "Afficher la bande",
"importPlaceholder": "- Mémo prioritaire | color=#f6c453\n- Mémo simple | color=#22d3ee", "importPlaceholder": "- Pour la reproduction, combine Pierre Stase + Nœud Destin pour transmettre nature et IV. | color=#f6c453\n- Les IV obtenus par Hyper Training ne sont pas transmis aux oeufs. | color=#8b5cf6\n- Pour une équipe Distorsion, vise 0 IV en Vitesse sur les Pokémon lents. | color=#22d3ee\n- Utilise les objets Pouvoir pour cibler précisément les EV à entraîner. | color=#d946ef",
"importOpenButton": "Importer du texte", "importOpenButton": "Importer du texte",
"exportOpenButton": "Exporter en format texte", "exportOpenButton": "Exporter en format texte",
"importModalTitle": "Importer des mémos", "importModalTitle": "Importer des mémos",
@ -2040,7 +2066,7 @@
"itemCheckedLabel": "État", "itemCheckedLabel": "État",
"itemTotalLabel": "Total", "itemTotalLabel": "Total",
"itemTotalPlaceholder": "base", "itemTotalPlaceholder": "base",
"itemTotalHelp": "Saisissez un nombre ou une formule avec base pour référencer la cible du parent.", "itemTotalHelp": "Saisissez un nombre ou une formule avec 'base' pour référencer la cible du parent.",
"prerequisitesSectionTitle": "Prérequis supplémentaires", "prerequisitesSectionTitle": "Prérequis supplémentaires",
"contentTabHint": "Le contenu se modifie dans l'onglet Contenu.", "contentTabHint": "Le contenu se modifie dans l'onglet Contenu.",
"stepContentUnavailable": "Les étapes n'ont pas de contenu interne.", "stepContentUnavailable": "Les étapes n'ont pas de contenu interne.",

View file

@ -2,7 +2,7 @@
import { CompactDropdown } from "../../../components/CompactDropdown.jsx"; import { CompactDropdown } from "../../../components/CompactDropdown.jsx";
import { Icon } from "../../../components/Icon.jsx"; import { Icon } from "../../../components/Icon.jsx";
import { moveItem, useGroupedReorder } from "../../../hooks/useGroupedReorder.js"; import { moveItem, useGroupedReorder } from "../../../hooks/useGroupedReorder.js";
import { useEffect, useMemo, useState } from "react"; import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
import { TextExchangeActions } from "./TextExchangeActions.jsx"; import { TextExchangeActions } from "./TextExchangeActions.jsx";
import { TEXT_COLORS } from "./notepadUtils.js"; import { TEXT_COLORS } from "./notepadUtils.js";
@ -11,6 +11,7 @@ export function MemosModule({ toolboxId, moduleId, context, editing, textExchang
const data = useMemo(() => context.normalizeMemosData(storedData), [context, storedData]); const data = useMemo(() => context.normalizeMemosData(storedData), [context, storedData]);
const textContent = context.moduleText?.memos || {}; const textContent = context.moduleText?.memos || {};
const tickerItems = data.tickerEnabled ? data.items : []; const tickerItems = data.tickerEnabled ? data.items : [];
const textareaRefs = useRef(new Map());
const [drafts, setDrafts] = useState({}); const [drafts, setDrafts] = useState({});
const { const {
itemReorder, itemReorder,
@ -32,6 +33,16 @@ export function MemosModule({ toolboxId, moduleId, context, editing, textExchang
setDrafts(Object.fromEntries(data.items.map((item) => [item.id, item.text]))); setDrafts(Object.fromEntries(data.items.map((item) => [item.id, item.text])));
}, [data.items]); }, [data.items]);
useLayoutEffect(() => {
data.items.forEach((item) => resizeMemoTextarea(textareaRefs.current.get(item.id)));
}, [data.items, drafts]);
function resizeMemoTextarea(element) {
if (!element) return;
element.style.height = "auto";
element.style.height = `${element.scrollHeight}px`;
}
function save(nextData) { function save(nextData) {
context.setModuleData(toolboxId, moduleId, nextData, "memos"); context.setModuleData(toolboxId, moduleId, nextData, "memos");
} }
@ -133,10 +144,21 @@ export function MemosModule({ toolboxId, moduleId, context, editing, textExchang
<Icon name="drag" /> <Icon name="drag" />
</button> </button>
<textarea <textarea
ref={(element) => {
if (element) {
textareaRefs.current.set(memo.id, element);
resizeMemoTextarea(element);
} else {
textareaRefs.current.delete(memo.id);
}
}}
value={drafts[memo.id] ?? memo.text} value={drafts[memo.id] ?? memo.text}
rows={3} rows={3}
placeholder={textContent.placeholder || "Mémo court..."} placeholder={textContent.placeholder || "Mémo court..."}
onChange={(event) => setDrafts((current) => ({ ...current, [memo.id]: event.target.value }))} onChange={(event) => {
resizeMemoTextarea(event.currentTarget);
setDrafts((current) => ({ ...current, [memo.id]: event.target.value }));
}}
onBlur={() => commitMemoText(memo.id)} onBlur={() => commitMemoText(memo.id)}
aria-label={textContent.editLabel || "Modifier le mémo"} aria-label={textContent.editLabel || "Modifier le mémo"}
/> />

View file

@ -1,5 +1,6 @@
// Rôle : gère l'import/export texte des outils simples et tabulaires. // Rôle : gère l'import/export texte des outils simples et tabulaires.
import { cellAddress, columnIndexToName } from "../tableFormulaEngine.js"; import { cellAddress, columnIndexToName } from "../tableFormulaEngine.js";
import { evaluateCalculatorEntries } from "../calculatorUtils.js";
import { cloneDrawingData, lineError, parseBoolean, parseColonImportLines, parseInteger, parseMetaLine, parseNumber, splitLines, textToNotepadExchangeHtml } from "./shared.js"; import { cloneDrawingData, lineError, parseBoolean, parseColonImportLines, parseInteger, parseMetaLine, parseNumber, splitLines, textToNotepadExchangeHtml } from "./shared.js";
export function exportNotepadText(data) { export function exportNotepadText(data) {
@ -216,9 +217,10 @@ export function importCountersText(text, context) {
export function exportCalculatorText(data) { export function exportCalculatorText(data) {
const lines = []; const lines = [];
const entries = evaluateCalculatorEntries(data.entries || []);
if (data.scrollResults) lines.push("@scrollResults: true"); if (data.scrollResults) lines.push("@scrollResults: true");
function visit(parentId = "", depth = 0) { function visit(parentId = "", depth = 0) {
(data.entries || []).filter((entry) => (entry.parentId || "") === parentId).forEach((entry) => { entries.filter((entry) => (entry.parentId || "") === parentId).forEach((entry) => {
const formula = String(entry.formula || "").trim(); const formula = String(entry.formula || "").trim();
lines.push(`${" ".repeat(depth)}${entry.label || ""}: ${entry.value}${formula ? ` | formula=${formula}` : ""}`); lines.push(`${" ".repeat(depth)}${entry.label || ""}: ${entry.value}${formula ? ` | formula=${formula}` : ""}`);
visit(entry.id, depth + 1); visit(entry.id, depth + 1);

View file

@ -3,7 +3,7 @@ import { formatMeta, lineError, parseBoolean, parseInteger, parseMetaLine, parse
export function exportTaskPlannerText(data) { export function exportTaskPlannerText(data) {
const lines = []; const lines = [];
if (data.weeklyResetDay !== 1) lines.push(`@weeklyResetDay: ${data.weeklyResetDay}`); if (Number.isFinite(data.weeklyResetDay) && data.weeklyResetDay !== 1) lines.push(`@weeklyResetDay: ${data.weeklyResetDay}`);
if (data.resetTime && data.resetTime !== "00:00") lines.push(`@resetTime: ${data.resetTime}`); if (data.resetTime && data.resetTime !== "00:00") lines.push(`@resetTime: ${data.resetTime}`);
if (data.hideCompleted) lines.push("@hideCompleted: true"); if (data.hideCompleted) lines.push("@hideCompleted: true");
if (data.collapsedCategories?.length) lines.push(`@collapsed: ${data.collapsedCategories.join(", ")}`); if (data.collapsedCategories?.length) lines.push(`@collapsed: ${data.collapsedCategories.join(", ")}`);
@ -169,4 +169,3 @@ export function parseGoalTreeItemsMeta(value) {
return []; return [];
} }
} }

View file

@ -79,7 +79,7 @@
display: grid; display: grid;
gap: var(--space-2); gap: var(--space-2);
min-height: 132px; min-height: 132px;
padding: var(--space-3) var(--space-3) var(--space-3) 52px; padding: var(--space-3);
border: 1px solid color-mix(in srgb, var(--memo-color) 42%, transparent); border: 1px solid color-mix(in srgb, var(--memo-color) 42%, transparent);
border-radius: var(--radius-md); border-radius: var(--radius-md);
background: background:
@ -94,7 +94,8 @@
.memo-card textarea { .memo-card textarea {
width: 100%; width: 100%;
min-height: 76px; min-height: 76px;
resize: vertical; overflow: hidden;
resize: none;
border: 0; border: 0;
background: transparent; background: transparent;
color: var(--color-text-primary); color: var(--color-text-primary);
@ -109,8 +110,8 @@
.memo-drag-handle { .memo-drag-handle {
position: absolute; position: absolute;
top: var(--space-3); top: 8px;
left: var(--space-3); right: 8px;
display: inline-grid; display: inline-grid;
width: 34px; width: 34px;
min-width: 34px; min-width: 34px;
@ -119,8 +120,15 @@
padding: 0; padding: 0;
color: var(--color-text-muted); color: var(--color-text-muted);
cursor: grab; cursor: grab;
opacity: 0.72;
touch-action: none; touch-action: none;
user-select: none; user-select: none;
z-index: 2;
}
.memo-card:hover .memo-drag-handle,
.memo-card:focus-within .memo-drag-handle {
opacity: 1;
} }
.memo-drag-handle:active { .memo-drag-handle:active {