@studiotitta/studio-editori 0.1.4 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/maarittely.d.ts +16 -0
- package/dist/palvelin/kuvat.js +10 -1
- package/dist/selain/Muokkaustila.d.ts +1 -1
- package/dist/selain/Muokkaustila.js +51 -9
- package/dist/selain/Paneeli.d.ts +1 -0
- package/dist/selain/Paneeli.js +76 -21
- package/package.json +1 -1
package/dist/maarittely.d.ts
CHANGED
|
@@ -102,6 +102,14 @@ export interface TekstilistaKentta<K extends boolean = boolean> extends KentanPe
|
|
|
102
102
|
/** Monirivinen kohta listassa. */
|
|
103
103
|
monirivinen?: boolean;
|
|
104
104
|
enintaan?: number;
|
|
105
|
+
/**
|
|
106
|
+
* Miten lista näkyy sivulla yhtenä tekstinä, esimerkiksi ", ".
|
|
107
|
+
*
|
|
108
|
+
* Kun sivusto näyttää listan pilkuilla eroteltuna ("Lounas, Materiaalit"),
|
|
109
|
+
* sen voi silti muokata paikallaan: editori jakaa tekstin takaisin listaksi
|
|
110
|
+
* tällä erottimella. Ilman tätä jokainen rivi on oma kohtansa.
|
|
111
|
+
*/
|
|
112
|
+
erotin?: string;
|
|
105
113
|
}
|
|
106
114
|
/**
|
|
107
115
|
* Kenttäryhmä, esimerkiksi hakukonetiedot.
|
|
@@ -134,6 +142,14 @@ export interface KohdelistaKentta extends KentanPerus {
|
|
|
134
142
|
* puhuttaisiin kohteista, mikä ei ole kenenkään sana omasta työstään.
|
|
135
143
|
*/
|
|
136
144
|
yksikko?: string;
|
|
145
|
+
/**
|
|
146
|
+
* Kohteen oma osoite sivustolla, esimerkiksi "/kurssit/{tunnus}".
|
|
147
|
+
*
|
|
148
|
+
* Editori näyttää linkin, jolla kohteen sivun pääsee katsomaan. Uuden
|
|
149
|
+
* kohteen sivu syntyy vasta tallennuksen jälkeen, joten linkki kertoo
|
|
150
|
+
* myös, mistä työn tuloksen näkee.
|
|
151
|
+
*/
|
|
152
|
+
osoite?: string;
|
|
137
153
|
kentat: Record<string, Kentta>;
|
|
138
154
|
}
|
|
139
155
|
export type Kentta = TekstiKentta | TekstialueKentta | LukuKentta | ValintaKentta | KytkinKentta | AikaKentta | KuvaKentta | KielivalintaKentta | TekstilistaKentta | RyhmaKentta | KohdelistaKentta;
|
package/dist/palvelin/kuvat.js
CHANGED
|
@@ -125,6 +125,15 @@ export function kuvienReitti(yliajot) {
|
|
|
125
125
|
console.error("[editori] Kuvan tallennus epäonnistui:", virhe);
|
|
126
126
|
return json({ virhe: "Kuvaa ei saatu tallennettua." }, 502);
|
|
127
127
|
}
|
|
128
|
-
|
|
128
|
+
/*
|
|
129
|
+
Esikatselu palautetaan kuvana, ei osoitteena: tiedosto on repossa, mutta
|
|
130
|
+
sivusto tarjoilee sen vasta seuraavan julkaisun jälkeen. Ilman tätä
|
|
131
|
+
muokkaaja näkee rikkinäisen kuvan eikä tiedä, menikö lataus läpi.
|
|
132
|
+
*/
|
|
133
|
+
return json({
|
|
134
|
+
src: kuvanOsoite(polku),
|
|
135
|
+
esikatselu: `data:image/${tunnistaTyyppi(valmis) ?? "jpeg"};base64,${Buffer.from(valmis).toString("base64")}`,
|
|
136
|
+
ehdotus: await ehdotus,
|
|
137
|
+
});
|
|
129
138
|
};
|
|
130
139
|
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
declare const PALKIN_TYYLIT = "\n :host { all: initial; }\n .palkki {\n position: fixed; inset: auto 1rem 1rem auto; z-index: 2147483000;\n display: flex; align-items: center; gap: .75rem;\n padding: .6rem .75rem .6rem 1rem; border-radius: .5rem;\n background: #1d1f1e; color: #fff;\n font: 500 .875rem/1.4 -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif;\n box-shadow: 0 .5rem 1.5rem rgba(0,0,0,.25);\n max-width: calc(100vw - 2rem);\n }\n .tila { opacity: .75; font-weight: 400; }\n .tila[data-virhe=\"1\"] { color: #ffb4a2; opacity: 1; }\n button {\n font: inherit; padding: .45rem .8rem; border-radius: .35rem;\n border: 1px solid rgba(255,255,255,.25); background: transparent; color: inherit;\n cursor: pointer;\n }\n button.ensisijainen { background: #fff; color: #1d1f1e; border-color: #fff; }\n button:disabled { opacity: .5; cursor: default; }\n button:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }\n\n /* Sivupaneeli */\n .paneeli {\n position: fixed; inset: 0 0 0 auto; z-index: 2147482999;\n width: min(23rem, 100vw); overflow-y: auto;\n padding: 1.25rem 1.25rem 6rem;\n background: #fbfaf8; color: #1d1f1e;\n border-left: 1px solid rgba(0,0,0,.12);\n font: 400 .875rem/1.5 -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif;\n box-shadow: -.5rem 0 1.5rem rgba(0,0,0,.08);\n }\n .paneeli h2 { margin: 0 0 .75rem; font-size: .8125rem; text-transform: uppercase; letter-spacing: .06em; opacity: .6; }\n .paneeli section + section { margin-top: 1.5rem; padding-top: 1.25rem; border-top: 1px solid rgba(0,0,0,.1); }\n .kentta { margin-bottom: 1rem; }\n .kentta label { display: flex; gap: .5rem; align-items: baseline; margin-bottom: .25rem; font-weight: 600; }\n .kieli { font-weight: 400; font-size: .75rem; padding: 0 .3rem; border-radius: .2rem; background: rgba(0,0,0,.08); text-transform: uppercase; }\n .kentta input[type=text], .kentta input[type=number], .kentta input[type=date],\n .kentta input[type=datetime-local], .kentta select, .kentta textarea {\n width: 100%; padding: .45rem .55rem; border: 1px solid rgba(0,0,0,.25);\n border-radius: .25rem; background: #fff; font: inherit; color: inherit;\n }\n .kentta textarea { resize: vertical; }\n .kytkin { display: flex; gap: .5rem; align-items: center; font-weight: 400; }\n .kielivalinta { display: grid; gap: .35rem; }\n .kaannos button { color: inherit; border-color: rgba(0,0,0,.25); }\n .ohje { margin: .25rem 0 0; font-size: .8125rem; opacity: .65; }\n .laskuri { margin: .3rem 0 0; font-size: .8125rem; font-weight: 600; }\n .virheet { padding: .75rem .9rem; border-radius: .3rem; background: #f6e8e3; color: #6d2b20; }\n .virheet h2 { margin: 0 0 .4rem; opacity: 1; color: inherit; }\n .virheet ul { margin: 0; padding-left: 1.1rem; display: grid; gap: .25rem; }\n .kuva img { display: block; width: 100%; height: auto; border-radius: .25rem; margin-bottom: .4rem; }\n .kuva input[type=text] { width: 100%; padding: .45rem .55rem; border: 1px solid rgba(0,0,0,.25); border-radius: .25rem; font: inherit; }\n /* Kuvan tekstikentt\u00E4 on oma lohkonsa: label on .kentta-tasolla flex,\n ja ilman t\u00E4t\u00E4 otsikko ja kentt\u00E4 asettuisivat vierekk\u00E4in. */\n .kentta label.alt, .kuva label.alt { display: block; font-weight: 600; margin-top: .5rem; }\n .alt input { margin-top: .25rem; width: 100%; }\n .huomio {\n margin: .4rem 0 0; padding: .5rem .6rem; border-radius: .25rem;\n background: #fdf3e7; color: #6b4a1f; font-size: .8125rem;\n }\n .huomio p { margin: 0 0 .4rem; }\n .huomio button { color: inherit; border-color: rgba(107,74,31,.35); padding: .3rem .6rem; }\n .lataus { display: inline-flex; margin: 0 0 .5rem; }\n .lataus span {\n padding: .45rem .8rem; border: 1px solid rgba(0,0,0,.25); border-radius: .35rem;\n cursor: pointer; font-weight: 600;\n }\n .lataus input[type=file] { position: absolute; width: 1px; height: 1px; opacity: 0; }\n .virheviesti { margin: 0 0 .5rem; color: #8c3b2e; font-size: .8125rem; }\n .tyhja { margin: 0; opacity: .6; }\n .kohteet { list-style: none; margin: 0 0 .75rem; padding: 0; display: grid; gap: .25rem; }\n .kohteet button {\n width: 100%; text-align: left; color: inherit; border-color: rgba(0,0,0,.15);\n display: flex; justify-content: space-between; gap: .5rem; align-items: center;\n }\n .kohteet button.valittu { background: #1d1f1e; color: #fff; border-color: #1d1f1e; }\n .merkki { font-size: .75rem; opacity: .7; white-space: nowrap; }\n /* Taitos n\u00E4ytt\u00E4\u00E4 painikkeelta ja nuoli osoittaa sinne, minne sis\u00E4lt\u00F6\n aukeaa: alas kiinni ollessa, yl\u00F6s auki ollessa. */\n .ryhma { margin: 0 0 1rem; padding: .75rem .8rem; border-radius: .3rem; background: rgba(0,0,0,.035); }\n .ryhma h3 { margin: 0 0 .5rem; font-size: .8125rem; text-transform: uppercase; letter-spacing: .05em; opacity: .65; }\n .ryhma .kentta:last-child { margin-bottom: 0; }\n .lisatiedot { margin: .25rem 0 1rem; border-top: 1px solid rgba(0,0,0,.1); }\n .lisatiedot summary {\n cursor: pointer; font-weight: 600; padding: .7rem 0; list-style: none;\n display: flex; align-items: center; gap: .5rem;\n }\n .lisatiedot summary::-webkit-details-marker { display: none; }\n .lisatiedot summary::after {\n content: \"\u25BE\"; font-size: 1.1rem; line-height: 1; margin-left: auto;\n }\n .lisatiedot[open] summary::after { content: \"\u25B4\"; }\n .lisatiedot summary:hover { text-decoration: underline; }\n .lisatiedot summary:focus-visible { outline: 2px solid currentColor; outline-offset: 2px; }\n .kaannos { margin: 0 0 1rem; padding: .6rem .7rem; border-radius: .25rem; background: #eef1ee; }\n .kaannos p { margin: 0 0 .5rem; }\n /* Kenttien nimet omalle rivilleen: lause pysyy luettavana ja nimet erottuvat. */\n .kaannos strong { display: block; margin-top: .15rem; }\n .kaannos button { color: inherit; border-color: rgba(0,0,0,.25); }\n .napit { display: flex; gap: .5rem; margin-bottom: 1rem; }\n .napit button { color: inherit; border-color: rgba(0,0,0,.25); }\n .napit button.vaarallinen { color: #8c3b2e; border-color: rgba(140,59,46,.4); }\n";
|
|
1
|
+
declare const PALKIN_TYYLIT = "\n :host { all: initial; }\n .palkki {\n position: fixed; inset: auto 1rem 1rem auto; z-index: 2147483000;\n display: flex; align-items: center; gap: .75rem;\n padding: .6rem .75rem .6rem 1rem; border-radius: .5rem;\n background: #1d1f1e; color: #fff;\n font: 500 .875rem/1.4 -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif;\n box-shadow: 0 .5rem 1.5rem rgba(0,0,0,.25);\n max-width: calc(100vw - 2rem);\n }\n .tila { opacity: .75; font-weight: 400; }\n .tila[data-virhe=\"1\"] { color: #ffb4a2; opacity: 1; }\n button {\n font: inherit; padding: .45rem .8rem; border-radius: .35rem;\n border: 1px solid rgba(255,255,255,.25); background: transparent; color: inherit;\n cursor: pointer;\n }\n button.ensisijainen { background: #fff; color: #1d1f1e; border-color: #fff; }\n button:disabled { opacity: .5; cursor: default; }\n button:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }\n\n /* Sivupaneeli */\n .paneeli {\n position: fixed; inset: 0 0 0 auto; z-index: 2147482999;\n width: min(23rem, 100vw); overflow-y: auto;\n padding: 1.25rem 1.25rem 6rem;\n background: #fbfaf8; color: #1d1f1e;\n border-left: 1px solid rgba(0,0,0,.12);\n font: 400 .875rem/1.5 -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif;\n box-shadow: -.5rem 0 1.5rem rgba(0,0,0,.08);\n }\n .paneeli h2 { margin: 0 0 .75rem; font-size: .8125rem; text-transform: uppercase; letter-spacing: .06em; opacity: .6; }\n .paneeli section + section { margin-top: 1.5rem; padding-top: 1.25rem; border-top: 1px solid rgba(0,0,0,.1); }\n .kentta { margin-bottom: 1rem; }\n .kentta label { display: flex; gap: .5rem; align-items: baseline; margin-bottom: .25rem; font-weight: 600; }\n .kieli { font-weight: 400; font-size: .75rem; padding: 0 .3rem; border-radius: .2rem; background: rgba(0,0,0,.08); text-transform: uppercase; }\n .kentta input[type=text], .kentta input[type=number], .kentta input[type=date],\n .kentta input[type=datetime-local], .kentta select, .kentta textarea {\n width: 100%; padding: .45rem .55rem; border: 1px solid rgba(0,0,0,.25);\n border-radius: .25rem; background: #fff; font: inherit; color: inherit;\n }\n .kentta textarea { resize: vertical; }\n .kytkin { display: flex; gap: .5rem; align-items: center; font-weight: 400; }\n .kielivalinta { display: grid; gap: .35rem; }\n .kaannos button { color: inherit; border-color: rgba(0,0,0,.25); }\n .ohje { margin: .25rem 0 0; font-size: .8125rem; opacity: .65; }\n .laskuri { margin: .3rem 0 0; font-size: .8125rem; font-weight: 600; }\n /* Pakollinen kentt\u00E4: punainen t\u00E4hti on tuttu merkki, eik\u00E4 se vie tilaa. */\n .pakko { color: #b3261e; font-weight: 700; }\n .piilossa {\n position: absolute; width: 1px; height: 1px; overflow: hidden;\n clip-path: inset(50%); white-space: nowrap;\n }\n .linkki {\n background: none; border: 0; padding: 0; margin: 0; font: inherit;\n color: inherit; text-decoration: underline; cursor: pointer;\n }\n .virheet { padding: .75rem .9rem; border-radius: .3rem; background: #f6e8e3; color: #6d2b20; }\n .virheet h2 { margin: 0 0 .4rem; opacity: 1; color: inherit; }\n .virheet ul { margin: 0; padding-left: 1.1rem; display: grid; gap: .25rem; }\n .kuva img { display: block; width: 100%; height: auto; border-radius: .25rem; margin-bottom: .4rem; }\n .kuva input[type=text] { width: 100%; padding: .45rem .55rem; border: 1px solid rgba(0,0,0,.25); border-radius: .25rem; font: inherit; }\n /* Kuvan tekstikentt\u00E4 on oma lohkonsa: label on .kentta-tasolla flex,\n ja ilman t\u00E4t\u00E4 otsikko ja kentt\u00E4 asettuisivat vierekk\u00E4in. */\n .kentta label.alt, .kuva label.alt { display: block; font-weight: 600; margin-top: .5rem; }\n .alt input { margin-top: .25rem; width: 100%; }\n .huomio {\n margin: .4rem 0 0; padding: .5rem .6rem; border-radius: .25rem;\n background: #fdf3e7; color: #6b4a1f; font-size: .8125rem;\n }\n .huomio p { margin: 0 0 .4rem; }\n .huomio button { color: inherit; border-color: rgba(107,74,31,.35); padding: .3rem .6rem; }\n .lataus { display: inline-flex; margin: 0 0 .5rem; }\n .lataus span {\n padding: .45rem .8rem; border: 1px solid rgba(0,0,0,.25); border-radius: .35rem;\n cursor: pointer; font-weight: 600;\n }\n .lataus input[type=file] { position: absolute; width: 1px; height: 1px; opacity: 0; }\n .virheviesti { margin: 0 0 .5rem; color: #8c3b2e; font-size: .8125rem; }\n .tyhja { margin: 0; opacity: .6; }\n .kohteet { list-style: none; margin: 0 0 .75rem; padding: 0; display: grid; gap: .25rem; }\n .kohteet button {\n width: 100%; text-align: left; color: inherit; border-color: rgba(0,0,0,.15);\n display: flex; justify-content: space-between; gap: .5rem; align-items: center;\n }\n .kohteet button.valittu { background: #1d1f1e; color: #fff; border-color: #1d1f1e; }\n .merkki { font-size: .75rem; opacity: .7; white-space: nowrap; }\n /* Taitos n\u00E4ytt\u00E4\u00E4 painikkeelta ja nuoli osoittaa sinne, minne sis\u00E4lt\u00F6\n aukeaa: alas kiinni ollessa, yl\u00F6s auki ollessa. */\n .ryhma { margin: 0 0 1rem; padding: .75rem .8rem; border-radius: .3rem; background: rgba(0,0,0,.035); }\n .ryhma h3 { margin: 0 0 .5rem; font-size: .8125rem; text-transform: uppercase; letter-spacing: .05em; opacity: .65; }\n .ryhma .kentta:last-child { margin-bottom: 0; }\n .lisatiedot { margin: .25rem 0 1rem; border-top: 1px solid rgba(0,0,0,.1); }\n .lisatiedot summary {\n cursor: pointer; font-weight: 600; padding: .7rem 0; list-style: none;\n display: flex; align-items: center; gap: .5rem;\n }\n .lisatiedot summary::-webkit-details-marker { display: none; }\n .lisatiedot summary::after {\n content: \"\u25BE\"; font-size: 1.1rem; line-height: 1; margin-left: auto;\n }\n .lisatiedot[open] summary::after { content: \"\u25B4\"; }\n .lisatiedot summary:hover { text-decoration: underline; }\n .lisatiedot summary:focus-visible { outline: 2px solid currentColor; outline-offset: 2px; }\n .kaannos { margin: 0 0 1rem; padding: .6rem .7rem; border-radius: .25rem; background: #eef1ee; }\n .kaannos p { margin: 0 0 .5rem; }\n /* Kenttien nimet omalle rivilleen: lause pysyy luettavana ja nimet erottuvat. */\n .kaannos strong { display: block; margin-top: .15rem; }\n .kaannos button { color: inherit; border-color: rgba(0,0,0,.25); }\n .napit { display: flex; gap: .5rem; margin-bottom: 1rem; }\n .napit button { color: inherit; border-color: rgba(0,0,0,.25); }\n .napit button.vaarallinen { color: #8c3b2e; border-color: rgba(140,59,46,.4); }\n";
|
|
2
2
|
export declare function Muokkaustila(): import("react").JSX.Element | null;
|
|
3
3
|
export { PALKIN_TYYLIT };
|
|
@@ -72,6 +72,16 @@ const PALKIN_TYYLIT = `
|
|
|
72
72
|
.kaannos button { color: inherit; border-color: rgba(0,0,0,.25); }
|
|
73
73
|
.ohje { margin: .25rem 0 0; font-size: .8125rem; opacity: .65; }
|
|
74
74
|
.laskuri { margin: .3rem 0 0; font-size: .8125rem; font-weight: 600; }
|
|
75
|
+
/* Pakollinen kenttä: punainen tähti on tuttu merkki, eikä se vie tilaa. */
|
|
76
|
+
.pakko { color: #b3261e; font-weight: 700; }
|
|
77
|
+
.piilossa {
|
|
78
|
+
position: absolute; width: 1px; height: 1px; overflow: hidden;
|
|
79
|
+
clip-path: inset(50%); white-space: nowrap;
|
|
80
|
+
}
|
|
81
|
+
.linkki {
|
|
82
|
+
background: none; border: 0; padding: 0; margin: 0; font: inherit;
|
|
83
|
+
color: inherit; text-decoration: underline; cursor: pointer;
|
|
84
|
+
}
|
|
75
85
|
.virheet { padding: .75rem .9rem; border-radius: .3rem; background: #f6e8e3; color: #6d2b20; }
|
|
76
86
|
.virheet h2 { margin: 0 0 .4rem; opacity: 1; color: inherit; }
|
|
77
87
|
.virheet ul { margin: 0; padding-left: 1.1rem; display: grid; gap: .25rem; }
|
|
@@ -159,12 +169,15 @@ function lisaaTyylit() {
|
|
|
159
169
|
function lueTeksti(elementti, lista) {
|
|
160
170
|
return (lista ? elementti.innerText : elementti.textContent) ?? "";
|
|
161
171
|
}
|
|
162
|
-
/**
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
172
|
+
/**
|
|
173
|
+
* Sivulla kirjoitettu lista takaisin listaksi.
|
|
174
|
+
*
|
|
175
|
+
* Oletuksena yksi rivi on yksi kohta. Jos sivusto näyttää listan yhtenä
|
|
176
|
+
* tekstinä (esim. "Lounas, Materiaalit"), erotin tulee sisältömallista.
|
|
177
|
+
*/
|
|
178
|
+
function jaaKappaleiksi(teksti, erotin) {
|
|
179
|
+
const osat = erotin ? teksti.split(erotin.trim()) : teksti.split(/\n+/);
|
|
180
|
+
return osat.map((osa) => osa.trim()).filter((osa) => osa !== "");
|
|
168
181
|
}
|
|
169
182
|
/** Sivulla olevat muokattavat kohdat merkintöineen. */
|
|
170
183
|
function merkinnat() {
|
|
@@ -325,8 +338,10 @@ export function Muokkaustila() {
|
|
|
325
338
|
continue;
|
|
326
339
|
if (onKaannettava(kenttä))
|
|
327
340
|
uusi[avain] = {};
|
|
341
|
+
/* Uusi kohde syntyy piilotettuna, kun mallissa on piilottava
|
|
342
|
+
kytkin: keskeneräinen kurssi ei kuulu kävijän silmien eteen. */
|
|
328
343
|
else if (kenttä.tyyppi === "kytkin")
|
|
329
|
-
uusi[avain] =
|
|
344
|
+
uusi[avain] = kenttä.piilottaa === true;
|
|
330
345
|
else if (kenttä.tyyppi === "tekstilista")
|
|
331
346
|
uusi[avain] = [];
|
|
332
347
|
else if (kenttä.tyyppi === "valinta")
|
|
@@ -386,6 +401,9 @@ export function Muokkaustila() {
|
|
|
386
401
|
}
|
|
387
402
|
return {
|
|
388
403
|
src: tulos.src,
|
|
404
|
+
/* Esikatselu on itse kuva: tallennettu tiedosto näkyy osoitteessaan
|
|
405
|
+
vasta seuraavan julkaisun jälkeen. */
|
|
406
|
+
esikatselu: typeof tulos.esikatselu === "string" ? tulos.esikatselu : null,
|
|
389
407
|
ehdotus: typeof tulos.ehdotus === "string" ? tulos.ehdotus : null,
|
|
390
408
|
};
|
|
391
409
|
}, []);
|
|
@@ -501,14 +519,38 @@ export function Muokkaustila() {
|
|
|
501
519
|
muutetut[sivu] = { ...data };
|
|
502
520
|
}
|
|
503
521
|
}
|
|
522
|
+
/* Sivulla kirjoitetut luvut tarkistetaan tässä: palvelimelle ei kannata
|
|
523
|
+
lähettää "kymmenen", kun virheen näkee jo selaimessa. */
|
|
524
|
+
const sivunVirheet = [];
|
|
504
525
|
for (const { elementti, polku } of merkinnat()) {
|
|
505
526
|
const maaritys = kentanMaaritys(sisalto, polku);
|
|
506
527
|
const lista = maaritys?.tyyppi === "tekstilista";
|
|
507
528
|
const teksti = lueTeksti(elementti, lista);
|
|
508
529
|
if (teksti === alkuperaiset.current.get(elementti))
|
|
509
530
|
continue;
|
|
531
|
+
let arvo = teksti;
|
|
532
|
+
if (lista && maaritys?.tyyppi === "tekstilista") {
|
|
533
|
+
arvo = jaaKappaleiksi(teksti, maaritys.erotin);
|
|
534
|
+
}
|
|
535
|
+
else if (maaritys?.tyyppi === "luku") {
|
|
536
|
+
const luku = Number(teksti.replace(",", ".").trim());
|
|
537
|
+
if (teksti.trim() === "" || !Number.isFinite(luku)) {
|
|
538
|
+
sivunVirheet.push({
|
|
539
|
+
kohde: maaritys.nimi ?? polku.alikentta ?? polku.kentta,
|
|
540
|
+
viesti: "Kirjoita pelkkä numero.",
|
|
541
|
+
});
|
|
542
|
+
continue;
|
|
543
|
+
}
|
|
544
|
+
arvo = luku;
|
|
545
|
+
}
|
|
510
546
|
const kieli = onKaannettava(maaritys) ? kentanKieli(elementti, sisalto.kielet) : undefined;
|
|
511
|
-
muutetut[polku.sivu] = asetaArvo(pohja(polku.sivu), polku,
|
|
547
|
+
muutetut[polku.sivu] = asetaArvo(pohja(polku.sivu), polku, arvo, kieli, tunnusKentta(sisalto, polku));
|
|
548
|
+
}
|
|
549
|
+
if (sivunVirheet.length > 0) {
|
|
550
|
+
setTila("muokkaa");
|
|
551
|
+
setViesti("Tarkista sivulla merkityt kohdat. Muutoksesi ovat yhä tässä.");
|
|
552
|
+
setVirheet(sivunVirheet);
|
|
553
|
+
return;
|
|
512
554
|
}
|
|
513
555
|
/* Uusi kohde sai tunnuksen "uusi". Nyt kun sillä on nimi, tunnus
|
|
514
556
|
johdetaan siitä: se näkyy osoitteessa, eikä kukaan halua /uusi-2. */
|
|
@@ -614,7 +656,7 @@ export function Muokkaustila() {
|
|
|
614
656
|
kohde: kaannettavat
|
|
615
657
|
.filter(({ polku }) => polku.tunnus !== undefined)
|
|
616
658
|
.map(({ nimi }) => nimi),
|
|
617
|
-
}, valitse: setValittu, lisaaKohde: lisaaKohde, poistaKohde: poistaKohde })) : null, _jsx("div", { className: "palkki", role: "region", "aria-label": "Sis\u00E4ll\u00F6n muokkaus", children: tila === "lataa" ? (_jsx("span", { className: "tila", children: "Editori avautuu\u2026" })) : muokataan || tallentaa ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "tila", role: "status", "data-virhe": viesti ? "1" : "0", children: tallentaa ? "Tallennetaan…" : (viesti ?? "") !== "" ? viesti : "Muokkaat sivua" }), _jsx("button", { type: "button", onClick: peru, disabled: tallentaa, children: "Peru" }), _jsx("button", { type: "button", className: "ensisijainen", onClick: tallenna, disabled: tallentaa, children: "Tallenna" })] })) : (_jsxs(_Fragment, { children: [viesti ? (_jsx("span", { className: "tila", role: "status", "data-virhe": tila === "virhe" ? "1" : "0", children: viesti })) : null, tila === "tallennettu" ? (_jsx("button", { type: "button", onClick: () => location.reload(), children: "P\u00E4ivit\u00E4 sivu" })) : null, sisalto ? (_jsx("button", { type: "button", className: "ensisijainen", onClick: paalle, children: "Muokkaa" })) : (_jsx("button", { type: "button", onClick: () => location.reload(), children: "Lataa uudelleen" }))] })) })] }));
|
|
659
|
+
}, valitse: setValittu, lisaaKohde: lisaaKohde, poistaKohde: poistaKohde })) : null, _jsx("div", { className: "palkki", role: "region", "aria-label": "Sis\u00E4ll\u00F6n muokkaus", children: tila === "lataa" ? (_jsx("span", { className: "tila", children: "Editori avautuu\u2026" })) : muokataan || tallentaa ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "tila", role: "status", "data-virhe": viesti ? "1" : "0", children: tallentaa ? "Tallennetaan…" : (viesti ?? "") !== "" ? viesti : "Muokkaat sivua" }), _jsx("button", { type: "button", onClick: peru, disabled: tallentaa, children: "Peru" }), _jsx("button", { type: "button", className: "ensisijainen", onClick: tallenna, disabled: tallentaa, children: "Tallenna" })] })) : (_jsxs(_Fragment, { children: [viesti ? (_jsx("span", { className: "tila", role: "status", "data-virhe": tila === "virhe" ? "1" : "0", children: viesti })) : null, tila === "tallennettu" ? (_jsx("button", { type: "button", onClick: () => location.reload(), children: "P\u00E4ivit\u00E4 sivu" })) : null, sisalto && malli ? (_jsx("button", { type: "button", className: "ensisijainen", onClick: paalle, children: "Muokkaa" })) : sisalto ? (_jsx("span", { className: "tila", children: "T\u00E4ll\u00E4 sivulla ei ole muokattavaa sis\u00E4lt\u00F6\u00E4." })) : (_jsx("button", { type: "button", onClick: () => location.reload(), children: "Lataa uudelleen" }))] })) })] }));
|
|
618
660
|
}
|
|
619
661
|
/** Mitä yhtä kohdetta tässä listassa kutsutaan, esimerkiksi "kurssi". */
|
|
620
662
|
function kohdelistanYksikko(sisalto, sivu, kentta) {
|
package/dist/selain/Paneeli.d.ts
CHANGED
package/dist/selain/Paneeli.js
CHANGED
|
@@ -85,15 +85,15 @@ function Kentta_({ polku, maaritys, tiedot, }) {
|
|
|
85
85
|
const tunniste = `kentta-${polku.kentta}-${polku.tunnus ?? ""}-${(polku.alikentta ?? "").replace(".", "-")}`;
|
|
86
86
|
const kaannettava = "kaannettava" in maaritys && maaritys.kaannettava === true;
|
|
87
87
|
const arvo = nykyinenArvo(maaritys, lueArvo(tiedot.data, polku, tunnuksenNimi(tiedot, polku.kentta)), tiedot.kieli);
|
|
88
|
-
return (_jsxs("div", { className: "kentta", children: [_jsxs("label", { htmlFor: tunniste, children: [nimi, kaannettava ? _jsx("span", { className: "kieli", children: tiedot.kieli }) : null] }), _jsx(Sisalto, { maaritys: maaritys, arvo: arvo, tunniste: tunniste, kielet: tiedot.kielet, muuta: (uusi) => tiedot.muuta(polku, uusi, kaannettava), lataa: maaritys.tyyppi === "kuva"
|
|
88
|
+
return (_jsxs("div", { className: "kentta", children: [_jsxs("label", { htmlFor: tunniste, children: [nimi, maaritys.pakollinen ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "pakko", "aria-hidden": "true", title: "Pakollinen tieto", children: "*" }), _jsx("span", { className: "piilossa", children: " (pakollinen)" })] })) : null, kaannettava ? _jsx("span", { className: "kieli", children: tiedot.kieli }) : null] }), _jsx(Sisalto, { maaritys: maaritys, arvo: arvo, tunniste: tunniste, kielet: tiedot.kielet, muuta: (uusi) => tiedot.muuta(polku, uusi, kaannettava), lataa: maaritys.tyyppi === "kuva"
|
|
89
89
|
? async (tiedosto) => {
|
|
90
|
-
const { src, ehdotus } = await tiedot.lataaKuva(tiedosto, maaritys);
|
|
90
|
+
const { src, ehdotus, esikatselu } = await tiedot.lataaKuva(tiedosto, maaritys);
|
|
91
91
|
const vanha = onOlio(arvo) ? arvo : {};
|
|
92
92
|
/* Tyhjä teksti täytetään ehdotuksella suoraan; olemassa
|
|
93
93
|
olevaa ei ylikirjoiteta, vaan ehdotus tarjotaan. */
|
|
94
94
|
const alt = typeof vanha.alt === "string" ? vanha.alt.trim() : "";
|
|
95
95
|
tiedot.muuta(polku, { ...vanha, src, ...(alt === "" && ehdotus ? { alt: ehdotus } : {}) }, false);
|
|
96
|
-
return ehdotus;
|
|
96
|
+
return { ehdotus, esikatselu };
|
|
97
97
|
}
|
|
98
98
|
: undefined }), _jsx(Vapaana, { polku: polku, maaritys: maaritys, arvo: arvo, tiedot: tiedot }), maaritys.ohje ? _jsx("p", { className: "ohje", children: maaritys.ohje }) : null] }));
|
|
99
99
|
}
|
|
@@ -115,6 +115,67 @@ function Vapaana({ polku, maaritys, arvo, tiedot, }) {
|
|
|
115
115
|
const vapaana = Math.max(0, yla - arvo);
|
|
116
116
|
return (_jsx("p", { className: "laskuri", role: "status", children: vapaana === 0 ? "Täynnä" : `Vapaana ${vapaana} / ${yla} paikkaa` }));
|
|
117
117
|
}
|
|
118
|
+
/**
|
|
119
|
+
* Kohteen nimi listassa.
|
|
120
|
+
*
|
|
121
|
+
* Nykyinen kieli ensin, mutta kääntämätön kurssi näkyy sillä kielellä, jolla
|
|
122
|
+
* se on kirjoitettu — ei nimettömänä.
|
|
123
|
+
*/
|
|
124
|
+
/** Onko kohde vasta lisätty eikä sitä ole vielä tallennettu? */
|
|
125
|
+
function uusi(tunnus) {
|
|
126
|
+
return /^uusi(-\d+)?$/.test(tunnus);
|
|
127
|
+
}
|
|
128
|
+
function kohteenOtsikko(maaritys, kohde, kieli) {
|
|
129
|
+
const arvo = kohde[maaritys.otsikko];
|
|
130
|
+
if (typeof arvo === "string")
|
|
131
|
+
return arvo === "" ? "(nimetön)" : arvo;
|
|
132
|
+
if (!onOlio(arvo))
|
|
133
|
+
return "(nimetön)";
|
|
134
|
+
const omalla = arvo[kieli];
|
|
135
|
+
if (typeof omalla === "string" && omalla !== "")
|
|
136
|
+
return omalla;
|
|
137
|
+
const joku = Object.values(arvo).find((muu) => typeof muu === "string" && muu !== "");
|
|
138
|
+
return joku ?? "(nimetön)";
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Virheen kohde muokkaajan sanoin.
|
|
142
|
+
*
|
|
143
|
+
* Palvelin kertoo polun ("kurssit.lista.testi.dateLabel"), mutta muokkaaja
|
|
144
|
+
* tuntee kentät niiden nimillä. Nimi haetaan sisältömallista, ja listan
|
|
145
|
+
* sisällä mukaan tulee se, minkä kohteen kentästä on kyse — muuten viisi
|
|
146
|
+
* virhettä näyttää samalta.
|
|
147
|
+
*/
|
|
148
|
+
function virheenKohde(tiedot, kohde) {
|
|
149
|
+
const osat = kohde.split(".");
|
|
150
|
+
const kentta = osat[1];
|
|
151
|
+
const maaritys = kentta ? tiedot.malli.kentat[kentta] : undefined;
|
|
152
|
+
if (!maaritys)
|
|
153
|
+
return { nimi: osat.at(-1) ?? kohde, sivulla: false };
|
|
154
|
+
if (maaritys.tyyppi !== "kohdelista") {
|
|
155
|
+
return {
|
|
156
|
+
nimi: maaritys.nimi ?? kentta,
|
|
157
|
+
sivulla: tiedot.sivullaNakyvat.has(`${tiedot.sivu}.${kentta}`),
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
const tunnus = osat[2];
|
|
161
|
+
const alikentta = osat[3];
|
|
162
|
+
const alimaaritys = alikentta ? maaritys.kentat[alikentta] : undefined;
|
|
163
|
+
let nimi = alimaaritys?.nimi ?? alikentta ?? maaritys.nimi ?? kentta;
|
|
164
|
+
if (alimaaritys?.tyyppi === "ryhma" && osat[4]) {
|
|
165
|
+
const sisempi = alimaaritys.kentat[osat[4]];
|
|
166
|
+
nimi = `${alimaaritys.nimi ?? alikentta}: ${sisempi?.nimi ?? osat[4]}`;
|
|
167
|
+
}
|
|
168
|
+
const kohteet = Array.isArray(tiedot.data[kentta])
|
|
169
|
+
? tiedot.data[kentta]
|
|
170
|
+
: [];
|
|
171
|
+
const osuma = kohteet.find((yksi) => yksi[maaritys.tunnus] === tunnus);
|
|
172
|
+
return {
|
|
173
|
+
nimi,
|
|
174
|
+
...(osuma ? { kohteenNimi: kohteenOtsikko(maaritys, osuma, tiedot.kieli) } : {}),
|
|
175
|
+
...(tunnus === undefined ? {} : { tunnus }),
|
|
176
|
+
sivulla: tiedot.sivullaNakyvat.has(`${tiedot.sivu}.${kentta}[${tunnus}].${alikentta}`),
|
|
177
|
+
};
|
|
178
|
+
}
|
|
118
179
|
export function Paneeli(tiedot) {
|
|
119
180
|
const kentat = Object.entries(tiedot.malli.kentat);
|
|
120
181
|
const lista = kentat.find(([, maaritys]) => maaritys.tyyppi === "kohdelista");
|
|
@@ -125,7 +186,10 @@ export function Paneeli(tiedot) {
|
|
|
125
186
|
return !tiedot.sivullaNakyvat.has(`${tiedot.sivu}.${avain}`);
|
|
126
187
|
};
|
|
127
188
|
const omat = kentat.filter(([avain, maaritys]) => maaritys.tyyppi !== "kohdelista" && nayta([avain, maaritys]));
|
|
128
|
-
return (_jsxs("aside", { className: "paneeli", "aria-label": "Sis\u00E4ll\u00F6n tiedot", children: [tiedot.virheet && tiedot.virheet.length > 0 ? (_jsxs("section", { className: "virheet", role: "alert", children: [_jsx("h2", { children: "Tallennus pys\u00E4htyi" }), _jsx("ul", { children: tiedot.virheet.slice(0, 6).map((virhe) =>
|
|
189
|
+
return (_jsxs("aside", { className: "paneeli", "aria-label": "Sis\u00E4ll\u00F6n tiedot", children: [tiedot.virheet && tiedot.virheet.length > 0 ? (_jsxs("section", { className: "virheet", role: "alert", children: [_jsx("h2", { children: "Tallennus pys\u00E4htyi" }), _jsx("ul", { children: tiedot.virheet.slice(0, 6).map((virhe) => {
|
|
190
|
+
const tiedot_ = virheenKohde(tiedot, virhe.kohde);
|
|
191
|
+
return (_jsxs("li", { children: [_jsx("strong", { children: tiedot_.nimi }), " ", virhe.viesti, tiedot_.kohteenNimi ? (_jsx("button", { type: "button", className: "linkki", onClick: () => tiedot_.tunnus && tiedot.valitse(tiedot_.tunnus), children: tiedot_.kohteenNimi })) : null, tiedot_.sivulla ? _jsx("span", { className: "merkki", children: " Muokataan sivulla." }) : null] }, `${virhe.kohde}-${virhe.viesti}`));
|
|
192
|
+
}) })] })) : null, omat.length > 0 || tiedot.kaannettavia.sivu.length > 0 ? (_jsxs("section", { children: [_jsx("h2", { children: tiedot.malli.nimi }), omat.map(([avain, maaritys]) => (_jsx(Kentta_, { polku: { sivu: tiedot.sivu, kentta: avain }, maaritys: maaritys, tiedot: tiedot }, avain))), tiedot.kielet.length > 1 ? _jsx(Kaannos, { tiedot: tiedot, mihin: "sivu" }) : null] })) : null, lista ? _jsx(Kohteet, { avain: lista[0], maaritys: lista[1], tiedot: tiedot }) : null] }));
|
|
129
193
|
}
|
|
130
194
|
const KIELELLE = {
|
|
131
195
|
fi: "suomeksi",
|
|
@@ -169,20 +233,7 @@ function Kohteet({ avain, maaritys, tiedot, }) {
|
|
|
169
233
|
const kohteet = Array.isArray(tiedot.data[avain])
|
|
170
234
|
? tiedot.data[avain]
|
|
171
235
|
: [];
|
|
172
|
-
const nimi = (kohde) =>
|
|
173
|
-
const arvo = kohde[maaritys.otsikko];
|
|
174
|
-
if (typeof arvo === "string")
|
|
175
|
-
return arvo === "" ? "(nimetön)" : arvo;
|
|
176
|
-
if (!onOlio(arvo))
|
|
177
|
-
return "(nimetön)";
|
|
178
|
-
/* Nykyinen kieli ensin, mutta kääntämätön kurssi näkyy listassa sillä
|
|
179
|
-
kielellä, jolla se on kirjoitettu — ei nimettömänä. */
|
|
180
|
-
const omalla = arvo[tiedot.kieli];
|
|
181
|
-
if (typeof omalla === "string" && omalla !== "")
|
|
182
|
-
return omalla;
|
|
183
|
-
const joku = Object.values(arvo).find((muu) => typeof muu === "string" && muu !== "");
|
|
184
|
-
return joku ?? "(nimetön)";
|
|
185
|
-
};
|
|
236
|
+
const nimi = (kohde) => kohteenOtsikko(maaritys, kohde, tiedot.kieli);
|
|
186
237
|
/**
|
|
187
238
|
* Merkinnät kohteen nimen perässä: piilotus, huomionarvoinen tila ja
|
|
188
239
|
* täyttyminen. Näin listasta näkee tilanteen avaamatta jokaista kohdetta.
|
|
@@ -228,7 +279,7 @@ function Kohteet({ avain, maaritys, tiedot, }) {
|
|
|
228
279
|
const tunnus = String(kohde[maaritys.tunnus] ?? "");
|
|
229
280
|
const omatMerkinnat = merkinnat(kohde);
|
|
230
281
|
return (_jsx("li", { children: _jsxs("button", { type: "button", className: tunnus === valitunTunnus ? "valittu" : undefined, "aria-current": tunnus === valitunTunnus ? "true" : undefined, onClick: () => tiedot.valitse(tunnus), children: [_jsx("span", { children: nimi(kohde) }), omatMerkinnat.length > 0 ? (_jsx("span", { className: "merkki", children: omatMerkinnat.join(" · ") })) : null] }) }, tunnus));
|
|
231
|
-
}) }), _jsx("div", { className: "napit", children: _jsxs("button", { type: "button", onClick: () => tiedot.lisaaKohde(avain, maaritys), children: ["+ Lis\u00E4\u00E4 ", yksikko] }) }), valittu ? null : (_jsxs("p", { className: "ohje", children: ["Valitse ", yksikko, ", niin n\u00E4et sen tiedot."] }))] }), valittu && valitunTunnus ? (_jsxs("section", { children: [_jsx("h2", { children: nimi(valittu) }), ensisijaiset.map(kentta), tiedot.kielet.length > 1 ? _jsx(Kaannos, { tiedot: tiedot, mihin: "kohde" }) : null, lisatiedot.length > 0 ? (_jsxs("details", { className: "lisatiedot", children: [_jsx("summary", { children: "Lis\u00E4tiedot" }), lisatiedot.map(kentta)] })) : null, _jsx("div", { className: "napit", children: _jsxs("button", { type: "button", className: "vaarallinen", onClick: () => tiedot.poistaKohde(avain, maaritys.tunnus, valitunTunnus), children: ["Poista ", yksikko] }) })] })) : null] }));
|
|
282
|
+
}) }), _jsx("div", { className: "napit", children: _jsxs("button", { type: "button", onClick: () => tiedot.lisaaKohde(avain, maaritys), children: ["+ Lis\u00E4\u00E4 ", yksikko] }) }), valittu ? null : (_jsxs("p", { className: "ohje", children: ["Valitse ", yksikko, ", niin n\u00E4et sen tiedot."] }))] }), valittu && valitunTunnus ? (_jsxs("section", { children: [_jsx("h2", { children: nimi(valittu) }), maaritys.osoite ? (uusi(valitunTunnus) ? (_jsx("p", { className: "ohje", children: "Oma sivu syntyy, kun tallennat." })) : (_jsx("p", { className: "ohje", children: _jsx("a", { className: "linkki", href: maaritys.osoite.replace("{tunnus}", valitunTunnus), children: "Avaa sivu" }) }))) : null, ensisijaiset.map(kentta), tiedot.kielet.length > 1 ? _jsx(Kaannos, { tiedot: tiedot, mihin: "kohde" }) : null, lisatiedot.length > 0 ? (_jsxs("details", { className: "lisatiedot", children: [_jsx("summary", { children: "Lis\u00E4tiedot" }), lisatiedot.map(kentta)] })) : null, _jsx("div", { className: "napit", children: _jsxs("button", { type: "button", className: "vaarallinen", onClick: () => tiedot.poistaKohde(avain, maaritys.tunnus, valitunTunnus), children: ["Poista ", yksikko] }) })] })) : null] }));
|
|
232
283
|
}
|
|
233
284
|
/**
|
|
234
285
|
* Kuvakenttä: nykyinen kuva, vaihto ja vaihtoehtoinen teksti.
|
|
@@ -242,6 +293,9 @@ function Kuvakentta({ maaritys, arvo, muuta, lataa, }) {
|
|
|
242
293
|
/* Vasta vaihdettu kuva: vanha teksti kuvaa yhä vanhaa kuvaa. */
|
|
243
294
|
const [vaihdettu, setVaihdettu] = useState(false);
|
|
244
295
|
const [ehdotus, setEhdotus] = useState(null);
|
|
296
|
+
/* Juuri ladattu kuva näytetään sellaisenaan: tallennettu tiedosto ilmestyy
|
|
297
|
+
osoitteeseensa vasta, kun sivusto on rakennettu uudelleen. */
|
|
298
|
+
const [esikatselu, setEsikatselu] = useState(null);
|
|
245
299
|
const kuva = onOlio(arvo) ? arvo : undefined;
|
|
246
300
|
async function valitse(tiedosto) {
|
|
247
301
|
if (!tiedosto)
|
|
@@ -251,9 +305,10 @@ function Kuvakentta({ maaritys, arvo, muuta, lataa, }) {
|
|
|
251
305
|
setEhdotus(null);
|
|
252
306
|
try {
|
|
253
307
|
const nykyinenAlt = typeof kuva?.alt === "string" ? kuva.alt.trim() : "";
|
|
254
|
-
const uusiEhdotus = await lataa(tiedosto);
|
|
308
|
+
const { ehdotus: uusiEhdotus, esikatselu: uusiKuva } = await lataa(tiedosto);
|
|
255
309
|
setTila("valmis");
|
|
256
310
|
setVaihdettu(true);
|
|
311
|
+
setEsikatselu(uusiKuva);
|
|
257
312
|
/* Jos kentässä oli jo teksti, ehdotusta tarjotaan mutta ei tyrkytetä. */
|
|
258
313
|
if (uusiEhdotus && nykyinenAlt !== "")
|
|
259
314
|
setEhdotus(uusiEhdotus);
|
|
@@ -263,7 +318,7 @@ function Kuvakentta({ maaritys, arvo, muuta, lataa, }) {
|
|
|
263
318
|
setVirhe(syy instanceof Error ? syy.message : "Kuvan lataus ei onnistunut.");
|
|
264
319
|
}
|
|
265
320
|
}
|
|
266
|
-
return (_jsxs("div", { className: "kuva", children: [typeof kuva?.src === "string" ? (_jsx("img", { src: kuva
|
|
321
|
+
return (_jsxs("div", { className: "kuva", children: [esikatselu ?? (typeof kuva?.src === "string" ? kuva.src : null) ? (_jsxs(_Fragment, { children: [_jsx("img", { src: esikatselu ?? kuva?.src, alt: "" }), esikatselu ? (_jsx("p", { className: "ohje", children: "Uusi kuva. Se tulee sivulle, kun tallennat." })) : null] })) : (_jsx("p", { className: "tyhja", children: "Ei kuvaa." })), _jsxs("label", { className: "lataus", children: [_jsx("span", { children: tila === "lataa" ? "Käsitellään…" : "Vaihda kuva" }), _jsx("input", { type: "file", accept: "image/*", disabled: tila === "lataa", onChange: (tapahtuma) => valitse(tapahtuma.target.files?.[0]) })] }), tila === "virhe" ? _jsx("p", { className: "virheviesti", children: virhe }) : null, _jsxs("label", { className: "alt", children: ["Kuvan kuvaus", _jsx("input", { type: "text", placeholder: "Esimerkiksi: kynttil\u00E4 ikkunalaudalla", value: typeof kuva?.alt === "string" ? kuva.alt : "", onChange: (tapahtuma) => {
|
|
267
322
|
setVaihdettu(false);
|
|
268
323
|
muuta({ ...(kuva ?? {}), alt: tapahtuma.target.value });
|
|
269
324
|
} })] }), ehdotus ? (_jsxs("div", { className: "huomio", role: "status", children: [_jsxs("p", { children: ["Ehdotus: \u201C", ehdotus, "\u201D"] }), _jsx("button", { type: "button", onClick: () => {
|
package/package.json
CHANGED