diff --git a/admin/static/js/app.js b/admin/static/js/app.js
index f7a22a44..f929b297 100644
--- a/admin/static/js/app.js
+++ b/admin/static/js/app.js
@@ -1405,7 +1405,7 @@ angular.module("FICApp")
})
.controller("ThemeController", function($scope, Theme, $routeParams, $location, $rootScope, $http) {
$scope.theme = Theme.get({ themeId: $routeParams.themeId });
- $scope.fields = ["name", "urlid", "authors", "headline", "intro", "image"];
+ $scope.fields = ["name", "urlid", "authors", "headline", "intro", "image", "partner_txt", "partner_href", "partner_img"];
$scope.saveTheme = function() {
if (this.theme.id) {
diff --git a/admin/sync/themes.go b/admin/sync/themes.go
index 2e468c81..677c5229 100644
--- a/admin/sync/themes.go
+++ b/admin/sync/themes.go
@@ -153,6 +153,22 @@ func BuildTheme(i Importer, tdir string) (th *fic.Theme, errs []string) {
errs = append(errs, fmt.Sprintf("%q: heading.jpg: No such file", tdir))
}
+ if i.exists(path.Join(tdir, "partner.jpg")) {
+ th.PartnerImage = path.Join(tdir, "partner.jpg")
+ } else if i.exists(path.Join(tdir, "partner.png")) {
+ th.PartnerImage = path.Join(tdir, "partner.png")
+ }
+
+ if i.exists(path.Join(tdir, "partner.txt")) {
+ if txt, err := getFileContent(i, path.Join(tdir, "partner.txt")); err != nil {
+ errs = append(errs, fmt.Sprintf("%q: unable to get partner's text: %s", th.Name, err))
+ } else {
+ th.PartnerText, err = ProcessMarkdown(i, txt, tdir)
+ if err != nil {
+ errs = append(errs, fmt.Sprintf("%q: partner.txt: an error occurs during markdown formating: %s", tdir, err))
+ }
+ }
+ }
return
}
@@ -189,6 +205,16 @@ func SyncThemes(i Importer) []string {
}
}
+ if len(btheme.PartnerImage) > 0 {
+ if _, err := i.importFile(btheme.PartnerImage,
+ func(filePath string, origin string) (interface{}, error) {
+ btheme.PartnerImage = strings.TrimPrefix(filePath, fic.FilesDir)
+ return nil, nil
+ }); err != nil {
+ errs = append(errs, fmt.Sprintf("%q: unable to import partner image: %s", tdir, err))
+ }
+ }
+
var theme fic.Theme
if theme, err = fic.GetThemeByPath(btheme.Path); err != nil {
if _, err := fic.CreateTheme(*btheme); err != nil {
diff --git a/frontend/ui/src/routes/[theme]/index.svelte b/frontend/ui/src/routes/[theme]/index.svelte
index ef3e6c23..defe4db5 100644
--- a/frontend/ui/src/routes/[theme]/index.svelte
+++ b/frontend/ui/src/routes/[theme]/index.svelte
@@ -12,7 +12,13 @@
import {
Alert,
Badge,
+ Button,
+ Card,
+ CardBody,
+ CardTitle,
+ Col,
Icon,
+ Row,
} from 'sveltestrap';
import { goto } from '$app/navigation';
@@ -23,12 +29,37 @@
{#if theme && theme.exercices}
-
+
-
-
{@html theme.headline}
-
{@html theme.intro}
-
+
+
+
+ {@html theme.headline}
+ {@html theme.intro}
+
+ {#if theme.partner_txt || theme.partner_img || theme.partner_href}
+
+
+ {#if theme.partner_img}
+
+ {/if}
+ {#if theme.partner_txt || theme.partner_href}
+
+ {#if theme.partner_txt}
+ {@html theme.partner_txt}
+ {/if}
+ {#if theme.partner_href}
+
+ {/if}
+
+ {/if}
+
+
+ {/if}
+
+
{#each Object.keys(theme.exercices) as k, index}
@@ -88,7 +119,7 @@
{/each}
-
+
{:else}
diff --git a/libfic/db.go b/libfic/db.go
index e63336f4..f6c130e4 100644
--- a/libfic/db.go
+++ b/libfic/db.go
@@ -85,7 +85,10 @@ CREATE TABLE IF NOT EXISTS themes(
headline TEXT NOT NULL,
intro TEXT NOT NULL,
image VARCHAR(255) NOT NULL,
- authors TEXT NOT NULL
+ authors TEXT NOT NULL,
+ partner_img VARCHAR(255) NOT NULL,
+ partner_href VARCHAR(255) NOT NULL,
+ partner_text TEXT NOT NULL
) DEFAULT CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
`); err != nil {
return err
diff --git a/libfic/theme.go b/libfic/theme.go
index e4f07ede..e461f11f 100644
--- a/libfic/theme.go
+++ b/libfic/theme.go
@@ -4,24 +4,27 @@ import ()
// Theme represents a group of challenges, to display to players
type Theme struct {
- Id int64 `json:"id"`
- Name string `json:"name"`
- URLId string `json:"urlid"`
- Path string `json:"path"`
- Authors string `json:"authors,omitempty"`
- Intro string `json:"intro,omitempty"`
- Headline string `json:"headline,omitempty"`
- Image string `json:"image,omitempty"`
+ Id int64 `json:"id"`
+ Name string `json:"name"`
+ URLId string `json:"urlid"`
+ Path string `json:"path"`
+ Authors string `json:"authors,omitempty"`
+ Intro string `json:"intro,omitempty"`
+ Headline string `json:"headline,omitempty"`
+ Image string `json:"image,omitempty"`
+ PartnerImage string `json:"partner_img,omitempty"`
+ PartnerLink string `json:"partner_href,omitempty"`
+ PartnerText string `json:"partner_txt,omitempty"`
}
// CmpTheme returns true if given Themes are identicals.
func CmpTheme(t1 Theme, t2 Theme) bool {
- return !(t1.Name != t2.Name || t1.URLId != t2.URLId || t1.Path != t2.Path || t1.Authors != t2.Authors || t1.Intro != t2.Intro || t1.Headline != t2.Headline || t1.Image != t2.Image)
+ return !(t1.Name != t2.Name || t1.URLId != t2.URLId || t1.Path != t2.Path || t1.Authors != t2.Authors || t1.Intro != t2.Intro || t1.Headline != t2.Headline || t1.Image != t2.Image || t1.PartnerImage != t2.PartnerImage || t1.PartnerLink != t2.PartnerLink || t1.PartnerText != t2.PartnerText)
}
// GetThemes returns a list of registered Themes from the database.
func GetThemes() ([]Theme, error) {
- if rows, err := DBQuery("SELECT id_theme, name, url_id, path, authors, intro, headline, image FROM themes"); err != nil {
+ if rows, err := DBQuery("SELECT id_theme, name, url_id, path, authors, intro, headline, image, partner_img, partner_href, partner_text FROM themes"); err != nil {
return nil, err
} else {
defer rows.Close()
@@ -29,7 +32,7 @@ func GetThemes() ([]Theme, error) {
var themes = make([]Theme, 0)
for rows.Next() {
var t Theme
- if err := rows.Scan(&t.Id, &t.Name, &t.URLId, &t.Path, &t.Authors, &t.Intro, &t.Headline, &t.Image); err != nil {
+ if err := rows.Scan(&t.Id, &t.Name, &t.URLId, &t.Path, &t.Authors, &t.Intro, &t.Headline, &t.Image, &t.PartnerImage, &t.PartnerLink, &t.PartnerText); err != nil {
return nil, err
}
themes = append(themes, t)
@@ -45,7 +48,7 @@ func GetThemes() ([]Theme, error) {
// GetTheme retrieves a Theme from its identifier.
func GetTheme(id int64) (Theme, error) {
var t Theme
- if err := DBQueryRow("SELECT id_theme, name, url_id, path, authors, intro, headline, image FROM themes WHERE id_theme=?", id).Scan(&t.Id, &t.Name, &t.URLId, &t.Path, &t.Authors, &t.Intro, &t.Headline, &t.Image); err != nil {
+ if err := DBQueryRow("SELECT id_theme, name, url_id, path, authors, intro, headline, image, partner_img, partner_href, partner_text FROM themes WHERE id_theme=?", id).Scan(&t.Id, &t.Name, &t.URLId, &t.Path, &t.Authors, &t.Intro, &t.Headline, &t.Image, &t.PartnerImage, &t.PartnerLink, &t.PartnerText); err != nil {
return t, err
}
@@ -55,7 +58,7 @@ func GetTheme(id int64) (Theme, error) {
// GetThemeByName retrieves a Theme from its title
func GetThemeByName(name string) (Theme, error) {
var t Theme
- if err := DBQueryRow("SELECT id_theme, name, url_id, path, authors, intro, headline, image FROM themes WHERE name=?", name).Scan(&t.Id, &t.Name, &t.URLId, &t.Path, &t.Authors, &t.Intro, &t.Headline, &t.Image); err != nil {
+ if err := DBQueryRow("SELECT id_theme, name, url_id, path, authors, intro, headline, image, partner_img, partner_text FROM themes WHERE name=?", name).Scan(&t.Id, &t.Name, &t.URLId, &t.Path, &t.Authors, &t.Intro, &t.Headline, &t.Image, &t.PartnerImage, &t.PartnerText); err != nil {
return t, err
}
@@ -65,7 +68,7 @@ func GetThemeByName(name string) (Theme, error) {
// GetThemeByPath retrieves a Theme from its dirname
func GetThemeByPath(dirname string) (Theme, error) {
var t Theme
- if err := DBQueryRow("SELECT id_theme, name, url_id, path, authors, intro, headline, image FROM themes WHERE path=?", dirname).Scan(&t.Id, &t.Name, &t.URLId, &t.Path, &t.Authors, &t.Intro, &t.Headline, &t.Image); err != nil {
+ if err := DBQueryRow("SELECT id_theme, name, url_id, path, authors, intro, headline, image, partner_img, partner_href, partner_text FROM themes WHERE path=?", dirname).Scan(&t.Id, &t.Name, &t.URLId, &t.Path, &t.Authors, &t.Intro, &t.Headline, &t.Image, &t.PartnerImage, &t.PartnerLink, &t.PartnerText); err != nil {
return t, err
}
@@ -74,7 +77,7 @@ func GetThemeByPath(dirname string) (Theme, error) {
// CreateTheme creates and fills a new struct Theme and registers it into the database.
func CreateTheme(theme Theme) (Theme, error) {
- if res, err := DBExec("INSERT INTO themes (name, url_id, authors, path, intro, headline, image) VALUES (?, ?, ?, ?, ?, ?, ?)", theme.Name, theme.URLId, theme.Authors, theme.Path, theme.Intro, theme.Headline, theme.Image); err != nil {
+ if res, err := DBExec("INSERT INTO themes (name, url_id, authors, path, intro, headline, image, partner_img, partner_href, partner_text) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", theme.Name, theme.URLId, theme.Authors, theme.Path, theme.Intro, theme.Headline, theme.Image, theme.PartnerImage, theme.PartnerLink, theme.PartnerText); err != nil {
return Theme{}, err
} else if theme.Id, err = res.LastInsertId(); err != nil {
return Theme{}, err
@@ -95,7 +98,7 @@ func (t *Theme) FixURLId() bool {
// Update applies modifications back to the database.
func (t Theme) Update() (int64, error) {
- if res, err := DBExec("UPDATE themes SET name = ?, url_id = ?, authors = ?, path = ?, intro = ?, headline = ?, image = ? WHERE id_theme = ?", t.Name, t.URLId, t.Authors, t.Path, t.Intro, t.Headline, t.Image, t.Id); err != nil {
+ if res, err := DBExec("UPDATE themes SET name = ?, url_id = ?, authors = ?, path = ?, intro = ?, headline = ?, image = ?, partner_img = ?, partner_href = ?, partner_text = ? WHERE id_theme = ?", t.Name, t.URLId, t.Authors, t.Path, t.Intro, t.Headline, t.Image, t.PartnerImage, t.PartnerLink, t.PartnerText, t.Id); err != nil {
return 0, err
} else if nb, err := res.RowsAffected(); err != nil {
return 0, err
diff --git a/libfic/theme_export.go b/libfic/theme_export.go
index c60caa3d..6834a38f 100644
--- a/libfic/theme_export.go
+++ b/libfic/theme_export.go
@@ -20,13 +20,16 @@ type exportedExercice struct {
// exportedTheme is a structure representing a Theme, as exposed to players.
type exportedTheme struct {
- Name string `json:"name"`
- URLId string `json:"urlid"`
- Authors string `json:"authors"`
- Headline string `json:"headline,omitempty"`
- Intro string `json:"intro"`
- Image string `json:"image,omitempty"`
- Exercices map[string]exportedExercice `json:"exercices"`
+ Name string `json:"name"`
+ URLId string `json:"urlid"`
+ Authors string `json:"authors"`
+ Headline string `json:"headline,omitempty"`
+ Intro string `json:"intro"`
+ Image string `json:"image,omitempty"`
+ PartnerImage string `json:"partner_img,omitempty"`
+ PartnerLink string `json:"partner_href,omitempty"`
+ PartnerText string `json:"partner_txt,omitempty"`
+ Exercices map[string]exportedExercice `json:"exercices"`
}
// Exportedthemes exports themes from the database, to be displayed to players.
@@ -59,6 +62,11 @@ func ExportThemes() (interface{}, error) {
imgpath = path.Join(FilesDir, theme.Image)
}
+ partnerImgpath := ""
+ if len(theme.PartnerImage) > 0 {
+ partnerImgpath = path.Join(FilesDir, theme.PartnerImage)
+ }
+
ret[fmt.Sprintf("%d", theme.Id)] = exportedTheme{
theme.Name,
theme.URLId,
@@ -66,6 +74,9 @@ func ExportThemes() (interface{}, error) {
theme.Headline,
strings.Replace(theme.Intro, "$FILES$", FilesDir, -1),
imgpath,
+ partnerImgpath,
+ theme.PartnerLink,
+ theme.PartnerText,
exos,
}
}