diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index 5aefb63..b099269 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -3,6 +3,13 @@ import { set_sidebar } from "../utils/auto_sidebar.js"; // https://vitepress.dev/reference/site-config export default defineConfig({ + markdown: { + vue: { + compilerOptions: { + isCustomElement: () => true // 禁止解析 ${{ }} + } + } + } as any, title: "devstar", head: [['link', { rel: 'icon', href: '/devstar-logo.png' }]], description: "A VitePress Site", @@ -35,6 +42,7 @@ export default defineConfig({ { text: 'devstar是什么', link: '/document/index', + attrs: { class: 'bold-item' } } as any, { text: '安装', @@ -58,7 +66,7 @@ export default defineConfig({ } as any, { text: '管理', - link: 'https://docs.gitea.com/zh-cn/category/administration', + // link: 'https://docs.gitea.com/zh-cn/category/administration', collapsible: true, // 使整个组可折叠 collapsed: true, // 默认展开 items: [ @@ -79,13 +87,14 @@ export default defineConfig({ { text: 'GPG提交签名', link: '/document/administration/signing.md' }, { text: '外部渲染器', link: '/document/administration/external-renderers.md' }, { text: '搜索引擎索引', link: '/document/administration/search-engines-indexation.md' }, - { text: '自定义Gitea配置', link: '/document/administration/customizing-gitea.md' }, + // { text: '自定义Gitea配置', link: '/document/administration/customizing-gitea.md' }, + { text: '自定义Gitea配置', link: 'https://docs.gitea.com/zh-cn/administration/customizing-gitea' }, { text: '添加法律页面', link: '/document/administration/adding-legal-pages.md' } ] } as any, { text: '使用', - link: 'https://docs.gitea.com/zh-cn/category/usage', + // link: 'https://docs.gitea.com/zh-cn/category/usage', collapsible: true, // 使整个组可折叠 collapsed: true, // 默认展开 items: [ @@ -94,15 +103,15 @@ export default defineConfig({ collapsible: true, // 使整个组可折叠 collapsed: false, // 默认展开 items: [ - { text: 'Overview', link: 'https://docs.gitea.com/zh-cn/installation/comparison' }, - { text: '快速入门', link: 'https://docs.gitea.com/zh-cn/installation/comparison' }, - { text: '与GitHub Actions的对比', link: 'https://docs.gitea.com/zh-cn/installation/comparison' }, - { text: 'Act Runner', link: 'https://docs.gitea.com/zh-cn/installation/comparison' }, - { text: '变量', link: 'https://docs.gitea.com/zh-cn/installation/comparison' }, - { text: 'Gitea Actions设计', link: 'https://docs.gitea.com/zh-cn/installation/comparison' }, - { text: '密钥管理', link: 'https://docs.gitea.com/zh-cn/installation/comparison' }, - { text: 'Gitea Actions常见问题解答', link: 'https://docs.gitea.com/zh-cn/installation/comparison' }, - { text: 'Badge', link: 'https://docs.gitea.com/zh-cn/installation/comparison' } + { text: 'Overview', link: 'document/usage/actions/overview.md' }, + { text: '快速入门', link: 'document/usage/actions/quickstart.md' }, + { text: '与GitHub Actions的对比', link: 'document/usage/actions/comparison.md' }, + { text: 'Act Runner', link: 'document/usage/actions/act-runner.md' }, + { text: '变量', link: 'document/usage/actions/variables.md' }, + { text: 'Gitea Actions设计', link: 'document/usage/actions/design.md' }, + { text: '密钥管理', link: 'document/usage/actions/secrets.md' }, + { text: 'Gitea Actions常见问题解答', link: 'document/usage/actions/faq.md' }, + { text: 'Badge', link: 'document/usage/actions/badge.md' } // 注意:Badge未找到对应文件,暂用comparison.md ] }, { @@ -110,88 +119,115 @@ export default defineConfig({ collapsible: true, // 使整个组可折叠 collapsed: true, // 默认展开 items: [ - { text: '软件包注册表', link: 'https://docs.gitea.com/zh-cn/installation/comparison' }, - { text: 'Alpine 软件包注册表', link: 'https://docs.gitea.com/zh-cn/installation/comparison' }, - { text: 'Arch package registry', link: 'https://docs.gitea.com/zh-cn/installation/comparison' }, - { text: 'Cargo 软件包注册表', link: 'https://docs.gitea.com/zh-cn/installation/comparison' }, - { text: 'Chef 软件包注册表', link: 'https://docs.gitea.com/zh-cn/installation/comparison' }, - { text: '存储', link: 'https://docs.gitea.com/zh-cn/installation/comparison' }, - { text: 'Composer 软件包注册表', link: 'https://docs.gitea.com/zh-cn/installation/comparison' }, - { text: 'Conan 软件包注册表', link: 'https://docs.gitea.com/zh-cn/installation/comparison' }, - { text: 'Conda 软件包注册表', link: 'https://docs.gitea.com/zh-cn/installation/comparison' }, - { text: '容器注册表', link: 'https://docs.gitea.com/zh-cn/installation/comparison' }, - { text: 'CRAN 软件包注册表', link: 'https://docs.gitea.com/zh-cn/installation/comparison' }, - { text: 'Go 软件包注册表', link: 'https://docs.gitea.com/zh-cn/installation/comparison' }, - { text: 'Helm Chart 注册表', link: 'https://docs.gitea.com/zh-cn/installation/comparison' }, - { text: 'Maven 软件包注册表', link: 'https://docs.gitea.com/zh-cn/installation/comparison' }, - { text: 'NPM Package Registry', link: 'https://docs.gitea.com/zh-cn/installation/comparison' }, - { text: 'NuGet 软件包注册表', link: 'https://docs.gitea.com/zh-cn/installation/comparison' }, - { text: 'Pub 软件包注册表', link: 'https://docs.gitea.com/zh-cn/installation/comparison' }, - { text: 'PyPI 软件包注册表', link: 'https://docs.gitea.com/zh-cn/installation/comparison' }, - { text: 'RPM 软件包注册表', link: 'https://docs.gitea.com/zh-cn/installation/comparison' }, - { text: 'RubyGems 软件包注册表', link: 'https://docs.gitea.com/zh-cn/installation/comparison' }, - { text: 'Swift 软件包注册表', link: 'https://docs.gitea.com/zh-cn/installation/comparison' }, - { text: 'Vagrant 软件包注册表', link: 'https://docs.gitea.com/zh-cn/installation/comparison' }, - { text: '通用软件包注册表', link: 'https://docs.gitea.com/zh-cn/installation/comparison' }, - { text: 'Debian 软件包注册表', link: 'https://docs.gitea.com/zh-cn/installation/comparison' } + { text: '软件包注册表', link: '/document/usage/packages/overview.md' }, + { text: 'Alpine 软件包注册表', link: '/document/usage/packages/alpine.md' }, + { text: 'Arch package registry', link: '/document/usage/packages/arch.md' }, + { text: 'Cargo 软件包注册表', link: '/document/usage/packages/cargo.md' }, + { text: 'Chef 软件包注册表', link: '/document/usage/packages/chef.md' }, + { text: 'Composer 软件包注册表', link: '/document/usage/packages/composer.md' }, + { text: 'Conan 软件包注册表', link: '/document/usage/packages/conan.md' }, + { text: 'Conda 软件包注册表', link: '/document/usage/packages/conda.md' }, + { text: '容器注册表', link: '/document/usage/packages/container.md' }, + { text: 'CRAN 软件包注册表', link: '/document/usage/packages/cran.md' }, + { text: 'Debian 软件包注册表', link: '/document/usage/packages/debian.md' }, + { text: '通用软件包注册表', link: '/document/usage/packages/generic.md' }, + { text: 'Go 软件包注册表', link: '/document/usage/packages/go.md' }, + { text: 'Helm Chart 注册表', link: '/document/usage/packages/helm.md' }, + { text: 'Maven 软件包注册表', link: '/document/usage/packages/maven.md' }, + { text: 'NPM Package Registry', link: '/document/usage/packages/npm.md' }, + { text: 'NuGet 软件包注册表', link: '/document/usage/packages/nuget.md' }, + { text: 'Pub 软件包注册表', link: '/document/usage/packages/pub.md' }, + { text: 'PyPI 软件包注册表', link: '/document/usage/packages/pypi.md' }, + { text: 'RPM 软件包注册表', link: '/document/usage/packages/rpm.md' }, + { text: 'RubyGems 软件包注册表', link: '/document/usage/packages/rubygems.md' }, + { text: '存储', link: '/document/usage/packages/storage.md' }, + { text: 'Swift 软件包注册表', link: '/document/usage/packages/swift.md' }, + { text: 'Vagrant 软件包注册表', link: '/document/usage/packages/vagrant.md' } ] }, - { text: 'AGit', link: 'https://docs.gitea.com/zh-cn/installation/comparison' }, - { text: '个人资料 README', link: 'https://docs.gitea.com/zh-cn/installation/comparison' }, - { text: 'Blame File View', link: 'https://docs.gitea.com/zh-cn/installation/comparison' }, - { text: '邮件接收', link: 'https://docs.gitea.com/zh-cn/installation/comparison' }, - { text: '标签', link: 'https://docs.gitea.com/zh-cn/installation/comparison' }, - { text: '合并请求', link: 'https://docs.gitea.com/zh-cn/installation/comparison' }, - { text: '权限', link: 'https://docs.gitea.com/zh-cn/installation/comparison' }, - { text: '模板仓库', link: 'https://docs.gitea.com/zh-cn/installation/comparison' }, - { text: '从模板创建工单与合并请求', link: 'https://docs.gitea.com/zh-cn/installation/comparison' }, - { text: '自动链接引用', link: 'https://docs.gitea.com/zh-cn/installation/comparison' }, - { text: '合并消息模板', link: 'https://docs.gitea.com/zh-cn/installation/comparison' }, - { text: '推送', link: 'https://docs.gitea.com/zh-cn/installation/comparison' }, - { text: 'Blocking a user', link: 'https://docs.gitea.com/zh-cn/installation/comparison' }, - { text: '克隆过滤器 (部分克隆)', link: 'https://docs.gitea.com/zh-cn/installation/comparison' }, - { text: 'Code Owners', link: 'https://docs.gitea.com/zh-cn/installation/comparison' }, - { text: 'Webhooks', link: 'https://docs.gitea.com/zh-cn/installation/comparison' }, - { text: 'Migration', link: 'https://docs.gitea.com/zh-cn/installation/comparison' }, - { text: '受保护的标签', link: 'https://docs.gitea.com/zh-cn/installation/comparison' }, - { text: '仓库镜像', link: 'https://docs.gitea.com/zh-cn/installation/comparison' }, - { text: 'Markdown', link: 'https://docs.gitea.com/zh-cn/installation/comparison' }, - { text: 'Multi-factor Authentication (MFA)', link: 'https://docs.gitea.com/zh-cn/installation/comparison' } + { text: 'AGit', link: '/document/usage/agit.md' }, + { text: '个人资料 README', link: '/document/usage/profile-readme.md' }, + { text: 'Blame File View', link: '/document/usage/blame.md' }, + { text: '邮件接收', link: '/document/usage/incoming-email.md' }, + { text: '标签', link: '/document/usage/labels.md' }, + { text: '合并请求', link: '/document/usage/pull-request.md' }, + { text: '权限', link: '/document/usage/permissions.md' }, + { text: '模板仓库', link: '/document/usage/template-repositories.md' }, + { text: '从模板创建工单与合并请求', link: '/document/usage/issue-pull-request-templates.md' }, + { text: '自动链接引用', link: '/document/usage/linked-references.md' }, + { text: '合并消息模板', link: '/document/usage/merge-message-templates.md' }, + { text: '推送', link: '/document/usage/push.md' }, + { text: 'Blocking a user', link: '/document/usage/blocking-users.md' }, + { text: '克隆过滤器(部分克隆)', link: '/document/usage/clone-filter.md' }, + { text: 'Code Owners', link: '/document/usage/code-owners.md' }, + { text: 'Webhooks', link: '/document/usage/webhooks.md' }, + { text: 'Migration', link: '/document/usage/migration.md' }, + { text: '受保护的标签', link: '/document/usage/protected-tags.md' }, + { text: '仓库镜像', link: '/document/usage/repo-mirror.md' }, + { text: 'Markdown', link: '/document/usage/markdown.md' }, + { text: 'Multi-factor Authentication (MFA)', link: '/document/usage/multi-factor-authentication.md' } ] } as any, { text: '开发', - link: 'https://docs.gitea.com/zh-cn/category/development', + // link: 'https://docs.gitea.com/zh-cn/category/development', collapsible: true, // 使整个组可折叠 collapsed: true, // 默认展开 items: [ - { text: '玩转 Gitea', link: 'https://docs.gitea.com/zh-cn/installation/comparison' }, - { text: 'API 使用指南', link: 'https://docs.gitea.com/zh-cn/installation/comparison' }, - { text: 'OAuth2 提供者', link: 'https://docs.gitea.com/zh-cn/installation/comparison' }, - { text: '迁移接口', link: 'https://docs.gitea.com/zh-cn/installation/comparison' }, - { text: '集成', link: 'https://docs.gitea.com/zh-cn/installation/comparison' } + { + text: '玩转 Gitea', + link: '/document/development/hacking-on-gitea.md' // 匹配"API 使用指南"对应的文件名 + }, + { + text: 'API 使用指南', + link: '/document/development/api-usage.md' // 直接使用文件名 + }, + { + text: 'OAuth2 提供者', + link: '/document/development/oauth2-provider.md' // 推测文件名 + }, + { + text: '迁移接口', + link: '/document/development/migrations.md' // 推测文件名 + }, + { + text: '集成', + link: '/document/development/integrations.md' // 推测文件名 + } ] } as any, { text: '贡献', - link: 'https://docs.gitea.com/zh-cn/category/contributing', + // link: 'https://docs.gitea.com/zh-cn/category/contributing', collapsible: true, // 使整个组可折叠 collapsed: true, // 默认展开 items: [ - { text: '后端开发指南', link: 'https://docs.gitea.com/zh-cn/installation/comparison' }, - { text: '前端开发指南', link: 'https://docs.gitea.com/zh-cn/installation/comparison' }, - { text: '重构指南', link: 'https://docs.gitea.com/zh-cn/installation/comparison' }, - { text: '本地化', link: 'https://docs.gitea.com/zh-cn/installation/comparison' } + { + text: '后端开发指南', + link: '/document/contributing/guidelines-backend.md' // 匹配文件名:guidelines - backend.md + }, + { + text: '前端开发指南', + link: '/document/contributing/guidelines-frontend.md' // 匹配文件名:guidelines - frontend.md + }, + { + text: '重构指南', + link: '/document/contributing/guidelines-refactoring.md' // 匹配文件名:guidelines - refactoring.md + }, + { + text: '本地化', + link: '/document/contributing/localization.md' // 文件名完全一致 + } ] } as any, { text: '帮助', - link: 'https://docs.gitea.com/zh-cn/category/help', + // link: 'https://docs.gitea.com/zh-cn/category/help', collapsible: true, // 使整个组可折叠 collapsed: true, // 默认展开 items: [ - { text: '常见问题', link: 'https://docs.gitea.com/zh-cn/installation/comparison' }, - { text: '支持选项', link: 'https://docs.gitea.com/zh-cn/installation/comparison' }, + { text: '常见问题', link: 'document/help/faq.md' }, + { text: '支持选项', link: 'document/help/support.md' }, ] } as any, ], diff --git a/docs/.vitepress/theme/custom.css b/docs/.vitepress/theme/custom.css new file mode 100644 index 0000000..b1130ac --- /dev/null +++ b/docs/.vitepress/theme/custom.css @@ -0,0 +1,6 @@ +/* .vitepress/theme/custom.css */ +.bold-item { + font-weight: 700 !important; + font-size: 1em !important; + /* 与其他目录项字号一致 */ +} \ No newline at end of file diff --git a/docs/document/contributing/guidelines-backend.md b/docs/document/contributing/guidelines-backend.md new file mode 100644 index 0000000..758e417 --- /dev/null +++ b/docs/document/contributing/guidelines-backend.md @@ -0,0 +1,110 @@ +--- +date: "2023-05-25T23:41:00+08:00" +slug: "guidelines-backend" +sidebar_position: 20 +aliases: + - /zh-cn/guidelines-backend +--- + +# 后端开发指南 + +## 背景 + +Gitea 使用 Golang 作为后端编程语言。它使用了许多第三方包,并且自己也编写了一些包。 +例如,Gitea 使用[Chi](https://github.com/go-chi/chi)作为基本的 Web 框架。[Xorm](https://xorm.io)是一个用于与数据库交互的 ORM 框架。 +因此,管理这些包非常重要。在开始编写后端代码之前,请参考以下准则。 + +## 包设计准则 + +### 包列表 + +为了保持易于理解的代码并避免循环依赖,拥有良好的代码结构是很重要的。Gitea 后端分为以下几个部分: + +- `build`:帮助构建 Gitea 的脚本。 +- `cmd`:包含所有 Gitea 的实际子命令,包括 web、doctor、serv、hooks、admin 等。`web`将启动 Web 服务。`serv`和`hooks`将被 Git 或 OpenSSH 调用。其他子命令可以帮助维护 Gitea。 +- `tests`:常用的测试函数 +- `tests/integration`:集成测试,用于测试后端回归。 +- `tests/e2e`:端到端测试,用于测试前端和后端的兼容性和视觉回归。 +- `models`:包含由 xorm 用于构建数据库表的数据结构。它还包含查询和更新数据库的函数。应避免与其他 Gitea 代码的依赖关系。在某些情况下,比如日志记录时可以例外。 + - `models/db`:基本的数据库操作。所有其他`models/xxx`包都应依赖于此包。`GetEngine`函数只能从 models/中调用。 + - `models/fixtures`:单元测试和集成测试中使用的示例数据。一个`yml`文件表示一个将在测试开始时加载到数据库中的表。 + - `models/migrations`:存储不同版本之间的数据库迁移。修改数据库结构的 PR**必须**包含一个迁移步骤。 +- `modules`:在 Gitea 中处理特定功能的不同模块。工作正在进行中:其中一些模块应该移到`services`中,特别是那些依赖于 models 的模块,因为它们依赖于数据库。 + - `modules/setting`:存储从 ini 文件中读取的所有系统配置,并在各处引用。但是在可能的情况下,应将其作为函数参数使用。 + - `modules/git`:用于与`Git`命令行或 Gogit 包交互的包。 +- `public`:编译后的前端文件(JavaScript、图像、CSS 等) +- `routers`:处理服务器请求。由于它使用其他 Gitea 包来处理请求,因此其他包(models、modules 或 services)不能依赖于 routers。 + - `routers/api`:包含`/api/v1`相关路由,用于处理 RESTful API 请求。 + - `routers/install`:只能在系统处于安装模式(INSTALL_LOCK=false)时响应。 + - `routers/private`:仅由内部子命令调用,特别是`serv`和`hooks`。 + - `routers/web`:处理来自 Web 浏览器或 Git SMART HTTP 协议的 HTTP 请求。 +- `services`:用于常见路由操作或命令执行的支持函数。使用`models`和`modules`来处理请求。 +- `templates`:用于生成 HTML 输出的 Golang 模板。 + +### 包依赖关系 + +由于 Golang 不支持导入循环,我们必须仔细决定包之间的依赖关系。这些包之间有一些级别。以下是理想的包依赖关系方向。 + +`cmd` -> `routers` -> `services` -> `models` -> `modules` + +从左到右,左侧的包可以依赖于右侧的包,但右侧的包不能依赖于左侧的包。在同一级别的子包中,可以根据该级别的规则进行依赖。 + +**注意事项** + +为什么我们需要在`models`之外使用数据库事务?以及如何使用? +某些操作在数据库记录插入/更新/删除失败时应该允许回滚。 +因此,服务必须能够创建数据库事务。以下是一些示例: + +```go +// services/repository/repository.go +func CreateXXXX() error { + return db.WithTx(func(ctx context.Context) error { + // do something, if err is returned, it will rollback automatically + if err := issues.UpdateIssue(ctx, repoID); err != nil { + // ... + return err + } + // ... + return nil + }) +} +``` + +在`services`中**不应该**直接使用`db.GetEngine(ctx)`,而是应该在`models/`下编写一个函数。 +如果该函数将在事务中使用,请将`context.Context`作为函数的第一个参数。 + +```go +// models/issues/issue.go +func UpdateIssue(ctx context.Context, repoID int64) error { + e := db.GetEngine(ctx) + + // ... +} +``` + +### 包名称 + +对于顶层包,请使用复数作为包名,例如`services`、`models`,对于子包,请使用单数,例如`services/user`、`models/repository`。 + +### 导入别名 + +由于有一些使用相同包名的包,例如`modules/user`、`models/user`和`services/user`,当这些包在一个 Go 文件中被导入时,很难知道我们使用的是哪个包以及它是变量名还是导入名。因此,我们始终建议使用导入别名。为了与常见的驼峰命名法的包变量区分开,建议使用**snake_case**作为导入别名的命名规则。 +例如:`import user_service "code.gitea.io/gitea/services/user"` + +### 重要注意事项 + +- 永远不要写成`x.Update(exemplar)`,而没有明确的`WHERE`子句: + - 这将导致表中的所有行都被使用 exemplar 的非零值进行更新,包括 ID。 + - 通常应该写成`x.ID(id).Update(exemplar)`。 +- 如果在迁移过程中使用`x.Insert(exemplar)`向表中插入记录,而 ID 是预设的: + - 对于 MSSQL 变体,你将需要执行`` SET IDENTITY_INSERT `table` ON ``(否则迁移将失败) + - 对于 PostgreSQL,你还需要更新 ID 序列,否则迁移将悄无声息地通过,但后续的插入将失败: + `` SELECT setval('table_name_id_seq', COALESCE((SELECT MAX(id)+1 FROM `table_name`), 1), false) `` + +### 未来的任务 + +目前,我们正在进行一些重构,以完成以下任务: + +- 纠正不符合规则的代码。 +- `models`中的文件太多了,所以我们正在将其中的一些移动到子包`models/xxx`中。 +- 由于它们依赖于`models`,因此应将某些`modules`子包移动到`services`中。 diff --git a/docs/document/contributing/guidelines-frontend.md b/docs/document/contributing/guidelines-frontend.md new file mode 100644 index 0000000..4f34789 --- /dev/null +++ b/docs/document/contributing/guidelines-frontend.md @@ -0,0 +1,149 @@ +--- +date: "2023-05-25T16:00:00+02:00" +slug: "guidelines-frontend" +sidebar_position: 20 +aliases: + - /zh-cn/guidelines-frontend +--- + +# 前端开发指南 + +## 背景 + +Gitea 在其前端中使用[Fomantic-UI](https://fomantic-ui.com/introduction/getting-started.html)(基于[jQuery](https://api.jquery.com))和 [Vue3](https://vuejs.org/)。 + +HTML 页面由[Go HTML Template](https://pkg.go.dev/html/template)渲染。 + +源文件可以在以下目录中找到: + +- **CSS 样式**: `web_src/css/` +- **JavaScript 文件**: `web_src/js/` +- **Vue 组件**: `web_src/js/components/` +- **Go HTML 模板**: `templates/` + +## 通用准则 + +我们推荐使用[Google HTML/CSS Style Guide](https://google.github.io/styleguide/htmlcssguide.html)和[Google JavaScript Style Guide](https://google.github.io/styleguide/jsguide.html)。 + +## Gitea 特定准则 + +1. 每个功能(Fomantic-UI/jQuery 模块)应放在单独的文件/目录中。 +2. HTML 的 id 和 class 应使用 kebab-case,最好包含 2-3 个与功能相关的关键词。 +3. 在 JavaScript 中使用的 HTML 的 id 和 class 应在整个项目中是唯一的,并且应包含 2-3 个与功能相关的关键词。建议在仅在 JavaScript 中使用的 class 中使用 `js-` 前缀。 +4. 不应覆盖框架提供的 class 的 CSS 样式。始终使用具有 2-3 个与功能相关的关键词的新 class 名称来覆盖框架样式。Gitea 中的帮助 CSS 类在 `helpers.less` 中。 +5. 后端可以通过使用`ctx.PageData["myModuleData"] = map[]{}`将复杂数据传递给前端,但不要将整个模型暴露给前端,以避免泄露敏感数据。 +6. 简单页面和与 SEO 相关的页面使用 Go HTML 模板渲染生成静态的 Fomantic-UI HTML 输出。复杂页面可以使用 Vue3。 +7. 明确变量类型,优先使用`elem.disabled = true`而不是`elem.setAttribute('disabled', 'anything')`,优先使用`$el.prop('checked', var === 'yes')`而不是`$el.prop('checked', var)`。 +8. 使用语义化元素,优先使用`