FastComments.com

Add Comments to Your iOS App

Это официальная библиотека iOS для FastComments.

Встраивайте виджеты живых комментариев, чата и отзывов в ваше iOS-приложение.

Репозиторий

Просмотреть на GitHub


Возможности Internal Link

  • Древовидные комментарии с вложенными ответами и пагинацией
  • Социальная лента с созданием постов, реакциями и медиа-вложениями
  • Режим живого чата с автопрокруткой и разделителями по датам
  • Обновления в реальном времени через WebSocket (новые комментарии, голоса, присутствие)
  • Единый вход (Simple SSO для тестирования, Secure SSO для продакшена)
  • Редактор с расширенным форматированием: жирный, курсив, код и @mentions
  • Голосование с настраиваемыми стилями (стрелки вверх/вниз или сердца)
  • Действия модерации: flag, pin, lock, block
  • Всеобъемлющее оформление с предустановками и полной кастомизацией
  • Пользовательские кнопки на панели инструментов для создания комментариев и постов в ленте
  • Загрузка изображений
  • Поддержка региона ЕС
  • Присутствие пользователей (индикаторы онлайн/оффлайн)
  • Фильтрация ленты по тегам
  • Поддержка локализации

Требования Internal Link

  • iOS 16+ или macOS 14+
  • Swift 5.9+
  • SwiftUI

Установка Internal Link

Добавьте FastCommentsUI в ваш проект с помощью Swift Package Manager.

В Xcode: Файл > Добавить зависимости пакета, затем введите 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

Быстрый старт Internal Link

Минимальная настройка для отображения виджета комментариев:

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) Internal Link


FastComments поддерживает три режима аутентификации:

  1. Анонимный -- без SSO-токена; пользователи получают идентичности на основе сессии
  2. Простой SSO -- токен на стороне клиента для демонстраций и тестирования (небезопасно)
  3. Защищённый SSO -- токен, подписанный сервером, для боевого использования

Простой SSO

Полезен для демонстраций и локального тестирования. С помощью Простой 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 -- идентификатор пользователя (по умолчанию email, если не указан)
  • displayName -- отдельное отображаемое имя
  • displayLabel -- пользовательская метка, показываемая рядом с именем (например, "VIP")
  • websiteUrl -- ссылка на имени пользователя
  • locale -- код локали
  • isProfileActivityPrivate -- скрыть активность в профиле (по умолчанию true)

Защищённый SSO

В продакшене ваш бэкенд генерирует подписанный SSO-токен, используя ваш API-секрет. iOS-приложение запрашивает этот токен у сервера и передаёт его в конфигурацию.

На вашем бэкенде (используя FastComments Swift SDK или любой другой язык):

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

В вашем iOS-приложении:

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()
            // Создайте новый config с токеном или установите его до загрузки
            isLoadingToken = false
            try? await sdk.load()
        }
    }
}

SecureSSOUserData поддерживает дополнительные поля:

  • optedInNotifications -- подписка на уведомления по email
  • displayLabel -- пользовательская метка
  • displayName -- отображаемое имя
  • websiteUrl -- URL веб-сайта
  • groupIds -- членство в группах
  • isAdmin -- привилегии администратора
  • isModerator -- привилегии модератора
  • isProfileActivityPrivate -- приватность активности профиля


Вложенные комментарии Internal Link

Базовое использование

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 Одна кнопка в виде сердца с отображением количества

Обработчики событий

Используйте колбэки в стиле модификаторов для обработки взаимодействий пользователя:

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
        // источник: .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  // Наиболее релевантные


Чат в реальном времени Internal Link


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 поддерживает следующие обратные вызовы:

  • .onCommentPosted -- срабатывает, когда пользователь отправляет сообщение
  • .onCommentDeleted -- срабатывает, когда сообщение удалено
  • .onUserClick -- срабатывает, когда нажимают на имя пользователя или на аватар


Социальная лента Internal Link

Система ленты — отдельный 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.load()
            }
    }
}

Представление ленты автоматически поддерживает pull-to-refresh и бесконечную прокрутку.

Создание постов

Используйте FeedPostCreateView для отображения формы создания поста:

@State private var showCreatePost = false

// В теле вашего представления:
.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")

// Check reaction state
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)

Удаление постов

sdk.onPostDeleted = { postId in
    print("Post \(postId) was deleted")
}

Настройка темы Internal Link

Предустановки темы

Доступны четыре встроенные предустановки:

// System defaults
sdk.theme = FastCommentsTheme.default

// Cards with shadows and large rounded corners
sdk.theme = FastCommentsTheme.modern

// Flat, no shadows, small corner radius, no thread lines
sdk.theme = FastCommentsTheme.minimal

// Set all action colors to a single brand color
sdk.theme = FastCommentsTheme.allPrimary(.indigo)

Стили отображения комментариев

var theme = FastCommentsTheme()
theme.commentStyle = .flat    // Flat list with dividers (default)
theme.commentStyle = .card    // Rounded cards with shadows
theme.commentStyle = .bubble  // Chat bubble style

Цвета

Все свойства цвета необязательны. Незаполненные значения возвращаются к разумным системным значениям по умолчанию.

var theme = FastCommentsTheme()

// Brand colors
theme.primaryColor = .indigo
theme.primaryLightColor = .indigo.opacity(0.6)
theme.primaryDarkColor = Color(red: 0.2, green: 0.1, blue: 0.5)

// Backgrounds
theme.commentBackgroundColor = Color(.secondarySystemGroupedBackground)
theme.containerBackgroundColor = Color(.systemGroupedBackground)

// Action buttons
theme.actionButtonColor = .indigo
theme.replyButtonColor = .indigo
theme.toggleRepliesButtonColor = .indigo.opacity(0.8)
theme.loadMoreButtonTextColor = .indigo

// Votes
theme.voteActiveColor = .red
theme.voteCountColor = .primary
theme.voteCountZeroColor = .secondary
theme.voteDividerColor = Color(.separator)

// Links
theme.linkColor = .indigo
theme.linkColorPressed = .indigo.opacity(0.5)

// Dialogs
theme.dialogHeaderBackgroundColor = .indigo
theme.dialogHeaderTextColor = .white

// Input bar
theme.inputBarBackgroundColor = Color(.systemBackground)
theme.inputBarBorderColor = Color(.separator)

// Other
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          // Subtle shadows on cards
theme.showThreadLine = true       // Vertical line connecting nested replies
theme.animateVotes = true         // Spring animation on vote changes

Применение тем

Два подхода:

// Via SwiftUI environment (recommended for view hierarchy)
FastCommentsView(sdk: sdk)
    .fastCommentsTheme(theme)

// Directly on the SDK
sdk.theme = theme


Пользовательские кнопки панели инструментов Internal Link

Кнопки панели инструментов комментариев

Реализуйте протокол 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}"
    }

    // Необязательные переопределения (по умолчанию 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: { }
)

Или установите их глобально в SDK ленты:

sdk.globalFeedToolbarButtons = [HashtagButton()]


Модерация Internal Link


Действия, доступные всем пользователям

  • Flag/Unflag -- сообщить о комментарии для проверки
try await sdk.flagComment(commentId: commentId)
try await sdk.unflagComment(commentId: commentId)
  • Block/Unblock -- скрыть все комментарии пользователя (для конкретного просматривающего)
try await sdk.blockUser(commentId: commentId)
try await sdk.unblockUser(commentId: commentId)

Действия, доступные только администраторам

  • Pin/Unpin -- закрепить/открепить комментарий вверху ветки обсуждения
try await sdk.pinComment(commentId: commentId)
try await sdk.unpinComment(commentId: commentId)
  • Lock/Unlock -- запретить/разрешить новые ответы на комментарий
try await sdk.lockComment(commentId: commentId)
try await sdk.unlockComment(commentId: commentId)

Все действия модерации также доступны через контекстное меню комментария в интерфейсе. Действия администратора отображаются только когда текущий пользователь является администратором сайта (устанавливается через SSO isAdmin флаг или конфигурацию в панели управления).



Обновления в реальном времени Internal Link

После вызова sdk.load(), SDK автоматически подписывается на события WebSocket для настроенного urlId. Обрабатываются следующие события:

  • Новые комментарии, правки и удаления
  • Голоса (новые и удалённые)
  • Изменения состояний pin, lock, flag и block
  • Присутствие пользователей (вход/выход)
  • Открытие/закрытие треда
  • Вручение бейджей
  • Обновления конфигурации сервера

Управление отображением в реальном времени

По умолчанию новые комментарии от других пользователей появляются сразу:

sdk.showLiveRightAway = true   // По умолчанию: показывать сразу

Установите это в false, чтобы буферизовать новые комментарии за кнопкой «N новых комментариев», позволяя пользователю решать, когда их показать:

sdk.showLiveRightAway = false

Присутствие пользователей

Индикаторы онлайн/оффлайн автоматически появляются на аватарах пользователей, когда сервер включает отслеживание присутствия. На клиенте дополнительная конфигурация не требуется.



Пагинация Internal Link

Размер страницы

// Комментарии: по умолчанию 30
sdk.pageSize = 50

// Лента: по умолчанию 10
feedSDK.pageSize = 20

Загрузка дополнительных комментариев

Интерфейс автоматически отображает элементы управления пагинацией. Также вы можете инициировать пагинацию программно:

// Загрузить следующую страницу
try await sdk.loadMore()

// Загрузить все оставшиеся (отключено, если >2000 комментариев из соображений производительности)
try await sdk.loadAll()

// Проверить состояние
sdk.hasMore            // Есть ли ещё страницы
sdk.shouldShowLoadAll()
sdk.getCountRemainingToShow()

Пагинация дочерних комментариев

Вложенные ответы загружаются лениво. Когда пользователь раскрывает ветку, загружаются первые 5 дочерних комментариев. Появляется элемент управления «загрузить больше ответов», если есть дополнительные. Это обрабатывается автоматически в интерфейсе.



Состояние и наблюдаемость Internal Link


Обе FastCommentsSDK и FastCommentsFeedSDK являются классами ObservableObject с свойствами @Published. Вы можете наблюдать за ними в ваших SwiftUI представлениях для реактивных обновлений интерфейса.

Свойства @Published в FastCommentsSDK

Свойство Тип Описание
commentCountOnServer Int Общее количество комментариев на сервере
newRootCommentCount Int Буферизованные новые комментарии (when showLiveRightAway is false)
currentUser UserSessionInfo? Текущий аутентифицированный пользователь
isSiteAdmin Bool Является ли текущий пользователь администратором сайта
isClosed Bool Закрыта ли ветка комментариев
hasBillingIssue Bool Наличие проблемы с оплатой
isLoading Bool Идёт ли сетевой запрос
hasMore Bool Существуют ли дополнительные страницы комментариев
blockingErrorMessage String? Ошибка, препятствующая функционированию интерфейса
warningMessage String? Неблокирующее предупреждение
isDemo Bool Запущено ли в демонстрационном режиме
commentsVisible Bool Переключатель видимости комментариев
toolbarEnabled Bool Отображается ли панель форматирования

Свойства @Published в FastCommentsFeedSDK

Свойство Тип Описание
feedPosts [FeedPost] В настоящее время загруженные записи ленты
hasMore Bool Существуют ли дополнительные страницы
currentUser UserSessionInfo? Текущий аутентифицированный пользователь
blockingErrorMessage String? Блокирующее сообщение об ошибке
isLoading Bool Идёт ли сетевой запрос
newPostsCount Int Количество новых записей с момента последней загрузки

Дерево комментариев

Дерево комментариев доступно через sdk.commentsTree:

// Плоский список видимых узлов для отрисовки
sdk.commentsTree.visibleNodes

// Поиск комментария по ID
sdk.commentsTree.commentsById["comment-id"]


Регион ЕС Internal Link


Чтобы использовать дата-центр ЕС, задайте поле region в вашей конфигурации:

let config = FastCommentsWidgetConfig(
    tenantId: "YOUR_TENANT_ID",
    urlId: "my-page",
    region: "eu"
)

Это перенаправляет все API-запросы и WebSocket-подключения на eu.fastcomments.com.



Очистка Internal Link

Когда вы закончите работу с экземпляром SDK (например, когда представление закрывается), вызовите cleanup(), чтобы закрыть соединение WebSocket и отменить фоновые задачи:

sdk.cleanup()

Для представлений, управляемых @StateObject в SwiftUI, это обычно вызывают в .onDisappear или когда представление освобождается из памяти.

Загрузка изображений Internal Link

Комментарии

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")
])

Упоминания пользователей Internal Link


Поиск пользователей для автодополнения @mention:

let results = try await sdk.searchUsers(query: "jan")
// Возвращает [UserSearchResult] с userId, username, avatar и т.д.

Встроенный CommentInputBar автоматически обрабатывает автозаполнение @mention.



Редактирование и удаление комментариев Internal Link

Редактировать

try await sdk.editComment(commentId: commentId, newText: "Updated text")

Сервер повторно рендерит HTML. Локальный комментарий обновляется автоматически.

Удалить

try await sdk.deleteComment(commentId: commentId)

Удаление комментария также удаляет его потомков из локального дерева.

Обе операции доступны через контекстное меню комментария в UI, когда текущий пользователь является автором комментария (или администратором сайта).



Обработка ошибок Internal Link

Методы 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 properties:

  • code -- код ошибки из API
  • reason -- описание ошибки на английском языке
  • translatedError -- локализованное сообщение об ошибке, предоставленное сервером

Ошибки блокировки также автоматически отображаются через sdk.blockingErrorMessage, которое встроенные представления показывают пользователю.

Локализация Internal Link


Укажите код локали в конфигурации, чтобы локализовать строки, предоставляемые сервером:

let config = FastCommentsWidgetConfig(
    tenantId: "YOUR_TENANT_ID",
    urlId: "my-page",
    locale: "fr_fr"
)

Строки пользовательского интерфейса на стороне клиента используют локализацию, основанную на iOS bundle.



Пример приложения Internal Link


Репозиторий включает полнофункциональное примерное приложение в ExampleApp/ с демонстрациями:

  • Вложенные комментарии с SSO и пользовательскими темами
  • Социальная лента с созданием постов и фильтрацией по тегам
  • Чат в реальном времени
  • Простые и безопасные потоки SSO
  • Пользовательские кнопки панели инструментов (комментарии и лента)

Нужна помощь?

Если вы столкнулись с какими-либо проблемами или у вас есть вопросы по iOS-библиотеке, пожалуйста:

Внесение вкладов

Вклады приветствуются! Пожалуйста, посетите репозиторий на GitHub для ознакомления с инструкциями по внесению вкладов.