Compare commits

...

8 Commits

Author SHA1 Message Date
Thomas Miceli
aec7ee2708 v1.5.2 2023-10-16 12:26:05 +02:00
Thomas Miceli
10fd170833 Fix markdown render dark background (#137) 2023-10-16 12:20:09 +02:00
Slava Krampetz
ba03b8df38 Add ru-RU translation (#135) 2023-10-15 18:09:54 +02:00
Thomas Miceli
ef45f3d0ca config.yml with Docker (#131) 2023-10-15 08:14:34 +02:00
Thomas Miceli
b1acea9f1c Better password hashes error handling (#132) 2023-10-13 05:36:00 +02:00
Gary Wang
7059d5c834 Add zh-CN translation and minor UI fix (#130) 2023-10-12 14:13:39 +02:00
Thomas Miceli
1539499294 Longer title and description (#129) 2023-10-04 18:48:02 +02:00
Thomas Miceli
6f587f4757 Fix private gist visibility (#128) 2023-10-04 18:47:50 +02:00
15 changed files with 415 additions and 13 deletions

View File

@@ -1,5 +1,17 @@
# Changelog
## [1.5.2](https://github.com/thomiceli/opengist/compare/v1.5.1...v1.5.2) - 2023-10-16
### Added
- zh-CN translation (#130)
- ru-RU translation (#135)
- config.yml usage in the Docker container (#131)
- Longer title and description (#129)
### Fixed
- Private gist visibility (#128)
- Dark background color in Markdown rendering (#137)
- Error handling for password hashes (#132)
## [1.5.1](https://github.com/thomiceli/opengist/compare/v1.5.0...v1.5.1) - 2023-09-29
### Added
- Hungarian translations (#123)

View File

@@ -40,6 +40,8 @@ RUN apk update && \
RUN addgroup -S opengist && \
adduser -S -G opengist -H -s /bin/ash -g 'Opengist User' opengist
COPY --from=build --chown=opengist:opengist /opengist/config.yml config.yml
WORKDIR /app/opengist
COPY --from=build --chown=opengist:opengist /opengist/opengist .

View File

@@ -77,14 +77,16 @@ Download the archive for your system from the release page [here](https://github
```shell
# example for linux amd64
wget https://github.com/thomiceli/opengist/releases/download/v1.5.1/opengist1.5.1-linux-amd64.tar.gz
wget https://github.com/thomiceli/opengist/releases/download/v1.5.2/opengist1.5.2-linux-amd64.tar.gz
tar xzvf opengist1.5.1-linux-amd64.tar.gz
tar xzvf opengist1.5.2-linux-amd64.tar.gz
cd opengist
chmod +x opengist
./opengist # with or without `--config config.yml`
```
Opengist is now running on port 6157, you can browse http://localhost:6157
### From source
Requirements : [Git](https://git-scm.com/downloads) (2.20+), [Go](https://go.dev/doc/install) (1.20+), [Node.js](https://nodejs.org/en/download/) (16+)

View File

@@ -7,5 +7,6 @@ groupmod -o -g "$GID" $USER
usermod -o -u "$UID" $USER
chown -R "$USER:$USER" /opengist
chown -R "$USER:$USER" /config.yml
exec su $USER -c "OG_OPENGIST_HOME=/opengist /app/opengist/opengist"
exec su $USER -c "OG_OPENGIST_HOME=/opengist /app/opengist/opengist --config /config.yml"

View File

@@ -12,6 +12,17 @@ The [configuration cheat sheet](cheat-sheet.md) lists all available configuratio
The configuration file must be specified when launching the application, using the `--config` flag followed by the path
to your YAML file.
Usage with Docker Compose :
```yml
services:
opengist:
# ...
volumes:
# ...
- "/path/to/config.yml:/config.yml"
```
Usage via command line :
```shell
./opengist --config /path/to/config.yml
```
@@ -22,7 +33,6 @@ You can start by copying and/or modifying the provided [config.yml](/config.yml)
## Configuration via Environment Variables
Usage with Docker Compose :
```yml
services:
opengist:
@@ -31,8 +41,8 @@ services:
OG_LOG_LEVEL: "info"
# etc.
```
Usage via command line :
Usage via command line :
```shell
OG_LOG_LEVEL=info ./opengist
```

View File

@@ -15,7 +15,7 @@ import (
"gopkg.in/yaml.v3"
)
var OpengistVersion = "1.5.1"
var OpengistVersion = "1.5.2"
var C *config

View File

@@ -386,8 +386,8 @@ func (gist *Gist) UpdatePreviewAndCount() error {
// -- DTO -- //
type GistDTO struct {
Title string `validate:"max=50" form:"title"`
Description string `validate:"max=150" form:"description"`
Title string `validate:"max=250" form:"title"`
Description string `validate:"max=1000" form:"description"`
Private int `validate:"number,min=0,max=2" form:"private"`
Files []FileDTO `validate:"min=1,dive"`
Name []string `form:"name"`
@@ -395,7 +395,7 @@ type GistDTO struct {
}
type FileDTO struct {
Filename string `validate:"excludes=\x2f,excludes=\x5c,max=50"`
Filename string `validate:"excludes=\x2f,excludes=\x5c,max=255"`
Content string `validate:"required"`
}

View File

@@ -0,0 +1,177 @@
gist.public: Публичный
gist.unlisted: Скрытый
gist.private: Приватный
gist.header.like: Нравится
gist.header.unlike: Не нравится
gist.header.fork: Создать форк
gist.header.edit: Редактировать
gist.header.delete: Удалить
gist.header.forked-from: Форк с
gist.header.last-active: Последняя активность
gist.header.select-tab: Перейти
gist.header.code: Код
gist.header.revisions: Версии
gist.header.revision: Версия
gist.header.clone-http: Клонировать с помощью %s
gist.header.clone-http-help: Клонировать с помощью Git используя аутентификацию HTTP.
gist.header.clone-ssh: Клонировать c помощью SSH
gist.header.clone-ssh-help: Клонировать c помощью Git используя ключ SSH.
gist.header.share: Поделиться
gist.header.share-help: Скопировать ссылку на фрагмент.
gist.header.download-zip: Скачать ZIP-архив
gist.raw: Исходник
gist.file-truncated: Файл был обрезан.
gist.watch-full-file: Просмотр всего файла.
gist.file-not-valid: Невалидный CSV.
gist.no-content: Нет данных
gist.new.new_gist: Новый фрагмент
gist.new.title: Название
gist.new.description: Описание
gist.new.filename-with-extension: Имя файла с расширением
gist.new.indent-mode: Отступы
gist.new.indent-mode-space: Пробелы
gist.new.indent-mode-tab: Табуляция
gist.new.indent-size: Размер отступа
gist.new.wrap-mode: Переносы строк
gist.new.wrap-mode-no: Без переносов
gist.new.wrap-mode-soft: Мягкие переносы
gist.new.add-file: Добавить файл
gist.new.create-public-button: Создать публичный фрагмент
gist.new.create-unlisted-button: Создать скрытый фрагмент
gist.new.create-private-button: Создать приватный фрагмент
gist.edit.editing: Редактирование
gist.edit.change-visibility: Применить
gist.edit.delete: Удалить
gist.edit.cancel: Отмена
gist.edit.save: Сохранить
gist.list.joined: Зарегистрирован
gist.list.all: Все фрагменты
gist.list.search-results: Результаты поиска
gist.list.sort: Сортировка
gist.list.sort-by-created: по дате создания
gist.list.sort-by-updated: по дате обновления
gist.list.order-by-asc: Свежие снизу
gist.list.order-by-desc: Свежие сверху
gist.list.select-tab: Перейти
gist.list.liked: Понравившиеся
gist.list.likes: лайк(-ов)
gist.list.forked: Форки
gist.list.forked-from: Форки с
gist.list.forks: форк(-ов)
gist.list.files: файл(-ов)
gist.list.last-active: Последняя активность
gist.list.no-gists: Нет фрагментов
gist.forks: Форки
gist.forks.view: Посмотреть форк
gist.forks.no: Нет форков
gist.likes: Нравятся
gist.likes.no: Нет
gist.revisions: Ревизии
gist.revision.revised: ревизий этого фрагмента
gist.revision.go-to-revision: К ревизии
gist.revision.file-created: файл создан
gist.revision.file-deleted: файл удалён
gist.revision.file-renamed: переименован в
gist.revision.diff-truncated: Разница (diff) обрезана, так как результат слишком большой для показа
gist.revision.file-renamed-no-changes: Файл переименован без изменений
gist.revision.empty-file: Пустой файл
gist.revision.no-changes: Без изменений
gist.revision.no-revisions: Нет ревизий
settings: Настройки
settings.email: Адрес эл. почты
settings.email-help: Нужен для коммитов и Gravatar
settings.email-set: Сохранить адрес
settings.link-accounts: Привязка доступов
settings.link-github-account: Привязать доступ GitHub
settings.link-gitea-account: Привязать доступ Gitea
settings.unlink-github-account: Отвязать доступ GitHub
settings.unlink-gitea-account: Отвязать доступ Gitea
settings.delete-account: Удалить аккаунт
settings.delete-account-confirm: Вы уверены что хотите удалить свой аккаунт?
settings.add-ssh-key: Добавить ключ SSH
settings.add-ssh-key-help: Нужен только для работы с фрагментами через Git+SSH
settings.add-ssh-key-title: Название
settings.add-ssh-key-content: Ключ
settings.delete-ssh-key: Удалить
settings.delete-ssh-key-confirm: Подтвердите удаления ключа SSH
settings.ssh-key-added-at: Дата добавления
settings.ssh-key-never-used: Не был использован
settings.ssh-key-last-used: Последнее использование
auth.signup-disabled: Регистрация запрещена Администратором сервиса
auth.login: Вход
auth.signup: Регистрация
auth.new-account: Новый аккаунт
auth.username: Имя пользователя
auth.password: Пароль
auth.register-instead: Зарегистрироваться
auth.login-instead: Войти
auth.github-oauth: Войти с помощью доступа GitHub
auth.gitea-oauth: Войти с помощью доступа Gitea
error: Ошибка
header.menu.all: Все
header.menu.new: Новый
header.menu.search: Поиск
header.menu.my-gists: Мои фрагменты
header.menu.liked: Понравившиеся
header.menu.admin: Администрирование
header.menu.settings: Настройки
header.menu.logout: Выйти
header.menu.register: Регистрация
header.menu.login: Войти
header.menu.light: Светлая
header.menu.dark: Тёмная
header.menu.system: Системная
footer.powered-by: Работает на %s
pagination.older: Позже
pagination.newer: Новее
pagination.previous: Предыдущий
pagination.next: Следующий
admin.admin_panel: Панель управления
admin.general: Общее
admin.users: Пользователи
admin.gists: Фрагменты
admin.configuration: Настройки
admin.versions: Версии
admin.ssh_keys: Ключи SSH
admin.stats: Статистика
admin.actions: Действия
admin.actions.sync-fs: Синхронизировать фрагменты из файловой системы
admin.actions.sync-db: Синхронизировать фрагменты с базой данных
admin.actions.git-gc: Сборка мусора в репозиториях Git
admin.id: ID
admin.user: Пользователь
admin.delete: Удалить
admin.created_at: Создан
admin.config-link: Эти настройки могут быть %s файлом конфигурации YAML и/или переменными окружения.
admin.config-link-overriden: перекрыты
admin.disable-signup: Запретить регистрацию
admin.disable-signup_help: Запретить создание новых доступов
admin.require-login: Требовать авторизацию
admin.require-login_help: Запретить просмотр фрагментов без авторизации.
admin.disable-login: Запретить авторизацию по паролю
admin.disable-login_help: Запретить авторизацию с вводом пароля, форсировать внешнюю авторизацию через Gitea/GitHub.
admin.disable-gravatar: Запретить Gravatar
admin.disable-gravatar_help: Запретить использование Gravatar как провайдера изображений профиля.
admin.users.delete_confirm: Вы уверены что хотите удалить этого пользователя?
admin.gists.title: Название
admin.gists.private: Приватный
admin.gists.nb-files: Файлов
admin.gists.nb-likes: Понравилось
admin.gists.delete_confirm: Вы уверены что хотите удалить этот фрагмент?

View File

@@ -0,0 +1,177 @@
gist.public: 公开
gist.unlisted: 非列出
gist.private: 私有
gist.header.like: 喜欢
gist.header.unlike: 取消喜欢
gist.header.fork: 派生
gist.header.edit: 编辑
gist.header.delete: 删除
gist.header.forked-from: 派生自
gist.header.last-active: 最后活跃于
gist.header.select-tab: Select a tab
gist.header.code: 代码
gist.header.revisions: 修订
gist.header.revision: 修订
gist.header.clone-http: 通过 %s 克隆
gist.header.clone-http-help: 使用 Git 通过 HTTP 基础认证克隆。
gist.header.clone-ssh: 通过 SSH 克隆
gist.header.clone-ssh-help: 使用 Git 通过 SSH 密钥克隆。
gist.header.share: 分享
gist.header.share-help: 为此 Gist 复制可供分享的链接。
gist.header.download-zip: 下载 ZIP
gist.raw: 原始文件
gist.file-truncated: 此文件已被截断。
gist.watch-full-file: 查看完整文件。
gist.file-not-valid: 此文件不是有效的 CSV 文件。
gist.no-content: 没有内容
gist.new.new_gist: 创建 Gist
gist.new.title: 标题
gist.new.description: 描述
gist.new.filename-with-extension: 文件名与扩展名
gist.new.indent-mode: 缩进模式
gist.new.indent-mode-space: 空格
gist.new.indent-mode-tab: 制表符
gist.new.indent-size: 缩进大小
gist.new.wrap-mode: 换行模式
gist.new.wrap-mode-no: 不自动换行
gist.new.wrap-mode-soft: 软换行
gist.new.add-file: 添加文件
gist.new.create-public-button: 创建公开 Gist
gist.new.create-unlisted-button: 创建非列出 Gist
gist.new.create-private-button: 创建私有 Gist
gist.edit.editing: 编辑
gist.edit.change-visibility: 设为
gist.edit.delete: 删除
gist.edit.cancel: 取消
gist.edit.save: 保存
gist.list.joined: Joined
gist.list.all: 所有 Gists
gist.list.search-results: 搜索结果
gist.list.sort: 排序
gist.list.sort-by-created: 创建
gist.list.sort-by-updated: 更新
gist.list.order-by-asc: 最早
gist.list.order-by-desc: 最近
gist.list.select-tab: Select a tab
gist.list.liked: 已喜欢
gist.list.likes: 喜欢
gist.list.forked: 已派生
gist.list.forked-from: 派生自
gist.list.forks: 派生
gist.list.files: 文件
gist.list.last-active: 最后活跃于
gist.list.no-gists: 没有 Gist
gist.forks: 派生
gist.forks.view: 查看派生
gist.forks.no: 无公开派生
gist.likes: 喜欢
gist.likes.no: 还没有喜欢
gist.revisions: 修订
gist.revision.revised: 修订了这个 Gist
gist.revision.go-to-revision: 跳至此修订
gist.revision.file-created: file created
gist.revision.file-deleted: file deleted
gist.revision.file-renamed: 重命名为
gist.revision.diff-truncated: 由于变更差异过大,显示内容已被截断
gist.revision.file-renamed-no-changes: File renamed without changes
gist.revision.empty-file: 空文件
gist.revision.no-changes: 没有变更
gist.revision.no-revisions: 无可供显示的修订
settings: 设置
settings.email: 邮箱
settings.email-help: 用于提交与 Gravatar
settings.email-set: 设置邮箱地址
settings.link-accounts: 关联账号
settings.link-github-account: 关联 GitHub 账号
settings.link-gitea-account: 关联 Gitea 账号
settings.unlink-github-account: 解除关联 GitHub 账号
settings.unlink-gitea-account: 解除关联 Gitea 账号
settings.delete-account: 删除账号
settings.delete-account-confirm: 您确认要删除您的账号吗?
settings.add-ssh-key: 添加 SSH 密钥
settings.add-ssh-key-help: 用于使用 Git 通过 SSH 拉取与推送 Gist
settings.add-ssh-key-title: 标题
settings.add-ssh-key-content: 密钥
settings.delete-ssh-key: 删除
settings.delete-ssh-key-confirm: Confirm deletion of SSH key
settings.ssh-key-added-at: 添加
settings.ssh-key-never-used: 从未使用过
settings.ssh-key-last-used: 最后使用于
auth.signup-disabled: 管理员已禁用了注册
auth.login: 登录
auth.signup: 注册
auth.new-account: 新建账号
auth.username: 用户名
auth.password: 密码
auth.register-instead: 转到注册
auth.login-instead: 转到登录
auth.github-oauth: 使用 GitHub 账号继续
auth.gitea-oauth: 使用 Gitea 账号继续
error: 错误
header.menu.all: 全部
header.menu.new: 创建
header.menu.search: 搜索
header.menu.my-gists: 我的 Gists
header.menu.liked: Liked
header.menu.admin: 管理
header.menu.settings: 设置
header.menu.logout: 登出
header.menu.register: 注册
header.menu.login: 登录
header.menu.light: 亮色
header.menu.dark: 暗色
header.menu.system: 系统
footer.powered-by: 由 %s 强力驱动
pagination.older: 更早
pagination.newer: 更新
pagination.previous: 上一页
pagination.next: 下一页
admin.admin_panel: 管理面板
admin.general: 通用
admin.users: 用户
admin.gists: Gists
admin.configuration: 配置
admin.versions: 版本
admin.ssh_keys: SSH 密钥
admin.stats: 状态
admin.actions: 动作
admin.actions.sync-fs: 从文件系统同步 Gist
admin.actions.sync-db: 从数据库同步 Gist
admin.actions.git-gc: 对 Git 仓库执行垃圾回收
admin.id: ID
admin.user: 用户
admin.delete: 删除
admin.created_at: 创建于
admin.config-link: 此配置可通过 YAML 配置和/或环境变量进行 %s 。
admin.config-link-overriden: 覆盖
admin.disable-signup: 禁用注册
admin.disable-signup_help: 阻止创建新的账号。
admin.require-login: 要求登录
admin.require-login_help: 强制用户登录后才能查看 Gist。
admin.disable-login: 禁用登录表单
admin.disable-login_help: 禁止使用登录表单进行登录以强制通过 OAuth 提供方登录。
admin.disable-gravatar: 禁用 Gravatar
admin.disable-gravatar_help: 停止使用 Gravatar 作为头像提供方。
admin.users.delete_confirm: 你想要删除此用户吗?
admin.gists.title: 标题
admin.gists.private: 私有?
admin.gists.nb-files: 文件数
admin.gists.nb-likes: 喜欢数
admin.gists.delete_confirm: 你想要删除此 Gist 吗?

View File

@@ -18,6 +18,8 @@ import (
func gistInit(next echo.HandlerFunc) echo.HandlerFunc {
return func(ctx echo.Context) error {
currUser := getUserLogged(ctx)
userName := ctx.Param("user")
gistName := ctx.Param("gistname")
@@ -27,6 +29,13 @@ func gistInit(next echo.HandlerFunc) echo.HandlerFunc {
if err != nil {
return notFound("Gist not found")
}
if gist.Private == 2 {
if currUser == nil || currUser.ID != gist.UserID {
return notFound("Gist not found")
}
}
setData(ctx, "gist", gist)
if config.C.SshGit {
@@ -72,7 +81,7 @@ func gistInit(next echo.HandlerFunc) echo.HandlerFunc {
}
setData(ctx, "nbCommits", nbCommits)
if currUser := getUserLogged(ctx); currUser != nil {
if currUser != nil {
hasLiked, err := currUser.HasLiked(gist)
if err != nil {
return errorRes(500, "Cannot get user like status", err)

View File

@@ -265,8 +265,16 @@ func (a Argon2ID) hash(plain string) (string, error) {
}
func (a Argon2ID) verify(plain, hash string) (bool, error) {
if hash == "" {
return false, nil
}
hashParts := strings.Split(hash, "$")
if len(hashParts) != 6 {
return false, errors.New("invalid hash")
}
_, err := fmt.Sscanf(hashParts[3], "m=%d,t=%d,p=%d", &a.memory, &a.time, &a.threads)
if err != nil {
return false, err

4
public/style.css vendored
View File

@@ -152,3 +152,7 @@ dl.dl-config dt {
dl.dl-config dd {
@apply ml-1 col-span-2 break-words;
}
.markdown-body {
@apply dark:bg-gray-900 !important;
}

View File

@@ -105,7 +105,7 @@
</svg>
</div>
<div class="hidden relative sm:inline-block text-left">
<div id="user-menu" class="hidden w-32 font-medium absolute right-0 z-10 mt-12 origin-top-right divide-y dark:divide-gray-600 divide-gray-100 rounded-md dark:bg-gray-800 bg-white shadow-lg ring-1 ring-gray-50 dark:ring-gray-700 focus:outline-none">
<div id="user-menu" class="hidden w-56 font-medium absolute right-0 z-10 mt-12 origin-top-right divide-y dark:divide-gray-600 divide-gray-100 rounded-md dark:bg-gray-800 bg-white shadow-lg ring-1 ring-gray-50 dark:ring-gray-700 focus:outline-none">
<div class="py-1" role="none">
<a href="{{ $.c.ExternalUrl }}/{{ .userLogged.Username }}" class="dark:text-slate-300 text-slate-700 group flex items-center px-3 py-1.5 text-sm w-full hover:text-slate-500 dark:hover:text-slate-400" role="menuitem" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="mr-3 h-5 w-5 text-slate-600 dark:text-slate-400 group-hover:text-slate-500">

View File

@@ -31,7 +31,7 @@
</svg>
</button>
</div>
<div id="sort-gists-dropdown" class="hidden absolute right-0 z-10 mt-2 w-56 origin-top-right divide-y divide-gray-200 dark:divide-gray-700 rounded-md rounded border border-gray-200 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 shadow-lg ring-1 ring-white dark:ring-black ring-opacity-5 focus:outline-none" role="menu" aria-orientation="vertical" aria-labelledby="menu-button" tabindex="-1">
<div id="sort-gists-dropdown" class="hidden absolute right-0 z-10 mt-2 w-max origin-top-right divide-y divide-gray-200 dark:divide-gray-700 rounded-md rounded border border-gray-200 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 shadow-lg ring-1 ring-white dark:ring-black ring-opacity-5 focus:outline-none" role="menu" aria-orientation="vertical" aria-labelledby="menu-button" tabindex="-1">
<div class="" role="none">
<a href="{{ $.c.ExternalUrl }}/{{ .urlPage }}?sort=created&order=desc{{.searchQueryUrl}}" class="text-slate-700 dark:text-slate-300 group flex items-center px-3 py-2 text-xs hover:bg-gray-200 dark:hover:bg-gray-700 hover:text-black dark:hover:text-white hover:text-white hover:bg-primary-500 hover:rounded-t-md" role="menuitem">
{{ .locale.Tr "gist.list.order-by-desc" }} {{ .locale.Tr "gist.list.sort-by-created" }}

View File

@@ -66,7 +66,7 @@
</svg>
</button>
<div id="gist-menu-visibility" class="hidden absolute right-0 z-10 mt-2 origin-top-right rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none" role="menu" aria-orientation="vertical" aria-labelledby="gist-visibility-menu-button">
<div class="rounded-md dark:bg-gray-800 bg-white shadow-lg ring-1 ring-gray-50 dark:ring-gray-700 focus:outline-none" role="none">
<div class="rounded-md dark:bg-gray-800 bg-white shadow-lg ring-1 ring-gray-50 dark:ring-gray-700 focus:outline-none" role="none" style="word-break: keep-all">
<span class="text-gray-700 block px-4 py-2 text-sm cursor-pointer dark:text-slate-300 hover:text-slate-500 dark:hover:text-slate-400 gist-visibility-option" data-btntext="{{ .locale.Tr "gist.new.create-public-button" }}" data-visibility="0" role="menuitem">{{ .locale.Tr "gist.public" }}</span>
<span class="text-gray-700 block px-4 py-2 text-sm cursor-pointer dark:text-slate-300 hover:text-slate-500 dark:hover:text-slate-400 gist-visibility-option" data-btntext="{{ .locale.Tr "gist.new.create-unlisted-button" }}" data-visibility="1" role="menuitem">{{ .locale.Tr "gist.unlisted" }}</span>
<span class="text-gray-700 block px-4 py-2 text-sm cursor-pointer dark:text-slate-300 hover:text-slate-500 dark:hover:text-slate-400 gist-visibility-option" data-btntext="{{ .locale.Tr "gist.new.create-private-button" }}" data-visibility="2" role="menuitem">{{ .locale.Tr "gist.private" }}</span>