# timOhjeet

1. TIM Plugin määritys

Dokumentissa määritellään TIMin ja pluginien välinen rajapinta ja niiden välinen vuorovaikutus.

2. Toimintaperiaate

# speksi
SelainSelainTIMTIMPluginPluginDocumentDocumentDBDBGET /view/1loop[Kerran per plugintyyppi]GET /reqs{js: <file>, css: <css>, ...}loop[Kerran per plugin]Fetch Markup <id><markup>Fetch State <id,user><state/null>POST /html/ {markup:<markup>, ...}HTMLHTMLGET /<js> | GET /<css>GET /<js> | GET /<css><js> | <css><js> | <css>opt[Jos tarvitaan muita pyyntöjä, kuten angular templaatteja tjsp.]<arbitrary request><arbitrary request><arbitrary response><arbitrary response>PUT <plugin>/<id>/answer/ {input:<answer>}PUT /answer/ {markup:<markup>, ...}{web:<web>, ...}Store <state,id,user>{web:<web>, ...}

Yllä olevassa kuvassa on esitetty TIMin ja pluginien välinen toiminta tilanteessa, jossa käyttäjä avaa tehtäväplugineja (yhden tai useampia) sisältävän dokumentin sekä lähettää vastauksen yhteen niistä. Kuvassa tapahtuu seuraavaa:

  1. Selain lähettää pyynnön osoitteeseen /view/1, mikä tarkoittaa, että ollaan avaamassa dokumenttia, jonka id on 1.

  2. TIM kysyy kaikilta sivun plugineilta, mitä CSS- ja JavaScript-tiedostoja sekä Angular-moduuleja se tarvitsee ja pitää ne muistissa.

  1. TIM pyytää jokaiselta pluginilta HTML-palan, joka tullaan viemään selaimelle. Tämä tapahtuu lähettämällä pluginin /html-reittiin seuraavat tiedot:
    • markup: dokumentissa oleva plugin-lohkon sisältö JSON-muodossa
    • state: tilatieto siitä, mihin tilaan käyttäjä on viimeksi pluginin saattanut
    • taskID: pluginin tehtävän id, jossa siis on mukana dokumentin id. Esimerkiksi 123.tehtava1. Jos plugin-instanssi ei ole tehtävä (esim. kuvannäyttöplugin), niin tämä kenttä on määrittelemätön, eli sen arvoa ei pidä katsoa (TODO: ei pitäisi lähettää tätä kenttää ollenkaan, jos plugin ei ole tehtävä).
    • taskIDExt: Laajennettu tehtävän id. Tämä kenttä lähetetään aina ja se on muotoa <dokumentin id>.<tehtävän nimi tai tyhjä>.<kappaleen id>.
    • doLazy: True, jos halutaan, että plugin latautuu laiskasti, ts. vasta sitten, kun hiiri koskettaa sitä. Tällä voidaan välttää hitaiden laitteiden työtä isoa dokumenttia ladattaessa. (TODO: Voisi olla infon sisällä.)
    • preview: True, jos pluginia ollaan renderöimässä esikatseluna muokatessa. (TODO: Voisi olla infon sisällä.)
    • anonymous: True, jos käyttäjä ei ole kirjautunut. (TODO: Voisi olla infon sisällä.)
    • info: sisältää seuraavat metatiedot:
      • earlier_answers: aikaisempien vastausten lukumäärä
      • max_answers: vastausten sallittu enimmäismäärä tähän tehtävään
      • current_user_id: kirjautuneen käyttäjän käyttäjätunnus
      • user_id: sama kuin edellinen (TODO: pitäisi olla vastauksen tekijä(t), jos statekin on olemassa)
      • look_answer: aina false, TODO: tätä ei tarvitsisi olla lainkaan HTML-reitissä
      • valid: onko viimeisin vastaus validi vai ei, TODO: pitäisi voida olla myös null, jos yhtään vastausta ei ole
  1. Pluginit palauttavat TIMille parametreja vastaavaa HTML:ää.

  2. TIM lähettää dokumentin plugineineen selaimelle.

  1. Plugin lataa mahdolliset muut tarvitsemansa resurssit reittiensä avulla.
  1. Käyttäjä painaa selaimeen renderöidyn tehtävän tallennuspainiketta, minkä johdosta selain (pluginin JavaScript) tekee HTTP-pyynnön /answer-reittiin. Pyynnön formaatti on JSON, joka sisältää input-nimisen kentän. Tämä kenttä voi sisältää mitä tahansa tietoa, jonka TIM lähettää eteenpäin pluginille. TIM kutsuu siis pluginin answer-reittiä seuraavin tiedoin:
    • input: selaimelta tullut data
    • markup: ks. yllä
    • state: ks. yllä
    • taskID: ks. yllä
    • info: sama kuin yllä seuraavin eroin:
      • look_answer voi olla true, jos opettaja on vain katsomassa jonkun käyttäjän vastausta
      • user_id sisältää puolipisteellä erotettuina niiden käyttäjien tunnukset, jotka ovat vastaamassa tehtävään
      • valid on alustava arvo nyt lähetetyn vastauksen validiudelle. Se siis voi olla false esim. silloin, jos TIM huomaa, että tehtävän vastausaikaraja on umpeutunut.
  1. Plugin palauttaa /answer-reitistä JSONin, jossa on seuraavat kentät:
    • web: Pakollinen. Tämän TIM lähettää selaimelle osana answer-reitin vastausta
    • save: Valinnainen. Jos kenttä on mitä tahansa muuta kuin null, TIM tallentaa sen sisällön tietokantaan käyttäen avaimena käyttäjän sisäistä tunnistetta ja taskID:tä. TODO: Pitäisikö myös null tallentaa ja vaatia, ettei save-kenttää ole lainkaan, jos ei ole tallennettavaa?
    • tim_info: Valinnainen. Voi sisältää seuraavia kenttiä:
      • points: Valinnainen. Pluginin antamat pisteet käyttäjän vastauksesta. Voi olla mikä tahansa liukuluku, myös negatiivinen.
      • notValid: Valinnainen. Jos muu kuin null, vastaus merkitään tietokantaan ei-validiksi. Huomaa, että vastaus saatetaan merkitä muustakin syystä ei-validiksi (esim. umpeutunut aikaraja).
  1. TIM lähettää selaimelle vastauksen JSONina, jossa on seuraavat kentät:
    • web: Pakollinen. Sama kuin pluginin palauttama web-kenttä.
    • savedNew: Pakollinen. Tallennetun vastauksen id tietokannassa, jos vastaus tallennettiin, muulloin null.
    • error: Valinnainen. Virhettä kuvaava merkkijono. Tämä kenttä palautetaan silloin, kun answer-reitin aikana tapahtuu jokin virhe tai jos vastaus todettiin epävalidiksi.

2.1 Pluginin markup

Pluginin markup-lohkot ovat YAML-muotoista tekstiä, joissa voi esiintyä makroja (kuten tavallisissakin kappaleissa), jotka oletuksena menevät kaksien prosenttimerkkien (%%...%%) sisälle. Nämä makrot korvataan arvoillaan, minkä jälkeen YAML parsii tekstin ja syntynyt YAML lähetetään pluginille JSONiksi muutettuna.

Pluginin markup kirjoitetaan koodilohkoon, jolla on plugin-attribuutti. Esimerkiksi:

```{#tehtava1 plugin=csPlugin}
type: console   
file: https://svn.cc.jyu.fi/srv/svn/ohj1/luentomonistecs/esimerkit/Pohja/Pohja/Pohja.cs  
replace: "Console "   
byCode: |
    Console.WriteLine("Moi 1");
    Console.WriteLine("Moi 2");
```

Toistaiseksi ei ole olemassa erityisesti pluginkappaleille tarkoitettuja makroja. Yleiset makrot on kuvattu ohjeissa.

2.1.1 Erityiskentät

Markupissa voi esiintyä kenttiä, joilla on tietty erityismerkitys ja joita TIM käyttää. Plugin ei saa määritellä näitä kenttiä eri tavalla omaan tarkoitukseensa. Kentät ovat seuraavat:

  • starttime: Tätä aikaleimaa aiemmin tehtävään ei voi vastata.
  • deadline: Tätä aikaleimaa myöhemmin tehtävään ei voi vastata.
  • pointsRule: Pisteytyssääntö tehtävälle. Tämä voi sisältää seuraavia kenttiä:
    • allowUserMin: Minimipistemäärä, jonka käyttäjä voi itse määritellä vastaukselleen.
    • allowUserMax: Maksimipistemäärä, jonka käyttäjä voi itse määritellä vastaukselleen.
    • maxPoints: Kuvaus tehtävän maksimipistemäärästä. Voi olla mikä tahansa merkkijono.
  • answerLimit: Vastausten enimmäismäärä per käyttäjä. Tämän rajan ylittäneet vastaukset merkitään epävalideiksi.
  • lazy: True, jos plugin pitäisi renderöidä laiskana. Tämä ylikirjoittaa ylätason doLazy-määrityksen.

2.2 Pluginin renderöinti selaimeen

Pluginin tuottamaan HTML:ään liittyy usein skriptejä, joiden pitää tietää, miten pluginille voidaan lähettää pyyntöjä. Tämän mahdollistamiseksi TIM sijoittaa pluginin tuottaman HTML:n metatieto-divin sisään:

<div id="{taskIDExt}"
     data-plugin="{URL, jota pitkin voi tehdä pyyntöjä pluginille}">
{pluginin HTML}
</div>

Tämän jälkeen pluginiin liittyvät skriptit voivat lukea yo. divistä riittävät metatiedot toimiakseen.

Pluginilta voidaan pyytää myös "laiska" (lazy) HTML-muoto, jolloin plugin näytetään "pelkistettynä" siihen asti, kunnes käyttäjä koskee pluginiin. Tällä voidaan välttää merkittävästi hitaiden laitteiden työtä isoa dokumenttia ladattaessa. Vastausselain vaihtaa pluginin sisällön sitten, kun siihen kosketaan.

Lazy-muodossa pluginin tulee sulkea laiskasti latautuva koodi HTML-kommenttien <!--lazy ... lazy--> sisään. Esimerkki:

<!--lazy <cs-console>...</cs-console> lazy-->

Heti tämän kommentin perään pluginin tulee antaa itsestään HTML:nä pelkistetty sisältö - siis se, joka näytetään ennen pluginiin koskemista.

2.3 Pluginin kanssa kommunikointi skriptistä

Skripti voi kommunikoida pluginin kanssa hakemalla parent-elementtinsä data-plugin arvon ja tekemällä pyyntöjä sinne. Osa reiteistä on standardeja ja speksattu tämän dokumentin lopussa, mutta pluginin kirjoittaja voi määritellä myös omia reittejään. Näiden reittien kohdalla TIM vain forwardoi pyynnöt sellaisenaan pluginille.

3. Pluginin tila (state)

Pluginin tila tallennetaan aina sellaisena kuin plugin sen antaa save-kentässä. Tila voi sisältää mitä tahansa dataa. Sen ei tarvitse olla JSONia (TODO: pitäisikö?), mutta TIM oletuksena yrittää parsia sen JSONina lähetettäessä pyyntöä html- ja answer-reittiin. Mikäli se onnistuu, state-kentän arvoksi asetetaan saatu JSON-olio. Muulloin arvoksi asetetaan data sellaisenaan.

4. Reitit

4.1 Pluginien tarjoamat reitit

Kaikki POST- ja PUT-reitit ottavat parametrit JSON-muodossa.

4.1.1 /plugin/html/ [POST]

  • SYÖTE: ks. yllä luku 2
  • PALAUTTAA: HTML:ksi renderöidyn pluginin
  • kutsutaan joka kerta, kun plugin renderöidään
  • optio, tehtävä jos ei toteuta /iframe (voi olla molemmat)

4.1.2 /plugin/multihtml/ [POST]

  • SYÖTE: Lista /html-reitin vaatimista parametreista

  • PALAUTTAA: JSON lista HTML:ksi renderöidyt pluginit

  • esimerkiksi syöte:

    [
        {"taskID": "25.Writeline", "state": null, "markup": {"user_id": "mikkalle", "byCode": "koodia"}},
        {"taskID": "25.puolipistevaarin",  "doLazy": true, "state": null, "markup": {"lazy": false, "user_id": "mikkalle", "byCode": "koodia"}},
        {"taskID": "25.virheita", "doLazy": true,"state": null, "markup": {"maxrows": 10, "header": "Teht\u00e4v\u00e4 4", "user_id": "mikkalle", "stem": "stem-teksti", "byCode": "koodia"}}
    ]

    johon vastaus on JSON-taulukko, csPluginin tapauksessa tyyliin

    [
    "<cs-runner>xxxHEXJSONxxx7b226...heksalukuja...</cs-runner>",
    "<cs-runner>xxxHEXJSONxxx7b226...heksalukuja...</cs-runner>",
    "<!--lazy <cs-runner>xxxHEXJSONxxx7b226...heksalukuja...</cs-runner> lazy--><div class="csRunDiv"><h4>Tehtävä 4...PLUGININ LAZY HTML...</div>
    ]
  • Jos plugin ei halua, että se on lazy-muotoinen pyynnöistä huolimatta, se voi liittää jonon

    <!--nolazy-->

    lähettämänsä HTML:n alkuun.

  • kutsutaan kerran per dokumentti per plugintyyppi, jotta dokumentin latausta saadaan nopeutettua

  • optio; reitin olemassaolo kerrotaan /plugin/reqs-reitin avulla, ks. alempana

  • säännöt sille, milloin lazy-tehdään Python-koodina yleisissä tapauksissa (jossa GenericHtmlModel on muoto, joka sisältää pluginin markupit):
# http_paramspy
def is_lazy(q: GenericHtmlModel) -> bool:
    """Determines if the server should render a lazy version of the plugin."""
    if q.doLazy == Laziness.Never:
        return False
    if q.markup.lazy:
        return True
    if q.markup.lazy is False:
        return False
    if q.doLazy == Laziness.Yes:
        return True
    return False

gitlab linkki http_params.py -tiedostoon ei toimi. Tiedostorakennetta muutettu gitlabin sisällä

DZ: Joo, tuota kooditiedostoa ei olekaan enää olemassa. Nyt on korjattu tilalle "yleinen" logiikka. Tämän logiikan tosin jokainen plugin voi määriittää itse.

09 Feb 22 (edited 09 Feb 22)

4.1.3 /plugin/iframe/ [POST]

  • optio, tehtävä jos ei toteuta reittiä /html
  • muuten kuten /html
  • tuottaa <iframe source="...."></iframe> -sisällön

4.1.4 /plugin/reqs/ [GET]

  • antaa listan tarvituista CSS, JS etc. tiedostoista sekä tiedon siitä, tuetaanko multihtml-reittiä
  • pluginilla voi olla kolme eri tyyppiä: embedded, simpleEmbedded ja iframe
  • alla 3 eri paluuesimerkkiä kunkin tyypin mukaan
    1. PALAUTTAA: (yleisin tapaus)

      {
          "type": "embedded",
          "js": ["munSkripti1.js", "http://path/to/js.js"],
          "css": ["munStyylit.css", "http://path/to/css.css"],
          "multihtml": true,
          "canGiveTask": true,
          "text": ["Mun plugin"],
          "templates": [[{"text":"Mun eka","file":"munEka","expl":"Ekat ominaisuudet"},
                        {"file":"munToka"}]]
      }
      • type (embedded/iframe/simpleEmbedded oletus: embedded). Pluginin tyyppi.
        • embedded tyyppi (oletus), html kääritään <div>in sisään, jossa taskId ja pluginURL
      • js: lista javascripteistä joita plugin tarvitsee
      • css: lista css-tiedostoista joita plugin tarvitsee
      • multihtml: true/false (oletus=false), vastaako plugin multihtml-reittiin
      • canGiveTask: plugin voi vastata answer reittiin jos siellä on parametri getTask: true. Tälläin mitään ei tallenneta ja annetaan "pienen" mahdollinen vastaus niin että itse tehtävä saadaan näkyviin. Tämä liittyy tehtäviin, joita ei nopeussyistä voida html tai multihtlm vastauksissa laitata mukaan. Oletus on false ja parametria ei anneta.
      • text: taulukko tekstejä, joilla plugin esittelee itsensä esimerkiksi editorin tab-listaan, oletus on pluginin nimi. Jos taulukossa on monta nimeä on niille vastaavat tiedot vastaavassa indeksissä templates taulukossa.
      • templates: Taulukko taulukoista. Ulomassa taulukossa on taulukko kutakin textkohdassa olevaa nimeä varten.
        Useimmissa tapauksissa sisempiä taulukkoja on vain yksi. Kukin sisempi taulukko on lista yhteen tabiin tulevista template-tiedostoista joita plugin tarjoaa. Template on tekstitiedosto, jonka sisällön editori kopioi tekstiin kun ko. template listään. Jos file puuttuu, niin oletus on file=template, tekstille text=<pluginin nimi>. Jos templates-kohdassa puuttuu expl, niin se on tyhjä. Jos plugin ei halua julkaista templateja, se ei palauta templates-attribuuttia tai palauttaa "templates": []" tai "templates": [[]]".
        • text: teksti jolla template näytetään mm. editorin listassa
        • file: tiedoston nimi, jolla TIMin editori pyytää ko. templatea jos käyttäjä valitsee templaten lisättäväksi
        • expl: editori voi laittaa tämän tekstin vaikka hoverilla templaten valinnan (text) kohdalle.
      • HUOMAA että kaikki templates-tiedostot pitää olla kirjoitettu UTF8-koodauksella.
    2. PALAUTTAA: {"type":"iframe"}

      • jos plugin palauttaa html-kutsusta iframen, niin sitä ei tarvitse kääriä <div>in sisään.
    3. PALAUTTAA: {"type":"simpleEmbedded","css":["munStyylit.css"]}

      • plugin tuottaa pelkän HTML, mutta voi tarvita JSS tai CSS:ää (silloin ne ovat tuossa mukana). Ei tarvitse kääriä <div>in sisään. Plugin ei kommunikoi TIMin kanssa HTML:än antamisen jälkeen.
  • Jos url alkaa http://, niin TIM tulkitsee sen absoluuttiseksi urliksi, mutta muuten url on suhteellinen pluginin osoitteeseen.

4.1.5 /plugin/template/ [GET]

  • SYÖTE:
    • file: templaten nimi
    • idx: templaten indeksi templates-listassa. Eli samalla indeksi sille, monenteenko pluginin ilmoittamaan tabiin sisältö liittyy. Oletus 0, jos puuttuu.
  • PALAUTTAA: templaten tekstimuodossa

Esimerkin tapauksessa idx ei muuta voisi ollakkaan kuin 0, koska plugin ilmoitti sisältöä vain yhteen tabiin.

4.1.6 /plugin/answer/ [PUT]

  • SYÖTE: ks. luku 2.
  • PALAUTTAA: ks. luku 2.
# templates

5. Templateiden lisääminen

Tässä luvussa template-sanalla tarkoitetaan tekstinpätkää, joka lisääntyy editorissa kun vastaava template valitaan Plugins-tabin alta.

Toistaiseksi vain pluginit: csPlugin ja svn tukevat templateja. Tosin niiden tukea voidaan käyttää hyväksi templetejen lisäämiseksi muidenkin pohjatekstien syöttämiseksi.

5.1 Template rakenne svn-pluginissa

Esimerkiksi svn-pluginissa on kolmen pluginin palvelu: svn, image ja video. csPlugin alla on vastaava rakenne. Templateja varten on hakemistot:

/opt/tim/timApp/modules/svn/templates
   svn
       0
   image
       0
   video
       0

5.1.1 Hymiöt tabin lisääminen

Editoriin lisätty Hymiöt tab
Editoriin lisätty Hymiöt tab

Noiden kolmen hakemiston alle voi lisätä tarvittaessa omia templateja vaikka seuraavasti (uutta hakemistoa EI voi tehdä):

Lisätään editoriin Plugins-kohdan alle Video-tabin viereen tab Hymiöt jonka alle tulee kaksi valintaa Nauru ja Itku:

Programs Questions Others Image Video Hymiöt
                                      Itku
                                      Nauru 
                                      Close menu

Lisätään hakemiston (joka on siis oltava joku noista kolmesta, koska se on kovakoodattu svn-pluginiin)

/opt/tim/timApp/modules/svn/templates/video

tiedostoon

tabs.txt

uusi "tabin" nimi vaikkapa Hymiöt lisäämällä uusi rivi:

Video
Hymiöt

Sitten tehdään tämän indeksiä vastaava hakemisto 1 (hakemiston nimi pitää olla sama indeksi kuin monenellako rivillä ko. tabin nimi on) ja sen alle tarvitava määrä tiedostoja vapaasti valittavilla nimillä tyyliin:

nauru

Nauru
Naurava hymiö
:-)

itku

Itku
Itkevä hymiö
;-(

Tiedostot listautuvat tabin alle niiden nimien mukaisessa aakkosjärjestyksessä. Mikäli nauru haluttaisiin listaan ennen itku-riviä, pitäisi tiedostot nimetä esim:

0-nauru
1-itku

Tiedostojen nimet eivät näy mitenkään käyttöliittymään.

Eli itse mallitiedostossa (esim nauru, nimessä ei saa olla pistettä) on rivit:

  • Tabin alla näkyvä teksti
  • tekstille tuleva tootip
  • loput rivit tulevat sellaisenaan editoriin kun template valitaan

Muutosten jälkeen rakenne on siis:

/opt/tim/timApp/modules/svn/templates/video
   tabs.txt
   0         - hakemisto video tempateille
   1         - hakemisto hymiö tempateille
      itku
      nauru

Kun muutokset on tehty, pitää (toistaiseksi) itse tim-kontti käynnistää uudelleen:

/opt/tim$ ./restart.sh tim

Mikäli itse template-tiedostoja nauru tai itku muutetaan 3:sta rivistä eteenpäin, ei tarvitse käynistään uudelleen. Mutta jos tiedostoja lisätään, silloin uudelleenkäynistys tarvitaan, koska templatejen menurakenne on cachessa.

5.1.2 Templaten tekstiosa

Itse templaten tekstiosassa voi olla mitä tahansa, mikä lisätään editoriin kun template valitaan. Esimerkiksi listamuotoista videota varten template-tiedosto on

List video
Video as a list item
``` {plugin="showVideo"}
type: list
stem: "Mistä aliohjelmille parametrejä"
videoname: Luento 3
doctext: 6. Aliohjelmat
doclink: "https://tim.jyu.fi/view/1#aliohjelmat"
start: 39:00
end: 1:19:15
width: 400
height: 300
file: "http://kurssit.it.jyu.fi/ITKP102/2015s/luento/luento03a.mp4"
```
# templparam

5.1.3 Templaten kyselyosa

Templaten tekstiosan alkuun voi lisätä kyselyitä tyyliin:

Tehtävän numero
Sijoittaa tehtävän 
- {"what": "TEHTNRO", "text": "Tehtävän numero", "default": "1", "pattern": "[0-9]+", "error": "Pitää olla luku!"}
``` {#tehtavaTEHTNRO plugin="csPlugin"}
...

Kun tällainen template lisätään, niin ennen lisäämistä kysellään dialogilla noiden

- {"what":

alkuisten rivien mukaan arvot korvattaville parametreille. Huomattavaa että rivin pitää alkaa juuri noin ja niiden pitää olla peräkkäin jos on monta korvattavaa kysymystä. Sitten kaikki korvattavat vaihdetaan annettuun arvoon. Mikäli jonkin kysymykseen vastataan Cancel, mitään ei sijoiteta editoriin.

Parametrien merkitys:

  • what - mitä tekstiä etsitään tekstistä ja korvataan kysymyksen vastauksella, regexp
  • text - kysymyksessä näytettävä teksti
  • default - oletusarvo kysymykselle
  • pattern - regexp jolla vastauksen oikeellisuus tarkistetaan
  • error - teksti joka näytetään, mikäli vastaus ei "mätsää" regexpiin
  • flags - mitä text regexp lippuja käytetään korvauksessa. Oletus tyhjä jos what puuttuu, muuten "gm".

5.1.4 addTemplateAbove

#- {.removePre nocache="true"}
[Lisää HT-aihe]{.addTemplateAbove .timButton .removePre}

    - {"what": "#1", "text": "Syötä työn nimi:", "default": "Työn nimi"}
    - {"what": "#2", "text": "Syötä työn Koko:", "default": "10"}
    - [Anonymous](./Anonymous) - Ryhmä: Anonymous, #1/#2, 
      [git](https://gitlab.jyu.fi/Anonymous/ohj1.git)

5.2 Template-tabit

Mikäli useampi plugin tarjoaa templateja saman tab-nimen alla (esim. Programs) lisää TIM niitä saman tabin alle siinä järjestyksessä missä itse pluginit on TIMille esitelty.

6. Muuta plugineista

These are the current permissions for this document; please modify if needed. You can always modify these permissions from the manage page.