Jeden formularz. Pięć automatyzacji. Zero pracy ręcznej.
Każda szkolna strona internetowa ma ten sam problem: treść tkwi w czyjejś głowie, a nie na stronie. Nauczyciele nie publikują, bo publikowanie jest zbyt trudne — zły folder, zły format nazwy pliku, brakujące zdjęcie, zapomniana data.
Odpowiedzią nie jest bardziej rozbudowany edytor. Odpowiedzią jest usunięcie edytora w całości i zastąpienie go czymś, co zna już każdy nauczyciel: formularzem Google Forms.
Dlaczego formularz bije CMS na głowę
Typowy „szkolny CMS” mnoży opory na każdym kroku: zaloguj się, znajdź właściwą sekcję, przypomnij sobie format, wgraj zdjęcie osobno, kliknij publikuj i módl się, żeby nic się nie posypało. Nauczyciele go unikają. Treść umiera w skrzynkach mailowych.
Google Forms usuwa to wszystko. Pięć pól. Trzydzieści sekund. Gotowe.
Najlepszy interfejs redakcyjny to taki, który w ogóle nie wygląda jak interfejs redakcyjny.
Jest znajomy z definicji. Każdy nauczyciel wypełniał kiedyś Google Forms. Żadnych szkoleń, żadnej dokumentacji, żadnego wdrożenia.
Działa z telefonu. Nauczyciel fotografuje szkolny koncert, otwiera formularz w drodze do samochodu, wpisuje kilka słów kluczowych — i wpis jest już opublikowany, zanim ten wróci do domu.
Wymusza strukturę. Nie ma ryzyka pominięcia daty, źle sformatowanego tytułu czy wrzucenia pliku do niewłaściwego folderu. Pola formularza są umową między nauczycielem a systemem.
Jest kontrolowany i audytowalny. Ograniczasz go do swojej domeny. Każde zgłoszenie niesie znacznik czasu i autora. Pełna historia, zero narzutu.
Potok: co dzieje się po kliknięciu „Wyślij”
Nauczyciel widzi formularz, a potem widzi efekt na szkolnej stronie. Między tymi dwoma momentami Apps Script uruchamia pięcioetapowy potok — niewidoczny, automatyczny i odporny na błędy.
Google Form
↓ trigger: onFormSubmit
Apps Script
↓
┌───────────────────┬────────────────────┐
│ │ │
Gemini API Google Drive Google Calendar
(expands notes) (creates Doc) (creates event)
│ │
└───────────────────┘
↓
Web App CMS
↓
Google Sites
Nauczyciel robi jedną rzecz. System robi pięć.
Formularz: pięć pól, żadnych instrukcji
Formularz ma dwie kategorie pól. Pola treści trafiają do opublikowanego dokumentu i na stronę. Pola operacyjne sterują tym, jak Apps Script kieruje zgłoszeniem — nigdy nie pojawiają się w gotowym artykule.
Pola treści:
- Tytuł — krótka odpowiedź. Przykład: „Rodzinny Koncert Bożonarodzeniowy”.
- Data — wybór daty. Jeśli wiadomość nie jest związana z konkretnym wydarzeniem, nauczyciel wpisuje datę publikacji.
- Typ — lista rozwijana: CONCERT, INFO, PROJECT, SPORT, RECRUITMENT. Staje się plakietką na stronie.
- Słowa kluczowe / opis — akapit. Nauczyciel spisuje sedno: „Klasy 4–6 zaśpiewały kolędy. Obecni byli rodzice i goście. Miejsce: Pałac Młodzieży.” Nie musi pisać dopracowanej prozy — Gemini to rozwinie.
- Zdjęcia — przesyłanie plików, tylko obrazy, dozwolone wiele plików.
Pola operacyjne (dodaj według potrzeb swojej szkoły): docelowe klasy, priorytet, kanały publikacji.
Krok po kroku: co robi skrypt
Krok 1 — Odczyt odpowiedzi z formularza
Apps Script przechwytuje zdarzenie onFormSubmit i odczytuje każde pole po kolei za pomocą responses[N].getResponse(). Data jest formatowana do postaci YYYY-MM-DD — formatu, którego Web App CMS oczekuje w nazwie pliku.
Krok 2 — Gemini rozwija słowa kluczowe
Punkty spisane przez nauczyciela trafiają do Gemini 2.5 Flash z rygorystycznym promptem: napisz 4–5 zdań, neutralnym, informacyjnym tonem, bez zmyślonych faktów. Oryginalne słowa kluczowe są zachowane na dole dokumentu jako metadane — dzięki temu zawsze widać, co napisał człowiek, a co rozwinęła AI.
Jeśli Gemini jest niedostępny (limit zapytań, awaria), tekstem artykułu staje się oryginalny opis. Potok nigdy nie blokuje się na etapie AI.
Krok 3 — Utworzenie ustrukturyzowanego Google Docs
W folderze CMS powstaje nowy dokument o nazwie YYYY-MM-DD | Title. Apps Script wstawia: nagłówek H1, plakietkę typu (pogrubioną, kolorową), tekst z Gemini, przeskalowane zdjęcia (maks. 5, maks. 500px szerokości) oraz oryginalne słowa kluczowe jako szare metadane 9pt na dole. Plik zostaje udostępniony w trybie „każdy z linkiem może wyświetlać”.
Krok 4 — Dodanie do szkolnego kalendarza
Powstaje całodniowe wydarzenie w kalendarzu z tagiem typu wydarzenia i linkiem do dokumentu. Dyrekcja, nauczyciele i rodzice mogą zasubskrybować kalendarz i automatycznie widzieć każdą opublikowaną wiadomość.
Krok 5 — Unieważnienie cache CMS
Klucz cache skryptu używany przez Web App zostaje wyczyszczony. Kolejne wczytanie strony pobiera świeżą listę dokumentów — nowy wpis pojawia się bez żadnego ręcznego odświeżania czy ponownej publikacji.
Kompletny skrypt
Podepnij go do swojego Google Forms przez Script Editor (⋮ → Script editor). Klucz API Gemini przechowuj w Project Settings → Script Properties jako GEMINI_API_KEY.
// ===== CONFIG =====
const CONFIG = {
FOLDER_ID: "PASTE_YOUR_NEWS_FOLDER_ID",
CALENDAR_ID: "PASTE_YOUR_CALENDAR_ID",
FONT_FAMILY: "Lato",
MAX_IMAGES: 5,
MAX_IMG_WIDTH: 500,
CACHE_KEY: "NEWS_HTML"
};
// ===== TRIGGER =====
function onFormSubmit(e) {
const responses = e.response.getItemResponses();
const title = responses[0].getResponse();
const dateStr = responses[1].getResponse();
const type = responses[2].getResponse();
const keywords = responses[3].getResponse();
const fileIds = responses[4] ? responses[4].getResponse() : null;
const date = new Date(dateStr);
const isoDate = Utilities.formatDate(date, "Europe/Warsaw", "yyyy-MM-dd");
let newsText = generateNewsText(title, type, keywords);
if (!newsText) newsText = keywords;
const docUrl = createNewsDoc(title, isoDate, type, newsText, keywords, fileIds);
createCalendarEvent(title, date, type, docUrl);
clearCmsCache();
}
// ===== GEMINI =====
function generateNewsText(title, type, keywords) {
try {
const apiKey = PropertiesService.getScriptProperties()
.getProperty("GEMINI_API_KEY");
if (!apiKey) return "";
const prompt =
"You are a school website editor. Based on the teacher's notes, " +
"write a polished news post. 4-5 sentences, neutral informational " +
"tone, understandable for parents. Do not invent facts.\n\n" +
"Title: " + title + "\nEvent type: " + type +
"\nTeacher's notes: " + keywords;
const url =
"https://generativelanguage.googleapis.com" +
"/v1beta/models/gemini-2.5-flash:generateContent?key=" + apiKey;
const payload = {
contents: [{ parts: [{ text: prompt }] }],
generationConfig: {
maxOutputTokens: 1024,
thinkingConfig: { thinkingBudget: 0 }
}
};
const res = UrlFetchApp.fetch(url, {
method: "post",
contentType: "application/json",
payload: JSON.stringify(payload),
muteHttpExceptions: true
});
const json = JSON.parse(res.getContentText());
if (json.candidates?.[0]) {
return json.candidates[0].content.parts[0].text.trim();
}
} catch (err) {
Logger.log("Gemini error: " + err);
}
return "";
}
// ===== DOCUMENT =====
function createNewsDoc(title, isoDate, type, newsText, keywords, fileIds) {
const folder = DriveApp.getFolderById(CONFIG.FOLDER_ID);
const fileName = isoDate + " | " + title;
const doc = DocumentApp.create(fileName);
const body = doc.getBody();
const h1 = body.getParagraphs()[0];
h1.setHeading(DocumentApp.ParagraphHeading.HEADING1);
h1.setText(title);
h1.setFontFamily(CONFIG.FONT_FAMILY);
if (type) {
const badge = body.appendParagraph(type.toUpperCase());
badge.setBold(true).setForegroundColor("#1a73e8").setFontFamily(CONFIG.FONT_FAMILY);
}
body.appendParagraph("");
body.appendParagraph(newsText).setFontFamily(CONFIG.FONT_FAMILY);
if (fileIds?.length > 0) {
body.appendParagraph("");
const limit = Math.min(fileIds.length, CONFIG.MAX_IMAGES);
for (let i = 0; i < limit; i++) {
try {
const blob = DriveApp.getFileById(fileIds[i]).getBlob();
const image = body.appendImage(blob);
const w = image.getWidth();
if (w > CONFIG.MAX_IMG_WIDTH) {
const ratio = CONFIG.MAX_IMG_WIDTH / w;
image.setWidth(CONFIG.MAX_IMG_WIDTH);
image.setHeight(Math.round(image.getHeight() * ratio));
}
body.appendParagraph("");
} catch (err) { Logger.log(err); }
}
if (fileIds.length > CONFIG.MAX_IMAGES) {
const info = body.appendParagraph(
"Remaining photos (" + (fileIds.length - CONFIG.MAX_IMAGES) + ") available in Drive folder."
);
info.setFontSize(9).setForegroundColor("#999999").setItalic(true);
}
}
const kw = body.appendParagraph("Keywords: " + keywords);
kw.setFontSize(9).setForegroundColor("#999999").setItalic(true).setFontFamily(CONFIG.FONT_FAMILY);
const file = DriveApp.getFileById(doc.getId());
folder.addFile(file);
DriveApp.getRootFolder().removeFile(file);
file.setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.VIEW);
return doc.getUrl();
}
// ===== CALENDAR =====
function createCalendarEvent(title, date, type, docUrl) {
try {
const cal = CalendarApp.getCalendarById(CONFIG.CALENDAR_ID);
if (!cal) { Logger.log("Calendar not found"); return; }
const event = cal.createAllDayEvent(
(type ? "[" + type + "] " : "") + title, date
);
event.setDescription("School news\n\nDocument:\n" + docUrl);
} catch (err) {
Logger.log("Calendar error: " + err);
}
}
// ===== CACHE =====
function clearCmsCache() {
try {
CacheService.getScriptCache().remove(CONFIG.CACHE_KEY);
} catch (err) {
Logger.log("Cache clear skipped: " + err);
}
}
Lista kontrolna wdrożenia
Konfiguracja Google Forms:
- Utwórz nowy Google Forms ograniczony do domeny Twojej szkoły
- Dodaj pole: Tytuł (krótka odpowiedź, wymagane)
- Dodaj pole: Data (wybór daty, wymagane)
- Dodaj pole: Typ (lista rozwijana: CONCERT, INFO, PROJECT, SPORT, RECRUITMENT)
- Dodaj pole: Słowa kluczowe / opis (akapit, wymagane)
- Dodaj pole: Zdjęcia (przesyłanie plików, tylko obrazy, zezwól na wiele)
Konfiguracja Apps Script:
- Otwórz Script Editor (⋮ → Script editor) i wklej cały skrypt
- Zaktualizuj
CONFIG.FOLDER_IDidentyfikatorem swojego folderu CMS (z adresu URL w Drive) - Zaktualizuj
CONFIG.CALENDAR_IDidentyfikatorem szkolnego kalendarza - Dodaj
GEMINI_API_KEYw Project Settings → Script Properties
Triggery i testowanie:
- Ustaw trigger: ikona zegara → Add trigger →
onFormSubmit→ On form submit - Wyślij testowe zgłoszenie i sprawdź: dokument pojawia się w folderze, wydarzenie w kalendarzu utworzone, strona się aktualizuje
Wersja interaktywna
Poniższy widżet przeprowadza przez cały potok krok po kroku i pozwala poznać kod w kontekście.
Ten sam wzorzec, różne problemy
Ta architektura — formularz → Apps Script → ustrukturyzowany rezultat — nadaje się do wielokrotnego użycia w dziesiątkach szkolnych procesów.
Zapisy na wycieczki. Nauczyciel przesyła nazwę wydarzenia, datę, klasę i liczbę uczestników. Skrypt tworzy dokument zapisów z szablonu, dodaje wpis do kalendarza wycieczek, wysyła maila do nauczyciela i powiadamia dyrekcję.
Raporty ewaluacyjne. Po zamknięciu ankiety Apps Script zlicza odpowiedzi, wysyła je do Gemini po streszczenie, generuje plik PDF i umieszcza go w folderze Drive dyrekcji. To, co kiedyś zajmowało godzinę, dzieje się w kilka sekund.
Cotygodniowy newsletter dla rodziców. Trigger czasowy zbiera dokumenty z wiadomościami z ostatnich siedmiu dni, prosi Gemini o napisanie przeglądu i wysyła maila w kopii ukrytej (BCC) do wszystkich grup rodziców. Zero wysiłku redakcyjnego przy tej samej jakości.
Prośby o zastępstwa. Nauczyciel przesyła datę, lekcję, klasę i uwagi. Skrypt tworzy kartę zastępstwa, wysyła maila do administracji i dodaje nieobecność do wspólnego kalendarza kadry.
Wzorzec jest zawsze taki sam: formularz zbiera ustrukturyzowane dane wejściowe, Apps Script kieruje nimi i przekształca, a rezultat ląduje dokładnie tam, gdzie powinien. Nauczyciel robi jedną rzecz. Instytucja pamięta wszystko.
Słowo o AI i danych
Prompt wysyłany do Gemini zawiera tytuł i słowa kluczowe — nigdy nazwisk uczniów, numerów identyfikacyjnych ani danych osobowych. I tak ma zostać. AI pomaga w pisaniu; nie przetwarza danych osobowych. Jeśli nauczyciele trzymają się prostej zasady — opisz, co się wydarzyło, a nie kto był w to zaangażowany — system jest zgodny z RODO już na poziomie założeń.