Compare commits

..

2 Commits

Author SHA1 Message Date
c78665b374 chore(deps): lock file maintenance
All checks were successful
continuous-integration/drone/push Build is passing
2024-09-16 00:51:03 +00:00
8733b835b1 chore(deps): lock file maintenance
All checks were successful
continuous-integration/drone/push Build is passing
2024-09-13 11:54:25 +00:00
26 changed files with 2738 additions and 3014 deletions

View File

@ -564,18 +564,6 @@ angular.module("FICApp")
})
.controller("AllTeamAssociationsController", function($scope, $http) {
$scope.newdqa = "";
$scope.addDelegatedQA = function () {
if ($scope.newdqa.length) {
if (!$scope.config.delegated_qa)
$scope.config.delegated_qa = [];
$scope.config.delegated_qa.push($scope.newdqa);
$scope.saveSettings();
$scope.newdqa = "";
}
}
$scope.allAssociations = [];
$http.get("api/teams-associations.json").then(function(response) {
$scope.allAssociations = response.data;
@ -637,16 +625,28 @@ angular.module("FICApp")
$scope.config.disablesubmitbutton = "";
};
$scope.dropDelegatedQA = function (member) {
if (!$scope.config.delegated_qa) {
$scope.newdqa = "";
$scope.addDelegatedQA = function() {
if ($scope.newdqa.length) {
if (!$scope.config.delegated_qa)
$scope.config.delegated_qa = [];
$scope.config.delegated_qa.push($scope.newdqa);
$scope.saveSettings();
$scope.newdqa = "";
}
}
$scope.dropDelegatedQA = function(member) {
if ($scope.config.delegated_qa) {
$scope.config.delegated_qa = [];
angular.forEach($scope.config.delegated_qa, function(m, k) {
if (member == m)
$scope.config.delegated_qa.splice(k, 1);
});
$scope.saveSettings();
}
}
$scope.saveChallengeInfo = function() {
this.challenge.duration = $scope.duration;
@ -2800,11 +2800,9 @@ function presenceCal(scope, location, data) {
.attr("width", cellSize)
.attr("height", cellSize)
.attr("transform", function(d) { return "translate(" + (d.getHours() * cellSize) + "," + (d.getMinutes() / 15 * cellSize) + ")"; })
.attr("class", function (d) {
if (d >= scope.settings.start && d < scope.settings.start + scope.settings.end - scope.settings.start) return color(data.reduce(function (prev, cur) {
.attr("class", function(d) { if (d >= scope.settings.start && d < scope.settings.start + scope.settings.end - scope.settings.start) return color(data.reduce(function(prev, cur){
cur = new Date(cur).getTime();
dv = d.getTime();
return prev + ((dv <= cur && cur < dv+15*60000)?1:0);
}, 0));
});
}, 0)); });
}

View File

@ -306,7 +306,7 @@
</div>
</form>
<div class="card my-3">
<form ng-submit="addDelegatedQA()" class="card my-3">
<div class="card-header">
<h3>Managers QA</h3>
</div>
@ -318,12 +318,10 @@
<span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
</button>
</li>
</ul>
<form class="row" ng-controller="AllTeamAssociationsController" ng-submit="addDelegatedQA()">
<div class="col">
<li class="row">
<div class="col" ng-controller="AllTeamAssociationsController">
<select class="form-control form-control-sm" ng-model="newdqa">
<option ng-selected="newdqa == m" ng-repeat="(i,m) in allAssociations" ng-value="m">{{ m }}</option>
<option ng-repeat="(i,m) in allAssociations" ng-value="m">{{ m }}</option>
</select>
</div>
<div class="col input-group">
@ -332,9 +330,10 @@
<button class="btn btn-sm btn-success" ng-disabled="!newdqa.length"><span class="glyphicon glyphicon-plus" aria-hidden="true"></span></button>
</span>
</div>
</li>
</ul>
</div>
</form>
</div>
</div>
<form ng-submit="saveChallengeInfo()" class="card my-3">
<div class="card-header">

View File

@ -365,7 +365,7 @@
<div class="carousel slide" data-interval="12000" style="padding-bottom: 0px" autocarousel>
<div class="carousel-inner">
<div class="carousel-item" ng-repeat="theme in themes" ng-class="{active: $first}">
<div class="carousel-caption text-indent" ng-if="theme.urlid !== '_'">
<div class="carousel-caption text-indent">
<div class="card-img-top theme-card" style="background-image: url('{{ theme.image.substr(0, theme.image.length-3) }}thumb.jpg')"></div>
<h3 class="text-left" ng-bind="theme.name"></h3>
<p class="text-justify" style="font-size: 111%" ng-bind-html="theme.headline"></p>
@ -377,8 +377,7 @@
</div>
<div class="card niceborder bg-dark" ng-if="s.type == 'exercice' && !s.params.hide">
<div class="card-img-top theme-card" style="background-image: url('{{exercices[s.params.exercice].image}}')" ng-if="exercices[s.params.exercice] && exercices[s.params.exercice].image"></div>
<div class="card-img-top theme-card" style="background-image: url('{{themes[my.exercices[s.params.exercice].theme_id].image}}')" ng-if="!exercices[s.params.exercice] || !exercices[s.params.exercice].image"></div>
<div class="card-img-top theme-card" style="background-image: url('{{themes[my.exercices[s.params.exercice].theme_id].image}}')"></div>
<div class="card-body text-light">
<h3 style="font-size: 1.0rem; text-weight: bold; overflow: hidden; text-overflow: ellipsis; white-space: nowrap">Défi <em>{{ exercices[s.params.exercice].title }}</em> du thème <em>{{ themes[my.exercices[s.params.exercice].theme_id].name }}</em></h3>
<p ng-bind-html="my.exercices[s.params.exercice].overview"></p>
@ -398,8 +397,7 @@
Challenges à la une
</span>
</div>
<div class="card-img-top theme-card" style="background-image: url('{{exercices[lastExercice].image}}')" ng-if="exercices[lastExercice] && exercices[lastExercice].image"></div>
<div class="card-img-top theme-card" style="background-image: url('{{themes[my.exercices[lastExercice].theme_id].image}}')" ng-if="!exercices[s.params.exercice] || !exercices[s.params.exercice].image"></div>
<div class="card-img-top theme-card" style="background-image: url('{{themes[my.exercices[lastExercice].theme_id].image}}')"></div>
<div class="card-body text-light">
<h3 style="font-size: 1.0rem; text-weight: bold; overflow: hidden; text-overflow: ellipsis; white-space: nowrap"><em>{{ exercices[lastExercice].title }}</em> du thème <em>{{ themes[my.exercices[lastExercice].theme_id].name }}</em></h3>
<p ng-bind-html="my.exercices[lastExercice].overview"></p>

View File

@ -1,6 +1,5 @@
<script>
import { base } from '$app/paths';
import { page } from '$app/stores';
import {
Badge,
@ -63,7 +62,7 @@
<NavbarToggler on:click={() => (isOpen = !isOpen)} />
<Collapse {isOpen} navbar expand="md" on:update={handleUpdate}>
<Nav navbar>
<NavItem active={$page.route && $page.route.id === "/"}>
<NavItem>
<NavLink href=".">
<Icon name="house" />
Accueil
@ -72,7 +71,7 @@
<NavThemes />
<NavTags />
{#if $settings && $settings.end - $settings.start > 0 && $teams && Object.keys($teams).length}
<NavItem active={$page.route && $page.route.id === "/rank"}>
<NavItem>
<NavLink href="rank">
<Icon name="sort-down" />
Classement
@ -80,7 +79,7 @@
</NavItem>
{/if}
<HeaderIssues />
<NavItem active={$page.route && $page.route.id === "/rules"}>
<NavItem>
<NavLink href="rules">
<Icon name="signpost-split" />
Aide

View File

@ -1,6 +1,4 @@
<script>
import { page } from '$app/stores';
import {
Badge,
Icon,
@ -26,7 +24,7 @@
</script>
{#if $issues.length}
<NavItem active={$page.route && $page.route.id === "/issues"}>
<NavItem>
<NavLink href="issues">
<Icon name="bug" />
Problèmes

View File

@ -1,6 +1,4 @@
<script>
import { page } from '$app/stores';
import {
Badge,
Dropdown,
@ -16,7 +14,7 @@
let filter = "";
</script>
<Dropdown nav inNavbar active={$page.params.tag}>
<Dropdown nav inNavbar>
<DropdownToggle nav caret>
<Icon name="tags" />
Tags

View File

@ -14,7 +14,6 @@
import { myThemes, themes } from '$lib/stores/mythemes.js';
</script>
{#if $themes.length > 0 && ($themes[0].id != 0 || $themes.length > 1)}
<Dropdown nav inNavbar active={$current_theme && $current_theme.id != 0}>
<DropdownToggle nav caret>
<Icon name="tv" />
@ -55,7 +54,6 @@
</div>
</DropdownMenu>
</Dropdown>
{/if}
{#if $themesStore && $themesStore["0"] && $themesStore["0"].exercices}
<Dropdown nav inNavbar active={$current_theme && $current_theme && $current_theme.id == 0}>
<DropdownToggle nav caret>

View File

@ -73,9 +73,7 @@
</Column>
</Table>
{:else}
<CardBody>
Vous n'avez fait aucune action vous faisant gagner ou perdre des points.
</CardBody>
{/if}
<button class="btn btn-primary" on:click={refresh_scores}>
<Icon name="arrow-clockwise" />

View File

@ -45,7 +45,6 @@
{#if exercices.length}
<Row cols="3">
{#key exercices}
{#each exercices as {theme, exercice, index} (index)}
<Col class="mb-3">
<CardTheme
@ -55,7 +54,6 @@
/>
</Col>
{/each}
{/key}
</Row>
{:else}
<p class="lead">

View File

@ -56,26 +56,6 @@ func GetThemes() ([]*Theme, error) {
}
}
// GetThemesExtended returns a list of Themes including standalone exercices.
func GetThemesExtended() ([]*Theme, error) {
if themes, err := GetThemes(); err != nil {
return nil, err
} else {
// Append standalone exercices fake-themes
stdthm := &Theme{
Name: "Défis indépendants",
URLId: "_",
Path: "exercices",
}
if exercices, err := stdthm.GetExercices(); err == nil && len(exercices) > 0 {
themes = append(themes, stdthm)
}
return themes, nil
}
}
// GetTheme retrieves a Theme from its identifier.
func GetTheme(id int64) (*Theme, error) {
t := &Theme{}

View File

@ -38,10 +38,6 @@ func init() {
oidcRedirectURL = v
}
if v, ok := os.LookupEnv("FIC_GITLAB_BASEURL"); ok {
gitlabBaseURL = v
}
flag.StringVar(&oidcRedirectURL, "oidc-redirect", oidcRedirectURL, "Base URL for the redirect after connection")
flag.StringVar(&gitlabBaseURL, "gitlab-baseurl", gitlabBaseURL, "Base URL of the Gitlab instance")
flag.StringVar(&gitlabClientID, "gitlab-clientid", os.Getenv("FIC_GITLAB_CLIENT_ID"), "ClientID for GitLab's OIDC")

View File

@ -118,7 +118,7 @@ func getExerciceQA(c *gin.Context) {
func exportQA(c *gin.Context) {
var report string
themes, err := fic.GetThemesExtended()
themes, err := fic.GetThemes()
if err != nil {
log.Println("Unable to GetThemes: ", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to list themes: %s", err.Error())})
@ -191,7 +191,7 @@ type ExportReport struct {
func exportQAJSON(c *gin.Context) {
report := map[string]ExportTheme{}
themes, err := fic.GetThemesExtended()
themes, err := fic.GetThemes()
if err != nil {
log.Println("Unable to GetThemes: ", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to list themes: %s", err.Error())})

View File

@ -1,11 +1,8 @@
package api
import (
"fmt"
"log"
"net/http"
"strconv"
"strings"
"srs.epita.fr/fic-server/libfic"
@ -20,7 +17,6 @@ func declareTodoRoutes(router *gin.RouterGroup) {
}
func declareTodoManagerRoutes(router *gin.RouterGroup) {
router.POST("/qa_assign_work", assignWork)
router.POST("/qa_my_exercices.json", addQAView)
router.POST("/qa_work.json", createQATodo)
@ -239,80 +235,3 @@ func deleteQATodo(c *gin.Context) {
c.Status(http.StatusOK)
}
}
type QAAssignWork struct {
Turns int `json:"turns"`
Start int `json:"start"`
TeamPrefix string `json:"team_prefix"`
TeamAssistants string `json:"team_assistants"`
}
func assignWork(c *gin.Context) {
var uaw QAAssignWork
if err := c.ShouldBindJSON(&uaw); err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
return
}
if uaw.Turns == 0 {
uaw.Turns = 1
}
if uaw.TeamPrefix == "" {
uaw.TeamPrefix = "FIC Groupe "
}
if uaw.TeamAssistants == "" {
uaw.TeamAssistants = "assistants"
}
teams, err := fic.GetTeams()
if err != nil {
log.Println("Unable to GetTeams: ", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to list teams: %s", err.Error())})
return
}
// Remove assistant team
for tid := len(teams) - 1; tid >= 0; tid-- {
team := teams[tid]
if strings.Contains(strings.ToLower(team.Name), uaw.TeamAssistants) {
teams = append(teams[:tid], teams[tid+1:]...)
}
}
exercices, err := fic.GetExercices()
if err != nil {
log.Println("Unable to GetExercices: ", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to list exercices: %s", err.Error())})
return
}
// Struct to store reported team (due to owned exercice)
var teamIdStack []int64
for i := 0; i < uaw.Turns; i++ {
for eid, ex := range exercices {
team := teams[(uaw.Start+eid+uaw.Turns*i)%len(teams)]
if len(teamIdStack) > 0 {
teamIdStack = append(teamIdStack, team.Id)
team, _ = fic.GetTeam(teamIdStack[0])
teamIdStack = append([]int64{}, teamIdStack[1:]...)
}
j := 0
// Find a team not responsible for this exercice
for (strings.Contains(ex.Path, "grp") && strings.Contains(ex.Path, "-grp"+strings.TrimPrefix(team.Name, uaw.TeamPrefix)+"-")) || (!strings.Contains(ex.Path, "grp") && strings.HasPrefix(ex.Path, strings.TrimPrefix(team.Name, uaw.TeamPrefix)+"-")) {
j += 1
teamIdStack = append(teamIdStack, team.Id)
team = teams[(uaw.Start+eid+uaw.Turns*i+j)%len(teams)]
}
_, err := team.NewQATodo(ex.Id)
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
return
}
}
}
c.JSON(http.StatusOK, "true")
}

View File

@ -15,11 +15,6 @@ import (
func reloadSettings(config *settings.Settings) {
api.ManagerUsers = config.DelegatedQA
fic.UnlockedChallengeDepth = config.UnlockedChallengeDepth
fic.UnlockedChallengeUpTo = config.UnlockedChallengeUpTo
fic.UnlockedStandaloneExercices = config.UnlockedStandaloneExercices
fic.UnlockedStandaloneExercicesByThemeStepValidation = config.UnlockedStandaloneExercicesByThemeStepValidation
fic.UnlockedStandaloneExercicesByStandaloneExerciceValidation = config.UnlockedStandaloneExercicesByStandaloneExerciceValidation
}
func main() {

View File

@ -23,7 +23,6 @@
} from '@sveltestrap/sveltestrap';
import { auth, gitlab, version } from '$lib/stores/auth';
import { themes } from '$lib/stores/themes';
export let activemenu = "";
$: {
@ -51,7 +50,6 @@
<span class="d-none d-md-inline">Accueil</span>
</NavLink>
</NavItem>
{#if $themes.length}
<NavItem>
<NavLink
href="themes"
@ -61,7 +59,6 @@
<span class="d-none d-md-inline">Scénarios</span>
</NavLink>
</NavItem>
{/if}
<NavItem>
<NavLink
href="exercices"
@ -81,6 +78,15 @@
<span class="d-none d-md-inline">Équipes</span>
</NavLink>
</NavItem>
<NavItem>
<NavLink
href="repositories"
active={activemenu === 'repositories'}
>
<Icon name="archive" />
<span class="d-none d-md-inline">Dépôts</span>
</NavLink>
</NavItem>
{/if}
</Nav>
<Nav class="ms-auto text-light" navbar>

View File

@ -83,8 +83,6 @@
<td>
{#if $exercicesIdx.length == 0 && $themesIdx.length == 0}
<Spinner size="sm" />
{:else if !$themesIdx[$exercicesIdx[todo.id_exercice].id_theme]}
Défis indépendants
{:else}
<a href="themes/{$exercicesIdx[todo.id_exercice].id_theme}">
{$themesIdx[$exercicesIdx[todo.id_exercice].id_theme].name}

View File

@ -29,7 +29,6 @@
style="overflow-y: auto"
>
{#await themesP then themes}
{#if Object.keys(themes).length > 1}
<Row
style={'min-width:'+15*Object.keys(themes).length + 'vw'}
>
@ -70,48 +69,5 @@
</Col>
{/each}
</Row>
{:else}
{#each Object.keys(themes) as tname}
{#if themes[tname].exercices}
{#each themes[tname].exercices as exercice}
<Row
style={'min-width:'+15*Object.keys(themes).length + 'vw'}
>
<Col style="border-right: 1px solid lightgray">
<h3
class="text-center py-3 mb-3"
style="border-bottom: 2px solid black"
on:click={() => goto(`exercices/${exercice.exercice.id}`)}
>
{exercice.exercice.title}
</h3>
{#if exercice.reports}
{#each exercice.reports as report}
<Card
class="mb-3"
color={state2Color(report.report.state)}
style="cursor: pointer"
on:click={() => goto(`exercices/${exercice.exercice.id}/${report.report.id}`)}
>
<CardBody class="p-2">
{report.report.subject}
{#if report.comments}
<Badge
class="float-end"
>
{report.comments.length}
</Badge>
{/if}
</CardBody>
</Card>
{/each}
<hr />
{/if}
</Col>
</Row>
{/each}
{/if}
{/each}
{/if}
{/await}
</div>

View File

@ -44,7 +44,6 @@ export const exercicesByTheme = derived(
const exercices_idx = { };
for (const e of $exercices) {
if (!e.id_theme) e.id_theme = 0;
if (!exercices_idx[e.id_theme]) {
exercices_idx[e.id_theme] = []
}

View File

@ -16,7 +16,6 @@ export function createTodosStore(team) {
refresh: async () => {
const list = await getQATodo(team);
list.map((e) => e.id += 10000000);
list.push(...await getQAWork(team));
update((m) => list);
return list;

View File

@ -22,14 +22,11 @@ export class Team {
toHexColor() {
let num = this.color;
num >>>= 0;
let b = (num & 0xFF).toString(16),
g = ((num & 0xFF00) >>> 8).toString(16),
r = ((num & 0xFF0000) >>> 16).toString(16),
let b = num & 0xFF,
g = (num & 0xFF00) >>> 8,
r = (num & 0xFF0000) >>> 16,
a = ( (num & 0xFF000000) >>> 24 ) / 255 ;
if (r.length <= 1) r = "0" + r;
if (g.length <= 1) g = "0" + g;
if (b.length <= 1) b = "0" + b;
return "#" + r + g + b;
return "#" + r.toString(16) + g.toString(16) + b.toString(16);
}
}

View File

@ -23,12 +23,7 @@ export class Theme {
export async function getThemes() {
const res = await fetch(`api/themes`, {headers: {'Accept': 'application/json'}})
if (res.status == 200) {
const data = await res.json();
if (data) {
return data.map((t) => new Theme(t));
} else {
return [];
}
return (await res.json()).map((t) => new Theme(t));
} else {
throw new Error((await res.json()).errmsg);
}

View File

@ -43,7 +43,7 @@
<tbody>
{#each exercices as exercice (exercice.id)}
{#if exercice.title.indexOf(query) >= 0}
<tr on:click={() => show(exercice.id)} style="cursor: pointer">
<tr on:click={() => show(exercice.id)}>
{#each fieldsExercices as field}
<td>
{@html exercice[field]}

View File

@ -4,12 +4,7 @@
import { teams } from '$lib/stores/teams';
import {
Button,
Container,
FormGroup,
Input,
Label,
Spinner,
Table,
} from '@sveltestrap/sveltestrap';
@ -21,44 +16,6 @@
function show(id) {
goto("teams/" + id)
}
let start = 0;
let turns = 3;
let team_prefix = "";
let team_assistants = "";
let assignInProgress = false;
async function assignExercices() {
assignInProgress = true;
const res = await fetch(`api/qa_assign_work`, {
method: 'POST',
headers: {'Accept': 'application/json'},
body: JSON.stringify({
start,
turns,
team_prefix,
team_assistants,
}),
})
if (res.status == 200) {
teams.refresh();
assignInProgress = false;
} else {
assignInProgress = false;
throw new Error((await res.json()).errmsg);
}
}
async function deleteAssignation() {
const res = await fetch(`api/qa_assign_work`, {
method: 'DELETE',
});
if (res.status == 200) {
teams.refresh();
} else {
throw new Error((await res.json()).errmsg);
}
}
</script>
<Container class="mt-2 mb-5">
@ -82,19 +39,10 @@
<tbody>
{#each $teams as team (team.id)}
{#if team.name.indexOf(query) >= 0}
<tr on:click={() => show(team.id)} style="cursor: pointer">
<tr on:click={() => show(team.id)}>
{#each fields as field}
<td class:text-end={field == "image"}>
{#if field == "color"}
<div
class="badge"
style={"background-color: " + team.toHexColor()}
>
{team.toHexColor()}
</div>
{:else}
{team[field]}
{/if}
</td>
{/each}
</tr>
@ -102,48 +50,4 @@
{/each}
</tbody>
</Table>
<hr>
<h2>
Assigner des exercices aux équipes
</h2>
<form on:submit|preventDefault={assignExercices}>
<FormGroup>
<Label for="ae-start">Compteur de départ</Label>
<Input type="number" id="ae-start" bind:value={start} />
<p class="form-text">
Incrémenter de 1 pour chaque nouveau challenge blanc, cela décale l'attribution des exercices.
</p>
</FormGroup>
<FormGroup>
<Label for="ae-turns">Nombre d'itérations</Label>
<Input type="number" id="ae-turns" bind:value={turns} />
</FormGroup>
<FormGroup>
<Label for="ae-prefix">Préfixe des noms d'équipes</Label>
<Input id="ae-prefix" bind:value={team_prefix} placeholder="FIC Groupe" />
</FormGroup>
<FormGroup>
<Label for="ae-assistants">Nom de l'équipe assistants</Label>
<Input id="ae-assistants" bind:value={team_assistants} placeholder="Assistants" />
</FormGroup>
<Button
type="submit"
disabled={assignInProgress}
color="primary"
>
{#if assignInProgress}
<Spinner size="sm" />
{/if}
Assigner des exercices
</Button>
<Button
type="button"
color="danger"
on:click={deleteAssignation}
>
Supprimer toute assignation
</Button>
</form>
</Container>

View File

@ -102,8 +102,6 @@
<td>
{#if $exercicesIdx.length == 0 && $themesIdx.length == 0}
<Spinner size="sm" />
{:else if !$themesIdx[$exercicesIdx[todo.id_exercice].id_theme]}
Défis indépendants
{:else}
<a href="themes/{$exercicesIdx[todo.id_exercice].id_theme}">
{$themesIdx[$exercicesIdx[todo.id_exercice].id_theme].name}
@ -143,7 +141,7 @@
bind:value={newTodo}
>
{#each Object.keys($exercicesByTheme) as thid}
<optgroup label={(thid != "0" ? $themesIdx[thid].name : "Exercices indépendants")}>
<optgroup label={$themesIdx[thid].name}>
{#each $exercicesByTheme[thid] as exercice (exercice.id)}
<option value={exercice.id}>{exercice.title}</option>
{/each}
@ -173,9 +171,7 @@
bind:value={newThemeTodo}
>
{#each Object.keys($exercicesByTheme) as thid}
{#if thid != "0"}
<option value={$themesIdx[thid].id}>{$themesIdx[thid].name}</option>
{/if}
{/each}
</select>
<Button

View File

@ -60,7 +60,7 @@
<tbody>
{#each $themes as theme (theme.id)}
{#if theme.name.indexOf(query) >= 0 || theme.authors.indexOf(query) >= 0 || theme.intro.indexOf(query) >= 0}
<tr on:click={() => show(theme.id)} style="cursor: pointer">
<tr on:click={() => show(theme.id)}>
{#each fields as field}
<td class:text-end={field == "image"}>
{#if field == "image"}

View File

@ -63,7 +63,7 @@
<tbody>
{#each exercices as exercice (exercice.id)}
{#if exercice.title.indexOf(query) >= 0}
<tr on:click={() => show(exercice.id)} style="cursor: pointer">
<tr on:click={() => show(exercice.id)}>
{#each fieldsExercices as field}
<td>
{@html exercice[field]}