Compare commits
No commits in common. "aa23ddb781469f54c0117275ccdf5751b3945958" and "e1a16b3c67c423f47386535e6eeef5ca4b7591fa" have entirely different histories.
aa23ddb781
...
e1a16b3c67
|
|
@ -2398,40 +2398,6 @@ public class Controller : ControllerBase
|
||||||
return comment;
|
return comment;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class UpdateTicketCommentRequest
|
|
||||||
{
|
|
||||||
public Int64 Id { get; set; }
|
|
||||||
public String Body { get; set; } = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost(nameof(UpdateTicketComment))]
|
|
||||||
public ActionResult<TicketComment> UpdateTicketComment([FromBody] UpdateTicketCommentRequest req, Token authToken)
|
|
||||||
{
|
|
||||||
var user = Db.GetSession(authToken)?.User;
|
|
||||||
if (user is null || user.UserType != 2) return Unauthorized();
|
|
||||||
|
|
||||||
var comment = Db.TicketComments.FirstOrDefault(c => c.Id == req.Id);
|
|
||||||
if (comment is null) return NotFound();
|
|
||||||
|
|
||||||
if (comment.AuthorType != (Int32)CommentAuthorType.Human) return Forbid();
|
|
||||||
if (comment.AuthorId != user.Id) return Forbid();
|
|
||||||
|
|
||||||
if (String.IsNullOrWhiteSpace(req.Body)) return BadRequest("Body required.");
|
|
||||||
|
|
||||||
comment.Body = req.Body;
|
|
||||||
comment.EditedAt = DateTime.UtcNow;
|
|
||||||
if (!Db.Update(comment)) return StatusCode(500, "Failed to update comment.");
|
|
||||||
|
|
||||||
var ticket = Db.GetTicketById(comment.TicketId);
|
|
||||||
if (ticket is not null)
|
|
||||||
{
|
|
||||||
ticket.UpdatedAt = DateTime.UtcNow;
|
|
||||||
Db.Update(ticket);
|
|
||||||
}
|
|
||||||
|
|
||||||
return comment;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet(nameof(GetTicketDetail))]
|
[HttpGet(nameof(GetTicketDetail))]
|
||||||
public ActionResult<Object> GetTicketDetail(Int64 id, Token authToken)
|
public ActionResult<Object> GetTicketDetail(Int64 id, Token authToken)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ public class TicketComment
|
||||||
public Int64? AuthorId { get; set; }
|
public Int64? AuthorId { get; set; }
|
||||||
public String Body { get; set; } = "";
|
public String Body { get; set; } = "";
|
||||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||||
public DateTime? EditedAt { get; set; }
|
|
||||||
|
|
||||||
[Ignore] public List<Int64> MentionedUserIds { get; set; } = new();
|
[Ignore] public List<Int64> MentionedUserIds { get; set; } = new();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -72,5 +72,4 @@ public static partial class Db
|
||||||
// Ticket system
|
// Ticket system
|
||||||
public static Boolean Update(Ticket ticket) => Update(obj: ticket);
|
public static Boolean Update(Ticket ticket) => Update(obj: ticket);
|
||||||
public static Boolean Update(TicketAiDiagnosis diagnosis) => Update(obj: diagnosis);
|
public static Boolean Update(TicketAiDiagnosis diagnosis) => Update(obj: diagnosis);
|
||||||
public static Boolean Update(TicketComment comment) => Update(obj: comment);
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,62 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { Box, Button, Tooltip } from '@mui/material';
|
|
||||||
import FormatBoldIcon from '@mui/icons-material/FormatBold';
|
|
||||||
import { useIntl } from 'react-intl';
|
|
||||||
import { applyFormat, FormatKind } from './commentMarkdown';
|
|
||||||
|
|
||||||
interface CommentFormatToolbarProps {
|
|
||||||
textareaRef: React.RefObject<HTMLTextAreaElement | HTMLInputElement | null>;
|
|
||||||
value: string;
|
|
||||||
onChange: (next: string) => void;
|
|
||||||
disabled?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
function CommentFormatToolbar({
|
|
||||||
textareaRef,
|
|
||||||
value,
|
|
||||||
onChange,
|
|
||||||
disabled
|
|
||||||
}: CommentFormatToolbarProps) {
|
|
||||||
const intl = useIntl();
|
|
||||||
|
|
||||||
const handle = (kind: FormatKind) => () => {
|
|
||||||
applyFormat(textareaRef.current, value, kind, onChange);
|
|
||||||
};
|
|
||||||
|
|
||||||
const btnSx = { minWidth: 32, px: 1, py: 0.25, fontSize: 12, textTransform: 'none' as const };
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box sx={{ display: 'flex', gap: 0.5, alignItems: 'center' }}>
|
|
||||||
<Tooltip title={intl.formatMessage({ id: 'commentFormatBold', defaultMessage: 'Bold' })}>
|
|
||||||
<span>
|
|
||||||
<Button size="small" variant="outlined" sx={btnSx} onClick={handle('bold')} disabled={disabled}>
|
|
||||||
<FormatBoldIcon fontSize="inherit" />
|
|
||||||
</Button>
|
|
||||||
</span>
|
|
||||||
</Tooltip>
|
|
||||||
<Tooltip title={intl.formatMessage({ id: 'commentFormatH1', defaultMessage: 'Heading 1' })}>
|
|
||||||
<span>
|
|
||||||
<Button size="small" variant="outlined" sx={btnSx} onClick={handle('h1')} disabled={disabled}>
|
|
||||||
H1
|
|
||||||
</Button>
|
|
||||||
</span>
|
|
||||||
</Tooltip>
|
|
||||||
<Tooltip title={intl.formatMessage({ id: 'commentFormatH2', defaultMessage: 'Heading 2' })}>
|
|
||||||
<span>
|
|
||||||
<Button size="small" variant="outlined" sx={btnSx} onClick={handle('h2')} disabled={disabled}>
|
|
||||||
H2
|
|
||||||
</Button>
|
|
||||||
</span>
|
|
||||||
</Tooltip>
|
|
||||||
<Tooltip title={intl.formatMessage({ id: 'commentFormatH3', defaultMessage: 'Heading 3' })}>
|
|
||||||
<span>
|
|
||||||
<Button size="small" variant="outlined" sx={btnSx} onClick={handle('h3')} disabled={disabled}>
|
|
||||||
H3
|
|
||||||
</Button>
|
|
||||||
</span>
|
|
||||||
</Tooltip>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default CommentFormatToolbar;
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import React, { useContext, useRef, useState } from 'react';
|
import React, { useRef, useState } from 'react';
|
||||||
import { UserContext } from 'src/contexts/userContext';
|
|
||||||
import {
|
import {
|
||||||
Avatar,
|
Avatar,
|
||||||
Box,
|
Box,
|
||||||
|
|
@ -25,8 +24,6 @@ import { FormattedMessage, useIntl } from 'react-intl';
|
||||||
import axiosConfig from 'src/Resources/axiosConfig';
|
import axiosConfig from 'src/Resources/axiosConfig';
|
||||||
import { AdminUser, CommentAuthorType, TicketComment } from 'src/interfaces/TicketTypes';
|
import { AdminUser, CommentAuthorType, TicketComment } from 'src/interfaces/TicketTypes';
|
||||||
import DocumentList from 'src/components/DocumentList';
|
import DocumentList from 'src/components/DocumentList';
|
||||||
import CommentFormatToolbar from './CommentFormatToolbar';
|
|
||||||
import { renderCommentBody } from './commentMarkdown';
|
|
||||||
|
|
||||||
interface CommentThreadProps {
|
interface CommentThreadProps {
|
||||||
ticketId: number;
|
ticketId: number;
|
||||||
|
|
@ -42,13 +39,8 @@ function CommentThread({
|
||||||
adminUsers = []
|
adminUsers = []
|
||||||
}: CommentThreadProps) {
|
}: CommentThreadProps) {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const userCtx = useContext(UserContext);
|
|
||||||
const currentUserId = userCtx?.currentUser?.id;
|
|
||||||
const [body, setBody] = useState('');
|
const [body, setBody] = useState('');
|
||||||
const [submitting, setSubmitting] = useState(false);
|
const [submitting, setSubmitting] = useState(false);
|
||||||
const [editingId, setEditingId] = useState<number | null>(null);
|
|
||||||
const [editBody, setEditBody] = useState('');
|
|
||||||
const [savingEdit, setSavingEdit] = useState(false);
|
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
|
const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
|
||||||
const [uploading, setUploading] = useState(false);
|
const [uploading, setUploading] = useState(false);
|
||||||
|
|
@ -57,7 +49,6 @@ function CommentThread({
|
||||||
const [mentionedIds, setMentionedIds] = useState<number[]>([]);
|
const [mentionedIds, setMentionedIds] = useState<number[]>([]);
|
||||||
const [mentionQuery, setMentionQuery] = useState<string | null>(null);
|
const [mentionQuery, setMentionQuery] = useState<string | null>(null);
|
||||||
const commentInputRef = useRef<HTMLInputElement | null>(null);
|
const commentInputRef = useRef<HTMLInputElement | null>(null);
|
||||||
const editInputRef = useRef<HTMLInputElement | null>(null);
|
|
||||||
|
|
||||||
const MENTION_EXCLUDED_NAMES = ['inesco energy Master Admin'];
|
const MENTION_EXCLUDED_NAMES = ['inesco energy Master Admin'];
|
||||||
|
|
||||||
|
|
@ -134,32 +125,6 @@ function CommentThread({
|
||||||
(a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()
|
(a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()
|
||||||
);
|
);
|
||||||
|
|
||||||
const startEdit = (comment: TicketComment) => {
|
|
||||||
setEditingId(comment.id);
|
|
||||||
setEditBody(comment.body);
|
|
||||||
};
|
|
||||||
|
|
||||||
const cancelEdit = () => {
|
|
||||||
setEditingId(null);
|
|
||||||
setEditBody('');
|
|
||||||
};
|
|
||||||
|
|
||||||
const saveEdit = async (commentId: number) => {
|
|
||||||
if (!editBody.trim()) return;
|
|
||||||
setSavingEdit(true);
|
|
||||||
try {
|
|
||||||
await axiosConfig.post('/UpdateTicketComment', {
|
|
||||||
id: commentId,
|
|
||||||
body: editBody
|
|
||||||
});
|
|
||||||
setEditingId(null);
|
|
||||||
setEditBody('');
|
|
||||||
onCommentAdded();
|
|
||||||
} finally {
|
|
||||||
setSavingEdit(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
if (!body.trim() && selectedFiles.length === 0) return;
|
if (!body.trim() && selectedFiles.length === 0) return;
|
||||||
setSubmitting(true);
|
setSubmitting(true);
|
||||||
|
|
@ -231,8 +196,6 @@ function CommentThread({
|
||||||
|
|
||||||
{sorted.map((comment) => {
|
{sorted.map((comment) => {
|
||||||
const isAi = comment.authorType === CommentAuthorType.AiAgent;
|
const isAi = comment.authorType === CommentAuthorType.AiAgent;
|
||||||
const canEdit = !isAi && currentUserId != null && comment.authorId === currentUserId;
|
|
||||||
const isEditing = editingId === comment.id;
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
key={comment.id}
|
key={comment.id}
|
||||||
|
|
@ -253,7 +216,7 @@ function CommentThread({
|
||||||
</Avatar>
|
</Avatar>
|
||||||
<Box sx={{ flex: 1 }}>
|
<Box sx={{ flex: 1 }}>
|
||||||
<Box
|
<Box
|
||||||
sx={{ display: 'flex', alignItems: 'baseline', gap: 1, flexWrap: 'wrap' }}
|
sx={{ display: 'flex', alignItems: 'baseline', gap: 1 }}
|
||||||
>
|
>
|
||||||
<Typography variant="subtitle2">
|
<Typography variant="subtitle2">
|
||||||
{isAi ? 'AI' : (adminUsers.find(u => u.id === comment.authorId)?.name ?? `User #${comment.authorId ?? '?'}`)}
|
{isAi ? 'AI' : (adminUsers.find(u => u.id === comment.authorId)?.name ?? `User #${comment.authorId ?? '?'}`)}
|
||||||
|
|
@ -261,64 +224,10 @@ function CommentThread({
|
||||||
<Typography variant="caption" color="text.disabled">
|
<Typography variant="caption" color="text.disabled">
|
||||||
{new Date(comment.createdAt).toLocaleString()}
|
{new Date(comment.createdAt).toLocaleString()}
|
||||||
</Typography>
|
</Typography>
|
||||||
{comment.editedAt && (
|
|
||||||
<Typography variant="caption" color="text.disabled" sx={{ fontStyle: 'italic' }}>
|
|
||||||
<FormattedMessage
|
|
||||||
id="commentEdited"
|
|
||||||
defaultMessage="(edited {time})"
|
|
||||||
values={{ time: new Date(comment.editedAt).toLocaleString() }}
|
|
||||||
/>
|
|
||||||
</Typography>
|
|
||||||
)}
|
|
||||||
{canEdit && !isEditing && (
|
|
||||||
<Button
|
|
||||||
size="small"
|
|
||||||
variant="text"
|
|
||||||
sx={{ minWidth: 0, p: 0, ml: 'auto', textTransform: 'none' }}
|
|
||||||
onClick={() => startEdit(comment)}
|
|
||||||
>
|
|
||||||
<FormattedMessage id="edit" defaultMessage="Edit" />
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</Box>
|
</Box>
|
||||||
{isEditing ? (
|
<Typography variant="body2" sx={{ whiteSpace: 'pre-wrap' }}>
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1, mt: 0.5 }}>
|
{comment.body}
|
||||||
<CommentFormatToolbar
|
</Typography>
|
||||||
textareaRef={editInputRef}
|
|
||||||
value={editBody}
|
|
||||||
onChange={setEditBody}
|
|
||||||
disabled={savingEdit}
|
|
||||||
/>
|
|
||||||
<TextField
|
|
||||||
size="small"
|
|
||||||
fullWidth
|
|
||||||
multiline
|
|
||||||
minRows={2}
|
|
||||||
value={editBody}
|
|
||||||
onChange={(e) => setEditBody(e.target.value)}
|
|
||||||
inputRef={editInputRef}
|
|
||||||
/>
|
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'flex-end', gap: 1 }}>
|
|
||||||
<Button
|
|
||||||
size="small"
|
|
||||||
onClick={cancelEdit}
|
|
||||||
disabled={savingEdit}
|
|
||||||
>
|
|
||||||
<FormattedMessage id="cancel" defaultMessage="Cancel" />
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
size="small"
|
|
||||||
variant="contained"
|
|
||||||
onClick={() => saveEdit(comment.id)}
|
|
||||||
disabled={savingEdit || !editBody.trim()}
|
|
||||||
>
|
|
||||||
<FormattedMessage id="save" defaultMessage="Save" />
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
) : (
|
|
||||||
renderCommentBody(comment.body)
|
|
||||||
)}
|
|
||||||
<DocumentList ticketCommentId={comment.id} refreshKey={refreshKey} />
|
<DocumentList ticketCommentId={comment.id} refreshKey={refreshKey} />
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
@ -328,20 +237,6 @@ function CommentThread({
|
||||||
<Divider />
|
<Divider />
|
||||||
|
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: 1 }}>
|
|
||||||
<CommentFormatToolbar
|
|
||||||
textareaRef={commentInputRef}
|
|
||||||
value={body}
|
|
||||||
onChange={setBody}
|
|
||||||
disabled={submitting || uploading}
|
|
||||||
/>
|
|
||||||
<Typography variant="caption" color="text.disabled">
|
|
||||||
<FormattedMessage
|
|
||||||
id="commentMarkdownHint"
|
|
||||||
defaultMessage="Markdown: **bold**, #, ##, ###"
|
|
||||||
/>
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
<Box sx={{ display: 'flex', gap: 1 }}>
|
<Box sx={{ display: 'flex', gap: 1 }}>
|
||||||
<TextField
|
<TextField
|
||||||
size="small"
|
size="small"
|
||||||
|
|
|
||||||
|
|
@ -1,85 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { Box, Typography } from '@mui/material';
|
|
||||||
|
|
||||||
export type FormatKind = 'bold' | 'h1' | 'h2' | 'h3';
|
|
||||||
|
|
||||||
function renderInline(text: string): React.ReactNode[] {
|
|
||||||
const parts = text.split(/\*\*(.+?)\*\*/g);
|
|
||||||
return parts.map((p, i) =>
|
|
||||||
i % 2 === 1 ? <strong key={i}>{p}</strong> : <React.Fragment key={i}>{p}</React.Fragment>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function renderCommentBody(body: string): JSX.Element {
|
|
||||||
const lines = body.split('\n');
|
|
||||||
return (
|
|
||||||
<Box sx={{ '& > *': { mb: 0.5 } }}>
|
|
||||||
{lines.map((line, idx) => {
|
|
||||||
if (line.startsWith('### ')) {
|
|
||||||
return (
|
|
||||||
<Typography key={idx} variant="subtitle1" sx={{ fontWeight: 600, mt: 1 }}>
|
|
||||||
{renderInline(line.slice(4))}
|
|
||||||
</Typography>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (line.startsWith('## ')) {
|
|
||||||
return (
|
|
||||||
<Typography key={idx} variant="h6" sx={{ mt: 1.5 }}>
|
|
||||||
{renderInline(line.slice(3))}
|
|
||||||
</Typography>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (line.startsWith('# ')) {
|
|
||||||
return (
|
|
||||||
<Typography key={idx} variant="h5" sx={{ mt: 2 }}>
|
|
||||||
{renderInline(line.slice(2))}
|
|
||||||
</Typography>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<Typography key={idx} variant="body2" sx={{ whiteSpace: 'pre-wrap' }}>
|
|
||||||
{line ? renderInline(line) : '\u00A0'}
|
|
||||||
</Typography>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function applyFormat(
|
|
||||||
el: HTMLTextAreaElement | HTMLInputElement | null,
|
|
||||||
value: string,
|
|
||||||
kind: FormatKind,
|
|
||||||
onChange: (next: string) => void
|
|
||||||
): void {
|
|
||||||
const start = el?.selectionStart ?? value.length;
|
|
||||||
const end = el?.selectionEnd ?? value.length;
|
|
||||||
|
|
||||||
if (kind === 'bold') {
|
|
||||||
const selected = value.slice(start, end);
|
|
||||||
const wrapped = `**${selected}**`;
|
|
||||||
const next = value.slice(0, start) + wrapped + value.slice(end);
|
|
||||||
onChange(next);
|
|
||||||
const caret = selected.length > 0 ? start + wrapped.length : start + 2;
|
|
||||||
requestAnimationFrame(() => {
|
|
||||||
el?.focus();
|
|
||||||
el?.setSelectionRange(caret, caret);
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const prefix = kind === 'h1' ? '# ' : kind === 'h2' ? '## ' : '### ';
|
|
||||||
const lineStart = value.lastIndexOf('\n', start - 1) + 1;
|
|
||||||
const nlAfter = value.indexOf('\n', start);
|
|
||||||
const lineEnd = nlAfter === -1 ? value.length : nlAfter;
|
|
||||||
const line = value.slice(lineStart, lineEnd);
|
|
||||||
const stripped = line.replace(/^#{1,3}\s/, '');
|
|
||||||
const newLine = prefix + stripped;
|
|
||||||
const next = value.slice(0, lineStart) + newLine + value.slice(lineEnd);
|
|
||||||
onChange(next);
|
|
||||||
const caret = lineStart + newLine.length;
|
|
||||||
requestAnimationFrame(() => {
|
|
||||||
el?.focus();
|
|
||||||
el?.setSelectionRange(caret, caret);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
@ -214,7 +214,6 @@ export type TicketComment = {
|
||||||
authorId: number | null;
|
authorId: number | null;
|
||||||
body: string;
|
body: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
editedAt?: string | null;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TicketAiDiagnosis = {
|
export type TicketAiDiagnosis = {
|
||||||
|
|
|
||||||
|
|
@ -569,12 +569,6 @@
|
||||||
"noDiagnosis": "Keine KI-Diagnose verfügbar.",
|
"noDiagnosis": "Keine KI-Diagnose verfügbar.",
|
||||||
"comments": "Kommentare",
|
"comments": "Kommentare",
|
||||||
"noComments": "Noch keine Kommentare.",
|
"noComments": "Noch keine Kommentare.",
|
||||||
"commentEdited": "(bearbeitet {time})",
|
|
||||||
"commentMarkdownHint": "Markdown: **fett**, #, ##, ###",
|
|
||||||
"commentFormatBold": "Fett",
|
|
||||||
"commentFormatH1": "Überschrift 1",
|
|
||||||
"commentFormatH2": "Überschrift 2",
|
|
||||||
"commentFormatH3": "Überschrift 3",
|
|
||||||
"addComment": "Hinzufügen",
|
"addComment": "Hinzufügen",
|
||||||
"timeline": "Zeitverlauf",
|
"timeline": "Zeitverlauf",
|
||||||
"noTimelineEvents": "Noch keine Ereignisse.",
|
"noTimelineEvents": "Noch keine Ereignisse.",
|
||||||
|
|
|
||||||
|
|
@ -317,12 +317,6 @@
|
||||||
"noDiagnosis": "No AI diagnosis available.",
|
"noDiagnosis": "No AI diagnosis available.",
|
||||||
"comments": "Comments",
|
"comments": "Comments",
|
||||||
"noComments": "No comments yet.",
|
"noComments": "No comments yet.",
|
||||||
"commentEdited": "(edited {time})",
|
|
||||||
"commentMarkdownHint": "Markdown: **bold**, #, ##, ###",
|
|
||||||
"commentFormatBold": "Bold",
|
|
||||||
"commentFormatH1": "Heading 1",
|
|
||||||
"commentFormatH2": "Heading 2",
|
|
||||||
"commentFormatH3": "Heading 3",
|
|
||||||
"addComment": "Add",
|
"addComment": "Add",
|
||||||
"timeline": "Timeline",
|
"timeline": "Timeline",
|
||||||
"noTimelineEvents": "No events yet.",
|
"noTimelineEvents": "No events yet.",
|
||||||
|
|
|
||||||
|
|
@ -569,12 +569,6 @@
|
||||||
"noDiagnosis": "Aucun diagnostic IA disponible.",
|
"noDiagnosis": "Aucun diagnostic IA disponible.",
|
||||||
"comments": "Commentaires",
|
"comments": "Commentaires",
|
||||||
"noComments": "Aucun commentaire pour le moment.",
|
"noComments": "Aucun commentaire pour le moment.",
|
||||||
"commentEdited": "(modifié {time})",
|
|
||||||
"commentMarkdownHint": "Markdown : **gras**, #, ##, ###",
|
|
||||||
"commentFormatBold": "Gras",
|
|
||||||
"commentFormatH1": "Titre 1",
|
|
||||||
"commentFormatH2": "Titre 2",
|
|
||||||
"commentFormatH3": "Titre 3",
|
|
||||||
"addComment": "Ajouter",
|
"addComment": "Ajouter",
|
||||||
"timeline": "Chronologie",
|
"timeline": "Chronologie",
|
||||||
"noTimelineEvents": "Aucun événement pour le moment.",
|
"noTimelineEvents": "Aucun événement pour le moment.",
|
||||||
|
|
|
||||||
|
|
@ -569,12 +569,6 @@
|
||||||
"noDiagnosis": "Nessuna diagnosi IA disponibile.",
|
"noDiagnosis": "Nessuna diagnosi IA disponibile.",
|
||||||
"comments": "Commenti",
|
"comments": "Commenti",
|
||||||
"noComments": "Nessun commento ancora.",
|
"noComments": "Nessun commento ancora.",
|
||||||
"commentEdited": "(modificato {time})",
|
|
||||||
"commentMarkdownHint": "Markdown: **grassetto**, #, ##, ###",
|
|
||||||
"commentFormatBold": "Grassetto",
|
|
||||||
"commentFormatH1": "Titolo 1",
|
|
||||||
"commentFormatH2": "Titolo 2",
|
|
||||||
"commentFormatH3": "Titolo 3",
|
|
||||||
"addComment": "Aggiungi",
|
"addComment": "Aggiungi",
|
||||||
"timeline": "Cronologia",
|
"timeline": "Cronologia",
|
||||||
"noTimelineEvents": "Nessun evento ancora.",
|
"noTimelineEvents": "Nessun evento ancora.",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue