Merge pull request '[gitea] cherry-pick' (#2375) from earl-warren/forgejo:wip-gitea-cherry-pick into forgejo

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/2375
Reviewed-by: Otto <otto@codeberg.org>
This commit is contained in:
Earl Warren 2024-02-19 14:42:41 +00:00
commit 6c9c0aca76
67 changed files with 3542 additions and 880 deletions

View file

@ -12,6 +12,7 @@ plugins:
- "@eslint-community/eslint-plugin-eslint-comments" - "@eslint-community/eslint-plugin-eslint-comments"
- "@stylistic/eslint-plugin-js" - "@stylistic/eslint-plugin-js"
- eslint-plugin-array-func - eslint-plugin-array-func
- eslint-plugin-github
- eslint-plugin-i - eslint-plugin-i
- eslint-plugin-jquery - eslint-plugin-jquery
- eslint-plugin-no-jquery - eslint-plugin-no-jquery
@ -209,6 +210,29 @@ rules:
func-names: [0] func-names: [0]
func-style: [0] func-style: [0]
getter-return: [2] getter-return: [2]
github/a11y-aria-label-is-well-formatted: [0]
github/a11y-no-title-attribute: [0]
github/a11y-no-visually-hidden-interactive-element: [0]
github/a11y-role-supports-aria-props: [0]
github/a11y-svg-has-accessible-name: [0]
github/array-foreach: [0]
github/async-currenttarget: [2]
github/async-preventdefault: [2]
github/authenticity-token: [0]
github/get-attribute: [0]
github/js-class-name: [0]
github/no-blur: [0]
github/no-d-none: [0]
github/no-dataset: [2]
github/no-dynamic-script-tag: [2]
github/no-implicit-buggy-globals: [2]
github/no-inner-html: [0]
github/no-innerText: [2]
github/no-then: [2]
github/no-useless-passive: [2]
github/prefer-observers: [2]
github/require-passive-events: [2]
github/unescaped-html-literal: [0]
grouped-accessor-pairs: [2] grouped-accessor-pairs: [2]
guard-for-in: [0] guard-for-in: [0]
id-blacklist: [0] id-blacklist: [0]

View file

@ -1023,3 +1023,8 @@ docker:
# This endif closes the if at the top of the file # This endif closes the if at the top of the file
endif endif
# Disable parallel execution because it would break some targets that don't
# specify exact dependencies like 'backend' which does currently not depend
# on 'frontend' to enable Node.js-less builds from source tarballs.
.NOTPARALLEL:

View file

@ -79,4 +79,8 @@ async function main() {
]); ]);
} }
main().then(exit).catch(exit); try {
exit(await main());
} catch (err) {
exit(err);
}

View file

@ -63,4 +63,8 @@ async function main() {
]); ]);
} }
main().then(exit).catch(exit); try {
exit(await main());
} catch (err) {
exit(err);
}

View file

@ -161,7 +161,11 @@ func FetchIssueContentHistoryList(dbCtx context.Context, issueID, commentID int6
} }
for _, item := range res { for _, item := range res {
if item.UserID > 0 {
item.UserAvatarLink = avatars.GenerateUserAvatarFastLink(item.UserName, 0) item.UserAvatarLink = avatars.GenerateUserAvatarFastLink(item.UserName, 0)
} else {
item.UserAvatarLink = avatars.DefaultAvatarLink()
}
} }
return res, nil return res, nil
} }

File diff suppressed because it is too large Load diff

View file

@ -382,6 +382,7 @@ email_not_associate=Η διεύθυνση ηλεκτρονικού ταχυδρ
send_reset_mail=Αποστολή Email Ανάκτησης Λογαριασμού send_reset_mail=Αποστολή Email Ανάκτησης Λογαριασμού
reset_password=Ανάκτηση Λογαριασμού reset_password=Ανάκτηση Λογαριασμού
invalid_code=Ο κωδικός επιβεβαίωσης δεν είναι έγκυρος ή έχει λήξει. invalid_code=Ο κωδικός επιβεβαίωσης δεν είναι έγκυρος ή έχει λήξει.
invalid_code_forgot_password=Ο κωδικός επιβεβαίωσης δεν είναι έγκυρος ή έληξε. Πατήστε <a href="%s">εδώ</a> για να ξεκινήσετε νέα συνεδρία.
invalid_password=Ο κωδικός πρόσβασης σας δεν ταιριάζει με τον κωδικό που χρησιμοποιήθηκε για τη δημιουργία του λογαριασμού. invalid_password=Ο κωδικός πρόσβασης σας δεν ταιριάζει με τον κωδικό που χρησιμοποιήθηκε για τη δημιουργία του λογαριασμού.
reset_password_helper=Ανάκτηση Λογαριασμού reset_password_helper=Ανάκτηση Λογαριασμού
reset_password_wrong_user=Έχετε συνδεθεί ως %s, αλλά ο σύνδεσμος ανάκτησης λογαριασμού προορίζεται για το %s reset_password_wrong_user=Έχετε συνδεθεί ως %s, αλλά ο σύνδεσμος ανάκτησης λογαριασμού προορίζεται για το %s
@ -865,10 +866,12 @@ revoke_oauth2_grant_description=Η ανάκληση πρόσβασης για α
revoke_oauth2_grant_success=Η πρόσβαση ανακλήθηκε επιτυχώς. revoke_oauth2_grant_success=Η πρόσβαση ανακλήθηκε επιτυχώς.
twofa_desc=Ο έλεγχος ταυτότητας δύο παραγόντων ενισχύει την ασφάλεια του λογαριασμού σας. twofa_desc=Ο έλεγχος ταυτότητας δύο παραγόντων ενισχύει την ασφάλεια του λογαριασμού σας.
twofa_recovery_tip=Αν χάσετε τη συσκευή σας, θα είστε σε θέση να χρησιμοποιήσετε ένα κλειδί ανάκτησης μιας χρήσης για να ανακτήσετε την πρόσβαση στο λογαριασμό σας.
twofa_is_enrolled=Ο λογαριασμός σας είναι <strong>εγγεγραμμένος</strong> σε έλεγχο ταυτότητας δύο παραγόντων. twofa_is_enrolled=Ο λογαριασμός σας είναι <strong>εγγεγραμμένος</strong> σε έλεγχο ταυτότητας δύο παραγόντων.
twofa_not_enrolled=Ο λογαριασμός σας δεν είναι εγγεγραμμένος σε έλεγχο ταυτότητας δύο παραγόντων. twofa_not_enrolled=Ο λογαριασμός σας δεν είναι εγγεγραμμένος σε έλεγχο ταυτότητας δύο παραγόντων.
twofa_disable=Απενεργοποίηση Ταυτοποίησης Δύο Παραμέτρων twofa_disable=Απενεργοποίηση Ταυτοποίησης Δύο Παραμέτρων
twofa_scratch_token_regenerate=Αναδημιουργία Διακριτικού Μίας Χρήσης twofa_scratch_token_regenerate=Αναδημιουργία Διακριτικού Μίας Χρήσης
twofa_scratch_token_regenerated=Το κλειδί ανάκτησης μιας χρήσης είναι τώρα %s. Αποθηκεύστε το σε ασφαλές μέρος, καθώς δε θα εμφανιστεί ξανά.
twofa_enroll=Εγγραφή στην ταυτοποίηση δύο παραγόντων twofa_enroll=Εγγραφή στην ταυτοποίηση δύο παραγόντων
twofa_disable_note=Μπορείτε να απενεργοποιήσετε την ταυτοποίηση δύο παραγόντων αν χρειαστεί. twofa_disable_note=Μπορείτε να απενεργοποιήσετε την ταυτοποίηση δύο παραγόντων αν χρειαστεί.
twofa_disable_desc=Η απενεργοποίηση της ταυτοποίησης δύο παραγόντων θα καταστήσει τον λογαριασμό σας λιγότερο ασφαλή. Συνέχεια; twofa_disable_desc=Η απενεργοποίηση της ταυτοποίησης δύο παραγόντων θα καταστήσει τον λογαριασμό σας λιγότερο ασφαλή. Συνέχεια;
@ -886,6 +889,8 @@ webauthn_register_key=Προσθήκη Κλειδιού Ασφαλείας
webauthn_nickname=Ψευδώνυμο webauthn_nickname=Ψευδώνυμο
webauthn_delete_key=Αφαίρεση Κλειδιού Ασφαλείας webauthn_delete_key=Αφαίρεση Κλειδιού Ασφαλείας
webauthn_delete_key_desc=Αν αφαιρέσετε ένα κλειδί ασφαλείας δεν μπορείτε πλέον να συνδεθείτε με αυτό. Συνέχεια; webauthn_delete_key_desc=Αν αφαιρέσετε ένα κλειδί ασφαλείας δεν μπορείτε πλέον να συνδεθείτε με αυτό. Συνέχεια;
webauthn_key_loss_warning=Αν χάσετε τα κλειδιά ασφαλείας σας, θα χάσετε την πρόσβαση στο λογαριασμό σας.
webauthn_alternative_tip=Μπορεί να θέλετε να ρυθμίσετε μια πρόσθετη μέθοδο ταυτοποίησης.
manage_account_links=Διαχείριση Συνδεδεμένων Λογαριασμών manage_account_links=Διαχείριση Συνδεδεμένων Λογαριασμών
manage_account_links_desc=Αυτοί οι εξωτερικοί λογαριασμοί είναι συνδεδεμένοι στον Forgejo λογαριασμό σας. manage_account_links_desc=Αυτοί οι εξωτερικοί λογαριασμοί είναι συνδεδεμένοι στον Forgejo λογαριασμό σας.
@ -895,6 +900,7 @@ remove_account_link=Αφαίρεση Συνδεδεμένου Λογαριασμ
remove_account_link_desc=Η κατάργηση ενός συνδεδεμένου λογαριασμού θα ανακαλέσει την πρόσβασή του στο λογαριασμό σας στο Forgejo. Συνέχεια; remove_account_link_desc=Η κατάργηση ενός συνδεδεμένου λογαριασμού θα ανακαλέσει την πρόσβασή του στο λογαριασμό σας στο Forgejo. Συνέχεια;
remove_account_link_success=Ο συνδεδεμένος λογαριασμός έχει αφαιρεθεί. remove_account_link_success=Ο συνδεδεμένος λογαριασμός έχει αφαιρεθεί.
hooks.desc=Προσθήκη webhooks που θα ενεργοποιούνται για <strong>όλα τα αποθετήρια</strong> που σας ανήκουν.
orgs_none=Δεν είστε μέλος σε κάποιο οργανισμό. orgs_none=Δεν είστε μέλος σε κάποιο οργανισμό.
repos_none=Δεν κατέχετε κάποιο αποθετήριο. repos_none=Δεν κατέχετε κάποιο αποθετήριο.
@ -916,9 +922,12 @@ visibility=Ορατότητα χρήστη
visibility.public=Δημόσια visibility.public=Δημόσια
visibility.public_tooltip=Ορατό σε όλους visibility.public_tooltip=Ορατό σε όλους
visibility.limited=Περιορισμένη visibility.limited=Περιορισμένη
visibility.limited_tooltip=Ορατό μόνο στους ταυτοποιημένους χρήστες
visibility.private=Ιδιωτική visibility.private=Ιδιωτική
visibility.private_tooltip=Ορατό μόνο στα μέλη των οργανισμών που συμμετέχετε
[repo] [repo]
new_repo_helper=Ένα αποθετήριο περιέχει όλα τα αρχεία έργου, συμπεριλαμβανομένου του ιστορικού εκδόσεων. Ήδη φιλοξενείται αλλού; <a href="%s">Μετεγκατάσταση αποθετηρίου.</a>
owner=Ιδιοκτήτης owner=Ιδιοκτήτης
owner_helper=Ορισμένοι οργανισμοί ενδέχεται να μην εμφανίζονται στο αναπτυσσόμενο μενού λόγω του μέγιστου αριθμού αποθετηρίων. owner_helper=Ορισμένοι οργανισμοί ενδέχεται να μην εμφανίζονται στο αναπτυσσόμενο μενού λόγω του μέγιστου αριθμού αποθετηρίων.
repo_name=Όνομα αποθετηρίου repo_name=Όνομα αποθετηρίου
@ -941,6 +950,7 @@ fork_to_different_account=Fork σε διαφορετικό λογαριασμό
fork_visibility_helper=Η ορατότητα ενός fork αποθετηρίου δεν μπορεί να αλλάξει. fork_visibility_helper=Η ορατότητα ενός fork αποθετηρίου δεν μπορεί να αλλάξει.
fork_branch=Κλάδος που θα κλωνοποιηθεί στο fork fork_branch=Κλάδος που θα κλωνοποιηθεί στο fork
all_branches=Όλοι οι κλάδοι all_branches=Όλοι οι κλάδοι
fork_no_valid_owners=Αυτό το αποθετήριο δεν μπορεί να γίνει fork επειδή δεν υπάρχουν έγκυροι ιδιοκτήτες.
use_template=Χρήση αυτού του πρότυπου use_template=Χρήση αυτού του πρότυπου
clone_in_vsc=Κλωνοποίηση στο VS Code clone_in_vsc=Κλωνοποίηση στο VS Code
download_zip=Λήψη ZIP download_zip=Λήψη ZIP
@ -1005,13 +1015,20 @@ delete_preexisting=Διαγραφή αρχείων που προϋπήρχαν
delete_preexisting_content=Διαγραφή αρχείων στο %s delete_preexisting_content=Διαγραφή αρχείων στο %s
delete_preexisting_success=Διαγράφηκαν τα μη υιοθετημένα αρχεία στο %s delete_preexisting_success=Διαγράφηκαν τα μη υιοθετημένα αρχεία στο %s
blame_prior=Προβολή ευθύνης πριν από αυτή την αλλαγή blame_prior=Προβολή ευθύνης πριν από αυτή την αλλαγή
blame.ignore_revs=Αγνόηση των αναθεωρήσεων στο <a href="%s">.git-blame-ignore-revs</a>. Πατήστε <a href="%s">εδώ</a> για να το παρακάμψετε και να δείτε την κανονική προβολή ευθυνών.
blame.ignore_revs.failed=Αποτυχία αγνόησης των αναθεωρήσεων στο <a href="%s">.git-blame-ignore-revs</a>.
author_search_tooltip=Εμφάνιση το πολύ 30 χρηστών author_search_tooltip=Εμφάνιση το πολύ 30 χρηστών
tree_path_not_found_commit=Η διαδρομή %[1]s δεν υπάρχει στην υποβολή %[2]s
tree_path_not_found_branch=Η διαδρομή %[1]s δεν υπάρχει στον κλάδο %[2]s
tree_path_not_found_tag=Η διαδρομή %[1]s δεν υπάρχει στην ετικέτα %[2]s
transfer.accept=Αποδοχή Μεταφοράς transfer.accept=Αποδοχή Μεταφοράς
transfer.accept_desc=`Μεταφορά στο "%s"` transfer.accept_desc=`Μεταφορά στο "%s"`
transfer.reject=Απόρριψη Μεταφοράς transfer.reject=Απόρριψη Μεταφοράς
transfer.reject_desc=`Ακύρωση μεταφοράς σε "%s"` transfer.reject_desc=`Ακύρωση μεταφοράς σε "%s"`
transfer.no_permission_to_accept=Δεν έχετε άδεια να αποδεχτείτε αυτή τη μεταφορά.
transfer.no_permission_to_reject=Δεν έχετε άδεια να απορρίψετε αυτή τη μεταφορά.
desc.private=Ιδιωτικό desc.private=Ιδιωτικό
desc.public=Δημόσιο desc.public=Δημόσιο
@ -1030,6 +1047,8 @@ template.issue_labels=Σήματα Ζητήματος
template.one_item=Πρέπει να επιλέξετε τουλάχιστον ένα αντικείμενο στο πρότυπο template.one_item=Πρέπει να επιλέξετε τουλάχιστον ένα αντικείμενο στο πρότυπο
template.invalid=Πρέπει να επιλέξετε ένα πρότυπο αποθετήριο template.invalid=Πρέπει να επιλέξετε ένα πρότυπο αποθετήριο
archive.title=Αυτό το αποθετήρειο αρχειοθετήθηκε. Μπορείτε να προβάλετε αρχεία και να τα κλωνοποιήσετε, αλλά δεν μπορείτε να ωθήσετε ή να ανοίξετε ζητήματα ή pull requests.
archive.title_date=Αυτό το αποθετήριο έχει αρχειοθετηθεί στο %s. Μπορείτε να προβάλετε αρχεία και να κλωνοποιήσετε, αλλά δεν μπορείτε να ωθήσετε ή να ανοίξετε ζητήματα ή pull requests.
archive.issue.nocomment=Αυτό το αποθετήριο αρχειοθετήθηκε. Δεν μπορείτε να σχολιάσετε σε ζητήματα. archive.issue.nocomment=Αυτό το αποθετήριο αρχειοθετήθηκε. Δεν μπορείτε να σχολιάσετε σε ζητήματα.
archive.pull.nocomment=Αυτό το repo αρχειοθετήθηκε. Δεν μπορείτε να σχολιάσετε στα pull requests. archive.pull.nocomment=Αυτό το repo αρχειοθετήθηκε. Δεν μπορείτε να σχολιάσετε στα pull requests.
@ -1046,6 +1065,7 @@ migrate_options_lfs=Μεταφορά αρχείων LFS
migrate_options_lfs_endpoint.label=Άκρο LFS migrate_options_lfs_endpoint.label=Άκρο LFS
migrate_options_lfs_endpoint.description=Η μεταφορά θα προσπαθήσει να χρησιμοποιήσει το Git remote για να <a target="_blank" rel="noopener noreferrer" href="%s">καθορίσει τον διακομιστή LFS</a>. Μπορείτε επίσης να καθορίσετε ένα δικό σας endpoint αν τα δεδομένα LFS του αποθετηρίου αποθηκεύονται κάπου αλλού. migrate_options_lfs_endpoint.description=Η μεταφορά θα προσπαθήσει να χρησιμοποιήσει το Git remote για να <a target="_blank" rel="noopener noreferrer" href="%s">καθορίσει τον διακομιστή LFS</a>. Μπορείτε επίσης να καθορίσετε ένα δικό σας endpoint αν τα δεδομένα LFS του αποθετηρίου αποθηκεύονται κάπου αλλού.
migrate_options_lfs_endpoint.description.local=Μια διαδρομή στο τοπικό διακομιστή επίσης υποστηρίζεται. migrate_options_lfs_endpoint.description.local=Μια διαδρομή στο τοπικό διακομιστή επίσης υποστηρίζεται.
migrate_options_lfs_endpoint.placeholder=Αν αφεθεί κενό, το άκρο θα προκύψει από το URL του κλώνου
migrate_items=Στοιχεία Μεταφοράς migrate_items=Στοιχεία Μεταφοράς
migrate_items_wiki=Wiki migrate_items_wiki=Wiki
migrate_items_milestones=Ορόσημα migrate_items_milestones=Ορόσημα
@ -1148,6 +1168,7 @@ file_view_rendered=Προβολή Απόδοσης
file_view_raw=Προβολή Ακατέργαστου file_view_raw=Προβολή Ακατέργαστου
file_permalink=Permalink file_permalink=Permalink
file_too_large=Το αρχείο είναι πολύ μεγάλο για να εμφανιστεί. file_too_large=Το αρχείο είναι πολύ μεγάλο για να εμφανιστεί.
invisible_runes_header=`Αυτό το αρχείο περιέχει αόρατους χαρακτήρες Unicode `
invisible_runes_description=`Αυτό το αρχείο περιέχει αόρατους χαρακτήρες Unicode που δεν διακρίνονται από ανθρώπους, αλλά μπορεί να επεξεργάζονται διαφορετικά από έναν υπολογιστή. Αν νομίζετε ότι αυτό είναι σκόπιμο, μπορείτε να αγνοήσετε με ασφάλεια αυτή την προειδοποίηση. Χρησιμοποιήστε το κουμπί Escape για να τους αποκαλύψετε.` invisible_runes_description=`Αυτό το αρχείο περιέχει αόρατους χαρακτήρες Unicode που δεν διακρίνονται από ανθρώπους, αλλά μπορεί να επεξεργάζονται διαφορετικά από έναν υπολογιστή. Αν νομίζετε ότι αυτό είναι σκόπιμο, μπορείτε να αγνοήσετε με ασφάλεια αυτή την προειδοποίηση. Χρησιμοποιήστε το κουμπί Escape για να τους αποκαλύψετε.`
ambiguous_runes_header=`Αυτό το αρχείο περιέχει ασαφείς χαρακτήρες Unicode ` ambiguous_runes_header=`Αυτό το αρχείο περιέχει ασαφείς χαρακτήρες Unicode `
ambiguous_runes_description=`Αυτό το αρχείο περιέχει χαρακτήρες Unicode που μπορεί να συγχέονται με άλλους χαρακτήρες. Αν νομίζετε ότι αυτό είναι σκόπιμο, μπορείτε να αγνοήσετε με ασφάλεια αυτή την προειδοποίηση. Χρησιμοποιήστε το κουμπί Escape για να τους αποκαλύψετε.` ambiguous_runes_description=`Αυτό το αρχείο περιέχει χαρακτήρες Unicode που μπορεί να συγχέονται με άλλους χαρακτήρες. Αν νομίζετε ότι αυτό είναι σκόπιμο, μπορείτε να αγνοήσετε με ασφάλεια αυτή την προειδοποίηση. Χρησιμοποιήστε το κουμπί Escape για να τους αποκαλύψετε.`
@ -1426,6 +1447,7 @@ issues.filter_sort.moststars=Περισσότερα αστέρια
issues.filter_sort.feweststars=Λιγότερα αστέρια issues.filter_sort.feweststars=Λιγότερα αστέρια
issues.filter_sort.mostforks=Περισσότερα forks issues.filter_sort.mostforks=Περισσότερα forks
issues.filter_sort.fewestforks=Λιγότερα forks issues.filter_sort.fewestforks=Λιγότερα forks
issues.keyword_search_unavailable=Η αναζήτηση μέσω λέξεων κλειδιών δεν είναι διαθέσιμη. Παρακαλώ επικοινωνήστε με το διαχειριστή.
issues.action_open=Άνοιγμα issues.action_open=Άνοιγμα
issues.action_close=Κλείσιμο issues.action_close=Κλείσιμο
issues.action_label=Σήμα issues.action_label=Σήμα
@ -1716,8 +1738,12 @@ pulls.is_empty=Οι αλλαγές σε αυτόν τον κλάδο είναι
pulls.required_status_check_failed=Ορισμένοι απαιτούμενοι έλεγχοι δεν ήταν επιτυχείς. pulls.required_status_check_failed=Ορισμένοι απαιτούμενοι έλεγχοι δεν ήταν επιτυχείς.
pulls.required_status_check_missing=Λείπουν ορισμένοι απαιτούμενοι έλεγχοι. pulls.required_status_check_missing=Λείπουν ορισμένοι απαιτούμενοι έλεγχοι.
pulls.required_status_check_administrator=Ως διαχειριστής, μπορείτε ακόμα να συγχωνεύσετε αυτό το pull request. pulls.required_status_check_administrator=Ως διαχειριστής, μπορείτε ακόμα να συγχωνεύσετε αυτό το pull request.
pulls.blocked_by_approvals=Το pull request δεν έχει ακόμα αρκετές εγκρίσεις. Δόθηκαν %d από %d εγκρίσεις.
pulls.blocked_by_rejection=Αυτό το Pull Request έχει αλλαγές που ζητούνται από έναν επίσημο εξεταστή. pulls.blocked_by_rejection=Αυτό το Pull Request έχει αλλαγές που ζητούνται από έναν επίσημο εξεταστή.
pulls.blocked_by_official_review_requests=Αυτό το Pull Request έχει επίσημες αιτήσεις αξιολόγησης. pulls.blocked_by_official_review_requests=Αυτό το Pull Request έχει επίσημες αιτήσεις αξιολόγησης.
pulls.blocked_by_outdated_branch=Αυτό το pull request έχει αποκλειστεί επειδή είναι παρωχημένο.
pulls.blocked_by_changed_protected_files_1=Αυτό το pull request έχει αποκλειστεί επειδή αλλάζει ένα προστατευμένο αρχείο:
pulls.blocked_by_changed_protected_files_n=Αυτό το pull request έχει αποκλειστεί επειδή αλλάζει προστατευμένα αρχεία:
pulls.can_auto_merge_desc=Αυτό το Pull Request μπορεί να συγχωνευθεί αυτόματα. pulls.can_auto_merge_desc=Αυτό το Pull Request μπορεί να συγχωνευθεί αυτόματα.
pulls.cannot_auto_merge_desc=Αυτό το pull request δεν μπορεί να συγχωνευθεί αυτόματα λόγω συγκρούσεων. pulls.cannot_auto_merge_desc=Αυτό το pull request δεν μπορεί να συγχωνευθεί αυτόματα λόγω συγκρούσεων.
pulls.cannot_auto_merge_helper=Χειροκίνητη Συγχώνευση για την επίλυση των συγκρούσεων. pulls.cannot_auto_merge_helper=Χειροκίνητη Συγχώνευση για την επίλυση των συγκρούσεων.
@ -1833,11 +1859,16 @@ milestones.filter_sort.least_issues=Λιγότερα ζητήματα
signing.will_sign=Αυτή η υποβολή θα υπογραφεί με το κλειδί "%s". signing.will_sign=Αυτή η υποβολή θα υπογραφεί με το κλειδί "%s".
signing.wont_sign.error=Παρουσιάστηκε σφάλμα κατά τον έλεγχο για το αν η υποβολή μπορεί να υπογραφεί. signing.wont_sign.error=Παρουσιάστηκε σφάλμα κατά τον έλεγχο για το αν η υποβολή μπορεί να υπογραφεί.
signing.wont_sign.nokey=Δεν υπάρχει διαθέσιμο κλειδί για να υπογραφεί αυτή η υποβολή.
signing.wont_sign.never=Οι υποβολές δεν υπογράφονται ποτέ. signing.wont_sign.never=Οι υποβολές δεν υπογράφονται ποτέ.
signing.wont_sign.always=Οι υποβολές υπογράφονται πάντα. signing.wont_sign.always=Οι υποβολές υπογράφονται πάντα.
signing.wont_sign.pubkey=Η υποβολή δε θα υπογραφεί επειδή δεν υπάρχει δημόσιο κλειδί που να συνδέεται με το λογαριασμό σας.
signing.wont_sign.twofa=Πρέπει να έχετε ενεργοποιημένη την ταυτοποίηση δύο παραγόντων για να υπογράφεται υποβολές.
signing.wont_sign.parentsigned=Η υποβολή δε θα υπογραφεί καθώς η γονική υποβολή δεν έχει υπογραφεί. signing.wont_sign.parentsigned=Η υποβολή δε θα υπογραφεί καθώς η γονική υποβολή δεν έχει υπογραφεί.
signing.wont_sign.basesigned=Η συγχώνευση δε θα υπογραφεί καθώς η βασική υποβολή δεν έχει υπογραφή της βάσης. signing.wont_sign.basesigned=Η συγχώνευση δε θα υπογραφεί καθώς η βασική υποβολή δεν έχει υπογραφή της βάσης.
signing.wont_sign.headsigned=Η συγχώνευση δε θα υπογραφεί καθώς δεν έχει υπογραφή η υποβολή της κεφαλής. signing.wont_sign.headsigned=Η συγχώνευση δε θα υπογραφεί καθώς δεν έχει υπογραφή η υποβολή της κεφαλής.
signing.wont_sign.commitssigned=Η συγχώνευση δε θα υπογραφεί καθώς όλες οι σχετικές υποβολές δεν έχουν υπογραφεί.
signing.wont_sign.approved=Η συγχώνευση δε θα υπογραφεί καθώς το PR δεν έχει εγκριθεί.
signing.wont_sign.not_signed_in=Δεν είστε συνδεδεμένοι. signing.wont_sign.not_signed_in=Δεν είστε συνδεδεμένοι.
ext_wiki=Πρόσβαση στο Εξωτερικό Wiki ext_wiki=Πρόσβαση στο Εξωτερικό Wiki
@ -1968,7 +1999,9 @@ settings.mirror_settings.docs.disabled_push_mirror.info=Τα είδωλα ώθη
settings.mirror_settings.docs.no_new_mirrors=Το αποθετήριο σας αντιγράφει τις αλλαγές προς ή από ένα άλλο αποθετήριο. Λάβετε υπόψη ότι δεν μπορείτε να δημιουργήσετε νέα είδωλα αυτή τη στιγμή. settings.mirror_settings.docs.no_new_mirrors=Το αποθετήριο σας αντιγράφει τις αλλαγές προς ή από ένα άλλο αποθετήριο. Λάβετε υπόψη ότι δεν μπορείτε να δημιουργήσετε νέα είδωλα αυτή τη στιγμή.
settings.mirror_settings.docs.can_still_use=Αν και δεν μπορείτε να τροποποιήσετε τα υπάρχοντα είδωλα ή να δημιουργήσετε νέα, μπορείτε να χρησιμοποιείται ακόμα το υπάρχων είδωλο. settings.mirror_settings.docs.can_still_use=Αν και δεν μπορείτε να τροποποιήσετε τα υπάρχοντα είδωλα ή να δημιουργήσετε νέα, μπορείτε να χρησιμοποιείται ακόμα το υπάρχων είδωλο.
settings.mirror_settings.docs.pull_mirror_instructions=Για να ορίσετε έναν είδωλο έλξης, παρακαλούμε συμβουλευθείτε: settings.mirror_settings.docs.pull_mirror_instructions=Για να ορίσετε έναν είδωλο έλξης, παρακαλούμε συμβουλευθείτε:
settings.mirror_settings.docs.more_information_if_disabled=Μπορείτε να μάθετε περισσότερα για τα είδωλα ώθησης και έλξης εδώ:
settings.mirror_settings.docs.doc_link_title=Πώς μπορώ να αντιγράψω αποθετήρια; settings.mirror_settings.docs.doc_link_title=Πώς μπορώ να αντιγράψω αποθετήρια;
settings.mirror_settings.docs.doc_link_pull_section=το κεφάλαιο "Pulling from a remote repository" της τεκμηρίωσης.
settings.mirror_settings.docs.pulling_remote_title=Έλξη από ένα απομακρυσμένο αποθετήριο settings.mirror_settings.docs.pulling_remote_title=Έλξη από ένα απομακρυσμένο αποθετήριο
settings.mirror_settings.mirrored_repository=Είδωλο αποθετηρίου settings.mirror_settings.mirrored_repository=Είδωλο αποθετηρίου
settings.mirror_settings.direction=Κατεύθυνση settings.mirror_settings.direction=Κατεύθυνση
@ -1981,6 +2014,8 @@ settings.mirror_settings.push_mirror.add=Προσθήκη Είδωλου Push
settings.mirror_settings.push_mirror.edit_sync_time=Επεξεργασία διαστήματος συγχρονισμού ειδώλου settings.mirror_settings.push_mirror.edit_sync_time=Επεξεργασία διαστήματος συγχρονισμού ειδώλου
settings.sync_mirror=Συγχρονισμός Τώρα settings.sync_mirror=Συγχρονισμός Τώρα
settings.pull_mirror_sync_in_progress=Έλκονται αλλαγές από το απομακρυσμένο %s αυτή τη στιγμή.
settings.push_mirror_sync_in_progress=Ώθηση αλλαγών στο απομακρυσμένο %s αυτή τη στιγμή.
settings.site=Ιστοσελίδα settings.site=Ιστοσελίδα
settings.update_settings=Ενημέρωση Ρυθμίσεων settings.update_settings=Ενημέρωση Ρυθμίσεων
settings.update_mirror_settings=Ενημέρωση Ρυθμίσεων Ειδώλου settings.update_mirror_settings=Ενημέρωση Ρυθμίσεων Ειδώλου
@ -2047,6 +2082,7 @@ settings.transfer.rejected=Η μεταβίβαση του αποθετηρίου
settings.transfer.success=Η μεταβίβαση του αποθετηρίου ήταν επιτυχής. settings.transfer.success=Η μεταβίβαση του αποθετηρίου ήταν επιτυχής.
settings.transfer_abort=Ακύρωση μεταβίβασης settings.transfer_abort=Ακύρωση μεταβίβασης
settings.transfer_abort_invalid=Δεν μπορείτε να ακυρώσετε μια ανύπαρκτη μεταβίβαση αποθετηρίου. settings.transfer_abort_invalid=Δεν μπορείτε να ακυρώσετε μια ανύπαρκτη μεταβίβαση αποθετηρίου.
settings.transfer_abort_success=Η μεταφορά αποθετηρίου στο %s ακυρώθηκε με επιτυχία.
settings.transfer_desc=Μεταβιβάστε αυτό το αποθετήριο σε έναν χρήστη ή σε έναν οργανισμό για τον οποίο έχετε δικαιώματα διαχειριστή. settings.transfer_desc=Μεταβιβάστε αυτό το αποθετήριο σε έναν χρήστη ή σε έναν οργανισμό για τον οποίο έχετε δικαιώματα διαχειριστή.
settings.transfer_form_title=Εισάγετε το όνομα του αποθετηρίου ως επιβεβαίωση: settings.transfer_form_title=Εισάγετε το όνομα του αποθετηρίου ως επιβεβαίωση:
settings.transfer_in_progress=Αυτή τη στιγμή υπάρχει μια εν εξελίξει μεταβίβαση. Παρακαλούμε ακυρώστε την αν θέλετε να μεταβιβάσετε αυτό το αποθετήριο σε άλλο χρήστη. settings.transfer_in_progress=Αυτή τη στιγμή υπάρχει μια εν εξελίξει μεταβίβαση. Παρακαλούμε ακυρώστε την αν θέλετε να μεταβιβάσετε αυτό το αποθετήριο σε άλλο χρήστη.
@ -2337,6 +2373,7 @@ settings.unarchive.button=Απο-Αρχειοθέτηση αποθετηρίου
settings.unarchive.header=Απο-Αρχειοθέτηση του αποθετηρίου settings.unarchive.header=Απο-Αρχειοθέτηση του αποθετηρίου
settings.unarchive.text=Η απο-αρχειοθέτηση του αποθετηρίου θα αποκαταστήσει την ικανότητά του να λαμβάνει υποβολές και ωθήσεις, καθώς και νέα ζητήματα και pull-requests. settings.unarchive.text=Η απο-αρχειοθέτηση του αποθετηρίου θα αποκαταστήσει την ικανότητά του να λαμβάνει υποβολές και ωθήσεις, καθώς και νέα ζητήματα και pull-requests.
settings.unarchive.success=Το αποθετήριο απο-αρχειοθετήθηκε με επιτυχία. settings.unarchive.success=Το αποθετήριο απο-αρχειοθετήθηκε με επιτυχία.
settings.unarchive.error=Παρουσιάστηκε σφάλμα κατά την προσπάθεια απο-αρχειοθέτησης του αποθετηρίου. Δείτε τις καταγραφές για περισσότερες λεπτομέρειες.
settings.update_avatar_success=Η εικόνα του αποθετηρίου έχει ενημερωθεί. settings.update_avatar_success=Η εικόνα του αποθετηρίου έχει ενημερωθεί.
settings.lfs=LFS settings.lfs=LFS
settings.lfs_filelist=Αρχεία LFS σε αυτό το αποθετήριο settings.lfs_filelist=Αρχεία LFS σε αυτό το αποθετήριο
@ -2460,6 +2497,7 @@ release.edit_release=Ενημέρωση Κυκλοφορίας
release.delete_release=Διαγραφή Κυκλοφορίας release.delete_release=Διαγραφή Κυκλοφορίας
release.delete_tag=Διαγραφή Ετικέτας release.delete_tag=Διαγραφή Ετικέτας
release.deletion=Διαγραφή Κυκλοφορίας release.deletion=Διαγραφή Κυκλοφορίας
release.deletion_desc=Διαγράφοντας μια κυκλοφορία, αυτή αφαιρείται μόνο από το Gitea. Δε θα επηρεάσει την ετικέτα Git, τα περιεχόμενα του αποθετηρίου σας ή το ιστορικό της. Συνέχεια;
release.deletion_success=Η κυκλοφορία έχει διαγραφεί. release.deletion_success=Η κυκλοφορία έχει διαγραφεί.
release.deletion_tag_desc=Θα διαγράψει αυτή την ετικέτα από το αποθετήριο. Τα περιεχόμενα του αποθετηρίου και το ιστορικό παραμένουν αμετάβλητα. Συνέχεια; release.deletion_tag_desc=Θα διαγράψει αυτή την ετικέτα από το αποθετήριο. Τα περιεχόμενα του αποθετηρίου και το ιστορικό παραμένουν αμετάβλητα. Συνέχεια;
release.deletion_tag_success=Η ετικέτα έχει διαγραφεί. release.deletion_tag_success=Η ετικέτα έχει διαγραφεί.
@ -2479,6 +2517,7 @@ branch.already_exists=Ήδη υπάρχει ένας κλάδος με το όν
branch.delete_head=Διαγραφή branch.delete_head=Διαγραφή
branch.delete=`Διαγραφή του Κλάδου "%s"` branch.delete=`Διαγραφή του Κλάδου "%s"`
branch.delete_html=Διαγραφή Κλάδου branch.delete_html=Διαγραφή Κλάδου
branch.delete_desc=Η διαγραφή ενός κλάδου είναι μόνιμη. Αν και ο διαγραμμένος κλάδος μπορεί να συνεχίσει να υπάρχει για σύντομο χρονικό διάστημα πριν να αφαιρεθεί, ΔΕΝ ΜΠΟΡΕΙ να αναιρεθεί στις περισσότερες περιπτώσεις. Συνέχεια;
branch.deletion_success=Ο κλάδος "%s" διαγράφηκε. branch.deletion_success=Ο κλάδος "%s" διαγράφηκε.
branch.deletion_failed=Αποτυχία διαγραφής του κλάδου "%s". branch.deletion_failed=Αποτυχία διαγραφής του κλάδου "%s".
branch.delete_branch_has_new_commits=Ο κλάδος "%s" δεν μπορεί να διαγραφεί επειδή προστέθηκαν νέες υποβολές μετά τη συγχώνευση. branch.delete_branch_has_new_commits=Ο κλάδος "%s" δεν μπορεί να διαγραφεί επειδή προστέθηκαν νέες υποβολές μετά τη συγχώνευση.
@ -2713,6 +2752,7 @@ dashboard.reinit_missing_repos=Επανεκκινήστε όλα τα αποθε
dashboard.sync_external_users=Συγχρονισμός δεδομένων εξωτερικών χρηστών dashboard.sync_external_users=Συγχρονισμός δεδομένων εξωτερικών χρηστών
dashboard.cleanup_hook_task_table=Εκκαθάριση πίνακα hook_task dashboard.cleanup_hook_task_table=Εκκαθάριση πίνακα hook_task
dashboard.cleanup_packages=Εκκαθάριση ληγμένων πακέτων dashboard.cleanup_packages=Εκκαθάριση ληγμένων πακέτων
dashboard.cleanup_actions=Οι ενέργειες καθαρισμού καταγραφές και αντικείμενα
dashboard.server_uptime=Διάρκεια Διακομιστή dashboard.server_uptime=Διάρκεια Διακομιστή
dashboard.current_goroutine=Τρέχουσες Goroutines dashboard.current_goroutine=Τρέχουσες Goroutines
dashboard.current_memory_usage=Τρέχουσα Χρήση Μνήμης dashboard.current_memory_usage=Τρέχουσα Χρήση Μνήμης
@ -2823,6 +2863,7 @@ emails.updated=Το email ενημερώθηκε
emails.not_updated=Αποτυχία ενημέρωσης της ζητούμενης διεύθυνσης email: %v emails.not_updated=Αποτυχία ενημέρωσης της ζητούμενης διεύθυνσης email: %v
emails.duplicate_active=Αυτή η διεύθυνση email είναι ήδη ενεργή σε διαφορετικό χρήστη. emails.duplicate_active=Αυτή η διεύθυνση email είναι ήδη ενεργή σε διαφορετικό χρήστη.
emails.change_email_header=Ενημέρωση Ιδιοτήτων Email emails.change_email_header=Ενημέρωση Ιδιοτήτων Email
emails.change_email_text=Είστε βέβαιοι ότι θέλετε να ενημερώσετε αυτή τη διεύθυνση email;
orgs.org_manage_panel=Διαχείριση Οργανισμού orgs.org_manage_panel=Διαχείριση Οργανισμού
orgs.name=Όνομα orgs.name=Όνομα
@ -2847,6 +2888,7 @@ packages.package_manage_panel=Διαχείριση Πακέτων
packages.total_size=Συνολικό Μέγεθος: %s packages.total_size=Συνολικό Μέγεθος: %s
packages.unreferenced_size=Μέγεθος Χωρίς Αναφορά: %s packages.unreferenced_size=Μέγεθος Χωρίς Αναφορά: %s
packages.cleanup=Εκκαθάριση ληγμένων δεδομένων packages.cleanup=Εκκαθάριση ληγμένων δεδομένων
packages.cleanup.success=Επιτυχής εκκαθάριση δεδομένων που έχουν λήξει
packages.owner=Ιδιοκτήτης packages.owner=Ιδιοκτήτης
packages.creator=Δημιουργός packages.creator=Δημιουργός
packages.name=Όνομα packages.name=Όνομα
@ -2857,10 +2899,12 @@ packages.size=Μέγεθος
packages.published=Δημοσιευμένα packages.published=Δημοσιευμένα
defaulthooks=Προεπιλεγμένα Webhooks defaulthooks=Προεπιλεγμένα Webhooks
defaulthooks.desc=Τα Webhooks κάνουν αυτόματα αιτήσεις HTTP POST σε ένα διακομιστή όταν ενεργοποιούν ορισμένα γεγονότα στο Gitea. Τα Webhooks που ορίζονται εδώ είναι προκαθορισμένα και θα αντιγραφούν σε όλα τα νέα αποθετήρια. Διαβάστε περισσότερα στον οδηγό <a target="_blank" rel="noopener" href="https://docs.gitea.com/usage/webhooks">webhooks</a>.
defaulthooks.add_webhook=Προσθήκη Προεπιλεγμένου Webhook defaulthooks.add_webhook=Προσθήκη Προεπιλεγμένου Webhook
defaulthooks.update_webhook=Ενημέρωση Προεπιλεγμένου Webhook defaulthooks.update_webhook=Ενημέρωση Προεπιλεγμένου Webhook
systemhooks=Webhooks Συστήματος systemhooks=Webhooks Συστήματος
systemhooks.desc=Τα Webhooks κάνουν αυτόματα αιτήσεις HTTP POST σε ένα διακομιστή όταν ενεργοποιούνται ορισμένα γεγονότα στο Gitea. Τα Webhooks που ορίζονται εδώ θα ενεργούν σε όλα τα αποθετήρια του συστήματος, γι 'αυτό παρακαλώ εξετάστε τυχόν επιπτώσεις απόδοσης που μπορεί να έχει. Διαβάστε περισσότερα στον οδηγό <a target="_blank" rel="noopener" href="https://docs.gitea.com/usage/webhooks">webhooks</a>.
systemhooks.add_webhook=Προσθήκη Webhook Συστήματος systemhooks.add_webhook=Προσθήκη Webhook Συστήματος
systemhooks.update_webhook=Ενημέρωση Webhook Συστήματος systemhooks.update_webhook=Ενημέρωση Webhook Συστήματος
@ -2953,6 +2997,7 @@ auths.sspi_default_language=Προεπιλεγμένη γλώσσα χρήστη
auths.sspi_default_language_helper=Προεπιλεγμένη γλώσσα για τους χρήστες που δημιουργούνται αυτόματα με τη μέθοδο ταυτοποίησης SSPI. Αφήστε κενό αν προτιμάτε η γλώσσα να εντοπιστεί αυτόματα. auths.sspi_default_language_helper=Προεπιλεγμένη γλώσσα για τους χρήστες που δημιουργούνται αυτόματα με τη μέθοδο ταυτοποίησης SSPI. Αφήστε κενό αν προτιμάτε η γλώσσα να εντοπιστεί αυτόματα.
auths.tips=Συμβουλές auths.tips=Συμβουλές
auths.tips.oauth2.general=Ταυτοποίηση OAuth2 auths.tips.oauth2.general=Ταυτοποίηση OAuth2
auths.tips.oauth2.general.tip=Κατά την εγγραφή μιας νέας ταυτοποίησης OAuth2, το URL κλήσης/ανακατεύθυνσης πρέπει να είναι:
auths.tip.oauth2_provider=Πάροχος OAuth2 auths.tip.oauth2_provider=Πάροχος OAuth2
auths.tip.bitbucket=Καταχωρήστε ένα νέο καταναλωτή OAuth στο https://bitbucket.org/account/user/<your username>/oauth-consumers/new και προσθέστε το δικαίωμα 'Account' - 'Read' auths.tip.bitbucket=Καταχωρήστε ένα νέο καταναλωτή OAuth στο https://bitbucket.org/account/user/<your username>/oauth-consumers/new και προσθέστε το δικαίωμα 'Account' - 'Read'
auths.tip.nextcloud=`Καταχωρήστε ένα νέο καταναλωτή OAuth στην υπηρεσία σας χρησιμοποιώντας το παρακάτω μενού "Settings -> Security -> OAuth 2.0 client"` auths.tip.nextcloud=`Καταχωρήστε ένα νέο καταναλωτή OAuth στην υπηρεσία σας χρησιμοποιώντας το παρακάτω μενού "Settings -> Security -> OAuth 2.0 client"`
@ -2964,6 +3009,7 @@ auths.tip.google_plus=Αποκτήστε τα διαπιστευτήρια πε
auths.tip.openid_connect=Χρησιμοποιήστε το OpenID Connect Discovery URL (<server>/.well known/openid-configuration) για να καθορίσετε τα τελικά σημεία auths.tip.openid_connect=Χρησιμοποιήστε το OpenID Connect Discovery URL (<server>/.well known/openid-configuration) για να καθορίσετε τα τελικά σημεία
auths.tip.twitter=Πηγαίνετε στο https://dev.twitter.com/apps, δημιουργήστε μια εφαρμογή και βεβαιωθείτε ότι η επιλογή “Allow this application to be used to Sign in with Twitter” είναι ενεργοποιημένη auths.tip.twitter=Πηγαίνετε στο https://dev.twitter.com/apps, δημιουργήστε μια εφαρμογή και βεβαιωθείτε ότι η επιλογή “Allow this application to be used to Sign in with Twitter” είναι ενεργοποιημένη
auths.tip.discord=Καταχωρήστε μια νέα εφαρμογή στο https://discordapp.com/developers/applications/me auths.tip.discord=Καταχωρήστε μια νέα εφαρμογή στο https://discordapp.com/developers/applications/me
auths.tip.gitea=Καταχωρήστε μια νέα εφαρμογή OAuth2. Μπορείτε να βρείτε τον οδηγό στο https://docs.gitea.com/development/oauth2-provider
auths.tip.yandex=`Δημιουργήστε μια νέα εφαρμογή στο https://oauth.yandex.com/client/new. Επιλέξτε τα ακόλουθα δικαιώματα από την ενότητα "Yandex.Passport API": "Access to email address", "Access to user avatar" και "Access to username, first name and surname, gender"` auths.tip.yandex=`Δημιουργήστε μια νέα εφαρμογή στο https://oauth.yandex.com/client/new. Επιλέξτε τα ακόλουθα δικαιώματα από την ενότητα "Yandex.Passport API": "Access to email address", "Access to user avatar" και "Access to username, first name and surname, gender"`
auths.tip.mastodon=Εισαγάγετε ένα προσαρμομένο URL για την υπηρεσία mastodon με την οποία θέλετε να πιστοποιήσετε (ή να χρησιμοποιήσετε την προεπιλεγμένη) auths.tip.mastodon=Εισαγάγετε ένα προσαρμομένο URL για την υπηρεσία mastodon με την οποία θέλετε να πιστοποιήσετε (ή να χρησιμοποιήσετε την προεπιλεγμένη)
auths.edit=Επεξεργασία Πηγής Ταυτοποίησης auths.edit=Επεξεργασία Πηγής Ταυτοποίησης
@ -3267,6 +3313,7 @@ desc=Διαχείριση πακέτων μητρώου.
empty=Δεν υπάρχουν πακέτα ακόμα. empty=Δεν υπάρχουν πακέτα ακόμα.
empty.documentation=Για περισσότερες πληροφορίες σχετικά με το μητρώο πακέτων, ανατρέξτε <a target="_blank" rel="noopener noreferrer" href="%s">στην τεκμηρίωση</a>. empty.documentation=Για περισσότερες πληροφορίες σχετικά με το μητρώο πακέτων, ανατρέξτε <a target="_blank" rel="noopener noreferrer" href="%s">στην τεκμηρίωση</a>.
empty.repo=Μήπως ανεβάσατε ένα πακέτο, αλλά δεν εμφανίζεται εδώ; Πηγαίνετε στις <a href="%[1]s">ρυθμίσεις πακέτων</a> και συνδέστε το σε αυτό το αποθετήριο. empty.repo=Μήπως ανεβάσατε ένα πακέτο, αλλά δεν εμφανίζεται εδώ; Πηγαίνετε στις <a href="%[1]s">ρυθμίσεις πακέτων</a> και συνδέστε το σε αυτό το αποθετήριο.
registry.documentation=Για περισσότερες πληροφορίες σχετικά με το μητρώο %s, ανατρέξτε στη τεκμηρίωση <a target="_blank" rel="noopener noreferrer" href="%s"></a>.
filter.type=Τύπος filter.type=Τύπος
filter.type.all=Όλα filter.type.all=Όλα
filter.no_result=Το φίλτρο δεν παρήγαγε αποτελέσματα. filter.no_result=Το φίλτρο δεν παρήγαγε αποτελέσματα.
@ -3378,9 +3425,11 @@ settings.delete.success=Το πακέτο έχει διαγραφεί.
settings.delete.error=Αποτυχία διαγραφής του πακέτου. settings.delete.error=Αποτυχία διαγραφής του πακέτου.
owner.settings.cargo.title=Ευρετήριο Μητρώου Cargo owner.settings.cargo.title=Ευρετήριο Μητρώου Cargo
owner.settings.cargo.initialize=Αρχικοποίηση Ευρετηρίου owner.settings.cargo.initialize=Αρχικοποίηση Ευρετηρίου
owner.settings.cargo.initialize.description=Απαιτείται ένα ειδικό αποθετήριο ευρετηρίου Git για τη χρήση του μητρώου Cargo. Χρησιμοποιώντας αυτή την επιλογή θα δημιουργηθεί ξανά το αποθετήριο και θα ρυθμιστεί αυτόματα.
owner.settings.cargo.initialize.error=Αποτυχία αρχικοποίησης ευρετηρίου Cargo: %v owner.settings.cargo.initialize.error=Αποτυχία αρχικοποίησης ευρετηρίου Cargo: %v
owner.settings.cargo.initialize.success=Ο ευρετήριο Cargo δημιουργήθηκε με επιτυχία. owner.settings.cargo.initialize.success=Ο ευρετήριο Cargo δημιουργήθηκε με επιτυχία.
owner.settings.cargo.rebuild=Αναδημιουργία Ευρετηρίου owner.settings.cargo.rebuild=Αναδημιουργία Ευρετηρίου
owner.settings.cargo.rebuild.description=Η ανοικοδόμηση μπορεί να είναι χρήσιμη εάν ο δείκτης δεν είναι συγχρονισμένος με τα αποθηκευμένα πακέτα Cargo.
owner.settings.cargo.rebuild.error=Αποτυχία αναδόμησης του ευρετηρίου Cargo: %v owner.settings.cargo.rebuild.error=Αποτυχία αναδόμησης του ευρετηρίου Cargo: %v
owner.settings.cargo.rebuild.success=Το ευρετήριο Cargo αναδομήθηκε με επιτυχία. owner.settings.cargo.rebuild.success=Το ευρετήριο Cargo αναδομήθηκε με επιτυχία.
owner.settings.cleanuprules.title=Διαχείριση Κανόνων Εκκαθάρισης owner.settings.cleanuprules.title=Διαχείριση Κανόνων Εκκαθάρισης
@ -3405,6 +3454,7 @@ owner.settings.cleanuprules.success.update=Ο κανόνας καθαρισμο
owner.settings.cleanuprules.success.delete=Ο κανόνας καθαρισμού διαγράφηκε. owner.settings.cleanuprules.success.delete=Ο κανόνας καθαρισμού διαγράφηκε.
owner.settings.chef.title=Μητρώο Chef owner.settings.chef.title=Μητρώο Chef
owner.settings.chef.keypair=Δημιουργία ζεύγους κλειδιών owner.settings.chef.keypair=Δημιουργία ζεύγους κλειδιών
owner.settings.chef.keypair.description=Ένα ζεύγος κλειδιών είναι απαραίτητο για ταυτοποίηση στο μητρώο Chef. Αν έχετε δημιουργήσει ένα ζεύγος κλειδιών πριν, η δημιουργία ενός νέου ζεύγους κλειδιών θα απορρίψει το παλιό ζεύγος κλειδιών.
[secrets] [secrets]
secrets=Μυστικά secrets=Μυστικά

View file

@ -1956,6 +1956,8 @@ wiki.page_name_desc = Enter a name for this Wiki page. Some special names are: '
wiki.original_git_entry_tooltip = View original Git file instead of using friendly link. wiki.original_git_entry_tooltip = View original Git file instead of using friendly link.
activity = Activity activity = Activity
activity.navbar.pulse = Pulse
activity.navbar.contributors = Contributors
activity.period.filter_label = Period: activity.period.filter_label = Period:
activity.period.daily = 1 day activity.period.daily = 1 day
activity.period.halfweekly = 3 days activity.period.halfweekly = 3 days
@ -2021,6 +2023,16 @@ activity.git_stats_and_deletions = and
activity.git_stats_deletion_1 = %d deletion activity.git_stats_deletion_1 = %d deletion
activity.git_stats_deletion_n = %d deletions activity.git_stats_deletion_n = %d deletions
contributors = Contributors
contributors.contribution_type.filter_label = Contribution type:
contributors.contribution_type.commits = Commits
contributors.contribution_type.additions = Additions
contributors.contribution_type.deletions = Deletions
contributors.loading_title = Loading contributions...
contributors.loading_title_failed = Could not load contributions
contributors.loading_info = This might take a bit…
contributors.component_failed_to_load = An unexpected error happened.
search = Search search = Search
search.search_repo = Search repository search.search_repo = Search repository
search.type.tooltip = Search type search.type.tooltip = Search type

View file

@ -18,6 +18,7 @@ template=Şablon
language=Dil language=Dil
notifications=Bildirimler notifications=Bildirimler
active_stopwatch=Etkin Zaman Takibi active_stopwatch=Etkin Zaman Takibi
tracked_time_summary=Konu listesi süzgeçlerine dayanan takip edilen zamanın özeti
create_new=Oluştur… create_new=Oluştur…
user_profile_and_more=Profil ve Ayarlar… user_profile_and_more=Profil ve Ayarlar…
signed_in_as=Giriş yapan: signed_in_as=Giriş yapan:
@ -91,6 +92,7 @@ remove=Kaldır
remove_all=Tümünü Kaldır remove_all=Tümünü Kaldır
remove_label_str=`"%s" öğesini kaldır` remove_label_str=`"%s" öğesini kaldır`
edit=Düzenle edit=Düzenle
view=Görüntüle
enabled=Aktifleştirilmiş enabled=Aktifleştirilmiş
disabled=Devre Dışı disabled=Devre Dışı
@ -98,6 +100,7 @@ locked=Kilitli
copy=Kopyala copy=Kopyala
copy_url=URL'yi kopyala copy_url=URL'yi kopyala
copy_hash=Hash'i kopyala
copy_content=İçeriği kopyala copy_content=İçeriği kopyala
copy_branch=Dal adını kopyala copy_branch=Dal adını kopyala
copy_success=Kopyalandı! copy_success=Kopyalandı!
@ -110,6 +113,7 @@ loading=Yükleniyor…
error=Hata error=Hata
error404=Ulaşmaya çalıştığınız sayfa <strong>mevcut değil</strong> veya <strong>görüntüleme yetkiniz yok</strong>. error404=Ulaşmaya çalıştığınız sayfa <strong>mevcut değil</strong> veya <strong>görüntüleme yetkiniz yok</strong>.
go_back=Geri Git
never=Asla never=Asla
unknown=Bilinmiyor unknown=Bilinmiyor
@ -181,6 +185,7 @@ network_error=Ağ hatası
[startpage] [startpage]
app_desc=Zahmetsiz, kendi sunucunuzda barındırabileceğiniz Git servisi app_desc=Zahmetsiz, kendi sunucunuzda barındırabileceğiniz Git servisi
install=Kurulumu kolay install=Kurulumu kolay
install_desc=Platformunuz için <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.com/installation/install-from-binary">ikili dosyayı çalıştırın</a>, <a target="_blank" rel="noopener noreferrer" href="https://github.com/go-gitea/gitea/tree/master/docker">Docker</a> ile yükleyin veya <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.com/installation/install-from-package">paket</a> olarak edinin.
platform=Farklı platformlarda çalışablir platform=Farklı platformlarda çalışablir
platform_desc=Forgejo <a target="_blank" rel="noopener noreferrer" href="http://golang.org/">Go</a> ile derleme yapılabilecek her yerde çalışmaktadır: Windows, macOS, Linux, ARM, vb. Hangisini seviyorsanız onu seçin! platform_desc=Forgejo <a target="_blank" rel="noopener noreferrer" href="http://golang.org/">Go</a> ile derleme yapılabilecek her yerde çalışmaktadır: Windows, macOS, Linux, ARM, vb. Hangisini seviyorsanız onu seçin!
lightweight=Hafif lightweight=Hafif
@ -357,6 +362,7 @@ disable_register_prompt=Kayıt işlemi devre dışıdır. Lütfen site yönetici
disable_register_mail=Kayıt için e-posta doğrulama devre dışıdır. disable_register_mail=Kayıt için e-posta doğrulama devre dışıdır.
manual_activation_only=Etkinleştirmeyi tamamlamak için site yöneticinizle bağlantıya geçin. manual_activation_only=Etkinleştirmeyi tamamlamak için site yöneticinizle bağlantıya geçin.
remember_me=Bu Aygıtı hatırla remember_me=Bu Aygıtı hatırla
remember_me.compromised=Oturum açma tokeni artık geçerli değil, bu ele geçirilmiş bir hesaba işaret ediyor olabilir. Lütfen hesabınızda olağandışı faaliyet olup olmadığını denetleyin.
forgot_password_title=Şifremi unuttum forgot_password_title=Şifremi unuttum
forgot_password=Şifrenizi mi unuttunuz? forgot_password=Şifrenizi mi unuttunuz?
sign_up_now=Bir hesaba mı ihtiyacınız var? Hemen kaydolun. sign_up_now=Bir hesaba mı ihtiyacınız var? Hemen kaydolun.
@ -376,6 +382,7 @@ email_not_associate=Bu e-posta adresi hiçbir hesap ile ilişkilendirilmemiştir
send_reset_mail=Hesap Kurtarma E-postası Gönder send_reset_mail=Hesap Kurtarma E-postası Gönder
reset_password=Hesap Kurtarma reset_password=Hesap Kurtarma
invalid_code=Doğrulama kodunuz geçersiz veya süresi dolmuş. invalid_code=Doğrulama kodunuz geçersiz veya süresi dolmuş.
invalid_code_forgot_password=Onay kodunuz hatalı veya süresi geçmiş. Yeni bir oturum başlatmak için <a href="%s">buraya</a> tıklayın.
invalid_password=Parolanız hesap oluşturulurken kullanılan parolayla eşleşmiyor. invalid_password=Parolanız hesap oluşturulurken kullanılan parolayla eşleşmiyor.
reset_password_helper=Hesabı Kurtar reset_password_helper=Hesabı Kurtar
reset_password_wrong_user=%s olarak oturum açmışsınız, ancak hesap kurtarma bağlantısı %s için reset_password_wrong_user=%s olarak oturum açmışsınız, ancak hesap kurtarma bağlantısı %s için
@ -677,6 +684,7 @@ choose_new_avatar=Yeni Avatar Seç
update_avatar=Profil Resmini Güncelle update_avatar=Profil Resmini Güncelle
delete_current_avatar=Güncel Avatarı Sil delete_current_avatar=Güncel Avatarı Sil
uploaded_avatar_not_a_image=Yüklenen dosya bir resim dosyası değil. uploaded_avatar_not_a_image=Yüklenen dosya bir resim dosyası değil.
uploaded_avatar_is_too_big=Yüklenen dosyanın boyutu (%d KiB), azami boyutu (%d KiB) aşıyor.
update_avatar_success=Profil resminiz değiştirildi. update_avatar_success=Profil resminiz değiştirildi.
update_user_avatar_success=Kullanıcının avatarı güncellendi. update_user_avatar_success=Kullanıcının avatarı güncellendi.
@ -858,6 +866,7 @@ revoke_oauth2_grant_description=Bu üçüncü taraf uygulamasına erişimin ipta
revoke_oauth2_grant_success=Erişim başarıyla kaldırıldı. revoke_oauth2_grant_success=Erişim başarıyla kaldırıldı.
twofa_desc=İki faktörlü kimlik doğrulama, hesabınızın güvenliğini artırır. twofa_desc=İki faktörlü kimlik doğrulama, hesabınızın güvenliğini artırır.
twofa_recovery_tip=Aygıtınızı kaybetmeniz durumunda, hesabınıza tekrar erişmek için tek kullanımlık kurtarma anahtarını kullanabileceksiniz.
twofa_is_enrolled=Hesabınız şu anda iki faktörlü kimlik doğrulaması içinde <strong>kaydedilmiş</strong>. twofa_is_enrolled=Hesabınız şu anda iki faktörlü kimlik doğrulaması içinde <strong>kaydedilmiş</strong>.
twofa_not_enrolled=Hesabınız şu anda iki faktörlü kimlik doğrulaması içinde kaydedilmemiş. twofa_not_enrolled=Hesabınız şu anda iki faktörlü kimlik doğrulaması içinde kaydedilmemiş.
twofa_disable=İki Aşamalı Doğrulamayı Devre Dışı Bırak twofa_disable=İki Aşamalı Doğrulamayı Devre Dışı Bırak
@ -880,6 +889,8 @@ webauthn_register_key=Güvenlik Anahtarı Ekle
webauthn_nickname=Takma Ad webauthn_nickname=Takma Ad
webauthn_delete_key=Güvenlik Anahtarını Kaldır webauthn_delete_key=Güvenlik Anahtarını Kaldır
webauthn_delete_key_desc=Bir güvenlik anahtarını kaldırırsanız, onunla artık giriş yapamazsınız. Devam edilsin mi? webauthn_delete_key_desc=Bir güvenlik anahtarını kaldırırsanız, onunla artık giriş yapamazsınız. Devam edilsin mi?
webauthn_key_loss_warning=Güvenlik anahtarlarınızı kaybederseniz, hesabınıza erişimi kaybedersiniz.
webauthn_alternative_tip=Ek bir kimlik doğrulama yöntemi ayarlamak isteyebilirsiniz.
manage_account_links=Bağlı Hesapları Yönet manage_account_links=Bağlı Hesapları Yönet
manage_account_links_desc=Bu harici hesaplar Forgejo hesabınızla bağlantılı. manage_account_links_desc=Bu harici hesaplar Forgejo hesabınızla bağlantılı.
@ -916,6 +927,7 @@ visibility.private=Özel
visibility.private_tooltip=Sadece katıldığınız organizasyonların üyeleri tarafından görünür visibility.private_tooltip=Sadece katıldığınız organizasyonların üyeleri tarafından görünür
[repo] [repo]
new_repo_helper=Bir depo, sürüm geçmişi dahil tüm proje dosyalarını içerir. Zaten başka bir yerde mi barındırıyorsunuz? <a href="%s">Depoyu taşıyın.</a>
owner=Sahibi owner=Sahibi
owner_helper=Bazı organizasyonlar, en çok depo sayısı sınırı nedeniyle açılır menüde görünmeyebilir. owner_helper=Bazı organizasyonlar, en çok depo sayısı sınırı nedeniyle açılır menüde görünmeyebilir.
repo_name=Depo İsmi repo_name=Depo İsmi
@ -936,6 +948,8 @@ fork_from=Buradan Çatalla
already_forked=%s deposunu zaten çatalladınız already_forked=%s deposunu zaten çatalladınız
fork_to_different_account=Başka bir hesaba çatalla fork_to_different_account=Başka bir hesaba çatalla
fork_visibility_helper=Çatallanmış bir deponun görünürlüğü değiştirilemez. fork_visibility_helper=Çatallanmış bir deponun görünürlüğü değiştirilemez.
fork_branch=Çatala klonlanacak dal
all_branches=Tüm dallar
fork_no_valid_owners=Geçerli bir sahibi olmadığı için bu depo çatallanamaz. fork_no_valid_owners=Geçerli bir sahibi olmadığı için bu depo çatallanamaz.
use_template=Bu şablonu kullan use_template=Bu şablonu kullan
clone_in_vsc=VS Code'ta klonla clone_in_vsc=VS Code'ta klonla
@ -965,6 +979,7 @@ trust_model_helper_collaborator_committer=Ortak çalışan+İşleyen: İşleyenl
trust_model_helper_default=Varsayılan: Bu kurulum için varsayılan güven modelini kullan trust_model_helper_default=Varsayılan: Bu kurulum için varsayılan güven modelini kullan
create_repo=Depo Oluştur create_repo=Depo Oluştur
default_branch=Varsayılan Dal default_branch=Varsayılan Dal
default_branch_label=varsayılan
default_branch_helper=Varsayılan dal, değişiklik istekleri ve kod işlemeleri için temel daldır. default_branch_helper=Varsayılan dal, değişiklik istekleri ve kod işlemeleri için temel daldır.
mirror_prune=Buda mirror_prune=Buda
mirror_prune_desc=Kullanılmayan uzak depoları izleyen referansları kaldır mirror_prune_desc=Kullanılmayan uzak depoları izleyen referansları kaldır
@ -1000,8 +1015,13 @@ delete_preexisting=Önceden var olan dosyaları sil
delete_preexisting_content=%s içindeki dosyaları sil delete_preexisting_content=%s içindeki dosyaları sil
delete_preexisting_success=%s içindeki kabul edilmeyen dosyalar silindi delete_preexisting_success=%s içindeki kabul edilmeyen dosyalar silindi
blame_prior=Bu değişiklikten önceki suçu görüntüle blame_prior=Bu değişiklikten önceki suçu görüntüle
blame.ignore_revs=<a href="%s">.git-blame-ignore-revs</a> dosyasındaki sürümler yok sayılıyor. Bunun yerine normal sorumlu görüntüsü için <a href="%s">buraya tıklayın</a>.
blame.ignore_revs.failed=<a href="%s">.git-blame-ignore-revs</a> dosyasındaki sürümler yok sayılamadı.
author_search_tooltip=En fazla 30 kullanıcı görüntüler author_search_tooltip=En fazla 30 kullanıcı görüntüler
tree_path_not_found_commit=%[1] yolu, %[2]s işlemesinde mevcut değil
tree_path_not_found_branch=%[1] yolu, %[2]s dalında mevcut değil
tree_path_not_found_tag=%[1] yolu, %[2]s etiketinde mevcut değil
transfer.accept=Aktarımı Kabul Et transfer.accept=Aktarımı Kabul Et
transfer.accept_desc=`"%s" tarafına aktar` transfer.accept_desc=`"%s" tarafına aktar`
@ -1265,6 +1285,7 @@ commits.signed_by_untrusted_user=Güvenilmeyen kullanıcı tarafından imzaland
commits.signed_by_untrusted_user_unmatched=İşleyici ile eşleşmeyen güvenilmeyen kullanıcı tarafından imzalanmış commits.signed_by_untrusted_user_unmatched=İşleyici ile eşleşmeyen güvenilmeyen kullanıcı tarafından imzalanmış
commits.gpg_key_id=GPG Anahtar Kimliği commits.gpg_key_id=GPG Anahtar Kimliği
commits.ssh_key_fingerprint=SSH Anahtar Parmak İzi commits.ssh_key_fingerprint=SSH Anahtar Parmak İzi
commits.view_path=Geçmişte bu noktayı görüntüle
commit.operations=İşlemler commit.operations=İşlemler
commit.revert=Geri Al commit.revert=Geri Al
@ -1475,8 +1496,17 @@ issues.ref_closed_from=`<a href="%[3]s">bu konuyu kapat%[4]s</a> <a id="%[1]s" h
issues.ref_reopened_from=`<a href="%[3]s">konuyu yeniden aç%[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>` issues.ref_reopened_from=`<a href="%[3]s">konuyu yeniden aç%[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.ref_from=`%[1]s'den` issues.ref_from=`%[1]s'den`
issues.author=Yazar issues.author=Yazar
issues.author_helper=Bu kullanıcı yazardır.
issues.role.owner=Sahibi issues.role.owner=Sahibi
issues.role.owner_helper=Bu kullanıcı bu deponun sahibidir.
issues.role.member=Üye issues.role.member=Üye
issues.role.member_helper=Bu kullanıcı bu deponun sahibi olan organizasyonun üyesidir.
issues.role.collaborator=Katkıcı
issues.role.collaborator_helper=Kullanıcı bu depoya işbirliği için davet edildi.
issues.role.first_time_contributor=İlk defa katkıcı
issues.role.first_time_contributor_helper=Bu, bu kullanıcının bu depoya ilk katkısı.
issues.role.contributor=Katılımcı
issues.role.contributor_helper=Bu kullanıcı bu depoya daha önce işleme gönderdi.
issues.re_request_review=İncelemeyi yeniden iste issues.re_request_review=İncelemeyi yeniden iste
issues.is_stale=Bu incelemeden bu yana bu istekte değişiklikler oldu issues.is_stale=Bu incelemeden bu yana bu istekte değişiklikler oldu
issues.remove_request_review=İnceleme isteğini kaldır issues.remove_request_review=İnceleme isteğini kaldır
@ -1492,6 +1522,8 @@ issues.label_description=Etiket açıklaması
issues.label_color=Etiket rengi issues.label_color=Etiket rengi
issues.label_exclusive=Özel issues.label_exclusive=Özel
issues.label_archive=Etiketi Arşivle issues.label_archive=Etiketi Arşivle
issues.label_archived_filter=Arşivlenmiş etiketleri göster
issues.label_archive_tooltip=Arşivlenmiş etiketler, etiket araması yapılırken varsayılan olarak önerilerin dışında tutuluyor.
issues.label_exclusive_desc=<code>Kapsam/öğe</code> etiketini, diğer <code>kapsam/</code> etiketleriyle ayrışık olacak şekilde adlandırın. issues.label_exclusive_desc=<code>Kapsam/öğe</code> etiketini, diğer <code>kapsam/</code> etiketleriyle ayrışık olacak şekilde adlandırın.
issues.label_exclusive_warning=Çakışan kapsamlı etiketler, bir konu veya değişiklik isteği etiketleri düzenlenirken kaldırılacaktır. issues.label_exclusive_warning=Çakışan kapsamlı etiketler, bir konu veya değişiklik isteği etiketleri düzenlenirken kaldırılacaktır.
issues.label_count=%d etiket issues.label_count=%d etiket
@ -1746,6 +1778,7 @@ pulls.rebase_conflict_summary=Hata Mesajı
pulls.unrelated_histories=Birleştirme Başarısız: Birleştirme başlığı ve tabanı ortak bir geçmişi paylaşmıyor. İpucu: Farklı bir strateji deneyin pulls.unrelated_histories=Birleştirme Başarısız: Birleştirme başlığı ve tabanı ortak bir geçmişi paylaşmıyor. İpucu: Farklı bir strateji deneyin
pulls.merge_out_of_date=Birleştirme Başarısız: Birleştirme oluşturulurken, taban güncellendi. İpucu: Tekrar deneyin. pulls.merge_out_of_date=Birleştirme Başarısız: Birleştirme oluşturulurken, taban güncellendi. İpucu: Tekrar deneyin.
pulls.head_out_of_date=Birleştirme Başarısız: Birleştirme oluşturulurken, ana güncellendi. İpucu: Tekrar deneyin. pulls.head_out_of_date=Birleştirme Başarısız: Birleştirme oluşturulurken, ana güncellendi. İpucu: Tekrar deneyin.
pulls.has_merged=Başarısız: Değişiklik isteği birleştirildi, yeniden birleştiremez veya hedef dalı değiştiremezsiniz.
pulls.push_rejected=Birleştirme Başarısız Oldu: Gönderme reddedildi. Bu depo için Git İstemcilerini inceleyin. pulls.push_rejected=Birleştirme Başarısız Oldu: Gönderme reddedildi. Bu depo için Git İstemcilerini inceleyin.
pulls.push_rejected_summary=Tam Red Mesajı pulls.push_rejected_summary=Tam Red Mesajı
pulls.push_rejected_no_message=Birleştirme başarısız oldu: Gönderme reddedildi, ancak uzak bir mesaj yoktu.<br>Bu depo için Git İstemcilerini inceleyin pulls.push_rejected_no_message=Birleştirme başarısız oldu: Gönderme reddedildi, ancak uzak bir mesaj yoktu.<br>Bu depo için Git İstemcilerini inceleyin
@ -1757,6 +1790,8 @@ pulls.status_checks_failure=Bazı kontroller başarısız oldu
pulls.status_checks_error=Bazı kontroller hatalar bildirdi pulls.status_checks_error=Bazı kontroller hatalar bildirdi
pulls.status_checks_requested=Gerekli pulls.status_checks_requested=Gerekli
pulls.status_checks_details=Ayrıntılar pulls.status_checks_details=Ayrıntılar
pulls.status_checks_hide_all=Tüm denetlemeleri gizle
pulls.status_checks_show_all=Tüm denetlemeleri göster
pulls.update_branch=Dalı birleştirmeyle güncelle pulls.update_branch=Dalı birleştirmeyle güncelle
pulls.update_branch_rebase=Dalı yeniden yapılandırmayla güncelle pulls.update_branch_rebase=Dalı yeniden yapılandırmayla güncelle
pulls.update_branch_success=Dal güncellemesi başarıyla gerçekleştirildi pulls.update_branch_success=Dal güncellemesi başarıyla gerçekleştirildi
@ -1765,6 +1800,11 @@ pulls.outdated_with_base_branch=Bu dal, temel dal ile güncel değil
pulls.close=Değişiklik İsteğini Kapat pulls.close=Değişiklik İsteğini Kapat
pulls.closed_at=`<a id="%[1]s" href="#%[1]s">%[2]s</a> değişiklik isteğini kapattı` pulls.closed_at=`<a id="%[1]s" href="#%[1]s">%[2]s</a> değişiklik isteğini kapattı`
pulls.reopened_at=`<a id="%[1]s" href="#%[1]s">%[2]s</a> değişiklik isteğini yeniden açtı` pulls.reopened_at=`<a id="%[1]s" href="#%[1]s">%[2]s</a> değişiklik isteğini yeniden açtı`
pulls.cmd_instruction_hint=`<a class="show-instruction">Komut satırı talimatlarını</a> görüntüleyin.`
pulls.cmd_instruction_checkout_title=Çekme
pulls.cmd_instruction_checkout_desc=Proje deponuzdan yeni bir dalı çekin ve değişiklikleri test edin.
pulls.cmd_instruction_merge_title=Birleştir
pulls.cmd_instruction_merge_desc=Değişiklikleri birleştirin ve Gitea'da güncelleyin.
pulls.clear_merge_message=Birleştirme iletilerini temizle pulls.clear_merge_message=Birleştirme iletilerini temizle
pulls.clear_merge_message_hint=Birleştirme iletisini temizlemek sadece işleme ileti içeriğini kaldırır ama üretilmiş "Co-Authored-By …" gibi git fragmanlarını korur. pulls.clear_merge_message_hint=Birleştirme iletisini temizlemek sadece işleme ileti içeriğini kaldırır ama üretilmiş "Co-Authored-By …" gibi git fragmanlarını korur.
@ -1810,6 +1850,8 @@ milestones.edit_success=`"%s" dönüm noktası güncellendi.`
milestones.deletion=Kilometre Taşını Sil milestones.deletion=Kilometre Taşını Sil
milestones.deletion_desc=Bir kilometre taşını silmek, onu ilgili tüm sorunlardan kaldırır. Devam edilsin mi? milestones.deletion_desc=Bir kilometre taşını silmek, onu ilgili tüm sorunlardan kaldırır. Devam edilsin mi?
milestones.deletion_success=Kilometre taşı silindi. milestones.deletion_success=Kilometre taşı silindi.
milestones.filter_sort.earliest_due_data=En erken bitiş tarihi
milestones.filter_sort.latest_due_date=En uzak bitiş tarihi
milestones.filter_sort.least_complete=En az tamamlama milestones.filter_sort.least_complete=En az tamamlama
milestones.filter_sort.most_complete=En çok tamamlama milestones.filter_sort.most_complete=En çok tamamlama
milestones.filter_sort.most_issues=En çok konu milestones.filter_sort.most_issues=En çok konu
@ -1972,6 +2014,8 @@ settings.mirror_settings.push_mirror.add=Yansı Gönderimi Ekle
settings.mirror_settings.push_mirror.edit_sync_time=Yansı eşzamanlama aralığını düzenle settings.mirror_settings.push_mirror.edit_sync_time=Yansı eşzamanlama aralığını düzenle
settings.sync_mirror=Şimdi Eşitle settings.sync_mirror=Şimdi Eşitle
settings.pull_mirror_sync_in_progress=Şu an %s uzak sunucusundan değişiklikler çekiliyor.
settings.push_mirror_sync_in_progress=Şu an %s uzak sunucusuna değişiklikler itiliyor.
settings.site=Web Sitesi settings.site=Web Sitesi
settings.update_settings=Ayarları Güncelle settings.update_settings=Ayarları Güncelle
settings.update_mirror_settings=Yansı Ayarları Güncelle settings.update_mirror_settings=Yansı Ayarları Güncelle
@ -2105,12 +2149,14 @@ settings.webhook_deletion_desc=Bir web isteğini kaldırmak, ayarlarını ve tes
settings.webhook_deletion_success=Web isteği silindi. settings.webhook_deletion_success=Web isteği silindi.
settings.webhook.test_delivery=Test Dağıtımı settings.webhook.test_delivery=Test Dağıtımı
settings.webhook.test_delivery_desc=Bu web isteğini sahte bir olayla test edin. settings.webhook.test_delivery_desc=Bu web isteğini sahte bir olayla test edin.
settings.webhook.test_delivery_desc_disabled=Bu web istemcisini sahte bir olayla denemek için etkinleştirin.
settings.webhook.request=İstekler settings.webhook.request=İstekler
settings.webhook.response=Cevaplar settings.webhook.response=Cevaplar
settings.webhook.headers=Başlıklar settings.webhook.headers=Başlıklar
settings.webhook.payload=İçerik settings.webhook.payload=İçerik
settings.webhook.body=Gövde settings.webhook.body=Gövde
settings.webhook.replay.description=Bu web kancasını tekrar çalıştır. settings.webhook.replay.description=Bu web kancasını tekrar çalıştır.
settings.webhook.replay.description_disabled=Bu web istemcisini yeniden oynatmak için etkinleştirin.
settings.webhook.delivery.success=Teslim kuyruğuna bir olay eklendi. Teslim geçmişinde görünmesi birkaç saniye alabilir. settings.webhook.delivery.success=Teslim kuyruğuna bir olay eklendi. Teslim geçmişinde görünmesi birkaç saniye alabilir.
settings.githooks_desc=Git İstemcileri Git'in kendisi tarafından desteklenmektedir. Özel işlemler ayarlamak için aşağıdaki istemci dosyalarını düzenleyebilirsiniz. settings.githooks_desc=Git İstemcileri Git'in kendisi tarafından desteklenmektedir. Özel işlemler ayarlamak için aşağıdaki istemci dosyalarını düzenleyebilirsiniz.
settings.githook_edit_desc=İstek aktif değilse örnek içerik sunulacaktır. İçeriği boş bırakmak, isteği devre dışı bırakmayı beraberinde getirecektir. settings.githook_edit_desc=İstek aktif değilse örnek içerik sunulacaktır. İçeriği boş bırakmak, isteği devre dışı bırakmayı beraberinde getirecektir.
@ -2271,6 +2317,7 @@ settings.dismiss_stale_approvals_desc=Değişiklik isteğinin içeriğini deği
settings.require_signed_commits=İmzalı İşleme Gerekli settings.require_signed_commits=İmzalı İşleme Gerekli
settings.require_signed_commits_desc=Reddetme, onlar imzasızsa veya doğrulanamazsa bu dala gönderir. settings.require_signed_commits_desc=Reddetme, onlar imzasızsa veya doğrulanamazsa bu dala gönderir.
settings.protect_branch_name_pattern=Korunmuş Dal Adı Deseni settings.protect_branch_name_pattern=Korunmuş Dal Adı Deseni
settings.protect_branch_name_pattern_desc=Korunmuş dal isim desenleri. Desen sözdizimi için <a href="https://github.com/gobwas/glob">belgelere</a> bakabilirsiniz. Örnekler: main, release/**
settings.protect_patterns=Desenler settings.protect_patterns=Desenler
settings.protect_protected_file_patterns=Korumalı dosya kalıpları (noktalı virgülle ayrılmış ';'): settings.protect_protected_file_patterns=Korumalı dosya kalıpları (noktalı virgülle ayrılmış ';'):
settings.protect_protected_file_patterns_desc=Kullanıcının bu dalda dosya ekleme, düzenleme veya silme hakları olsa bile doğrudan değiştirilmesine izin verilmeyen korumalı dosyalar. Birden çok desen noktalı virgül (';') kullanılarak ayrılabilir. Desen sözdizimi için <a href='https://pkg.go.dev/github.com/gobwas/glob#Compile'>github.com/gobwas/glob</a> belgelerine bakın. Örnekler: <code>.drone.yml</code>, <code>/docs/**/*.txt</code>. settings.protect_protected_file_patterns_desc=Kullanıcının bu dalda dosya ekleme, düzenleme veya silme hakları olsa bile doğrudan değiştirilmesine izin verilmeyen korumalı dosyalar. Birden çok desen noktalı virgül (';') kullanılarak ayrılabilir. Desen sözdizimi için <a href='https://pkg.go.dev/github.com/gobwas/glob#Compile'>github.com/gobwas/glob</a> belgelerine bakın. Örnekler: <code>.drone.yml</code>, <code>/docs/**/*.txt</code>.
@ -2307,6 +2354,7 @@ settings.tags.protection.allowed.teams=İzin verilen takımlar
settings.tags.protection.allowed.noone=Hiç kimse settings.tags.protection.allowed.noone=Hiç kimse
settings.tags.protection.create=Etiketi Koru settings.tags.protection.create=Etiketi Koru
settings.tags.protection.none=Korumalı etiket yok. settings.tags.protection.none=Korumalı etiket yok.
settings.tags.protection.pattern.description=Birden çok etiketi eşleştirmek için tek bir ad, glob deseni veya normal ifade kullanabilirsiniz. Daha fazlası için <a target="_blank" rel="noopener" href="https://docs.gitea.com/usage/protected-tags">korumalı etiketler rehberini</a> okuyun.
settings.bot_token=Bot Jetonu settings.bot_token=Bot Jetonu
settings.chat_id=Sohbet Kimliği settings.chat_id=Sohbet Kimliği
settings.thread_id=İş Parçacığı ID settings.thread_id=İş Parçacığı ID
@ -2487,6 +2535,7 @@ branch.default_deletion_failed=`"%s" dalı varsayılan daldır. Silinemez.`
branch.restore=`"%s" Dalını Geri Yükle` branch.restore=`"%s" Dalını Geri Yükle`
branch.download=`"%s" Dalını İndir` branch.download=`"%s" Dalını İndir`
branch.rename=`"%s" Dalının Adını Değiştir` branch.rename=`"%s" Dalının Adını Değiştir`
branch.search=Dal Ara
branch.included_desc=Bu dal varsayılan dalın bir parçasıdır branch.included_desc=Bu dal varsayılan dalın bir parçasıdır
branch.included=Dahil branch.included=Dahil
branch.create_new_branch=Şu daldan dal oluştur: branch.create_new_branch=Şu daldan dal oluştur:
@ -2703,6 +2752,7 @@ dashboard.reinit_missing_repos=Kayıtları bulunanlar için tüm eksik Git depol
dashboard.sync_external_users=Harici kullanıcı verisini senkronize et dashboard.sync_external_users=Harici kullanıcı verisini senkronize et
dashboard.cleanup_hook_task_table=Hook_task tablosunu temizleme dashboard.cleanup_hook_task_table=Hook_task tablosunu temizleme
dashboard.cleanup_packages=Süresi dolmuş paketleri temizleme dashboard.cleanup_packages=Süresi dolmuş paketleri temizleme
dashboard.cleanup_actions=Eylemlerin süresi geçmiş günlük ve yapılarını temizle
dashboard.server_uptime=Sunucunun Ayakta Kalma Süresi dashboard.server_uptime=Sunucunun Ayakta Kalma Süresi
dashboard.current_goroutine=Güncel Goroutine'ler dashboard.current_goroutine=Güncel Goroutine'ler
dashboard.current_memory_usage=Güncel Bellek Kullanımı dashboard.current_memory_usage=Güncel Bellek Kullanımı
@ -2740,7 +2790,9 @@ dashboard.gc_lfs=LFS üst nesnelerin atıklarını temizle
dashboard.stop_zombie_tasks=Zombi görevleri durdur dashboard.stop_zombie_tasks=Zombi görevleri durdur
dashboard.stop_endless_tasks=Daimi görevleri durdur dashboard.stop_endless_tasks=Daimi görevleri durdur
dashboard.cancel_abandoned_jobs=Terkedilmiş görevleri iptal et dashboard.cancel_abandoned_jobs=Terkedilmiş görevleri iptal et
dashboard.start_schedule_tasks=Zamanlanmış görevleri başlat
dashboard.sync_branch.started=Dal Eşzamanlaması başladı dashboard.sync_branch.started=Dal Eşzamanlaması başladı
dashboard.rebuild_issue_indexer=Konu indeksini yeniden oluştur
users.user_manage_panel=Kullanıcı Hesap Yönetimi users.user_manage_panel=Kullanıcı Hesap Yönetimi
users.new_account=Yeni Kullanıcı Hesabı users.new_account=Yeni Kullanıcı Hesabı
@ -2749,6 +2801,9 @@ users.full_name=Tam İsim
users.activated=Aktifleştirilmiş users.activated=Aktifleştirilmiş
users.admin=Yönetici users.admin=Yönetici
users.restricted=Kısıtlanmış users.restricted=Kısıtlanmış
users.reserved=Rezerve
users.bot=Bot
users.remote=Uzak
users.2fa=2FD users.2fa=2FD
users.repos=Depolar users.repos=Depolar
users.created=Oluşturuldu users.created=Oluşturuldu
@ -2795,6 +2850,7 @@ users.list_status_filter.is_prohibit_login=Oturum Açmayı Önle
users.list_status_filter.not_prohibit_login=Oturum Açmaya İzin Ver users.list_status_filter.not_prohibit_login=Oturum Açmaya İzin Ver
users.list_status_filter.is_2fa_enabled=2FA Etkin users.list_status_filter.is_2fa_enabled=2FA Etkin
users.list_status_filter.not_2fa_enabled=2FA Devre Dışı users.list_status_filter.not_2fa_enabled=2FA Devre Dışı
users.details=Kullanıcı Ayrıntıları
emails.email_manage_panel=Kullanıcı E-posta Yönetimi emails.email_manage_panel=Kullanıcı E-posta Yönetimi
emails.primary=Birincil emails.primary=Birincil
@ -2807,6 +2863,7 @@ emails.updated=E-posta güncellendi
emails.not_updated=İstenen e-posta adresi güncellenemedi: %v emails.not_updated=İstenen e-posta adresi güncellenemedi: %v
emails.duplicate_active=Bu e-posta adresi farklı bir kullanıcı için zaten aktif. emails.duplicate_active=Bu e-posta adresi farklı bir kullanıcı için zaten aktif.
emails.change_email_header=E-posta Özelliklerini Güncelle emails.change_email_header=E-posta Özelliklerini Güncelle
emails.change_email_text=Bu e-posta adresini güncellemek istediğinizden emin misiniz?
orgs.org_manage_panel=Organizasyon Yönetimi orgs.org_manage_panel=Organizasyon Yönetimi
orgs.name=İsim orgs.name=İsim
@ -2831,6 +2888,7 @@ packages.package_manage_panel=Paket Yönetimi
packages.total_size=Toplam Boyut: %s packages.total_size=Toplam Boyut: %s
packages.unreferenced_size=Referanssız Boyut: %s packages.unreferenced_size=Referanssız Boyut: %s
packages.cleanup=Süresi dolmuş veriyi temizle packages.cleanup=Süresi dolmuş veriyi temizle
packages.cleanup.success=Süresi dolmuş veri başarıyla temizlendi
packages.owner=Sahibi packages.owner=Sahibi
packages.creator=Oluşturan packages.creator=Oluşturan
packages.name=İsim packages.name=İsim
@ -2841,10 +2899,12 @@ packages.size=Boyut
packages.published=Yayınlandı packages.published=Yayınlandı
defaulthooks=Varsayılan Web İstemcileri defaulthooks=Varsayılan Web İstemcileri
defaulthooks.desc=Web İstemcileri, belirli Gitea olayları tetiklendiğinde otomatik olarak HTTP POST isteklerini sunucuya yapar. Burada tanımlanan Web İstemcileri varsayılandır ve tüm yeni depolara kopyalanır. <a target="_blank" rel="noopener" href="https://docs.gitea.com/usage/webhooks">web istemcileri kılavuzunda</a> daha fazla bilgi edinin.
defaulthooks.add_webhook=Varsayılan Web İstemcisi Ekle defaulthooks.add_webhook=Varsayılan Web İstemcisi Ekle
defaulthooks.update_webhook=Varsayılan Web İstemcisini Güncelle defaulthooks.update_webhook=Varsayılan Web İstemcisini Güncelle
systemhooks=Sistem Web İstemcileri systemhooks=Sistem Web İstemcileri
systemhooks.desc=Belirli Gitea olayları tetiklendiğinde Web istemcileri otomatik olarak bir sunucuya HTTP POST istekleri yapar. Burada tanımlanan web istemcileri sistemdeki tüm depolar üzerinde çalışır, bu yüzden lütfen bunun olabilecek tüm performans sonuçlarını göz önünde bulundurun. <a target="_blank" rel="noopener" href="https://docs.gitea.com/usage/webhooks">web istemcileri kılavuzunda</a> daha fazla bilgi edinin.
systemhooks.add_webhook=Sistem Web İstemcisi Ekle systemhooks.add_webhook=Sistem Web İstemcisi Ekle
systemhooks.update_webhook=Sistem Web İstemcisi Güncelle systemhooks.update_webhook=Sistem Web İstemcisi Güncelle
@ -2949,6 +3009,7 @@ auths.tip.google_plus=OAuth2 istemci kimlik bilgilerini https://console.develope
auths.tip.openid_connect=Bitiş noktalarını belirlemek için OpenID Connect Discovery URL'sini kullanın (<server>/.well-known/openid-configuration) auths.tip.openid_connect=Bitiş noktalarını belirlemek için OpenID Connect Discovery URL'sini kullanın (<server>/.well-known/openid-configuration)
auths.tip.twitter=https://dev.twitter.com/apps adresine gidin, bir uygulama oluşturun ve “Bu uygulamanın Twitter ile oturum açmak için kullanılmasına izin ver” seçeneğinin etkin olduğundan emin olun auths.tip.twitter=https://dev.twitter.com/apps adresine gidin, bir uygulama oluşturun ve “Bu uygulamanın Twitter ile oturum açmak için kullanılmasına izin ver” seçeneğinin etkin olduğundan emin olun
auths.tip.discord=https://discordapp.com/developers/applications/me adresinde yeni bir uygulama kaydedin auths.tip.discord=https://discordapp.com/developers/applications/me adresinde yeni bir uygulama kaydedin
auths.tip.gitea=Yeni bir OAuth2 uygulaması kaydedin. Rehber https://docs.gitea.com/development/oauth2-provider adresinde bulunabilir
auths.tip.yandex=`https://oauth.yandex.com/client/new adresinde yeni bir uygulama oluşturun. "Yandex.Passport API'sı" bölümünden aşağıdaki izinleri seçin: "E-posta adresine erişim", "Kullanıcı avatarına erişim" ve "Kullanıcı adına, ad ve soyadına, cinsiyete erişim"` auths.tip.yandex=`https://oauth.yandex.com/client/new adresinde yeni bir uygulama oluşturun. "Yandex.Passport API'sı" bölümünden aşağıdaki izinleri seçin: "E-posta adresine erişim", "Kullanıcı avatarına erişim" ve "Kullanıcı adına, ad ve soyadına, cinsiyete erişim"`
auths.tip.mastodon=Kimlik doğrulaması yapmak istediğiniz mastodon örneği için özel bir örnek URL girin (veya varsayılan olanı kullanın) auths.tip.mastodon=Kimlik doğrulaması yapmak istediğiniz mastodon örneği için özel bir örnek URL girin (veya varsayılan olanı kullanın)
auths.edit=Kimlik Doğrulama Kaynağı Düzenle auths.edit=Kimlik Doğrulama Kaynağı Düzenle
@ -3128,8 +3189,10 @@ monitor.queue.name=İsim
monitor.queue.type=Tür monitor.queue.type=Tür
monitor.queue.exemplar=Örnek Türü monitor.queue.exemplar=Örnek Türü
monitor.queue.numberworkers=Çalışan Sayısı monitor.queue.numberworkers=Çalışan Sayısı
monitor.queue.activeworkers=Etkin Çalışanlar
monitor.queue.maxnumberworkers=En Fazla Çalışan Sayısı monitor.queue.maxnumberworkers=En Fazla Çalışan Sayısı
monitor.queue.numberinqueue=Kuyruktaki Sayı monitor.queue.numberinqueue=Kuyruktaki Sayı
monitor.queue.review_add=Çalışanları İncele / Ekle
monitor.queue.settings.title=Havuz Ayarları monitor.queue.settings.title=Havuz Ayarları
monitor.queue.settings.desc=Havuzlar, çalışan kuyruğu tıkanmasına bir yanıt olarak dinamik olarak büyürler. monitor.queue.settings.desc=Havuzlar, çalışan kuyruğu tıkanmasına bir yanıt olarak dinamik olarak büyürler.
monitor.queue.settings.maxnumberworkers=En fazla çalışan Sayısı monitor.queue.settings.maxnumberworkers=En fazla çalışan Sayısı
@ -3456,23 +3519,31 @@ runners.status.idle=Boşta
runners.status.active=Etkin runners.status.active=Etkin
runners.status.offline=Çevrimdışı runners.status.offline=Çevrimdışı
runners.version=Sürüm runners.version=Sürüm
runners.reset_registration_token=Kayıt tokenini sıfırla
runners.reset_registration_token_success=Çalıştırıcı kayıt belirteci başarıyla sıfırlandı runners.reset_registration_token_success=Çalıştırıcı kayıt belirteci başarıyla sıfırlandı
runs.all_workflows=Tüm İş Akışları runs.all_workflows=Tüm İş Akışları
runs.commit=İşle runs.commit=İşle
runs.scheduled=Zamanlanmış
runs.pushed_by=iten runs.pushed_by=iten
runs.invalid_workflow_helper=İş akışı yapılandırma dosyası geçersiz. Lütfen yapılandırma dosyanızı denetleyin: %s runs.invalid_workflow_helper=İş akışı yapılandırma dosyası geçersiz. Lütfen yapılandırma dosyanızı denetleyin: %s
runs.no_matching_online_runner_helper=Şu etiket ile eşleşen çevrimiçi çalıştırıcı bulunamadı: %s
runs.actor=Aktör runs.actor=Aktör
runs.status=Durum runs.status=Durum
runs.actors_no_select=Tüm aktörler runs.actors_no_select=Tüm aktörler
runs.status_no_select=Tüm durumlar runs.status_no_select=Tüm durumlar
runs.no_results=Eşleşen sonuç yok. runs.no_results=Eşleşen sonuç yok.
runs.no_workflows=Henüz hiç bir iş akışı yok.
runs.no_workflows.quick_start=Gitea İşlem'i nasıl başlatacağınızı bilmiyor musunuz? <a target="_blank" rel="noopener noreferrer" href="%s">Hızlı başlangıç rehberine</a> bakabilirsiniz.
runs.no_workflows.documentation=Gitea İşlem'i hakkında daha fazla bilgi için, <a target="_blank" rel="noopener noreferrer" href="%s">belgeye</a> bakabilirsiniz.
runs.no_runs=İş akışı henüz hiç çalıştırılmadı. runs.no_runs=İş akışı henüz hiç çalıştırılmadı.
runs.empty_commit_message=(boş işleme iletisi)
workflow.disable=İş Akışını Devre Dışı Bırak workflow.disable=İş Akışını Devre Dışı Bırak
workflow.disable_success='%s' iş akışı başarıyla devre dışı bırakıldı. workflow.disable_success='%s' iş akışı başarıyla devre dışı bırakıldı.
workflow.enable=İş Akışını Etkinleştir workflow.enable=İş Akışını Etkinleştir
workflow.enable_success='%s' iş akışı başarıyla etkinleştirildi. workflow.enable_success='%s' iş akışı başarıyla etkinleştirildi.
workflow.disabled=İş akışı devre dışı.
need_approval_desc=Değişiklik isteği çatalında iş akışı çalıştırmak için onay gerekiyor. need_approval_desc=Değişiklik isteği çatalında iş akışı çalıştırmak için onay gerekiyor.

1740
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -17,15 +17,20 @@
"@webcomponents/custom-elements": "1.6.0", "@webcomponents/custom-elements": "1.6.0",
"add-asset-webpack-plugin": "2.0.1", "add-asset-webpack-plugin": "2.0.1",
"ansi_up": "6.0.2", "ansi_up": "6.0.2",
"asciinema-player": "3.6.3", "asciinema-player": "3.6.4",
"chart.js": "4.4.1",
"chartjs-adapter-dayjs-4": "1.0.4",
"chartjs-plugin-zoom": "2.0.1",
"clippie": "4.0.6", "clippie": "4.0.6",
"css-loader": "6.10.0", "css-loader": "6.10.0",
"dayjs": "1.11.10",
"dropzone": "6.0.0-beta.2", "dropzone": "6.0.0-beta.2",
"easymde": "2.18.0", "easymde": "2.18.0",
"esbuild-loader": "4.0.3", "esbuild-loader": "4.0.3",
"escape-goat": "4.0.0", "escape-goat": "4.0.0",
"fast-glob": "3.3.2", "fast-glob": "3.3.2",
"htmx.org": "1.9.10", "htmx.org": "1.9.10",
"idiomorph": "0.3.0",
"jquery": "3.7.1", "jquery": "3.7.1",
"katex": "0.16.9", "katex": "0.16.9",
"license-checker-webpack-plugin": "0.2.1", "license-checker-webpack-plugin": "0.2.1",
@ -34,21 +39,22 @@
"minimatch": "9.0.3", "minimatch": "9.0.3",
"monaco-editor": "0.46.0", "monaco-editor": "0.46.0",
"monaco-editor-webpack-plugin": "7.1.0", "monaco-editor-webpack-plugin": "7.1.0",
"pdfobject": "2.2.12", "pdfobject": "2.3.0",
"pretty-ms": "9.0.0", "pretty-ms": "9.0.0",
"sortablejs": "1.15.2", "sortablejs": "1.15.2",
"swagger-ui-dist": "5.11.3", "swagger-ui-dist": "5.11.6",
"throttle-debounce": "5.0.0", "throttle-debounce": "5.0.0",
"tinycolor2": "1.6.0", "tinycolor2": "1.6.0",
"tippy.js": "6.3.7", "tippy.js": "6.3.7",
"toastify-js": "1.12.0", "toastify-js": "1.12.0",
"tributejs": "5.1.3", "tributejs": "5.1.3",
"uint8-to-base64": "0.2.0", "uint8-to-base64": "0.2.0",
"vue": "3.4.18", "vue": "3.4.19",
"vue-bar-graph": "2.0.0", "vue-bar-graph": "2.0.0",
"vue-chartjs": "5.3.0",
"vue-loader": "17.4.2", "vue-loader": "17.4.2",
"vue3-calendar-heatmap": "2.0.5", "vue3-calendar-heatmap": "2.0.5",
"webpack": "5.90.1", "webpack": "5.90.2",
"webpack-cli": "5.1.4", "webpack-cli": "5.1.4",
"wrap-ansi": "9.0.0" "wrap-ansi": "9.0.0"
}, },
@ -56,17 +62,18 @@
"@eslint-community/eslint-plugin-eslint-comments": "4.1.0", "@eslint-community/eslint-plugin-eslint-comments": "4.1.0",
"@playwright/test": "1.41.2", "@playwright/test": "1.41.2",
"@stoplight/spectral-cli": "6.11.0", "@stoplight/spectral-cli": "6.11.0",
"@stylistic/eslint-plugin-js": "1.6.1", "@stylistic/eslint-plugin-js": "1.6.2",
"@stylistic/stylelint-plugin": "2.0.0", "@stylistic/stylelint-plugin": "2.0.0",
"@vitejs/plugin-vue": "5.0.4", "@vitejs/plugin-vue": "5.0.4",
"eslint": "8.56.0", "eslint": "8.56.0",
"eslint-plugin-array-func": "4.0.0", "eslint-plugin-array-func": "4.0.0",
"eslint-plugin-github": "4.10.1",
"eslint-plugin-i": "2.29.1", "eslint-plugin-i": "2.29.1",
"eslint-plugin-jquery": "1.5.1", "eslint-plugin-jquery": "1.5.1",
"eslint-plugin-no-jquery": "2.7.0", "eslint-plugin-no-jquery": "2.7.0",
"eslint-plugin-no-use-extend-native": "0.5.0", "eslint-plugin-no-use-extend-native": "0.5.0",
"eslint-plugin-regexp": "2.2.0", "eslint-plugin-regexp": "2.2.0",
"eslint-plugin-sonarjs": "0.23.0", "eslint-plugin-sonarjs": "0.24.0",
"eslint-plugin-unicorn": "51.0.1", "eslint-plugin-unicorn": "51.0.1",
"eslint-plugin-vitest": "0.3.22", "eslint-plugin-vitest": "0.3.22",
"eslint-plugin-vitest-globals": "1.4.0", "eslint-plugin-vitest-globals": "1.4.0",

8
poetry.lock generated
View file

@ -342,13 +342,13 @@ telegram = ["requests"]
[[package]] [[package]]
name = "yamllint" name = "yamllint"
version = "1.34.0" version = "1.35.0"
description = "A linter for YAML files." description = "A linter for YAML files."
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "yamllint-1.34.0-py3-none-any.whl", hash = "sha256:33b813f6ff2ffad2e57a288281098392b85f7463ce1f3d5cd45aa848b916a806"}, {file = "yamllint-1.35.0-py3-none-any.whl", hash = "sha256:601b0adaaac6d9bacb16a2e612e7ee8d23caf941ceebf9bfe2cff0f196266004"},
{file = "yamllint-1.34.0.tar.gz", hash = "sha256:7f0a6a41e8aab3904878da4ae34b6248b6bc74634e0d3a90f0fb2d7e723a3d4f"}, {file = "yamllint-1.35.0.tar.gz", hash = "sha256:9bc99c3e9fe89b4c6ee26e17aa817cf2d14390de6577cb6e2e6ed5f72120c835"},
] ]
[package.dependencies] [package.dependencies]
@ -361,4 +361,4 @@ dev = ["doc8", "flake8", "flake8-import-order", "rstcheck[sphinx]", "sphinx"]
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = "^3.8" python-versions = "^3.8"
content-hash = "e4ea4301a70487379fce7008493d15c005af3aada7d88fbf0bd3167147ec6502" content-hash = "ba1c2c4235872f67354b5f52aa5bf0cd616354961530d9dc907f9fba28cc1ece"

View file

@ -9,7 +9,7 @@ python = "^3.8"
[tool.poetry.group.dev.dependencies] [tool.poetry.group.dev.dependencies]
djlint = "1.34.1" djlint = "1.34.1"
yamllint = "1.34.0" yamllint = "1.35.0"
[tool.djlint] [tool.djlint]
profile="golang" profile="golang"

View file

@ -63,6 +63,7 @@ package actions
import ( import (
"crypto/md5" "crypto/md5"
"errors"
"fmt" "fmt"
"net/http" "net/http"
"strconv" "strconv"
@ -426,7 +427,19 @@ func (ar artifactRoutes) getDownloadArtifactURL(ctx *ArtifactContext) {
var items []downloadArtifactResponseItem var items []downloadArtifactResponseItem
for _, artifact := range artifacts { for _, artifact := range artifacts {
downloadURL := ar.buildArtifactURL(runID, strconv.FormatInt(artifact.ID, 10), "download") var downloadURL string
if setting.Actions.ArtifactStorage.MinioConfig.ServeDirect {
u, err := ar.fs.URL(artifact.StoragePath, artifact.ArtifactName)
if err != nil && !errors.Is(err, storage.ErrURLNotSupported) {
log.Error("Error getting serve direct url: %v", err)
}
if u != nil {
downloadURL = u.String()
}
}
if downloadURL == "" {
downloadURL = ar.buildArtifactURL(runID, strconv.FormatInt(artifact.ID, 10), "download")
}
item := downloadArtifactResponseItem{ item := downloadArtifactResponseItem{
Path: util.PathJoinRel(itemPath, artifact.ArtifactPath), Path: util.PathJoinRel(itemPath, artifact.ArtifactPath),
ItemType: "file", ItemType: "file",

View file

@ -29,6 +29,7 @@ import (
const ( const (
tplDashboard base.TplName = "admin/dashboard" tplDashboard base.TplName = "admin/dashboard"
tplSystemStatus base.TplName = "admin/system_status"
tplSelfCheck base.TplName = "admin/self_check" tplSelfCheck base.TplName = "admin/self_check"
tplCron base.TplName = "admin/cron" tplCron base.TplName = "admin/cron"
tplQueue base.TplName = "admin/queue" tplQueue base.TplName = "admin/queue"
@ -72,7 +73,7 @@ var sysStatus struct {
// Garbage collector statistics. // Garbage collector statistics.
NextGC string // next run in HeapAlloc time (bytes) NextGC string // next run in HeapAlloc time (bytes)
LastGC string // last run in absolute time (ns) LastGCTime string // last run time
PauseTotalNs string PauseTotalNs string
PauseNs string // circular buffer of recent GC pause times, most recent at [(NumGC+255)%256] PauseNs string // circular buffer of recent GC pause times, most recent at [(NumGC+255)%256]
NumGC uint32 NumGC uint32
@ -110,7 +111,7 @@ func updateSystemStatus() {
sysStatus.OtherSys = base.FileSize(int64(m.OtherSys)) sysStatus.OtherSys = base.FileSize(int64(m.OtherSys))
sysStatus.NextGC = base.FileSize(int64(m.NextGC)) sysStatus.NextGC = base.FileSize(int64(m.NextGC))
sysStatus.LastGC = fmt.Sprintf("%.1fs", float64(time.Now().UnixNano()-int64(m.LastGC))/1000/1000/1000) sysStatus.LastGCTime = time.Unix(0, int64(m.LastGC)).Format(time.RFC3339)
sysStatus.PauseTotalNs = fmt.Sprintf("%.1fs", float64(m.PauseTotalNs)/1000/1000/1000) sysStatus.PauseTotalNs = fmt.Sprintf("%.1fs", float64(m.PauseTotalNs)/1000/1000/1000)
sysStatus.PauseNs = fmt.Sprintf("%.3fs", float64(m.PauseNs[(m.NumGC+255)%256])/1000/1000/1000) sysStatus.PauseNs = fmt.Sprintf("%.3fs", float64(m.PauseNs[(m.NumGC+255)%256])/1000/1000/1000)
sysStatus.NumGC = m.NumGC sysStatus.NumGC = m.NumGC
@ -132,7 +133,6 @@ func Dashboard(ctx *context.Context) {
ctx.Data["PageIsAdminDashboard"] = true ctx.Data["PageIsAdminDashboard"] = true
ctx.Data["NeedUpdate"] = updatechecker.GetNeedUpdate(ctx) ctx.Data["NeedUpdate"] = updatechecker.GetNeedUpdate(ctx)
ctx.Data["RemoteVersion"] = updatechecker.GetRemoteVersion(ctx) ctx.Data["RemoteVersion"] = updatechecker.GetRemoteVersion(ctx)
// FIXME: update periodically
updateSystemStatus() updateSystemStatus()
ctx.Data["SysStatus"] = sysStatus ctx.Data["SysStatus"] = sysStatus
ctx.Data["SSH"] = setting.SSH ctx.Data["SSH"] = setting.SSH
@ -140,6 +140,12 @@ func Dashboard(ctx *context.Context) {
ctx.HTML(http.StatusOK, tplDashboard) ctx.HTML(http.StatusOK, tplDashboard)
} }
func SystemStatus(ctx *context.Context) {
updateSystemStatus()
ctx.Data["SysStatus"] = sysStatus
ctx.HTML(http.StatusOK, tplSystemStatus)
}
// DashboardPost run an admin operation // DashboardPost run an admin operation
func DashboardPost(ctx *context.Context) { func DashboardPost(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.AdminDashboardForm) form := web.GetForm(ctx).(*forms.AdminDashboardForm)

View file

@ -22,6 +22,8 @@ func Activity(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.activity") ctx.Data["Title"] = ctx.Tr("repo.activity")
ctx.Data["PageIsActivity"] = true ctx.Data["PageIsActivity"] = true
ctx.Data["PageIsPulse"] = true
ctx.Data["Period"] = ctx.Params("period") ctx.Data["Period"] = ctx.Params("period")
timeUntil := time.Now() timeUntil := time.Now()

View file

@ -0,0 +1,44 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package repo
import (
"errors"
"net/http"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
contributors_service "code.gitea.io/gitea/services/repository"
)
const (
tplContributors base.TplName = "repo/activity"
)
// Contributors render the page to show repository contributors graph
func Contributors(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.contributors")
ctx.Data["PageIsActivity"] = true
ctx.Data["PageIsContributors"] = true
ctx.PageData["contributionType"] = "commits"
ctx.PageData["repoLink"] = ctx.Repo.RepoLink
ctx.HTML(http.StatusOK, tplContributors)
}
// ContributorsData renders JSON of contributors along with their weekly commit statistics
func ContributorsData(ctx *context.Context) {
if contributorStats, err := contributors_service.GetContributorStats(ctx, ctx.Cache, ctx.Repo.Repository, ctx.Repo.CommitID); err != nil {
if errors.Is(err, contributors_service.ErrAwaitGeneration) {
ctx.Status(http.StatusAccepted)
return
}
ctx.ServerError("GetContributorStats", err)
} else {
ctx.JSON(http.StatusOK, contributorStats)
}
}

View file

@ -151,6 +151,7 @@ func WebhooksNew(ctx *context.Context) {
} }
} }
ctx.Data["BaseLink"] = orCtx.LinkNew ctx.Data["BaseLink"] = orCtx.LinkNew
ctx.Data["BaseLinkNew"] = orCtx.LinkNew
ctx.HTML(http.StatusOK, orCtx.NewTemplate) ctx.HTML(http.StatusOK, orCtx.NewTemplate)
} }

View file

@ -31,6 +31,7 @@ import (
const ( const (
tplProfileBigAvatar base.TplName = "shared/user/profile_big_avatar" tplProfileBigAvatar base.TplName = "shared/user/profile_big_avatar"
tplFollowUnfollow base.TplName = "org/follow_unfollow"
) )
// OwnerProfile render profile page for a user or a organization (aka, repo owner) // OwnerProfile render profile page for a user or a organization (aka, repo owner)
@ -349,6 +350,15 @@ func Action(ctx *context.Context) {
return return
} }
if ctx.ContextUser.IsIndividual() {
shared_user.PrepareContextForProfileBigAvatar(ctx) shared_user.PrepareContextForProfileBigAvatar(ctx)
ctx.HTML(http.StatusOK, tplProfileBigAvatar) ctx.HTML(http.StatusOK, tplProfileBigAvatar)
return
} else if ctx.ContextUser.IsOrganization() {
ctx.Data["IsFollowing"] = ctx.Doer != nil && user_model.IsFollowing(ctx, ctx.Doer.ID, ctx.ContextUser.ID)
ctx.HTML(http.StatusOK, tplFollowUnfollow)
return
}
log.Error("Failed to apply action %q: unsupport context user type: %s", ctx.FormString("action"), ctx.ContextUser.Type)
ctx.Error(http.StatusBadRequest, fmt.Sprintf("Action %q failed", ctx.FormString("action")))
} }

View file

@ -681,6 +681,7 @@ func registerRoutes(m *web.Route) {
// ***** START: Admin ***** // ***** START: Admin *****
m.Group("/admin", func() { m.Group("/admin", func() {
m.Get("", admin.Dashboard) m.Get("", admin.Dashboard)
m.Get("/system_status", admin.SystemStatus)
m.Post("", web.Bind(forms.AdminDashboardForm{}), admin.DashboardPost) m.Post("", web.Bind(forms.AdminDashboardForm{}), admin.DashboardPost)
if setting.Database.Type.IsMySQL() || setting.Database.Type.IsMSSQL() { if setting.Database.Type.IsMySQL() || setting.Database.Type.IsMSSQL() {
@ -1431,6 +1432,10 @@ func registerRoutes(m *web.Route) {
m.Group("/activity", func() { m.Group("/activity", func() {
m.Get("", repo.Activity) m.Get("", repo.Activity)
m.Get("/{period}", repo.Activity) m.Get("/{period}", repo.Activity)
m.Group("/contributors", func() {
m.Get("", repo.Contributors)
m.Get("/data", repo.ContributorsData)
})
}, context.RepoRef(), repo.MustBeNotEmpty, context.RequireRepoReaderOr(unit.TypePullRequests, unit.TypeIssues, unit.TypeReleases)) }, context.RepoRef(), repo.MustBeNotEmpty, context.RequireRepoReaderOr(unit.TypePullRequests, unit.TypeIssues, unit.TypeReleases))
m.Group("/activity_author_data", func() { m.Group("/activity_author_data", func() {

View file

@ -342,7 +342,7 @@ func buildReleaseFiles(ctx context.Context, ownerID int64, repoVersion *packages
fmt.Fprintf(w, "Components: %s\n", strings.Join(components, " ")) fmt.Fprintf(w, "Components: %s\n", strings.Join(components, " "))
fmt.Fprintf(w, "Architectures: %s\n", strings.Join(architectures, " ")) fmt.Fprintf(w, "Architectures: %s\n", strings.Join(architectures, " "))
fmt.Fprintf(w, "Date: %s\n", time.Now().UTC().Format(time.RFC1123)) fmt.Fprintf(w, "Date: %s\n", time.Now().UTC().Format(time.RFC1123))
fmt.Fprint(w, "Acquire-By-Hash: yes") fmt.Fprint(w, "Acquire-By-Hash: yes\n")
pfds, err := packages_model.GetPackageFileDescriptors(ctx, pfs) pfds, err := packages_model.GetPackageFileDescriptors(ctx, pfs)
if err != nil { if err != nil {

View file

@ -0,0 +1,319 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package repository
import (
"bufio"
"context"
"errors"
"fmt"
"os"
"strconv"
"strings"
"sync"
"time"
"code.gitea.io/gitea/models/avatars"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/log"
api "code.gitea.io/gitea/modules/structs"
"gitea.com/go-chi/cache"
)
const (
contributorStatsCacheKey = "GetContributorStats/%s/%s"
contributorStatsCacheTimeout int64 = 60 * 10
)
var (
ErrAwaitGeneration = errors.New("generation took longer than ")
awaitGenerationTime = time.Second * 5
generateLock = sync.Map{}
)
type WeekData struct {
Week int64 `json:"week"` // Starting day of the week as Unix timestamp
Additions int `json:"additions"` // Number of additions in that week
Deletions int `json:"deletions"` // Number of deletions in that week
Commits int `json:"commits"` // Number of commits in that week
}
// ContributorData represents statistical git commit count data
type ContributorData struct {
Name string `json:"name"` // Display name of the contributor
Login string `json:"login"` // Login name of the contributor in case it exists
AvatarLink string `json:"avatar_link"`
HomeLink string `json:"home_link"`
TotalCommits int64 `json:"total_commits"`
Weeks map[int64]*WeekData `json:"weeks"`
}
// ExtendedCommitStats contains information for commit stats with author data
type ExtendedCommitStats struct {
Author *api.CommitUser `json:"author"`
Stats *api.CommitStats `json:"stats"`
}
const layout = time.DateOnly
func findLastSundayBeforeDate(dateStr string) (string, error) {
date, err := time.Parse(layout, dateStr)
if err != nil {
return "", err
}
weekday := date.Weekday()
daysToSubtract := int(weekday) - int(time.Sunday)
if daysToSubtract < 0 {
daysToSubtract += 7
}
lastSunday := date.AddDate(0, 0, -daysToSubtract)
return lastSunday.Format(layout), nil
}
// GetContributorStats returns contributors stats for git commits for given revision or default branch
func GetContributorStats(ctx context.Context, cache cache.Cache, repo *repo_model.Repository, revision string) (map[string]*ContributorData, error) {
// as GetContributorStats is resource intensive we cache the result
cacheKey := fmt.Sprintf(contributorStatsCacheKey, repo.FullName(), revision)
if !cache.IsExist(cacheKey) {
genReady := make(chan struct{})
// dont start multible async generations
_, run := generateLock.Load(cacheKey)
if run {
return nil, ErrAwaitGeneration
}
generateLock.Store(cacheKey, struct{}{})
// run generation async
go generateContributorStats(genReady, cache, cacheKey, repo, revision)
select {
case <-time.After(awaitGenerationTime):
return nil, ErrAwaitGeneration
case <-genReady:
// we got generation ready before timeout
break
}
}
// TODO: renew timeout of cache cache.UpdateTimeout(cacheKey, contributorStatsCacheTimeout)
switch v := cache.Get(cacheKey).(type) {
case error:
return nil, v
case map[string]*ContributorData:
return v, nil
default:
return nil, fmt.Errorf("unexpected type in cache detected")
}
}
// getExtendedCommitStats return the list of *ExtendedCommitStats for the given revision
func getExtendedCommitStats(repo *git.Repository, revision string /*, limit int */) ([]*ExtendedCommitStats, error) {
baseCommit, err := repo.GetCommit(revision)
if err != nil {
return nil, err
}
stdoutReader, stdoutWriter, err := os.Pipe()
if err != nil {
return nil, err
}
defer func() {
_ = stdoutReader.Close()
_ = stdoutWriter.Close()
}()
gitCmd := git.NewCommand(repo.Ctx, "log", "--shortstat", "--no-merges", "--pretty=format:---%n%aN%n%aE%n%as", "--reverse")
// AddOptionFormat("--max-count=%d", limit)
gitCmd.AddDynamicArguments(baseCommit.ID.String())
var extendedCommitStats []*ExtendedCommitStats
stderr := new(strings.Builder)
err = gitCmd.Run(&git.RunOpts{
Dir: repo.Path,
Stdout: stdoutWriter,
Stderr: stderr,
PipelineFunc: func(ctx context.Context, cancel context.CancelFunc) error {
_ = stdoutWriter.Close()
scanner := bufio.NewScanner(stdoutReader)
scanner.Split(bufio.ScanLines)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line != "---" {
continue
}
scanner.Scan()
authorName := strings.TrimSpace(scanner.Text())
scanner.Scan()
authorEmail := strings.TrimSpace(scanner.Text())
scanner.Scan()
date := strings.TrimSpace(scanner.Text())
scanner.Scan()
stats := strings.TrimSpace(scanner.Text())
if authorName == "" || authorEmail == "" || date == "" || stats == "" {
// FIXME: find a better way to parse the output so that we will handle this properly
log.Warn("Something is wrong with git log output, skipping...")
log.Warn("authorName: %s, authorEmail: %s, date: %s, stats: %s", authorName, authorEmail, date, stats)
continue
}
// 1 file changed, 1 insertion(+), 1 deletion(-)
fields := strings.Split(stats, ",")
commitStats := api.CommitStats{}
for _, field := range fields[1:] {
parts := strings.Split(strings.TrimSpace(field), " ")
value, contributionType := parts[0], parts[1]
amount, _ := strconv.Atoi(value)
if strings.HasPrefix(contributionType, "insertion") {
commitStats.Additions = amount
} else {
commitStats.Deletions = amount
}
}
commitStats.Total = commitStats.Additions + commitStats.Deletions
scanner.Scan()
scanner.Text() // empty line at the end
res := &ExtendedCommitStats{
Author: &api.CommitUser{
Identity: api.Identity{
Name: authorName,
Email: authorEmail,
},
Date: date,
},
Stats: &commitStats,
}
extendedCommitStats = append(extendedCommitStats, res)
}
_ = stdoutReader.Close()
return nil
},
})
if err != nil {
return nil, fmt.Errorf("Failed to get ContributorsCommitStats for repository.\nError: %w\nStderr: %s", err, stderr)
}
return extendedCommitStats, nil
}
func generateContributorStats(genDone chan struct{}, cache cache.Cache, cacheKey string, repo *repo_model.Repository, revision string) {
ctx := graceful.GetManager().HammerContext()
gitRepo, closer, err := gitrepo.RepositoryFromContextOrOpen(ctx, repo)
if err != nil {
err := fmt.Errorf("OpenRepository: %w", err)
_ = cache.Put(cacheKey, err, contributorStatsCacheTimeout)
return
}
defer closer.Close()
if len(revision) == 0 {
revision = repo.DefaultBranch
}
extendedCommitStats, err := getExtendedCommitStats(gitRepo, revision)
if err != nil {
err := fmt.Errorf("ExtendedCommitStats: %w", err)
_ = cache.Put(cacheKey, err, contributorStatsCacheTimeout)
return
}
if len(extendedCommitStats) == 0 {
err := fmt.Errorf("no commit stats returned for revision '%s'", revision)
_ = cache.Put(cacheKey, err, contributorStatsCacheTimeout)
return
}
layout := time.DateOnly
unknownUserAvatarLink := user_model.NewGhostUser().AvatarLinkWithSize(ctx, 0)
contributorsCommitStats := make(map[string]*ContributorData)
contributorsCommitStats["total"] = &ContributorData{
Name: "Total",
Weeks: make(map[int64]*WeekData),
}
total := contributorsCommitStats["total"]
for _, v := range extendedCommitStats {
userEmail := v.Author.Email
if len(userEmail) == 0 {
continue
}
u, _ := user_model.GetUserByEmail(ctx, userEmail)
if u != nil {
// update userEmail with user's primary email address so
// that different mail addresses will linked to same account
userEmail = u.GetEmail()
}
// duplicated logic
if _, ok := contributorsCommitStats[userEmail]; !ok {
if u == nil {
avatarLink := avatars.GenerateEmailAvatarFastLink(ctx, userEmail, 0)
if avatarLink == "" {
avatarLink = unknownUserAvatarLink
}
contributorsCommitStats[userEmail] = &ContributorData{
Name: v.Author.Name,
AvatarLink: avatarLink,
Weeks: make(map[int64]*WeekData),
}
} else {
contributorsCommitStats[userEmail] = &ContributorData{
Name: u.DisplayName(),
Login: u.LowerName,
AvatarLink: u.AvatarLinkWithSize(ctx, 0),
HomeLink: u.HomeLink(),
Weeks: make(map[int64]*WeekData),
}
}
}
// Update user statistics
user := contributorsCommitStats[userEmail]
startingOfWeek, _ := findLastSundayBeforeDate(v.Author.Date)
val, _ := time.Parse(layout, startingOfWeek)
week := val.UnixMilli()
if user.Weeks[week] == nil {
user.Weeks[week] = &WeekData{
Additions: 0,
Deletions: 0,
Commits: 0,
Week: week,
}
}
if total.Weeks[week] == nil {
total.Weeks[week] = &WeekData{
Additions: 0,
Deletions: 0,
Commits: 0,
Week: week,
}
}
user.Weeks[week].Additions += v.Stats.Additions
user.Weeks[week].Deletions += v.Stats.Deletions
user.Weeks[week].Commits++
user.TotalCommits++
// Update overall statistics
total.Weeks[week].Additions += v.Stats.Additions
total.Weeks[week].Deletions += v.Stats.Deletions
total.Weeks[week].Commits++
total.TotalCommits++
}
_ = cache.Put(cacheKey, contributorsCommitStats, contributorStatsCacheTimeout)
generateLock.Delete(cacheKey)
if genDone != nil {
genDone <- struct{}{}
}
}

View file

@ -0,0 +1,87 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package repository
import (
"slices"
"testing"
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/git"
"gitea.com/go-chi/cache"
"github.com/stretchr/testify/assert"
)
func TestRepository_ContributorsGraph(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
assert.NoError(t, repo.LoadOwner(db.DefaultContext))
mockCache, err := cache.NewCacher(cache.Options{
Adapter: "memory",
Interval: 24 * 60,
})
assert.NoError(t, err)
generateContributorStats(nil, mockCache, "key", repo, "404ref")
err, isErr := mockCache.Get("key").(error)
assert.True(t, isErr)
assert.ErrorAs(t, err, &git.ErrNotExist{})
generateContributorStats(nil, mockCache, "key2", repo, "master")
data, isData := mockCache.Get("key2").(map[string]*ContributorData)
assert.True(t, isData)
var keys []string
for k := range data {
keys = append(keys, k)
}
slices.Sort(keys)
assert.EqualValues(t, []string{
"ethantkoenig@gmail.com",
"jimmy.praet@telenet.be",
"jon@allspice.io",
"total", // generated summary
}, keys)
assert.EqualValues(t, &ContributorData{
Name: "Ethan Koenig",
AvatarLink: "https://secure.gravatar.com/avatar/b42fb195faa8c61b8d88abfefe30e9e3?d=identicon",
TotalCommits: 1,
Weeks: map[int64]*WeekData{
1511654400000: {
Week: 1511654400000, // sunday 2017-11-26
Additions: 3,
Deletions: 0,
Commits: 1,
},
},
}, data["ethantkoenig@gmail.com"])
assert.EqualValues(t, &ContributorData{
Name: "Total",
AvatarLink: "",
TotalCommits: 3,
Weeks: map[int64]*WeekData{
1511654400000: {
Week: 1511654400000, // sunday 2017-11-26 (2017-11-26 20:31:18 -0800)
Additions: 3,
Deletions: 0,
Commits: 1,
},
1607817600000: {
Week: 1607817600000, // sunday 2020-12-13 (2020-12-15 15:23:11 -0500)
Additions: 10,
Deletions: 0,
Commits: 1,
},
1624752000000: {
Week: 1624752000000, // sunday 2021-06-27 (2021-06-29 21:54:09 +0200)
Additions: 2,
Deletions: 0,
Commits: 1,
},
},
}, data["total"])
}

View file

@ -75,69 +75,9 @@
<h4 class="ui top attached header"> <h4 class="ui top attached header">
{{ctx.Locale.Tr "admin.dashboard.system_status"}} {{ctx.Locale.Tr "admin.dashboard.system_status"}}
</h4> </h4>
<div class="ui attached table segment"> {{/* TODO: make these stats work in multi-server deployments, likely needs per-server stats in DB */}}
<dl class="admin-dl-horizontal"> <div hx-get="{{$.Link}}/system_status" hx-swap="morph:innerHTML" hx-trigger="every 5s" hx-indicator=".divider" class="ui attached table segment">
<dt>{{ctx.Locale.Tr "admin.dashboard.server_uptime"}}</dt> {{template "admin/system_status" .}}
<dd><relative-time format="duration" datetime="{{.SysStatus.StartTime}}">{{.SysStatus.StartTime}}</relative-time></dd>
<dt>{{ctx.Locale.Tr "admin.dashboard.current_goroutine"}}</dt>
<dd>{{.SysStatus.NumGoroutine}}</dd>
<div class="divider"></div>
<dt>{{ctx.Locale.Tr "admin.dashboard.current_memory_usage"}}</dt>
<dd>{{.SysStatus.MemAllocated}}</dd>
<dt>{{ctx.Locale.Tr "admin.dashboard.total_memory_allocated"}}</dt>
<dd>{{.SysStatus.MemTotal}}</dd>
<dt>{{ctx.Locale.Tr "admin.dashboard.memory_obtained"}}</dt>
<dd>{{.SysStatus.MemSys}}</dd>
<dt>{{ctx.Locale.Tr "admin.dashboard.pointer_lookup_times"}}</dt>
<dd>{{.SysStatus.Lookups}}</dd>
<dt>{{ctx.Locale.Tr "admin.dashboard.memory_allocate_times"}}</dt>
<dd>{{.SysStatus.MemMallocs}}</dd>
<dt>{{ctx.Locale.Tr "admin.dashboard.memory_free_times"}}</dt>
<dd>{{.SysStatus.MemFrees}}</dd>
<div class="divider"></div>
<dt>{{ctx.Locale.Tr "admin.dashboard.current_heap_usage"}}</dt>
<dd>{{.SysStatus.HeapAlloc}}</dd>
<dt>{{ctx.Locale.Tr "admin.dashboard.heap_memory_obtained"}}</dt>
<dd>{{.SysStatus.HeapSys}}</dd>
<dt>{{ctx.Locale.Tr "admin.dashboard.heap_memory_idle"}}</dt>
<dd>{{.SysStatus.HeapIdle}}</dd>
<dt>{{ctx.Locale.Tr "admin.dashboard.heap_memory_in_use"}}</dt>
<dd>{{.SysStatus.HeapInuse}}</dd>
<dt>{{ctx.Locale.Tr "admin.dashboard.heap_memory_released"}}</dt>
<dd>{{.SysStatus.HeapReleased}}</dd>
<dt>{{ctx.Locale.Tr "admin.dashboard.heap_objects"}}</dt>
<dd>{{.SysStatus.HeapObjects}}</dd>
<div class="divider"></div>
<dt>{{ctx.Locale.Tr "admin.dashboard.bootstrap_stack_usage"}}</dt>
<dd>{{.SysStatus.StackInuse}}</dd>
<dt>{{ctx.Locale.Tr "admin.dashboard.stack_memory_obtained"}}</dt>
<dd>{{.SysStatus.StackSys}}</dd>
<dt>{{ctx.Locale.Tr "admin.dashboard.mspan_structures_usage"}}</dt>
<dd>{{.SysStatus.MSpanInuse}}</dd>
<dt>{{ctx.Locale.Tr "admin.dashboard.mspan_structures_obtained"}}</dt>
<dd>{{.SysStatus.MSpanSys}}</dd>
<dt>{{ctx.Locale.Tr "admin.dashboard.mcache_structures_usage"}}</dt>
<dd>{{.SysStatus.MCacheInuse}}</dd>
<dt>{{ctx.Locale.Tr "admin.dashboard.mcache_structures_obtained"}}</dt>
<dd>{{.SysStatus.MCacheSys}}</dd>
<dt>{{ctx.Locale.Tr "admin.dashboard.profiling_bucket_hash_table_obtained"}}</dt>
<dd>{{.SysStatus.BuckHashSys}}</dd>
<dt>{{ctx.Locale.Tr "admin.dashboard.gc_metadata_obtained"}}</dt>
<dd>{{.SysStatus.GCSys}}</dd>
<dt>{{ctx.Locale.Tr "admin.dashboard.other_system_allocation_obtained"}}</dt>
<dd>{{.SysStatus.OtherSys}}</dd>
<div class="divider"></div>
<dt>{{ctx.Locale.Tr "admin.dashboard.next_gc_recycle"}}</dt>
<dd>{{.SysStatus.NextGC}}</dd>
<dt>{{ctx.Locale.Tr "admin.dashboard.last_gc_time"}}</dt>
<dd>{{.SysStatus.LastGC}}</dd>
<dt>{{ctx.Locale.Tr "admin.dashboard.total_gc_pause"}}</dt>
<dd>{{.SysStatus.PauseTotalNs}}</dd>
<dt>{{ctx.Locale.Tr "admin.dashboard.last_gc_pause"}}</dt>
<dd>{{.SysStatus.PauseNs}}</dd>
<dt>{{ctx.Locale.Tr "admin.dashboard.gc_times"}}</dt>
<dd>{{.SysStatus.NumGC}}</dd>
</dl>
</div> </div>
</div> </div>
{{template "admin/layout_footer" .}} {{template "admin/layout_footer" .}}

View file

@ -0,0 +1,62 @@
<dl class="admin-dl-horizontal">
<dt>{{ctx.Locale.Tr "admin.dashboard.server_uptime"}}</dt>
<dd><relative-time format="duration" datetime="{{.SysStatus.StartTime}}">{{.SysStatus.StartTime}}</relative-time></dd>
<dt>{{ctx.Locale.Tr "admin.dashboard.current_goroutine"}}</dt>
<dd>{{.SysStatus.NumGoroutine}}</dd>
<div class="divider"></div>
<dt>{{ctx.Locale.Tr "admin.dashboard.current_memory_usage"}}</dt>
<dd>{{.SysStatus.MemAllocated}}</dd>
<dt>{{ctx.Locale.Tr "admin.dashboard.total_memory_allocated"}}</dt>
<dd>{{.SysStatus.MemTotal}}</dd>
<dt>{{ctx.Locale.Tr "admin.dashboard.memory_obtained"}}</dt>
<dd>{{.SysStatus.MemSys}}</dd>
<dt>{{ctx.Locale.Tr "admin.dashboard.pointer_lookup_times"}}</dt>
<dd>{{.SysStatus.Lookups}}</dd>
<dt>{{ctx.Locale.Tr "admin.dashboard.memory_allocate_times"}}</dt>
<dd>{{.SysStatus.MemMallocs}}</dd>
<dt>{{ctx.Locale.Tr "admin.dashboard.memory_free_times"}}</dt>
<dd>{{.SysStatus.MemFrees}}</dd>
<div class="divider"></div>
<dt>{{ctx.Locale.Tr "admin.dashboard.current_heap_usage"}}</dt>
<dd>{{.SysStatus.HeapAlloc}}</dd>
<dt>{{ctx.Locale.Tr "admin.dashboard.heap_memory_obtained"}}</dt>
<dd>{{.SysStatus.HeapSys}}</dd>
<dt>{{ctx.Locale.Tr "admin.dashboard.heap_memory_idle"}}</dt>
<dd>{{.SysStatus.HeapIdle}}</dd>
<dt>{{ctx.Locale.Tr "admin.dashboard.heap_memory_in_use"}}</dt>
<dd>{{.SysStatus.HeapInuse}}</dd>
<dt>{{ctx.Locale.Tr "admin.dashboard.heap_memory_released"}}</dt>
<dd>{{.SysStatus.HeapReleased}}</dd>
<dt>{{ctx.Locale.Tr "admin.dashboard.heap_objects"}}</dt>
<dd>{{.SysStatus.HeapObjects}}</dd>
<div class="divider"></div>
<dt>{{ctx.Locale.Tr "admin.dashboard.bootstrap_stack_usage"}}</dt>
<dd>{{.SysStatus.StackInuse}}</dd>
<dt>{{ctx.Locale.Tr "admin.dashboard.stack_memory_obtained"}}</dt>
<dd>{{.SysStatus.StackSys}}</dd>
<dt>{{ctx.Locale.Tr "admin.dashboard.mspan_structures_usage"}}</dt>
<dd>{{.SysStatus.MSpanInuse}}</dd>
<dt>{{ctx.Locale.Tr "admin.dashboard.mspan_structures_obtained"}}</dt>
<dd>{{.SysStatus.MSpanSys}}</dd>
<dt>{{ctx.Locale.Tr "admin.dashboard.mcache_structures_usage"}}</dt>
<dd>{{.SysStatus.MCacheInuse}}</dd>
<dt>{{ctx.Locale.Tr "admin.dashboard.mcache_structures_obtained"}}</dt>
<dd>{{.SysStatus.MCacheSys}}</dd>
<dt>{{ctx.Locale.Tr "admin.dashboard.profiling_bucket_hash_table_obtained"}}</dt>
<dd>{{.SysStatus.BuckHashSys}}</dd>
<dt>{{ctx.Locale.Tr "admin.dashboard.gc_metadata_obtained"}}</dt>
<dd>{{.SysStatus.GCSys}}</dd>
<dt>{{ctx.Locale.Tr "admin.dashboard.other_system_allocation_obtained"}}</dt>
<dd>{{.SysStatus.OtherSys}}</dd>
<div class="divider"></div>
<dt>{{ctx.Locale.Tr "admin.dashboard.next_gc_recycle"}}</dt>
<dd>{{.SysStatus.NextGC}}</dd>
<dt>{{ctx.Locale.Tr "admin.dashboard.last_gc_time"}}</dt>
<dd><relative-time format="duration" datetime="{{.SysStatus.LastGCTime}}">{{.SysStatus.LastGCTime}}</relative-time></dd>
<dt>{{ctx.Locale.Tr "admin.dashboard.total_gc_pause"}}</dt>
<dd>{{.SysStatus.PauseTotalNs}}</dd>
<dt>{{ctx.Locale.Tr "admin.dashboard.last_gc_pause"}}</dt>
<dd>{{.SysStatus.PauseNs}}</dd>
<dt>{{ctx.Locale.Tr "admin.dashboard.gc_times"}}</dt>
<dd>{{.SysStatus.NumGC}}</dd>
</dl>

View file

@ -30,7 +30,7 @@
{{template "base/head_style" .}} {{template "base/head_style" .}}
{{template "custom/header" .}} {{template "custom/header" .}}
</head> </head>
<body hx-headers='{"x-csrf-token": "{{.CsrfToken}}"}' hx-swap="outerHTML" hx-push-url="false"> <body hx-headers='{"x-csrf-token": "{{.CsrfToken}}"}' hx-swap="outerHTML" hx-ext="morph" hx-push-url="false">
{{ctx.DataRaceCheck $.Context}} {{ctx.DataRaceCheck $.Context}}
{{template "custom/body_outer_pre" .}} {{template "custom/body_outer_pre" .}}

View file

@ -0,0 +1,7 @@
<button class="ui basic button gt-mr-0" hx-post="{{.Org.HomeLink}}?action={{if $.IsFollowing}}unfollow{{else}}follow{{end}}">
{{if $.IsFollowing}}
{{ctx.Locale.Tr "user.unfollow"}}
{{else}}
{{ctx.Locale.Tr "user.follow"}}
{{end}}
</button>

View file

@ -30,13 +30,9 @@
{{svg "octicon-rss" 24}} {{svg "octicon-rss" 24}}
</a> </a>
{{end}} {{end}}
<button class="link-action ui basic button gt-mr-0" data-url="{{.Org.HomeLink}}?action={{if $.IsFollowing}}unfollow{{else}}follow{{end}}"> {{if .IsSigned}}
{{if $.IsFollowing}} {{template "org/follow_unfollow" .}}
{{ctx.Locale.Tr "user.unfollow"}}
{{else}}
{{ctx.Locale.Tr "user.follow"}}
{{end}} {{end}}
</button>
</div> </div>
</div> </div>

View file

@ -1,235 +1,15 @@
{{template "base/head" .}} {{template "base/head" .}}
<div role="main" aria-label="{{.Title}}" class="page-content repository commits"> <div role="main" aria-label="{{.Title}}" class="page-content repository commits">
{{template "repo/header" .}} {{template "repo/header" .}}
<div class="ui container"> <div class="ui container flex-container">
<h2 class="ui header activity-header"> <div class="flex-container-nav">
<span>{{DateTime "long" .DateFrom}} - {{DateTime "long" .DateUntil}}</span> {{template "repo/navbar" .}}
<!-- Period -->
<div class="ui floating dropdown jump filter">
<div class="ui basic compact button">
{{ctx.Locale.Tr "repo.activity.period.filter_label"}} <strong>{{.PeriodText}}</strong>
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
</div> </div>
<div class="menu"> <div class="flex-container-main">
<a class="{{if eq .Period "daily"}}active {{end}}item" href="{{$.RepoLink}}/activity/daily">{{ctx.Locale.Tr "repo.activity.period.daily"}}</a> {{if .PageIsPulse}}{{template "repo/pulse" .}}{{end}}
<a class="{{if eq .Period "halfweekly"}}active {{end}}item" href="{{$.RepoLink}}/activity/halfweekly">{{ctx.Locale.Tr "repo.activity.period.halfweekly"}}</a> {{if .PageIsContributors}}{{template "repo/contributors" .}}{{end}}
<a class="{{if eq .Period "weekly"}}active {{end}}item" href="{{$.RepoLink}}/activity/weekly">{{ctx.Locale.Tr "repo.activity.period.weekly"}}</a>
<a class="{{if eq .Period "monthly"}}active {{end}}item" href="{{$.RepoLink}}/activity/monthly">{{ctx.Locale.Tr "repo.activity.period.monthly"}}</a>
<a class="{{if eq .Period "quarterly"}}active {{end}}item" href="{{$.RepoLink}}/activity/quarterly">{{ctx.Locale.Tr "repo.activity.period.quarterly"}}</a>
<a class="{{if eq .Period "semiyearly"}}active {{end}}item" href="{{$.RepoLink}}/activity/semiyearly">{{ctx.Locale.Tr "repo.activity.period.semiyearly"}}</a>
<a class="{{if eq .Period "yearly"}}active {{end}}item" href="{{$.RepoLink}}/activity/yearly">{{ctx.Locale.Tr "repo.activity.period.yearly"}}</a>
</div> </div>
</div> </div>
</h2>
<div class="divider"></div>
{{if (or (.Permission.CanRead $.UnitTypeIssues) (.Permission.CanRead $.UnitTypePullRequests))}}
<h4 class="ui top attached header">{{ctx.Locale.Tr "repo.activity.overview"}}</h4>
<div class="ui attached segment two column grid">
{{if .Permission.CanRead $.UnitTypePullRequests}}
<div class="column">
{{if gt .Activity.ActivePRCount 0}}
<div class="stats-table">
<a href="#merged-pull-requests" class="table-cell tiny background purple" style="width: {{.Activity.MergedPRPerc}}{{if ne .Activity.MergedPRPerc 0}}%{{end}}"></a>
<a href="#proposed-pull-requests" class="table-cell tiny background green"></a>
</div>
{{else}}
<div class="stats-table">
<a class="table-cell tiny background light grey"></a>
</div>
{{end}}
{{ctx.Locale.TrN .Activity.ActivePRCount "repo.activity.active_prs_count_1" "repo.activity.active_prs_count_n" .Activity.ActivePRCount | Safe}}
</div>
{{end}}
{{if .Permission.CanRead $.UnitTypeIssues}}
<div class="column">
{{if gt .Activity.ActiveIssueCount 0}}
<div class="stats-table">
<a href="#closed-issues" class="table-cell tiny background red" style="width: {{.Activity.ClosedIssuePerc}}{{if ne .Activity.ClosedIssuePerc 0}}%{{end}}"></a>
<a href="#new-issues" class="table-cell tiny background green"></a>
</div>
{{else}}
<div class="stats-table">
<a class="table-cell tiny background light grey"></a>
</div>
{{end}}
{{ctx.Locale.TrN .Activity.ActiveIssueCount "repo.activity.active_issues_count_1" "repo.activity.active_issues_count_n" .Activity.ActiveIssueCount | Safe}}
</div>
{{end}}
</div>
<div class="ui attached segment horizontal segments">
{{if .Permission.CanRead $.UnitTypePullRequests}}
<a href="#merged-pull-requests" class="ui attached segment text center">
<span class="text purple">{{svg "octicon-git-pull-request"}}</span> <strong>{{.Activity.MergedPRCount}}</strong><br>
{{ctx.Locale.TrN .Activity.MergedPRCount "repo.activity.merged_prs_count_1" "repo.activity.merged_prs_count_n"}}
</a>
<a href="#proposed-pull-requests" class="ui attached segment text center">
<span class="text green">{{svg "octicon-git-branch"}}</span> <strong>{{.Activity.OpenedPRCount}}</strong><br>
{{ctx.Locale.TrN .Activity.OpenedPRCount "repo.activity.opened_prs_count_1" "repo.activity.opened_prs_count_n"}}
</a>
{{end}}
{{if .Permission.CanRead $.UnitTypeIssues}}
<a href="#closed-issues" class="ui attached segment text center">
<span class="text red">{{svg "octicon-issue-closed"}}</span> <strong>{{.Activity.ClosedIssueCount}}</strong><br>
{{ctx.Locale.TrN .Activity.ClosedIssueCount "repo.activity.closed_issues_count_1" "repo.activity.closed_issues_count_n"}}
</a>
<a href="#new-issues" class="ui attached segment text center">
<span class="text green">{{svg "octicon-issue-opened"}}</span> <strong>{{.Activity.OpenedIssueCount}}</strong><br>
{{ctx.Locale.TrN .Activity.OpenedIssueCount "repo.activity.new_issues_count_1" "repo.activity.new_issues_count_n"}}
</a>
{{end}}
</div>
{{end}}
{{if .Permission.CanRead $.UnitTypeCode}}
{{if eq .Activity.Code.CommitCountInAllBranches 0}}
<div class="ui center aligned segment">
<h4 class="ui header">{{ctx.Locale.Tr "repo.activity.no_git_activity"}}</h4>
</div>
{{end}}
{{if gt .Activity.Code.CommitCountInAllBranches 0}}
<div class="ui attached segment horizontal segments">
<div class="ui attached segment text">
{{ctx.Locale.Tr "repo.activity.git_stats_exclude_merges"}}
<strong>{{ctx.Locale.TrN .Activity.Code.AuthorCount "repo.activity.git_stats_author_1" "repo.activity.git_stats_author_n" .Activity.Code.AuthorCount}}</strong>
{{ctx.Locale.TrN .Activity.Code.AuthorCount "repo.activity.git_stats_pushed_1" "repo.activity.git_stats_pushed_n"}}
<strong>{{ctx.Locale.TrN .Activity.Code.CommitCount "repo.activity.git_stats_commit_1" "repo.activity.git_stats_commit_n" .Activity.Code.CommitCount}}</strong>
{{ctx.Locale.Tr "repo.activity.git_stats_push_to_branch" .Repository.DefaultBranch}}
<strong>{{ctx.Locale.TrN .Activity.Code.CommitCountInAllBranches "repo.activity.git_stats_commit_1" "repo.activity.git_stats_commit_n" .Activity.Code.CommitCountInAllBranches}}</strong>
{{ctx.Locale.Tr "repo.activity.git_stats_push_to_all_branches"}}
{{ctx.Locale.Tr "repo.activity.git_stats_on_default_branch" .Repository.DefaultBranch}}
<strong>{{ctx.Locale.TrN .Activity.Code.ChangedFiles "repo.activity.git_stats_file_1" "repo.activity.git_stats_file_n" .Activity.Code.ChangedFiles}}</strong>
{{ctx.Locale.TrN .Activity.Code.ChangedFiles "repo.activity.git_stats_files_changed_1" "repo.activity.git_stats_files_changed_n"}}
{{ctx.Locale.Tr "repo.activity.git_stats_additions"}}
<strong class="text green">{{ctx.Locale.TrN .Activity.Code.Additions "repo.activity.git_stats_addition_1" "repo.activity.git_stats_addition_n" .Activity.Code.Additions}}</strong>
{{ctx.Locale.Tr "repo.activity.git_stats_and_deletions"}}
<strong class="text red">{{ctx.Locale.TrN .Activity.Code.Deletions "repo.activity.git_stats_deletion_1" "repo.activity.git_stats_deletion_n" .Activity.Code.Deletions}}</strong>.
</div>
<div class="ui attached segment">
<div id="repo-activity-top-authors-chart"></div>
</div>
</div>
{{end}}
{{end}}
{{if gt .Activity.PublishedReleaseCount 0}}
<h4 class="divider divider-text gt-normal-case" id="published-releases">
{{svg "octicon-tag" 16 "gt-mr-3"}}
{{ctx.Locale.Tr "repo.activity.title.releases_published_by"
(ctx.Locale.TrN .Activity.PublishedReleaseCount "repo.activity.title.releases_1" "repo.activity.title.releases_n" .Activity.PublishedReleaseCount)
(ctx.Locale.TrN .Activity.PublishedReleaseAuthorCount "repo.activity.title.user_1" "repo.activity.title.user_n" .Activity.PublishedReleaseAuthorCount)
}}
</h4>
<div class="list">
{{range .Activity.PublishedReleases}}
<p class="desc">
<span class="ui green label">{{ctx.Locale.Tr "repo.activity.published_release_label"}}</span>
{{.TagName}}
{{if not .IsTag}}
<a class="title" href="{{$.RepoLink}}/src/{{.TagName | PathEscapeSegments}}">{{.Title | RenderEmoji $.Context | RenderCodeBlock}}</a>
{{end}}
{{TimeSinceUnix .CreatedUnix ctx.Locale}}
</p>
{{end}}
</div>
{{end}}
{{if gt .Activity.MergedPRCount 0}}
<h4 class="divider divider-text gt-normal-case" id="merged-pull-requests">
{{svg "octicon-git-pull-request" 16 "gt-mr-3"}}
{{ctx.Locale.Tr "repo.activity.title.prs_merged_by"
(ctx.Locale.TrN .Activity.MergedPRCount "repo.activity.title.prs_1" "repo.activity.title.prs_n" .Activity.MergedPRCount)
(ctx.Locale.TrN .Activity.MergedPRAuthorCount "repo.activity.title.user_1" "repo.activity.title.user_n" .Activity.MergedPRAuthorCount)
}}
</h4>
<div class="list">
{{range .Activity.MergedPRs}}
<p class="desc">
<span class="ui purple label">{{ctx.Locale.Tr "repo.activity.merged_prs_label"}}</span>
#{{.Index}} <a class="title" href="{{$.RepoLink}}/pulls/{{.Index}}">{{.Issue.Title | RenderEmoji $.Context | RenderCodeBlock}}</a>
{{TimeSinceUnix .MergedUnix ctx.Locale}}
</p>
{{end}}
</div>
{{end}}
{{if gt .Activity.OpenedPRCount 0}}
<h4 class="divider divider-text gt-normal-case" id="proposed-pull-requests">
{{svg "octicon-git-branch" 16 "gt-mr-3"}}
{{ctx.Locale.Tr "repo.activity.title.prs_opened_by"
(ctx.Locale.TrN .Activity.OpenedPRCount "repo.activity.title.prs_1" "repo.activity.title.prs_n" .Activity.OpenedPRCount)
(ctx.Locale.TrN .Activity.OpenedPRAuthorCount "repo.activity.title.user_1" "repo.activity.title.user_n" .Activity.OpenedPRAuthorCount)
}}
</h4>
<div class="list">
{{range .Activity.OpenedPRs}}
<p class="desc">
<span class="ui green label">{{ctx.Locale.Tr "repo.activity.opened_prs_label"}}</span>
#{{.Index}} <a class="title" href="{{$.RepoLink}}/pulls/{{.Index}}">{{.Issue.Title | RenderEmoji $.Context | RenderCodeBlock}}</a>
{{TimeSinceUnix .Issue.CreatedUnix ctx.Locale}}
</p>
{{end}}
</div>
{{end}}
{{if gt .Activity.ClosedIssueCount 0}}
<h4 class="divider divider-text gt-normal-case" id="closed-issues">
{{svg "octicon-issue-closed" 16 "gt-mr-3"}}
{{ctx.Locale.Tr "repo.activity.title.issues_closed_from"
(ctx.Locale.TrN .Activity.ClosedIssueCount "repo.activity.title.issues_1" "repo.activity.title.issues_n" .Activity.ClosedIssueCount)
(ctx.Locale.TrN .Activity.ClosedIssueAuthorCount "repo.activity.title.user_1" "repo.activity.title.user_n" .Activity.ClosedIssueAuthorCount)
}}
</h4>
<div class="list">
{{range .Activity.ClosedIssues}}
<p class="desc">
<span class="ui red label">{{ctx.Locale.Tr "repo.activity.closed_issue_label"}}</span>
#{{.Index}} <a class="title" href="{{$.RepoLink}}/issues/{{.Index}}">{{.Title | RenderEmoji $.Context | RenderCodeBlock}}</a>
{{TimeSinceUnix .ClosedUnix ctx.Locale}}
</p>
{{end}}
</div>
{{end}}
{{if gt .Activity.OpenedIssueCount 0}}
<h4 class="divider divider-text gt-normal-case" id="new-issues">
{{svg "octicon-issue-opened" 16 "gt-mr-3"}}
{{ctx.Locale.Tr "repo.activity.title.issues_created_by"
(ctx.Locale.TrN .Activity.OpenedIssueCount "repo.activity.title.issues_1" "repo.activity.title.issues_n" .Activity.OpenedIssueCount)
(ctx.Locale.TrN .Activity.OpenedIssueAuthorCount "repo.activity.title.user_1" "repo.activity.title.user_n" .Activity.OpenedIssueAuthorCount)
}}
</h4>
<div class="list">
{{range .Activity.OpenedIssues}}
<p class="desc">
<span class="ui green label">{{ctx.Locale.Tr "repo.activity.new_issue_label"}}</span>
#{{.Index}} <a class="title" href="{{$.RepoLink}}/issues/{{.Index}}">{{.Title | RenderEmoji $.Context | RenderCodeBlock}}</a>
{{TimeSinceUnix .CreatedUnix ctx.Locale}}
</p>
{{end}}
</div>
{{end}}
{{if gt .Activity.UnresolvedIssueCount 0}}
<h4 class="divider divider-text gt-normal-case" id="unresolved-conversations" data-tooltip-content="{{ctx.Locale.Tr "repo.activity.unresolved_conv_desc"}}">
{{svg "octicon-comment-discussion" 16 "gt-mr-3"}}
{{ctx.Locale.TrN .Activity.UnresolvedIssueCount "repo.activity.title.unresolved_conv_1" "repo.activity.title.unresolved_conv_n" .Activity.UnresolvedIssueCount}}
</h4>
<div class="list">
{{range .Activity.UnresolvedIssues}}
<p class="desc">
<span class="ui green label">{{ctx.Locale.Tr "repo.activity.unresolved_conv_label"}}</span>
#{{.Index}}
{{if .IsPull}}
<a class="title" href="{{$.RepoLink}}/pulls/{{.Index}}">{{.Title | RenderEmoji $.Context | RenderCodeBlock}}</a>
{{else}}
<a class="title" href="{{$.RepoLink}}/issues/{{.Index}}">{{.Title | RenderEmoji $.Context | RenderCodeBlock}}</a>
{{end}}
{{TimeSinceUnix .UpdatedUnix ctx.Locale}}
</p>
{{end}}
</div>
{{end}}
</div>
</div> </div>
{{template "base/footer" .}} {{template "base/footer" .}}

View file

@ -0,0 +1,13 @@
{{if .Permission.CanRead $.UnitTypeCode}}
<div id="repo-contributors-chart"
data-locale-filter-label="{{ctx.Locale.Tr "repo.contributors.contribution_type.filter_label"}}"
data-locale-contribution-type-commits="{{ctx.Locale.Tr "repo.contributors.contribution_type.commits"}}"
data-locale-contribution-type-additions="{{ctx.Locale.Tr "repo.contributors.contribution_type.additions"}}"
data-locale-contribution-type-deletions="{{ctx.Locale.Tr "repo.contributors.contribution_type.deletions"}}"
data-locale-loading-title="{{ctx.Locale.Tr "repo.contributors.loading_title"}}"
data-locale-loading-title-failed="{{ctx.Locale.Tr "repo.contributors.loading_title_failed"}}"
data-locale-loading-info="{{ctx.Locale.Tr "repo.contributors.loading_info"}}"
data-locale-component-failed-to-load="{{ctx.Locale.Tr "repo.contributors.component_failed_to_load"}}"
>
</div>
{{end}}

View file

@ -5,9 +5,9 @@
<div class="flex-item gt-ac"> <div class="flex-item gt-ac">
<div class="flex-item-leading">{{template "repo/icon" .}}</div> <div class="flex-item-leading">{{template "repo/icon" .}}</div>
<div class="flex-item-main"> <div class="flex-item-main">
<div class="flex-item-title"> <div class="flex-item-title gt-font-18">
<a class="text light thin" href="{{.Owner.HomeLink}}">{{.Owner.Name}}</a>/ <a class="muted gt-font-normal" href="{{.Owner.HomeLink}}">{{.Owner.Name}}</a>/
<a class="text primary" href="{{$.RepoLink}}">{{.Name}}</a></div> <a class="muted" href="{{$.RepoLink}}">{{.Name}}</a></div>
</div> </div>
<div class="flex-item-trailing"> <div class="flex-item-trailing">
{{if .IsArchived}} {{if .IsArchived}}

View file

@ -1,10 +1,10 @@
{{$avatarLink := (.RelAvatarLink ctx)}} {{$avatarLink := (.RelAvatarLink ctx)}}
{{if $avatarLink}} {{if $avatarLink}}
<img class="ui avatar gt-vm" src="{{$avatarLink}}" width="32" height="32" alt="{{.FullName}}"> <img class="ui avatar gt-vm" src="{{$avatarLink}}" width="24" height="24" alt="{{.FullName}}">
{{else if $.IsMirror}} {{else if $.IsMirror}}
{{svg "octicon-mirror" 32}} {{svg "octicon-mirror" 24}}
{{else if $.IsFork}} {{else if $.IsFork}}
{{svg "octicon-repo-forked" 32}} {{svg "octicon-repo-forked" 24}}
{{else}} {{else}}
{{svg "octicon-repo" 32}} {{svg "octicon-repo" 24}}
{{end}} {{end}}

View file

@ -2,7 +2,7 @@
{{template "repo/issue/branch_selector_field" .}} {{template "repo/issue/branch_selector_field" .}}
{{if .Issue.IsPull}} {{if .Issue.IsPull}}
<input id="reviewer_id" name="reviewer_id" type="hidden" value="{{.reviewer_id}}"> <input id="reviewer_id" name="reviewer_id" type="hidden" value="{{.reviewer_id}}">
<div class="ui {{if or (not .Reviewers) (not .CanChooseReviewer) .Repository.IsArchived}}disabled{{end}} floating jump select-reviewers-modify dropdown"> <div class="ui {{if or (and (not .Reviewers) (not .TeamReviewers)) (not .CanChooseReviewer) .Repository.IsArchived}}disabled{{end}} floating jump select-reviewers-modify dropdown">
<a class="text gt-df gt-ac muted"> <a class="text gt-df gt-ac muted">
<strong>{{ctx.Locale.Tr "repo.issues.review.reviewers"}}</strong> <strong>{{ctx.Locale.Tr "repo.issues.review.reviewers"}}</strong>
{{if and .CanChooseReviewer (not .Repository.IsArchived)}} {{if and .CanChooseReviewer (not .Repository.IsArchived)}}
@ -29,7 +29,9 @@
{{end}} {{end}}
{{end}} {{end}}
{{if .TeamReviewers}} {{if .TeamReviewers}}
{{if .Reviewers}}
<div class="divider"></div> <div class="divider"></div>
{{end}}
{{range .TeamReviewers}} {{range .TeamReviewers}}
{{if .Team}} {{if .Team}}
<a class="{{if not .CanChange}}ui{{end}} item {{if .Checked}}checked{{end}} {{if not .CanChange}}ban-change{{end}}" href="#" data-id="{{.ItemID}}" data-id-selector="#review_request_team_{{.Team.ID}}" {{if not .CanChange}} data-tooltip-content="{{ctx.Locale.Tr "repo.issues.remove_request_review_block"}}"{{end}}> <a class="{{if not .CanChange}}ui{{end}} item {{if .Checked}}checked{{end}} {{if not .CanChange}}ban-change{{end}}" href="#" data-id="{{.ItemID}}" data-id-selector="#review_request_team_{{.Team.ID}}" {{if not .CanChange}} data-tooltip-content="{{ctx.Locale.Tr "repo.issues.remove_request_review_block"}}"{{end}}>

View file

@ -0,0 +1,8 @@
<div class="ui fluid vertical menu">
<a class="{{if .PageIsPulse}}active {{end}}item" href="{{.RepoLink}}/activity">
{{ctx.Locale.Tr "repo.activity.navbar.pulse"}}
</a>
<a class="{{if .PageIsContributors}}active {{end}}item" href="{{.RepoLink}}/activity/contributors">
{{ctx.Locale.Tr "repo.activity.navbar.contributors"}}
</a>
</div>

227
templates/repo/pulse.tmpl Normal file
View file

@ -0,0 +1,227 @@
<h2 class="ui header activity-header">
<span>{{DateTime "long" .DateFrom}} - {{DateTime "long" .DateUntil}}</span>
<!-- Period -->
<div class="ui floating dropdown jump filter">
<div class="ui basic compact button">
{{ctx.Locale.Tr "repo.activity.period.filter_label"}} <strong>{{.PeriodText}}</strong>
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
</div>
<div class="menu">
<a class="{{if eq .Period "daily"}}active {{end}}item" href="{{$.RepoLink}}/activity/daily">{{ctx.Locale.Tr "repo.activity.period.daily"}}</a>
<a class="{{if eq .Period "halfweekly"}}active {{end}}item" href="{{$.RepoLink}}/activity/halfweekly">{{ctx.Locale.Tr "repo.activity.period.halfweekly"}}</a>
<a class="{{if eq .Period "weekly"}}active {{end}}item" href="{{$.RepoLink}}/activity/weekly">{{ctx.Locale.Tr "repo.activity.period.weekly"}}</a>
<a class="{{if eq .Period "monthly"}}active {{end}}item" href="{{$.RepoLink}}/activity/monthly">{{ctx.Locale.Tr "repo.activity.period.monthly"}}</a>
<a class="{{if eq .Period "quarterly"}}active {{end}}item" href="{{$.RepoLink}}/activity/quarterly">{{ctx.Locale.Tr "repo.activity.period.quarterly"}}</a>
<a class="{{if eq .Period "semiyearly"}}active {{end}}item" href="{{$.RepoLink}}/activity/semiyearly">{{ctx.Locale.Tr "repo.activity.period.semiyearly"}}</a>
<a class="{{if eq .Period "yearly"}}active {{end}}item" href="{{$.RepoLink}}/activity/yearly">{{ctx.Locale.Tr "repo.activity.period.yearly"}}</a>
</div>
</div>
</h2>
{{if (or (.Permission.CanRead $.UnitTypeIssues) (.Permission.CanRead $.UnitTypePullRequests))}}
<h4 class="ui top attached header">{{ctx.Locale.Tr "repo.activity.overview"}}</h4>
<div class="ui attached segment two column grid">
{{if .Permission.CanRead $.UnitTypePullRequests}}
<div class="column">
{{if gt .Activity.ActivePRCount 0}}
<div class="stats-table">
<a href="#merged-pull-requests" class="table-cell tiny background purple" style="width: {{.Activity.MergedPRPerc}}{{if ne .Activity.MergedPRPerc 0}}%{{end}}"></a>
<a href="#proposed-pull-requests" class="table-cell tiny background green"></a>
</div>
{{else}}
<div class="stats-table">
<a class="table-cell tiny background light grey"></a>
</div>
{{end}}
{{ctx.Locale.TrN .Activity.ActivePRCount "repo.activity.active_prs_count_1" "repo.activity.active_prs_count_n" .Activity.ActivePRCount | Safe}}
</div>
{{end}}
{{if .Permission.CanRead $.UnitTypeIssues}}
<div class="column">
{{if gt .Activity.ActiveIssueCount 0}}
<div class="stats-table">
<a href="#closed-issues" class="table-cell tiny background red" style="width: {{.Activity.ClosedIssuePerc}}{{if ne .Activity.ClosedIssuePerc 0}}%{{end}}"></a>
<a href="#new-issues" class="table-cell tiny background green"></a>
</div>
{{else}}
<div class="stats-table">
<a class="table-cell tiny background light grey"></a>
</div>
{{end}}
{{ctx.Locale.TrN .Activity.ActiveIssueCount "repo.activity.active_issues_count_1" "repo.activity.active_issues_count_n" .Activity.ActiveIssueCount | Safe}}
</div>
{{end}}
</div>
<div class="ui attached segment horizontal segments">
{{if .Permission.CanRead $.UnitTypePullRequests}}
<a href="#merged-pull-requests" class="ui attached segment text center">
<span class="text purple">{{svg "octicon-git-pull-request"}}</span> <strong>{{.Activity.MergedPRCount}}</strong><br>
{{ctx.Locale.TrN .Activity.MergedPRCount "repo.activity.merged_prs_count_1" "repo.activity.merged_prs_count_n"}}
</a>
<a href="#proposed-pull-requests" class="ui attached segment text center">
<span class="text green">{{svg "octicon-git-branch"}}</span> <strong>{{.Activity.OpenedPRCount}}</strong><br>
{{ctx.Locale.TrN .Activity.OpenedPRCount "repo.activity.opened_prs_count_1" "repo.activity.opened_prs_count_n"}}
</a>
{{end}}
{{if .Permission.CanRead $.UnitTypeIssues}}
<a href="#closed-issues" class="ui attached segment text center">
<span class="text red">{{svg "octicon-issue-closed"}}</span> <strong>{{.Activity.ClosedIssueCount}}</strong><br>
{{ctx.Locale.TrN .Activity.ClosedIssueCount "repo.activity.closed_issues_count_1" "repo.activity.closed_issues_count_n"}}
</a>
<a href="#new-issues" class="ui attached segment text center">
<span class="text green">{{svg "octicon-issue-opened"}}</span> <strong>{{.Activity.OpenedIssueCount}}</strong><br>
{{ctx.Locale.TrN .Activity.OpenedIssueCount "repo.activity.new_issues_count_1" "repo.activity.new_issues_count_n"}}
</a>
{{end}}
</div>
{{end}}
{{if .Permission.CanRead $.UnitTypeCode}}
{{if eq .Activity.Code.CommitCountInAllBranches 0}}
<div class="ui center aligned segment">
<h4 class="ui header">{{ctx.Locale.Tr "repo.activity.no_git_activity"}}</h4>
</div>
{{end}}
{{if gt .Activity.Code.CommitCountInAllBranches 0}}
<div class="ui attached segment horizontal segments">
<div class="ui attached segment text">
{{ctx.Locale.Tr "repo.activity.git_stats_exclude_merges"}}
<strong>{{ctx.Locale.TrN .Activity.Code.AuthorCount "repo.activity.git_stats_author_1" "repo.activity.git_stats_author_n" .Activity.Code.AuthorCount}}</strong>
{{ctx.Locale.TrN .Activity.Code.AuthorCount "repo.activity.git_stats_pushed_1" "repo.activity.git_stats_pushed_n"}}
<strong>{{ctx.Locale.TrN .Activity.Code.CommitCount "repo.activity.git_stats_commit_1" "repo.activity.git_stats_commit_n" .Activity.Code.CommitCount}}</strong>
{{ctx.Locale.Tr "repo.activity.git_stats_push_to_branch" .Repository.DefaultBranch}}
<strong>{{ctx.Locale.TrN .Activity.Code.CommitCountInAllBranches "repo.activity.git_stats_commit_1" "repo.activity.git_stats_commit_n" .Activity.Code.CommitCountInAllBranches}}</strong>
{{ctx.Locale.Tr "repo.activity.git_stats_push_to_all_branches"}}
{{ctx.Locale.Tr "repo.activity.git_stats_on_default_branch" .Repository.DefaultBranch}}
<strong>{{ctx.Locale.TrN .Activity.Code.ChangedFiles "repo.activity.git_stats_file_1" "repo.activity.git_stats_file_n" .Activity.Code.ChangedFiles}}</strong>
{{ctx.Locale.TrN .Activity.Code.ChangedFiles "repo.activity.git_stats_files_changed_1" "repo.activity.git_stats_files_changed_n"}}
{{ctx.Locale.Tr "repo.activity.git_stats_additions"}}
<strong class="text green">{{ctx.Locale.TrN .Activity.Code.Additions "repo.activity.git_stats_addition_1" "repo.activity.git_stats_addition_n" .Activity.Code.Additions}}</strong>
{{ctx.Locale.Tr "repo.activity.git_stats_and_deletions"}}
<strong class="text red">{{ctx.Locale.TrN .Activity.Code.Deletions "repo.activity.git_stats_deletion_1" "repo.activity.git_stats_deletion_n" .Activity.Code.Deletions}}</strong>.
</div>
<div class="ui attached segment">
<div id="repo-activity-top-authors-chart"></div>
</div>
</div>
{{end}}
{{end}}
{{if gt .Activity.PublishedReleaseCount 0}}
<h4 class="divider divider-text gt-normal-case" id="published-releases">
{{svg "octicon-tag" 16 "gt-mr-3"}}
{{ctx.Locale.Tr "repo.activity.title.releases_published_by"
(ctx.Locale.TrN .Activity.PublishedReleaseCount "repo.activity.title.releases_1" "repo.activity.title.releases_n" .Activity.PublishedReleaseCount)
(ctx.Locale.TrN .Activity.PublishedReleaseAuthorCount "repo.activity.title.user_1" "repo.activity.title.user_n" .Activity.PublishedReleaseAuthorCount)
}}
</h4>
<div class="list">
{{range .Activity.PublishedReleases}}
<p class="desc">
<span class="ui green label">{{ctx.Locale.Tr "repo.activity.published_release_label"}}</span>
{{.TagName}}
{{if not .IsTag}}
<a class="title" href="{{$.RepoLink}}/src/{{.TagName | PathEscapeSegments}}">{{.Title | RenderEmoji $.Context | RenderCodeBlock}}</a>
{{end}}
{{TimeSinceUnix .CreatedUnix ctx.Locale}}
</p>
{{end}}
</div>
{{end}}
{{if gt .Activity.MergedPRCount 0}}
<h4 class="divider divider-text gt-normal-case" id="merged-pull-requests">
{{svg "octicon-git-pull-request" 16 "gt-mr-3"}}
{{ctx.Locale.Tr "repo.activity.title.prs_merged_by"
(ctx.Locale.TrN .Activity.MergedPRCount "repo.activity.title.prs_1" "repo.activity.title.prs_n" .Activity.MergedPRCount)
(ctx.Locale.TrN .Activity.MergedPRAuthorCount "repo.activity.title.user_1" "repo.activity.title.user_n" .Activity.MergedPRAuthorCount)
}}
</h4>
<div class="list">
{{range .Activity.MergedPRs}}
<p class="desc">
<span class="ui purple label">{{ctx.Locale.Tr "repo.activity.merged_prs_label"}}</span>
#{{.Index}} <a class="title" href="{{$.RepoLink}}/pulls/{{.Index}}">{{.Issue.Title | RenderEmoji $.Context | RenderCodeBlock}}</a>
{{TimeSinceUnix .MergedUnix ctx.Locale}}
</p>
{{end}}
</div>
{{end}}
{{if gt .Activity.OpenedPRCount 0}}
<h4 class="divider divider-text gt-normal-case" id="proposed-pull-requests">
{{svg "octicon-git-branch" 16 "gt-mr-3"}}
{{ctx.Locale.Tr "repo.activity.title.prs_opened_by"
(ctx.Locale.TrN .Activity.OpenedPRCount "repo.activity.title.prs_1" "repo.activity.title.prs_n" .Activity.OpenedPRCount)
(ctx.Locale.TrN .Activity.OpenedPRAuthorCount "repo.activity.title.user_1" "repo.activity.title.user_n" .Activity.OpenedPRAuthorCount)
}}
</h4>
<div class="list">
{{range .Activity.OpenedPRs}}
<p class="desc">
<span class="ui green label">{{ctx.Locale.Tr "repo.activity.opened_prs_label"}}</span>
#{{.Index}} <a class="title" href="{{$.RepoLink}}/pulls/{{.Index}}">{{.Issue.Title | RenderEmoji $.Context | RenderCodeBlock}}</a>
{{TimeSinceUnix .Issue.CreatedUnix ctx.Locale}}
</p>
{{end}}
</div>
{{end}}
{{if gt .Activity.ClosedIssueCount 0}}
<h4 class="divider divider-text gt-normal-case" id="closed-issues">
{{svg "octicon-issue-closed" 16 "gt-mr-3"}}
{{ctx.Locale.Tr "repo.activity.title.issues_closed_from"
(ctx.Locale.TrN .Activity.ClosedIssueCount "repo.activity.title.issues_1" "repo.activity.title.issues_n" .Activity.ClosedIssueCount)
(ctx.Locale.TrN .Activity.ClosedIssueAuthorCount "repo.activity.title.user_1" "repo.activity.title.user_n" .Activity.ClosedIssueAuthorCount)
}}
</h4>
<div class="list">
{{range .Activity.ClosedIssues}}
<p class="desc">
<span class="ui red label">{{ctx.Locale.Tr "repo.activity.closed_issue_label"}}</span>
#{{.Index}} <a class="title" href="{{$.RepoLink}}/issues/{{.Index}}">{{.Title | RenderEmoji $.Context | RenderCodeBlock}}</a>
{{TimeSinceUnix .ClosedUnix ctx.Locale}}
</p>
{{end}}
</div>
{{end}}
{{if gt .Activity.OpenedIssueCount 0}}
<h4 class="divider divider-text gt-normal-case" id="new-issues">
{{svg "octicon-issue-opened" 16 "gt-mr-3"}}
{{ctx.Locale.Tr "repo.activity.title.issues_created_by"
(ctx.Locale.TrN .Activity.OpenedIssueCount "repo.activity.title.issues_1" "repo.activity.title.issues_n" .Activity.OpenedIssueCount)
(ctx.Locale.TrN .Activity.OpenedIssueAuthorCount "repo.activity.title.user_1" "repo.activity.title.user_n" .Activity.OpenedIssueAuthorCount)
}}
</h4>
<div class="list">
{{range .Activity.OpenedIssues}}
<p class="desc">
<span class="ui green label">{{ctx.Locale.Tr "repo.activity.new_issue_label"}}</span>
#{{.Index}} <a class="title" href="{{$.RepoLink}}/issues/{{.Index}}">{{.Title | RenderEmoji $.Context | RenderCodeBlock}}</a>
{{TimeSinceUnix .CreatedUnix ctx.Locale}}
</p>
{{end}}
</div>
{{end}}
{{if gt .Activity.UnresolvedIssueCount 0}}
<h4 class="divider divider-text gt-normal-case" id="unresolved-conversations" data-tooltip-content="{{ctx.Locale.Tr "repo.activity.unresolved_conv_desc"}}">
{{svg "octicon-comment-discussion" 16 "gt-mr-3"}}
{{ctx.Locale.TrN .Activity.UnresolvedIssueCount "repo.activity.title.unresolved_conv_1" "repo.activity.title.unresolved_conv_n" .Activity.UnresolvedIssueCount}}
</h4>
<div class="list">
{{range .Activity.UnresolvedIssues}}
<p class="desc">
<span class="ui green label">{{ctx.Locale.Tr "repo.activity.unresolved_conv_label"}}</span>
#{{.Index}}
{{if .IsPull}}
<a class="title" href="{{$.RepoLink}}/pulls/{{.Index}}">{{.Title | RenderEmoji $.Context | RenderCodeBlock}}</a>
{{else}}
<a class="title" href="{{$.RepoLink}}/issues/{{.Index}}">{{.Title | RenderEmoji $.Context | RenderCodeBlock}}</a>
{{end}}
{{TimeSinceUnix .UpdatedUnix ctx.Locale}}
</p>
{{end}}
</div>
{{end}}

View file

@ -18,11 +18,11 @@
{{ctx.Locale.Tr "repo.settings.deploy_key_desc"}} {{ctx.Locale.Tr "repo.settings.deploy_key_desc"}}
</div> </div>
<div class="field {{if .Err_Title}}error{{end}}"> <div class="field {{if .Err_Title}}error{{end}}">
<label for="title">{{ctx.Locale.Tr "repo.settings.title"}}</label> <label for="ssh-key-title">{{ctx.Locale.Tr "repo.settings.title"}}</label>
<input id="ssh-key-title" name="title" value="{{.title}}" autofocus required> <input id="ssh-key-title" name="title" value="{{.title}}" autofocus required>
</div> </div>
<div class="field {{if .Err_Content}}error{{end}}"> <div class="field {{if .Err_Content}}error{{end}}">
<label for="content">{{ctx.Locale.Tr "repo.settings.deploy_key_content"}}</label> <label for="ssh-key-content">{{ctx.Locale.Tr "repo.settings.deploy_key_content"}}</label>
<textarea id="ssh-key-content" name="content" placeholder="{{ctx.Locale.Tr "settings.key_content_ssh_placeholder"}}" required>{{.content}}</textarea> <textarea id="ssh-key-content" name="content" placeholder="{{ctx.Locale.Tr "settings.key_content_ssh_placeholder"}}" required>{{.content}}</textarea>
</div> </div>
<div class="field"> <div class="field">

View file

@ -3,56 +3,7 @@
<div class="ui right"> <div class="ui right">
<div class="ui jump dropdown"> <div class="ui jump dropdown">
<div class="ui primary tiny button">{{ctx.Locale.Tr "repo.settings.add_webhook"}}</div> <div class="ui primary tiny button">{{ctx.Locale.Tr "repo.settings.add_webhook"}}</div>
<div class="menu"> {{template "repo/settings/webhook/link_menu" .}}
<a class="item" href="{{.BaseLinkNew}}/forgejo/new">
{{template "shared/webhook/icon" (dict "HookType" "forgejo" "Size" 20)}}
{{ctx.Locale.Tr "repo.settings.web_hook_name_forgejo"}}
</a>
<a class="item" href="{{.BaseLinkNew}}/gitea/new">
{{template "shared/webhook/icon" (dict "HookType" "gitea" "Size" 20)}}
{{ctx.Locale.Tr "repo.settings.web_hook_name_gitea"}}
</a>
<a class="item" href="{{.BaseLinkNew}}/gogs/new">
{{template "shared/webhook/icon" (dict "HookType" "gogs" "Size" 20)}}
{{ctx.Locale.Tr "repo.settings.web_hook_name_gogs"}}
</a>
<a class="item" href="{{.BaseLinkNew}}/slack/new">
{{template "shared/webhook/icon" (dict "HookType" "slack" "Size" 20)}}
{{ctx.Locale.Tr "repo.settings.web_hook_name_slack"}}
</a>
<a class="item" href="{{.BaseLinkNew}}/discord/new">
{{template "shared/webhook/icon" (dict "HookType" "discord" "Size" 20)}}
{{ctx.Locale.Tr "repo.settings.web_hook_name_discord"}}
</a>
<a class="item" href="{{.BaseLinkNew}}/dingtalk/new">
{{template "shared/webhook/icon" (dict "HookType" "dingtalk" "Size" 20)}}
{{ctx.Locale.Tr "repo.settings.web_hook_name_dingtalk"}}
</a>
<a class="item" href="{{.BaseLinkNew}}/telegram/new">
{{template "shared/webhook/icon" (dict "HookType" "telegram" "Size" 20)}}
{{ctx.Locale.Tr "repo.settings.web_hook_name_telegram"}}
</a>
<a class="item" href="{{.BaseLinkNew}}/msteams/new">
{{template "shared/webhook/icon" (dict "HookType" "msteams" "Size" 20)}}
{{ctx.Locale.Tr "repo.settings.web_hook_name_msteams"}}
</a>
<a class="item" href="{{.BaseLinkNew}}/feishu/new">
{{template "shared/webhook/icon" (dict "HookType" "feishu" "Size" 20)}}
{{ctx.Locale.Tr "repo.settings.web_hook_name_feishu_or_larksuite"}}
</a>
<a class="item" href="{{.BaseLinkNew}}/matrix/new">
{{template "shared/webhook/icon" (dict "HookType" "matrix" "Size" 20)}}
{{ctx.Locale.Tr "repo.settings.web_hook_name_matrix"}}
</a>
<a class="item" href="{{.BaseLinkNew}}/wechatwork/new">
{{template "shared/webhook/icon" (dict "HookType" "wechatwork" "Size" 20)}}
{{ctx.Locale.Tr "repo.settings.web_hook_name_wechatwork"}}
</a>
<a class="item" href="{{.BaseLinkNew}}/packagist/new">
{{template "shared/webhook/icon" (dict "HookType" "packagist" "Size" 20)}}
{{ctx.Locale.Tr "repo.settings.web_hook_name_packagist"}}
</a>
</div>
</div> </div>
</div> </div>
</h4> </h4>

View file

@ -0,0 +1,54 @@
{{$size := 20}}
{{if .Size}}
{{$size = .Size}}
{{end}}
<div class="menu">
<a class="item" href="{{.BaseLinkNew}}/forgejo/new">
{{template "shared/webhook/icon" (dict "HookType" "forgejo" "Size" $size)}}
{{ctx.Locale.Tr "repo.settings.web_hook_name_forgejo"}}
</a>
<a class="item" href="{{.BaseLinkNew}}/gitea/new">
{{template "shared/webhook/icon" (dict "HookType" "gitea" "Size" $size)}}
{{ctx.Locale.Tr "repo.settings.web_hook_name_gitea"}}
</a>
<a class="item" href="{{.BaseLinkNew}}/gogs/new">
{{template "shared/webhook/icon" (dict "HookType" "gogs" "Size" $size)}}
{{ctx.Locale.Tr "repo.settings.web_hook_name_gogs"}}
</a>
<a class="item" href="{{.BaseLinkNew}}/slack/new">
{{template "shared/webhook/icon" (dict "HookType" "slack" "Size" $size)}}
{{ctx.Locale.Tr "repo.settings.web_hook_name_slack"}}
</a>
<a class="item" href="{{.BaseLinkNew}}/discord/new">
{{template "shared/webhook/icon" (dict "HookType" "discord" "Size" $size)}}
{{ctx.Locale.Tr "repo.settings.web_hook_name_discord"}}
</a>
<a class="item" href="{{.BaseLinkNew}}/dingtalk/new">
{{template "shared/webhook/icon" (dict "HookType" "dingtalk" "Size" $size)}}
{{ctx.Locale.Tr "repo.settings.web_hook_name_dingtalk"}}
</a>
<a class="item" href="{{.BaseLinkNew}}/telegram/new">
{{template "shared/webhook/icon" (dict "HookType" "telegram" "Size" $size)}}
{{ctx.Locale.Tr "repo.settings.web_hook_name_telegram"}}
</a>
<a class="item" href="{{.BaseLinkNew}}/msteams/new">
{{template "shared/webhook/icon" (dict "HookType" "msteams" "Size" $size)}}
{{ctx.Locale.Tr "repo.settings.web_hook_name_msteams"}}
</a>
<a class="item" href="{{.BaseLinkNew}}/feishu/new">
{{template "shared/webhook/icon" (dict "HookType" "feishu" "Size" $size)}}
{{ctx.Locale.Tr "repo.settings.web_hook_name_feishu_or_larksuite"}}
</a>
<a class="item" href="{{.BaseLinkNew}}/matrix/new">
{{template "shared/webhook/icon" (dict "HookType" "matrix" "Size" $size)}}
{{ctx.Locale.Tr "repo.settings.web_hook_name_matrix"}}
</a>
<a class="item" href="{{.BaseLinkNew}}/wechatwork/new">
{{template "shared/webhook/icon" (dict "HookType" "wechatwork" "Size" $size)}}
{{ctx.Locale.Tr "repo.settings.web_hook_name_wechatwork"}}
</a>
<a class="item" href="{{.BaseLinkNew}}/packagist/new">
{{template "shared/webhook/icon" (dict "HookType" "packagist" "Size" $size)}}
{{ctx.Locale.Tr "repo.settings.web_hook_name_packagist"}}
</a>
</div>

View file

@ -254,7 +254,7 @@
<!-- Branch filter --> <!-- Branch filter -->
<div class="field"> <div class="field">
<label for="branch_filter">{{ctx.Locale.Tr "repo.settings.branch_filter"}}</label> <label for="branch_filter">{{ctx.Locale.Tr "repo.settings.branch_filter"}}</label>
<input name="branch_filter" type="text" value="{{or .Webhook.BranchFilter "*"}}"> <input id="branch_filter" name="branch_filter" type="text" value="{{or .Webhook.BranchFilter "*"}}">
<span class="help">{{ctx.Locale.Tr "repo.settings.branch_filter_desc" | Str2html}}</span> <span class="help">{{ctx.Locale.Tr "repo.settings.branch_filter_desc" | Str2html}}</span>
</div> </div>

View file

@ -5,7 +5,7 @@
{{if eq .HookType "forgejo"}} {{if eq .HookType "forgejo"}}
<img width="{{$size}}" height="{{$size}}" src="{{AssetUrlPrefix}}/img/forgejo.svg"> <img width="{{$size}}" height="{{$size}}" src="{{AssetUrlPrefix}}/img/forgejo.svg">
{{else if eq .HookType "gitea"}} {{else if eq .HookType "gitea"}}
<img width="{{$size}}" height="{{$size}}" src="{{AssetUrlPrefix}}/img/gitea-original.svg"> {{svg "gitea-gitea" $size "img"}}
{{else if eq .HookType "gogs"}} {{else if eq .HookType "gogs"}}
<img width="{{$size}}" height="{{$size}}" src="{{AssetUrlPrefix}}/img/gogs.ico"> <img width="{{$size}}" height="{{$size}}" src="{{AssetUrlPrefix}}/img/gogs.ico">
{{else if eq .HookType "slack"}} {{else if eq .HookType "slack"}}

View file

@ -10,7 +10,7 @@
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<input type="hidden" name="title" value="none"> <input type="hidden" name="title" value="none">
<div class="field {{if .Err_Content}}error{{end}}"> <div class="field {{if .Err_Content}}error{{end}}">
<label for="content">{{ctx.Locale.Tr "settings.key_content"}}</label> <label for="gpg-key-content">{{ctx.Locale.Tr "settings.key_content"}}</label>
<textarea id="gpg-key-content" name="content" placeholder="{{ctx.Locale.Tr "settings.key_content_gpg_placeholder"}}" required>{{.content}}</textarea> <textarea id="gpg-key-content" name="content" placeholder="{{ctx.Locale.Tr "settings.key_content_gpg_placeholder"}}" required>{{.content}}</textarea>
</div> </div>
{{if .Err_Signature}} {{if .Err_Signature}}
@ -26,7 +26,7 @@
</div> </div>
</div> </div>
<div class="field"> <div class="field">
<label for="signature">{{ctx.Locale.Tr "settings.gpg_token_signature"}}</label> <label for="gpg-key-signature">{{ctx.Locale.Tr "settings.gpg_token_signature"}}</label>
<textarea id="gpg-key-signature" name="signature" placeholder="{{ctx.Locale.Tr "settings.key_signature_gpg_placeholder"}}" required>{{.signature}}</textarea> <textarea id="gpg-key-signature" name="signature" placeholder="{{ctx.Locale.Tr "settings.key_signature_gpg_placeholder"}}" required>{{.signature}}</textarea>
</div> </div>
{{end}} {{end}}

View file

@ -44,7 +44,7 @@
<form class="ui form" action="{{.Link}}" method="post"> <form class="ui form" action="{{.Link}}" method="post">
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<div class="field {{if .Err_Content}}error{{end}}"> <div class="field {{if .Err_Content}}error{{end}}">
<label for="content">{{ctx.Locale.Tr "settings.principal_content"}}</label> <label for="ssh-principal-content">{{ctx.Locale.Tr "settings.principal_content"}}</label>
<input id="ssh-principal-content" name="content" value="{{.content}}" autofocus required> <input id="ssh-principal-content" name="content" value="{{.content}}" autofocus required>
</div> </div>
<input name="title" type="hidden" value="principal"> <input name="title" type="hidden" value="principal">

View file

@ -11,11 +11,11 @@
<form class="ui form" action="{{.Link}}" method="post"> <form class="ui form" action="{{.Link}}" method="post">
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<div class="field {{if .Err_Title}}error{{end}}"> <div class="field {{if .Err_Title}}error{{end}}">
<label for="title">{{ctx.Locale.Tr "settings.key_name"}}</label> <label for="ssh-key-title">{{ctx.Locale.Tr "settings.key_name"}}</label>
<input id="ssh-key-title" name="title" value="{{.title}}" autofocus required maxlength="50"> <input id="ssh-key-title" name="title" value="{{.title}}" autofocus required maxlength="50">
</div> </div>
<div class="field {{if .Err_Content}}error{{end}}"> <div class="field {{if .Err_Content}}error{{end}}">
<label for="content">{{ctx.Locale.Tr "settings.key_content"}}</label> <label for="ssh-key-content">{{ctx.Locale.Tr "settings.key_content"}}</label>
<textarea id="ssh-key-content" name="content" class="js-quick-submit" placeholder="{{ctx.Locale.Tr "settings.key_content_ssh_placeholder"}}" required>{{.content}}</textarea> <textarea id="ssh-key-content" name="content" class="js-quick-submit" placeholder="{{ctx.Locale.Tr "settings.key_content_ssh_placeholder"}}" required>{{.content}}</textarea>
</div> </div>
<input name="type" type="hidden" value="ssh"> <input name="type" type="hidden" value="ssh">

View file

@ -22,8 +22,8 @@
<input id="full_name" name="full_name" value="{{.SignedUser.FullName}}" maxlength="100"> <input id="full_name" name="full_name" value="{{.SignedUser.FullName}}" maxlength="100">
</div> </div>
<div class="field {{if .Err_Email}}error{{end}}"> <div class="field {{if .Err_Email}}error{{end}}">
<label for="email">{{ctx.Locale.Tr "email"}}</label> <label>{{ctx.Locale.Tr "email"}}</label>
<p>{{.SignedUser.Email}}</p> <p id="signed-user-email">{{.SignedUser.Email}}</p>
</div> </div>
<div class="field {{if .Err_Description}}error{{end}}"> <div class="field {{if .Err_Description}}error{{end}}">
<label for="description">{{ctx.Locale.Tr "user.user_bio"}}</label> <label for="description">{{ctx.Locale.Tr "user.user_bio"}}</label>
@ -42,11 +42,11 @@
<!-- private block --> <!-- private block -->
<div class="field" id="privacy-user-settings"> <div class="field" id="privacy-user-settings">
<label for="security-private"><strong>{{ctx.Locale.Tr "settings.privacy"}}</strong></label> <label><strong>{{ctx.Locale.Tr "settings.privacy"}}</strong></label>
</div> </div>
<div class="inline field {{if .Err_Visibility}}error{{end}}"> <div class="inline field {{if .Err_Visibility}}error{{end}}">
<span class="inline required field"><label for="visibility">{{ctx.Locale.Tr "settings.visibility"}}</label></span> <span class="inline required field"><label>{{ctx.Locale.Tr "settings.visibility"}}</label></span>
<div class="ui selection type dropdown"> <div class="ui selection type dropdown">
{{if .SignedUser.Visibility.IsPublic}}<input type="hidden" id="visibility" name="visibility" value="0">{{end}} {{if .SignedUser.Visibility.IsPublic}}<input type="hidden" id="visibility" name="visibility" value="0">{{end}}
{{if .SignedUser.Visibility.IsLimited}}<input type="hidden" id="visibility" name="visibility" value="1">{{end}} {{if .SignedUser.Visibility.IsLimited}}<input type="hidden" id="visibility" name="visibility" value="1">{{end}}
@ -120,8 +120,8 @@
</div> </div>
<div class="inline field gt-pl-4"> <div class="inline field gt-pl-4">
<label for="avatar">{{ctx.Locale.Tr "settings.choose_new_avatar"}}</label> <label for="new-avatar">{{ctx.Locale.Tr "settings.choose_new_avatar"}}</label>
<input name="avatar" type="file" accept="image/png,image/jpeg,image/gif,image/webp"> <input id="new-avatar" name="avatar" type="file" accept="image/png,image/jpeg,image/gif,image/webp">
</div> </div>
<div class="field"> <div class="field">

View file

@ -1,7 +1,12 @@
<h4 class="ui top attached header"> <h4 class="ui top attached header">
{{.CustomHeaderTitle}} {{.CustomHeaderTitle}}
<div class="ui right"> <div class="ui right type dropdown">
{{template "shared/webhook/icon" .ctxData}} <div class="text gt-df gt-ac">
{{template "shared/webhook/icon" (dict "Size" 20 "HookType" .ctxData.HookType)}}
{{ctx.Locale.Tr (print "repo.settings.web_hook_name_" .ctxData.HookType)}}
</div>
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
{{template "repo/settings/webhook/link_menu" .ctxData}}
</div> </div>
</h4> </h4>
<div class="ui attached segment"> <div class="ui attached segment">

View file

@ -179,7 +179,7 @@ func TestLDAPUserSignin(t *testing.T) {
assert.Equal(t, u.UserName, htmlDoc.GetInputValueByName("name")) assert.Equal(t, u.UserName, htmlDoc.GetInputValueByName("name"))
assert.Equal(t, u.FullName, htmlDoc.GetInputValueByName("full_name")) assert.Equal(t, u.FullName, htmlDoc.GetInputValueByName("full_name"))
assert.Equal(t, u.Email, htmlDoc.Find(`label[for="email"]`).Siblings().First().Text()) assert.Equal(t, u.Email, htmlDoc.Find("#signed-user-email").Text())
} }
func TestLDAPAuthChange(t *testing.T) { func TestLDAPAuthChange(t *testing.T) {

View file

@ -413,6 +413,13 @@ ol.ui.list li,
color: var(--color-text-light-2); color: var(--color-text-light-2);
} }
/* extend fomantic style '.ui.dropdown > .text > img' to include svg.img */
.ui.dropdown > .text > .img {
margin-left: 0;
float: none;
margin-right: 0.78571429rem;
}
.ui.dropdown > .text > .description, .ui.dropdown > .text > .description,
.ui.dropdown .menu > .item > .description { .ui.dropdown .menu > .item > .description {
color: var(--color-text-light-2); color: var(--color-text-light-2);

View file

@ -7,6 +7,10 @@ extends:
- plugin:vue/vue3-recommended - plugin:vue/vue3-recommended
- plugin:vue-scoped-css/vue3-recommended - plugin:vue-scoped-css/vue3-recommended
parserOptions:
sourceType: module
ecmaVersion: latest
env: env:
browser: true browser: true

View file

@ -0,0 +1,443 @@
<script>
import {SvgIcon} from '../svg.js';
import {
Chart,
Title,
Tooltip,
Legend,
BarElement,
CategoryScale,
LinearScale,
TimeScale,
PointElement,
LineElement,
Filler,
} from 'chart.js';
import {GET} from '../modules/fetch.js';
import zoomPlugin from 'chartjs-plugin-zoom';
import {Line as ChartLine} from 'vue-chartjs';
import {
startDaysBetween,
firstStartDateAfterDate,
fillEmptyStartDaysWithZeroes,
} from '../utils/time.js';
import 'chartjs-adapter-dayjs-4/dist/chartjs-adapter-dayjs-4.esm';
import $ from 'jquery';
const {pageData} = window.config;
const colors = {
text: '--color-text',
border: '--color-secondary-alpha-60',
commits: '--color-primary-alpha-60',
additions: '--color-green',
deletions: '--color-red',
title: '--color-secondary-dark-4',
};
const styles = window.getComputedStyle(document.documentElement);
const getColor = (name) => styles.getPropertyValue(name).trim();
for (const [key, value] of Object.entries(colors)) {
colors[key] = getColor(value);
}
const customEventListener = {
id: 'customEventListener',
afterEvent: (chart, args, opts) => {
// event will be replayed from chart.update when reset zoom,
// so we need to check whether args.replay is true to avoid call loops
if (args.event.type === 'dblclick' && opts.chartType === 'main' && !args.replay) {
chart.resetZoom();
opts.instance.updateOtherCharts(args.event, true);
}
}
};
Chart.defaults.color = colors.text;
Chart.defaults.borderColor = colors.border;
Chart.register(
TimeScale,
CategoryScale,
LinearScale,
BarElement,
Title,
Tooltip,
Legend,
PointElement,
LineElement,
Filler,
zoomPlugin,
customEventListener,
);
export default {
components: {ChartLine, SvgIcon},
props: {
locale: {
type: Object,
required: true,
},
},
data: () => ({
isLoading: false,
errorText: '',
totalStats: {},
sortedContributors: {},
repoLink: pageData.repoLink || [],
type: pageData.contributionType,
contributorsStats: [],
xAxisStart: null,
xAxisEnd: null,
xAxisMin: null,
xAxisMax: null,
}),
mounted() {
this.fetchGraphData();
$('#repo-contributors').dropdown({
onChange: (val) => {
this.xAxisMin = this.xAxisStart;
this.xAxisMax = this.xAxisEnd;
this.type = val;
this.sortContributors();
}
});
},
methods: {
sortContributors() {
const contributors = this.filterContributorWeeksByDateRange();
const criteria = `total_${this.type}`;
this.sortedContributors = Object.values(contributors)
.filter((contributor) => contributor[criteria] !== 0)
.sort((a, b) => a[criteria] > b[criteria] ? -1 : a[criteria] === b[criteria] ? 0 : 1)
.slice(0, 100);
},
async fetchGraphData() {
this.isLoading = true;
try {
let response;
do {
response = await GET(`${this.repoLink}/activity/contributors/data`);
if (response.status === 202) {
await new Promise((resolve) => setTimeout(resolve, 1000)); // wait for 1 second before retrying
}
} while (response.status === 202);
if (response.ok) {
const data = await response.json();
const {total, ...rest} = data;
// below line might be deleted if we are sure go produces map always sorted by keys
total.weeks = Object.fromEntries(Object.entries(total.weeks).sort());
const weekValues = Object.values(total.weeks);
this.xAxisStart = weekValues[0].week;
this.xAxisEnd = firstStartDateAfterDate(new Date());
const startDays = startDaysBetween(new Date(this.xAxisStart), new Date(this.xAxisEnd));
total.weeks = fillEmptyStartDaysWithZeroes(startDays, total.weeks);
this.xAxisMin = this.xAxisStart;
this.xAxisMax = this.xAxisEnd;
this.contributorsStats = {};
for (const [email, user] of Object.entries(rest)) {
user.weeks = fillEmptyStartDaysWithZeroes(startDays, user.weeks);
this.contributorsStats[email] = user;
}
this.sortContributors();
this.totalStats = total;
this.errorText = '';
} else {
this.errorText = response.statusText;
}
} catch (err) {
this.errorText = err.message;
} finally {
this.isLoading = false;
}
},
filterContributorWeeksByDateRange() {
const filteredData = {};
const data = this.contributorsStats;
for (const key of Object.keys(data)) {
const user = data[key];
user.total_commits = 0;
user.total_additions = 0;
user.total_deletions = 0;
user.max_contribution_type = 0;
const filteredWeeks = user.weeks.filter((week) => {
const oneWeek = 7 * 24 * 60 * 60 * 1000;
if (week.week >= this.xAxisMin - oneWeek && week.week <= this.xAxisMax + oneWeek) {
user.total_commits += week.commits;
user.total_additions += week.additions;
user.total_deletions += week.deletions;
if (week[this.type] > user.max_contribution_type) {
user.max_contribution_type = week[this.type];
}
return true;
}
return false;
});
// this line is required. See https://github.com/sahinakkaya/gitea/pull/3#discussion_r1396495722
// for details.
user.max_contribution_type += 1;
filteredData[key] = {...user, weeks: filteredWeeks};
}
return filteredData;
},
maxMainGraph() {
// This method calculates maximum value for Y value of the main graph. If the number
// of maximum contributions for selected contribution type is 15.955 it is probably
// better to round it up to 20.000.This method is responsible for doing that.
// Normally, chartjs handles this automatically, but it will resize the graph when you
// zoom, pan etc. I think resizing the graph makes it harder to compare things visually.
const maxValue = Math.max(
...this.totalStats.weeks.map((o) => o[this.type])
);
const [coefficient, exp] = maxValue.toExponential().split('e').map(Number);
if (coefficient % 1 === 0) return maxValue;
return (1 - (coefficient % 1)) * 10 ** exp + maxValue;
},
maxContributorGraph() {
// Similar to maxMainGraph method this method calculates maximum value for Y value
// for contributors' graph. If I let chartjs do this for me, it will choose different
// maxY value for each contributors' graph which again makes it harder to compare.
const maxValue = Math.max(
...this.sortedContributors.map((c) => c.max_contribution_type)
);
const [coefficient, exp] = maxValue.toExponential().split('e').map(Number);
if (coefficient % 1 === 0) return maxValue;
return (1 - (coefficient % 1)) * 10 ** exp + maxValue;
},
toGraphData(data) {
return {
datasets: [
{
data: data.map((i) => ({x: i.week, y: i[this.type]})),
pointRadius: 0,
pointHitRadius: 0,
fill: 'start',
backgroundColor: colors[this.type],
borderWidth: 0,
tension: 0.3,
},
],
};
},
updateOtherCharts(event, reset) {
const minVal = event.chart.options.scales.x.min;
const maxVal = event.chart.options.scales.x.max;
if (reset) {
this.xAxisMin = this.xAxisStart;
this.xAxisMax = this.xAxisEnd;
this.sortContributors();
} else if (minVal) {
this.xAxisMin = minVal;
this.xAxisMax = maxVal;
this.sortContributors();
}
},
getOptions(type) {
return {
responsive: true,
maintainAspectRatio: false,
animation: false,
events: ['mousemove', 'mouseout', 'click', 'touchstart', 'touchmove', 'dblclick'],
plugins: {
title: {
display: type === 'main',
text: 'drag: zoom, shift+drag: pan, double click: reset zoom',
color: colors.title,
position: 'top',
align: 'center',
},
customEventListener: {
chartType: type,
instance: this,
},
legend: {
display: false,
},
zoom: {
pan: {
enabled: true,
modifierKey: 'shift',
mode: 'x',
threshold: 20,
onPanComplete: this.updateOtherCharts,
},
limits: {
x: {
// Check https://www.chartjs.org/chartjs-plugin-zoom/latest/guide/options.html#scale-limits
// to know what each option means
min: 'original',
max: 'original',
// number of milliseconds in 2 weeks. Minimum x range will be 2 weeks when you zoom on the graph
minRange: 2 * 7 * 24 * 60 * 60 * 1000,
},
},
zoom: {
drag: {
enabled: type === 'main',
},
pinch: {
enabled: type === 'main',
},
mode: 'x',
onZoomComplete: this.updateOtherCharts,
},
},
},
scales: {
x: {
min: this.xAxisMin,
max: this.xAxisMax,
type: 'time',
grid: {
display: false,
},
time: {
minUnit: 'month',
},
ticks: {
maxRotation: 0,
maxTicksLimit: type === 'main' ? 12 : 6,
},
},
y: {
min: 0,
max: type === 'main' ? this.maxMainGraph() : this.maxContributorGraph(),
ticks: {
maxTicksLimit: type === 'main' ? 6 : 4,
},
},
},
};
},
},
};
</script>
<template>
<div>
<h2 class="ui header gt-df gt-ac gt-sb">
<div>
<relative-time
v-if="xAxisMin > 0"
format="datetime"
year="numeric"
month="short"
day="numeric"
weekday=""
:datetime="new Date(xAxisMin)"
>
{{ new Date(xAxisMin) }}
</relative-time>
{{ isLoading ? locale.loadingTitle : errorText ? locale.loadingTitleFailed: "-" }}
<relative-time
v-if="xAxisMax > 0"
format="datetime"
year="numeric"
month="short"
day="numeric"
weekday=""
:datetime="new Date(xAxisMax)"
>
{{ new Date(xAxisMax) }}
</relative-time>
</div>
<div>
<!-- Contribution type -->
<div class="ui dropdown jump" id="repo-contributors">
<div class="ui basic compact button">
<span class="text">
{{ locale.filterLabel }} <strong>{{ locale.contributionType[type] }}</strong>
<svg-icon name="octicon-triangle-down" :size="14"/>
</span>
</div>
<div class="menu">
<div :class="['item', {'active': type === 'commits'}]">
{{ locale.contributionType.commits }}
</div>
<div :class="['item', {'active': type === 'additions'}]">
{{ locale.contributionType.additions }}
</div>
<div :class="['item', {'active': type === 'deletions'}]">
{{ locale.contributionType.deletions }}
</div>
</div>
</div>
</div>
</h2>
<div class="gt-df ui segment main-graph">
<div v-if="isLoading || errorText !== ''" class="gt-tc gt-m-auto">
<div v-if="isLoading">
<SvgIcon name="octicon-sync" class="gt-mr-3 job-status-rotate"/>
{{ locale.loadingInfo }}
</div>
<div v-else class="text red">
<SvgIcon name="octicon-x-circle-fill"/>
{{ errorText }}
</div>
</div>
<ChartLine
v-memo="[totalStats.weeks, type]" v-if="Object.keys(totalStats).length !== 0"
:data="toGraphData(totalStats.weeks)" :options="getOptions('main')"
/>
</div>
<div class="contributor-grid">
<div
v-for="(contributor, index) in sortedContributors" :key="index" class="stats-table"
v-memo="[sortedContributors, type]"
>
<div class="ui top attached header gt-df gt-f1">
<b class="ui right">#{{ index + 1 }}</b>
<a :href="contributor.home_link">
<img class="ui avatar gt-vm" height="40" width="40" :src="contributor.avatar_link">
</a>
<div class="gt-ml-3">
<a v-if="contributor.home_link !== ''" :href="contributor.home_link"><h4>{{ contributor.name }}</h4></a>
<h4 v-else class="contributor-name">
{{ contributor.name }}
</h4>
<p class="gt-font-12 gt-df gt-gap-2">
<strong v-if="contributor.total_commits">{{ contributor.total_commits.toLocaleString() }} {{ locale.contributionType.commits }}</strong>
<strong v-if="contributor.total_additions" class="text green">{{ contributor.total_additions.toLocaleString() }}++ </strong>
<strong v-if="contributor.total_deletions" class="text red">
{{ contributor.total_deletions.toLocaleString() }}--</strong>
</p>
</div>
</div>
<div class="ui attached segment">
<div>
<ChartLine
:data="toGraphData(contributor.weeks)"
:options="getOptions('contributor')"
/>
</div>
</div>
</div>
</div>
</div>
</template>
<style scoped>
.main-graph {
height: 260px;
}
.contributor-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 1rem;
}
.contributor-name {
margin-bottom: 0;
}
</style>

View file

@ -1,14 +1,13 @@
import $ from 'jquery';
import {initCompLabelEdit} from './comp/LabelEdit.js'; import {initCompLabelEdit} from './comp/LabelEdit.js';
import {toggleElem} from '../utils/dom.js'; import {toggleElem} from '../utils/dom.js';
export function initCommonOrganization() { export function initCommonOrganization() {
if ($('.organization').length === 0) { if (!document.querySelectorAll('.organization').length) {
return; return;
} }
$('.organization.settings.options #org_name').on('input', function () { document.querySelector('.organization.settings.options #org_name')?.addEventListener('input', function () {
const nameChanged = $(this).val().toLowerCase() !== $(this).attr('data-org-name').toLowerCase(); const nameChanged = this.value.toLowerCase() !== this.getAttribute('data-org-name').toLowerCase();
toggleElem('#org-name-change-prompt', nameChanged); toggleElem('#org-name-change-prompt', nameChanged);
}); });

View file

@ -1,5 +1,3 @@
import $ from 'jquery';
export function handleGlobalEnterQuickSubmit(target) { export function handleGlobalEnterQuickSubmit(target) {
const form = target.closest('form'); const form = target.closest('form');
if (form) { if (form) {
@ -8,14 +6,9 @@ export function handleGlobalEnterQuickSubmit(target) {
return; return;
} }
if (form.classList.contains('form-fetch-action')) {
form.dispatchEvent(new SubmitEvent('submit', {bubbles: true, cancelable: true}));
return;
}
// here use the event to trigger the submit event (instead of calling `submit()` method directly) // here use the event to trigger the submit event (instead of calling `submit()` method directly)
// otherwise the `areYouSure` handler won't be executed, then there will be an annoying "confirm to leave" dialog // otherwise the `areYouSure` handler won't be executed, then there will be an annoying "confirm to leave" dialog
$(form).trigger('submit'); form.dispatchEvent(new SubmitEvent('submit', {bubbles: true, cancelable: true}));
} else { } else {
// if no form, then the editor is for an AJAX request, dispatch an event to the target, let the target's event handler to do the AJAX request. // if no form, then the editor is for an AJAX request, dispatch an event to the target, let the target's event handler to do the AJAX request.
// the 'ce-' prefix means this is a CustomEvent // the 'ce-' prefix means this is a CustomEvent

View file

@ -1,43 +1,41 @@
import $ from 'jquery'; import {POST} from '../../modules/fetch.js';
import {hideElem, showElem, toggleElem} from '../../utils/dom.js'; import {hideElem, showElem, toggleElem} from '../../utils/dom.js';
const {csrfToken} = window.config;
export function initCompWebHookEditor() { export function initCompWebHookEditor() {
if ($('.new.webhook').length === 0) { if (!document.querySelectorAll('.new.webhook').length) {
return; return;
} }
$('.events.checkbox input').on('change', function () { for (const input of document.querySelectorAll('.events.checkbox input')) {
if ($(this).is(':checked')) { input.addEventListener('change', function () {
showElem($('.events.fields')); if (this.checked) {
showElem('.events.fields');
} }
}); });
$('.non-events.checkbox input').on('change', function () { }
if ($(this).is(':checked')) {
hideElem($('.events.fields')); for (const input of document.querySelectorAll('.non-events.checkbox input')) {
input.addEventListener('change', function () {
if (this.checked) {
hideElem('.events.fields');
} }
}); });
}
const updateContentType = function () { const updateContentType = function () {
const visible = $('#http_method').val() === 'POST'; const visible = document.getElementById('http_method').value === 'POST';
toggleElem($('#content_type').parent().parent(), visible); toggleElem(document.getElementById('content_type').parentNode.parentNode, visible);
}; };
updateContentType(); updateContentType();
$('#http_method').on('change', () => {
updateContentType(); document.getElementById('http_method').addEventListener('change', updateContentType);
});
// Test delivery // Test delivery
$('#test-delivery').on('click', function () { document.getElementById('test-delivery')?.addEventListener('click', async function () {
const $this = $(this); this.classList.add('loading', 'disabled');
$this.addClass('loading disabled'); await POST(this.getAttribute('data-link'));
$.post($this.data('link'), {
_csrf: csrfToken
}).done(
setTimeout(() => { setTimeout(() => {
window.location.href = $this.data('redirect'); window.location.href = this.getAttribute('data-redirect');
}, 5000) }, 5000);
);
}); });
} }

View file

@ -1,11 +1,10 @@
import $ from 'jquery';
import {createApp} from 'vue'; import {createApp} from 'vue';
import ContextPopup from '../components/ContextPopup.vue'; import ContextPopup from '../components/ContextPopup.vue';
import {parseIssueHref} from '../utils.js'; import {parseIssueHref} from '../utils.js';
import {createTippy} from '../modules/tippy.js'; import {createTippy} from '../modules/tippy.js';
export function initContextPopups() { export function initContextPopups() {
const refIssues = $('.ref-issue'); const refIssues = document.querySelectorAll('.ref-issue');
attachRefIssueContextPopup(refIssues); attachRefIssueContextPopup(refIssues);
} }

View file

@ -0,0 +1,28 @@
import {createApp} from 'vue';
export async function initRepoContributors() {
const el = document.getElementById('repo-contributors-chart');
if (!el) return;
const {default: RepoContributors} = await import(/* webpackChunkName: "contributors-graph" */'../components/RepoContributors.vue');
try {
const View = createApp(RepoContributors, {
locale: {
filterLabel: el.getAttribute('data-locale-filter-label'),
contributionType: {
commits: el.getAttribute('data-locale-contribution-type-commits'),
additions: el.getAttribute('data-locale-contribution-type-additions'),
deletions: el.getAttribute('data-locale-contribution-type-deletions'),
},
loadingTitle: el.getAttribute('data-locale-loading-title'),
loadingTitleFailed: el.getAttribute('data-locale-loading-title-failed'),
loadingInfo: el.getAttribute('data-locale-loading-info'),
}
});
View.mount(el);
} catch (err) {
console.error('RepoContributors failed to load', err);
el.textContent = el.getAttribute('data-locale-component-failed-to-load');
}
}

View file

@ -194,7 +194,7 @@ export function initRepoCodeView() {
const blob = await $.get(`${url}?${query}&anchor=${anchor}`); const blob = await $.get(`${url}?${query}&anchor=${anchor}`);
currentTarget.closest('tr').outerHTML = blob; currentTarget.closest('tr').outerHTML = blob;
}); });
$(document).on('click', '.copy-line-permalink', async (e) => { $(document).on('click', '.copy-line-permalink', async ({currentTarget}) => {
await clippie(toAbsoluteUrl(e.currentTarget.getAttribute('data-url'))); await clippie(toAbsoluteUrl(currentTarget.getAttribute('data-url')));
}); });
} }

View file

@ -69,16 +69,12 @@ function initRepoIssueListCheckboxes() {
} }
} }
updateIssuesMeta( try {
url, await updateIssuesMeta(url, action, issueIDs, elementId);
action,
issueIDs,
elementId,
).then(() => {
window.location.reload(); window.location.reload();
}).catch((reason) => { } catch (err) {
showErrorToast(reason.responseJSON.error); showErrorToast(err.responseJSON?.error ?? err.message);
}); }
}); });
} }

View file

@ -344,19 +344,15 @@ export async function updateIssuesMeta(url, action, issueIds, elementId) {
export function initRepoIssueComments() { export function initRepoIssueComments() {
if ($('.repository.view.issue .timeline').length === 0) return; if ($('.repository.view.issue .timeline').length === 0) return;
$('.re-request-review').on('click', function (e) { $('.re-request-review').on('click', async function (e) {
e.preventDefault(); e.preventDefault();
const url = $(this).data('update-url'); const url = $(this).data('update-url');
const issueId = $(this).data('issue-id'); const issueId = $(this).data('issue-id');
const id = $(this).data('id'); const id = $(this).data('id');
const isChecked = $(this).hasClass('checked'); const isChecked = $(this).hasClass('checked');
updateIssuesMeta( await updateIssuesMeta(url, isChecked ? 'detach' : 'attach', issueId, id);
url, window.location.reload();
isChecked ? 'detach' : 'attach',
issueId,
id,
).then(() => window.location.reload());
}); });
$(document).on('click', (event) => { $(document).on('click', (event) => {

View file

@ -205,12 +205,15 @@ export function initRepoCommentForm() {
$listMenu.find('.no-select.item').on('click', function (e) { $listMenu.find('.no-select.item').on('click', function (e) {
e.preventDefault(); e.preventDefault();
if (hasUpdateAction) { if (hasUpdateAction) {
updateIssuesMeta( (async () => {
await updateIssuesMeta(
$listMenu.data('update-url'), $listMenu.data('update-url'),
'clear', 'clear',
$listMenu.data('issue-id'), $listMenu.data('issue-id'),
'', '',
).then(reloadConfirmDraftComment); );
reloadConfirmDraftComment();
})();
} }
$(this).parent().find('.item').each(function () { $(this).parent().find('.item').each(function () {
@ -248,12 +251,15 @@ export function initRepoCommentForm() {
$(this).addClass('selected active'); $(this).addClass('selected active');
if (hasUpdateAction) { if (hasUpdateAction) {
updateIssuesMeta( (async () => {
await updateIssuesMeta(
$menu.data('update-url'), $menu.data('update-url'),
'', '',
$menu.data('issue-id'), $menu.data('issue-id'),
$(this).data('id'), $(this).data('id'),
).then(reloadConfirmDraftComment); );
reloadConfirmDraftComment();
})();
} }
let icon = ''; let icon = '';
@ -281,12 +287,15 @@ export function initRepoCommentForm() {
}); });
if (hasUpdateAction) { if (hasUpdateAction) {
updateIssuesMeta( (async () => {
await updateIssuesMeta(
$menu.data('update-url'), $menu.data('update-url'),
'', '',
$menu.data('issue-id'), $menu.data('issue-id'),
$(this).data('id'), $(this).data('id'),
).then(reloadConfirmDraftComment); );
reloadConfirmDraftComment();
})();
} }
$list.find('.selected').html(''); $list.find('.selected').html('');

View file

@ -1,12 +1,10 @@
import $ from 'jquery';
export function initSshKeyFormParser() { export function initSshKeyFormParser() {
// Parse SSH Key // Parse SSH Key
$('#ssh-key-content').on('change paste keyup', function () { document.getElementById('ssh-key-content')?.addEventListener('input', function () {
const arrays = $(this).val().split(' '); const arrays = this.value.split(' ');
const $title = $('#ssh-key-title'); const title = document.getElementById('ssh-key-title');
if ($title.val() === '' && arrays.length === 3 && arrays[2] !== '') { if (!title.value && arrays.length === 3 && arrays[2] !== '') {
$title.val(arrays[2]); title.value = arrays[2];
} }
}); });
} }

View file

@ -1,18 +1,19 @@
import $ from 'jquery';
import {hideElem, showElem} from '../utils/dom.js'; import {hideElem, showElem} from '../utils/dom.js';
export function initUserSettings() { export function initUserSettings() {
if ($('.user.settings.profile').length > 0) { if (document.querySelectorAll('.user.settings.profile').length === 0) return;
$('#username').on('keyup', function () {
const $prompt = $('#name-change-prompt'); const usernameInput = document.getElementById('username');
const $prompt_redirect = $('#name-change-redirect-prompt'); if (!usernameInput) return;
if ($(this).val().toString().toLowerCase() !== $(this).data('name').toString().toLowerCase()) { usernameInput.addEventListener('input', function () {
showElem($prompt); const prompt = document.getElementById('name-change-prompt');
showElem($prompt_redirect); const promptRedirect = document.getElementById('name-change-redirect-prompt');
if (this.value.toLowerCase() !== this.getAttribute('data-name').toLowerCase()) {
showElem(prompt);
showElem(promptRedirect);
} else { } else {
hideElem($prompt); hideElem(prompt);
hideElem($prompt_redirect); hideElem(promptRedirect);
} }
}); });
}
} }

View file

@ -1,6 +1,9 @@
import * as htmx from 'htmx.org'; import * as htmx from 'htmx.org';
import {showErrorToast} from './modules/toast.js'; import {showErrorToast} from './modules/toast.js';
// https://github.com/bigskysoftware/idiomorph#htmx
import 'idiomorph/dist/idiomorph-ext.js';
// https://htmx.org/reference/#config // https://htmx.org/reference/#config
htmx.config.requestClass = 'is-loading'; htmx.config.requestClass = 'is-loading';
htmx.config.scrollIntoViewOnBoost = false; htmx.config.scrollIntoViewOnBoost = false;

View file

@ -83,6 +83,7 @@ import {initGiteaFomantic} from './modules/fomantic.js';
import {onDomReady} from './utils/dom.js'; import {onDomReady} from './utils/dom.js';
import {initRepoIssueList} from './features/repo-issue-list.js'; import {initRepoIssueList} from './features/repo-issue-list.js';
import {initCommonIssueListQuickGoto} from './features/common-issue-list.js'; import {initCommonIssueListQuickGoto} from './features/common-issue-list.js';
import {initRepoContributors} from './features/contributors.js';
import {initRepoDiffCommitBranchesAndTags} from './features/repo-diff-commit.js'; import {initRepoDiffCommitBranchesAndTags} from './features/repo-diff-commit.js';
import {initDirAuto} from './modules/dirauto.js'; import {initDirAuto} from './modules/dirauto.js';
@ -172,6 +173,7 @@ onDomReady(() => {
initRepoWikiForm(); initRepoWikiForm();
initRepository(); initRepository();
initRepositoryActionView(); initRepositoryActionView();
initRepoContributors();
initCommitStatuses(); initCommitStatuses();
initCaptcha(); initCaptcha();

View file

@ -1,4 +1,4 @@
import $ from 'jquery'; import {POST} from '../modules/fetch.js';
const preventListener = (e) => e.preventDefault(); const preventListener = (e) => e.preventDefault();
@ -55,12 +55,11 @@ export function initMarkupTasklist() {
const updateUrl = editContentZone.getAttribute('data-update-url'); const updateUrl = editContentZone.getAttribute('data-update-url');
const context = editContentZone.getAttribute('data-context'); const context = editContentZone.getAttribute('data-context');
await $.post(updateUrl, { const requestBody = new FormData();
ignore_attachments: true, requestBody.append('ignore_attachments', 'true');
_csrf: window.config.csrfToken, requestBody.append('content', newContent);
content: newContent, requestBody.append('context', context);
context await POST(updateUrl, {data: requestBody});
});
rawContent.textContent = newContent; rawContent.textContent = newContent;
} catch (err) { } catch (err) {

View file

@ -8,19 +8,17 @@ const safeMethods = new Set(['GET', 'HEAD', 'OPTIONS', 'TRACE']);
// fetch wrapper, use below method name functions and the `data` option to pass in data // fetch wrapper, use below method name functions and the `data` option to pass in data
// which will automatically set an appropriate headers. For json content, only object // which will automatically set an appropriate headers. For json content, only object
// and array types are currently supported. // and array types are currently supported.
export function request(url, {method = 'GET', headers = {}, data, body, ...other} = {}) { export function request(url, {method = 'GET', data, headers = {}, ...other} = {}) {
let contentType; let body, contentType;
if (!body) {
if (data instanceof FormData || data instanceof URLSearchParams) { if (data instanceof FormData || data instanceof URLSearchParams) {
body = data; body = data;
} else if (isObject(data) || Array.isArray(data)) { } else if (isObject(data) || Array.isArray(data)) {
contentType = 'application/json'; contentType = 'application/json';
body = JSON.stringify(data); body = JSON.stringify(data);
} }
}
const headersMerged = new Headers({ const headersMerged = new Headers({
...(!safeMethods.has(method.toUpperCase()) && {'x-csrf-token': csrfToken}), ...(!safeMethods.has(method) && {'x-csrf-token': csrfToken}),
...(contentType && {'content-type': contentType}), ...(contentType && {'content-type': contentType}),
}); });
@ -31,8 +29,8 @@ export function request(url, {method = 'GET', headers = {}, data, body, ...other
return fetch(url, { return fetch(url, {
method, method,
headers: headersMerged, headers: headersMerged,
...(body && {body}),
...other, ...other,
...(body && {body}),
}); });
} }

46
web_src/js/utils/time.js Normal file
View file

@ -0,0 +1,46 @@
import dayjs from 'dayjs';
// Returns an array of millisecond-timestamps of start-of-week days (Sundays)
export function startDaysBetween(startDate, endDate) {
// Ensure the start date is a Sunday
while (startDate.getDay() !== 0) {
startDate.setDate(startDate.getDate() + 1);
}
const start = dayjs(startDate);
const end = dayjs(endDate);
const startDays = [];
let current = start;
while (current.isBefore(end)) {
startDays.push(current.valueOf());
// we are adding 7 * 24 hours instead of 1 week because we don't want
// date library to use local time zone to calculate 1 week from now.
// local time zone is problematic because of daylight saving time (dst)
// used on some countries
current = current.add(7 * 24, 'hour');
}
return startDays;
}
export function firstStartDateAfterDate(inputDate) {
if (!(inputDate instanceof Date)) {
throw new Error('Invalid date');
}
const dayOfWeek = inputDate.getDay();
const daysUntilSunday = 7 - dayOfWeek;
const resultDate = new Date(inputDate.getTime());
resultDate.setDate(resultDate.getDate() + daysUntilSunday);
return resultDate.valueOf();
}
export function fillEmptyStartDaysWithZeroes(startDays, data) {
const result = {};
for (const startDay of startDays) {
result[startDay] = data[startDay] || {'week': startDay, 'additions': 0, 'deletions': 0, 'commits': 0};
}
return Object.values(result);
}

View file

@ -0,0 +1,15 @@
import {startDaysBetween} from './time.js';
test('startDaysBetween', () => {
expect(startDaysBetween(new Date('2024-02-15'), new Date('2024-04-18'))).toEqual([
1708214400000,
1708819200000,
1709424000000,
1710028800000,
1710633600000,
1711238400000,
1711843200000,
1712448000000,
1713052800000,
]);
});

View file

@ -173,9 +173,13 @@ export default {
], ],
}, },
plugins: [ plugins: [
new webpack.ProvidePlugin({ // for htmx extensions
htmx: 'htmx.org',
}),
new DefinePlugin({ new DefinePlugin({
__VUE_OPTIONS_API__: true, // at the moment, many Vue components still use the Vue Options API __VUE_OPTIONS_API__: true, // at the moment, many Vue components still use the Vue Options API
__VUE_PROD_DEVTOOLS__: false, // do not enable devtools support in production __VUE_PROD_DEVTOOLS__: false, // do not enable devtools support in production
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: false, // https://github.com/vuejs/vue-cli/pull/7443
}), }),
new VueLoaderPlugin(), new VueLoaderPlugin(),
new MiniCssExtractPlugin({ new MiniCssExtractPlugin({
@ -210,6 +214,7 @@ export default {
override: { override: {
'khroma@*': {licenseName: 'MIT'}, // https://github.com/fabiospampinato/khroma/pull/33 'khroma@*': {licenseName: 'MIT'}, // https://github.com/fabiospampinato/khroma/pull/33
'htmx.org@1.9.10': {licenseName: 'BSD-2-Clause'}, // "BSD 2-Clause" -> "BSD-2-Clause" 'htmx.org@1.9.10': {licenseName: 'BSD-2-Clause'}, // "BSD 2-Clause" -> "BSD-2-Clause"
'idiomorph@0.3.0': {licenseName: 'BSD-2-Clause'}, // "BSD 2-Clause" -> "BSD-2-Clause"
}, },
emitError: true, emitError: true,
allow: '(Apache-2.0 OR BSD-2-Clause OR BSD-3-Clause OR MIT OR ISC OR CPAL-1.0 OR Unlicense OR EPL-1.0 OR EPL-2.0)', allow: '(Apache-2.0 OR BSD-2-Clause OR BSD-3-Clause OR MIT OR ISC OR CPAL-1.0 OR Unlicense OR EPL-1.0 OR EPL-2.0)',