@studiotitta/studio-editori 0.1.3 → 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 +84 -24
- 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
|
@@ -48,10 +48,15 @@ function Sisalto({ maaritys, arvo, tunniste, muuta, lataa, kielet, }) {
|
|
|
48
48
|
case "tekstialue":
|
|
49
49
|
return (_jsx("textarea", { id: tunniste, rows: 4, value: typeof arvo === "string" ? arvo : "", onChange: (tapahtuma) => muuta(tapahtuma.target.value) }));
|
|
50
50
|
case "tekstilista":
|
|
51
|
+
/*
|
|
52
|
+
Yksi rivi = yksi kappale, sama sääntö kuin sivulla muokatessa.
|
|
53
|
+
Tyhjät rivit ohitetaan, joten ylimääräinen rivinvaihto ei tuota
|
|
54
|
+
tyhjää kappaletta.
|
|
55
|
+
*/
|
|
51
56
|
return (_jsx("textarea", { id: tunniste, rows: 5, value: Array.isArray(arvo) ? arvo.join("\n\n") : "", onChange: (tapahtuma) => muuta(tapahtuma.target.value
|
|
52
|
-
.split(/\n
|
|
53
|
-
.map((
|
|
54
|
-
.filter((
|
|
57
|
+
.split(/\n+/)
|
|
58
|
+
.map((kappale) => kappale.trim())
|
|
59
|
+
.filter((kappale) => kappale !== "")) }));
|
|
55
60
|
case "kielivalinta": {
|
|
56
61
|
const valitut = Array.isArray(arvo) ? arvo : [];
|
|
57
62
|
return (_jsxs("div", { className: "kielivalinta", children: [kielet.map((kieli) => (_jsxs("label", { className: "kytkin", children: [_jsx("input", { type: "checkbox", checked: valitut.includes(kieli), onChange: (tapahtuma) => muuta(tapahtuma.target.checked
|
|
@@ -80,15 +85,15 @@ function Kentta_({ polku, maaritys, tiedot, }) {
|
|
|
80
85
|
const tunniste = `kentta-${polku.kentta}-${polku.tunnus ?? ""}-${(polku.alikentta ?? "").replace(".", "-")}`;
|
|
81
86
|
const kaannettava = "kaannettava" in maaritys && maaritys.kaannettava === true;
|
|
82
87
|
const arvo = nykyinenArvo(maaritys, lueArvo(tiedot.data, polku, tunnuksenNimi(tiedot, polku.kentta)), tiedot.kieli);
|
|
83
|
-
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"
|
|
84
89
|
? async (tiedosto) => {
|
|
85
|
-
const { src, ehdotus } = await tiedot.lataaKuva(tiedosto, maaritys);
|
|
90
|
+
const { src, ehdotus, esikatselu } = await tiedot.lataaKuva(tiedosto, maaritys);
|
|
86
91
|
const vanha = onOlio(arvo) ? arvo : {};
|
|
87
92
|
/* Tyhjä teksti täytetään ehdotuksella suoraan; olemassa
|
|
88
93
|
olevaa ei ylikirjoiteta, vaan ehdotus tarjotaan. */
|
|
89
94
|
const alt = typeof vanha.alt === "string" ? vanha.alt.trim() : "";
|
|
90
95
|
tiedot.muuta(polku, { ...vanha, src, ...(alt === "" && ehdotus ? { alt: ehdotus } : {}) }, false);
|
|
91
|
-
return ehdotus;
|
|
96
|
+
return { ehdotus, esikatselu };
|
|
92
97
|
}
|
|
93
98
|
: undefined }), _jsx(Vapaana, { polku: polku, maaritys: maaritys, arvo: arvo, tiedot: tiedot }), maaritys.ohje ? _jsx("p", { className: "ohje", children: maaritys.ohje }) : null] }));
|
|
94
99
|
}
|
|
@@ -110,6 +115,67 @@ function Vapaana({ polku, maaritys, arvo, tiedot, }) {
|
|
|
110
115
|
const vapaana = Math.max(0, yla - arvo);
|
|
111
116
|
return (_jsx("p", { className: "laskuri", role: "status", children: vapaana === 0 ? "Täynnä" : `Vapaana ${vapaana} / ${yla} paikkaa` }));
|
|
112
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
|
+
}
|
|
113
179
|
export function Paneeli(tiedot) {
|
|
114
180
|
const kentat = Object.entries(tiedot.malli.kentat);
|
|
115
181
|
const lista = kentat.find(([, maaritys]) => maaritys.tyyppi === "kohdelista");
|
|
@@ -120,7 +186,10 @@ export function Paneeli(tiedot) {
|
|
|
120
186
|
return !tiedot.sivullaNakyvat.has(`${tiedot.sivu}.${avain}`);
|
|
121
187
|
};
|
|
122
188
|
const omat = kentat.filter(([avain, maaritys]) => maaritys.tyyppi !== "kohdelista" && nayta([avain, maaritys]));
|
|
123
|
-
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] }));
|
|
124
193
|
}
|
|
125
194
|
const KIELELLE = {
|
|
126
195
|
fi: "suomeksi",
|
|
@@ -164,20 +233,7 @@ function Kohteet({ avain, maaritys, tiedot, }) {
|
|
|
164
233
|
const kohteet = Array.isArray(tiedot.data[avain])
|
|
165
234
|
? tiedot.data[avain]
|
|
166
235
|
: [];
|
|
167
|
-
const nimi = (kohde) =>
|
|
168
|
-
const arvo = kohde[maaritys.otsikko];
|
|
169
|
-
if (typeof arvo === "string")
|
|
170
|
-
return arvo === "" ? "(nimetön)" : arvo;
|
|
171
|
-
if (!onOlio(arvo))
|
|
172
|
-
return "(nimetön)";
|
|
173
|
-
/* Nykyinen kieli ensin, mutta kääntämätön kurssi näkyy listassa sillä
|
|
174
|
-
kielellä, jolla se on kirjoitettu — ei nimettömänä. */
|
|
175
|
-
const omalla = arvo[tiedot.kieli];
|
|
176
|
-
if (typeof omalla === "string" && omalla !== "")
|
|
177
|
-
return omalla;
|
|
178
|
-
const joku = Object.values(arvo).find((muu) => typeof muu === "string" && muu !== "");
|
|
179
|
-
return joku ?? "(nimetön)";
|
|
180
|
-
};
|
|
236
|
+
const nimi = (kohde) => kohteenOtsikko(maaritys, kohde, tiedot.kieli);
|
|
181
237
|
/**
|
|
182
238
|
* Merkinnät kohteen nimen perässä: piilotus, huomionarvoinen tila ja
|
|
183
239
|
* täyttyminen. Näin listasta näkee tilanteen avaamatta jokaista kohdetta.
|
|
@@ -223,7 +279,7 @@ function Kohteet({ avain, maaritys, tiedot, }) {
|
|
|
223
279
|
const tunnus = String(kohde[maaritys.tunnus] ?? "");
|
|
224
280
|
const omatMerkinnat = merkinnat(kohde);
|
|
225
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));
|
|
226
|
-
}) }), _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] }));
|
|
227
283
|
}
|
|
228
284
|
/**
|
|
229
285
|
* Kuvakenttä: nykyinen kuva, vaihto ja vaihtoehtoinen teksti.
|
|
@@ -237,6 +293,9 @@ function Kuvakentta({ maaritys, arvo, muuta, lataa, }) {
|
|
|
237
293
|
/* Vasta vaihdettu kuva: vanha teksti kuvaa yhä vanhaa kuvaa. */
|
|
238
294
|
const [vaihdettu, setVaihdettu] = useState(false);
|
|
239
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);
|
|
240
299
|
const kuva = onOlio(arvo) ? arvo : undefined;
|
|
241
300
|
async function valitse(tiedosto) {
|
|
242
301
|
if (!tiedosto)
|
|
@@ -246,9 +305,10 @@ function Kuvakentta({ maaritys, arvo, muuta, lataa, }) {
|
|
|
246
305
|
setEhdotus(null);
|
|
247
306
|
try {
|
|
248
307
|
const nykyinenAlt = typeof kuva?.alt === "string" ? kuva.alt.trim() : "";
|
|
249
|
-
const uusiEhdotus = await lataa(tiedosto);
|
|
308
|
+
const { ehdotus: uusiEhdotus, esikatselu: uusiKuva } = await lataa(tiedosto);
|
|
250
309
|
setTila("valmis");
|
|
251
310
|
setVaihdettu(true);
|
|
311
|
+
setEsikatselu(uusiKuva);
|
|
252
312
|
/* Jos kentässä oli jo teksti, ehdotusta tarjotaan mutta ei tyrkytetä. */
|
|
253
313
|
if (uusiEhdotus && nykyinenAlt !== "")
|
|
254
314
|
setEhdotus(uusiEhdotus);
|
|
@@ -258,7 +318,7 @@ function Kuvakentta({ maaritys, arvo, muuta, lataa, }) {
|
|
|
258
318
|
setVirhe(syy instanceof Error ? syy.message : "Kuvan lataus ei onnistunut.");
|
|
259
319
|
}
|
|
260
320
|
}
|
|
261
|
-
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) => {
|
|
262
322
|
setVaihdettu(false);
|
|
263
323
|
muuta({ ...(kuva ?? {}), alt: tapahtuma.target.value });
|
|
264
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