Compare commits
43 Commits
1b92fd269e
...
signals-co
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c4f887bf33 | ||
|
|
61ec781bbb | ||
|
|
3ee41cf571 | ||
|
|
7f2e762a44 | ||
|
|
cfa3015a7b | ||
|
|
778a92fd5e | ||
| ff52a198dc | |||
|
|
f3d59a0ef3 | ||
|
|
d84485e52b | ||
|
|
f789d89995 | ||
|
|
7e0174bcc2 | ||
|
|
fe1d59a3bb | ||
|
|
69a99c9312 | ||
|
|
a6414ae64d | ||
|
|
9cf47f0e2a | ||
|
|
6c89562dc3 | ||
|
|
e85eabbed5 | ||
|
|
1ec4ba8212 | ||
|
|
a1ff181443 | ||
|
|
ee8f48bc43 | ||
|
|
7ec1aee884 | ||
|
|
a3adfa8ee0 | ||
|
|
d893afa1f3 | ||
|
|
d984128176 | ||
|
|
f8d73c9ed0 | ||
|
|
208b935ffa | ||
|
|
f12dfc7029 | ||
|
|
98a890e915 | ||
|
|
0c1b52d734 | ||
|
|
3f6764dd7d | ||
|
|
67c3d0b3e6 | ||
|
|
36208ef071 | ||
|
|
b546a0cf01 | ||
|
|
3935f6ad21 | ||
|
|
fd5ad7e88e | ||
|
|
8c957fe694 | ||
|
|
cb0ef7ddd5 | ||
|
|
42e466fe8b | ||
|
|
2c5fa4fa13 | ||
|
|
ebce44c889 | ||
|
|
955dc48f51 | ||
|
|
29e75e6298 | ||
|
|
187fd105d3 |
@@ -7,6 +7,6 @@ RUN npm run build-prod-fr
|
||||
|
||||
FROM nginx:1.27-alpine AS final
|
||||
WORKDIR /app
|
||||
COPY --from=builder /app/dist/codiki-ng/en/browser /usr/share/nginx/html/en/
|
||||
COPY --from=builder /app/dist/codiki-ng/fr/browser /usr/share/nginx/html/fr/
|
||||
COPY --from=builder /app/dist/codiki/en/browser /usr/share/nginx/html/en/
|
||||
COPY --from=builder /app/dist/codiki/fr/browser/fr /usr/share/nginx/html/fr/
|
||||
COPY frontend/conf/nginx.conf /etc/nginx/nginx.conf
|
||||
84
Jenkinsfile
vendored
Normal file
84
Jenkinsfile
vendored
Normal file
@@ -0,0 +1,84 @@
|
||||
pipeline {
|
||||
agent any
|
||||
|
||||
stages {
|
||||
stage('Configuration') {
|
||||
steps {
|
||||
dir('codiki-properties') {
|
||||
git url:'https://gitea.takiguchi.ovh/Codiki/codiki-properties.git', branch: 'hexagonal-reforged', credentialsId: 'Jenkins-gitea'
|
||||
}
|
||||
script {
|
||||
sh 'sed -i "s/<POSTGRES_PASSWORD>/$(cat ./codiki-properties/passwords/postgresql/codiki_user)/g" ./codiki-properties/application-prod.yml'
|
||||
sh 'cp ./codiki-properties/application-prod.yml ./backend/codiki-launcher/src/main/resources/application-prod.yml'
|
||||
sh 'sed -i "s/<POSTGRES_PASSWORD>/$(cat ./codiki-properties/passwords/postgresql/codiki_admin)/g" ./docker-compose.yml'
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('Build') {
|
||||
steps {
|
||||
script {
|
||||
sh """
|
||||
sudo /usr/bin/docker build -t codiki-backend -f ./Dockerfile-backend . --no-cache
|
||||
sudo /usr/bin/docker build -t codiki-frontend -f ./Dockerfile-frontend . --no-cache
|
||||
sudo /usr/bin/docker save codiki-backend:latest -o ./codiki-backend.tar
|
||||
sudo /usr/bin/docker save codiki-frontend:latest -o ./codiki-frontend.tar
|
||||
sudo chown jenkins:jenkins ./codiki-backend.tar
|
||||
sudo chown jenkins:jenkins ./codiki-frontend.tar
|
||||
chmod 644 ./codiki-backend.tar
|
||||
chmod 644 ./codiki-frontend.tar
|
||||
"""
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('Deploy') {
|
||||
steps {
|
||||
sshPublisher(
|
||||
publishers: [
|
||||
sshPublisherDesc(
|
||||
configName: 'DebianServer [codiki]',
|
||||
transfers: [
|
||||
sshTransfer(
|
||||
cleanRemote: false,
|
||||
excludes: '',
|
||||
execCommand: '',
|
||||
execTimeout: 120000,
|
||||
flatten: false,
|
||||
makeEmptyDirs: false,
|
||||
noDefaultExcludes: false,
|
||||
patternSeparator: '[, ]+',
|
||||
remoteDirectory: '',
|
||||
remoteDirectorySDF: false,
|
||||
removePrefix: '',
|
||||
sourceFiles: 'codiki-backend.tar,codiki-frontend.tar,docker-compose.yml'
|
||||
),
|
||||
sshTransfer(
|
||||
cleanRemote: false,
|
||||
excludes: '',
|
||||
execCommand: """
|
||||
cd /opt/codiki
|
||||
sudo /usr/bin/docker load < /opt/codiki/codiki-backend.tar
|
||||
sudo /usr/bin/docker load < /opt/codiki/codiki-frontend.tar
|
||||
sudo /usr/bin/docker compose down
|
||||
sudo /usr/bin/docker compose up --detach
|
||||
""",
|
||||
execTimeout: 120000,
|
||||
flatten: false,
|
||||
makeEmptyDirs: false,
|
||||
noDefaultExcludes: false,
|
||||
patternSeparator: '[, ]+',
|
||||
remoteDirectory: '',
|
||||
remoteDirectorySDF: false,
|
||||
removePrefix: '',
|
||||
sourceFiles: ''
|
||||
)
|
||||
],
|
||||
usePromotionTimestamp: false,
|
||||
useWorkspaceInPromotion: false,
|
||||
verbose: true
|
||||
)
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,29 +1,22 @@
|
||||
package org.codiki.exposition.publication;
|
||||
|
||||
import org.codiki.application.publication.PublicationUseCases;
|
||||
import org.codiki.domain.publication.exception.NoPublicationSearchResultException;
|
||||
import org.codiki.domain.publication.exception.PublicationNotFoundException;
|
||||
import org.codiki.domain.publication.model.Publication;
|
||||
import org.codiki.domain.publication.model.PublicationEditionRequest;
|
||||
import org.codiki.exposition.publication.model.PreviewContentRequest;
|
||||
import org.codiki.exposition.publication.model.PreviewContentResponse;
|
||||
import org.codiki.exposition.publication.model.PublicationDto;
|
||||
import org.codiki.exposition.publication.model.PublicationEditionRequestDto;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.springframework.http.HttpStatus.CREATED;
|
||||
import static org.springframework.http.HttpStatus.NO_CONTENT;
|
||||
import static org.springframework.util.ObjectUtils.isEmpty;
|
||||
import org.codiki.application.publication.PublicationUseCases;
|
||||
import org.codiki.domain.publication.exception.NoPublicationSearchResultException;
|
||||
import org.codiki.domain.publication.exception.PublicationNotFoundException;
|
||||
import org.codiki.domain.publication.model.Publication;
|
||||
import org.codiki.domain.publication.model.PublicationEditionRequest;
|
||||
import org.codiki.exposition.publication.model.PreviewContentRequest;
|
||||
import org.codiki.exposition.publication.model.PublicationDto;
|
||||
import org.codiki.exposition.publication.model.PublicationEditionRequestDto;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/publications")
|
||||
@@ -93,8 +86,9 @@ public class PublicationController {
|
||||
return publications;
|
||||
}
|
||||
|
||||
@PostMapping("/preview")
|
||||
public String previewPublicationContent(@RequestBody PreviewContentRequest request) {
|
||||
return publicationUseCases.previewContent(request.text());
|
||||
@PostMapping(value = "/preview")
|
||||
public PreviewContentResponse previewPublicationContent(@RequestBody PreviewContentRequest request) {
|
||||
String previewContent = publicationUseCases.previewContent(request.text());
|
||||
return new PreviewContentResponse(previewContent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
package org.codiki.exposition.publication.model;
|
||||
|
||||
public record PreviewContentResponse(
|
||||
String text
|
||||
) {}
|
||||
@@ -1,13 +0,0 @@
|
||||
application:
|
||||
pictures:
|
||||
path: /opt/codiki/pictures/
|
||||
temp-path: /opt/codiki/pictures/temp/
|
||||
|
||||
server:
|
||||
port: 8080
|
||||
|
||||
spring:
|
||||
datasource:
|
||||
url: jdbc:postgresql://codiki-database:5432/codiki_db
|
||||
username: codiki_user
|
||||
password: password
|
||||
@@ -0,0 +1,21 @@
|
||||
meta {
|
||||
name: Preview content
|
||||
type: http
|
||||
seq: 7
|
||||
}
|
||||
|
||||
post {
|
||||
url: {{url}}/api/publications/preview
|
||||
body: json
|
||||
auth: bearer
|
||||
}
|
||||
|
||||
auth:bearer {
|
||||
token: {{bearerToken}}
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"text" : "[h1]Test[/h1]"
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
vars {
|
||||
url: http://localhost:8987
|
||||
publicationId: ec76602f-5501-4091-868e-b471611e63de
|
||||
bearerToken: eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1YWQ0NjJiOC04ZjllLTRhMjYtYmI4Ni1jNzRmZWY1ZDExYjYiLCJleHAiOjE3MTA4Mzc2ODQsInBzZXVkbyI6IlN0YW5kYXJkIHVzZXIiLCJlbWFpbCI6InN0YW5kYXJkLnVzZXJAY29kaWtpLm9yZyIsInJvbGVzIjoiU1RBTkRBUkQifQ.2HggC3T_4I14IpW02DZJiYfgYwc074kU8Y4AmuGf1mZzv0U8OUxpAw_xEhnKtn8NcaCozz_2vFv4o_CaBqS8Ag
|
||||
bearerToken: eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkMWQ1NTdhNi04OGIxLTQyNzQtOTk0ZS1mOWE5YTYwOTc5OTciLCJleHAiOjE3MjY5NTExNTgsInBob3RvSWQiOiI2MjhkYTFhNy0wNzAyLTRlNDktOGIwNi00ZDg2MGE2YTNkZTUiLCJwc2V1ZG8iOiJUYWtpZ3VjaGkiLCJlbWFpbCI6ImZsb3JpYW4udGhpZXJyeTcyQGdtYWlsLmNvbSIsInJvbGVzIjoiU1RBTkRBUkQifQ.4OQglB0cT2hTMO7_Bfxj7nQPYi42e0Gh06jmHj2q-SQTM6Md70Ii_BiKR__GxY14bahPAjLcIWfAYS2A0Tc1Vw
|
||||
categoryId: 172fa901-3f4b-4540-92f3-1c15820e8ec9
|
||||
pictureId: 65b660b7-66bb-4e4a-a62c-fd0ca101f972
|
||||
}
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
application:
|
||||
pictures:
|
||||
path: /opt/codiki/pictures/
|
||||
temp-path: /opt/codiki/pictures/temp/
|
||||
|
||||
server:
|
||||
port: 8080
|
||||
|
||||
spring:
|
||||
datasource:
|
||||
url: jdbc:postgresql://codiki-database:5432/codiki_db
|
||||
username: codiki_user
|
||||
password: password
|
||||
83
ci/i18n-completer/app.ts
Normal file
83
ci/i18n-completer/app.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
'use strict';
|
||||
|
||||
import fs from 'fs';
|
||||
|
||||
const englishI18nFilePath='../../frontend/src/locale/messages.json';
|
||||
const frenchI18nFilePath='../../frontend/src/locale/messages-fr.json';
|
||||
|
||||
interface Translations {
|
||||
[key: string]: string;
|
||||
}
|
||||
|
||||
interface I18nFileContent {
|
||||
locale: string;
|
||||
translations: Translations;
|
||||
};
|
||||
|
||||
interface Translation {
|
||||
key: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
interface I18nTranslations {
|
||||
locale: string;
|
||||
translations: Translation[];
|
||||
}
|
||||
|
||||
function readAndParseFile(filePath): I18nTranslations {
|
||||
const fileContent = fs.readFileSync(filePath);
|
||||
const parsedFileContent: I18nFileContent = JSON.parse(fileContent.toString());
|
||||
return {
|
||||
locale: parsedFileContent.locale,
|
||||
translations: Object.keys(parsedFileContent.translations)
|
||||
.map(translationKey => {
|
||||
return {
|
||||
key: translationKey,
|
||||
value: parsedFileContent.translations[translationKey]
|
||||
};
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
function findMatchingFrenchTranslation(englishTranslation: Translation, frenchI18nFileContent: I18nTranslations): Translation | undefined {
|
||||
return frenchI18nFileContent.translations.find(frenchTranslation => frenchTranslation.key === englishTranslation.key);
|
||||
}
|
||||
|
||||
function main(): void {
|
||||
const englishI18nTranslations = readAndParseFile(englishI18nFilePath);
|
||||
const frenchI18nTranslations = readAndParseFile(frenchI18nFilePath);
|
||||
|
||||
const frenchTranslations = englishI18nTranslations.translations
|
||||
.map(englishTranslation => {
|
||||
let result: Translation;
|
||||
|
||||
const matchingFrenchTranslation = findMatchingFrenchTranslation(englishTranslation, frenchI18nTranslations);
|
||||
if (matchingFrenchTranslation?.value?.length) {
|
||||
result = matchingFrenchTranslation;
|
||||
} else {
|
||||
result = {
|
||||
key: englishTranslation.key,
|
||||
value: `<À TRADUIRE> - ${englishTranslation.value}`
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
const newFrenchI18nTranslations: I18nTranslations = {
|
||||
locale: 'fr-FR',
|
||||
translations: frenchTranslations
|
||||
};
|
||||
|
||||
const newFrenchTranslationFileContent: I18nFileContent = {
|
||||
locale: newFrenchI18nTranslations.locale,
|
||||
translations: newFrenchI18nTranslations.translations.reduce(
|
||||
(result, translation) => Object.assign(result, { [translation.key]: translation.value }),
|
||||
{}
|
||||
)
|
||||
}
|
||||
|
||||
fs.writeFileSync(frenchI18nFilePath, JSON.stringify(newFrenchTranslationFileContent));
|
||||
}
|
||||
|
||||
main();
|
||||
540
ci/i18n-completer/package-lock.json
generated
Normal file
540
ci/i18n-completer/package-lock.json
generated
Normal file
@@ -0,0 +1,540 @@
|
||||
{
|
||||
"name": "i18n-completer",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "i18n-completer",
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.5.5",
|
||||
"tsx": "^4.19.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz",
|
||||
"integrity": "sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"aix"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.1.tgz",
|
||||
"integrity": "sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz",
|
||||
"integrity": "sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-x64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.1.tgz",
|
||||
"integrity": "sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-arm64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz",
|
||||
"integrity": "sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-x64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz",
|
||||
"integrity": "sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-arm64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz",
|
||||
"integrity": "sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-x64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz",
|
||||
"integrity": "sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz",
|
||||
"integrity": "sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz",
|
||||
"integrity": "sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ia32": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz",
|
||||
"integrity": "sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz",
|
||||
"integrity": "sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-mips64el": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz",
|
||||
"integrity": "sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ppc64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz",
|
||||
"integrity": "sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-riscv64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz",
|
||||
"integrity": "sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-s390x": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz",
|
||||
"integrity": "sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-x64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz",
|
||||
"integrity": "sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-x64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz",
|
||||
"integrity": "sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-arm64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz",
|
||||
"integrity": "sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-x64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz",
|
||||
"integrity": "sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/sunos-x64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz",
|
||||
"integrity": "sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"sunos"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-arm64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz",
|
||||
"integrity": "sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-ia32": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.1.tgz",
|
||||
"integrity": "sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-x64": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.1.tgz",
|
||||
"integrity": "sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "22.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.5.tgz",
|
||||
"integrity": "sha512-Xjs4y5UPO/CLdzpgR6GirZJx36yScjh73+2NlLlkFRSoQN8B0DpfXPdZGnvVmLRLOsqDpOfTNv7D9trgGhmOIA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~6.19.2"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.1.tgz",
|
||||
"integrity": "sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"esbuild": "bin/esbuild"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/aix-ppc64": "0.23.1",
|
||||
"@esbuild/android-arm": "0.23.1",
|
||||
"@esbuild/android-arm64": "0.23.1",
|
||||
"@esbuild/android-x64": "0.23.1",
|
||||
"@esbuild/darwin-arm64": "0.23.1",
|
||||
"@esbuild/darwin-x64": "0.23.1",
|
||||
"@esbuild/freebsd-arm64": "0.23.1",
|
||||
"@esbuild/freebsd-x64": "0.23.1",
|
||||
"@esbuild/linux-arm": "0.23.1",
|
||||
"@esbuild/linux-arm64": "0.23.1",
|
||||
"@esbuild/linux-ia32": "0.23.1",
|
||||
"@esbuild/linux-loong64": "0.23.1",
|
||||
"@esbuild/linux-mips64el": "0.23.1",
|
||||
"@esbuild/linux-ppc64": "0.23.1",
|
||||
"@esbuild/linux-riscv64": "0.23.1",
|
||||
"@esbuild/linux-s390x": "0.23.1",
|
||||
"@esbuild/linux-x64": "0.23.1",
|
||||
"@esbuild/netbsd-x64": "0.23.1",
|
||||
"@esbuild/openbsd-arm64": "0.23.1",
|
||||
"@esbuild/openbsd-x64": "0.23.1",
|
||||
"@esbuild/sunos-x64": "0.23.1",
|
||||
"@esbuild/win32-arm64": "0.23.1",
|
||||
"@esbuild/win32-ia32": "0.23.1",
|
||||
"@esbuild/win32-x64": "0.23.1"
|
||||
}
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/get-tsconfig": {
|
||||
"version": "4.8.1",
|
||||
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.8.1.tgz",
|
||||
"integrity": "sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"resolve-pkg-maps": "^1.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/resolve-pkg-maps": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
|
||||
"integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx": {
|
||||
"version": "4.19.1",
|
||||
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.1.tgz",
|
||||
"integrity": "sha512-0flMz1lh74BR4wOvBjuh9olbnwqCPc35OOlfyzHba0Dc+QNUeWX/Gq2YTbnwcWPO3BMd8fkzRVrHcsR+a7z7rA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"esbuild": "~0.23.0",
|
||||
"get-tsconfig": "^4.7.5"
|
||||
},
|
||||
"bin": {
|
||||
"tsx": "dist/cli.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "~2.3.3"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "6.19.8",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
|
||||
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
||||
}
|
||||
17
ci/i18n-completer/package.json
Normal file
17
ci/i18n-completer/package.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "i18n-completer",
|
||||
"version": "1.0.0",
|
||||
"main": "app.ts",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "npx tsx app.ts",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"description": "",
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.5.5",
|
||||
"tsx": "^4.19.1"
|
||||
}
|
||||
}
|
||||
@@ -9,10 +9,11 @@ services:
|
||||
environment:
|
||||
POSTGRES_DB: codiki_db
|
||||
POSTGRES_USER: codiki_admin
|
||||
POSTGRES_PASSWORD: password
|
||||
POSTGRES_PASSWORD: <POSTGRES_PASSWORD>
|
||||
PGDATA: /var/lib/postgresql/data/pgdata
|
||||
volumes:
|
||||
- "./docker/postgresql/pgdata:/var/lib/postgresql/data/pgdata"
|
||||
restart: always
|
||||
|
||||
codiki-backend:
|
||||
image: codiki-backend:latest
|
||||
@@ -25,6 +26,7 @@ services:
|
||||
- "/opt/codiki/pictures:/opt/codiki/pictures"
|
||||
networks:
|
||||
- "codiki-network"
|
||||
restart: always
|
||||
|
||||
codiki-frontend:
|
||||
image: codiki-frontend:latest
|
||||
@@ -33,6 +35,7 @@ services:
|
||||
- "50012:80"
|
||||
networks:
|
||||
- "codiki-network"
|
||||
restart: always
|
||||
|
||||
networks:
|
||||
codiki-network:
|
||||
@@ -3,7 +3,7 @@
|
||||
"version": 1,
|
||||
"newProjectRoot": "projects",
|
||||
"projects": {
|
||||
"codiki-ng": {
|
||||
"codiki": {
|
||||
"projectType": "application",
|
||||
"schematics": {
|
||||
"@schematics/angular:component": {
|
||||
@@ -14,16 +14,19 @@
|
||||
"sourceRoot": "src",
|
||||
"prefix": "app",
|
||||
"i18n": {
|
||||
"sourceLocale": "en-UK",
|
||||
"sourceLocale": "en",
|
||||
"locales": {
|
||||
"fr": "src/locale/messages-fr.json"
|
||||
"fr": {
|
||||
"translation": "src/locale/messages-fr.json"
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:application",
|
||||
"options": {
|
||||
"outputPath": "dist/codiki-ng",
|
||||
"outputPath": "dist/codiki",
|
||||
"index": "src/index.html",
|
||||
"browser": "src/main.ts",
|
||||
"polyfills": [
|
||||
@@ -56,7 +59,7 @@
|
||||
}
|
||||
],
|
||||
"outputHashing": "all",
|
||||
"outputPath": "dist/codiki-ng/en/"
|
||||
"outputPath": "dist/codiki/en/"
|
||||
},
|
||||
"production-fr": {
|
||||
"budgets": [
|
||||
@@ -71,8 +74,10 @@
|
||||
"maximumError": "4kb"
|
||||
}
|
||||
],
|
||||
"localize": ["fr"],
|
||||
"i18nMissingTranslation": "error",
|
||||
"outputHashing": "all",
|
||||
"outputPath": "dist/codiki-ng/fr/"
|
||||
"outputPath": "dist/codiki/fr/"
|
||||
},
|
||||
"development": {
|
||||
"optimization": false,
|
||||
@@ -80,13 +85,13 @@
|
||||
"sourceMap": true
|
||||
},
|
||||
"en": {
|
||||
"outputPath": "dist/codiki-ng/en/",
|
||||
"outputPath": "dist/codiki/en/",
|
||||
"optimization": false,
|
||||
"extractLicenses": false,
|
||||
"sourceMap": true
|
||||
},
|
||||
"fr": {
|
||||
"outputPath": "dist/codiki-ng/fr/",
|
||||
"outputPath": "dist/codiki/fr/",
|
||||
"optimization": false,
|
||||
"extractLicenses": false,
|
||||
"sourceMap": true,
|
||||
@@ -100,19 +105,19 @@
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"configurations": {
|
||||
"production-en": {
|
||||
"buildTarget": "codiki-ng:build:production-en"
|
||||
"buildTarget": "codiki:build:production-en"
|
||||
},
|
||||
"production-fr": {
|
||||
"buildTarget": "codiki-ng:build:production-fr"
|
||||
"buildTarget": "codiki:build:production-fr"
|
||||
},
|
||||
"development": {
|
||||
"buildTarget": "codiki-ng:build:development"
|
||||
"buildTarget": "codiki:build:development"
|
||||
},
|
||||
"en": {
|
||||
"buildTarget": "codiki-ng:build:en"
|
||||
"buildTarget": "codiki:build:en"
|
||||
},
|
||||
"fr": {
|
||||
"buildTarget": "codiki-ng:build:fr"
|
||||
"buildTarget": "codiki:build:fr"
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "development"
|
||||
@@ -120,7 +125,7 @@
|
||||
"extract-i18n": {
|
||||
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||
"options": {
|
||||
"buildTarget": "codiki-ng:build"
|
||||
"buildTarget": "codiki:build"
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
|
||||
5017
frontend/package-lock.json
generated
5017
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -8,32 +8,34 @@
|
||||
"start-fr": "ng serve --port 4201 --configuration=fr --proxy-config proxy.conf.json",
|
||||
"build": "ng build",
|
||||
"build-prod-en": "ng build --configuration=production-en --base-href /en/",
|
||||
"build-prod-fr": "ng build --configuration=production-fr --base-href /fr/",
|
||||
"build-prod-fr": "ng build --configuration=production-fr --base-href",
|
||||
"watch": "ng build --watch --configuration development",
|
||||
"test": "ng test",
|
||||
"i18n": "ng extract-i18n --output-path src/locale --format=json"
|
||||
"i18n": "npm run i18n-ng-extraction && npm run i18n-fr-file-completion",
|
||||
"i18n-ng-extraction": "ng extract-i18n --output-path src/locale --format=json",
|
||||
"i18n-fr-file-completion": "cd ../ci/i18n-completer && npm start && cd -"
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "^17.0.0",
|
||||
"@angular/cdk": "^17.3.1",
|
||||
"@angular/common": "^17.0.0",
|
||||
"@angular/compiler": "^17.0.0",
|
||||
"@angular/core": "^17.0.0",
|
||||
"@angular/forms": "^17.0.0",
|
||||
"@angular/material": "^17.3.1",
|
||||
"@angular/platform-browser": "^17.0.0",
|
||||
"@angular/platform-browser-dynamic": "^17.0.0",
|
||||
"@angular/router": "^17.0.0",
|
||||
"@angular/animations": "^18.2.5",
|
||||
"@angular/cdk": "^18.2.5",
|
||||
"@angular/common": "^18.2.5",
|
||||
"@angular/compiler": "^18.2.5",
|
||||
"@angular/core": "^18.2.5",
|
||||
"@angular/forms": "^18.2.5",
|
||||
"@angular/material": "^18.2.5",
|
||||
"@angular/platform-browser": "^18.2.5",
|
||||
"@angular/platform-browser-dynamic": "^18.2.5",
|
||||
"@angular/router": "^18.2.5",
|
||||
"rxjs": "~7.8.0",
|
||||
"tslib": "^2.3.0",
|
||||
"zone.js": "~0.14.2"
|
||||
"zone.js": "~0.14.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "^17.0.5",
|
||||
"@angular/cli": "^17.0.5",
|
||||
"@angular/compiler-cli": "^17.0.0",
|
||||
"@angular/localize": "^17.3.12",
|
||||
"@angular-devkit/build-angular": "^18.2.5",
|
||||
"@angular/cli": "^18.2.5",
|
||||
"@angular/compiler-cli": "^18.2.5",
|
||||
"@angular/localize": "^18.2.5",
|
||||
"@types/jasmine": "~5.1.0",
|
||||
"jasmine-core": "~5.1.0",
|
||||
"karma": "~6.4.0",
|
||||
@@ -41,6 +43,6 @@
|
||||
"karma-coverage": "~2.2.0",
|
||||
"karma-jasmine": "~5.1.0",
|
||||
"karma-jasmine-html-reporter": "~2.1.0",
|
||||
"typescript": "~5.2.2"
|
||||
"typescript": "~5.5.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
"/api": {
|
||||
"target": "http://localhost:8987",
|
||||
"secure": false
|
||||
},
|
||||
"/pictures": {
|
||||
"target": "http://localhost:8987/api",
|
||||
"secure": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ApplicationConfig } from '@angular/core';
|
||||
import { ApplicationConfig, provideExperimentalZonelessChangeDetection } from '@angular/core';
|
||||
import { provideRouter, withRouterConfig } from '@angular/router';
|
||||
|
||||
import { HTTP_INTERCEPTORS, provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
|
||||
@@ -16,7 +16,9 @@ export const appConfig: ApplicationConfig = {
|
||||
})
|
||||
),
|
||||
provideAnimationsAsync(),
|
||||
provideExperimentalZonelessChangeDetection(),
|
||||
provideHttpClient(withInterceptorsFromDi()),
|
||||
|
||||
{ provide: HTTP_INTERCEPTORS, useClass: JwtInterceptor, multi: true },
|
||||
]
|
||||
};
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<h1>{{title}}</h1>
|
||||
<h2>{{description}}</h2>
|
||||
<footer>
|
||||
<button type="button" class="secondary" (click)="closeDialog()">
|
||||
<button type="button" class="secondary" (click)="closeDialog()" i18n>
|
||||
No
|
||||
</button>
|
||||
<button type="button" (click)="closeAndValidate()">
|
||||
<button type="button" (click)="closeAndValidate()" i18n>
|
||||
Yes
|
||||
</button>
|
||||
</footer>
|
||||
@@ -1,14 +1,14 @@
|
||||
<div>
|
||||
<div i18n>
|
||||
<span class="copy-left">©</span>
|
||||
2016 - 2024 Tous droits réservés
|
||||
2016 - 2024 All rights reserved
|
||||
-
|
||||
2.0-alpha
|
||||
<a [routerLink]="['./']" matTooltip="Health checking will be available in future...">
|
||||
<a [routerLink]="['./']" matTooltip="Health checking will be available in future..." i18n-matTooltip>
|
||||
<mat-icon>favorite</mat-icon>
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<mat-icon matTooltip="Documentation will be available in future...">menu_book</mat-icon>
|
||||
<mat-icon matTooltip="Documentation will be available in future..." i18n-matTooltip>menu_book</mat-icon>
|
||||
-
|
||||
Développements réalisés par Florian THIERRY
|
||||
<span i18n>Development realised by</span> Florian THIERRY
|
||||
</div>
|
||||
@@ -1,23 +0,0 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { FooterComponent } from './footer.component';
|
||||
|
||||
describe('FooterComponent', () => {
|
||||
let component: FooterComponent;
|
||||
let fixture: ComponentFixture<FooterComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [FooterComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(FooterComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
<div class="left">
|
||||
<button type="button" (click)="sideMenu.open()" matTooltip="Click to show side menu">
|
||||
<button type="button" (click)="sideMenu.open()" matTooltip="Click to show side menu" i18n-matTooltip>
|
||||
<mat-icon>menu</mat-icon>
|
||||
</button>
|
||||
<a [routerLink]="['/home']">
|
||||
@@ -19,18 +19,18 @@
|
||||
</button>
|
||||
<mat-menu #authenticatedUserMenu="matMenu">
|
||||
<div class="authenticated-user-menu">
|
||||
<a [routerLink]="['/my-publications']" matRipple>
|
||||
<a [routerLink]="['/my-publications']" matRipple i18n>
|
||||
<mat-icon>description</mat-icon>
|
||||
My publications
|
||||
</a>
|
||||
<a [routerLink]="['/disconnect']" matRipple class="disconnection">
|
||||
<a [routerLink]="['/disconnect']" matRipple class="disconnection" i18n>
|
||||
<mat-icon>logout</mat-icon>
|
||||
Disconnect
|
||||
</a>
|
||||
</div>
|
||||
</mat-menu>
|
||||
} @else {
|
||||
<a [routerLink]="['/login']" class="button" matRipple>Login</a>
|
||||
<a [routerLink]="['/login']" class="button" matRipple i18n>Login</a>
|
||||
}
|
||||
</div>
|
||||
<app-side-menu #sideMenu></app-side-menu>
|
||||
@@ -1,16 +1,15 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Component, inject } from '@angular/core';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { FormControl, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatRippleModule } from '@angular/material/core';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatMenuModule } from '@angular/material/menu';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { AuthenticationService } from '../../core/service/authentication.service';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { SideMenuComponent } from '../side-menu/side-menu.component';
|
||||
import { MatRippleModule } from '@angular/material/core';
|
||||
import { FormControl, ReactiveFormsModule } from '@angular/forms';
|
||||
import { PublicationsSearchBarComponent } from '../publications-search-bar/publications-search-bar.component';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { MatMenuModule } from '@angular/material/menu';
|
||||
import { User } from '../../core/model/User';
|
||||
import { SideMenuComponent } from '../side-menu/side-menu.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-header',
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
<mat-icon>close</mat-icon>
|
||||
</button>
|
||||
<header>
|
||||
<h1>Add a code block</h1>
|
||||
<h1 i18n>Add a code block</h1>
|
||||
</header>
|
||||
<form [formGroup]="formGroup" (submit)="closeAndValidate()" ngNativeValidate>
|
||||
<div class="form-content">
|
||||
<mat-form-field>
|
||||
<mat-label>Programming language</mat-label>
|
||||
<mat-label i18n>Programming language</mat-label>
|
||||
<mat-select #programmingLanguageSelect formControlName="programmingLanguage">
|
||||
@for(programmingLanguage of programmingLanguages; track programmingLanguage) {
|
||||
<mat-option [value]="programmingLanguage.code">
|
||||
@@ -17,15 +17,15 @@
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<mat-label>Code block</mat-label>
|
||||
<mat-label i18n>Code block</mat-label>
|
||||
<textarea matInput formControlName="codeBlock"></textarea>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button type="submit">
|
||||
<button type="submit" i18n>
|
||||
Validate
|
||||
</button>
|
||||
<button type="button" (click)="closeDialog()" class="secondary">
|
||||
<button type="button" (click)="closeDialog()" class="secondary" i18n>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -2,27 +2,27 @@
|
||||
<mat-icon>close</mat-icon>
|
||||
</button>
|
||||
<header>
|
||||
<h1>Select an illustration:</h1>
|
||||
<h1 i18n>Select an illustration</h1>
|
||||
</header>
|
||||
<div class="picture-container">
|
||||
@if (isLoading) {
|
||||
<h2>Pictures loading...</h2>
|
||||
<h2 i18n>Pictures loading...</h2>
|
||||
<mat-spinner></mat-spinner>
|
||||
} @else {
|
||||
@if (pictures.length) {
|
||||
@for(picture of pictures; track picture) {
|
||||
<img src="/api/pictures/{{picture.id}}" (click)="selectPicture(picture)" matTooltip="Choose this illustration"/>
|
||||
<img src="/api/pictures/{{picture.id}}" (click)="selectPicture(picture)" matTooltip="Choose this illustration" i18n-matTooltip/>
|
||||
}
|
||||
} @else {
|
||||
<h2>There is no any picture.</h2>
|
||||
<h2 i18n>There is no any picture.</h2>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
<footer>
|
||||
<button type="button" class="secondary" matRipple (click)="closeDialog()">
|
||||
<button type="button" class="secondary" matRipple (click)="closeDialog()" i18n>
|
||||
Cancel
|
||||
</button>
|
||||
<button type="button" (click)="fileUpload.click()" matRipple>
|
||||
<button type="button" (click)="fileUpload.click()" matRipple i18n>
|
||||
<mat-icon>upload_file</mat-icon>
|
||||
Add new picture
|
||||
</button>
|
||||
|
||||
@@ -34,9 +34,9 @@ export class PictureSelectionDialog implements OnInit {
|
||||
if (error.status === 401) {
|
||||
this.dialogRef.close();
|
||||
} else {
|
||||
const errorMessage = 'An error occured while loading pictures.';
|
||||
const errorMessage = $localize`An error occured while loading pictures.`;
|
||||
console.error(errorMessage, error);
|
||||
this.snackBar.open(errorMessage, 'Close', { duration: 5000 });
|
||||
this.snackBar.open(errorMessage, $localize`Close`, { duration: 5000 });
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
@@ -61,9 +61,9 @@ export class PictureSelectionDialog implements OnInit {
|
||||
this.dialogRef.close(pictureId);
|
||||
})
|
||||
.catch(error => {
|
||||
const errorMessage = 'A technical error occured while uploading your picture.';
|
||||
const errorMessage = $localize`A technical error occured while uploading your picture.`;
|
||||
console.error(errorMessage, error);
|
||||
this.snackBar.open(errorMessage, 'Close', { duration: 5000 });
|
||||
this.snackBar.open(errorMessage, $localize`Close`, { duration: 5000 });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,9 +16,9 @@ export class PictureSelectionDialogService {
|
||||
this.dialogRef.close(pictureId);
|
||||
})
|
||||
.catch(error => {
|
||||
const errorMessage = 'An error occured while uploading a picture...';
|
||||
const errorMessage = $localize`An error occured while uploading a picture...`;
|
||||
console.error(errorMessage, error);
|
||||
this.snackBar.open(errorMessage, 'Close', { duration: 5000 });
|
||||
this.snackBar.open(errorMessage, $localize`Close`, { duration: 5000 });
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -4,20 +4,20 @@
|
||||
</header>
|
||||
|
||||
<mat-tab-group dynamicHeight (selectedIndexChange)="onTabChange($event)">
|
||||
<mat-tab label="Edition">
|
||||
<mat-tab label="Edition" i18n-label>
|
||||
<div class="form-content">
|
||||
<div class="first-part">
|
||||
<div>
|
||||
<mat-form-field>
|
||||
<mat-label>Title</mat-label>
|
||||
<mat-label i18n>Title</mat-label>
|
||||
<input matInput type="text" formControlName="title" />
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<mat-label>Description</mat-label>
|
||||
<mat-label i18n>Description</mat-label>
|
||||
<input matInput type="text" formControlName="description" />
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<mat-label>Category</mat-label>
|
||||
<mat-label i18n>Category</mat-label>
|
||||
<mat-select formControlName="categoryId">
|
||||
@for (category of categories$ | async; track category) {
|
||||
<mat-option [value]="category.id">
|
||||
@@ -31,35 +31,36 @@
|
||||
<div class="picture-container">
|
||||
<img [src]="publication.illustrationId.length ? '/api/pictures/' + publication.illustrationId : '/assets/images/default-picture.png'"
|
||||
(click)="displayPictureSectionDialog()"
|
||||
matTooltip="Click to change illustration"/>
|
||||
matTooltip="Click to change illustration"
|
||||
i18n-matTooltip/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<button type="button" matTooltip="Click to insert a title 1 section" (click)="insertTitle(1)">
|
||||
<button type="button" matTooltip="Click to insert a title 1 section" (click)="insertTitle(1)" i18n-matTooltip>
|
||||
H1
|
||||
</button>
|
||||
<button type="button" matTooltip="Click to insert a title 2 section" (click)="insertTitle(2)">
|
||||
<button type="button" matTooltip="Click to insert a title 2 section" (click)="insertTitle(2)" i18n-matTooltip>
|
||||
H2
|
||||
</button>
|
||||
<button type="button" matTooltip="Click to insert a title 1 section" (click)="insertTitle(3)">
|
||||
<button type="button" matTooltip="Click to insert a title 3 section" (click)="insertTitle(3)" i18n-matTooltip>
|
||||
H3
|
||||
</button>
|
||||
<button type="button" matTooltip="Click to insert a picture" (click)="selectAPicture()">
|
||||
<button type="button" matTooltip="Click to insert a picture" (click)="selectAPicture()" i18n-matTooltip>
|
||||
<mat-icon>image</mat-icon>
|
||||
</button>
|
||||
<button type="button" matTooltip="Click to insert a link" (click)="insertLink()">
|
||||
<button type="button" matTooltip="Click to insert a link" (click)="insertLink()" i18n-matTooltip>
|
||||
<mat-icon>link</mat-icon>
|
||||
</button>
|
||||
<button type="button" matTooltip="Click to insert a code block" (click)="displayCodeBlockDialog()">
|
||||
<button type="button" matTooltip="Click to insert a code block" (click)="displayCodeBlockDialog()" i18n-matTooltip>
|
||||
<mat-icon>code</mat-icon>
|
||||
</button>
|
||||
<button type="button" disabled matTooltip="Click to display editor help">
|
||||
<button type="button" disabled matTooltip="Click to display editor help" i18n-matTooltip>
|
||||
<mat-icon>help</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
<mat-form-field class="example-form-field">
|
||||
<mat-label>Content</mat-label>
|
||||
<mat-form-field>
|
||||
<mat-label i18n>Content</mat-label>
|
||||
<textarea
|
||||
#textArea
|
||||
matInput
|
||||
@@ -72,27 +73,27 @@
|
||||
</div>
|
||||
</mat-tab>
|
||||
|
||||
<mat-tab label="Previewing">
|
||||
<mat-tab label="Previewing" i18n-label>
|
||||
<div class="preview">
|
||||
@if ((isPreviewing$ | async) === true) {
|
||||
<div class="preview-loading">
|
||||
<h2>Preview is loading...</h2>
|
||||
<h2 i18n>Preview is loading...</h2>
|
||||
<mat-spinner></mat-spinner>
|
||||
</div>
|
||||
} @else {
|
||||
<img class="illustration" src="/pictures/{{ publication.illustrationId }}" />
|
||||
<img class="illustration" src="/api/pictures/{{ publication.illustrationId }}" />
|
||||
<header>
|
||||
<h1>{{ publication.title }}</h1>
|
||||
<h2>{{ publication.description }}</h2>
|
||||
</header>
|
||||
<main [innerHTML]="publication.parsedText"></main>
|
||||
<main [innerHTML]="publicationInEdition.parsedText"></main>
|
||||
}
|
||||
</div>
|
||||
</mat-tab>
|
||||
</mat-tab-group>
|
||||
<footer>
|
||||
<app-submit-button label="Save" [requestPending]="!!(isSaving$ | async)"></app-submit-button>
|
||||
<button type="button" class="secondary" (click)="goPreviousLocation()">
|
||||
<app-submit-button label="Save" [requestPending]="!!(isSaving$ | async)" i18n-label></app-submit-button>
|
||||
<button type="button" class="secondary" (click)="goPreviousLocation()" i18n>
|
||||
Cancel
|
||||
</button>
|
||||
</footer>
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import { CommonModule, Location } from "@angular/common";
|
||||
import { Component, EventEmitter, inject, Input, OnChanges, OnDestroy, OnInit, Output } from "@angular/core";
|
||||
import { Component, EventEmitter, inject, Input, OnChanges, OnDestroy, Output } from "@angular/core";
|
||||
import { FormBuilder, FormControl, FormGroup, ReactiveFormsModule, Validators } from "@angular/forms";
|
||||
import { MatDialogModule } from "@angular/material/dialog";
|
||||
import { MatIconModule } from "@angular/material/icon";
|
||||
import { MatInputModule } from "@angular/material/input";
|
||||
import { MatProgressSpinnerModule } from "@angular/material/progress-spinner";
|
||||
import { MatSelectModule } from "@angular/material/select";
|
||||
import { MatTabsModule } from "@angular/material/tabs";
|
||||
import { MatTooltipModule } from "@angular/material/tooltip";
|
||||
import { filter, map, Observable, of, Subscription } from "rxjs";
|
||||
import { Publication } from "../../core/rest-services/publications/model/publication";
|
||||
import { PictureSelectionDialog } from "./picture-selection-dialog/picture-selection-dialog.component";
|
||||
import { SubmitButtonComponent } from "../submit-button/submit-button.component";
|
||||
import { PublicationEditionService } from "./publication-edition.service";
|
||||
import { MatSelectModule } from "@angular/material/select";
|
||||
import { CategoryService } from "../../core/service/category.service";
|
||||
import { map, Observable, of, Subscription } from "rxjs";
|
||||
import { Category } from "../../core/rest-services/category/model/category";
|
||||
import { Publication } from "../../core/rest-services/publications/model/publication";
|
||||
import { CategoryService } from "../../core/service/category.service";
|
||||
import { SubmitButtonComponent } from "../submit-button/submit-button.component";
|
||||
import { PictureSelectionDialog } from "./picture-selection-dialog/picture-selection-dialog.component";
|
||||
import { PublicationEditionService } from "./publication-edition.service";
|
||||
|
||||
@Component({
|
||||
selector: 'app-publication-edition',
|
||||
@@ -46,11 +46,11 @@ export class PublicationEditionComponent implements OnChanges, OnDestroy {
|
||||
@Output()
|
||||
publicationSave = new EventEmitter<Publication>();
|
||||
|
||||
publicationInEdition!: Publication;
|
||||
private readonly categoryService = inject(CategoryService);
|
||||
private readonly formBuilder = inject(FormBuilder);
|
||||
private readonly location = inject(Location);
|
||||
private readonly publicationEditionService = inject(PublicationEditionService);
|
||||
private publicationInEdition!: Publication;
|
||||
private subscriptions: Subscription[] = [];
|
||||
|
||||
publicationEditionForm: FormGroup = this.formBuilder.group({
|
||||
@@ -92,7 +92,7 @@ export class PublicationEditionComponent implements OnChanges, OnDestroy {
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
ngOnChanges(): void {
|
||||
this.ngOnDestroy();
|
||||
|
||||
@@ -129,12 +129,13 @@ export class PublicationEditionComponent implements OnChanges, OnDestroy {
|
||||
this.subscriptions.push(categoryIdChangeSubscription);
|
||||
|
||||
const publicationSubscription = this.publicationEditionService.state$.subscribe(state => {
|
||||
console.log(state.publication.parsedText.substring(0, 15));
|
||||
this.publicationInEdition = state.publication;
|
||||
this.publicationEditionForm.controls['title'].setValue(this.publication.title, { emitEvent: false });
|
||||
this.publicationEditionForm.controls['description'].setValue(this.publication.description, { emitEvent: false });
|
||||
this.publicationEditionForm.controls['text'].setValue(this.publication.text, { emitEvent: false });
|
||||
this.publicationEditionForm.controls['illustrationId'].setValue(this.publication.illustrationId, { emitEvent: false });
|
||||
this.publicationEditionForm.controls['categoryId'].setValue(this.publication.categoryId, { emitEvent: false });
|
||||
this.publicationEditionForm.controls['title'].setValue(this.publicationInEdition.title, { emitEvent: false });
|
||||
this.publicationEditionForm.controls['description'].setValue(this.publicationInEdition.description, { emitEvent: false });
|
||||
this.publicationEditionForm.controls['text'].setValue(this.publicationInEdition.text, { emitEvent: false });
|
||||
this.publicationEditionForm.controls['illustrationId'].setValue(this.publicationInEdition.illustrationId, { emitEvent: false });
|
||||
this.publicationEditionForm.controls['categoryId'].setValue(this.publicationInEdition.categoryId, { emitEvent: false });
|
||||
});
|
||||
this.subscriptions.push(publicationSubscription);
|
||||
}
|
||||
@@ -178,8 +179,6 @@ export class PublicationEditionComponent implements OnChanges, OnDestroy {
|
||||
const positionStart = textarea.selectionStart;
|
||||
const positionEnd = textarea.selectionEnd;
|
||||
|
||||
const selectedCharacterCount = positionEnd - positionStart;
|
||||
console.log(`cursor position updated: [${positionStart}, ${positionEnd}] (${selectedCharacterCount})`);
|
||||
this.publicationEditionService.editCursorPosition(positionStart, positionEnd);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import { PublicationRestService } from "../../core/rest-services/publications/pu
|
||||
import { copy } from "../../core/utils/ObjectUtils";
|
||||
import { CodeBlockDialog } from "./code-block-dialog/code-block-dialog.component";
|
||||
import { PictureSelectionDialog } from "./picture-selection-dialog/picture-selection-dialog.component";
|
||||
import { PreviewContentRequest } from "../../core/rest-services/publications/model/preview";
|
||||
|
||||
declare let Prism: any;
|
||||
|
||||
@@ -76,7 +77,7 @@ export class PublicationEditionService implements OnDestroy {
|
||||
}
|
||||
|
||||
private _save(state: PublicationEditionState): void {
|
||||
this.stateSubject.next(state);
|
||||
this.stateSubject.next(state);
|
||||
}
|
||||
|
||||
get isLoading$(): Observable<boolean> {
|
||||
@@ -101,7 +102,7 @@ export class PublicationEditionService implements OnDestroy {
|
||||
this.activatedRoute.paramMap.subscribe(params => {
|
||||
const publicationId = params.get('publicationId');
|
||||
if (publicationId == undefined) {
|
||||
this.snackBar.open('A technical error occurred while loading publication data.', 'Close', {duration: 5000});
|
||||
this.snackBar.open($localize`A technical error occurred while loading publication data.`, $localize`Close`, { duration: 5000 });
|
||||
this.location.back();
|
||||
} else {
|
||||
this.publicationRestService.getById(publicationId)
|
||||
@@ -111,8 +112,8 @@ export class PublicationEditionService implements OnDestroy {
|
||||
this.stateSubject.next(state);
|
||||
})
|
||||
.catch(error => {
|
||||
const errorMessage = 'A technical error occurred while loading publication data.';
|
||||
this.snackBar.open(errorMessage, 'Close', {duration: 5000});
|
||||
const errorMessage = $localize`A technical error occurred while loading publication data.`;
|
||||
this.snackBar.open(errorMessage, $localize`Close`, {duration: 5000});
|
||||
console.error(errorMessage, error)
|
||||
})
|
||||
.finally(() => this.isLoadingSubject.next(false));
|
||||
@@ -268,9 +269,12 @@ export class PublicationEditionService implements OnDestroy {
|
||||
const state = this._state;
|
||||
|
||||
this.isPreviewingSubject.next(true);
|
||||
this.publicationRestService.preview(state.publication.text)
|
||||
.then(parsedText => {
|
||||
state.publication.parsedText = parsedText;
|
||||
const request: PreviewContentRequest = {
|
||||
text: state.publication.text
|
||||
};
|
||||
this.publicationRestService.preview(request)
|
||||
.then(response => {
|
||||
state.publication.parsedText = response.text;
|
||||
this._save(state);
|
||||
setTimeout(() => Prism.highlightAll(), 1000);
|
||||
})
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
@for(publication of publications$ | async; track publication) {
|
||||
<a [routerLink]="['/publications/' + publication.id]" class="publication">
|
||||
<img src="/pictures/{{ publication.illustrationId }}"/>
|
||||
<img src="/api/pictures/{{ publication.illustrationId }}"/>
|
||||
<div class="body">
|
||||
<h1>{{publication.title}}</h1>
|
||||
<h2>{{publication.description}}</h2>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<img src="/pictures/{{ publication.author.image }}" [matTooltip]="publication.author.name"/>
|
||||
Publication posted by {{publication.author.name}}
|
||||
<img src="/api/pictures/{{ publication.author.image }}" [matTooltip]="publication.author.name"/>
|
||||
<span i18n>Publication posted by {{publication.author.name}}</span>
|
||||
<span class="publication-date">
|
||||
({{ publication.creationDate | date: 'short' : 'fr-Fr' }})
|
||||
({{ publication.creationDate | date: 'short' }})
|
||||
</span>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<form [formGroup]="formGroup">
|
||||
<input name="search-query" placeholder="Search something..." formControlName="criteria"/>
|
||||
<input name="search-query" placeholder="Search something..." formControlName="criteria" i18n-placeholder/>
|
||||
<button type="submit" (click)="searchPublications()">
|
||||
<mat-icon>search</mat-icon>
|
||||
</button>
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
import { HttpParams } from "@angular/common/http";
|
||||
import { inject, Injectable } from "@angular/core";
|
||||
import { Router } from "@angular/router";
|
||||
import { BehaviorSubject } from "rxjs";
|
||||
|
||||
@Injectable()
|
||||
export class PublicationsSearchBarService {
|
||||
private router = inject(Router);
|
||||
private criteriaSubject = new BehaviorSubject<string>('');
|
||||
|
||||
private cri
|
||||
|
||||
searchPublications(): void {
|
||||
let queryParams = new HttpParams();
|
||||
queryParams = queryParams.set('query', this.criteriaSubject.value);
|
||||
this.router.navigate(['/publications'], {queryParams});
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
@for(category of categories$ | async; track category) {
|
||||
<div class="category {{category.isOpenned ? 'openned' : ''}}">
|
||||
@for(category of categories(); track category) {
|
||||
<div class="category {{category.isOpenned ? 'openned' : ''}}">
|
||||
<div id="category-{{category.id}}" class="category-header" (click)="setOpenned(category)">
|
||||
{{category.name}}
|
||||
<mat-icon>chevron_right</mat-icon>
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, EventEmitter, inject, OnInit, Output } from "@angular/core";
|
||||
import { Component, EventEmitter, inject, OnDestroy, OnInit, Output, signal } from "@angular/core";
|
||||
import { MatIconModule } from "@angular/material/icon";
|
||||
import { DisplayableCategory, SideMenuService } from "../side-menu.service";
|
||||
import { Observable } from "rxjs";
|
||||
import { Observable, Subscription } from "rxjs";
|
||||
import { RouterModule } from "@angular/router";
|
||||
import { Category } from "../../../core/rest-services/category/model/category";
|
||||
|
||||
@Component({
|
||||
selector: 'app-categories-menu',
|
||||
@@ -12,17 +13,21 @@ import { RouterModule } from "@angular/router";
|
||||
templateUrl: './categories-menu.component.html',
|
||||
styleUrl: './categories-menu.component.scss'
|
||||
})
|
||||
export class CategoriesMenuComponent implements OnInit {
|
||||
private sideMenuService = inject(SideMenuService);
|
||||
export class CategoriesMenuComponent implements OnInit, OnDestroy {
|
||||
@Output()
|
||||
categoryClicked = new EventEmitter<void>();
|
||||
private sideMenuService = inject(SideMenuService);
|
||||
private categoriesSubscription?: Subscription;
|
||||
categories = signal<DisplayableCategory[]>([]);
|
||||
|
||||
ngOnInit(): void {
|
||||
this.categoriesSubscription = this.sideMenuService.categories$
|
||||
.subscribe(categories => this.categories.set(categories));
|
||||
this.sideMenuService.loadCategories();
|
||||
}
|
||||
|
||||
get categories$(): Observable<DisplayableCategory[]> {
|
||||
return this.sideMenuService.categories$;
|
||||
ngOnDestroy(): void {
|
||||
this.categoriesSubscription?.unsubscribe();
|
||||
}
|
||||
|
||||
setOpenned(category: DisplayableCategory): void {
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<div class="menu {{ isOpenned ? 'displayed' : '' }}">
|
||||
<div class="menu {{ isOpenned() ? 'displayed' : '' }}">
|
||||
<h1>
|
||||
<a [routerLink]="['/home']">
|
||||
<img src="assets/images/codiki.png" alt="logo"/>
|
||||
Codiki
|
||||
</a>
|
||||
<button type="button" (click)="close()" matTooltip="Close the menu">
|
||||
<button type="button" (click)="close()" matTooltip="Close the menu" i18n-matTooltip>
|
||||
<mat-icon>close</mat-icon>
|
||||
</button>
|
||||
</h1>
|
||||
<h2>Catégories</h2>
|
||||
<h2 i18n>Categories</h2>
|
||||
<app-categories-menu (categoryClicked)="close()"></app-categories-menu>
|
||||
</div>
|
||||
<div class="overlay {{ isOpenned ? 'displayed' : ''}}" (click)="close()"></div>
|
||||
<div class="overlay {{ isOpenned() ? 'displayed' : ''}}" (click)="close()"></div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, signal } from '@angular/core';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { RouterModule } from '@angular/router';
|
||||
@@ -12,13 +12,13 @@ import { CategoriesMenuComponent } from './categories-menu/categories-menu.compo
|
||||
styleUrl: './side-menu.component.scss'
|
||||
})
|
||||
export class SideMenuComponent {
|
||||
isOpenned: boolean = false;
|
||||
isOpenned = signal(false);
|
||||
|
||||
open(): void {
|
||||
this.isOpenned = true;
|
||||
this.isOpenned.set(true);
|
||||
}
|
||||
|
||||
close(): void {
|
||||
this.isOpenned = false;
|
||||
this.isOpenned.set(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { Injectable, OnDestroy, inject } from '@angular/core';
|
||||
import { CategoryService } from '../../core/service/category.service';
|
||||
import { BehaviorSubject, Observable, Subscription, map } from 'rxjs';
|
||||
import { Category } from '../../core/rest-services/category/model/category';
|
||||
import { CategoryRestService } from '../../core/rest-services/category/category.rest-service';
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
import { CategoryRestService } from '../../core/rest-services/category/category.rest-service';
|
||||
import { Category } from '../../core/rest-services/category/model/category';
|
||||
|
||||
export interface DisplayableCategory {
|
||||
id: string;
|
||||
@@ -20,31 +19,13 @@ export interface DisplayableSubCategory {
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class SideMenuService implements OnDestroy {
|
||||
export class SideMenuService {
|
||||
private categoryRestService = inject(CategoryRestService);
|
||||
private snackBar = inject(MatSnackBar);
|
||||
private categoriesSubject = new BehaviorSubject<DisplayableCategory[]>([]);
|
||||
private isLoadingSubject = new BehaviorSubject<boolean>(false);
|
||||
private isLoadedSubject = new BehaviorSubject<boolean>(false);
|
||||
|
||||
constructor() {
|
||||
// this.categoriesSubscription = this.categoryService.categories$
|
||||
// .pipe(
|
||||
// map(categories =>
|
||||
// categories
|
||||
// .filter(category => category.subCategories?.length)
|
||||
// .map(category =>
|
||||
// this.mapToDisplayableCategory(category)
|
||||
// )
|
||||
// )
|
||||
// )
|
||||
// .subscribe(categories => this.categoriesSubject.next(categories));
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
// this.categoriesSubscription?.unsubscribe();
|
||||
}
|
||||
|
||||
private mapToDisplayableCategory(category: Category): DisplayableCategory {
|
||||
return {
|
||||
id: category.id,
|
||||
@@ -89,9 +70,9 @@ export class SideMenuService implements OnDestroy {
|
||||
this.categoriesSubject.next(displayableCategories);
|
||||
})
|
||||
.catch(error => {
|
||||
const errorMessage = "An error occured while loading categories.";
|
||||
const errorMessage = $localize`An error occured while loading categories.`;
|
||||
console.error(errorMessage, error);
|
||||
this.snackBar.open(errorMessage, 'Close', { duration: 5000 });
|
||||
this.snackBar.open(errorMessage, $localize`Close`, { duration: 5000 });
|
||||
})
|
||||
.finally(() => {
|
||||
this.isLoadingSubject.next(false);
|
||||
|
||||
@@ -12,7 +12,7 @@ export const authenticationGuard: CanActivateFn = () => {
|
||||
return true;
|
||||
} else {
|
||||
router.navigate(['/login']);
|
||||
snackBar.open('You are unauthenticated. Please, log-in first.', 'Close', { duration: 5000 });
|
||||
snackBar.open($localize`You are unauthenticated. Please, log-in first.`, $localize`Close`, { duration: 5000 });
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -83,7 +83,7 @@ export class JwtInterceptor implements HttpInterceptor {
|
||||
this.router.navigate(['/login']);
|
||||
this.refreshTokenSubject.next(undefined);
|
||||
this.authenticationService.unauthenticate();
|
||||
this.snackBar.open('You are unauthenticated. Please, re-authenticate before retrying your action.', 'Close', { duration: 5000 });
|
||||
this.snackBar.open($localize`You are unauthenticated. Please, re-authenticate before retrying your action.`, $localize`Close`, { duration: 5000 });
|
||||
return throwError(() => initialError);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export interface PreviewContentRequest {
|
||||
text: string;
|
||||
}
|
||||
|
||||
export interface PreviewContentResponse {
|
||||
text: string;
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { HttpClient, HttpParams } from '@angular/common/http';
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { last, lastValueFrom } from 'rxjs';
|
||||
import { Publication } from './model/publication';
|
||||
import { PreviewContentRequest, PreviewContentResponse } from './model/preview';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
@@ -31,9 +32,8 @@ export class PublicationRestService {
|
||||
return lastValueFrom(this.httpClient.get<Publication[]>('/api/publications', { params }));
|
||||
}
|
||||
|
||||
preview(publicationText: string): Promise<string> {
|
||||
const request = { text: publicationText };
|
||||
return lastValueFrom(this.httpClient.post<string>('/api/publications/preview', request));
|
||||
preview(request: PreviewContentRequest): Promise<PreviewContentResponse> {
|
||||
return lastValueFrom(this.httpClient.post<PreviewContentResponse>('/api/publications/preview', request));
|
||||
}
|
||||
|
||||
delete(publicationId: string): Promise<void> {
|
||||
|
||||
@@ -18,7 +18,7 @@ export class CategoryService {
|
||||
if (!this.categories?.length) {
|
||||
this.categoryRestService.getCategories()
|
||||
.then(categories => this.categoriesSubject.next(categories))
|
||||
.catch(error => console.error('An error occured while loading categories.', error));
|
||||
.catch(error => console.error($localize`An error occured while loading categories.`, error));
|
||||
}
|
||||
return this.categoriesSubject.asObservable();
|
||||
}
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
<h1>Disconnection...</h1>
|
||||
<h1 i18n>Disconnection...</h1>
|
||||
<mat-spinner></mat-spinner>
|
||||
@@ -1,2 +1,11 @@
|
||||
<h1 i18n>Last publications</h1>
|
||||
<app-publication-list [publications$]="publications$"></app-publication-list>
|
||||
@if (isLoading()) {
|
||||
<h2 i18n>Publications loading...</h2>
|
||||
<mat-spinner></mat-spinner>
|
||||
} @else {
|
||||
@if ((publications$ | async) != []) {
|
||||
<app-publication-list [publications$]="publications$"></app-publication-list>
|
||||
} @else {
|
||||
<h2 i18n>No any publication.</h2>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { HomeComponent } from './home.component';
|
||||
|
||||
describe('HomeComponent', () => {
|
||||
let component: HomeComponent;
|
||||
let fixture: ComponentFixture<HomeComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [HomeComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(HomeComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -1,32 +1,44 @@
|
||||
import { Component, OnInit, inject } from '@angular/core';
|
||||
import { HomeService } from './home.service';
|
||||
import { Observable } from 'rxjs';
|
||||
import { Publication } from '../../core/rest-services/publications/model/publication';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Component, OnDestroy, OnInit, inject, signal } from '@angular/core';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
import { PublicationListComponent } from '../../components/publication-list/publication-list.component';
|
||||
import { Publication } from '../../core/rest-services/publications/model/publication';
|
||||
import { HomeService } from './home.service';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
@Component({
|
||||
selector: 'app-home',
|
||||
standalone: true,
|
||||
imports: [PublicationListComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
MatProgressSpinnerModule,
|
||||
PublicationListComponent
|
||||
],
|
||||
templateUrl: './home.component.html',
|
||||
styleUrl: './home.component.scss',
|
||||
providers: [HomeService]
|
||||
})
|
||||
export class HomeComponent implements OnInit {
|
||||
export class HomeComponent implements OnInit, OnDestroy {
|
||||
private homeService = inject(HomeService);
|
||||
|
||||
get isLoading$(): Observable<boolean> {
|
||||
return this.homeService.isLoading$;
|
||||
}
|
||||
private isLoadingSubscription?: Subscription;
|
||||
private publicationsSubscription?: Subscription;
|
||||
isLoading = signal(false);
|
||||
publicationsIsEmpty = signal(true);
|
||||
|
||||
get publications$(): Observable<Publication[]> {
|
||||
return this.homeService.publications$;
|
||||
}
|
||||
|
||||
|
||||
ngOnInit(): void {
|
||||
this.isLoadingSubscription = this.homeService.isLoading$
|
||||
.subscribe(isLoading => this.isLoading.set(isLoading));
|
||||
this.publicationsSubscription = this.homeService.publications$
|
||||
.subscribe(publications => this.publicationsIsEmpty.set(publications.length > 0));
|
||||
this.homeService.startLatestPublicationsRetrieving();
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.isLoadingSubscription?.unsubscribe();
|
||||
this.publicationsSubscription?.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,8 +26,9 @@ export class HomeService {
|
||||
this.publicationRestService.getLatest()
|
||||
.then(publications => this.publicationsSubject.next(publications))
|
||||
.catch(error => {
|
||||
this.snackBar.open('An error occurred while retrieving latest publications...');
|
||||
console.error('An error occurred while retrieving latest publications...', error);
|
||||
const errorMessage = $localize`An error occurred while retrieving latest publications...`;
|
||||
this.snackBar.open(errorMessage, $localize`Close`, { duration: 5000 });
|
||||
console.error(errorMessage, error);
|
||||
})
|
||||
.finally(() => this.isLoadingSubject.next(false));
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<form [formGroup]="loginForm" (submit)="performLogin()" ngNativeValidate>
|
||||
<h1>Login</h1>
|
||||
<h1 i18n>Login</h1>
|
||||
<div>
|
||||
<mat-icon>mail</mat-icon>
|
||||
<label for="email">
|
||||
<label for="email" i18n>
|
||||
Email address
|
||||
<span class="required">*</span>
|
||||
</label>
|
||||
@@ -10,14 +10,14 @@
|
||||
</div>
|
||||
<div>
|
||||
<mat-icon>lock</mat-icon>
|
||||
<label for="password">
|
||||
<label for="password" i18n>
|
||||
Password
|
||||
<span class="required">*</span>
|
||||
</label>
|
||||
<input type="password" id="password" formControlName="password" required />
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button type="submit">Send</button>
|
||||
<a [routerLink]="['/signin']">Create an account</a>
|
||||
<button type="submit" i18n>Send</button>
|
||||
<a [routerLink]="['/signin']" i18n>Create an account</a>
|
||||
</div>
|
||||
</form>
|
||||
@@ -1,23 +0,0 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { LoginComponent } from './login.component';
|
||||
|
||||
describe('LoginComponent', () => {
|
||||
let component: LoginComponent;
|
||||
let fixture: ComponentFixture<LoginComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [LoginComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(LoginComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -66,16 +66,12 @@ export class LoginService {
|
||||
.login(state.request)
|
||||
.then((response) => {
|
||||
this.authenticationService.authenticate(response.accessToken, response.refreshToken);
|
||||
this.snackBar.open('Authentication succeeded!', 'Close', {
|
||||
duration: 5000,
|
||||
});
|
||||
this.snackBar.open($localize`Authentication succeeded!`, $localize`Close`, { duration: 5000 });
|
||||
this.router.navigate(['/home']);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
this.snackBar.open('Authentication failed.', 'Close', {
|
||||
duration: 5000,
|
||||
});
|
||||
this.snackBar.open($localize`Authentication failed.`, $localize`Close`, { duration: 5000 });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
<h1>Your publications</h1>
|
||||
<h1 i18n>Your publications</h1>
|
||||
|
||||
<a [routerLink]="['/publications/new']"
|
||||
class="new-publication"
|
||||
matTooltip="Add a new publication"
|
||||
matTooltipPosition="left">
|
||||
matTooltipPosition="left"
|
||||
i18n-matTooltip>
|
||||
+
|
||||
</a>
|
||||
|
||||
@if ((isLoading$ | async) === true) {
|
||||
<h2>Publication loading...</h2>
|
||||
@if (isLoading()) {
|
||||
<h2 i18n>Publication loading...</h2>
|
||||
<mat-spinner></mat-spinner>
|
||||
} @else {
|
||||
@if ((isLoaded$ | async) === true) {
|
||||
@if (isLoaded()) {
|
||||
<app-publication-list [publications$]="publications$"></app-publication-list>
|
||||
} @else {
|
||||
<h2>There is no any publication...</h2>
|
||||
<h2 i18n>There is no any publication...</h2>
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,13 @@
|
||||
import { Component, inject, OnInit } from "@angular/core";
|
||||
import { Component, inject, OnDestroy, OnInit, signal } from "@angular/core";
|
||||
import { MatProgressSpinnerModule } from "@angular/material/progress-spinner";
|
||||
import { MyPublicationsService } from "./my-publications.service";
|
||||
import { Observable } from "rxjs";
|
||||
import { Observable, Subscription } from "rxjs";
|
||||
import { PublicationListComponent } from "../../components/publication-list/publication-list.component";
|
||||
import { Publication } from "../../core/rest-services/publications/model/publication";
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { RouterModule } from "@angular/router";
|
||||
import { MatTooltipModule } from "@angular/material/tooltip";
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'app-my-component',
|
||||
standalone: true,
|
||||
@@ -23,22 +22,28 @@ import { MatTooltipModule } from "@angular/material/tooltip";
|
||||
],
|
||||
providers: [MyPublicationsService]
|
||||
})
|
||||
export class MyPublicationsComponent implements OnInit {
|
||||
export class MyPublicationsComponent implements OnInit, OnDestroy {
|
||||
private readonly myPublicationsService = inject(MyPublicationsService);
|
||||
private isLoadingSubscription?: Subscription;
|
||||
private isLoadedSubscription?: Subscription;
|
||||
|
||||
isLoading = signal(false);
|
||||
isLoaded = signal(false);
|
||||
|
||||
get publications$(): Observable<Publication[]> {
|
||||
return this.myPublicationsService.publications$;
|
||||
}
|
||||
|
||||
get isLoading$(): Observable<boolean> {
|
||||
return this.myPublicationsService.isLoading$;
|
||||
}
|
||||
|
||||
get isLoaded$(): Observable<boolean> {
|
||||
return this.myPublicationsService.isLoaded$;
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.isLoadingSubscription = this.myPublicationsService.isLoading$
|
||||
.subscribe(isLoading => this.isLoading.set(isLoading));
|
||||
this.isLoadedSubscription = this.myPublicationsService.isLoaded$
|
||||
.subscribe(isLoaded => this.isLoaded.set(isLoaded));
|
||||
this.myPublicationsService.loadAuthenticatedUserPublications();
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.isLoadingSubscription?.unsubscribe();
|
||||
this.isLoadedSubscription?.unsubscribe();
|
||||
}
|
||||
}
|
||||
@@ -40,8 +40,8 @@ export class MyPublicationsService {
|
||||
this.publicationsSubject.next(publications);
|
||||
})
|
||||
.catch(error => {
|
||||
const errorMessage = 'An error occurred while retrieving your publications...';
|
||||
this.snackBar.open(errorMessage);
|
||||
const errorMessage = $localize`An error occurred while retrieving your publications...`;
|
||||
this.snackBar.open(errorMessage, $localize`Close`, { duration: 5000 });
|
||||
console.error(errorMessage, error);
|
||||
})
|
||||
.finally(() => {
|
||||
@@ -50,7 +50,7 @@ export class MyPublicationsService {
|
||||
});
|
||||
} else {
|
||||
this.authenticationService.unauthenticate();
|
||||
this.snackBar.open('You are unauthenticated. Please, log-in first.', 'Close', { duration: 5000 });
|
||||
this.snackBar.open($localize`You are unauthenticated. Please, log-in first.`, $localize`Close`, { duration: 5000 });
|
||||
this.router.navigate(['/login']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1,9 @@
|
||||
<app-publication-edition title="Creation of a new publication" [publication]="publication" [isSaving$]="isSaving$" (publicationSave)="onPublicationSave($event)"></app-publication-edition>
|
||||
@if (publicationIsNotNull()) {
|
||||
<app-publication-edition
|
||||
title="Creation of a new publication"
|
||||
[publication]="publication()!!"
|
||||
[isSaving$]="isSaving$"
|
||||
(publicationSave)="onPublicationSave($event)"
|
||||
i18n-title>
|
||||
</app-publication-edition>
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component, inject, OnInit } from "@angular/core";
|
||||
import { Component, computed, inject, OnInit, signal } from "@angular/core";
|
||||
import { PublicationEditionComponent } from "../../components/publication-edition/publication-edition.component";
|
||||
import { PublicationRestService } from "../../core/rest-services/publications/publication.rest-service";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
@@ -26,7 +26,8 @@ export class PublicationCreationComponent implements OnInit {
|
||||
private readonly snackBar = inject(MatSnackBar);
|
||||
private isSavingSubject = new BehaviorSubject<boolean>(false);
|
||||
private subscriptions: Subscription[] = [];
|
||||
publication!: Publication;
|
||||
publication = signal<Publication | undefined>(undefined);
|
||||
publicationIsNotNull = computed(() => !!this.publication());
|
||||
|
||||
get isSaving$(): Observable<boolean> {
|
||||
return this.isSavingSubject.asObservable();
|
||||
@@ -40,7 +41,7 @@ export class PublicationCreationComponent implements OnInit {
|
||||
name: authenticatedUser.pseudo,
|
||||
image: authenticatedUser.photoId ?? ''
|
||||
};
|
||||
this.publication = {
|
||||
const newPublication: Publication = {
|
||||
id: '',
|
||||
key: '',
|
||||
title: '',
|
||||
@@ -52,6 +53,7 @@ export class PublicationCreationComponent implements OnInit {
|
||||
categoryId: '',
|
||||
author
|
||||
};
|
||||
this.publication.set(newPublication);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,13 +65,13 @@ export class PublicationCreationComponent implements OnInit {
|
||||
this.isSavingSubject.next(true);
|
||||
this.publicationRestService.create(publication)
|
||||
.then(() => {
|
||||
this.snackBar.open('Publication created succesfully!', 'Close', { duration: 5000 });
|
||||
this.snackBar.open($localize`Publication created succesfully!`, $localize`Close`, { duration: 5000 });
|
||||
this.router.navigate(['/my-publications']);
|
||||
})
|
||||
.catch(error => {
|
||||
const errorMessage = 'An error occured while saving new publication.';
|
||||
const errorMessage = $localize`An error occured while saving new publication.`;
|
||||
console.error(errorMessage, error);
|
||||
this.snackBar.open(errorMessage, 'Close', { duration: 5000 });
|
||||
this.snackBar.open(errorMessage, $localize`Close`, { duration: 5000 });
|
||||
})
|
||||
.finally(() => this.isSavingSubject.next(false));
|
||||
}
|
||||
|
||||
@@ -1,13 +1,20 @@
|
||||
@if ((isLoading$ | async) == true) {
|
||||
@if (isLoading()) {
|
||||
<h2 i18n>Loading publication to edit...</h2>
|
||||
<mat-spinner></mat-spinner>
|
||||
}
|
||||
@else {
|
||||
@if (publication) {
|
||||
<app-publication-edition title="Update publication {{ publication.title }}" [publication]="publication" [isSaving$]="isSaving$" (publicationSave)="onPublicationSave($event)"></app-publication-edition>
|
||||
<app-publication-edition
|
||||
title="Update publication {{ publication.title }}"
|
||||
[publication]="publication"
|
||||
[isSaving$]="isSaving$"
|
||||
(publicationSave)="onPublicationSave($event)"
|
||||
i18n-title>
|
||||
</app-publication-edition>
|
||||
}
|
||||
@else {
|
||||
<div class="loading-failed">
|
||||
<h1>Publication failed to load...</h1>
|
||||
<h1 i18n>Publication failed to load...</h1>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { CommonModule, Location } from '@angular/common';
|
||||
import { Component, inject, OnDestroy, OnInit } from '@angular/core';
|
||||
import { Component, inject, OnDestroy, OnInit, signal } from '@angular/core';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatDialogModule } from '@angular/material/dialog';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
@@ -41,25 +41,21 @@ export class PublicationUpdateComponent implements OnInit, OnDestroy {
|
||||
private readonly location = inject(Location);
|
||||
private readonly router = inject(Router);
|
||||
private readonly snackBar = inject(MatSnackBar);
|
||||
private isLoadingSubject = new BehaviorSubject<boolean>(false);
|
||||
private isSavingSubject = new BehaviorSubject<boolean>(false);
|
||||
private subscriptions: Subscription[] = [];
|
||||
isLoading = signal(false);
|
||||
publication: Publication | undefined;
|
||||
|
||||
get isLoading$(): Observable<boolean> {
|
||||
return this.isLoadingSubject.asObservable();
|
||||
}
|
||||
|
||||
get isSaving$(): Observable<boolean> {
|
||||
return this.isSavingSubject.asObservable();
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.isLoadingSubject.next(true);
|
||||
this.isLoading.set(true);
|
||||
this.activatedRoute.paramMap.subscribe(params => {
|
||||
const publicationId = params.get('publicationId');
|
||||
if (publicationId == undefined) {
|
||||
this.snackBar.open('A technical error occurred while loading publication data.', 'Close', { duration: 5000 });
|
||||
this.snackBar.open($localize`A technical error occurred while loading publication data.`, $localize`Close`, { duration: 5000 });
|
||||
this.location.back();
|
||||
} else {
|
||||
this.publicationRestService.getById(publicationId)
|
||||
@@ -67,11 +63,11 @@ export class PublicationUpdateComponent implements OnInit, OnDestroy {
|
||||
this.publication = publication;
|
||||
})
|
||||
.catch(error => {
|
||||
const errorMessage = 'A technical error occurred while loading publication data.';
|
||||
this.snackBar.open(errorMessage, 'Close', { duration: 5000 });
|
||||
const errorMessage = $localize`A technical error occurred while loading publication data.`;
|
||||
this.snackBar.open(errorMessage, $localize`Close`, { duration: 5000 });
|
||||
console.error(errorMessage, error)
|
||||
})
|
||||
.finally(() => this.isLoadingSubject.next(false));
|
||||
.finally(() => this.isLoading.set(false));
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -84,13 +80,13 @@ export class PublicationUpdateComponent implements OnInit, OnDestroy {
|
||||
this.isSavingSubject.next(true);
|
||||
this.publicationRestService.update(publication)
|
||||
.then(() => {
|
||||
this.snackBar.open('Publication updated succesfully!', 'Close', { duration: 5000 });
|
||||
this.snackBar.open($localize`Publication updated succesfully!`, $localize`Close`, { duration: 5000 });
|
||||
this.router.navigate(['/home']);
|
||||
})
|
||||
.catch(error => {
|
||||
const errorMessage = 'An error occured while saving publication modifications.';
|
||||
const errorMessage = $localize`An error occured while saving publication modifications.`;
|
||||
console.error(errorMessage, error);
|
||||
this.snackBar.open(errorMessage, 'Close', { duration: 5000 });
|
||||
this.snackBar.open(errorMessage, $localize`Close`, { duration: 5000 });
|
||||
})
|
||||
.finally(() => this.isSavingSubject.next(false));
|
||||
}
|
||||
|
||||
@@ -1,40 +1,48 @@
|
||||
@if (isLoading) {
|
||||
@if (isLoading()) {
|
||||
<h2 i18n>Publication content loading...</h2>
|
||||
<mat-spinner></mat-spinner>
|
||||
} @else {
|
||||
@if (publication) {
|
||||
@if (isPublicationUndefined()) {
|
||||
<div class="loading-failed">
|
||||
<h1 i18n>Publication failed to load...</h1>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="card">
|
||||
<img src="/pictures/{{ publication.illustrationId }}" />
|
||||
<img src="/api/pictures/{{ publication()?.illustrationId }}" />
|
||||
<header>
|
||||
<h1>{{ publication.title }}</h1>
|
||||
<h2>{{ publication.description }}</h2>
|
||||
@if (isAuthorAndUserEquals) {
|
||||
<a [routerLink]="['edit']" class="button action" matTooltip="Click to edit the publication">
|
||||
<h1>{{ publication()?.title }}</h1>
|
||||
<h2>{{ publication()?.description }}</h2>
|
||||
@if (isAuthorAndUserEquals()) {
|
||||
<a [routerLink]="['edit']"
|
||||
class="button action"
|
||||
matTooltip="Click to edit the publication"
|
||||
i18n-matTooltip>
|
||||
<mat-icon>edit</mat-icon>
|
||||
</a>
|
||||
}
|
||||
</header>
|
||||
<main [innerHTML]="publication.parsedText"></main>
|
||||
<main [innerHTML]="publication()?.parsedText"></main>
|
||||
<footer>
|
||||
<div class="metadata">
|
||||
<img src="/pictures/{{ publication.author.image }}" [matTooltip]="publication.author.name" />
|
||||
<img src="/api/pictures/{{ publication()?.author?.image }}" [matTooltip]="publication()?.author?.name" />
|
||||
<div class="posting-data">
|
||||
Publication posted by {{ publication.author.name }}
|
||||
<span i18n>Publication posted by {{ publication()?.author?.name }}</span>
|
||||
<span class="publication-date">
|
||||
({{ publication.creationDate | date: 'short' : 'fr-FR' }})
|
||||
({{ publication()?.creationDate | date: 'short' }})
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@if (isAuthorAndUserEquals) {
|
||||
<button type="button" (click)="deletePublication()" matTooltip="Click to delete the publication" matTooltipPosition="left">
|
||||
@if (isAuthorAndUserEquals()) {
|
||||
<button type="button"
|
||||
(click)="deletePublication()"
|
||||
matTooltip="Click to delete the publication"
|
||||
matTooltipPosition="left"
|
||||
i18n-matTooltip>
|
||||
<mat-icon>delete</mat-icon>
|
||||
Delete
|
||||
</button>
|
||||
}
|
||||
</footer>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="loading-failed">
|
||||
<h1>Publication failed to load...</h1>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,8 @@ $cardBorderRadius: .5em;
|
||||
|
||||
:host {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
.card {
|
||||
display: flex;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component, OnDestroy, OnInit, inject } from '@angular/core';
|
||||
import { Component, OnDestroy, OnInit, computed, inject, signal } from '@angular/core';
|
||||
import { PublicationRestService } from '../../core/rest-services/publications/publication.rest-service';
|
||||
import { ActivatedRoute, Router, RouterModule } from '@angular/router';
|
||||
import { Subscription } from 'rxjs';
|
||||
@@ -30,9 +30,10 @@ export class PublicationComponent implements OnInit, OnDestroy {
|
||||
private readonly snackBar = inject(MatSnackBar);
|
||||
private paramMapSubscription?: Subscription;
|
||||
private afterDialogCloseSubscription?: Subscription;
|
||||
isLoading: boolean = false;
|
||||
isAuthorAndUserEquals: boolean = false;
|
||||
publication?: Publication;
|
||||
isLoading = signal(false);
|
||||
isAuthorAndUserEquals = computed(() => this.authenticationService.getAuthenticatedUser()?.id === this.publication()?.author.id);
|
||||
publication = signal<Publication | undefined>(undefined);
|
||||
isPublicationUndefined = computed(() => !this.publication())
|
||||
|
||||
ngOnInit(): void {
|
||||
this.paramMapSubscription = this.activatedRoute
|
||||
@@ -41,20 +42,20 @@ export class PublicationComponent implements OnInit, OnDestroy {
|
||||
const publicationId = params.get('publicationId');
|
||||
|
||||
if (publicationId) {
|
||||
this.isLoading = true;
|
||||
this.isLoading.set(true);
|
||||
|
||||
this.publicationRestService.getById(publicationId)
|
||||
.then(publication => {
|
||||
this.publication = publication;
|
||||
this.isAuthorAndUserEquals = this.authenticationService.getAuthenticatedUser()?.id === this.publication.author.id;
|
||||
this.publication.set(publication);
|
||||
setTimeout(() => Prism.highlightAll(), 100);
|
||||
})
|
||||
.catch(error => {
|
||||
this.snackBar.open('An error occurred while loading publication...', 'Close', { duration: 5000 });
|
||||
console.error('An error occurred while loading publication...', error);
|
||||
const errorMessage = $localize`An error occurred while loading publication...`;
|
||||
this.snackBar.open(errorMessage, $localize`Close`, { duration: 5000 });
|
||||
console.error(errorMessage, error);
|
||||
})
|
||||
.finally(() => {
|
||||
this.isLoading = false;
|
||||
this.isLoading.set(false);
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -70,17 +71,17 @@ export class PublicationComponent implements OnInit, OnDestroy {
|
||||
ConfirmationDialog,
|
||||
{
|
||||
data: {
|
||||
title: 'Publication deletion',
|
||||
description: 'Are you sure you want to delete this publication?'
|
||||
title: $localize`Publication deletion`,
|
||||
description: $localize`Are you sure you want to delete this publication?`
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
this.afterDialogCloseSubscription = dialogRef.afterClosed()
|
||||
.subscribe(response => {
|
||||
if (response && this.publication?.id) {
|
||||
this.publicationRestService.delete(this.publication.id);
|
||||
this.snackBar.open('Publication deleted', 'Close', { duration: 5000 });
|
||||
if (response && !this.isPublicationUndefined()) {
|
||||
this.publicationRestService.delete(this.publication()?.id!!);
|
||||
this.snackBar.open($localize`Publication deleted`, $localize`Close`, { duration: 5000 });
|
||||
this.location.back();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<h1>Search results</h1>
|
||||
<h1 i18n>Search results</h1>
|
||||
@if((isLoading$ | async) === true) {
|
||||
<h2>Search in progress...</h2>
|
||||
<h2 i18n>Search in progress...</h2>
|
||||
<mat-spinner></mat-spinner>
|
||||
} @else if((isLoaded$ | async) === true) {
|
||||
@if((publications$ | async)?.length) {
|
||||
<app-publication-list [publications$]="publications$"></app-publication-list>
|
||||
} @else {
|
||||
No any result.
|
||||
<span i18n>No any result.</span>
|
||||
}
|
||||
} @else {
|
||||
No any result.
|
||||
<span i18n>No any result.</span>
|
||||
}
|
||||
|
||||
@@ -36,9 +36,9 @@ export class SearchPublicationsService {
|
||||
})
|
||||
.catch(error => {
|
||||
if (error.status !== 404) {
|
||||
const errorMessage = 'An error occured while retrieving publications.';
|
||||
const errorMessage = $localize`An error occured while retrieving publications.`;
|
||||
console.error(errorMessage, error);
|
||||
this.snackBar.open(errorMessage, 'Close', { duration: 5000 });
|
||||
this.snackBar.open(errorMessage, $localize`Close`, { duration: 5000 });
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<form [formGroup]="signinForm" (submit)="performSignin()" ngNativeValidate>
|
||||
<h1>Signin</h1>
|
||||
<h1 i18n>Signin</h1>
|
||||
<div>
|
||||
<mat-icon>person</mat-icon>
|
||||
<label for="pseudo">
|
||||
<label for="pseudo" i18n>
|
||||
Pseudo
|
||||
<span class="required">*</span>
|
||||
</label>
|
||||
@@ -10,7 +10,7 @@
|
||||
</div>
|
||||
<div>
|
||||
<mat-icon>mail</mat-icon>
|
||||
<label for="email">
|
||||
<label for="email" i18n>
|
||||
Email address
|
||||
<span class="required">*</span>
|
||||
</label>
|
||||
@@ -18,7 +18,7 @@
|
||||
</div>
|
||||
<div>
|
||||
<mat-icon>lock</mat-icon>
|
||||
<label for="password">
|
||||
<label for="password" i18n>
|
||||
Password
|
||||
<span class="required">*</span>
|
||||
</label>
|
||||
@@ -26,14 +26,14 @@
|
||||
</div>
|
||||
<div>
|
||||
<mat-icon>lock</mat-icon>
|
||||
<label for="confirm-password">
|
||||
<label for="confirm-password" i18n>
|
||||
Confirm password
|
||||
<span class="required">*</span>
|
||||
</label>
|
||||
<input type="password" id="confirm-password" formControlName="confirmPassword" required />
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button type="submit">Send</button>
|
||||
<a [routerLink]="['/login']">I already have an account</a>
|
||||
<button type="submit" i18n>Send</button>
|
||||
<a [routerLink]="['/login']" i18n>I already have an account</a>
|
||||
</div>
|
||||
</form>
|
||||
@@ -1,23 +0,0 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { SigninComponent } from './signin.component';
|
||||
|
||||
describe('SigninComponent', () => {
|
||||
let component: SigninComponent;
|
||||
let fixture: ComponentFixture<SigninComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [SigninComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(SigninComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -76,7 +76,7 @@ export class SigninService {
|
||||
if (state.request.password !== state.confirmPassword) {
|
||||
const confirmPasswordError: FormError = {
|
||||
fieldName: 'confirmPassword',
|
||||
errorMessage: 'Typed password are different.'
|
||||
errorMessage: $localize`Typed passwords are different.`
|
||||
}
|
||||
state.errors.filter(error => error.fieldName !== 'confirmPassword');
|
||||
state.errors.push(confirmPasswordError)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>CodikiNg</title>
|
||||
<title>Codiki</title>
|
||||
<base href="/">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="icon" type="image/x-icon" href="assets/images/favicon.ico">
|
||||
|
||||
@@ -1,6 +1,92 @@
|
||||
{
|
||||
"locale": "fr-FR",
|
||||
"translations": {
|
||||
"4869473828758837325": "Dernières publications"
|
||||
"3603720768157919481": "Non",
|
||||
"4861926948802653243": "Oui",
|
||||
"5277292207846698726": "{$START_TAG_SPAN}©{$CLOSE_TAG_SPAN} 2016 - 2024 Tous droits réservés - 2.0-alpha {$START_LINK}{$START_TAG_MAT_ICON}favorite{$CLOSE_TAG_MAT_ICON}{$CLOSE_LINK}",
|
||||
"9214089025589249203": "Les indicateurs de vie du site seront disponibles ultérieurement...",
|
||||
"1711651175531679766": "La documentation sera disponible ultérieurement...",
|
||||
"6299155290121808295": "Développements realisés par",
|
||||
"6413533995205687351": "Cliquez pour afficher le menu",
|
||||
"3394094310583807145": "{$START_TAG_MAT_ICON}description{$CLOSE_TAG_MAT_ICON} Mes publications",
|
||||
"5690703874094076840": "{$START_TAG_MAT_ICON}logout{$CLOSE_TAG_MAT_ICON} Déconnexion",
|
||||
"2454050363478003966": "Connexion",
|
||||
"6379828571327118783": "Ajouter un bloc de code",
|
||||
"933522671092765466": "Langage de programmation",
|
||||
"5953425146193292178": "Bloc de code",
|
||||
"7975252153657972743": "Valider",
|
||||
"2330577642930707695": "Annuler",
|
||||
"162921950259491830": "Sélectionnez une illustation",
|
||||
"1678880547384144969": "Chargement des images...",
|
||||
"4111988153902305972": "Choisir cette illustation",
|
||||
"4491342806775118195": "Il n'y a aucune image.",
|
||||
"5036131155743433968": "{$START_TAG_MAT_ICON}upload_file{$CLOSE_TAG_MAT_ICON} Ajouter une nouvelle image",
|
||||
"6852365376059142995": "Une erreur est survenue lors du chargements de vos images.",
|
||||
"7819314041543176992": "Fermer",
|
||||
"6198966268398913224": "Une erreur technique est survenue lors de l'ajout de votre image.",
|
||||
"3603937053948195893": "Édition",
|
||||
"5701618810648052610": "Titre",
|
||||
"4902817035128594900": "Description",
|
||||
"1806667489382256324": "Categorie",
|
||||
"4954740313142081867": "Cliquez pour changer l'illustation",
|
||||
"382530603854484923": "Cliquez pour ajouter une section titre de niveau 1",
|
||||
"7918008528690631661": "Cliquez pour ajouter une section titre de niveau 2",
|
||||
"200884310255063303": "Cliquez pour ajouter une section titre de niveau 3",
|
||||
"327371240590280003": "Cliquez pour ajouter une image",
|
||||
"2615926469669796978": "Cliquez pour ajouter un lien",
|
||||
"4689637499823680515": "Cliquez pour ajouter un bloc de code",
|
||||
"1065684538660053227": "Cliquez pour afficher l'aide",
|
||||
"6205355627445317276": "Contenu",
|
||||
"725618658772732433": "Prévisualisation",
|
||||
"1775659119052622417": "Chargement du rendu de la prévisualisation...",
|
||||
"3768927257183755959": "Enregistrer",
|
||||
"4580042433737768435": "Une erreur technique est survenue lors du chargement de la publication à modifier.",
|
||||
"2578598149846191609": "Publication postée par {$INTERPOLATION}",
|
||||
"6514394873612421064": "Rechercher quelque chose...",
|
||||
"9122763438636464100": "Fermer le menu",
|
||||
"1902100407096396858": "Catégories",
|
||||
"6940115735259407353": "Une erreur est survenue lors du chargement des catégories.",
|
||||
"3450287383703155559": "Vous n'êtes pas connecté. Veuillez vous connecter avant de réessayer.",
|
||||
"5455465794443528807": "You n'êtes pas connecté. Veuillez vous connecter avant de réessayer votre opération.",
|
||||
"4011987306265136481": "Déconnexion...",
|
||||
"4869473828758837325": "Dernières publications",
|
||||
"5148998676057880041": "Chargement des publications...",
|
||||
"3688381096110057852": "Il n'y a aucune publication.",
|
||||
"8393632007890629197": "Une erreur est survenue lors du chargement des dernières publications...",
|
||||
"8138320902772264034": "Adresse email {$START_TAG_SPAN}*{$CLOSE_TAG_SPAN}",
|
||||
"9175472990822669391": "Mot de passe {$START_TAG_SPAN}*{$CLOSE_TAG_SPAN}",
|
||||
"6490688569532630280": "Valider",
|
||||
"2308975396733519902": "Créer un compte",
|
||||
"1037765878727976611": "Connexion réussie",
|
||||
"6034686865111167926": "Une erreur est survenue lors de la connexion.",
|
||||
"1041423751558601074": "Vos publications",
|
||||
"5463894166935799864": "Rédiger une nouvelle publication",
|
||||
"1519054954638405159": "Chargement de la liste de vos publications...",
|
||||
"5982957837973242128": "Vous n'avez rien publié...",
|
||||
"6147923540123489141": "Une erreur est survenue lors de la récupération de vos publications...",
|
||||
"2804059545779555969": "Rédaction d'une nouvelle publication",
|
||||
"4229813881636784544": "Publication créée avec succès !",
|
||||
"1772332295232318552": "Une erreur est survenue lors de l'enregistrement de la nouvelle publication.",
|
||||
"5585500423922995936": "Chargement de la publication à modifier...",
|
||||
"1487849090405054079": "Modification de la publication {$INTERPOLATION}",
|
||||
"570282468314450588": "La publication n'a pas pu être chargée...",
|
||||
"1766298802296530620": "Publication modifiée avec succès !",
|
||||
"5690532907092398845": "Une erreur est survenue lors de l'enregistrement des modifications de la publication.",
|
||||
"9035578711395348230": "Chargement du contenu de la publication...",
|
||||
"1224244276514211842": "Cliquez pour modifier la publication",
|
||||
"2759576657543552825": "Cliquez pour supprimer la publication",
|
||||
"3861667381167371965": "Une erreur est survenue lors du chargement de la publication...",
|
||||
"3189372093194446122": "Suppression de la publication",
|
||||
"7149611045520326321": "Êtes vous sûr de vouloir supprimer cette publication ?",
|
||||
"4353366709080867962": "Publication supprimée",
|
||||
"1460318440531551596": "Résultats de la recherche",
|
||||
"886526241743571962": "Recherche des publications...",
|
||||
"3467080651873197381": "Il n'y a aucun résultat.",
|
||||
"8212807341111457015": "Une erreur est survenue lors de la recherche des publications..",
|
||||
"2101902914887471883": "Créer un compte",
|
||||
"963610942522043725": "Pseudo {$START_TAG_SPAN}*{$CLOSE_TAG_SPAN}",
|
||||
"5090593460426139718": "Confirmation du mot de passe {$START_TAG_SPAN}*{$CLOSE_TAG_SPAN}",
|
||||
"3461230574295546047": "J'ai déjà un compte",
|
||||
"5052944271008222026": "Les mots de passe saisis sont différents."
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,92 @@
|
||||
{
|
||||
"locale": "en-UK",
|
||||
"translations": {
|
||||
"4869473828758837325": "Last publications"
|
||||
"3603720768157919481": " No ",
|
||||
"4861926948802653243": " Yes ",
|
||||
"5277292207846698726": "{$START_TAG_SPAN}©{$CLOSE_TAG_SPAN} 2016 - 2024 All rights reserved - 2.0-alpha {$START_LINK}{$START_TAG_MAT_ICON}favorite{$CLOSE_TAG_MAT_ICON}{$CLOSE_LINK}",
|
||||
"9214089025589249203": "Health checking will be available in future...",
|
||||
"1711651175531679766": "Documentation will be available in future...",
|
||||
"6299155290121808295": "Development realised by",
|
||||
"6413533995205687351": "Click to show side menu",
|
||||
"3394094310583807145": "{$START_TAG_MAT_ICON}description{$CLOSE_TAG_MAT_ICON} My publications ",
|
||||
"5690703874094076840": "{$START_TAG_MAT_ICON}logout{$CLOSE_TAG_MAT_ICON} Disconnect ",
|
||||
"2454050363478003966": "Login",
|
||||
"6379828571327118783": "Add a code block",
|
||||
"933522671092765466": "Programming language",
|
||||
"5953425146193292178": "Code block",
|
||||
"7975252153657972743": " Validate ",
|
||||
"2330577642930707695": " Cancel ",
|
||||
"162921950259491830": "Select an illustration",
|
||||
"1678880547384144969": "Pictures loading...",
|
||||
"4111988153902305972": "Choose this illustration",
|
||||
"4491342806775118195": "There is no any picture.",
|
||||
"5036131155743433968": "{$START_TAG_MAT_ICON}upload_file{$CLOSE_TAG_MAT_ICON} Add new picture ",
|
||||
"6852365376059142995": "An error occured while loading pictures.",
|
||||
"7819314041543176992": "Close",
|
||||
"6198966268398913224": "A technical error occured while uploading your picture.",
|
||||
"3603937053948195893": "Edition",
|
||||
"5701618810648052610": "Title",
|
||||
"4902817035128594900": "Description",
|
||||
"1806667489382256324": "Category",
|
||||
"4954740313142081867": "Click to change illustration",
|
||||
"382530603854484923": "Click to insert a title 1 section",
|
||||
"7918008528690631661": "Click to insert a title 2 section",
|
||||
"200884310255063303": "Click to insert a title 3 section",
|
||||
"327371240590280003": "Click to insert a picture",
|
||||
"2615926469669796978": "Click to insert a link",
|
||||
"4689637499823680515": "Click to insert a code block",
|
||||
"1065684538660053227": "Click to display editor help",
|
||||
"6205355627445317276": "Content",
|
||||
"725618658772732433": "Previewing",
|
||||
"1775659119052622417": "Preview is loading...",
|
||||
"3768927257183755959": "Save",
|
||||
"4580042433737768435": "A technical error occurred while loading publication data.",
|
||||
"2578598149846191609": "Publication posted by {$INTERPOLATION}",
|
||||
"6514394873612421064": "Search something...",
|
||||
"9122763438636464100": "Close the menu",
|
||||
"1902100407096396858": "Categories",
|
||||
"6940115735259407353": "An error occured while loading categories.",
|
||||
"3450287383703155559": "You are unauthenticated. Please, log-in first.",
|
||||
"5455465794443528807": "You are unauthenticated. Please, re-authenticate before retrying your action.",
|
||||
"4011987306265136481": "Disconnection...",
|
||||
"4869473828758837325": "Last publications",
|
||||
"5148998676057880041": "Publications loading...",
|
||||
"3688381096110057852": "No any publication.",
|
||||
"8393632007890629197": "An error occurred while retrieving latest publications...",
|
||||
"8138320902772264034": " Email address {$START_TAG_SPAN}*{$CLOSE_TAG_SPAN}",
|
||||
"9175472990822669391": " Password {$START_TAG_SPAN}*{$CLOSE_TAG_SPAN}",
|
||||
"6490688569532630280": "Send",
|
||||
"2308975396733519902": "Create an account",
|
||||
"1037765878727976611": "Authentication succeeded!",
|
||||
"6034686865111167926": "Authentication failed.",
|
||||
"1041423751558601074": "Your publications",
|
||||
"5463894166935799864": "Add a new publication",
|
||||
"1519054954638405159": "Publication loading...",
|
||||
"5982957837973242128": "There is no any publication...",
|
||||
"6147923540123489141": "An error occurred while retrieving your publications...",
|
||||
"2804059545779555969": "Creation of a new publication",
|
||||
"4229813881636784544": "Publication created succesfully!",
|
||||
"1772332295232318552": "An error occured while saving new publication.",
|
||||
"5585500423922995936": "Loading publication to edit...",
|
||||
"1487849090405054079": "Update publication {$INTERPOLATION}",
|
||||
"570282468314450588": "Publication failed to load...",
|
||||
"1766298802296530620": "Publication updated succesfully!",
|
||||
"5690532907092398845": "An error occured while saving publication modifications.",
|
||||
"9035578711395348230": "Publication content loading...",
|
||||
"1224244276514211842": "Click to edit the publication",
|
||||
"2759576657543552825": "Click to delete the publication",
|
||||
"3861667381167371965": "An error occurred while loading publication...",
|
||||
"3189372093194446122": "Publication deletion",
|
||||
"7149611045520326321": "Are you sure you want to delete this publication?",
|
||||
"4353366709080867962": "Publication deleted",
|
||||
"1460318440531551596": "Search results",
|
||||
"886526241743571962": "Search in progress...",
|
||||
"3467080651873197381": "No any result.",
|
||||
"8212807341111457015": "An error occured while retrieving publications.",
|
||||
"2101902914887471883": "Signin",
|
||||
"963610942522043725": " Pseudo {$START_TAG_SPAN}*{$CLOSE_TAG_SPAN}",
|
||||
"5090593460426139718": " Confirm password {$START_TAG_SPAN}*{$CLOSE_TAG_SPAN}",
|
||||
"3461230574295546047": "I already have an account",
|
||||
"5052944271008222026": "Typed passwords are different."
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user