
Език 🇧🇬 Български
Документация
Първи стъпки
Аутентификация
Използване
Add Comments to Your iOS App
Това е официалната iOS библиотека за FastComments.
Вградете живи коментари, чат и уиджети за ревюта в iOS приложението си.
Репозитория
Функции 
- Разклонени дървета от коментари с вложени отговори и пагинация
- Социална емисия със създаване на публикации, реакции и прикачени медии
- Режим на жив чат с автоматично превъртане и разделители за дати
- Актуализации в реално време чрез WebSocket (нови коментари, гласове, присъствие)
- Еднократно влизане (Simple SSO за тестване, Secure SSO за продукция)
- Редактиране на богато форматиран текст с удебелен, курсив, код и @споменавания
- Гласуване с конфигурируеми стилове (стрелки нагоре/надолу или сърца)
- Модераторски действия: маркиране, закрепване, заключване, блокиране
- Разширено тематизиране с предварително зададени стилове и пълна персонализация
- Персонализирани бутони в лентата с инструменти за коментари и създаване на публикации в емисията
- Качване на изображения
- Поддръжка за региона на ЕС
- Присъствие на потребители (индикатори за онлайн/офлайн)
- Филтриране на емисията по етикети
- Поддръжка за локализация
Инсталиране 
Добавете FastCommentsUI към вашия проект, като използвате Swift Package Manager.
В Xcode: File > Add Package Dependencies, след това въведете URL адреса на репозиторията.
Или го добавете във вашия Package.swift:
dependencies: [
.package(url: "https://github.com/fastcomments/fastcomments-ios.git", from: "1.0.0")
]
След това добавете продукта към вашата цел:
.target(
name: "YourApp",
dependencies: [
.product(name: "FastCommentsUI", package: "fastcomments-ios")
]
)
Импортирайте и двата модула, където е необходимо:
import FastCommentsUI
import FastCommentsSwift
Бърз старт 
Минималната настройка за показване на коментарен уиджет:
import SwiftUI
import FastCommentsUI
struct ContentView: View {
@StateObject private var sdk = FastCommentsSDK(
config: FastCommentsWidgetConfig(
tenantId: "demo",
urlId: "my-page-1",
url: "https://example.com/page-1",
pageTitle: "My Page"
)
)
var body: some View {
FastCommentsView(sdk: sdk)
.task {
try? await sdk.load()
}
}
}
Заменете "demo" с вашия FastComments tenant ID. urlId идентифицира страницата или нишката, където се съхраняват коментарите.
Аутентификация (SSO) 
FastComments поддържа три режима на удостоверяване:
- Анонимен -- няма SSO токен; потребителите получават идентичности, базирани на сесия
- Simple SSO -- клиентски токен за демота и тестове (не е сигурно)
- Secure SSO -- сървърно-подписан токен за продукция
Simple SSO
Подходящ за демота и локални тестове. Всеки може да се представи като всеки потребител със Simple SSO, затова не го използвайте в продукция.
import FastCommentsSwift
let userData = SimpleSSOUserData(
username: "Jane Doe",
email: "jane@example.com",
avatar: "https://example.com/avatar.jpg"
)
let sso = FastCommentsSSO.createSimple(simpleSSOUserData: userData)
let token = try? sso.prepareToSend()
let config = FastCommentsWidgetConfig(
tenantId: "YOUR_TENANT_ID",
urlId: "my-page-1",
sso: token
)
let sdk = FastCommentsSDK(config: config)
SimpleSSOUserData също така поддържа опционални полета:
id-- потребителско ID (по подразбиране е email, ако не е зададено)displayName-- отделно показвано имеdisplayLabel-- персонализиран етикет, показван до името (например "VIP")websiteUrl-- връзка към името на потребителяlocale-- код на локалаisProfileActivityPrivate-- скриване на активността от профила (по подразбиране true)
Secure SSO
В продукция вашият бекенд генерира подписан SSO токен, използвайки вашия API секрет. iOS приложението извлича този токен от вашия сървър и го подава към конфигурацията.
On your backend (using the FastComments Swift SDK or any language):
let userData = SecureSSOUserData(
id: "user-123",
email: "user@example.com",
username: "Display Name",
avatar: "https://example.com/avatar.jpg"
)
let sso = try FastCommentsSSO.createSecure(apiKey: "YOUR_API_KEY", secureSSOUserData: userData)
let token = try sso.prepareToSend()
// Върнете този токен на вашето iOS приложение чрез вашето API
In your iOS app:
struct MyView: View {
@StateObject private var sdk = FastCommentsSDK(
config: FastCommentsWidgetConfig(
tenantId: "YOUR_TENANT_ID",
urlId: "my-page-1"
)
)
@State private var isLoadingToken = true
var body: some View {
Group {
if isLoadingToken {
ProgressView("Loading...")
} else {
FastCommentsView(sdk: sdk)
}
}
.task {
// Извлечете токена от вашия бекенд
let token = try? await fetchSSOTokenFromYourBackend()
// Създайте нова конфигурация с токена, или го задайте преди зареждане
isLoadingToken = false
try? await sdk.load()
}
}
}
SecureSSOUserData поддържа допълнителни полета:
optedInNotifications-- съгласие за имейл известияdisplayLabel-- персонализиран етикетdisplayName-- показвано имеwebsiteUrl-- URL на уебсайтgroupIds-- членства в групиisAdmin-- администраторски привилегииisModerator-- модераторски привилегииisProfileActivityPrivate-- поверителност на профилната активност
Коментари в нишки 
Основно използване
struct CommentsPage: View {
@StateObject private var sdk = FastCommentsSDK(
config: FastCommentsWidgetConfig(
tenantId: "YOUR_TENANT_ID",
urlId: "article-42",
url: "https://example.com/article/42",
pageTitle: "Article Title"
)
)
var body: some View {
FastCommentsView(sdk: sdk)
.task {
try? await sdk.load()
}
}
}
Стилове гласуване
По подразбиране стилът за гласуване показва стрелки нагоре/надолу. Използвайте ._1 за стил гласуване със сърце:
FastCommentsView(sdk: sdk, voteStyle: ._1)
| Стил | Външен вид |
|---|---|
._0 |
Бутони със стрелки нагоре/надолу с нетен брой |
._1 |
Един бутон със сърце и брой |
Обратни повиквания за събития
Използвайте модификаторни callback-и за обработка на потребителските взаимодействия:
FastCommentsView(sdk: sdk)
.onCommentPosted { comment in
print("New comment: \(comment.commentHTML)")
}
.onReplyClick { renderableComment in
print("Replying to: \(renderableComment.comment.id)")
}
.onUserClick { context, userInfo, source in
// source е .name или .avatar
print("Tapped \(userInfo.displayName)")
}
Прилагане на тема
Подайте тема чрез SwiftUI средата:
FastCommentsView(sdk: sdk)
.fastCommentsTheme(myTheme)
.task { try? await sdk.load() }
Или я задайте директно в SDK:
sdk.theme = FastCommentsTheme.modern
Посока на сортиране
sdk.defaultSortDirection = .nf // Най-нови първи (по подразбиране)
sdk.defaultSortDirection = .of // Най-стари първи
sdk.defaultSortDirection = .mr // Най-релевантни
Чат на живо 
LiveChatView предоставя чат в реално време с автоматично превъртане, разделители по дати и компактен изглед. Той автоматично конфигурира SDK за сортиране от най-старите към най-новите и незабавно показване на живо.
struct ChatView: View {
@StateObject private var sdk: FastCommentsSDK = {
let config = FastCommentsWidgetConfig(
tenantId: "YOUR_TENANT_ID",
urlId: "chat-room-1",
sso: ssoToken // Препоръчва се SSO, за да имат потребителите имена
)
return FastCommentsSDK(config: config)
}()
var body: some View {
LiveChatView(sdk: sdk)
.onCommentPosted { comment in
print("Sent: \(comment.commentHTML)")
}
.task {
try? await sdk.load()
}
}
}
LiveChatView supports these callbacks:
.onCommentPosted-- извиква се, когато потребителят изпрати съобщение.onCommentDeleted-- извиква се, когато съобщение бъде изтрито.onUserClick-- извиква се, когато името или аватарът на потребител бъде докоснат
Социална емисия 
Системата за емисия е отделен SDK (FastCommentsFeedSDK) с собствен изглед.
Зареждане и показване на емисията
struct FeedPage: View {
@StateObject private var sdk: FastCommentsFeedSDK = {
let config = FastCommentsWidgetConfig(
tenantId: "YOUR_TENANT_ID",
urlId: "my-feed",
sso: ssoToken
)
return FastCommentsFeedSDK(config: config)
}()
@State private var commentsPost: FeedPost?
var body: some View {
FastCommentsFeedView(sdk: sdk)
.onPostSelected { post in
commentsPost = post
}
.onCommentsRequested { post in
commentsPost = post
}
.onSharePost { post in
// Покажете лист за споделяне
}
.onUserClick { context, userInfo, source in
// Навигирайте до профила на потребителя
}
.onMediaClick { mediaItem, index in
// Покажете изображение на цял екран
}
.task {
try? await sdk.loadIfNeeded()
}
}
}
Изгледът на емисията включва автоматично опресняване чрез издърпване и безкрайно превъртане.
Използвайте loadIfNeeded() при връщане в жизнения цикъл на екрана, за да предотвратите нулиране на вече заредена или възстановена емисия обратно на страница 1.
Създаване на публикации
Използвайте FeedPostCreateView, за да покажете формуляр за създаване на публикация:
@State private var showCreatePost = false
// In your view body:
.sheet(isPresented: $showCreatePost) {
FeedPostCreateView(
sdk: sdk,
onPostCreated: { post in
showCreatePost = false
Task { try? await sdk.refresh() }
},
onCancelled: {
showCreatePost = false
}
)
}
Реакции към публикации
SDK обработва реакциите с оптимистични ъпдейти:
try await sdk.reactPost(postId: post.id, reactionType: "l")
// Проверете състоянието на реакцията
let hasLiked = sdk.hasUserReacted(postId: post.id, reactType: "l")
let likeCount = sdk.getLikeCount(postId: post.id)
Отваряне на коментари за публикация
Използвайте CommentsSheet, за да покажете коментарите за публикация в емисията. Той създава вътрешно инстанция FastCommentsSDK, използвайки конфигурацията на feed SDK:
.sheet(item: $commentsPost) { post in
CommentsSheet(post: post, feedSDK: sdk, onUserClick: { context, userInfo, source in
// Обработете клик върху потребител
})
}
Забележка: FeedPost трябва да съответства на Identifiable за .sheet(item:). Добавете това разширение:
extension FeedPost: @retroactive Identifiable {}
Филтриране на емисия по тагове
Реализирайте протокола TagSupplier, за да филтрирате публикациите в емисията по тагове:
struct TeamTagSupplier: TagSupplier {
func getTags(currentUser: UserSessionInfo?) -> [String]? {
guard let user = currentUser else { return nil }
return ["team:\(user.id ?? "")", "public"]
}
}
sdk.tagSupplier = TeamTagSupplier()
Върнете nil за нефилтрирана глобална емисия.
Запазване и възстановяване на състоянието на емисията
Запазете състоянието на пагинацията през събития от жизнения цикъл на изгледа:
let state = sdk.savePaginationState()
// Later...
sdk.restorePaginationState(state)
try? await sdk.loadIfNeeded()
Ако вашият екран изчезне временно, изгледът на емисията автоматично паузира живите ъпдейти и ги възобновява при повторно появяване без да изчиства заредените публикации. Извиквайте sdk.cleanup() само когато наистина сте приключили с инстанцията на SDK.
Изтриване на публикации
sdk.onPostDeleted = { postId in
print("Post \(postId) was deleted")
}
Персонализиране на тема 
Предварителни настройки на тема
Налични са четири вградени предварителни настройки:
// Системни настройки по подразбиране
sdk.theme = FastCommentsTheme.default
// Карти с сенки и големи заоблени ъгли
sdk.theme = FastCommentsTheme.modern
// Плосък, без сенки, малък радиус на ъглите, без линии на нишката
sdk.theme = FastCommentsTheme.minimal
// Задава всички цветове за действията като един брандов цвят
sdk.theme = FastCommentsTheme.allPrimary(.indigo)
Стилове на показване на коментари
var theme = FastCommentsTheme()
theme.commentStyle = .flat // Плосък списък с разделители (по подразбиране)
theme.commentStyle = .card // Заоблени карти със сенки
theme.commentStyle = .bubble // Стил тип чат балон
Цветове
Всички цветови свойства са незадължителни. Непопълнените стойности се връщат към разумни системни стойности по подразбиране.
var theme = FastCommentsTheme()
// Бранд цветове
theme.primaryColor = .indigo
theme.primaryLightColor = .indigo.opacity(0.6)
theme.primaryDarkColor = Color(red: 0.2, green: 0.1, blue: 0.5)
// Фонове
theme.commentBackgroundColor = Color(.secondarySystemGroupedBackground)
theme.containerBackgroundColor = Color(.systemGroupedBackground)
// Бутони за действие
theme.actionButtonColor = .indigo
theme.replyButtonColor = .indigo
theme.toggleRepliesButtonColor = .indigo.opacity(0.8)
theme.loadMoreButtonTextColor = .indigo
// Гласувания
theme.voteActiveColor = .red
theme.voteCountColor = .primary
theme.voteCountZeroColor = .secondary
theme.voteDividerColor = Color(.separator)
// Връзки
theme.linkColor = .indigo
theme.linkColorPressed = .indigo.opacity(0.5)
// Диалози
theme.dialogHeaderBackgroundColor = .indigo
theme.dialogHeaderTextColor = .white
// Лента за въвеждане
theme.inputBarBackgroundColor = Color(.systemBackground)
theme.inputBarBorderColor = Color(.separator)
// Други
theme.onlineIndicatorColor = .green
theme.separatorColor = Color(.separator)
theme.badgeBackgroundColor = .gray.opacity(0.2)
theme.threadLineColor = .indigo.opacity(0.15)
Типография
theme.commenterNameFont = .subheadline.weight(.bold)
theme.bodyFont = .body
theme.captionFont = .caption
theme.actionFont = .caption.weight(.medium)
Оформление и разстояния
theme.cornerRadius = .large // .none, .small, .medium, .large
theme.commentSpacing = 4 // Точки между редовете с коментари
theme.nestingIndent = 20 // Отместване (в точки) за всяко ниво на вложеност
theme.avatarSize = 36 // Диаметър на аватара за кореновите коментари
theme.replyAvatarSize = 28 // Диаметър на аватара за вложени отговори
Визуални ефекти
theme.showShadows = true // Нежни сенки върху картите
theme.showThreadLine = true // Вертикална линия свързваща вложените отговори
theme.animateVotes = true // Пружинна анимация при промяна на гласовете
Прилагане на теми
Два подхода:
// Чрез средата на SwiftUI (препоръчително за йерархията на изгледите)
FastCommentsView(sdk: sdk)
.fastCommentsTheme(theme)
// Директно в SDK
sdk.theme = theme
Персонализирани бутони в лентата с инструменти 
Бутони в лентата с инструменти за коментари
Имплементирайте протокола CustomToolbarButton, за да добавите бутони в лентата с инструменти за въвеждане на коментари:
struct EmojiButton: CustomToolbarButton {
let id = "emoji"
let iconSystemName = "face.smiling" // Име на SF Symbol
let contentDescription = "Add Emoji"
let badgeText: String? = nil // Опционален брой за значка
func onClick(text: Binding<String>) {
text.wrappedValue += "\u{1F44D}"
}
// Optional overrides (default to true)
func isEnabled() -> Bool { true }
func isVisible() -> Bool { true }
}
Подайте персонализирани бутони при създаване на изгледа:
FastCommentsView(
sdk: sdk,
customToolbarButtons: [EmojiButton(), CodeBlockButton()]
)
Или ги добавете глобално в SDK (важи за всички инстанции):
sdk.addGlobalCustomToolbarButton(EmojiButton())
sdk.removeGlobalCustomToolbarButton(id: "emoji")
sdk.clearGlobalCustomToolbarButtons()
Бутони в лентата с инструменти за емисията
Имплементирайте FeedCustomToolbarButton за формата за създаване на публикация:
struct HashtagButton: FeedCustomToolbarButton {
let id = "hashtag"
let iconSystemName = "number"
let contentDescription = "Add Hashtag"
func onClick(content: Binding<String>) {
content.wrappedValue += "#"
}
}
Подайте ги в изгледа за създаване:
FeedPostCreateView(
sdk: sdk,
customToolbarButtons: [HashtagButton()],
onPostCreated: { _ in },
onCancelled: { }
)
Или ги задайте глобално в feed SDK:
sdk.globalFeedToolbarButtons = [HashtagButton()]
Модерация 
Действия, достъпни за всички потребители
- Поставяне/Премахване на флаг -- докладване на коментар за преглед
try await sdk.flagComment(commentId: commentId)
try await sdk.unflagComment(commentId: commentId)
- Блокиране/Разблокиране -- скриване на всички коментари от потребител (за текущия зрител)
try await sdk.blockUser(commentId: commentId)
try await sdk.unblockUser(commentId: commentId)
Действия само за администратори
- Прикрепване/Откачване -- прикрепяне на коментар в горната част на нишката
try await sdk.pinComment(commentId: commentId)
try await sdk.unpinComment(commentId: commentId)
- Заключване/Отключване -- предотвратяване на нови отговори на коментар
try await sdk.lockComment(commentId: commentId)
try await sdk.unlockComment(commentId: commentId)
Всички модерационни действия са налични и чрез контекстното меню на коментара в потребителския интерфейс. Администраторските действия се показват само когато текущият потребител е администратор на сайта (зададено чрез SSO флага isAdmin или конфигурация в таблото за управление).
Актуализации в реално време 
След извикване на sdk.load(), SDK автоматично се абонира за WebSocket събития за конфигурирания urlId. Следните събития се обработват:
- Нови коментари, редакции и изтривания
- Гласувания (нови и премахнати)
- Промени в състоянието на закрепване, заключване, маркиране и блокиране
- Присъствие на потребители (влизане/напускане)
- Отваряне/затваряне на нишки
- Присъждане на значки
- Актуализации на конфигурацията на сървъра
Управление на живото показване
По подразбиране новите коментари от други потребители се появяват веднага:
sdk.showLiveRightAway = true // По подразбиране: показва се веднага
Задайте това на false, за да буферирате новите коментари зад бутон "N нови коментара", позволявайки на потребителя да избере кога да ги разкрие:
sdk.showLiveRightAway = false
Присъствие на потребителите
Индикаторите за онлайн/офлайн се появяват автоматично върху аватарите на потребителите, когато сървърът активира проследяването на присъствията. Не е необходима допълнителна конфигурация на клиента.
Пагинация 
Размер на страницата
// Коментари: по подразбиране 30
sdk.pageSize = 50
// Фийд: по подразбиране 10
feedSDK.pageSize = 20
Зареждане на още коментари
Потребителският интерфейс автоматично показва контролите за странициране. Можете също да задействате странициране програмно:
// Зареди следващата страница
try await sdk.loadMore()
// Зареди всички останали (деактивирано ако има >2000 коментара поради производителност)
try await sdk.loadAll()
// Проверка на състоянието
sdk.hasMore // Дали има още страници
sdk.shouldShowLoadAll()
sdk.getCountRemainingToShow()
Странициране на вложени коментари
Вложените отговори се зареждат лениво. Когато потребителят разшири нишка, първите 5 вложени отговора се зареждат. Ако има още, се появява контрол "зареди още отговори". Това се обработва автоматично от потребителския интерфейс.
Състояние и наблюдаемост 
И двете FastCommentsSDK и FastCommentsFeedSDK са класове ObservableObject с свойства @Published. Можете да наблюдавате тези свойства във вашите SwiftUI изгледи за реактивни актуализации на потребителския интерфейс.
Публикувани свойства на FastCommentsSDK
| Свойство | Тип | Описание |
|---|---|---|
commentCountOnServer |
Int |
Общ брой коментари на сървъра |
newRootCommentCount |
Int |
Буферирани нови коментари (когато showLiveRightAway е false) |
currentUser |
UserSessionInfo? |
Текущ удостоверен потребител |
isSiteAdmin |
Bool |
Дали текущият потребител е администратор на сайта |
isClosed |
Bool |
Дали нишката с коментари е затворена |
hasBillingIssue |
Bool |
Има ли проблем с фактурирането |
isLoading |
Bool |
Дали се изпълнява мрежова заявка |
hasMore |
Bool |
Има ли още страници с коментари |
blockingErrorMessage |
String? |
Грешка, която предотвратява функционирането на потребителския интерфейс |
warningMessage |
String? |
Неблокиращо предупредително съобщение |
isDemo |
Bool |
Дали работи в демонстрационен режим |
commentsVisible |
Bool |
Превключвател за видимостта на коментарите |
toolbarEnabled |
Bool |
Дали панелът за форматиране е показан |
Публикувани свойства на FastCommentsFeedSDK
| Свойство | Тип | Описание |
|---|---|---|
feedPosts |
[FeedPost] |
В момента заредени публикации във фийда |
hasMore |
Bool |
Има ли още страници |
currentUser |
UserSessionInfo? |
Текущ удостоверен потребител |
blockingErrorMessage |
String? |
Блокиращо съобщение за грешка |
isLoading |
Bool |
Дали се изпълнява мрежова заявка |
newPostsCount |
Int |
Брой нови публикации от последното зареждане |
Дървото на коментарите
Дървото на коментарите е достъпно чрез sdk.commentsTree:
// Плосък списък с видимите възли за рендиране
sdk.commentsTree.visibleNodes
// Намиране на коментар по ID
sdk.commentsTree.commentsById["comment-id"]
Регион на ЕС 
За да използвате центъра за данни в ЕС, задайте полето region във вашата конфигурация:
let config = FastCommentsWidgetConfig(
tenantId: "YOUR_TENANT_ID",
urlId: "my-page",
region: "eu"
)
Това насочва всички API заявки и WebSocket връзки към eu.fastcomments.com.
Почистване 
Когато приключите с екземпляр на SDK (например когато изгледът се затваря), извикайте cleanup(), за да затворите WebSocket връзката и да отмените фоновите задачи:
sdk.cleanup()
За изгледи, управлявани от SwiftUI чрез @StateObject, това обикновено се извиква в .onDisappear или когато изгледът бъде освободен от паметта.
Качване на изображения 
Коментари
let imageUrl = try await sdk.uploadImage(imageData: jpegData, filename: "photo.jpg")
Връща низ със URL адреса на каченото изображение.
Публикации в емисията
let mediaItem = try await feedSDK.uploadImage(imageData: jpegData, filename: "photo.jpg")
// Качване на няколко изображения паралелно
let mediaItems = try await feedSDK.uploadImages(images: [
(jpegData1, "photo1.jpg"),
(jpegData2, "photo2.jpg")
])
Споменавания на потребители 
Търсене на потребители за автоматичното довършване при @mention:
let results = try await sdk.searchUsers(query: "jan")
// Връща [UserSearchResult] с userId, username, avatar и т.н.
Вградената CommentInputBar се грижи за автоматичното довършване на @mention.
Редактиране и изтриване на коментари 
Редактиране
try await sdk.editComment(commentId: commentId, newText: "Updated text")
Сървърът рендерира HTML отново. Локалният коментар се актуализира автоматично.
Изтриване
try await sdk.deleteComment(commentId: commentId)
Изтриването на коментар също премахва неговите наследници от локалното дърво.
И двете действия са налични чрез контекстното меню на коментара в интерфейса, когато текущият потребител е авторът на коментара (или администратор на сайта).
Обработка на грешки 
Методите на SDK хвърлят FastCommentsError, който съответства на LocalizedError:
do {
try await sdk.load()
} catch let error as FastCommentsError {
print(error.translatedError ?? error.reason ?? "Unknown error")
} catch {
print(error.localizedDescription)
}
FastCommentsError свойства:
code-- код на грешка от API-тоreason-- описание на грешката на английскиtranslatedError-- локализирано съобщение за грешка, предоставено от сървъра
Blocking errors are also surfaced automatically via sdk.blockingErrorMessage, which the built-in views display to the user.
Локализация 
Предайте код на локал в конфигурацията, за да локализирате низовете, предоставени от сървъра:
let config = FastCommentsWidgetConfig(
tenantId: "YOUR_TENANT_ID",
urlId: "my-page",
locale: "fr_fr"
)
Низовете на потребителския интерфейс на клиентската страна използват локализация, базирана на iOS bundle.
Примерно приложение 
Хранилището съдържа пълно примерно приложение в ExampleApp/ с демонстрации на:
- Вложени коментари с SSO и персонализирани теми
- Социална емисия със създаване на публикации и филтриране по тагове
- Чат на живо
- Прости и сигурни SSO потоци
- Персонализирани бутони на лентата с инструменти (коментари и емисия)
Нужда от помощ?
Ако срещнете проблеми или имате въпроси относно iOS библиотеката, моля:
Принос
Приноси са добре дошли! Моля, посетете GitHub хранилището за указания относно приноса.