mirror of
https://github.com/Floriansylvain/RenewCMS.git
synced 2026-08-19 19:53:21 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6796ce63cb | ||
|
|
99812ddba4 | ||
|
|
0238c7511d | ||
|
|
d7ee0a488c | ||
|
|
c57394d4fd | ||
|
|
f47f145b6c | ||
|
|
83a1dec8ae | ||
|
|
b470bfcc67 | ||
|
|
cb9a431488 | ||
|
|
2292a8ef34 | ||
|
|
a77a5ad314 | ||
|
|
d895477b8c | ||
|
|
6563db43dc | ||
|
|
6fb666fc89 | ||
|
|
4d6054e22b | ||
|
|
7e29c08ea9 | ||
|
|
c69876a704 | ||
|
|
0759ab6d70 | ||
|
|
e4c2230b64 | ||
|
|
727418c8e6 | ||
|
|
f46737399f | ||
|
|
26984980b7 | ||
|
|
c25fb1b0a3 | ||
|
|
eb98a1003e | ||
|
|
46b816a8af | ||
|
|
ad1692d168 | ||
|
|
bcf3c19702 | ||
|
|
bfc310aeab | ||
|
|
adcd6fc08a | ||
|
|
ed5d52a506 | ||
|
|
5e9098aa20 | ||
|
|
7d2795a9e0 | ||
|
|
1b6690802f | ||
|
|
c1317e9f21 | ||
|
|
92c6d06d00 | ||
|
|
900ad3e8b8 | ||
|
|
24eefb743b | ||
|
|
29fce9a241 | ||
|
|
7097d71f45 | ||
|
|
a35e7c4283 | ||
|
|
aabdbfae95 | ||
|
|
6c54c9607c | ||
|
|
9c4d4fa87c | ||
|
|
a331c9173b | ||
|
|
830b062a76 | ||
|
|
3b57961946 | ||
|
|
c929acd4ab | ||
|
|
37647e60d9 | ||
|
|
1a75615be4 | ||
|
|
4d6a799c5d | ||
|
|
ec14eee439 | ||
|
|
8015639db5 | ||
|
|
d146363246 | ||
|
|
00a4e464ff | ||
|
|
dca17a744d | ||
|
|
2eae066a24 | ||
|
|
c90fa26c73 | ||
|
|
33cd947958 | ||
|
|
2805425317 | ||
|
|
411adc02b0 | ||
|
|
c663bcf4a9 | ||
|
|
777666a92f | ||
|
|
55d9e3bfc1 | ||
|
|
7d071ebc2e | ||
|
|
c2b64bf0fa | ||
|
|
ea942355ab | ||
|
|
e113c79a6d | ||
|
|
509329951f | ||
|
|
5bd4b4769f | ||
|
|
554fdc1989 | ||
|
|
46491a688b | ||
|
|
d91dc256a2 | ||
|
|
60e25cea2f | ||
|
|
a785483220 | ||
|
|
b414a07f6d | ||
|
|
245bf2fbf4 | ||
|
|
64ecbc6995 | ||
|
|
c6bd7731cc | ||
|
|
2525a64d0a | ||
|
|
04ebbfda87 | ||
|
|
5c61796313 | ||
|
|
e575062209 | ||
|
|
1539e06ec8 | ||
|
|
869fb3f145 | ||
|
|
7e8b415cab | ||
|
|
047e14df60 | ||
|
|
8faf3f8d9d | ||
|
|
542bcded3e | ||
|
|
46daadb8e4 | ||
|
|
0466da2d4b | ||
|
|
318fd0f53a | ||
|
|
2ddabe2222 | ||
|
|
d953a66f22 | ||
|
|
4b918aa2fa | ||
|
|
7611738b48 | ||
|
|
366ae190ff | ||
|
|
96a64097c4 | ||
|
|
0c139b1f72 | ||
|
|
209555498f | ||
|
|
61f221e49c | ||
|
|
1d1adfdfc7 | ||
|
|
836847e9b2 | ||
|
|
e3246e337d | ||
|
|
dad0c906f0 | ||
|
|
91d82b0ad7 | ||
|
|
f33f53ca76 | ||
|
|
e258dccc1c | ||
|
|
195c247235 | ||
|
|
5b4b5a8622 | ||
|
|
4187f0b2e2 | ||
|
|
1328758984 |
@@ -0,0 +1,51 @@
|
||||
root = "."
|
||||
testdata_dir = "testdata"
|
||||
tmp_dir = "tmp"
|
||||
|
||||
[build]
|
||||
args_bin = []
|
||||
bin = "tmp/main.exe"
|
||||
cmd = "go build -o ./tmp/main.exe ./main"
|
||||
delay = 1000
|
||||
exclude_dir = ["assets", "tmp", "vendor", "testdata", "api\\static\\tinymce", "api\\static\\bootstrap-icons", "api\\static\\uploadedImages"]
|
||||
exclude_file = []
|
||||
exclude_regex = ["_test.go"]
|
||||
exclude_unchanged = false
|
||||
follow_symlink = false
|
||||
full_bin = ""
|
||||
include_dir = []
|
||||
include_ext = ["go", "tpl", "tmpl", "html"]
|
||||
include_file = []
|
||||
kill_delay = "0s"
|
||||
log = "build-errors.log"
|
||||
poll = false
|
||||
poll_interval = 0
|
||||
post_cmd = []
|
||||
pre_cmd = []
|
||||
rerun = false
|
||||
rerun_delay = 500
|
||||
send_interrupt = false
|
||||
stop_on_error = false
|
||||
|
||||
[color]
|
||||
app = ""
|
||||
build = "yellow"
|
||||
main = "magenta"
|
||||
runner = "green"
|
||||
watcher = "cyan"
|
||||
|
||||
[log]
|
||||
main_only = false
|
||||
time = false
|
||||
|
||||
[misc]
|
||||
clean_on_exit = false
|
||||
|
||||
[proxy]
|
||||
app_port = 0
|
||||
enabled = false
|
||||
proxy_port = 0
|
||||
|
||||
[screen]
|
||||
clear_on_rebuild = false
|
||||
keep_scroll = true
|
||||
@@ -0,0 +1,11 @@
|
||||
ENVIRONMENT=development
|
||||
HOST=example.com
|
||||
PORT=8080
|
||||
JWT_SECRET=abc123
|
||||
CORS_ALLOWED_ORIGINS=*
|
||||
DB_FILE=./renewcms.db
|
||||
DOCKER_DB_FOLDER=./data
|
||||
SMTP_EMAIL=a@b.c
|
||||
SMTP_PASSWORD=12341234
|
||||
SMTP_HOST=mail.service.abc
|
||||
SMTP_PORT=465
|
||||
@@ -0,0 +1 @@
|
||||
api/static/** linguist-vendored
|
||||
@@ -0,0 +1,40 @@
|
||||
# This workflow will build a golang project
|
||||
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go
|
||||
|
||||
name: Go
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["main"]
|
||||
pull_request:
|
||||
branches: ["main"]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
environment: test
|
||||
env:
|
||||
ENVIRONMENT: ${{ secrets.ENVIRONMENT }}
|
||||
HOST: ${{ secrets.HOST }}
|
||||
PORT: ${{ secrets.PORT }}
|
||||
JWT_SECRET: ${{ secrets.JWT_SECRET }}
|
||||
CORS_ALLOWED_ORIGINS: ${{ secrets.CORS_ALLOWED_ORIGINS }}
|
||||
DB_FILE: ${{ secrets.DB_FILE }}
|
||||
DOCKER_DB_FOLDER: ${{ secrets.DOCKER_DB_FOLDER }}
|
||||
SMTP_EMAIL: ${{ secrets.SMTP_EMAIL }}
|
||||
SMTP_PASSWORD: ${{ secrets.SMTP_PASSWORD }}
|
||||
SMTP_HOST: ${{ secrets.SMTP_HOST }}
|
||||
SMTP_PORT: ${{ secrets.SMTP_PORT }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: "1.23.0"
|
||||
|
||||
- name: Build
|
||||
run: go build -v ./...
|
||||
|
||||
- name: Test
|
||||
run: go test -v ./...
|
||||
+15
-29
@@ -1,31 +1,17 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
coverage
|
||||
*.local
|
||||
|
||||
/cypress/videos/
|
||||
/cypress/screenshots/
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
# IDE
|
||||
.idea
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
.vscode
|
||||
|
||||
mongodbdata
|
||||
.env
|
||||
# DB
|
||||
*.db
|
||||
|
||||
# Build
|
||||
bin
|
||||
|
||||
# Environment
|
||||
.env
|
||||
|
||||
# Generated files
|
||||
tmp/
|
||||
uploadedImages/
|
||||
*.exe
|
||||
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
FROM golang:1.20.5-alpine
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY go.mod go.sum ./
|
||||
COPY adapters ./adapters
|
||||
COPY api ./api
|
||||
COPY domain ./domain
|
||||
COPY main ./main
|
||||
COPY useCases ./useCases
|
||||
COPY .env ./.env
|
||||
|
||||
RUN go mod download
|
||||
RUN go build -o startServer ./main
|
||||
|
||||
CMD ["./startServer"]
|
||||
@@ -1,14 +0,0 @@
|
||||
FROM golang:1.19
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY go.mod ./
|
||||
COPY go.sum ./
|
||||
RUN go mod download
|
||||
|
||||
ADD cmd ./cmd/
|
||||
ADD internal ./internal/
|
||||
COPY .env ./
|
||||
RUN go build -o ./GohCMS/ ./...
|
||||
|
||||
CMD ["./GohCMS/cmd"]
|
||||
@@ -1,12 +0,0 @@
|
||||
FROM node:latest as build-stage
|
||||
WORKDIR /app
|
||||
COPY ./web/admin-gui/package*.json ./
|
||||
COPY ./.env ./
|
||||
RUN npm install
|
||||
COPY ./web/admin-gui/ .
|
||||
RUN npm run build
|
||||
|
||||
FROM nginx as production-stage
|
||||
RUN mkdir /app
|
||||
COPY --from=build-stage /app/dist /app
|
||||
COPY ./web/admin-gui/nginx.conf /etc/nginx/nginx.conf
|
||||
@@ -1,4 +1,4 @@
|
||||
# GohCMS
|
||||
# RenewCMS
|
||||
|
||||
## 🚧 This project is under development !
|
||||
|
||||
@@ -10,29 +10,48 @@ Don't forget to setup the [Environment variables](#environment-variables)!
|
||||
|
||||
### Use with Docker
|
||||
|
||||
To **run** the app, run the command
|
||||
#### Compose
|
||||
|
||||
```shell
|
||||
docker-compose up
|
||||
The easiest way to run the project is to use docker-compose.
|
||||
You still need to set up the environment variables, but you can use the `.env` file for that.
|
||||
Note that some supplementary variables are required for docker-compose to work, like `DOCKER_DB_FOLDER`.
|
||||
|
||||
```bash
|
||||
docker compose up --build -d
|
||||
```
|
||||
|
||||
### Build and run it yourself
|
||||
|
||||
TODO
|
||||
#### Build
|
||||
|
||||
There are 2 scripts to build the project:
|
||||
|
||||
- `super_build.sh`
|
||||
- `super_build.bat`
|
||||
|
||||
They will build the project for windows, linux and macOS. The binaries will be in the `bin` folder.
|
||||
You can also really just use `go build` to build the project for your current OS.
|
||||
|
||||
#### Run
|
||||
|
||||
Once the project is built, you can simply run the binary for your OS. Setting the environment variables is
|
||||
of course required, but not necessarily via the `.env` file.
|
||||
|
||||
### Environment variables
|
||||
|
||||
- ./env:
|
||||
|
||||
```
|
||||
APP_API_ADDRESS=http://example.com
|
||||
APP_FRONT_ADDRESS=http://example.com
|
||||
APP_BASE_API_PATH=/example
|
||||
APP_BASE_FRONT_PATH=/example
|
||||
APP_API_PORT=1234
|
||||
APP_FRONT_PORT=1234
|
||||
APP_JWT_SECRET=secret
|
||||
```
|
||||
| Name | Type | Description | Comment |
|
||||
|----------------------|--------|:----------------------------------------------|-----------------------------------------------------------------------------------------------------------------------|
|
||||
| ENVIRONMENT | string | environment the API is running in | required, `development` or `production` |
|
||||
| HOST | string | the host domain name for emails and callbacks | required, if `localhost`: add the port (e.g. `localhost:8080`) |
|
||||
| PORT | int | port the API will use | required |
|
||||
| JWT_SECRET | string | secret for the jwt auth | required |
|
||||
| CORS_ALLOWED_ORIGINS | string | allowed origins for CORS | required, semicolon separated list |
|
||||
| DB_FILE | string | path to the sqlite db file | required, can be at the root but name still required (e.g. `./RenewCMS.db`) ; have to end up with `.db` |
|
||||
| DOCKER_DB_FOLDER | string | path to the sqlite db file's folder | required with docker, this will basically be the host machine folder (e.g. `./data`) that contains the sqlite db file |
|
||||
| SMTP_EMAIL | string | sender email | required |
|
||||
| SMTP_PASSWORD | string | smtp account password | required |
|
||||
| SMTP_HOST | string | smtp server address | required |
|
||||
| SMTP_PORT | int | smtp server port | required |
|
||||
|
||||
## API Usage
|
||||
|
||||
@@ -41,3 +60,7 @@ TODO
|
||||
## Demo
|
||||
|
||||
TODO
|
||||
|
||||
## TODOs
|
||||
|
||||
- Cancel button when email validation pending or expired
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
package gateways
|
||||
|
||||
import (
|
||||
entity "RenewCMS/adapters/secondary/gateways/models"
|
||||
domain "RenewCMS/domain/article"
|
||||
"RenewCMS/domain/gateways"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type ArticleRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewArticleRepository(db *gorm.DB) *ArticleRepository {
|
||||
return &ArticleRepository{db}
|
||||
}
|
||||
|
||||
func mapArticleToDomain(article entity.Article) domain.Article {
|
||||
return domain.FromDb(article.ID, article.Title, article.Body, article.Images, article.IsOnline, article.CreatedAt, article.UpdatedAt)
|
||||
}
|
||||
|
||||
func (a *ArticleRepository) Get(id uint32) (domain.Article, error) {
|
||||
var article entity.Article
|
||||
err := a.db.Model(&entity.Article{}).Preload("Images").First(&article, id).Error
|
||||
if err != nil {
|
||||
return domain.Article{}, err
|
||||
}
|
||||
|
||||
return mapArticleToDomain(article), nil
|
||||
}
|
||||
|
||||
func (a *ArticleRepository) GetByName(name string) (domain.Article, error) {
|
||||
var article entity.Article
|
||||
err := a.db.Model(&entity.Article{}).Where("title = ?", name).First(&article).Error
|
||||
if err != nil {
|
||||
return domain.Article{}, err
|
||||
}
|
||||
|
||||
return mapArticleToDomain(article), nil
|
||||
}
|
||||
|
||||
func (a *ArticleRepository) Create(article domain.Article) (domain.Article, error) {
|
||||
creationResult := a.db.Create(&entity.Article{
|
||||
Title: article.Title,
|
||||
Body: article.Body,
|
||||
})
|
||||
if creationResult.Error != nil {
|
||||
return domain.Article{}, creationResult.Error
|
||||
}
|
||||
|
||||
var createdArticle entity.Article
|
||||
creationResult.Scan(&createdArticle)
|
||||
|
||||
return mapArticleToDomain(createdArticle), nil
|
||||
}
|
||||
|
||||
func (a *ArticleRepository) GetAll() []domain.Article {
|
||||
var articles []entity.Article
|
||||
err := a.db.Model(&entity.Article{}).Find(&articles).Error
|
||||
if err != nil {
|
||||
return []domain.Article{}
|
||||
}
|
||||
|
||||
var domainArticles = make([]domain.Article, 0)
|
||||
for _, article := range articles {
|
||||
domainArticles = append(domainArticles, mapArticleToDomain(article))
|
||||
}
|
||||
|
||||
return domainArticles
|
||||
}
|
||||
|
||||
func (a *ArticleRepository) UpdateBody(id uint32, body string) (domain.Article, error) {
|
||||
var localArticle entity.Article
|
||||
err := a.db.Model(&entity.Article{}).First(&localArticle, id).Error
|
||||
if err != nil {
|
||||
return domain.Article{}, err
|
||||
}
|
||||
|
||||
localArticle.Body = body
|
||||
err = a.db.Save(&localArticle).Error
|
||||
if err != nil {
|
||||
return domain.Article{}, err
|
||||
}
|
||||
|
||||
newArticle := domain.FromDb(
|
||||
localArticle.ID,
|
||||
localArticle.Title,
|
||||
localArticle.Body,
|
||||
localArticle.Images,
|
||||
localArticle.IsOnline,
|
||||
localArticle.CreatedAt,
|
||||
localArticle.UpdatedAt,
|
||||
)
|
||||
|
||||
return newArticle, nil
|
||||
}
|
||||
|
||||
func (a *ArticleRepository) UpdateIsOnline(id uint32, isOnline bool) (domain.Article, error) {
|
||||
var localArticle entity.Article
|
||||
err := a.db.Model(&entity.Article{}).First(&localArticle, id).Error
|
||||
if err != nil {
|
||||
return domain.Article{}, err
|
||||
}
|
||||
|
||||
localArticle.IsOnline = isOnline
|
||||
err = a.db.Save(&localArticle).Error
|
||||
if err != nil {
|
||||
return domain.Article{}, err
|
||||
}
|
||||
|
||||
return mapArticleToDomain(localArticle), nil
|
||||
}
|
||||
|
||||
func (a *ArticleRepository) Delete(id uint32) error {
|
||||
return a.db.Delete(&entity.Article{}, id).Error
|
||||
}
|
||||
|
||||
func (a *ArticleRepository) AddImage(articleId uint32, imageId uint32) error {
|
||||
var localArticle entity.Article
|
||||
err := a.db.Model(&entity.Article{}).First(&localArticle, articleId).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var localImage entity.Image
|
||||
err = a.db.Model(&entity.Image{}).First(&localImage, imageId).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = a.db.Model(&localArticle).Association("Images").Append(&localImage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ gateways.IArticleRepository = &ArticleRepository{}
|
||||
@@ -0,0 +1,85 @@
|
||||
package gateways
|
||||
|
||||
import (
|
||||
entity "RenewCMS/adapters/secondary/gateways/models"
|
||||
"RenewCMS/domain/gateways"
|
||||
domain "RenewCMS/domain/image"
|
||||
"errors"
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
"mime/multipart"
|
||||
"os"
|
||||
)
|
||||
|
||||
type ImageRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewImageRepository(db *gorm.DB) *ImageRepository {
|
||||
return &ImageRepository{db}
|
||||
}
|
||||
|
||||
var contentTypeExtensions = map[string]string{
|
||||
"image/png": ".png",
|
||||
"image/jpeg": ".jpeg",
|
||||
"image/webp": ".webp",
|
||||
"image/svg+xml": ".svg",
|
||||
}
|
||||
|
||||
func mapImageToDomain(image entity.Image) domain.Image {
|
||||
return domain.FromDB(
|
||||
image.ID,
|
||||
image.Path,
|
||||
image.ArticleID,
|
||||
image.CreatedAt,
|
||||
image.UpdatedAt,
|
||||
)
|
||||
}
|
||||
|
||||
func (i ImageRepository) Create(file multipart.File, fileHeader multipart.FileHeader) (domain.Image, error) {
|
||||
fileBytes := make([]byte, fileHeader.Size)
|
||||
_, err := file.Read(fileBytes)
|
||||
if err != nil {
|
||||
return domain.Image{}, err
|
||||
}
|
||||
|
||||
err = os.MkdirAll("api/static/uploadedImages/", 0666)
|
||||
if err != nil {
|
||||
return domain.Image{}, err
|
||||
}
|
||||
|
||||
contentType := fileHeader.Header.Get("Content-Type")
|
||||
extension := contentTypeExtensions[contentType]
|
||||
if extension == "" {
|
||||
return domain.Image{}, errors.New("the file must be a PNG, JPEG, WEBP, or SVG image")
|
||||
}
|
||||
|
||||
newName := uuid.NewString() + extension
|
||||
err = os.WriteFile("api/static/uploadedImages/"+newName, fileBytes, 0666)
|
||||
if err != nil {
|
||||
return domain.Image{}, err
|
||||
}
|
||||
|
||||
newImage := i.db.Create(&domain.Image{Path: "/static/uploadedImages/" + newName})
|
||||
var createdImage entity.Image
|
||||
newImage.Scan(&createdImage)
|
||||
|
||||
return mapImageToDomain(createdImage), nil
|
||||
}
|
||||
|
||||
func (i ImageRepository) Delete(id uint32) error {
|
||||
var image entity.Image
|
||||
err := i.db.Model(&entity.Image{}).First(&image, id).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = os.Remove("api" + image.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return i.db.Delete(&entity.Image{}, id).Error
|
||||
}
|
||||
|
||||
var _ gateways.IImageRepository = &ImageRepository{}
|
||||
@@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<body>
|
||||
<h1>
|
||||
<img src="{{.Host}}/static/renewcms-favicon-64.png" alt="G" style="width: 2.5rem"/>
|
||||
RenewCMS
|
||||
</h1>
|
||||
<main>
|
||||
<p>You or someone tried to create a RenewCMS admin account with this e-mail address. If you are not responsible for
|
||||
this, please do not validate the account creation.</p>
|
||||
<p><b>If you want to validate the account creation, please <a
|
||||
href="{{.Host}}/register/validate?c={{.VerificationCode}}" target="_blank">click HERE</a>.</b>
|
||||
</p>
|
||||
<p>If you can't click the link:</p>
|
||||
<p>{{.Host}}/register/validate?c={{.VerificationCode}}</p>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<body>
|
||||
<h1>
|
||||
<img src="{{.Host}}/static/renewcms-favicon-64.png" alt="G" style="width: 2.5rem"/>
|
||||
RenewCMS
|
||||
</h1>
|
||||
<main>
|
||||
<p>You or someone tried to create reset your RenewCMS account with this e-mail address. If you are not responsible for
|
||||
this, please do not validate the password reset.</p>
|
||||
<p><b>If you want to reset your password, please <a
|
||||
href="{{.Host}}/register/reset/validate?c={{.VerificationCode}}&email={{.Email}}" target="_blank">click HERE</a>.</b>
|
||||
</p>
|
||||
<p>If you can't click the link:</p>
|
||||
<p>{{.Host}}/register/reset/validate?c={{.VerificationCode}}&email={{.Email}}</p>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,64 @@
|
||||
package gateways
|
||||
|
||||
import (
|
||||
"RenewCMS/domain/gateways"
|
||||
"bytes"
|
||||
"embed"
|
||||
"html/template"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"gopkg.in/gomail.v2"
|
||||
)
|
||||
|
||||
type MailRepository struct{}
|
||||
|
||||
//go:embed mail/templates/*
|
||||
var mailTemplateFiles embed.FS
|
||||
|
||||
func NewMailRepository() *MailRepository {
|
||||
return &MailRepository{}
|
||||
}
|
||||
|
||||
func (m MailRepository) Send(receiverAddress string, templateName string, data any) error {
|
||||
from := os.Getenv("SMTP_EMAIL")
|
||||
password := os.Getenv("SMTP_PASSWORD")
|
||||
smtpHost := os.Getenv("SMTP_HOST")
|
||||
smtpPort, _ := strconv.Atoi(os.Getenv("SMTP_PORT"))
|
||||
|
||||
d := gomail.NewDialer(smtpHost, smtpPort, from, password)
|
||||
|
||||
tmpl, err := template.ParseFS(mailTemplateFiles, "mail/templates/"+templateName+".html")
|
||||
if err != nil {
|
||||
log.Println("error when template.ParseFS:", err)
|
||||
return err
|
||||
}
|
||||
|
||||
var body bytes.Buffer
|
||||
err = tmpl.Execute(&body, data)
|
||||
if err != nil {
|
||||
log.Println("error when tmpl.Execute:", err)
|
||||
return err
|
||||
}
|
||||
|
||||
msg := gomail.NewMessage()
|
||||
msg.SetHeaders(map[string][]string{
|
||||
"From": {"RenewCMS <" + from + ">"},
|
||||
"To": {receiverAddress},
|
||||
"MIME-version": {"1.0"},
|
||||
"Content-Type": {"text/html"},
|
||||
"charset": {"UTF-8"},
|
||||
"Subject": {"RenewCMS | Action required"},
|
||||
})
|
||||
msg.SetBody("text/html", body.String())
|
||||
|
||||
if err := d.DialAndSend(msg); err != nil {
|
||||
log.Println("error when sending email:", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ gateways.IMailRepository = &MailRepository{}
|
||||
@@ -0,0 +1,18 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Article struct {
|
||||
gorm.Model
|
||||
ID uint32 `gorm:"primary_key;auto_increment;not_null"`
|
||||
Title string
|
||||
Body string
|
||||
Images []*Image `gorm:"many2many:article_images;"`
|
||||
IsOnline bool `gorm:"not_null;default:false"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Image struct {
|
||||
gorm.Model
|
||||
ID uint32 `gorm:"primary_key;auto_increment;not_null"`
|
||||
Path string
|
||||
ArticleID uint32
|
||||
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"gorm.io/gorm"
|
||||
"time"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
gorm.Model
|
||||
ID uint32 `gorm:"primaryKey;autoIncrement"`
|
||||
Username string `gorm:"unique;not null"`
|
||||
Password string `gorm:"not null"`
|
||||
PasswordResetCode string `gorm:"unique"`
|
||||
Email string `gorm:"unique;not null"`
|
||||
IsVerified bool `gorm:"default=false;not null"`
|
||||
VerificationCode string `gorm:"unique"`
|
||||
VerificationExpiration time.Time
|
||||
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package gateways
|
||||
|
||||
import (
|
||||
"RenewCMS/domain/gateways"
|
||||
"bytes"
|
||||
"embed"
|
||||
"html/template"
|
||||
)
|
||||
|
||||
type PageRepository struct{}
|
||||
|
||||
//go:embed web/templates/*
|
||||
var webTemplateFiles embed.FS
|
||||
|
||||
func NewPageRepository() *PageRepository {
|
||||
return &PageRepository{}
|
||||
}
|
||||
|
||||
func (p *PageRepository) Get(name string, data any) ([]byte, error) {
|
||||
var processedHTML bytes.Buffer
|
||||
tmpl, err := template.ParseFS(webTemplateFiles, "web/templates/"+name+".html")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = tmpl.Execute(&processedHTML, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return processedHTML.Bytes(), nil
|
||||
}
|
||||
|
||||
var _ gateways.IPageRepository = &PageRepository{}
|
||||
@@ -0,0 +1,152 @@
|
||||
package gateways
|
||||
|
||||
import (
|
||||
entity "RenewCMS/adapters/secondary/gateways/models"
|
||||
"RenewCMS/domain/gateways"
|
||||
"RenewCMS/domain/user"
|
||||
domain "RenewCMS/domain/user"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type UserRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewUserRepository(db *gorm.DB) *UserRepository {
|
||||
return &UserRepository{db}
|
||||
}
|
||||
|
||||
func mapUserToDomain(user entity.User) domain.User {
|
||||
return domain.FromDb(
|
||||
user.ID,
|
||||
user.Username,
|
||||
user.Password,
|
||||
user.PasswordResetCode,
|
||||
user.Email,
|
||||
user.IsVerified,
|
||||
user.VerificationCode,
|
||||
user.VerificationExpiration,
|
||||
user.CreatedAt, user.UpdatedAt,
|
||||
)
|
||||
}
|
||||
|
||||
func (u *UserRepository) Get(id uint32) (domain.User, error) {
|
||||
var localUser entity.User
|
||||
err := u.db.Model(&entity.User{}).First(&localUser, id).Error
|
||||
if err != nil {
|
||||
return domain.User{}, err
|
||||
}
|
||||
|
||||
return mapUserToDomain(localUser), nil
|
||||
}
|
||||
|
||||
func (u *UserRepository) Create(user domain.User) (domain.User, error) {
|
||||
hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(user.Password), 12)
|
||||
hashedVerificationCode, _ := bcrypt.GenerateFromPassword([]byte(user.VerificationCode), 12)
|
||||
|
||||
creationResult := u.db.Create(&entity.User{
|
||||
Username: user.Username,
|
||||
Password: string(hashedPassword),
|
||||
Email: user.Email,
|
||||
VerificationCode: string(hashedVerificationCode),
|
||||
VerificationExpiration: user.VerificationExpiration,
|
||||
})
|
||||
if creationResult.Error != nil {
|
||||
return domain.User{}, creationResult.Error
|
||||
}
|
||||
|
||||
var createdUser entity.User
|
||||
creationResult.Scan(&createdUser)
|
||||
|
||||
return mapUserToDomain(createdUser),
|
||||
nil
|
||||
}
|
||||
|
||||
func (u *UserRepository) Delete(id uint32) error {
|
||||
return u.db.Delete(&user.User{}, id).Error
|
||||
}
|
||||
|
||||
func (u *UserRepository) GetAll() []domain.User {
|
||||
var users []entity.User
|
||||
u.db.Model(&entity.User{}).Find(&users)
|
||||
|
||||
var domainUsers []domain.User
|
||||
for _, localUser := range users {
|
||||
domainUsers = append(domainUsers, mapUserToDomain(localUser))
|
||||
}
|
||||
|
||||
return domainUsers
|
||||
}
|
||||
|
||||
func (u *UserRepository) GetByUsername(username string) (domain.User, error) {
|
||||
var localUser entity.User
|
||||
err := u.db.Model(&entity.User{}).Where("username = ?", username).First(&localUser).Error
|
||||
if err != nil {
|
||||
return domain.User{}, err
|
||||
}
|
||||
|
||||
return mapUserToDomain(localUser), nil
|
||||
}
|
||||
|
||||
func (u *UserRepository) GetByEmail(email string) (domain.User, error) {
|
||||
var localUser entity.User
|
||||
err := u.db.Model(&entity.User{}).Where("email = ?", email).First(&localUser).Error
|
||||
if err != nil {
|
||||
return domain.User{}, err
|
||||
}
|
||||
|
||||
return mapUserToDomain(localUser), nil
|
||||
}
|
||||
|
||||
func (u *UserRepository) UpdateVerificationStatus(userId uint32, isVerified bool) (domain.User, error) {
|
||||
var localUser entity.User
|
||||
err := u.db.Model(&entity.User{}).First(&localUser, userId).Error
|
||||
if err != nil {
|
||||
return domain.User{}, err
|
||||
}
|
||||
|
||||
localUser.IsVerified = isVerified
|
||||
err = u.db.Save(&localUser).Error
|
||||
if err != nil {
|
||||
return domain.User{}, err
|
||||
}
|
||||
|
||||
return mapUserToDomain(localUser), nil
|
||||
}
|
||||
|
||||
func (u *UserRepository) UpdatePassword(userId uint32, password string) (domain.User, error) {
|
||||
var localUser entity.User
|
||||
err := u.db.Model(&entity.User{}).First(&localUser, userId).Error
|
||||
if err != nil {
|
||||
return domain.User{}, err
|
||||
}
|
||||
|
||||
hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(password), 12)
|
||||
localUser.Password = string(hashedPassword)
|
||||
err = u.db.Save(&localUser).Error
|
||||
if err != nil {
|
||||
return domain.User{}, err
|
||||
}
|
||||
|
||||
return mapUserToDomain(localUser), nil
|
||||
}
|
||||
|
||||
func (u *UserRepository) UpdatePasswordResetCode(userId uint32, code string) (domain.User, error) {
|
||||
var localUser entity.User
|
||||
err := u.db.Model(&entity.User{}).First(&localUser, userId).Error
|
||||
if err != nil {
|
||||
return domain.User{}, err
|
||||
}
|
||||
|
||||
hashedCode, _ := bcrypt.GenerateFromPassword([]byte(code), 12)
|
||||
localUser.PasswordResetCode = string(hashedCode)
|
||||
err = u.db.Save(&localUser).Error
|
||||
if err != nil {
|
||||
return domain.User{}, err
|
||||
}
|
||||
|
||||
return mapUserToDomain(localUser), nil
|
||||
}
|
||||
|
||||
var _ gateways.IUserRepository = &UserRepository{}
|
||||
@@ -0,0 +1,66 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>RenewCMS | Create article</title>
|
||||
{{.Head}}
|
||||
<style>
|
||||
form {
|
||||
max-width: 24rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="text-dark">
|
||||
{{.Navbar}}
|
||||
<div class="container mt-3 text-black-50">
|
||||
<h1>Article - creation</h1>
|
||||
<p>Create a new article</p>
|
||||
<form action="create" method="post" class="d-flex align-items-center flex-column gap-3" id="createArticleForm">
|
||||
<div class="form-floating w-100">
|
||||
<input type="text" class="form-control {{ if .PageError.IsError }} is-invalid {{ end }}" id="name"
|
||||
name="name" placeholder="Chose the article name" value="{{ .Name }}">
|
||||
<label for="name">Name</label>
|
||||
<div class="invalid-feedback">{{ .PageError.Message }}</div>
|
||||
</div>
|
||||
<button id="createArticleButton" class="btn btn-primary w-100" disabled type="submit">
|
||||
<span class="createArticleFormButtonLoading visually-hidden spinner-border spinner-border-sm"
|
||||
role="status"></span>
|
||||
<span class="createArticleFormButtonDefault">Create</span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const button = document.querySelector("#createArticleButton")
|
||||
const inputs = document.querySelectorAll('input')
|
||||
const form = document.querySelector("#createArticleForm")
|
||||
|
||||
function formFieldsEmpty() {
|
||||
return Array.from(inputs).some((input) => input.value === "")
|
||||
}
|
||||
|
||||
function setButtonDisabled() {
|
||||
button.disabled = formFieldsEmpty() ? "disabled" : ""
|
||||
}
|
||||
|
||||
function setButtonLoading() {
|
||||
button.classList.add("disabled")
|
||||
button.querySelector(".createArticleFormButtonDefault").classList.add("visually-hidden")
|
||||
button.querySelector(".createArticleFormButtonLoading").classList.remove("visually-hidden")
|
||||
}
|
||||
|
||||
function onCreateArticleFormSubmit(event) {
|
||||
setButtonLoading()
|
||||
event.target.submit()
|
||||
}
|
||||
|
||||
function onInput(event) {
|
||||
if (event.target.tagName === "INPUT") setButtonDisabled()
|
||||
}
|
||||
|
||||
form.addEventListener('submit', onCreateArticleFormSubmit)
|
||||
window.addEventListener('input', onInput)
|
||||
setButtonDisabled()
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,94 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>RenewCMS | Article edition</title>
|
||||
{{.Head}}
|
||||
</head>
|
||||
<body class="text-dark vh-100 d-flex flex-column">
|
||||
{{.Navbar}}
|
||||
<form class="container mt-3 text-black-50 d-flex flex-column h-100" action="edit" method="post">
|
||||
<div class="d-flex align-items-center justify-content-between">
|
||||
<div>
|
||||
<h1>Article - edition</h1>
|
||||
<label for="articleBody">Edition</label>
|
||||
</div>
|
||||
{{ if .Alert.Message }}
|
||||
<div class="alert {{ if .Alert.IsError }} alert-danger {{ else }} alert-success {{ end }} alert-dismissible"
|
||||
role="alert">
|
||||
{{ .Alert.Message }}
|
||||
<button type="button" class="btn-close" aria-label="Close" data-bs-dismiss="alert"></button>
|
||||
</div>
|
||||
{{ end }}
|
||||
<div class="d-flex justify-content-center align-items-center">
|
||||
<button class="btn btn-success d-flex justify-content-center align-items-center gap-1" style="fill: white;"
|
||||
type="submit">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24">
|
||||
<path d="M840-680v480q0 33-23.5 56.5T760-120H200q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h480l160 160Zm-80 34L646-760H200v560h560v-446ZM480-240q50 0 85-35t35-85q0-50-35-85t-85-35q-50 0-85 35t-35 85q0 50 35 85t85 35ZM240-560h360v-160H240v160Zm-40-86v446-560 114Z"/>
|
||||
</svg>
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-black-50 d-flex align-items-center justify-content-center h-100 py-3">
|
||||
<textarea id="articleBody" name="articleBody">{{.Article.Body}}</textarea>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<script src="/static/tinymce/js/tinymce/tinymce.min.js"></script>
|
||||
<script>
|
||||
tinymce.init({
|
||||
selector: '#articleBody',
|
||||
promotion: false,
|
||||
plugins: 'image lists visualblocks',
|
||||
toolbar: 'undo redo | formatselect | bold italic underline strikethrough | alignleft aligncenter alignright alignjustify | bullist numlist | outdent indent | removeformat | image',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
setup: function (editor) {
|
||||
editor.on('init', function () {
|
||||
console.log("LOADED")
|
||||
});
|
||||
},
|
||||
license_key: 'gpl',
|
||||
images_upload_handler: (blobInfo, progress) => new Promise((resolve, reject) => {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.withCredentials = false;
|
||||
xhr.open('POST', "/article/{{.Article.ID}}/image/create");
|
||||
|
||||
xhr.upload.onprogress = (e) => {
|
||||
progress(e.loaded / e.total * 100);
|
||||
};
|
||||
|
||||
xhr.onload = () => {
|
||||
if (xhr.status === 403) {
|
||||
reject({message: 'HTTP Error: ' + xhr.status, remove: true});
|
||||
return;
|
||||
}
|
||||
|
||||
if (xhr.status < 200 || xhr.status >= 300) {
|
||||
reject('HTTP Error: ' + xhr.status);
|
||||
return;
|
||||
}
|
||||
|
||||
const json = JSON.parse(xhr.responseText);
|
||||
|
||||
if (!json || typeof json.location != 'string') {
|
||||
reject('Invalid JSON: ' + xhr.responseText);
|
||||
return;
|
||||
}
|
||||
|
||||
resolve(`http{{ if .Secured }}s{{ end }}://${window.location.host}${json.location}`);
|
||||
};
|
||||
|
||||
xhr.onerror = () => {
|
||||
reject('Image upload failed due to a XHR Transport error. Code: ' + xhr.status);
|
||||
};
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('file', blobInfo.blob(), blobInfo.filename());
|
||||
|
||||
xhr.send(formData);
|
||||
}),
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,112 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>RenewCMS | Articles</title>
|
||||
{{.Head}}
|
||||
|
||||
<style>
|
||||
.btn-outline-info:hover > *,
|
||||
.btn-outline-danger:hover > * {
|
||||
fill: #fff !important;
|
||||
transition: fill ease-in-out 50ms;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="text-dark">
|
||||
{{.Navbar}}
|
||||
<div class="container mt-3 text-black-50">
|
||||
<h1>Articles</h1>
|
||||
<p>List of your articles</p>
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Name</th>
|
||||
<th scope="col">Creation Date</th>
|
||||
<th scope="col">Edition Date</th>
|
||||
<th scope="col">Status</th>
|
||||
<th scope="col">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td colspan="5" class="text-center">
|
||||
<form action="article/create" method="get">
|
||||
<button class="btn btn-sm btn-link w-100 h-100" type="submit">Create a new article...</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{{ range $article := .Articles }}
|
||||
<tr>
|
||||
<td>{{ $article.Title }}</td>
|
||||
<td class="date">{{ $article.CreatedAt }}</td>
|
||||
<td class="date">{{ $article.UpdatedAt }}</td>
|
||||
<td>{{ if $article.IsOnline }}🟢 Online{{ else }}🟠 Offline{{ end }}</td>
|
||||
<td>
|
||||
<a href="/article/{{ $article.ID }}/edit" class="btn btn-outline-primary btn-sm">
|
||||
<svg width="12px" height="12px" fill="currentColor">
|
||||
<use xlink:href="/static/bootstrap-icons.svg#pen"/>
|
||||
</svg>
|
||||
</a>
|
||||
{{ if $article.IsOnline }}
|
||||
<a href="/article/{{ $article.ID }}/unpublish" class="btn btn-outline-info btn-sm">
|
||||
<svg width="12px" height="12px" fill="currentColor">
|
||||
<use xlink:href="/static/bootstrap-icons.svg#eye-slash"/>
|
||||
</svg>
|
||||
</a>
|
||||
{{ else }}
|
||||
<a href="/article/{{ $article.ID }}/publish" class="btn btn-outline-info btn-sm">
|
||||
<svg width="12px" height="12px" fill="currentColor">
|
||||
<use xlink:href="/static/bootstrap-icons.svg#eye"/>
|
||||
</svg>
|
||||
</a>
|
||||
{{ end }}
|
||||
<a href="/article/{{ $article.Title }}/delete" class="btn btn-outline-danger btn-sm" data-bs-toggle="modal"
|
||||
data-bs-target="#{{ $article.ID }}">
|
||||
<svg width=" 12px" height="12px" fill="currentColor">
|
||||
<use xlink:href="/static/bootstrap-icons.svg#trash"/>
|
||||
</svg>
|
||||
</a>
|
||||
<div class="modal fade" id="{{ $article.ID }}" tabindex="-1" aria-labelledby="{{ $article.ID }}Label"
|
||||
aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h1 class="modal-title fs-5" id="{{ $article.ID }}Label">Delete post</h1>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"
|
||||
aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>You are about to delete the post <b>« {{ $article.Title }} »</b>, this is definitive,
|
||||
are you sure?</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel
|
||||
</button>
|
||||
<a href="/article/{{$article.ID}}/delete" class="btn btn-danger">Delete</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const datesElements = document.querySelectorAll("td.date")
|
||||
datesElements.forEach(date => {
|
||||
date.innerText = new Date(date.innerText).toLocaleDateString(undefined, {
|
||||
year: "2-digit",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit"
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,32 @@
|
||||
<nav class="navbar navbar-expand-lg bg-body-tertiary">
|
||||
<div class="container">
|
||||
<div class="d-flex gap-2 justify-content-center align-items-center">
|
||||
<img src="/static/renewcms-favicon-64.png" alt="Logo" style="width: 2.5rem"/>
|
||||
<a class="navbar-brand h1 my-auto" href="https://github.com/Floriansylvain/RenewCMS" target="_blank">
|
||||
RenewCMS
|
||||
</a>
|
||||
</div>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav"
|
||||
aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav me-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/home">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/article">Articles</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/integration">Integration</a>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link disconnect-link" href="/logout">Log out</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
@@ -0,0 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>RenewCMS | Home</title>
|
||||
{{.Head}}
|
||||
</head>
|
||||
<body class="text-dark">
|
||||
{{.Navbar}}
|
||||
<div class="container mt-3 text-black-50">
|
||||
<h1>Home</h1>
|
||||
<p>Welcome on RenewCMS !</p>
|
||||
<a href="/article">
|
||||
<button class="btn btn-outline-primary">Articles »</button>
|
||||
</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,64 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>RenewCMS | Home</title>
|
||||
{{.Head}}
|
||||
</head>
|
||||
<body class="text-dark">
|
||||
{{.Navbar}}
|
||||
<div class="container mt-3 text-black-50">
|
||||
<h1>Implementation</h1>
|
||||
<p>How to use headless API</p>
|
||||
<div class="mt-5">
|
||||
<section>
|
||||
<h2>Introduction</h2>
|
||||
<p>This documentation explains how to integrate our headless CMS API to retrieve article data via the <code>/article</code>
|
||||
route.</p>
|
||||
</section>
|
||||
<section>
|
||||
<h2>/v1/article</h2>
|
||||
<p>The <code>{{ .Host }}/v1/article</code> route returns a list of articles in JSON format. Each article has the
|
||||
following
|
||||
structure:</p>
|
||||
<pre>
|
||||
<code>[
|
||||
{
|
||||
"id": 12,
|
||||
"title": "This is a article!",
|
||||
"body": "<p>?</p>\r\n<p><img src=\"../../static/uploadedImages/649691a8-4f2e-48ca-9abe-27a60621d2e4.png\"
|
||||
alt=\"\" width=\"391\" height=\"465\"></p>",
|
||||
"created_at": "2024-06-13T15:30:55.3563238+02:00",
|
||||
"updated_at": "2024-06-13T15:31:17.5508931+02:00"
|
||||
}
|
||||
]</code>
|
||||
</pre>
|
||||
</section>
|
||||
<section>
|
||||
<h2>/v1/article/{id}</h2>
|
||||
<p>The <code>{{ .Host }}/v1/article/{id}</code> route returns a single article in JSON format. The article has the
|
||||
following
|
||||
structure:</p>
|
||||
<pre>
|
||||
<code>{
|
||||
"id": 12,
|
||||
"title": "ravioli",
|
||||
"body": "<\p>?<\/p>\r\n<\p><\img src=\"../../static/uploadedImages/649691a8-4f2e-48ca-9abe-27a60621d2e4.png\" alt=\"\"
|
||||
width=\"391\" height=\"465\"><\/p>",
|
||||
"images": [
|
||||
{
|
||||
"id": 17,
|
||||
"path": "/static/uploadedImages/649691a8-4f2e-48ca-9abe-27a60621d2e4.png",
|
||||
"article_id": 0,
|
||||
"created_at": "2024-06-13T15:31:09.728359+02:00",
|
||||
"updated_at": "2024-06-13T15:31:09.728359+02:00"
|
||||
}
|
||||
],
|
||||
"created_at": "2024-06-13T15:30:55.3563238+02:00",
|
||||
"updated_at": "2024-06-13T15:31:17.5508931+02:00"
|
||||
}</code>
|
||||
</pre>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,108 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>RenewCMS | Login</title>
|
||||
{{.Head}}
|
||||
|
||||
<style>
|
||||
.form-container {
|
||||
max-width: 24rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="d-flex min-vh-100 vw-100 justify-content-center align-items-center text-dark">
|
||||
<div class="container">
|
||||
<div class="d-flex flex-column m-auto form-container">
|
||||
<div>
|
||||
<h1>RenewCMS</h1>
|
||||
<h2>Login</h2>
|
||||
</div>
|
||||
<form action="login" method="POST" class="mt-5" id="loginForm">
|
||||
<div class="d-flex flex-column gap-4">
|
||||
<div class="form-floating">
|
||||
<input class="form-control {{ if .PageError.IsError }} is-invalid {{ end }}"
|
||||
id="username"
|
||||
name="username"
|
||||
placeholder="Username"
|
||||
required
|
||||
type="text"
|
||||
value="{{.Username}}">
|
||||
<label for="username">Username</label>
|
||||
</div>
|
||||
<div class="form-floating">
|
||||
<input class="form-control {{ if .PageError.IsError }} is-invalid {{ end }}"
|
||||
id="password"
|
||||
name="password"
|
||||
placeholder="Password"
|
||||
required
|
||||
type="password">
|
||||
<label for="password">Password</label>
|
||||
<div class="invalid-feedback">{{.PageError.Message}}</div>
|
||||
</div>
|
||||
<button id="loginFormButton" class="btn btn-primary" disabled type="submit">
|
||||
<span class="loginFormButtonLoading visually-hidden spinner-border spinner-border-sm"
|
||||
role="status"></span>
|
||||
<span class="loginFormButtonDefault">Log in</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<div class="d-flex gap-2 my-2 w-100">
|
||||
<a href="register" class="btn btn-outline-secondary w-100" type="submit">
|
||||
First setup? Register
|
||||
</a>
|
||||
<a href="register/reset/request" class="btn btn-outline-secondary w-100" type="submit">
|
||||
Forgot password
|
||||
</a>
|
||||
</div>
|
||||
{{ if .Success }}
|
||||
<div class="alert alert-success my-4" role="alert">
|
||||
{{ .Success }}
|
||||
</div>
|
||||
{{ else if .Failure }}
|
||||
<div class="alert alert-danger my-4" role="alert">
|
||||
{{ .Failure }}
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const button = document.querySelector("#loginFormButton")
|
||||
const inputs = document.querySelectorAll('input')
|
||||
const form = document.querySelector("#loginForm")
|
||||
|
||||
function formFieldsEmpty() {
|
||||
return Array.from(inputs).some((input) => input.value === "")
|
||||
}
|
||||
|
||||
function isFormValid() {
|
||||
return !formFieldsEmpty() && validateEmail()
|
||||
}
|
||||
|
||||
function setButtonDisabled() {
|
||||
button.disabled = isFormValid() ? "disabled" : ""
|
||||
}
|
||||
|
||||
function setButtonLoading() {
|
||||
button.classList.add("disabled")
|
||||
button.querySelector(".loginFormButtonDefault").classList.add("visually-hidden")
|
||||
button.querySelector(".loginFormButtonLoading").classList.remove("visually-hidden")
|
||||
}
|
||||
|
||||
function onLoginFormSubmit(event) {
|
||||
event.preventDefault()
|
||||
setButtonLoading()
|
||||
event.target.submit()
|
||||
}
|
||||
|
||||
function onInput(event) {
|
||||
if (event.target.tagName === "INPUT") setButtonDisabled()
|
||||
}
|
||||
|
||||
form.addEventListener('submit', onLoginFormSubmit)
|
||||
window.addEventListener('input', onInput)
|
||||
setButtonDisabled()
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,98 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>RenewCMS | Password reset</title>
|
||||
{{.Head}}
|
||||
|
||||
<style>
|
||||
.form-container {
|
||||
max-width: 24rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="d-flex min-vh-100 vw-100 justify-content-center align-items-center text-dark">
|
||||
<div class="container">
|
||||
<div class="d-flex flex-column m-auto form-container">
|
||||
<div>
|
||||
<h1>RenewCMS</h1>
|
||||
<h2>Admin account password reset</h2>
|
||||
</div>
|
||||
<form action="request" method="POST" class="mt-5" id="registerForm">
|
||||
<div class="d-flex flex-column gap-4">
|
||||
<div class="form-floating">
|
||||
<input class="form-control {{ if .PageError.Email }} is-invalid {{ end }}"
|
||||
id="email"
|
||||
name="email"
|
||||
placeholder="E-mail"
|
||||
required
|
||||
type="email"
|
||||
value="{{.Email}}"
|
||||
onblur="validateEmail()"
|
||||
{{ if .Email }} disabled {{ end }}>
|
||||
<label for="email">E-mail</label>
|
||||
</div>
|
||||
<button id="registerFormButton" class="btn btn-primary" disabled type="submit">
|
||||
<span class="registerFormButtonLoading visually-hidden spinner-border spinner-border-sm"
|
||||
role="status"></span>
|
||||
<span class="registerFormButtonDefault">Send e-mail</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{{ if .Success }}
|
||||
<div class="alert alert-success my-4" role="alert">
|
||||
{{ .Success }}
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const docElems = {
|
||||
form: document.querySelector("#registerForm"),
|
||||
button: document.querySelector("#registerFormButton"),
|
||||
inputs: document.querySelectorAll("input"),
|
||||
email: document.querySelector("#email"),
|
||||
}
|
||||
|
||||
function formSomeFieldsEmpty() {
|
||||
return Array.from(docElems.inputs).some((input) => input.value === "")
|
||||
}
|
||||
|
||||
function setButtonLoading() {
|
||||
docElems.button.classList.add("disabled")
|
||||
docElems.button.querySelector(".registerFormButtonDefault").classList.add("visually-hidden")
|
||||
docElems.button.querySelector(".registerFormButtonLoading").classList.remove("visually-hidden")
|
||||
}
|
||||
|
||||
function onRegisterFormSubmit(event) {
|
||||
event.preventDefault()
|
||||
setButtonLoading()
|
||||
if (formSomeFieldsEmpty()) return
|
||||
event.target.submit()
|
||||
}
|
||||
|
||||
function validateEmail() {
|
||||
const email = docElems.email.value.trim()
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
||||
|
||||
if (!emailRegex.test(email)) {
|
||||
docElems.email.classList.add("is-invalid")
|
||||
return false
|
||||
} else {
|
||||
docElems.email.classList.remove("is-invalid")
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
function onInput(event) {
|
||||
if (event.target.tagName === "INPUT") {
|
||||
docElems.button.disabled = !validateEmail() || formSomeFieldsEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
docElems.form.addEventListener('submit', onRegisterFormSubmit)
|
||||
window.addEventListener('input', onInput)
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,140 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>RenewCMS | Password reset</title>
|
||||
{{.Head}}
|
||||
|
||||
<style>
|
||||
.form-container {
|
||||
max-width: 24rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="d-flex min-vh-100 vw-100 justify-content-center align-items-center text-dark">
|
||||
<div class="container">
|
||||
<div class="d-flex flex-column m-auto form-container">
|
||||
<div>
|
||||
<h1>RenewCMS</h1>
|
||||
<h2>Admin account password reset</h2>
|
||||
</div>
|
||||
<form action="validate" method="POST" class="mt-5" id="registerForm">
|
||||
<div class="d-flex flex-column gap-4">
|
||||
<div class="form-floating">
|
||||
<input class="form-control"
|
||||
id="code"
|
||||
name="code"
|
||||
placeholder="Code"
|
||||
type="text"
|
||||
value="{{.Code}}"
|
||||
readonly>
|
||||
<label for="code">Code</label>
|
||||
</div>
|
||||
<div class="form-floating">
|
||||
<input class="form-control"
|
||||
id="email"
|
||||
name="email"
|
||||
placeholder="E-mail"
|
||||
type="email"
|
||||
value="{{.Email}}"
|
||||
readonly>
|
||||
<label for="email">E-mail</label>
|
||||
</div>
|
||||
<div class="form-floating">
|
||||
<input class="form-control {{ if .PageError.Password }} is-invalid {{ end }}"
|
||||
id="password"
|
||||
name="password"
|
||||
placeholder="Password"
|
||||
required
|
||||
type="password">
|
||||
<label for="password">Password</label>
|
||||
<div class="invalid-feedback">The password must contain at least 8 characters.</div>
|
||||
</div>
|
||||
<div class="form-floating">
|
||||
<input class="form-control {{ if .PageError.Password }} is-invalid {{ end }}"
|
||||
id="confirmPassword"
|
||||
name="confirmPassword"
|
||||
placeholder="Password confirmation"
|
||||
required
|
||||
type="password"
|
||||
onblur="validatePasswordMatch()"
|
||||
oninput="validatePasswordMatch()">
|
||||
<label for="confirmPassword">Password confirmation</label>
|
||||
<div class="invalid-feedback">The passwords do not match.</div>
|
||||
</div>
|
||||
<button id="registerFormButton" class="btn btn-primary" disabled type="submit">
|
||||
<span class="registerFormButtonLoading visually-hidden spinner-border spinner-border-sm"
|
||||
role="status"></span>
|
||||
<span class="registerFormButtonDefault">Confirm changes</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{{ if .Error.Message }}
|
||||
<div class="alert alert-danger my-4" role="alert">
|
||||
{{ .Error.Message }}
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const docElems = {
|
||||
form: document.querySelector("#registerForm"),
|
||||
button: document.querySelector("#registerFormButton"),
|
||||
inputs: document.querySelectorAll("input"),
|
||||
email: document.querySelector("#email"),
|
||||
password: document.querySelector("#password"),
|
||||
confirmPassword: document.querySelector("#confirmPassword"),
|
||||
}
|
||||
|
||||
function formSomeFieldsEmpty() {
|
||||
return Array.from(docElems.inputs).some((input) => input.value === "")
|
||||
}
|
||||
|
||||
function setButtonLoading() {
|
||||
docElems.button.classList.add("disabled")
|
||||
docElems.button.querySelector(".registerFormButtonDefault").classList.add("visually-hidden")
|
||||
docElems.button.querySelector(".registerFormButtonLoading").classList.remove("visually-hidden")
|
||||
}
|
||||
|
||||
function onRegisterFormSubmit(event) {
|
||||
event.preventDefault()
|
||||
setButtonLoading()
|
||||
if (formSomeFieldsEmpty()) return
|
||||
event.target.submit()
|
||||
}
|
||||
|
||||
function validateEmail() {
|
||||
const email = docElems.email.value.trim()
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
||||
|
||||
if (!emailRegex.test(email)) {
|
||||
docElems.email.classList.add("is-invalid")
|
||||
return false
|
||||
} else {
|
||||
docElems.email.classList.remove("is-invalid")
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
function onInput(event) {
|
||||
if (event.target.tagName === "INPUT") {
|
||||
docElems.button.disabled = !validateEmail() || formSomeFieldsEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
function validatePasswordMatch() {
|
||||
if (docElems.password.value !== docElems.confirmPassword.value) {
|
||||
docElems.confirmPassword.classList.add("is-invalid")
|
||||
return false
|
||||
} else {
|
||||
docElems.confirmPassword.classList.remove("is-invalid")
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
docElems.form.addEventListener('submit', onRegisterFormSubmit)
|
||||
window.addEventListener('input', onInput)
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,147 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>RenewCMS | Setup</title>
|
||||
{{.Head}}
|
||||
|
||||
<style>
|
||||
.form-container {
|
||||
max-width: 24rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="d-flex min-vh-100 vw-100 justify-content-center align-items-center text-dark">
|
||||
<div class="container">
|
||||
<div class="d-flex flex-column m-auto form-container">
|
||||
<div>
|
||||
<h1>RenewCMS</h1>
|
||||
<h2>Admin account creation</h2>
|
||||
</div>
|
||||
<form action="register" method="POST" class="mt-5" id="registerForm">
|
||||
<div class="d-flex flex-column gap-4">
|
||||
<div class="form-floating">
|
||||
<input class="form-control {{ if .PageError.Email }} is-invalid {{ end }}"
|
||||
id="email"
|
||||
name="email"
|
||||
placeholder="E-mail"
|
||||
required
|
||||
type="email"
|
||||
value="{{.Email}}"
|
||||
onblur="validateEmail()">
|
||||
<label for="email">E-mail</label>
|
||||
<div class="invalid-feedback">The e-mail must be a valid e-mail and not already in use.</div>
|
||||
</div>
|
||||
<div class="form-floating">
|
||||
<input class="form-control {{ if .PageError.Username }} is-invalid {{ end }}"
|
||||
id="username"
|
||||
name="username"
|
||||
placeholder="Username"
|
||||
required
|
||||
type="text"
|
||||
value="{{.Username}}">
|
||||
<label for="username">Username</label>
|
||||
<div class="invalid-feedback">The username must be between 3 and 20 characters long.</div>
|
||||
</div>
|
||||
<div class="form-floating">
|
||||
<input class="form-control {{ if .PageError.Password }} is-invalid {{ end }}"
|
||||
id="password"
|
||||
name="password"
|
||||
placeholder="Password"
|
||||
required
|
||||
type="password">
|
||||
<label for="password">Password</label>
|
||||
<div class="invalid-feedback">The password must contain at least 8 characters.</div>
|
||||
</div>
|
||||
<div class="form-floating">
|
||||
<input class="form-control {{ if .PageError.Password }} is-invalid {{ end }}"
|
||||
id="confirmPassword"
|
||||
name="confirmPassword"
|
||||
placeholder="Password confirmation"
|
||||
required
|
||||
type="password"
|
||||
onblur="validatePasswordMatch()"
|
||||
oninput="validatePasswordMatch()">
|
||||
<label for="confirmPassword">Password confirmation</label>
|
||||
<div class="invalid-feedback">The passwords do not match.</div>
|
||||
</div>
|
||||
<button id="registerFormButton" class="btn btn-primary" disabled type="submit">
|
||||
<span class="registerFormButtonLoading visually-hidden spinner-border spinner-border-sm"
|
||||
role="status"></span>
|
||||
<span class="registerFormButtonDefault">Next</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<form action="login" method="get" class="mt-2 w-100">
|
||||
<button class="btn btn-outline-primary w-100" type="submit">
|
||||
Already registered? Log in!
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const docElems = {
|
||||
form: document.querySelector("#registerForm"),
|
||||
button: document.querySelector("#registerFormButton"),
|
||||
inputs: document.querySelectorAll("input"),
|
||||
email: document.querySelector("#email"),
|
||||
password: document.querySelector("#password"),
|
||||
confirmPassword: document.querySelector("#confirmPassword"),
|
||||
}
|
||||
|
||||
function formSomeFieldsEmpty() {
|
||||
return Array.from(docElems.inputs).some((input) => input.value === "")
|
||||
}
|
||||
|
||||
function isFormValid() {
|
||||
return !formSomeFieldsEmpty() && validateEmail() && validatePasswordMatch()
|
||||
}
|
||||
|
||||
function setButtonLoading() {
|
||||
docElems.button.classList.add("disabled")
|
||||
docElems.button.querySelector(".registerFormButtonDefault").classList.add("visually-hidden")
|
||||
docElems.button.querySelector(".registerFormButtonLoading").classList.remove("visually-hidden")
|
||||
}
|
||||
|
||||
function onRegisterFormSubmit(event) {
|
||||
event.preventDefault()
|
||||
setButtonLoading()
|
||||
if (!isFormValid()) return
|
||||
event.target.submit()
|
||||
}
|
||||
|
||||
function onInput(event) {
|
||||
if (event.target.tagName === "INPUT") {
|
||||
docElems.button.disabled = !isFormValid()
|
||||
}
|
||||
}
|
||||
|
||||
function validateEmail() {
|
||||
const email = docElems.email.value.trim()
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
||||
|
||||
if (!emailRegex.test(email)) {
|
||||
docElems.email.classList.add("is-invalid")
|
||||
return false
|
||||
} else {
|
||||
docElems.email.classList.remove("is-invalid")
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
function validatePasswordMatch() {
|
||||
if (docElems.password.value !== docElems.confirmPassword.value) {
|
||||
docElems.confirmPassword.classList.add("is-invalid")
|
||||
return false
|
||||
} else {
|
||||
docElems.confirmPassword.classList.remove("is-invalid")
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
docElems.form.addEventListener('submit', onRegisterFormSubmit)
|
||||
window.addEventListener('input', onInput)
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,47 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>RenewCMS | Setup</title>
|
||||
{{.Head}}
|
||||
|
||||
<style>
|
||||
.form-container {
|
||||
max-width: 24rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="d-flex min-vh-100 vw-100 justify-content-center align-items-center text-dark">
|
||||
<div class="container">
|
||||
<div class="d-flex flex-column m-auto form-container">
|
||||
<div>
|
||||
<h1>RenewCMS</h1>
|
||||
<h2>Verify your email</h2>
|
||||
</div>
|
||||
<p>An e-mail with the validation link was sent to the address you just registered.</p>
|
||||
<p>If you misspelled the address or just didn't receive any e-mails, please check your spams or cancel the
|
||||
account creation.</p>
|
||||
<form action="" method="post" class="d-flex">
|
||||
<button class="btn btn-outline-primary w-100" type="submit">
|
||||
<span class="visually-hidden spinner-border spinner-border-sm"
|
||||
role="status"></span>
|
||||
<span>Cancel</span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const button = document.querySelector("button")
|
||||
|
||||
function setButtonLoading() {
|
||||
button.classList.add("disabled")
|
||||
button.querySelector("button > span + span").classList.add("visually-hidden")
|
||||
button.querySelector("button > span:first-child").classList.remove("visually-hidden")
|
||||
}
|
||||
|
||||
window.addEventListener('submit', setButtonLoading)
|
||||
button.addEventListener('click', setButtonLoading)
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,49 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>RenewCMS | Setup</title>
|
||||
{{.Head}}
|
||||
|
||||
<style>
|
||||
.form-container {
|
||||
max-width: 24rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="d-flex min-vh-100 vw-100 justify-content-center align-items-center text-dark">
|
||||
<div class="container">
|
||||
<div class="d-flex flex-column gap-5 m-auto form-container">
|
||||
<div>
|
||||
<h1>RenewCMS</h1>
|
||||
<h2>E-mail verification</h2>
|
||||
</div>
|
||||
{{ if .PageError.IsError }}
|
||||
<p>{{.PageError.Message}}</p>
|
||||
{{ else }}
|
||||
<p>Your e-mail was successfully validated!</p>
|
||||
<form action="../home" method="get" class="d-flex">
|
||||
<button class="btn btn-primary w-100" type="submit">
|
||||
<span class="visually-hidden spinner-border spinner-border-sm"
|
||||
role="status"></span>
|
||||
<span>Continue</span>
|
||||
</button>
|
||||
</form>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const button = document.querySelector("button")
|
||||
|
||||
function setButtonLoading() {
|
||||
button.classList.add("disabled")
|
||||
button.querySelector("button > span + span").classList.add("visually-hidden")
|
||||
button.querySelector("button > span:first-child").classList.remove("visually-hidden")
|
||||
}
|
||||
|
||||
window.addEventListener('submit', setButtonLoading)
|
||||
button.addEventListener('click', setButtonLoading)
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,7 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
||||
<link rel="icon" type="image/x-icon" href="/static/favicon.ico">
|
||||
<link rel="stylesheet" href="/static/bootstrap.min.css" type="text/css">
|
||||
<script src="/static/bootstrap.min.js"
|
||||
type="application/javascript"
|
||||
defer></script>
|
||||
@@ -0,0 +1,99 @@
|
||||
package article
|
||||
|
||||
import (
|
||||
"RenewCMS/api"
|
||||
"RenewCMS/api/controllers/auth"
|
||||
"RenewCMS/domain/article"
|
||||
"RenewCMS/useCases"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
type PostArticle struct {
|
||||
Title string `json:"title" validate:"required,min=3,max=50"`
|
||||
Body string `json:"body" validate:"required,max=10000"`
|
||||
}
|
||||
|
||||
const IdUint32ErrorMessage = "The server expects the ID to be in the format of an unsigned 32-bit integer (uint32)."
|
||||
|
||||
func getArticle(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := strconv.ParseUint(chi.URLParam(r, "id"), 10, 32)
|
||||
if err != nil {
|
||||
http.Error(w, IdUint32ErrorMessage, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
localArticle, err := api.Container.GetArticleUseCase.GetArticle(uint32(id))
|
||||
if err != nil || !localArticle.IsOnline {
|
||||
http.Error(w, "The requested resource, identified by its unique ID, could not be found on the server.", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
articleJson, _ := json.Marshal(localArticle)
|
||||
_, _ = w.Write(articleJson)
|
||||
}
|
||||
|
||||
func postArticle(w http.ResponseWriter, r *http.Request) {
|
||||
var localArticle PostArticle
|
||||
err := json.NewDecoder(r.Body).Decode(&localArticle)
|
||||
if err != nil {
|
||||
http.Error(w, auth.BodyErrorMessage, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
err = api.Validate.Struct(localArticle)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
createdArticle, err := api.Container.CreateArticleUseCase.CreateArticle(useCases.CreateArticleCommand{
|
||||
Title: localArticle.Title,
|
||||
Body: localArticle.Body,
|
||||
})
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
articleJson, _ := json.Marshal(createdArticle)
|
||||
|
||||
_, _ = w.Write(articleJson)
|
||||
}
|
||||
|
||||
func listArticles(w http.ResponseWriter, _ *http.Request) {
|
||||
articles := api.Container.ListArticlesUseCase.ListArticles()
|
||||
onlineArticles := make([]article.Article, 0)
|
||||
for _, localArticle := range articles {
|
||||
if localArticle.IsOnline {
|
||||
onlineArticles = append(onlineArticles, localArticle)
|
||||
}
|
||||
}
|
||||
articlesJson, _ := json.Marshal(onlineArticles)
|
||||
_, _ = w.Write(articlesJson)
|
||||
}
|
||||
|
||||
func deleteArticle(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := strconv.ParseUint(chi.URLParam(r, "id"), 10, 32)
|
||||
if err != nil {
|
||||
http.Error(w, IdUint32ErrorMessage, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
err = api.Container.DeleteArticleUseCase.DeleteArticle(uint32(id))
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusNotFound)
|
||||
}
|
||||
|
||||
_, _ = w.Write([]byte("article deleted"))
|
||||
}
|
||||
|
||||
func NewArticleRouter() http.Handler {
|
||||
r := chi.NewRouter()
|
||||
r.Get("/{id}", getArticle)
|
||||
r.Post("/", postArticle)
|
||||
r.Get("/", listArticles)
|
||||
r.Delete("/{id}", deleteArticle)
|
||||
return r
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"RenewCMS/api"
|
||||
"RenewCMS/domain/user"
|
||||
"RenewCMS/useCases"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/jwtauth/v5"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
type RegisterCredentials struct {
|
||||
Username string `json:"username" validate:"required,min=3,max=20"`
|
||||
Password string `json:"password" validate:"required,min=8"`
|
||||
Email string `json:"email" validate:"required,email"`
|
||||
}
|
||||
|
||||
type LoginCredentials struct {
|
||||
Username string `json:"username" validate:"required,min=3,max=20"`
|
||||
Password string `json:"password" validate:"required,min=8"`
|
||||
}
|
||||
|
||||
var shouldCookieBeSecure = os.Getenv("ENVIRONMENT") == "production"
|
||||
var Token *jwtauth.JWTAuth
|
||||
|
||||
const LogsErrorMessage = "Access to the requested resource is forbidden due to incorrect password and/or username."
|
||||
const BodyErrorMessage = "The request cannot be processed due to a mismatch in the format of the body."
|
||||
|
||||
func SetJwtCookie(w *http.ResponseWriter, userId uint32) error {
|
||||
_, tokenString, err := Token.Encode(map[string]any{"user_id": userId})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
http.SetCookie(*w, &http.Cookie{
|
||||
Name: "jwt",
|
||||
Value: tokenString,
|
||||
Expires: time.Now().Add(24 * time.Hour),
|
||||
Secure: shouldCookieBeSecure,
|
||||
HttpOnly: true,
|
||||
Path: "/",
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func SomeUsersVerified() bool {
|
||||
users := api.Container.ListUsersUseCase.ListUsers()
|
||||
for _, localUser := range users {
|
||||
if localUser.IsVerified {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func IsUserTableEmpty() bool {
|
||||
users := api.Container.ListUsersUseCase.ListUsers()
|
||||
return len(users) == 0
|
||||
}
|
||||
|
||||
func IsLoggedIn(r *http.Request) bool {
|
||||
token, err := jwtauth.VerifyRequest(
|
||||
Token,
|
||||
r,
|
||||
jwtauth.TokenFromCookie,
|
||||
jwtauth.TokenFromHeader,
|
||||
jwtauth.TokenFromQuery)
|
||||
return token != nil && err == nil
|
||||
}
|
||||
|
||||
func IsVerified(r *http.Request) bool {
|
||||
token, err := jwtauth.VerifyRequest(
|
||||
Token,
|
||||
r,
|
||||
jwtauth.TokenFromCookie,
|
||||
jwtauth.TokenFromHeader,
|
||||
jwtauth.TokenFromQuery)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
userId := token.PrivateClaims()["user_id"].(float64)
|
||||
currentUser, _ := api.Container.GetUserUseCase.GetUser(uint32(userId))
|
||||
return currentUser.IsVerified
|
||||
}
|
||||
|
||||
func GetUserFromCredentials(credentials LoginCredentials) (user.User, error) {
|
||||
dbUser, err := api.Container.GetUserUseCase.GetUserByUsername(credentials.Username)
|
||||
if err != nil {
|
||||
return user.User{}, err
|
||||
}
|
||||
|
||||
err = bcrypt.CompareHashAndPassword([]byte(dbUser.Password), []byte(credentials.Password))
|
||||
if err != nil {
|
||||
return user.User{}, err
|
||||
}
|
||||
|
||||
return dbUser, nil
|
||||
}
|
||||
|
||||
func login(w http.ResponseWriter, r *http.Request) {
|
||||
var credentials LoginCredentials
|
||||
|
||||
err := json.NewDecoder(r.Body).Decode(&credentials)
|
||||
if err != nil {
|
||||
http.Error(w, BodyErrorMessage, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
err = api.Validate.Struct(credentials)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
dbUser, err := GetUserFromCredentials(credentials)
|
||||
if err != nil {
|
||||
http.Error(w, LogsErrorMessage, http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
_ = SetJwtCookie(&w, dbUser.ID)
|
||||
|
||||
message, _ := json.Marshal(map[string]any{"message": "User logged in! HTTPonly jwt cookie created"})
|
||||
_, _ = w.Write(message)
|
||||
}
|
||||
|
||||
func register(w http.ResponseWriter, r *http.Request) {
|
||||
if !IsLoggedIn(r) && !SomeUsersVerified() {
|
||||
http.Error(w, "You are not allowed to create a user. Log in or reset database.", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
var credentials RegisterCredentials
|
||||
err := json.NewDecoder(r.Body).Decode(&credentials)
|
||||
if err != nil {
|
||||
http.Error(w, BodyErrorMessage, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
err = api.Validate.Struct(credentials)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
createdUser, err := api.Container.CreateUserUseCase.CreateUser(useCases.CreateUserCommand{
|
||||
Username: credentials.Username,
|
||||
Password: credentials.Password,
|
||||
Email: credentials.Email,
|
||||
})
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
_ = SetJwtCookie(&w, createdUser.ID)
|
||||
|
||||
message, _ := json.Marshal(map[string]any{"message": "User registered! HTTPonly jwt cookie created"})
|
||||
_, _ = w.Write(message)
|
||||
}
|
||||
|
||||
func RemoveJwtCookie(w http.ResponseWriter) {
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: "jwt",
|
||||
Value: "",
|
||||
Expires: time.Now(),
|
||||
MaxAge: -1,
|
||||
Secure: shouldCookieBeSecure,
|
||||
HttpOnly: true,
|
||||
Path: "/",
|
||||
})
|
||||
}
|
||||
|
||||
func logout(w http.ResponseWriter, _ *http.Request) {
|
||||
RemoveJwtCookie(w)
|
||||
message, _ := json.Marshal(map[string]any{"message": "User logged out! HTTPonly jwt cookie deleted"})
|
||||
_, _ = w.Write(message)
|
||||
}
|
||||
|
||||
func NewAuthRouter() http.Handler {
|
||||
r := chi.NewRouter()
|
||||
r.Post("/register", register)
|
||||
r.Post("/login", login)
|
||||
r.Post("/logout", logout)
|
||||
return r
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package image
|
||||
|
||||
import (
|
||||
"RenewCMS/api"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
func PostImage(w http.ResponseWriter, r *http.Request) {
|
||||
_ = r.ParseForm()
|
||||
|
||||
id := chi.URLParam(r, "id")
|
||||
idInt, _ := strconv.Atoi(id)
|
||||
|
||||
file, fileHeader, err := r.FormFile("file")
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
newImage, err := api.Container.CreateImageUseCase.CreateImage(file, *fileHeader)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
err = api.Container.UpdateArticleUseCase.AddImage(uint32(idInt), newImage.ID)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
newJson := map[string]any{"location": newImage.Path}
|
||||
newJsonBytes, _ := json.Marshal(newJson)
|
||||
|
||||
_, _ = w.Write(newJsonBytes)
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package pages
|
||||
|
||||
import (
|
||||
"RenewCMS/api"
|
||||
"RenewCMS/useCases"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type ArticleCreatePageError struct {
|
||||
IsError bool
|
||||
Message string
|
||||
}
|
||||
|
||||
func GetArticleCreatePageTemplate(postName string, errorMessage string) ([]byte, error) {
|
||||
navbarTmpl, _ := api.Container.GetPageUseCase.GetPage("componentNavbar", nil)
|
||||
return api.Container.GetPageUseCase.GetPage("articleCreate", map[string]any{
|
||||
"Navbar": template.HTML(navbarTmpl),
|
||||
"Head": headTmpl,
|
||||
"PageError": ArticleCreatePageError{
|
||||
IsError: errorMessage != "",
|
||||
Message: errorMessage,
|
||||
},
|
||||
"Name": postName,
|
||||
})
|
||||
}
|
||||
|
||||
func PostArticleCreatePage(w http.ResponseWriter, r *http.Request) {
|
||||
_ = r.ParseForm()
|
||||
|
||||
postName := r.FormValue("name")
|
||||
pattern := regexp.MustCompile("^[a-zA-Z0-9À-ÖØ-öø-ÿĀ-ſḀ-ỿ ]{3,50}$")
|
||||
|
||||
if !pattern.MatchString(postName) {
|
||||
postsTmpl, _ := GetArticleCreatePageTemplate(postName, "Name should be alphanumeric, and between 3 and 50 characters.")
|
||||
_, _ = w.Write(postsTmpl)
|
||||
return
|
||||
}
|
||||
|
||||
post, err := api.Container.CreateArticleUseCase.CreateArticle(useCases.CreateArticleCommand{
|
||||
Title: postName,
|
||||
Body: "",
|
||||
})
|
||||
if err != nil {
|
||||
postsTmpl, _ := GetArticleCreatePageTemplate(postName, "Something went wrong when creating the article, please contact admin.")
|
||||
_, _ = w.Write(postsTmpl)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/article/"+strconv.Itoa(int(post.ID))+"/edit", http.StatusSeeOther)
|
||||
}
|
||||
|
||||
func GetArticleCreatePage(w http.ResponseWriter, _ *http.Request) {
|
||||
postsTmpl, _ := GetArticleCreatePageTemplate("", "")
|
||||
_, _ = w.Write(postsTmpl)
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package pages
|
||||
|
||||
import (
|
||||
"RenewCMS/api"
|
||||
"RenewCMS/api/controllers/article"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
func GetArticleDeletePage(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := strconv.ParseUint(chi.URLParam(r, "id"), 10, 32)
|
||||
if err != nil {
|
||||
http.Error(w, article.IdUint32ErrorMessage, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
localArticle, err := api.Container.GetArticleUseCase.GetArticle(uint32(id))
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(localArticle.Images)
|
||||
for _, image := range localArticle.Images {
|
||||
err = api.Container.DeleteImageUseCase.DeleteImage(image.ID)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
err = api.Container.DeleteArticleUseCase.DeleteArticle(uint32(id))
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(400), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/article", http.StatusSeeOther)
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package pages
|
||||
|
||||
import (
|
||||
"RenewCMS/api"
|
||||
"RenewCMS/domain/article"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
type ArticleEditPageAlert struct {
|
||||
IsError bool
|
||||
Message string
|
||||
}
|
||||
|
||||
func getArticleEditPageTemplate(post article.Article, alert ArticleEditPageAlert) []byte {
|
||||
navbarTmpl, _ := api.Container.GetPageUseCase.GetPage("componentNavbar", nil)
|
||||
articleTmpl, _ := api.Container.GetPageUseCase.GetPage("articleEdit", map[string]any{
|
||||
"Navbar": template.HTML(navbarTmpl),
|
||||
"Head": headTmpl,
|
||||
"Article": post,
|
||||
"Alert": alert,
|
||||
"Secured": os.Getenv("ENVIRONMENT") == "production",
|
||||
})
|
||||
return articleTmpl
|
||||
}
|
||||
|
||||
func PostArticleEditPage(w http.ResponseWriter, r *http.Request) {
|
||||
articleID := chi.URLParam(r, "id")
|
||||
articleIDint, err := strconv.Atoi(articleID)
|
||||
if err != nil {
|
||||
_, _ = w.Write(getArticleEditPageTemplate(article.Article{}, ArticleEditPageAlert{
|
||||
IsError: true,
|
||||
Message: "Could not find the requested article.",
|
||||
}))
|
||||
return
|
||||
}
|
||||
|
||||
_ = r.ParseForm()
|
||||
articleBody := r.FormValue("articleBody")
|
||||
|
||||
getArticle, _ := api.Container.GetArticleUseCase.GetArticle(uint32(articleIDint))
|
||||
updatedArticle, err := api.Container.UpdateArticleUseCase.UpdateBody(getArticle.ID, articleBody)
|
||||
if err != nil {
|
||||
_, _ = w.Write(getArticleEditPageTemplate(article.Article{Body: articleBody}, ArticleEditPageAlert{
|
||||
IsError: true,
|
||||
Message: "Could not save the article: " + err.Error(),
|
||||
}))
|
||||
return
|
||||
}
|
||||
|
||||
_, _ = w.Write(getArticleEditPageTemplate(updatedArticle, ArticleEditPageAlert{
|
||||
IsError: false,
|
||||
Message: "Article successfully edited!",
|
||||
}))
|
||||
}
|
||||
|
||||
func GetArticleEditPage(w http.ResponseWriter, r *http.Request) {
|
||||
articleID := chi.URLParam(r, "id")
|
||||
articleIDint, err := strconv.Atoi(articleID)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
getArticle, _ := api.Container.GetArticleUseCase.GetArticle(uint32(articleIDint))
|
||||
|
||||
_, _ = w.Write(getArticleEditPageTemplate(getArticle, ArticleEditPageAlert{
|
||||
IsError: false,
|
||||
Message: "",
|
||||
}))
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package pages
|
||||
|
||||
import (
|
||||
"RenewCMS/api"
|
||||
"errors"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
func updateIsOnline(articleId string, isOnline bool) (error, int) {
|
||||
articleIdInt, err := strconv.Atoi(articleId)
|
||||
if err != nil {
|
||||
return errors.New("the server expects the ID to be in the format of an unsigned 32-bit integer (uint32)"), http.StatusBadRequest
|
||||
}
|
||||
|
||||
_, err = api.Container.UpdateArticleUseCase.UpdateIsOnline(uint32(articleIdInt), isOnline)
|
||||
if err != nil {
|
||||
return errors.New("the requested resource, identified by its unique ID, could not be found on the server"), http.StatusNotFound
|
||||
}
|
||||
|
||||
return nil, http.StatusOK
|
||||
}
|
||||
|
||||
func GetArticleUnpublishPage(w http.ResponseWriter, r *http.Request) {
|
||||
articleId := chi.URLParam(r, "id")
|
||||
err, statusCode := updateIsOnline(articleId, false)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), statusCode)
|
||||
return
|
||||
}
|
||||
http.Redirect(w, r, "/article", http.StatusSeeOther)
|
||||
}
|
||||
|
||||
func GetArticlePublishPage(w http.ResponseWriter, r *http.Request) {
|
||||
articleId := chi.URLParam(r, "id")
|
||||
err, statusCode := updateIsOnline(articleId, true)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), statusCode)
|
||||
return
|
||||
}
|
||||
http.Redirect(w, r, "/article", http.StatusSeeOther)
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package pages
|
||||
|
||||
import (
|
||||
"RenewCMS/api"
|
||||
"html/template"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func GetArticlesPage(w http.ResponseWriter, _ *http.Request) {
|
||||
articles := api.Container.ListArticlesUseCase.ListArticles()
|
||||
|
||||
var formattedArticles []map[string]any
|
||||
for _, article := range articles {
|
||||
formattedArticles = append(formattedArticles, map[string]any{
|
||||
"ID": article.ID,
|
||||
"Title": article.Title,
|
||||
"IsOnline": article.IsOnline,
|
||||
"CreatedAt": article.CreatedAt.Format("2006-01-02 15:04:05"),
|
||||
"UpdatedAt": article.UpdatedAt.Format("2006-01-02 15:04:05"),
|
||||
})
|
||||
}
|
||||
|
||||
navbarTmpl, _ := api.Container.GetPageUseCase.GetPage("componentNavbar", nil)
|
||||
articlesTmpl, _ := api.Container.GetPageUseCase.GetPage("articles", map[string]any{
|
||||
"Navbar": template.HTML(navbarTmpl),
|
||||
"Head": headTmpl,
|
||||
"Articles": formattedArticles,
|
||||
})
|
||||
|
||||
_, _ = w.Write(articlesTmpl)
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package pages
|
||||
|
||||
import (
|
||||
"RenewCMS/api"
|
||||
"html/template"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func GetHomePage(w http.ResponseWriter, _ *http.Request) {
|
||||
navbarTmpl, _ := api.Container.GetPageUseCase.GetPage("componentNavbar", nil)
|
||||
homeTmpl, _ := api.Container.GetPageUseCase.GetPage("home", map[string]any{
|
||||
"Navbar": template.HTML(navbarTmpl),
|
||||
"Head": headTmpl,
|
||||
})
|
||||
_, _ = w.Write(homeTmpl)
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package pages
|
||||
|
||||
import (
|
||||
"RenewCMS/api"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
|
||||
func GetPageIntegration(w http.ResponseWriter, _ *http.Request) {
|
||||
navbarTmpl, _ := api.Container.GetPageUseCase.GetPage("componentNavbar", nil)
|
||||
templ, _ := api.Container.GetPageUseCase.GetPage("integration", map[string]any{
|
||||
"Navbar": template.HTML(navbarTmpl),
|
||||
"Head": headTmpl,
|
||||
"Host": os.Getenv("HOST"),
|
||||
})
|
||||
_, _ = w.Write(templ)
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package pages
|
||||
|
||||
import (
|
||||
"RenewCMS/api"
|
||||
"RenewCMS/api/controllers/auth"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
const LoginRoute = "/login"
|
||||
|
||||
type LoginPage struct {
|
||||
PageError *PageError `json:"error"`
|
||||
Username string `json:"username"`
|
||||
}
|
||||
|
||||
var EmptyLoginPage = &LoginPage{
|
||||
PageError: NewPageError(""),
|
||||
Username: "",
|
||||
}
|
||||
|
||||
func GetLoginPageHandler(loginPage *LoginPage) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if auth.IsLoggedIn(r) {
|
||||
http.Redirect(w, r, "/home", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
if r.Method == http.MethodPost {
|
||||
PostLoginPage(w, r)
|
||||
return
|
||||
}
|
||||
if auth.IsUserTableEmpty() {
|
||||
http.Redirect(w, r, "/register", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
success, _ := url.QueryUnescape(r.URL.Query().Get("success"))
|
||||
failure, _ := url.QueryUnescape(r.URL.Query().Get("failure"))
|
||||
bs, _ := api.Container.GetPageUseCase.GetPage("login", map[string]any{
|
||||
"PageError": loginPage.PageError,
|
||||
"Username": loginPage.Username,
|
||||
"Head": headTmpl,
|
||||
"Success": success,
|
||||
"Failure": failure,
|
||||
})
|
||||
_, _ = w.Write(bs)
|
||||
}
|
||||
}
|
||||
|
||||
func PostLoginPage(w http.ResponseWriter, r *http.Request) {
|
||||
_ = r.ParseForm()
|
||||
|
||||
credentials := auth.LoginCredentials{
|
||||
Username: r.FormValue("username"),
|
||||
Password: r.FormValue("password"),
|
||||
}
|
||||
err := api.Validate.Struct(credentials)
|
||||
if err != nil {
|
||||
r.Method = http.MethodGet
|
||||
GetLoginPageHandler(&LoginPage{
|
||||
PageError: NewPageError("Invalid username or password format."),
|
||||
Username: r.FormValue("username"),
|
||||
})(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
dbUser, err := auth.GetUserFromCredentials(credentials)
|
||||
if err != nil {
|
||||
r.Method = http.MethodGet
|
||||
GetLoginPageHandler(&LoginPage{
|
||||
PageError: NewPageError("Invalid username or password combination."),
|
||||
Username: r.FormValue("username"),
|
||||
})(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
_ = auth.SetJwtCookie(&w, dbUser.ID)
|
||||
|
||||
http.Redirect(w, r, "/home", http.StatusSeeOther)
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
package pages
|
||||
|
||||
import (
|
||||
"RenewCMS/api"
|
||||
"RenewCMS/api/controllers/auth"
|
||||
"RenewCMS/api/controllers/image"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
var headTmpl template.HTML
|
||||
|
||||
var contentTypes = map[string]string{
|
||||
".css": "text/css",
|
||||
".js": "application/javascript",
|
||||
".png": "image/png",
|
||||
".jpg": "image/jpeg",
|
||||
".webp": "image/webp",
|
||||
".svg": "image/svg+xml",
|
||||
".ico": "image/x-icon",
|
||||
}
|
||||
|
||||
type PageError struct {
|
||||
Message string `json:"message"`
|
||||
IsError bool `json:"isError"`
|
||||
}
|
||||
|
||||
func NewPageError(message string) *PageError {
|
||||
return &PageError{
|
||||
Message: message,
|
||||
IsError: strings.Compare(message, "") != 0,
|
||||
}
|
||||
}
|
||||
|
||||
func IsLoggedInMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if !auth.IsLoggedIn(r) {
|
||||
http.Redirect(w, r, LoginRoute, http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func IsVerifiedMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if !auth.IsVerified(r) {
|
||||
http.Redirect(w, r, "/register/pending", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func IsNotVerifiedMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if auth.IsVerified(r) {
|
||||
http.Redirect(w, r, "/register/pending", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func GetLogin(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, LoginRoute, http.StatusPermanentRedirect)
|
||||
}
|
||||
|
||||
func GetLogout(w http.ResponseWriter, r *http.Request) {
|
||||
auth.RemoveJwtCookie(w)
|
||||
http.Redirect(w, r, LoginRoute, http.StatusSeeOther)
|
||||
}
|
||||
|
||||
func StaticFileServerWithContentType(fsys http.FileSystem) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
path := r.URL.Path
|
||||
filePath := filepath.Join("api/static", r.URL.Path)
|
||||
fileInfo, err := os.Stat(filePath)
|
||||
if err == nil && fileInfo.IsDir() {
|
||||
http.Error(w, "Folder direct access is disabled.", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
if ext := filepath.Ext(path); ext != "" {
|
||||
if ct, ok := contentTypes[ext]; ok {
|
||||
w.Header().Set("Cache-Control", "public, max-age=31536000")
|
||||
w.Header().Set("Content-Type", ct)
|
||||
}
|
||||
}
|
||||
http.FileServer(fsys).ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func InitHeadTmpl() {
|
||||
headTmplHtml, _ := api.Container.GetPageUseCase.GetPage("utilsHead", nil)
|
||||
headTmpl = template.HTML(headTmplHtml)
|
||||
}
|
||||
|
||||
func NewPageRouter() http.Handler {
|
||||
r := chi.NewRouter()
|
||||
|
||||
InitHeadTmpl()
|
||||
|
||||
fileServer := StaticFileServerWithContentType(http.Dir("api/static"))
|
||||
r.Handle("/static/*", http.StripPrefix("/static/", fileServer))
|
||||
|
||||
r.Get("/", GetLogin)
|
||||
|
||||
r.Get(LoginRoute, GetLoginPageHandler(EmptyLoginPage))
|
||||
r.Post(LoginRoute, GetLoginPageHandler(EmptyLoginPage))
|
||||
|
||||
r.Get("/register", GetRegisterPageHandler(EmptyRegisterPage))
|
||||
r.Post("/register", GetRegisterPageHandler(EmptyRegisterPage))
|
||||
|
||||
r.Get("/logout", GetLogout)
|
||||
|
||||
r.Get("/register/reset/request", GetPasswordResetRequest)
|
||||
r.Post("/register/reset/request", PostPasswordResetRequest)
|
||||
|
||||
r.Get("/register/reset/validate", GetPasswordResetValidate)
|
||||
r.Post("/register/reset/validate", PostPasswordResetValidate)
|
||||
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(IsLoggedInMiddleware)
|
||||
r.Use(IsNotVerifiedMiddleware)
|
||||
|
||||
r.Get("/register/pending", GetRegisterPendingPage)
|
||||
r.Post("/register/pending", PostRegisterPendingPage)
|
||||
|
||||
r.Get("/register/validate", GetRegisterValidatePage)
|
||||
})
|
||||
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(IsLoggedInMiddleware)
|
||||
r.Use(IsVerifiedMiddleware)
|
||||
|
||||
r.Get("/home", GetHomePage)
|
||||
|
||||
r.Get("/article", GetArticlesPage)
|
||||
|
||||
r.Get("/article/{id}/edit", GetArticleEditPage)
|
||||
r.Post("/article/{id}/edit", PostArticleEditPage)
|
||||
|
||||
r.Get("/article/{id}/delete", GetArticleDeletePage)
|
||||
|
||||
r.Get("/article/create", GetArticleCreatePage)
|
||||
r.Post("/article/create", PostArticleCreatePage)
|
||||
|
||||
r.Post("/article/{id}/image/create", image.PostImage)
|
||||
|
||||
r.Get("/article/{id}/publish", GetArticlePublishPage)
|
||||
r.Get("/article/{id}/unpublish", GetArticleUnpublishPage)
|
||||
|
||||
r.Get("/integration", GetPageIntegration)
|
||||
})
|
||||
|
||||
return r
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package pages
|
||||
|
||||
import (
|
||||
"RenewCMS/api"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type PasswordResetRequest struct {
|
||||
Email string `json:"email"`
|
||||
}
|
||||
|
||||
var successMessage = "An email has been sent to the provided email address if it exists."
|
||||
|
||||
func GetPasswordResetRequest(w http.ResponseWriter, r *http.Request) {
|
||||
success := r.URL.Query().Get("success")
|
||||
email := r.URL.Query().Get("email")
|
||||
|
||||
bs, _ := api.Container.GetPageUseCase.GetPage("passwordResetRequest", map[string]any{
|
||||
"Head": headTmpl,
|
||||
"Email": email,
|
||||
"Success": success,
|
||||
})
|
||||
_, _ = w.Write(bs)
|
||||
}
|
||||
|
||||
func PostPasswordResetRequest(w http.ResponseWriter, r *http.Request) {
|
||||
_ = r.ParseForm()
|
||||
passResetReq := PasswordResetRequest{
|
||||
Email: r.FormValue("email"),
|
||||
}
|
||||
var getRedirectUrl = "/register/reset/request?success=" + successMessage + "&email=" + passResetReq.Email
|
||||
|
||||
err := api.Validate.Struct(passResetReq)
|
||||
if err != nil {
|
||||
http.Redirect(w, r, getRedirectUrl, http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
fetchedUser, err := api.Container.GetUserUseCase.GetUserByEmail(passResetReq.Email)
|
||||
if err != nil {
|
||||
http.Redirect(w, r, getRedirectUrl, http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
verificationCode := uuid.NewString()
|
||||
updatedUser, _ := api.Container.UpdateUserUseCase.UpdatePasswordResetCode(fetchedUser.ID, verificationCode)
|
||||
|
||||
_ = api.Container.SendMailUseCase.SendMail(updatedUser.Email, "passwordReset", map[string]string{
|
||||
"Host": os.Getenv("HOST"),
|
||||
"VerificationCode": verificationCode,
|
||||
"Email": updatedUser.Email,
|
||||
})
|
||||
|
||||
http.Redirect(w, r, getRedirectUrl, http.StatusSeeOther)
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package pages
|
||||
|
||||
import (
|
||||
"RenewCMS/api"
|
||||
"net/http"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
var PasswordLinkErrorMessage = "The reset password link you used is invalid."
|
||||
var PasswordLinkSuccessMessage = "Your account password was successfully updated."
|
||||
|
||||
func GetPasswordResetValidate(w http.ResponseWriter, r *http.Request) {
|
||||
failure := r.URL.Query().Get("failure")
|
||||
pageError := NewPageError("")
|
||||
if failure != "" {
|
||||
pageError = NewPageError(failure)
|
||||
}
|
||||
template, _ := api.Container.GetPageUseCase.GetPage("passwordResetValidate", map[string]any{
|
||||
"Head": headTmpl,
|
||||
"Error": pageError,
|
||||
"Email": r.URL.Query().Get("email"),
|
||||
"Code": r.URL.Query().Get("c"),
|
||||
})
|
||||
_, _ = w.Write(template)
|
||||
}
|
||||
|
||||
func PostPasswordResetValidate(w http.ResponseWriter, r *http.Request) {
|
||||
_ = r.ParseForm()
|
||||
email := r.FormValue("email")
|
||||
password := r.FormValue("password")
|
||||
resetCode := r.FormValue("code")
|
||||
|
||||
var redirectionErrorLink = "/register/reset/validate?email=" + email + "&c=" + resetCode
|
||||
|
||||
if len(password) < 8 {
|
||||
http.Redirect(w, r, redirectionErrorLink+"&failure=Password length should be at least 8 characters.", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
fetchedUser, err := api.Container.GetUserUseCase.GetUserByEmail(email)
|
||||
if err != nil {
|
||||
http.Redirect(w, r, redirectionErrorLink+"&failure="+PasswordLinkErrorMessage, http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
err = bcrypt.CompareHashAndPassword([]byte(fetchedUser.PasswordResetCode), []byte(resetCode))
|
||||
if err != nil {
|
||||
http.Redirect(w, r, redirectionErrorLink+"&failure="+PasswordLinkErrorMessage, http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
_, _ = api.Container.UpdateUserUseCase.UpdatePassword(fetchedUser.ID, password)
|
||||
_, _ = api.Container.UpdateUserUseCase.UpdatePasswordResetCode(fetchedUser.ID, "")
|
||||
|
||||
http.Redirect(w, r, "/login?success="+PasswordLinkSuccessMessage, http.StatusSeeOther)
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package pages
|
||||
|
||||
import (
|
||||
"RenewCMS/api"
|
||||
"RenewCMS/api/controllers/auth"
|
||||
"RenewCMS/useCases"
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
|
||||
type RegisterPageError struct {
|
||||
Email bool `json:"email"`
|
||||
Password bool `json:"password"`
|
||||
Username bool `json:"username"`
|
||||
}
|
||||
|
||||
type RegisterPage struct {
|
||||
PageError *RegisterPageError `json:"error"`
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
}
|
||||
|
||||
var EmptyRegisterPage = &RegisterPage{
|
||||
PageError: &RegisterPageError{
|
||||
Email: false,
|
||||
Password: false,
|
||||
Username: false,
|
||||
},
|
||||
Username: "",
|
||||
Email: "",
|
||||
}
|
||||
|
||||
func GetRegisterPageHandler(registerPage *RegisterPage) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if !auth.IsLoggedIn(r) && auth.SomeUsersVerified() {
|
||||
http.Redirect(w, r, "/login?failure=There is already a verified account, please login.", http.StatusSeeOther)
|
||||
}
|
||||
if (auth.IsLoggedIn(r) && auth.IsVerified(r)) || auth.SomeUsersVerified() {
|
||||
http.Redirect(w, r, "/home", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
if r.Method == http.MethodPost {
|
||||
PostRegisterPage(w, r)
|
||||
return
|
||||
}
|
||||
bs, err := api.Container.GetPageUseCase.GetPage("register", map[string]any{
|
||||
"PageError": registerPage.PageError,
|
||||
"Username": registerPage.Username,
|
||||
"Email": registerPage.Email,
|
||||
"Head": headTmpl,
|
||||
})
|
||||
if err != nil {
|
||||
_, _ = w.Write([]byte(err.Error()))
|
||||
}
|
||||
_, _ = w.Write(bs)
|
||||
}
|
||||
}
|
||||
|
||||
func PostRegisterPage(w http.ResponseWriter, r *http.Request) {
|
||||
_ = r.ParseForm()
|
||||
|
||||
credentials := auth.RegisterCredentials{
|
||||
Username: r.FormValue("username"),
|
||||
Password: r.FormValue("password"),
|
||||
Email: r.FormValue("email"),
|
||||
}
|
||||
err := api.Validate.Struct(credentials)
|
||||
if err != nil {
|
||||
r.Method = http.MethodGet
|
||||
GetRegisterPageHandler(&RegisterPage{
|
||||
PageError: &RegisterPageError{
|
||||
Email: true,
|
||||
Password: true,
|
||||
Username: true,
|
||||
},
|
||||
Username: r.FormValue("username"),
|
||||
Email: r.FormValue("email"),
|
||||
})(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
createdUser, err := api.Container.CreateUserUseCase.CreateUser(useCases.CreateUserCommand{
|
||||
Username: credentials.Username,
|
||||
Password: credentials.Password,
|
||||
Email: credentials.Email,
|
||||
})
|
||||
if err != nil {
|
||||
r.Method = http.MethodGet
|
||||
GetRegisterPageHandler(&RegisterPage{
|
||||
PageError: &RegisterPageError{
|
||||
Email: true,
|
||||
Password: false,
|
||||
Username: false,
|
||||
},
|
||||
Username: r.FormValue("username"),
|
||||
Email: r.FormValue("email"),
|
||||
})(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
_ = api.Container.SendMailUseCase.SendMail(createdUser.Email, "mailValidation", map[string]string{
|
||||
"Host": os.Getenv("HOST"),
|
||||
"VerificationCode": createdUser.VerificationCode,
|
||||
})
|
||||
|
||||
_ = auth.SetJwtCookie(&w, createdUser.ID)
|
||||
|
||||
http.Redirect(w, r, "/register/pending", http.StatusSeeOther)
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package pages
|
||||
|
||||
import (
|
||||
"RenewCMS/api"
|
||||
"RenewCMS/api/controllers/auth"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/jwtauth/v5"
|
||||
)
|
||||
|
||||
func PostRegisterPendingPage(w http.ResponseWriter, r *http.Request) {
|
||||
token, _ := jwtauth.VerifyRequest(
|
||||
auth.Token, r,
|
||||
jwtauth.TokenFromCookie,
|
||||
jwtauth.TokenFromHeader,
|
||||
jwtauth.TokenFromQuery)
|
||||
userId := token.PrivateClaims()["user_id"].(float64)
|
||||
err := api.Container.DeleteUserUseCase.DeleteUser(uint32(userId))
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
r.Method = http.MethodGet
|
||||
http.Redirect(w, r, "/register/pending", http.StatusSeeOther)
|
||||
}
|
||||
|
||||
auth.RemoveJwtCookie(w)
|
||||
|
||||
http.Redirect(w, r, "/register", http.StatusSeeOther)
|
||||
}
|
||||
|
||||
func GetRegisterPendingPage(w http.ResponseWriter, _ *http.Request) {
|
||||
registerPendingTmpl, _ := api.Container.GetPageUseCase.GetPage("registerPending", map[string]any{
|
||||
"Head": headTmpl,
|
||||
})
|
||||
_, _ = w.Write(registerPendingTmpl)
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package pages
|
||||
|
||||
import (
|
||||
"RenewCMS/api"
|
||||
"RenewCMS/api/controllers/auth"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/jwtauth/v5"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
func GetRegisterValidatePage(w http.ResponseWriter, r *http.Request) {
|
||||
queryVerificationCode := r.URL.Query().Get("c")
|
||||
if queryVerificationCode == "" {
|
||||
http.Redirect(w, r, "/register/pending", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
token, _ := jwtauth.VerifyRequest(
|
||||
auth.Token,
|
||||
r,
|
||||
jwtauth.TokenFromCookie,
|
||||
jwtauth.TokenFromHeader,
|
||||
jwtauth.TokenFromQuery)
|
||||
|
||||
userId := token.PrivateClaims()["user_id"].(float64)
|
||||
user, _ := api.Container.GetUserUseCase.GetUser(uint32(userId))
|
||||
errorMessage := ""
|
||||
|
||||
err := bcrypt.CompareHashAndPassword([]byte(user.VerificationCode), []byte(queryVerificationCode))
|
||||
if err != nil || user.VerificationExpiration.Before(time.Now()) {
|
||||
errorMessage = "Verification link is incorrect or has expired."
|
||||
} else {
|
||||
_, err := api.Container.UpdateUserUseCase.UpdateVerificationStatus(user.ID, true)
|
||||
if err != nil {
|
||||
errorMessage = "Something went wrong server-side. User account may not exist."
|
||||
}
|
||||
}
|
||||
|
||||
registerValidateTmpl, _ := api.Container.GetPageUseCase.GetPage("registerValidate", map[string]any{
|
||||
"Head": headTmpl,
|
||||
"PageError": NewPageError(errorMessage),
|
||||
})
|
||||
_, _ = w.Write(registerValidateTmpl)
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"RenewCMS/adapters/secondary/gateways"
|
||||
"RenewCMS/adapters/secondary/gateways/models"
|
||||
domainGateways "RenewCMS/domain/gateways"
|
||||
"RenewCMS/useCases"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/glebarez/sqlite"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type UseCases struct {
|
||||
CreateArticleUseCase *useCases.CreateArticleUseCase
|
||||
GetArticleUseCase *useCases.GetArticleUseCase
|
||||
ListArticlesUseCase *useCases.ListArticlesUseCase
|
||||
UpdateArticleUseCase *useCases.UpdateArticleUseCase
|
||||
DeleteArticleUseCase *useCases.DeleteArticleUseCase
|
||||
GetUserUseCase *useCases.GetUserUseCase
|
||||
CreateUserUseCase *useCases.CreateUserUseCase
|
||||
UpdateUserUseCase *useCases.UpdateUserUseCase
|
||||
DeleteUserUseCase *useCases.DeleteUserUseCase
|
||||
ListUsersUseCase *useCases.ListUsersUseCase
|
||||
GetPageUseCase *useCases.GetPageUseCase
|
||||
SendMailUseCase *useCases.SendMailUseCase
|
||||
CreateImageUseCase *useCases.CreateImageUseCase
|
||||
DeleteImageUseCase *useCases.DeleteImageUseCase
|
||||
}
|
||||
|
||||
type Repositories struct {
|
||||
ArticleRepo domainGateways.IArticleRepository
|
||||
UserRepo domainGateways.IUserRepository
|
||||
ImageRepo domainGateways.IImageRepository
|
||||
MailRepo domainGateways.IMailRepository
|
||||
PageRepo domainGateways.IPageRepository
|
||||
}
|
||||
|
||||
var Container *UseCases
|
||||
|
||||
func getDb() *gorm.DB {
|
||||
dbName := os.Getenv("DB_FILE")
|
||||
if err := os.MkdirAll(filepath.Dir(dbName), os.ModePerm); err != nil {
|
||||
panic("Unable to create necessary subdirectories: " + err.Error())
|
||||
}
|
||||
db, err := gorm.Open(sqlite.Open(dbName), &gorm.Config{})
|
||||
if err != nil {
|
||||
panic("Unable to open the database: " + err.Error())
|
||||
}
|
||||
|
||||
if err := db.AutoMigrate(&models.Article{}, &models.User{}); err != nil {
|
||||
panic("Failed to migrate database: " + err.Error())
|
||||
}
|
||||
|
||||
return db
|
||||
}
|
||||
|
||||
func getRepositories(db *gorm.DB) *Repositories {
|
||||
return &Repositories{
|
||||
ArticleRepo: gateways.NewArticleRepository(db),
|
||||
UserRepo: gateways.NewUserRepository(db),
|
||||
ImageRepo: gateways.NewImageRepository(db),
|
||||
MailRepo: gateways.NewMailRepository(),
|
||||
PageRepo: gateways.NewPageRepository(),
|
||||
}
|
||||
}
|
||||
|
||||
func getUseCases(repos *Repositories) *UseCases {
|
||||
return &UseCases{
|
||||
CreateArticleUseCase: useCases.NewCreateArticleUseCase(repos.ArticleRepo),
|
||||
GetArticleUseCase: useCases.NewGetArticleUseCase(repos.ArticleRepo),
|
||||
ListArticlesUseCase: useCases.NewListArticlesUseCase(repos.ArticleRepo),
|
||||
UpdateArticleUseCase: useCases.NewUpdateArticleUseCase(repos.ArticleRepo),
|
||||
DeleteArticleUseCase: useCases.NewDeleteArticleUseCase(repos.ArticleRepo),
|
||||
GetUserUseCase: useCases.NewGetUserUseCase(repos.UserRepo),
|
||||
CreateUserUseCase: useCases.NewCreateUserUseCase(repos.UserRepo),
|
||||
UpdateUserUseCase: useCases.NewUpdateUserUseCase(repos.UserRepo),
|
||||
DeleteUserUseCase: useCases.NewDeleteUserUseCase(repos.UserRepo),
|
||||
ListUsersUseCase: useCases.NewListUsersUseCase(repos.UserRepo),
|
||||
GetPageUseCase: useCases.NewGetPageUseCase(repos.PageRepo),
|
||||
SendMailUseCase: useCases.NewSendMailUseCase(repos.MailRepo),
|
||||
CreateImageUseCase: useCases.NewCreateImageUseCase(repos.ImageRepo),
|
||||
DeleteImageUseCase: useCases.NewDeleteImageUseCase(repos.ImageRepo),
|
||||
}
|
||||
}
|
||||
|
||||
func InitContainer() {
|
||||
db := getDb()
|
||||
repos := getRepositories(db)
|
||||
Container = getUseCases(repos)
|
||||
}
|
||||
Vendored
+1
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 1.0 MiB |
Vendored
+6
File diff suppressed because one or more lines are too long
Vendored
+1
File diff suppressed because one or more lines are too long
Vendored
+7
File diff suppressed because one or more lines are too long
Vendored
+1
File diff suppressed because one or more lines are too long
Vendored
BIN
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
Vendored
BIN
Binary file not shown.
|
After Width: | Height: | Size: 472 B |
Vendored
+3561
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1,3 @@
|
||||
This is where language files should be placed.
|
||||
|
||||
Please DO NOT translate these directly, use this service instead: https://crowdin.com/project/tinymce
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
# Software License Agreement
|
||||
|
||||
**TinyMCE** – [<https://github.com/tinymce/tinymce>](https://github.com/tinymce/tinymce)
|
||||
Copyright (c) 2024, Ephox Corporation DBA Tiny Technologies, Inc.
|
||||
|
||||
Licensed under the terms of [GNU General Public License Version 2 or later](http://www.gnu.org/licenses/gpl.html).
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* TinyMCE version 7.1.1 (2024-05-22)
|
||||
*/
|
||||
!function(){"use strict";var t=tinymce.util.Tools.resolve("tinymce.PluginManager");const e=(t,e,s)=>{const r="UL"===e?"InsertUnorderedList":"InsertOrderedList";t.execCommand(r,!1,!1===s?null:{"list-style-type":s})},s=t=>e=>e.options.get(t),r=s("advlist_number_styles"),n=s("advlist_bullet_styles"),i=t=>null==t,l=t=>!i(t);var o=tinymce.util.Tools.resolve("tinymce.util.Tools");class a{constructor(t,e){this.tag=t,this.value=e}static some(t){return new a(!0,t)}static none(){return a.singletonNone}fold(t,e){return this.tag?e(this.value):t()}isSome(){return this.tag}isNone(){return!this.tag}map(t){return this.tag?a.some(t(this.value)):a.none()}bind(t){return this.tag?t(this.value):a.none()}exists(t){return this.tag&&t(this.value)}forall(t){return!this.tag||t(this.value)}filter(t){return!this.tag||t(this.value)?this:a.none()}getOr(t){return this.tag?this.value:t}or(t){return this.tag?this:t}getOrThunk(t){return this.tag?this.value:t()}orThunk(t){return this.tag?this:t()}getOrDie(t){if(this.tag)return this.value;throw new Error(null!=t?t:"Called getOrDie on None")}static from(t){return l(t)?a.some(t):a.none()}getOrNull(){return this.tag?this.value:null}getOrUndefined(){return this.value}each(t){this.tag&&t(this.value)}toArray(){return this.tag?[this.value]:[]}toString(){return this.tag?`some(${this.value})`:"none()"}}a.singletonNone=new a(!1);const u=t=>e=>l(e)&&t.test(e.nodeName),d=u(/^(OL|UL|DL)$/),g=u(/^(TH|TD)$/),c=t=>i(t)||"default"===t?"":t,h=(t,e)=>s=>((t,e)=>{const s=t.selection.getNode();return e({parents:t.dom.getParents(s),element:s}),t.on("NodeChange",e),()=>t.off("NodeChange",e)})(t,(r=>((t,r)=>{const n=t.selection.getStart(!0);s.setActive(((t,e,s)=>((t,e,s)=>{for(let e=0,n=t.length;e<n;e++){const n=t[e];if(d(r=n)&&!/\btox\-/.test(r.className))return a.some(n);if(s(n,e))break}var r;return a.none()})(e,0,g).exists((e=>e.nodeName===s&&((t,e)=>t.dom.isChildOf(e,t.getBody()))(t,e))))(t,r,e)),s.setEnabled(!((t,e)=>{const s=t.dom.getParent(e,"ol,ul,dl");return((t,e)=>null!==e&&!t.dom.isEditable(e))(t,s)&&t.selection.isEditable()})(t,n)&&t.selection.isEditable())})(t,r.parents))),m=(t,s,r,n,i,l)=>{l.length>1?((t,s,r,n,i,l)=>{t.ui.registry.addSplitButton(s,{tooltip:r,icon:"OL"===i?"ordered-list":"unordered-list",presets:"listpreview",columns:3,fetch:t=>{t(o.map(l,(t=>{const e="OL"===i?"num":"bull",s="disc"===t||"decimal"===t?"default":t,r=c(t),n=(t=>t.replace(/\-/g," ").replace(/\b\w/g,(t=>t.toUpperCase())))(t);return{type:"choiceitem",value:r,icon:"list-"+e+"-"+s,text:n}})))},onAction:()=>t.execCommand(n),onItemAction:(s,r)=>{e(t,i,r)},select:e=>{const s=(t=>{const e=t.dom.getParent(t.selection.getNode(),"ol,ul"),s=t.dom.getStyle(e,"listStyleType");return a.from(s)})(t);return s.map((t=>e===t)).getOr(!1)},onSetup:h(t,i)})})(t,s,r,n,i,l):((t,s,r,n,i,l)=>{t.ui.registry.addToggleButton(s,{active:!1,tooltip:r,icon:"OL"===i?"ordered-list":"unordered-list",onSetup:h(t,i),onAction:()=>t.queryCommandState(n)||""===l?t.execCommand(n):e(t,i,l)})})(t,s,r,n,i,c(l[0]))};t.add("advlist",(t=>{t.hasPlugin("lists")?((t=>{const e=t.options.register;e("advlist_number_styles",{processor:"string[]",default:"default,lower-alpha,lower-greek,lower-roman,upper-alpha,upper-roman".split(",")}),e("advlist_bullet_styles",{processor:"string[]",default:"default,circle,square".split(",")})})(t),(t=>{m(t,"numlist","Numbered list","InsertOrderedList","OL",r(t)),m(t,"bullist","Bullet list","InsertUnorderedList","UL",n(t))})(t),(t=>{t.addCommand("ApplyUnorderedListStyle",((s,r)=>{e(t,"UL",r["list-style-type"])})),t.addCommand("ApplyOrderedListStyle",((s,r)=>{e(t,"OL",r["list-style-type"])}))})(t)):console.error("Please use the Lists plugin together with the List Styles plugin.")}))}();
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* TinyMCE version 7.1.1 (2024-05-22)
|
||||
*/
|
||||
!function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager"),t=tinymce.util.Tools.resolve("tinymce.dom.RangeUtils"),o=tinymce.util.Tools.resolve("tinymce.util.Tools");const n=("allow_html_in_named_anchor",e=>e.options.get("allow_html_in_named_anchor"));const a="a:not([href])",r=e=>!e,i=e=>e.getAttribute("id")||e.getAttribute("name")||"",l=e=>(e=>"a"===e.nodeName.toLowerCase())(e)&&!e.getAttribute("href")&&""!==i(e),s=e=>e.dom.getParent(e.selection.getStart(),a),d=(e,a)=>{const r=s(e);r?((e,t,o)=>{o.removeAttribute("name"),o.id=t,e.addVisual(),e.undoManager.add()})(e,a,r):((e,a)=>{e.undoManager.transact((()=>{n(e)||e.selection.collapse(!0),e.selection.isCollapsed()?e.insertContent(e.dom.createHTML("a",{id:a})):((e=>{const n=e.dom;t(n).walk(e.selection.getRng(),(e=>{o.each(e,(e=>{var t;l(t=e)&&!t.firstChild&&n.remove(e,!1)}))}))})(e),e.formatter.remove("namedAnchor",void 0,void 0,!0),e.formatter.apply("namedAnchor",{value:a}),e.addVisual())}))})(e,a),e.focus()},c=e=>(e=>r(e.attr("href"))&&!r(e.attr("id")||e.attr("name")))(e)&&!e.firstChild,m=e=>t=>{for(let o=0;o<t.length;o++){const n=t[o];c(n)&&n.attr("contenteditable",e)}},u=e=>t=>{const o=()=>{t.setEnabled(e.selection.isEditable())};return e.on("NodeChange",o),o(),()=>{e.off("NodeChange",o)}};e.add("anchor",(e=>{(e=>{(0,e.options.register)("allow_html_in_named_anchor",{processor:"boolean",default:!1})})(e),(e=>{e.on("PreInit",(()=>{e.parser.addNodeFilter("a",m("false")),e.serializer.addNodeFilter("a",m(null))}))})(e),(e=>{e.addCommand("mceAnchor",(()=>{(e=>{const t=(e=>{const t=s(e);return t?i(t):""})(e);e.windowManager.open({title:"Anchor",size:"normal",body:{type:"panel",items:[{name:"id",type:"input",label:"ID",placeholder:"example"}]},buttons:[{type:"cancel",name:"cancel",text:"Cancel"},{type:"submit",name:"save",text:"Save",primary:!0}],initialData:{id:t},onSubmit:t=>{((e,t)=>/^[A-Za-z][A-Za-z0-9\-:._]*$/.test(t)?(d(e,t),!0):(e.windowManager.alert("ID should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores."),!1))(e,t.getData().id)&&t.close()}})})(e)}))})(e),(e=>{const t=()=>e.execCommand("mceAnchor");e.ui.registry.addToggleButton("anchor",{icon:"bookmark",tooltip:"Anchor",onAction:t,onSetup:t=>{const o=e.selection.selectorChangedWithUnbind("a:not([href])",t.setActive).unbind,n=u(e)(t);return()=>{o(),n()}}}),e.ui.registry.addMenuItem("anchor",{icon:"bookmark",text:"Anchor...",onAction:t,onSetup:u(e)})})(e),e.on("PreInit",(()=>{(e=>{e.formatter.register("namedAnchor",{inline:"a",selector:a,remove:"all",split:!0,deep:!0,attributes:{id:"%value"},onmatch:(e,t,o)=>l(e)})})(e)}))}))}();
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* TinyMCE version 7.1.1 (2024-05-22)
|
||||
*/
|
||||
!function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager");const t=e=>t=>t.options.get(e),n=t("autolink_pattern"),o=t("link_default_target"),r=t("link_default_protocol"),a=t("allow_unsafe_link_target"),s=("string",e=>"string"===(e=>{const t=typeof e;return null===e?"null":"object"===t&&Array.isArray(e)?"array":"object"===t&&(n=o=e,(r=String).prototype.isPrototypeOf(n)||(null===(a=o.constructor)||void 0===a?void 0:a.name)===r.name)?"string":t;var n,o,r,a})(e));const l=(void 0,e=>undefined===e);const i=e=>!(e=>null==e)(e),c=Object.hasOwnProperty,d=e=>"\ufeff"===e;var u=tinymce.util.Tools.resolve("tinymce.dom.TextSeeker");const f=e=>/^[(\[{ \u00a0]$/.test(e),g=(e,t,n)=>{for(let o=t-1;o>=0;o--){const t=e.charAt(o);if(!d(t)&&n(t))return o}return-1},m=(e,t)=>{var o;const a=e.schema.getVoidElements(),s=n(e),{dom:i,selection:d}=e;if(null!==i.getParent(d.getNode(),"a[href]"))return null;const m=d.getRng(),k=u(i,(e=>{return i.isBlock(e)||(t=a,n=e.nodeName.toLowerCase(),c.call(t,n))||"false"===i.getContentEditable(e);var t,n})),{container:p,offset:y}=((e,t)=>{let n=e,o=t;for(;1===n.nodeType&&n.childNodes[o];)n=n.childNodes[o],o=3===n.nodeType?n.data.length:n.childNodes.length;return{container:n,offset:o}})(m.endContainer,m.endOffset),w=null!==(o=i.getParent(p,i.isBlock))&&void 0!==o?o:i.getRoot(),h=k.backwards(p,y+t,((e,t)=>{const n=e.data,o=g(n,t,(r=f,e=>!r(e)));var r,a;return-1===o||(a=n[o],/[?!,.;:]/.test(a))?o:o+1}),w);if(!h)return null;let v=h.container;const _=k.backwards(h.container,h.offset,((e,t)=>{v=e;const n=g(e.data,t,f);return-1===n?n:n+1}),w),A=i.createRng();_?A.setStart(_.container,_.offset):A.setStart(v,0),A.setEnd(h.container,h.offset);const C=A.toString().replace(/\uFEFF/g,"").match(s);if(C){let t=C[0];return $="www.",(b=t).length>=4&&b.substr(0,4)===$?t=r(e)+"://"+t:((e,t,n=0,o)=>{const r=e.indexOf(t,n);return-1!==r&&(!!l(o)||r+t.length<=o)})(t,"@")&&!(e=>/^([A-Za-z][A-Za-z\d.+-]*:\/\/)|mailto:/.test(e))(t)&&(t="mailto:"+t),{rng:A,url:t}}var b,$;return null},k=(e,t)=>{const{dom:n,selection:r}=e,{rng:l,url:i}=t,c=r.getBookmark();r.setRng(l);const d="createlink",u={command:d,ui:!1,value:i};if(!e.dispatch("BeforeExecCommand",u).isDefaultPrevented()){e.getDoc().execCommand(d,!1,i),e.dispatch("ExecCommand",u);const t=o(e);if(s(t)){const o=r.getNode();n.setAttrib(o,"target",t),"_blank"!==t||a(e)||n.setAttrib(o,"rel","noopener")}}r.moveToBookmark(c),e.nodeChanged()},p=e=>{const t=m(e,-1);i(t)&&k(e,t)},y=p;e.add("autolink",(e=>{(e=>{const t=e.options.register;t("autolink_pattern",{processor:"regexp",default:new RegExp("^"+/(?:[A-Za-z][A-Za-z\d.+-]{0,14}:\/\/(?:[-.~*+=!&;:'%@?^${}(),\w]+@)?|www\.|[-;:&=+$,.\w]+@)[A-Za-z\d-]+(?:\.[A-Za-z\d-]+)*(?::\d+)?(?:\/(?:[-.~*+=!;:'%@$(),\/\w]*[-~*+=%@$()\/\w])?)?(?:\?(?:[-.~*+=!&;:'%@?^${}(),\/\w]+))?(?:#(?:[-.~*+=!&;:'%@?^${}(),\/\w]+))?/g.source+"$","i")}),t("link_default_target",{processor:"string"}),t("link_default_protocol",{processor:"string",default:"https"})})(e),(e=>{e.on("keydown",(t=>{13!==t.keyCode||t.isDefaultPrevented()||(e=>{const t=m(e,0);i(t)&&k(e,t)})(e)})),e.on("keyup",(t=>{32===t.keyCode?p(e):(48===t.keyCode&&t.shiftKey||221===t.keyCode)&&y(e)}))})(e)}))}();
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* TinyMCE version 7.1.1 (2024-05-22)
|
||||
*/
|
||||
!function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager"),t=tinymce.util.Tools.resolve("tinymce.Env");const o=e=>t=>t.options.get(e),s=o("min_height"),i=o("max_height"),n=o("autoresize_overflow_padding"),r=o("autoresize_bottom_margin"),l=(e,t)=>{const o=e.getBody();o&&(o.style.overflowY=t?"":"hidden",t||(o.scrollTop=0))},g=(e,t,o,s)=>{var i;const n=parseInt(null!==(i=e.getStyle(t,o,s))&&void 0!==i?i:"",10);return isNaN(n)?0:n},a=(e,o,r,c)=>{var d;const f=e.dom,u=e.getDoc();if(!u)return;if((e=>e.plugins.fullscreen&&e.plugins.fullscreen.isFullscreen())(e))return void l(e,!0);const m=u.documentElement,h=c?c():n(e),p=null!==(d=s(e))&&void 0!==d?d:e.getElement().offsetHeight;let y=p;const S=g(f,m,"margin-top",!0),v=g(f,m,"margin-bottom",!0);let C=m.offsetHeight+S+v+h;C<0&&(C=0);const b=e.getContainer().offsetHeight-e.getContentAreaContainer().offsetHeight;C+b>p&&(y=C+b);const w=i(e);if(w&&y>w?(y=w,l(e,!0)):l(e,!1),y!==o.get()){const s=y-o.get();if(f.setStyle(e.getContainer(),"height",y+"px"),o.set(y),(e=>{e.dispatch("ResizeEditor")})(e),t.browser.isSafari()&&(t.os.isMacOS()||t.os.isiOS())){const t=e.getWin();t.scrollTo(t.pageXOffset,t.pageYOffset)}e.hasFocus()&&(e=>{if("setcontent"===(null==e?void 0:e.type.toLowerCase())){const t=e;return!0===t.selection||!0===t.paste}return!1})(r)&&e.selection.scrollIntoView(),(t.browser.isSafari()||t.browser.isChromium())&&s<0&&a(e,o,r,c)}};e.add("autoresize",(e=>{if((e=>{const t=e.options.register;t("autoresize_overflow_padding",{processor:"number",default:1}),t("autoresize_bottom_margin",{processor:"number",default:50})})(e),e.options.isSet("resize")||e.options.set("resize",!1),!e.inline){const o=(e=>{let t=0;return{get:()=>t,set:e=>{t=e}}})();((e,t)=>{e.addCommand("mceAutoResize",(()=>{a(e,t)}))})(e,o),((e,o)=>{let s,i,l=()=>r(e);e.on("init",(i=>{s=0;const r=n(e),g=e.dom;g.setStyles(e.getDoc().documentElement,{height:"auto"}),t.browser.isEdge()||t.browser.isIE()?g.setStyles(e.getBody(),{paddingLeft:r,paddingRight:r,"min-height":0}):g.setStyles(e.getBody(),{paddingLeft:r,paddingRight:r}),a(e,o,i,l),s+=1})),e.on("NodeChange SetContent keyup FullscreenStateChanged ResizeContent",(t=>{if(1===s)i=e.getContainer().offsetHeight,a(e,o,t,l),s+=1;else if(2===s){const t=i<e.getContainer().offsetHeight;if(t){const t=e.dom,o=e.getDoc();t.setStyles(o.documentElement,{"min-height":0}),t.setStyles(e.getBody(),{"min-height":"inherit"})}l=t?(0,()=>0):l,s+=1}else a(e,o,t,l)}))})(e,o)}}))}();
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
/**
|
||||
* TinyMCE version 6.3.1 (2022-12-06)
|
||||
* TinyMCE version 7.1.1 (2024-05-22)
|
||||
*/
|
||||
!function(){"use strict";var t=tinymce.util.Tools.resolve("tinymce.PluginManager");const e=("string",t=>"string"===(t=>{const e=typeof t;return null===t?"null":"object"===e&&Array.isArray(t)?"array":"object"===e&&(r=o=t,(a=String).prototype.isPrototypeOf(r)||(null===(s=o.constructor)||void 0===s?void 0:s.name)===a.name)?"string":e;var r,o,a,s})(t));const r=(void 0,t=>undefined===t);var o=tinymce.util.Tools.resolve("tinymce.util.Delay"),a=tinymce.util.Tools.resolve("tinymce.util.LocalStorage"),s=tinymce.util.Tools.resolve("tinymce.util.Tools");const n=t=>{const e=/^(\d+)([ms]?)$/.exec(t);return(e&&e[2]?{s:1e3,m:6e4}[e[2]]:1)*parseInt(t,10)},i=t=>e=>e.options.get(t),u=i("autosave_ask_before_unload"),l=i("autosave_restore_when_empty"),c=i("autosave_interval"),d=i("autosave_retention"),m=t=>{const e=document.location;return t.options.get("autosave_prefix").replace(/{path}/g,e.pathname).replace(/{query}/g,e.search).replace(/{hash}/g,e.hash).replace(/{id}/g,t.id)},v=(t,e)=>{if(r(e))return t.dom.isEmpty(t.getBody());{const r=s.trim(e);if(""===r)return!0;{const e=(new DOMParser).parseFromString(r,"text/html");return t.dom.isEmpty(e)}}},f=t=>{var e;const r=parseInt(null!==(e=a.getItem(m(t)+"time"))&&void 0!==e?e:"0",10)||0;return!((new Date).getTime()-r>d(t)&&(p(t,!1),1))},p=(t,e)=>{const r=m(t);a.removeItem(r+"draft"),a.removeItem(r+"time"),!1!==e&&(t=>{t.dispatch("RemoveDraft")})(t)},g=t=>{const e=m(t);!v(t)&&t.isDirty()&&(a.setItem(e+"draft",t.getContent({format:"raw",no_events:!0})),a.setItem(e+"time",(new Date).getTime().toString()),(t=>{t.dispatch("StoreDraft")})(t))},y=t=>{var e;const r=m(t);f(t)&&(t.setContent(null!==(e=a.getItem(r+"draft"))&&void 0!==e?e:"",{format:"raw"}),(t=>{t.dispatch("RestoreDraft")})(t))};var D=tinymce.util.Tools.resolve("tinymce.EditorManager");const h=t=>e=>{e.setEnabled(f(t));const r=()=>e.setEnabled(f(t));return t.on("StoreDraft RestoreDraft RemoveDraft",r),()=>t.off("StoreDraft RestoreDraft RemoveDraft",r)};t.add("autosave",(t=>((t=>{const r=t.options.register,o=t=>{const r=e(t);return r?{value:n(t),valid:r}:{valid:!1,message:"Must be a string."}};r("autosave_ask_before_unload",{processor:"boolean",default:!0}),r("autosave_prefix",{processor:"string",default:"tinymce-autosave-{path}{query}{hash}-{id}-"}),r("autosave_restore_when_empty",{processor:"boolean",default:!1}),r("autosave_interval",{processor:o,default:"30s"}),r("autosave_retention",{processor:o,default:"20m"})})(t),(t=>{t.editorManager.on("BeforeUnload",(t=>{let e;s.each(D.get(),(t=>{t.plugins.autosave&&t.plugins.autosave.storeDraft(),!e&&t.isDirty()&&u(t)&&(e=t.translate("You have unsaved changes are you sure you want to navigate away?"))})),e&&(t.preventDefault(),t.returnValue=e)}))})(t),(t=>{(t=>{const e=c(t);o.setEditorInterval(t,(()=>{g(t)}),e)})(t);const e=()=>{(t=>{t.undoManager.transact((()=>{y(t),p(t)})),t.focus()})(t)};t.ui.registry.addButton("restoredraft",{tooltip:"Restore last draft",icon:"restore-draft",onAction:e,onSetup:h(t)}),t.ui.registry.addMenuItem("restoredraft",{text:"Restore last draft",icon:"restore-draft",onAction:e,onSetup:h(t)})})(t),t.on("init",(()=>{l(t)&&t.dom.isEmpty(t.getBody())&&y(t)})),(t=>({hasDraft:()=>f(t),storeDraft:()=>g(t),restoreDraft:()=>y(t),removeDraft:e=>p(t,e),isEmpty:e=>v(t,e)}))(t))))}();
|
||||
+2
-2
File diff suppressed because one or more lines are too long
+1
-1
@@ -1,4 +1,4 @@
|
||||
/**
|
||||
* TinyMCE version 6.3.1 (2022-12-06)
|
||||
* TinyMCE version 7.1.1 (2024-05-22)
|
||||
*/
|
||||
!function(){"use strict";tinymce.util.Tools.resolve("tinymce.PluginManager").add("code",(e=>((e=>{e.addCommand("mceCodeEditor",(()=>{(e=>{const o=(e=>e.getContent({source_view:!0}))(e);e.windowManager.open({title:"Source Code",size:"large",body:{type:"panel",items:[{type:"textarea",name:"code"}]},buttons:[{type:"cancel",name:"cancel",text:"Cancel"},{type:"submit",name:"save",text:"Save",primary:!0}],initialData:{code:o},onSubmit:o=>{((e,o)=>{e.focus(),e.undoManager.transact((()=>{e.setContent(o)})),e.selection.setCursorLocation(),e.nodeChanged()})(e,o.getData().code),o.close()}})})(e)}))})(e),(e=>{const o=()=>e.execCommand("mceCodeEditor");e.ui.registry.addButton("code",{icon:"sourcecode",tooltip:"Source code",onAction:o}),e.ui.registry.addMenuItem("code",{icon:"sourcecode",text:"Source code",onAction:o})})(e),{})))}();
|
||||
File diff suppressed because one or more lines are too long
+2
-2
@@ -1,4 +1,4 @@
|
||||
/**
|
||||
* TinyMCE version 6.3.1 (2022-12-06)
|
||||
* TinyMCE version 7.1.1 (2024-05-22)
|
||||
*/
|
||||
!function(){"use strict";var t=tinymce.util.Tools.resolve("tinymce.PluginManager");const e=t=>e=>typeof e===t,o=t=>"string"===(t=>{const e=typeof t;return null===t?"null":"object"===e&&Array.isArray(t)?"array":"object"===e&&(o=r=t,(n=String).prototype.isPrototypeOf(o)||(null===(i=r.constructor)||void 0===i?void 0:i.name)===n.name)?"string":e;var o,r,n,i})(t),r=e("boolean"),n=t=>!(t=>null==t)(t),i=e("function"),s=e("number"),l=(!1,()=>false);class a{constructor(t,e){this.tag=t,this.value=e}static some(t){return new a(!0,t)}static none(){return a.singletonNone}fold(t,e){return this.tag?e(this.value):t()}isSome(){return this.tag}isNone(){return!this.tag}map(t){return this.tag?a.some(t(this.value)):a.none()}bind(t){return this.tag?t(this.value):a.none()}exists(t){return this.tag&&t(this.value)}forall(t){return!this.tag||t(this.value)}filter(t){return!this.tag||t(this.value)?this:a.none()}getOr(t){return this.tag?this.value:t}or(t){return this.tag?this:t}getOrThunk(t){return this.tag?this.value:t()}orThunk(t){return this.tag?this:t()}getOrDie(t){if(this.tag)return this.value;throw new Error(null!=t?t:"Called getOrDie on None")}static from(t){return n(t)?a.some(t):a.none()}getOrNull(){return this.tag?this.value:null}getOrUndefined(){return this.value}each(t){this.tag&&t(this.value)}toArray(){return this.tag?[this.value]:[]}toString(){return this.tag?`some(${this.value})`:"none()"}}a.singletonNone=new a(!1);const u=(t,e)=>{for(let o=0,r=t.length;o<r;o++)e(t[o],o)},c=t=>{if(null==t)throw new Error("Node cannot be null or undefined");return{dom:t}},d=c,h=(t,e)=>{const o=t.dom;if(1!==o.nodeType)return!1;{const t=o;if(void 0!==t.matches)return t.matches(e);if(void 0!==t.msMatchesSelector)return t.msMatchesSelector(e);if(void 0!==t.webkitMatchesSelector)return t.webkitMatchesSelector(e);if(void 0!==t.mozMatchesSelector)return t.mozMatchesSelector(e);throw new Error("Browser lacks native selectors")}};"undefined"!=typeof window?window:Function("return this;")();const m=t=>e=>(t=>t.dom.nodeType)(e)===t,g=m(1),f=m(3),v=m(9),p=m(11),y=(t,e)=>{t.dom.removeAttribute(e)},w=i(Element.prototype.attachShadow)&&i(Node.prototype.getRootNode)?t=>d(t.dom.getRootNode()):t=>v(t)?t:d(t.dom.ownerDocument),N=t=>d(t.dom.host),b=t=>{const e=f(t)?t.dom.parentNode:t.dom;if(null==e||null===e.ownerDocument)return!1;const o=e.ownerDocument;return(t=>{const e=w(t);return p(o=e)&&n(o.dom.host)?a.some(e):a.none();var o})(d(e)).fold((()=>o.body.contains(e)),(r=b,i=N,t=>r(i(t))));var r,i},S=t=>"rtl"===((t,e)=>{const o=t.dom,r=window.getComputedStyle(o).getPropertyValue(e);return""!==r||b(t)?r:((t,e)=>(t=>void 0!==t.style&&i(t.style.getPropertyValue))(t)?t.style.getPropertyValue(e):"")(o,e)})(t,"direction")?"rtl":"ltr",A=(t,e)=>((t,o)=>((t,e)=>{const o=[];for(let r=0,n=t.length;r<n;r++){const n=t[r];e(n,r)&&o.push(n)}return o})(((t,e)=>{const o=t.length,r=new Array(o);for(let n=0;n<o;n++){const o=t[n];r[n]=e(o,n)}return r})(t.dom.childNodes,d),(t=>h(t,e))))(t),T=("li",t=>g(t)&&"li"===t.dom.nodeName.toLowerCase());const C=(t,e)=>{const n=t.selection.getSelectedBlocks();n.length>0&&(u(n,(t=>{const n=d(t),c=T(n),m=((t,e)=>{return(e?(o=t,r="ol,ul",((t,e,o)=>{let n=t.dom;const s=i(o)?o:l;for(;n.parentNode;){n=n.parentNode;const t=d(n);if(h(t,r))return a.some(t);if(s(t))break}return a.none()})(o,0,n)):a.some(t)).getOr(t);var o,r,n})(n,c);var f;(f=m,(t=>a.from(t.dom.parentNode).map(d))(f).filter(g)).each((t=>{if(S(t)!==e?((t,e,n)=>{((t,e,n)=>{if(!(o(n)||r(n)||s(n)))throw console.error("Invalid call to Attribute.set. Key ",e,":: Value ",n,":: Element ",t),new Error("Attribute value was not simple");t.setAttribute(e,n+"")})(t.dom,e,n)})(m,"dir",e):S(m)!==e&&y(m,"dir"),c){const t=A(m,"li[dir]");u(t,(t=>y(t,"dir")))}}))})),t.nodeChanged())},D=(t,e)=>o=>{const r=t=>{const r=d(t.element);o.setActive(S(r)===e)};return t.on("NodeChange",r),()=>t.off("NodeChange",r)};t.add("directionality",(t=>{(t=>{t.addCommand("mceDirectionLTR",(()=>{C(t,"ltr")})),t.addCommand("mceDirectionRTL",(()=>{C(t,"rtl")}))})(t),(t=>{t.ui.registry.addToggleButton("ltr",{tooltip:"Left to right",icon:"ltr",onAction:()=>t.execCommand("mceDirectionLTR"),onSetup:D(t,"ltr")}),t.ui.registry.addToggleButton("rtl",{tooltip:"Right to left",icon:"rtl",onAction:()=>t.execCommand("mceDirectionRTL"),onSetup:D(t,"rtl")})})(t)}))}();
|
||||
!function(){"use strict";var t=tinymce.util.Tools.resolve("tinymce.PluginManager");const e=t=>e=>typeof e===t,o=t=>"string"===(t=>{const e=typeof t;return null===t?"null":"object"===e&&Array.isArray(t)?"array":"object"===e&&(o=r=t,(n=String).prototype.isPrototypeOf(o)||(null===(i=r.constructor)||void 0===i?void 0:i.name)===n.name)?"string":e;var o,r,n,i})(t),r=e("boolean"),n=t=>!(t=>null==t)(t),i=e("function"),s=e("number"),l=(!1,()=>false);class a{constructor(t,e){this.tag=t,this.value=e}static some(t){return new a(!0,t)}static none(){return a.singletonNone}fold(t,e){return this.tag?e(this.value):t()}isSome(){return this.tag}isNone(){return!this.tag}map(t){return this.tag?a.some(t(this.value)):a.none()}bind(t){return this.tag?t(this.value):a.none()}exists(t){return this.tag&&t(this.value)}forall(t){return!this.tag||t(this.value)}filter(t){return!this.tag||t(this.value)?this:a.none()}getOr(t){return this.tag?this.value:t}or(t){return this.tag?this:t}getOrThunk(t){return this.tag?this.value:t()}orThunk(t){return this.tag?this:t()}getOrDie(t){if(this.tag)return this.value;throw new Error(null!=t?t:"Called getOrDie on None")}static from(t){return n(t)?a.some(t):a.none()}getOrNull(){return this.tag?this.value:null}getOrUndefined(){return this.value}each(t){this.tag&&t(this.value)}toArray(){return this.tag?[this.value]:[]}toString(){return this.tag?`some(${this.value})`:"none()"}}a.singletonNone=new a(!1);const u=(t,e)=>{for(let o=0,r=t.length;o<r;o++)e(t[o],o)},c=t=>{if(null==t)throw new Error("Node cannot be null or undefined");return{dom:t}},d=c,h=(t,e)=>{const o=t.dom;if(1!==o.nodeType)return!1;{const t=o;if(void 0!==t.matches)return t.matches(e);if(void 0!==t.msMatchesSelector)return t.msMatchesSelector(e);if(void 0!==t.webkitMatchesSelector)return t.webkitMatchesSelector(e);if(void 0!==t.mozMatchesSelector)return t.mozMatchesSelector(e);throw new Error("Browser lacks native selectors")}};"undefined"!=typeof window?window:Function("return this;")();const m=t=>e=>(t=>t.dom.nodeType)(e)===t,g=m(1),f=m(3),v=m(9),y=m(11),p=(t,e)=>{t.dom.removeAttribute(e)},w=i(Element.prototype.attachShadow)&&i(Node.prototype.getRootNode)?t=>d(t.dom.getRootNode()):t=>v(t)?t:d(t.dom.ownerDocument),b=t=>d(t.dom.host),N=t=>{const e=f(t)?t.dom.parentNode:t.dom;if(null==e||null===e.ownerDocument)return!1;const o=e.ownerDocument;return(t=>{const e=w(t);return y(o=e)&&n(o.dom.host)?a.some(e):a.none();var o})(d(e)).fold((()=>o.body.contains(e)),(r=N,i=b,t=>r(i(t))));var r,i},S=t=>"rtl"===((t,e)=>{const o=t.dom,r=window.getComputedStyle(o).getPropertyValue(e);return""!==r||N(t)?r:((t,e)=>(t=>void 0!==t.style&&i(t.style.getPropertyValue))(t)?t.style.getPropertyValue(e):"")(o,e)})(t,"direction")?"rtl":"ltr",A=(t,e)=>((t,o)=>((t,e)=>{const o=[];for(let r=0,n=t.length;r<n;r++){const n=t[r];e(n,r)&&o.push(n)}return o})(((t,e)=>{const o=t.length,r=new Array(o);for(let n=0;n<o;n++){const o=t[n];r[n]=e(o,n)}return r})(t.dom.childNodes,d),(t=>h(t,e))))(t),E=("li",t=>g(t)&&"li"===t.dom.nodeName.toLowerCase());const T=(t,e,n)=>{u(e,(e=>{const c=d(e),m=E(c),f=((t,e)=>{return(e?(o=t,r="ol,ul",((t,e,o)=>{let n=t.dom;const s=i(o)?o:l;for(;n.parentNode;){n=n.parentNode;const t=d(n);if(h(t,r))return a.some(t);if(s(t))break}return a.none()})(o,0,n)):a.some(t)).getOr(t);var o,r,n})(c,m);var v;(v=f,(t=>a.from(t.dom.parentNode).map(d))(v).filter(g)).each((e=>{if(t.setStyle(f.dom,"direction",null),S(e)===n?p(f,"dir"):((t,e,n)=>{((t,e,n)=>{if(!(o(n)||r(n)||s(n)))throw console.error("Invalid call to Attribute.set. Key ",e,":: Value ",n,":: Element ",t),new Error("Attribute value was not simple");t.setAttribute(e,n+"")})(t.dom,e,n)})(f,"dir",n),S(f)!==n&&t.setStyle(f.dom,"direction",n),m){const e=A(f,"li[dir],li[style]");u(e,(e=>{p(e,"dir"),t.setStyle(e.dom,"direction",null)}))}}))}))},C=(t,e)=>{t.selection.isEditable()&&(T(t.dom,t.selection.getSelectedBlocks(),e),t.nodeChanged())},D=(t,e)=>o=>{const r=r=>{const n=d(r.element);o.setActive(S(n)===e),o.setEnabled(t.selection.isEditable())};return t.on("NodeChange",r),o.setEnabled(t.selection.isEditable()),()=>t.off("NodeChange",r)};t.add("directionality",(t=>{(t=>{t.addCommand("mceDirectionLTR",(()=>{C(t,"ltr")})),t.addCommand("mceDirectionRTL",(()=>{C(t,"rtl")}))})(t),(t=>{t.ui.registry.addToggleButton("ltr",{tooltip:"Left to right",icon:"ltr",onAction:()=>t.execCommand("mceDirectionLTR"),onSetup:D(t,"ltr")}),t.ui.registry.addToggleButton("rtl",{tooltip:"Right to left",icon:"rtl",onAction:()=>t.execCommand("mceDirectionRTL"),onSetup:D(t,"rtl")})})(t)}))}();
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,93 @@
|
||||
tinymce.Resource.add('tinymce.html-i18n.help-keynav.ar',
|
||||
'<h1>بدء التنقل بواسطة لوحة المفاتيح</h1>\n' +
|
||||
'\n' +
|
||||
'<dl>\n' +
|
||||
' <dt>التركيز على شريط القوائم</dt>\n' +
|
||||
' <dd>نظاما التشغيل Windows أو Linux: Alt + F9</dd>\n' +
|
||||
' <dd>نظام التشغيل macOS: ⌥F9</dd>\n' +
|
||||
' <dt>التركيز على شريط الأدوات</dt>\n' +
|
||||
' <dd>نظاما التشغيل Windows أو Linux: Alt + F10</dd>\n' +
|
||||
' <dd>نظام التشغيل macOS: ⌥F10</dd>\n' +
|
||||
' <dt>التركيز على التذييل</dt>\n' +
|
||||
' <dd>نظاما التشغيل Windows أو Linux: Alt + F11</dd>\n' +
|
||||
' <dd>نظام التشغيل macOS: ⌥F11</dd>\n' +
|
||||
' <dt>تركيز الإشعارات</dt>\n' +
|
||||
' <dd>نظاما التشغيل Windows أو Linux: Alt + F12</dd>\n' +
|
||||
' <dd>نظام التشغيل macOS: ⌥F12</dd>\n' +
|
||||
' <dt>التركيز على شريط أدوات السياق</dt>\n' +
|
||||
' <dd>أنظمة التشغيل Windows أو Linux أو macOS: Ctrl+F9</dd>\n' +
|
||||
'</dl>\n' +
|
||||
'\n' +
|
||||
'<p>سيبدأ التنقل عند عنصر واجهة المستخدم الأول، والذي سيتم تمييزه أو تسطيره في حالة العنصر الأول في\n' +
|
||||
' مسار عنصر التذييل.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>التنقل بين أقسام واجهة المستخدم</h1>\n' +
|
||||
'\n' +
|
||||
'<p>للانتقال من أحد أقسام واجهة المستخدم إلى القسم التالي، اضغط على <strong>Tab</strong>.</p>\n' +
|
||||
'\n' +
|
||||
'<p>للانتقال من أحد أقسام واجهة المستخدم إلى القسم السابق، اضغط على <strong>Shift+Tab</strong>.</p>\n' +
|
||||
'\n' +
|
||||
'<p>ترتيب علامات <strong>Tab</strong> لأقسام واجهة المستخدم هذه هو:</p>\n' +
|
||||
'\n' +
|
||||
'<ol>\n' +
|
||||
' <li>شريط القوائم</li>\n' +
|
||||
' <li>كل مجموعة شريط الأدوات</li>\n' +
|
||||
' <li>الشريط الجانبي</li>\n' +
|
||||
' <li>مسار العنصر في التذييل</li>\n' +
|
||||
' <li>زر تبديل عدد الكلمات في التذييل</li>\n' +
|
||||
' <li>رابط إدراج العلامة التجارية في التذييل</li>\n' +
|
||||
' <li>مؤشر تغيير حجم المحرر في التذييل</li>\n' +
|
||||
'</ol>\n' +
|
||||
'\n' +
|
||||
'<p>إذا لم يكن قسم واجهة المستخدم موجودًا، فسيتم تخطيه.</p>\n' +
|
||||
'\n' +
|
||||
'<p>إذا كان التذييل يحتوي على التركيز على التنقل بواسطة لوحة المفاتيح، ولا يوجد شريط جانبي مرئي، فإن الضغط على <strong>Shift+Tab</strong>\n' +
|
||||
' ينقل التركيز إلى مجموعة شريط الأدوات الأولى، وليس الأخيرة.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>التنقل بين أقسام واجهة المستخدم</h1>\n' +
|
||||
'\n' +
|
||||
'<p>للانتقال من أحد عناصر واجهة المستخدم إلى العنصر التالي، اضغط على مفتاح <strong>السهم</strong> المناسب.</p>\n' +
|
||||
'\n' +
|
||||
'<p>مفتاحا السهمين <strong>اليسار</strong> و<strong>اليمين</strong></p>\n' +
|
||||
'\n' +
|
||||
'<ul>\n' +
|
||||
' <li>التنقل بين القوائم في شريط القوائم.</li>\n' +
|
||||
' <li>فتح قائمة فرعية في القائمة.</li>\n' +
|
||||
' <li>التنقل بين الأزرار في مجموعة شريط الأدوات.</li>\n' +
|
||||
' <li>التنقل بين العناصر في مسار عنصر التذييل.</li>\n' +
|
||||
'</ul>\n' +
|
||||
'\n' +
|
||||
'<p>مفتاحا السهمين <strong>لأسفل</strong> و<strong>لأعلى</strong></p>\n' +
|
||||
'\n' +
|
||||
'<ul>\n' +
|
||||
' <li>التنقل بين عناصر القائمة في القائمة.</li>\n' +
|
||||
' <li>التنقل بين العناصر في قائمة شريط الأدوات المنبثقة.</li>\n' +
|
||||
'</ul>\n' +
|
||||
'\n' +
|
||||
'<p>دورة مفاتيح <strong>الأسهم</strong> داخل قسم واجهة المستخدم التي تم التركيز عليها.</p>\n' +
|
||||
'\n' +
|
||||
'<p>لإغلاق قائمة مفتوحة أو قائمة فرعية مفتوحة أو قائمة منبثقة مفتوحة، اضغط على مفتاح <strong>Esc</strong>.</p>\n' +
|
||||
'\n' +
|
||||
'<p>إذا كان التركيز الحالي على "الجزء العلوي" من قسم معين لواجهة المستخدم، فإن الضغط على مفتاح <strong>Esc</strong> يؤدي أيضًا إلى الخروج\n' +
|
||||
' من التنقل بواسطة لوحة المفاتيح بالكامل.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>تنفيذ عنصر قائمة أو زر شريط أدوات</h1>\n' +
|
||||
'\n' +
|
||||
'<p>عندما يتم تمييز عنصر القائمة المطلوب أو زر شريط الأدوات، اضغط على زر <strong>Return</strong>، أو <strong>Enter</strong>،\n' +
|
||||
' أو <strong>مفتاح المسافة</strong> لتنفيذ العنصر.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>التنقل في مربعات الحوار غير المبوبة</h1>\n' +
|
||||
'\n' +
|
||||
'<p>في مربعات الحوار غير المبوبة، يتم التركيز على المكون التفاعلي الأول عند فتح مربع الحوار.</p>\n' +
|
||||
'\n' +
|
||||
'<p>التنقل بين مكونات الحوار التفاعلي بالضغط على زر <strong>Tab</strong> أو <strong>Shift+Tab</strong>.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>التنقل في مربعات الحوار المبوبة</h1>\n' +
|
||||
'\n' +
|
||||
'<p>في مربعات الحوار المبوبة، يتم التركيز على الزر الأول في قائمة علامات التبويب عند فتح مربع الحوار.</p>\n' +
|
||||
'\n' +
|
||||
'<p>التنقل بين المكونات التفاعلية لعلامة التبويب لمربع الحوار هذه بالضغط على زر <strong>Tab</strong> أو\n' +
|
||||
' <strong>Shift+Tab</strong>.</p>\n' +
|
||||
'\n' +
|
||||
'<p>التبديل إلى علامة تبويب أخرى لمربع الحوار من خلال التركيز على قائمة علامة التبويب ثم الضغط على زر <strong>السهم</strong> المناسب\n' +
|
||||
' مفتاح للتنقل بين علامات التبويب المتاحة.</p>\n');
|
||||
@@ -0,0 +1,93 @@
|
||||
tinymce.Resource.add('tinymce.html-i18n.help-keynav.bg_BG',
|
||||
'<h1>Начало на навигацията с клавиатурата</h1>\n' +
|
||||
'\n' +
|
||||
'<dl>\n' +
|
||||
' <dt>Фокусиране върху лентата с менюта</dt>\n' +
|
||||
' <dd>Windows или Linux: Alt+F9</dd>\n' +
|
||||
' <dd>macOS: ⌥F9</dd>\n' +
|
||||
' <dt>Фокусиране върху лентата с инструменти</dt>\n' +
|
||||
' <dd>Windows или Linux: Alt+F10</dd>\n' +
|
||||
' <dd>macOS: ⌥F10</dd>\n' +
|
||||
' <dt>Фокусиране върху долния колонтитул</dt>\n' +
|
||||
' <dd>Windows или Linux: Alt+F11</dd>\n' +
|
||||
' <dd>macOS: ⌥F11</dd>\n' +
|
||||
' <dt>Фокусиране на известието</dt>\n' +
|
||||
' <dd>Windows или Linux: Alt+F12</dd>\n' +
|
||||
' <dd>macOS: ⌥F12</dd>\n' +
|
||||
' <dt>Фокусиране върху контекстуалната лента с инструменти</dt>\n' +
|
||||
' <dd>Windows, Linux или macOS: Ctrl+F9</dd>\n' +
|
||||
'</dl>\n' +
|
||||
'\n' +
|
||||
'<p>Навигацията ще започне с първия елемент на ПИ, който ще бъде маркиран или подчертан в случая на първия елемент в\n' +
|
||||
' пътя до елемента в долния колонтитул.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>Навигиране между раздели на ПИ</h1>\n' +
|
||||
'\n' +
|
||||
'<p>За да преминете от един раздел на ПИ към следващия, натиснете <strong>Tab</strong>.</p>\n' +
|
||||
'\n' +
|
||||
'<p>За да преминете от един раздел на ПИ към предишния, натиснете <strong>Shift+Tab</strong>.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Редът за <strong>обхождане с табулация</strong> на тези раздели на ПИ е:</p>\n' +
|
||||
'\n' +
|
||||
'<ol>\n' +
|
||||
' <li>Лентата с менюта</li>\n' +
|
||||
' <li>Всяка група на лентата с инструменти</li>\n' +
|
||||
' <li>Страничната лента</li>\n' +
|
||||
' <li>Пътят до елемента в долния колонтитул</li>\n' +
|
||||
' <li>Бутонът за превключване на броя на думите в долния колонтитул</li>\n' +
|
||||
' <li>Връзката за търговска марка в долния колонтитул</li>\n' +
|
||||
' <li>Манипулаторът за преоразмеряване на редактора в долния колонтитул</li>\n' +
|
||||
'</ol>\n' +
|
||||
'\n' +
|
||||
'<p>Ако някой раздел на ПИ липсва, той се пропуска.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Ако долният колонтитул има фокус за навигация с клавиатурата и няма странична лента, натискането на <strong>Shift+Tab</strong>\n' +
|
||||
' премества фокуса към първата група на лентата с инструменти, а не към последната.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>Навигиране в разделите на ПИ</h1>\n' +
|
||||
'\n' +
|
||||
'<p>За да преминете от един елемент на ПИ към следващия, натиснете съответния клавиш със <strong>стрелка</strong>.</p>\n' +
|
||||
'\n' +
|
||||
'<p>С клавишите със стрелка <strong>наляво</strong> и <strong>надясно</strong></p>\n' +
|
||||
'\n' +
|
||||
'<ul>\n' +
|
||||
' <li>се придвижвате между менютата в лентата с менюто;</li>\n' +
|
||||
' <li>отваряте подменю в меню;</li>\n' +
|
||||
' <li>се придвижвате между бутоните в група на лентата с инструменти;</li>\n' +
|
||||
' <li>се придвижвате между елементи в пътя до елемент в долния колонтитул.</li>\n' +
|
||||
'</ul>\n' +
|
||||
'\n' +
|
||||
'<p>С клавишите със стрелка <strong>надолу</strong> и <strong>нагоре</strong></p>\n' +
|
||||
'\n' +
|
||||
'<ul>\n' +
|
||||
' <li>се придвижвате между елементите от менюто в дадено меню;</li>\n' +
|
||||
' <li>се придвижвате между елементите в изскачащо меню на лентата с инструменти.</li>\n' +
|
||||
'</ul>\n' +
|
||||
'\n' +
|
||||
'<p>Клавишите със <strong>стрелки</strong> се придвижват в рамките на фокусирания раздел на ПИ.</p>\n' +
|
||||
'\n' +
|
||||
'<p>За да затворите отворено меню, подменю или изскачащо меню, натиснете клавиша <strong>Esc</strong>.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Ако текущият фокус е върху „горната част“ на конкретен раздел на ПИ, натискането на клавиша <strong>Esc</strong> също излиза\n' +
|
||||
' напълно от навигацията с клавиатурата.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>Изпълнение на елемент от менюто или бутон от лентата с инструменти</h1>\n' +
|
||||
'\n' +
|
||||
'<p>Когато желаният елемент от менюто или бутон от лентата с инструменти е маркиран, натиснете <strong>Return</strong>, <strong>Enter</strong>\n' +
|
||||
' или <strong>клавиша за интервал</strong>, за да изпълните елемента.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>Навигиране в диалогови прозорци без раздели</h1>\n' +
|
||||
'\n' +
|
||||
'<p>В диалоговите прозорци без раздели първият интерактивен компонент се фокусира, когато се отвори диалоговият прозорец.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Навигирайте между интерактивните компоненти на диалоговия прозорец, като натиснете <strong>Tab</strong> или <strong>Shift+Tab</strong>.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>Навигиране в диалогови прозорци с раздели</h1>\n' +
|
||||
'\n' +
|
||||
'<p>В диалоговите прозорци с раздели първият бутон в менюто с раздели се фокусира, когато се отвори диалоговият прозорец.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Навигирайте между интерактивните компоненти на този диалогов раздел, като натиснете <strong>Tab</strong> или\n' +
|
||||
' <strong>Shift+Tab</strong>.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Превключете към друг диалогов раздел, като фокусирате върху менюто с раздели и след това натиснете съответния клавиш със <strong>стрелка</strong>,\n' +
|
||||
' за да преминете през наличните раздели.</p>\n');
|
||||
@@ -0,0 +1,93 @@
|
||||
tinymce.Resource.add('tinymce.html-i18n.help-keynav.ca',
|
||||
'<h1>Inici de la navegació amb el teclat</h1>\n' +
|
||||
'\n' +
|
||||
'<dl>\n' +
|
||||
' <dt>Enfocar la barra de menús</dt>\n' +
|
||||
' <dd>Windows o Linux: Alt+F9</dd>\n' +
|
||||
' <dd>macOS: ⌥F9</dd>\n' +
|
||||
" <dt>Enfocar la barra d'eines</dt>\n" +
|
||||
' <dd>Windows o Linux: Alt+F10</dd>\n' +
|
||||
' <dd>macOS: ⌥F10</dd>\n' +
|
||||
' <dt>Enfocar el peu de pàgina</dt>\n' +
|
||||
' <dd>Windows o Linux: Alt+F11</dd>\n' +
|
||||
' <dd>macOS: ⌥F11</dd>\n' +
|
||||
' <dt>Enfocar la notificació</dt>\n' +
|
||||
' <dd>Windows o Linux: Alt+F12</dd>\n' +
|
||||
' <dd>macOS: ⌥F12</dd>\n' +
|
||||
" <dt>Enfocar una barra d'eines contextual</dt>\n" +
|
||||
' <dd>Windows, Linux o macOS: Ctrl+F9</dd>\n' +
|
||||
'</dl>\n' +
|
||||
'\n' +
|
||||
"<p>La navegació començarà en el primer element de la interfície d'usuari, que es ressaltarà o subratllarà per al primer element a\n" +
|
||||
" la ruta de l'element de peu de pàgina.</p>\n" +
|
||||
'\n' +
|
||||
"<h1>Navegació entre seccions de la interfície d'usuari</h1>\n" +
|
||||
'\n' +
|
||||
"<p>Per desplaçar-vos des d'una secció de la interfície d'usuari a la següent, premeu la tecla <strong>Tab</strong>.</p>\n" +
|
||||
'\n' +
|
||||
"<p>Per desplaçar-vos des d'una secció de la interfície d'usuari a l'anterior, premeu les tecles <strong>Maj+Tab</strong>.</p>\n" +
|
||||
'\n' +
|
||||
"<p>L'ordre en prémer la tecla <strong>Tab</strong> d'aquestes secciones de la interfície d'usuari és:</p>\n" +
|
||||
'\n' +
|
||||
'<ol>\n' +
|
||||
' <li>Barra de menús</li>\n' +
|
||||
" <li>Cada grup de la barra d'eines</li>\n" +
|
||||
' <li>Barra lateral</li>\n' +
|
||||
" <li>Ruta de l'element del peu de pàgina</li>\n" +
|
||||
' <li>Botó de commutació de recompte de paraules al peu de pàgina</li>\n' +
|
||||
' <li>Enllaç de marca del peu de pàgina</li>\n' +
|
||||
" <li>Control de canvi de mida de l'editor al peu de pàgina</li>\n" +
|
||||
'</ol>\n' +
|
||||
'\n' +
|
||||
"<p>Si no hi ha una secció de la interfície d'usuari, s'ometrà.</p>\n" +
|
||||
'\n' +
|
||||
'<p>Si el peu de pàgina té el focus de navegació del teclat i no hi ha cap barra lateral visible, en prémer <strong>Maj+Tab</strong>\n' +
|
||||
" el focus es mou al primer grup de la barra d'eines, no l'últim.</p>\n" +
|
||||
'\n' +
|
||||
"<h1>Navegació dins de les seccions de la interfície d'usuari</h1>\n" +
|
||||
'\n' +
|
||||
"<p>Per desplaçar-vos des d'un element de la interfície d'usuari al següent, premeu la tecla de <strong>Fletxa</strong> adequada.</p>\n" +
|
||||
'\n' +
|
||||
'<p>Les tecles de fletxa <strong>Esquerra</strong> i <strong>Dreta</strong></p>\n' +
|
||||
'\n' +
|
||||
'<ul>\n' +
|
||||
' <li>us permeten desplaçar-vos entre menús de la barra de menús.</li>\n' +
|
||||
' <li>obren un submenú en un menú.</li>\n' +
|
||||
" <li>us permeten desplaçar-vos entre botons d'un grup de la barra d'eines.</li>\n" +
|
||||
" <li>us permeten desplaçar-vos entre elements de la ruta d'elements del peu de pàgina.</li>\n" +
|
||||
'</ul>\n' +
|
||||
'\n' +
|
||||
'<p>Les tecles de fletxa <strong>Avall</strong> i <strong>Amunt</strong></p>\n' +
|
||||
'\n' +
|
||||
'<ul>\n' +
|
||||
" <li>us permeten desplaçar-vos entre elements de menú d'un menú.</li>\n" +
|
||||
" <li>us permeten desplaçar-vos entre elements d'un menú emergent de la barra d'eines.</li>\n" +
|
||||
'</ul>\n' +
|
||||
'\n' +
|
||||
"<p>Les tecles de <strong>Fletxa</strong> us permeten desplaçar-vos dins de la secció de la interfície d'usuari que té el focus.</p>\n" +
|
||||
'\n' +
|
||||
'<p>Per tancar un menú, un submenú o un menú emergent oberts, premeu la tecla <strong>Esc</strong>.</p>\n' +
|
||||
'\n' +
|
||||
"<p>Si el focus actual es troba a la ‘part superior’ d'una secció específica de la interfície d'usuari, en prémer la tecla <strong>Esc</strong> també es tanca\n" +
|
||||
' completament la navegació amb el teclat.</p>\n' +
|
||||
'\n' +
|
||||
"<h1>Execució d'un element de menú o d'un botó de la barra d'eines</h1>\n" +
|
||||
'\n' +
|
||||
"<p>Quan l'element del menú o el botó de la barra d'eines que desitgeu estigui ressaltat, premeu <strong>Retorn</strong>, <strong>Intro</strong>\n" +
|
||||
" o la <strong>barra d'espai</strong> per executar l'element.</p>\n" +
|
||||
'\n' +
|
||||
'<h1>Navegació per quadres de diàleg sense pestanyes</h1>\n' +
|
||||
'\n' +
|
||||
"<p>En els quadres de diàleg sense pestanyes, el primer component interactiu pren el focus quan s'obre el quadre diàleg.</p>\n" +
|
||||
'\n' +
|
||||
'<p>Premeu la tecla <strong>Tab</strong> o les tecles <strong>Maj+Tab</strong> per desplaçar-vos entre components interactius del quadre de diàleg.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>Navegació per quadres de diàleg amb pestanyes</h1>\n' +
|
||||
'\n' +
|
||||
"<p>En els quadres de diàleg amb pestanyes, el primer botó del menú de la pestanya pren el focus quan s'obre el quadre diàleg.</p>\n" +
|
||||
'\n' +
|
||||
"<p>Per desplaçar-vos entre components interactius d'aquest quadre de diàleg, premeu la tecla <strong>Tab</strong> o\n" +
|
||||
' les tecles <strong>Maj+Tab</strong>.</p>\n' +
|
||||
'\n' +
|
||||
"<p>Canvieu a la pestanya d'un altre quadre de diàleg, tot enfocant el menú de la pestanya, i després premeu la tecla <strong>Fletxa</strong> adequada\n" +
|
||||
' per canviar entre les pestanyes disponibles.</p>\n');
|
||||
@@ -0,0 +1,93 @@
|
||||
tinymce.Resource.add('tinymce.html-i18n.help-keynav.cs',
|
||||
'<h1>Začínáme navigovat pomocí klávesnice</h1>\n' +
|
||||
'\n' +
|
||||
'<dl>\n' +
|
||||
' <dt>Přejít na řádek nabídek</dt>\n' +
|
||||
' <dd>Windows nebo Linux: Alt+F9</dd>\n' +
|
||||
' <dd>macOS: ⌥F9</dd>\n' +
|
||||
' <dt>Přejít na panel nástrojů</dt>\n' +
|
||||
' <dd>Windows nebo Linux: Alt+F10</dd>\n' +
|
||||
' <dd>macOS: ⌥F10</dd>\n' +
|
||||
' <dt>Přejít na zápatí</dt>\n' +
|
||||
' <dd>Windows nebo Linux: Alt+F11</dd>\n' +
|
||||
' <dd>macOS: ⌥F11</dd>\n' +
|
||||
' <dt>Přejít na oznámení</dt>\n' +
|
||||
' <dd>Windows nebo Linux: Alt+F12</dd>\n' +
|
||||
' <dd>macOS: ⌥F12</dd>\n' +
|
||||
' <dt>Přejít na kontextový panel nástrojů</dt>\n' +
|
||||
' <dd>Windows, Linux nebo macOS: Ctrl+F9</dd>\n' +
|
||||
'</dl>\n' +
|
||||
'\n' +
|
||||
'<p>Navigace začne u první položky uživatelského rozhraní, která bude zvýrazněna nebo v případě první položky\n' +
|
||||
' cesty k prvku zápatí podtržena.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>Navigace mezi oddíly uživatelského rozhraní</h1>\n' +
|
||||
'\n' +
|
||||
'<p>Stisknutím klávesy <strong>Tab</strong> se posunete z jednoho oddílu uživatelského rozhraní na další.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Stisknutím kláves <strong>Shift+Tab</strong> se posunete z jednoho oddílu uživatelského rozhraní na předchozí.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Pořadí přepínání mezi oddíly uživatelského rozhraní pomocí klávesy <strong>Tab</strong>:</p>\n' +
|
||||
'\n' +
|
||||
'<ol>\n' +
|
||||
' <li>Řádek nabídek</li>\n' +
|
||||
' <li>Každá skupina panelu nástrojů</li>\n' +
|
||||
' <li>Boční panel</li>\n' +
|
||||
' <li>Cesta k prvku v zápatí.</li>\n' +
|
||||
' <li>Tlačítko přepínače počtu slov v zápatí</li>\n' +
|
||||
' <li>Odkaz na informace o značce v zápatí</li>\n' +
|
||||
' <li>Úchyt pro změnu velikosti editoru v zápatí</li>\n' +
|
||||
'</ol>\n' +
|
||||
'\n' +
|
||||
'<p>Pokud nějaký oddíl uživatelského rozhraní není přítomen, je přeskočen.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Pokud je zápatí vybrané pro navigaci pomocí klávesnice a není zobrazen žádný boční panel, stisknutím kláves <strong>Shift+Tab</strong>\n' +
|
||||
' přejdete na první skupinu panelu nástrojů, nikoli na poslední.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>Navigace v rámci oddílů uživatelského rozhraní</h1>\n' +
|
||||
'\n' +
|
||||
'<p>Chcete-li se přesunout z jednoho prvku uživatelského rozhraní na další, stiskněte příslušnou klávesu s <strong>šipkou</strong>.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Klávesy s šipkou <strong>vlevo</strong> a <strong>vpravo</strong></p>\n' +
|
||||
'\n' +
|
||||
'<ul>\n' +
|
||||
' <li>umožňují přesun mezi nabídkami na řádku nabídek;</li>\n' +
|
||||
' <li>otevírají podnabídku nabídky;</li>\n' +
|
||||
' <li>umožňují přesun mezi tlačítky ve skupině panelu nástrojů;</li>\n' +
|
||||
' <li>umožňují přesun mezi položkami cesty prvku v zápatí.</li>\n' +
|
||||
'</ul>\n' +
|
||||
'\n' +
|
||||
'<p>Klávesy se šipkou <strong>dolů</strong> a <strong>nahoru</strong></p>\n' +
|
||||
'\n' +
|
||||
'<ul>\n' +
|
||||
' <li>umožňují přesun mezi položkami nabídky;</li>\n' +
|
||||
' <li>umožňují přesun mezi položkami místní nabídky panelu nástrojů.</li>\n' +
|
||||
'</ul>\n' +
|
||||
'\n' +
|
||||
'<p><strong>Šipky</strong> provádí přepínání v rámci vybraného oddílu uživatelského rozhraní.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Chcete-li zavřít otevřenou nabídku, podnabídku nebo místní nabídku, stiskněte klávesu <strong>Esc</strong>.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Pokud je aktuálně vybrána horní část oddílu uživatelského rozhraní, stisknutím klávesy <strong>Esc</strong> zcela ukončíte také\n' +
|
||||
' navigaci pomocí klávesnice.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>Provedení příkazu položky nabídky nebo tlačítka panelu nástrojů</h1>\n' +
|
||||
'\n' +
|
||||
'<p>Pokud je zvýrazněna požadovaná položka nabídky nebo tlačítko panelu nástrojů, stisknutím klávesy <strong>Return</strong>, <strong>Enter</strong>\n' +
|
||||
' nebo <strong>mezerníku</strong> provedete příslušný příkaz.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>Navigace v dialogových oknech bez záložek</h1>\n' +
|
||||
'\n' +
|
||||
'<p>Při otevření dialogových oken bez záložek přejdete na první interaktivní komponentu.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Přecházet mezi interaktivními komponentami dialogového okna můžete stisknutím klávesy <strong>Tab</strong> nebo kombinace <strong>Shift+Tab</strong>.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>Navigace v dialogových oknech se záložkami</h1>\n' +
|
||||
'\n' +
|
||||
'<p>Při otevření dialogových oken se záložkami přejdete na první tlačítko v nabídce záložek.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Přecházet mezi interaktivními komponentami této záložky dialogového okna můžete stisknutím klávesy <strong>Tab</strong> nebo\n' +
|
||||
' kombinace <strong>Shift+Tab</strong>.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Chcete-li přepnout na další záložku dialogového okna, přejděte na nabídku záložek a poté můžete stisknutím požadované <strong>šipky</strong>\n' +
|
||||
' přepínat mezi dostupnými záložkami.</p>\n');
|
||||
@@ -0,0 +1,93 @@
|
||||
tinymce.Resource.add('tinymce.html-i18n.help-keynav.da',
|
||||
'<h1>Start tastaturnavigation</h1>\n' +
|
||||
'\n' +
|
||||
'<dl>\n' +
|
||||
' <dt>Fokuser på menulinjen</dt>\n' +
|
||||
' <dd>Windows eller Linux: Alt+F9</dd>\n' +
|
||||
' <dd>macOS: ⌥F9</dd>\n' +
|
||||
' <dt>Fokuser på værktøjslinjen</dt>\n' +
|
||||
' <dd>Windows eller Linux: Alt+F10</dd>\n' +
|
||||
' <dd>macOS: ⌥F10</dd>\n' +
|
||||
' <dt>Fokuser på sidefoden</dt>\n' +
|
||||
' <dd>Windows eller Linux: Alt+F11</dd>\n' +
|
||||
' <dd>macOS: ⌥F11</dd>\n' +
|
||||
' <dt>Fokuser på meddelelsen</dt>\n' +
|
||||
' <dd>Windows eller Linux: Alt+F12</dd>\n' +
|
||||
' <dd>macOS: ⌥F12</dd>\n' +
|
||||
' <dt>Fokuser på kontekstuel værktøjslinje</dt>\n' +
|
||||
' <dd>Windows, Linux eller macOS: Ctrl+F9</dd>\n' +
|
||||
'</dl>\n' +
|
||||
'\n' +
|
||||
'<p>Navigationen starter ved det første UI-element, som fremhæves eller understreges hvad angår det første element i\n' +
|
||||
' sidefodens sti til elementet.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>Naviger mellem UI-sektioner</h1>\n' +
|
||||
'\n' +
|
||||
'<p>Gå fra én UI-sektion til den næste ved at trykke på <strong>Tab</strong>.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Gå fra én UI-sektion til den forrige ved at trykke på <strong>Shift+Tab</strong>.</p>\n' +
|
||||
'\n' +
|
||||
'<p><strong>Tab</strong>-rækkefølgen af disse UI-sektioner er:</p>\n' +
|
||||
'\n' +
|
||||
'<ol>\n' +
|
||||
' <li>Menulinje</li>\n' +
|
||||
' <li>Hver værktøjsgruppe</li>\n' +
|
||||
' <li>Sidepanel</li>\n' +
|
||||
' <li>Sti til elementet i sidefoden</li>\n' +
|
||||
' <li>Til/fra-knap for ordoptælling i sidefoden</li>\n' +
|
||||
' <li>Brandinglink i sidefoden</li>\n' +
|
||||
' <li>Tilpasningshåndtag for editor i sidefoden</li>\n' +
|
||||
'</ol>\n' +
|
||||
'\n' +
|
||||
'<p>Hvis en UI-sektion ikke er til stede, springes den over.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Hvis sidefoden har fokus til tastaturnavigation, og der ikke er noget synligt sidepanel, kan der trykkes på <strong>Shift+Tab</strong>\n' +
|
||||
' for at flytte fokus til den første værktøjsgruppe, ikke den sidste.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>Naviger inden for UI-sektioner</h1>\n' +
|
||||
'\n' +
|
||||
'<p>Gå fra ét UI-element til det næste ved at trykke på den relevante <strong>piletast</strong>.</p>\n' +
|
||||
'\n' +
|
||||
'<p><strong>Venstre</strong> og <strong>højre</strong> piletast</p>\n' +
|
||||
'\n' +
|
||||
'<ul>\n' +
|
||||
' <li>flytter mellem menuerne i menulinjen.</li>\n' +
|
||||
' <li>åbner en undermenu i en menu.</li>\n' +
|
||||
' <li>flytter mellem knapperne i en værktøjsgruppe.</li>\n' +
|
||||
' <li>flytter mellem elementer i sidefodens sti til elementet.</li>\n' +
|
||||
'</ul>\n' +
|
||||
'\n' +
|
||||
'<p>Pil <strong>ned</strong> og <strong>op</strong></p>\n' +
|
||||
'\n' +
|
||||
'<ul>\n' +
|
||||
' <li>flytter mellem menupunkterne i en menu.</li>\n' +
|
||||
' <li>flytter mellem punkterne i en genvejsmenu i værktøjslinjen.</li>\n' +
|
||||
'</ul>\n' +
|
||||
'\n' +
|
||||
'<p><strong>Piletasterne</strong> kører rundt inden for UI-sektionen, der fokuseres på.</p>\n' +
|
||||
'\n' +
|
||||
'<p>For at lukke en åben menu, en åben undermenu eller en åben genvejsmenu trykkes der på <strong>Esc</strong>-tasten.</p>\n' +
|
||||
'\n' +
|
||||
"<p>Hvis det aktuelle fokus er i 'toppen' af en bestemt UI-sektion, vil tryk på <strong>Esc</strong>-tasten også afslutte\n" +
|
||||
' tastaturnavigationen helt.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>Udfør et menupunkt eller en værktøjslinjeknap</h1>\n' +
|
||||
'\n' +
|
||||
'<p>Når det ønskede menupunkt eller den ønskede værktøjslinjeknap er fremhævet, trykkes der på <strong>Retur</strong>, <strong>Enter</strong>\n' +
|
||||
' eller <strong>mellemrumstasten</strong> for at udføre elementet.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>Naviger i ikke-faneopdelte dialogbokse</h1>\n' +
|
||||
'\n' +
|
||||
'<p>I ikke-faneopdelte dialogbokse får den første interaktive komponent fokus, når dialogboksen åbnes.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Naviger mellem interaktive dialogbokskomponenter ved at trykke på <strong>Tab</strong> eller <strong>Shift+Tab</strong>.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>Naviger i faneopdelte dialogbokse</h1>\n' +
|
||||
'\n' +
|
||||
'<p>I faneopdelte dialogbokse får den første knap i fanemenuen fokus, når dialogboksen åbnes.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Naviger mellem interaktive komponenter i denne dialogboksfane ved at trykke på <strong>Tab</strong> eller\n' +
|
||||
' <strong>Shift+Tab</strong>.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Skift til en anden dialogboksfane ved at fokusere på fanemenuen og derefter trykke på den relevante <strong>piletast</strong>\n' +
|
||||
' for at køre igennem de tilgængelige faner.</p>\n');
|
||||
@@ -0,0 +1,93 @@
|
||||
tinymce.Resource.add('tinymce.html-i18n.help-keynav.de',
|
||||
'<h1>Grundlagen der Tastaturnavigation</h1>\n' +
|
||||
'\n' +
|
||||
'<dl>\n' +
|
||||
' <dt>Fokus auf Menüleiste</dt>\n' +
|
||||
' <dd>Windows oder Linux: ALT+F9</dd>\n' +
|
||||
' <dd>macOS: ⌥F9</dd>\n' +
|
||||
' <dt>Fokus auf Symbolleiste</dt>\n' +
|
||||
' <dd>Windows oder Linux: ALT+F10</dd>\n' +
|
||||
' <dd>macOS: ⌥F10</dd>\n' +
|
||||
' <dt>Fokus auf Fußzeile</dt>\n' +
|
||||
' <dd>Windows oder Linux: ALT+F11</dd>\n' +
|
||||
' <dd>macOS: ⌥F11</dd>\n' +
|
||||
' <dt>Benachrichtigung fokussieren</dt>\n' +
|
||||
' <dd>Windows oder Linux: ALT+F12</dd>\n' +
|
||||
' <dd>macOS: ⌥F12</dd>\n' +
|
||||
' <dt>Fokus auf kontextbezogene Symbolleiste</dt>\n' +
|
||||
' <dd>Windows, Linux oder macOS: STRG+F9</dd>\n' +
|
||||
'</dl>\n' +
|
||||
'\n' +
|
||||
'<p>Die Navigation beginnt beim ersten Benutzeroberflächenelement, welches hervorgehoben ist. Falls sich das erste Element im Pfad der Fußzeile befindet,\n' +
|
||||
' ist es unterstrichen.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>Zwischen Abschnitten der Benutzeroberfläche navigieren</h1>\n' +
|
||||
'\n' +
|
||||
'<p>Um von einem Abschnitt der Benutzeroberfläche zum nächsten zu wechseln, drücken Sie <strong>TAB</strong>.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Um von einem Abschnitt der Benutzeroberfläche zum vorherigen zu wechseln, drücken Sie <strong>UMSCHALT+TAB</strong>.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Die Abschnitte der Benutzeroberfläche haben folgende <strong>TAB</strong>-Reihenfolge:</p>\n' +
|
||||
'\n' +
|
||||
'<ol>\n' +
|
||||
' <li>Menüleiste</li>\n' +
|
||||
' <li>Einzelne Gruppen der Symbolleiste</li>\n' +
|
||||
' <li>Randleiste</li>\n' +
|
||||
' <li>Elementpfad in der Fußzeile</li>\n' +
|
||||
' <li>Umschaltfläche „Wörter zählen“ in der Fußzeile</li>\n' +
|
||||
' <li>Branding-Link in der Fußzeile</li>\n' +
|
||||
' <li>Editor-Ziehpunkt zur Größenänderung in der Fußzeile</li>\n' +
|
||||
'</ol>\n' +
|
||||
'\n' +
|
||||
'<p>Falls ein Abschnitt der Benutzeroberflächen nicht vorhanden ist, wird er übersprungen.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Wenn in der Fußzeile die Tastaturnavigation fokussiert ist und keine Randleiste angezeigt wird, wechselt der Fokus durch Drücken von <strong>UMSCHALT+TAB</strong>\n' +
|
||||
' zur ersten Gruppe der Symbolleiste, nicht zur letzten.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>Innerhalb von Abschnitten der Benutzeroberfläche navigieren</h1>\n' +
|
||||
'\n' +
|
||||
'<p>Um von einem Element der Benutzeroberfläche zum nächsten zu wechseln, drücken Sie die entsprechende <strong>Pfeiltaste</strong>.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Die Pfeiltasten <strong>Links</strong> und <strong>Rechts</strong></p>\n' +
|
||||
'\n' +
|
||||
'<ul>\n' +
|
||||
' <li>wechseln zwischen Menüs in der Menüleiste.</li>\n' +
|
||||
' <li>öffnen das Untermenü eines Menüs.</li>\n' +
|
||||
' <li>wechseln zwischen Schaltflächen in einer Gruppe der Symbolleiste.</li>\n' +
|
||||
' <li>wechseln zwischen Elementen im Elementpfad der Fußzeile.</li>\n' +
|
||||
'</ul>\n' +
|
||||
'\n' +
|
||||
'<p>Die Pfeiltasten <strong>Abwärts</strong> und <strong>Aufwärts</strong></p>\n' +
|
||||
'\n' +
|
||||
'<ul>\n' +
|
||||
' <li>wechseln zwischen Menüelementen in einem Menü.</li>\n' +
|
||||
' <li>wechseln zwischen Elementen in einem Popupmenü der Symbolleiste.</li>\n' +
|
||||
'</ul>\n' +
|
||||
'\n' +
|
||||
'<p>Die <strong>Pfeiltasten</strong> rotieren innerhalb des fokussierten Abschnitts der Benutzeroberfläche.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Um ein geöffnetes Menü, ein geöffnetes Untermenü oder ein geöffnetes Popupmenü zu schließen, drücken Sie die <strong>ESC</strong>-Taste.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Wenn sich der aktuelle Fokus ganz oben in einem bestimmten Abschnitt der Benutzeroberfläche befindet, wird durch Drücken der <strong>ESC</strong>-Taste auch\n' +
|
||||
' die Tastaturnavigation beendet.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>Ein Menüelement oder eine Symbolleistenschaltfläche ausführen</h1>\n' +
|
||||
'\n' +
|
||||
'<p>Wenn das gewünschte Menüelement oder die gewünschte Symbolleistenschaltfläche hervorgehoben ist, drücken Sie <strong>Zurück</strong>, <strong>Eingabe</strong>\n' +
|
||||
' oder die <strong>Leertaste</strong>, um das Element auszuführen.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>In Dialogfeldern ohne Registerkarten navigieren</h1>\n' +
|
||||
'\n' +
|
||||
'<p>In Dialogfeldern ohne Registerkarten ist beim Öffnen eines Dialogfelds die erste interaktive Komponente fokussiert.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Navigieren Sie zwischen den interaktiven Komponenten eines Dialogfelds, indem Sie <strong>TAB</strong> oder <strong>UMSCHALT+TAB</strong> drücken.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>In Dialogfeldern mit Registerkarten navigieren</h1>\n' +
|
||||
'\n' +
|
||||
'<p>In Dialogfeldern mit Registerkarten ist beim Öffnen eines Dialogfelds die erste Schaltfläche eines Registerkartenmenüs fokussiert.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Navigieren Sie zwischen den interaktiven Komponenten auf dieser Registerkarte des Dialogfelds, indem Sie <strong>TAB</strong> oder\n' +
|
||||
' <strong>UMSCHALT+TAB</strong> drücken.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Wechseln Sie zu einer anderen Registerkarte des Dialogfelds, indem Sie den Fokus auf das Registerkartenmenü legen und dann die entsprechende <strong>Pfeiltaste</strong>\n' +
|
||||
' drücken, um durch die verfügbaren Registerkarten zu rotieren.</p>\n');
|
||||
@@ -0,0 +1,93 @@
|
||||
tinymce.Resource.add('tinymce.html-i18n.help-keynav.el',
|
||||
'<h1>Έναρξη πλοήγησης μέσω πληκτρολογίου</h1>\n' +
|
||||
'\n' +
|
||||
'<dl>\n' +
|
||||
' <dt>Εστίαση στη γραμμή μενού</dt>\n' +
|
||||
' <dd>Windows ή Linux: Alt+F9</dd>\n' +
|
||||
' <dd>macOS: ⌥F9</dd>\n' +
|
||||
' <dt>Εστίαση στη γραμμή εργαλείων</dt>\n' +
|
||||
' <dd>Windows ή Linux: Alt+F10</dd>\n' +
|
||||
' <dd>macOS: ⌥F10</dd>\n' +
|
||||
' <dt>Εστίαση στο υποσέλιδο</dt>\n' +
|
||||
' <dd>Windows ή Linux: Alt+F11</dd>\n' +
|
||||
' <dd>macOS: ⌥F11</dd>\n' +
|
||||
' <dt>Εστίαση στην ειδοποίηση</dt>\n' +
|
||||
' <dd>Windows ή Linux: Alt+F12</dd>\n' +
|
||||
' <dd>macOS: ⌥F12</dd>\n' +
|
||||
' <dt>Εστίαση σε γραμμή εργαλείων βάσει περιεχομένου</dt>\n' +
|
||||
' <dd>Windows, Linux ή macOS: Ctrl+F9</dd>\n' +
|
||||
'</dl>\n' +
|
||||
'\n' +
|
||||
'<p>Η πλοήγηση θα ξεκινήσει από το πρώτο στοιχείο περιβάλλοντος χρήστη, που θα επισημαίνεται ή θα είναι υπογραμμισμένο,\n' +
|
||||
' όπως στην περίπτωση της διαδρομής του στοιχείου Υποσέλιδου.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>Πλοήγηση μεταξύ ενοτήτων του περιβάλλοντος χρήστη</h1>\n' +
|
||||
'\n' +
|
||||
'<p>Για να μετακινηθείτε από μια ενότητα περιβάλλοντος χρήστη στην επόμενη, πιέστε το πλήκτρο <strong>Tab</strong>.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Για να μετακινηθείτε από μια ενότητα περιβάλλοντος χρήστη στην προηγούμενη, πιέστε τα πλήκτρα <strong>Shift+Tab</strong>.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Η σειρά <strong>Tab</strong> αυτών των ενοτήτων περιβάλλοντος χρήστη είναι η εξής:</p>\n' +
|
||||
'\n' +
|
||||
'<ol>\n' +
|
||||
' <li>Γραμμή μενού</li>\n' +
|
||||
' <li>Κάθε ομάδα γραμμής εργαλείων</li>\n' +
|
||||
' <li>Πλαϊνή γραμμή</li>\n' +
|
||||
' <li>Διαδρομή στοιχείου στο υποσέλιδο</li>\n' +
|
||||
' <li>Κουμπί εναλλαγής μέτρησης λέξεων στο υποσέλιδο</li>\n' +
|
||||
' <li>Σύνδεσμος επωνυμίας στο υποσέλιδο</li>\n' +
|
||||
' <li>Λαβή αλλαγής μεγέθους προγράμματος επεξεργασίας στο υποσέλιδο</li>\n' +
|
||||
'</ol>\n' +
|
||||
'\n' +
|
||||
'<p>Εάν δεν εμφανίζεται ενότητα περιβάλλοντος χρήστη, παραλείπεται.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Εάν η εστίαση πλοήγησης βρίσκεται στο πληκτρολόγιο και δεν υπάρχει εμφανής πλαϊνή γραμμή, εάν πιέσετε <strong>Shift+Tab</strong>\n' +
|
||||
' η εστίαση μετακινείται στην πρώτη ομάδα γραμμής εργαλείων, όχι στην τελευταία.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>Πλοήγηση εντός των ενοτήτων του περιβάλλοντος χρήστη</h1>\n' +
|
||||
'\n' +
|
||||
'<p>Για να μετακινηθείτε από ένα στοιχείο περιβάλλοντος χρήστη στο επόμενο, πιέστε το αντίστοιχο πλήκτρο <strong>βέλους</strong>.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Με τα πλήκτρα <strong>αριστερού</strong> και <strong>δεξιού</strong> βέλους</p>\n' +
|
||||
'\n' +
|
||||
'<ul>\n' +
|
||||
' <li>γίνεται μετακίνηση μεταξύ των μενού στη γραμμή μενού.</li>\n' +
|
||||
' <li>ανοίγει ένα υπομενού σε ένα μενού.</li>\n' +
|
||||
' <li>γίνεται μετακίνηση μεταξύ κουμπιών σε μια ομάδα γραμμής εργαλείων.</li>\n' +
|
||||
' <li>γίνεται μετακίνηση μεταξύ στοιχείων στη διαδρομή στοιχείου στο υποσέλιδο.</li>\n' +
|
||||
'</ul>\n' +
|
||||
'\n' +
|
||||
'<p>Με τα πλήκτρα <strong>επάνω</strong> και <strong>κάτω</strong> βέλους</p>\n' +
|
||||
'\n' +
|
||||
'<ul>\n' +
|
||||
' <li>γίνεται μετακίνηση μεταξύ των στοιχείων μενού σε ένα μενού.</li>\n' +
|
||||
' <li>γίνεται μετακίνηση μεταξύ των στοιχείων μενού σε ένα αναδυόμενο μενού γραμμής εργαλείων.</li>\n' +
|
||||
'</ul>\n' +
|
||||
'\n' +
|
||||
'<p>Με τα πλήκτρα <strong>βέλους</strong> γίνεται κυκλική μετακίνηση εντός της εστιασμένης ενότητας περιβάλλοντος χρήστη.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Για να κλείσετε ένα ανοιχτό μενού, ένα ανοιχτό υπομενού ή ένα ανοιχτό αναδυόμενο μενού, πιέστε το πλήκτρο <strong>Esc</strong>.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Εάν η τρέχουσα εστίαση βρίσκεται στην κορυφή μιας ενότητας περιβάλλοντος χρήστη, πιέζοντας το πλήκτρο <strong>Esc</strong>,\n' +
|
||||
' γίνεται επίσης πλήρης έξοδος από την πλοήγηση μέσω πληκτρολογίου.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>Εκτέλεση ενός στοιχείου μενού ή κουμπιού γραμμής εργαλείων</h1>\n' +
|
||||
'\n' +
|
||||
'<p>Όταν το επιθυμητό στοιχείο μενού ή κουμπί γραμμής εργαλείων είναι επισημασμένο, πιέστε τα πλήκτρα <strong>Return</strong>, <strong>Enter</strong>,\n' +
|
||||
' ή το <strong>πλήκτρο διαστήματος</strong> για να εκτελέσετε το στοιχείο.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>Πλοήγηση σε παράθυρα διαλόγου χωρίς καρτέλες</h1>\n' +
|
||||
'\n' +
|
||||
'<p>Σε παράθυρα διαλόγου χωρίς καρτέλες, το πρώτο αλληλεπιδραστικό στοιχείο λαμβάνει την εστίαση όταν ανοίγει το παράθυρο διαλόγου.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Μπορείτε να πλοηγηθείτε μεταξύ των αλληλεπιδραστικών στοιχείων παραθύρων διαλόγων πιέζοντας τα πλήκτρα <strong>Tab</strong> ή <strong>Shift+Tab</strong>.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>Πλοήγηση σε παράθυρα διαλόγου με καρτέλες</h1>\n' +
|
||||
'\n' +
|
||||
'<p>Σε παράθυρα διαλόγου με καρτέλες, το πρώτο κουμπί στο μενού καρτέλας λαμβάνει την εστίαση όταν ανοίγει το παράθυρο διαλόγου.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Μπορείτε να πλοηγηθείτε μεταξύ των αλληλεπιδραστικών στοιχείων αυτής της καρτέλα διαλόγου πιέζοντας τα πλήκτρα <strong>Tab</strong> ή\n' +
|
||||
' <strong>Shift+Tab</strong>.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Μπορείτε να κάνετε εναλλαγή σε άλλη καρτέλα του παραθύρου διαλόγου, μεταφέροντας την εστίαση στο μενού καρτέλας και πιέζοντας το κατάλληλο πλήκτρο <strong>βέλους</strong>\n' +
|
||||
' για να μετακινηθείτε κυκλικά στις διαθέσιμες καρτέλες.</p>\n');
|
||||
@@ -0,0 +1,93 @@
|
||||
tinymce.Resource.add('tinymce.html-i18n.help-keynav.en',
|
||||
'<h1>Begin keyboard navigation</h1>\n' +
|
||||
'\n' +
|
||||
'<dl>\n' +
|
||||
' <dt>Focus the Menu bar</dt>\n' +
|
||||
' <dd>Windows or Linux: Alt+F9</dd>\n' +
|
||||
' <dd>macOS: ⌥F9</dd>\n' +
|
||||
' <dt>Focus the Toolbar</dt>\n' +
|
||||
' <dd>Windows or Linux: Alt+F10</dd>\n' +
|
||||
' <dd>macOS: ⌥F10</dd>\n' +
|
||||
' <dt>Focus the footer</dt>\n' +
|
||||
' <dd>Windows or Linux: Alt+F11</dd>\n' +
|
||||
' <dd>macOS: ⌥F11</dd>\n' +
|
||||
' <dt>Focus the notification</dt>\n' +
|
||||
' <dd>Windows or Linux: Alt+F12</dd>\n' +
|
||||
' <dd>macOS: ⌥F12</dd>\n' +
|
||||
' <dt>Focus a contextual toolbar</dt>\n' +
|
||||
' <dd>Windows, Linux or macOS: Ctrl+F9</dd>\n' +
|
||||
'</dl>\n' +
|
||||
'\n' +
|
||||
'<p>Navigation will start at the first UI item, which will be highlighted, or underlined in the case of the first item in\n' +
|
||||
' the Footer element path.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>Navigate between UI sections</h1>\n' +
|
||||
'\n' +
|
||||
'<p>To move from one UI section to the next, press <strong>Tab</strong>.</p>\n' +
|
||||
'\n' +
|
||||
'<p>To move from one UI section to the previous, press <strong>Shift+Tab</strong>.</p>\n' +
|
||||
'\n' +
|
||||
'<p>The <strong>Tab</strong> order of these UI sections is:</p>\n' +
|
||||
'\n' +
|
||||
'<ol>\n' +
|
||||
' <li>Menu bar</li>\n' +
|
||||
' <li>Each toolbar group</li>\n' +
|
||||
' <li>Sidebar</li>\n' +
|
||||
' <li>Element path in the footer</li>\n' +
|
||||
' <li>Word count toggle button in the footer</li>\n' +
|
||||
' <li>Branding link in the footer</li>\n' +
|
||||
' <li>Editor resize handle in the footer</li>\n' +
|
||||
'</ol>\n' +
|
||||
'\n' +
|
||||
'<p>If a UI section is not present, it is skipped.</p>\n' +
|
||||
'\n' +
|
||||
'<p>If the footer has keyboard navigation focus, and there is no visible sidebar, pressing <strong>Shift+Tab</strong>\n' +
|
||||
' moves focus to the first toolbar group, not the last.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>Navigate within UI sections</h1>\n' +
|
||||
'\n' +
|
||||
'<p>To move from one UI element to the next, press the appropriate <strong>Arrow</strong> key.</p>\n' +
|
||||
'\n' +
|
||||
'<p>The <strong>Left</strong> and <strong>Right</strong> arrow keys</p>\n' +
|
||||
'\n' +
|
||||
'<ul>\n' +
|
||||
' <li>move between menus in the menu bar.</li>\n' +
|
||||
' <li>open a sub-menu in a menu.</li>\n' +
|
||||
' <li>move between buttons in a toolbar group.</li>\n' +
|
||||
' <li>move between items in the footer’s element path.</li>\n' +
|
||||
'</ul>\n' +
|
||||
'\n' +
|
||||
'<p>The <strong>Down</strong> and <strong>Up</strong> arrow keys</p>\n' +
|
||||
'\n' +
|
||||
'<ul>\n' +
|
||||
' <li>move between menu items in a menu.</li>\n' +
|
||||
' <li>move between items in a toolbar pop-up menu.</li>\n' +
|
||||
'</ul>\n' +
|
||||
'\n' +
|
||||
'<p><strong>Arrow</strong> keys cycle within the focused UI section.</p>\n' +
|
||||
'\n' +
|
||||
'<p>To close an open menu, an open sub-menu, or an open pop-up menu, press the <strong>Esc</strong> key.</p>\n' +
|
||||
'\n' +
|
||||
'<p>If the current focus is at the ‘top’ of a particular UI section, pressing the <strong>Esc</strong> key also exits\n' +
|
||||
' keyboard navigation entirely.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>Execute a menu item or toolbar button</h1>\n' +
|
||||
'\n' +
|
||||
'<p>When the desired menu item or toolbar button is highlighted, press <strong>Return</strong>, <strong>Enter</strong>,\n' +
|
||||
' or the <strong>Space bar</strong> to execute the item.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>Navigate non-tabbed dialogs</h1>\n' +
|
||||
'\n' +
|
||||
'<p>In non-tabbed dialogs, the first interactive component takes focus when the dialog opens.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Navigate between interactive dialog components by pressing <strong>Tab</strong> or <strong>Shift+Tab</strong>.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>Navigate tabbed dialogs</h1>\n' +
|
||||
'\n' +
|
||||
'<p>In tabbed dialogs, the first button in the tab menu takes focus when the dialog opens.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Navigate between interactive components of this dialog tab by pressing <strong>Tab</strong> or\n' +
|
||||
' <strong>Shift+Tab</strong>.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Switch to another dialog tab by giving the tab menu focus and then pressing the appropriate <strong>Arrow</strong>\n' +
|
||||
' key to cycle through the available tabs.</p>\n');
|
||||
@@ -0,0 +1,93 @@
|
||||
tinymce.Resource.add('tinymce.html-i18n.help-keynav.es',
|
||||
'<h1>Iniciar la navegación con el teclado</h1>\n' +
|
||||
'\n' +
|
||||
'<dl>\n' +
|
||||
' <dt>Enfocar la barra de menús</dt>\n' +
|
||||
' <dd>Windows o Linux: Alt+F9</dd>\n' +
|
||||
' <dd>macOS: ⌥F9</dd>\n' +
|
||||
' <dt>Enfocar la barra de herramientas</dt>\n' +
|
||||
' <dd>Windows o Linux: Alt+F10</dd>\n' +
|
||||
' <dd>macOS: ⌥F10</dd>\n' +
|
||||
' <dt>Enfocar el pie de página</dt>\n' +
|
||||
' <dd>Windows o Linux: Alt+F11</dd>\n' +
|
||||
' <dd>macOS: ⌥F11</dd>\n' +
|
||||
' <dt>Enfocar la notificación</dt>\n' +
|
||||
' <dd>Windows o Linux: Alt+F12</dd>\n' +
|
||||
' <dd>macOS: ⌥F12</dd>\n' +
|
||||
' <dt>Enfocar una barra de herramientas contextual</dt>\n' +
|
||||
' <dd>Windows, Linux o macOS: Ctrl+F9</dd>\n' +
|
||||
'</dl>\n' +
|
||||
'\n' +
|
||||
'<p>La navegación comenzará por el primer elemento de la interfaz de usuario (IU), de tal manera que se resaltará, o bien se subrayará si se trata del primer elemento de\n' +
|
||||
' la ruta de elemento del pie de página.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>Navegar entre las secciones de la IU</h1>\n' +
|
||||
'\n' +
|
||||
'<p>Para pasar de una sección de la IU a la siguiente, pulse la tecla <strong>Tab</strong>.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Para pasar de una sección de la IU a la anterior, pulse <strong>Mayús+Tab</strong>.</p>\n' +
|
||||
'\n' +
|
||||
'<p>El orden de <strong>tabulación</strong> de estas secciones de la IU es:</p>\n' +
|
||||
'\n' +
|
||||
'<ol>\n' +
|
||||
' <li>Barra de menús</li>\n' +
|
||||
' <li>Cada grupo de barra de herramientas</li>\n' +
|
||||
' <li>Barra lateral</li>\n' +
|
||||
' <li>Ruta del elemento en el pie de página</li>\n' +
|
||||
' <li>Botón de alternancia de recuento de palabras en el pie de página</li>\n' +
|
||||
' <li>Enlace de personalización de marca en el pie de página</li>\n' +
|
||||
' <li>Controlador de cambio de tamaño en el pie de página</li>\n' +
|
||||
'</ol>\n' +
|
||||
'\n' +
|
||||
'<p>Si una sección de la IU no está presente, esta se omite.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Si el pie de página tiene un enfoque de navegación con el teclado y no hay ninguna barra lateral visible, al pulsar <strong>Mayús+Tab</strong>,\n' +
|
||||
' el enfoque se moverá al primer grupo de barra de herramientas, en lugar de al último.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>Navegar dentro de las secciones de la IU</h1>\n' +
|
||||
'\n' +
|
||||
'<p>Para pasar de un elemento de la IU al siguiente, pulse la tecla de <strong>flecha</strong> correspondiente.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Las teclas de flecha <strong>izquierda</strong> y <strong>derecha</strong> permiten</p>\n' +
|
||||
'\n' +
|
||||
'<ul>\n' +
|
||||
' <li>desplazarse entre los menús de la barra de menús.</li>\n' +
|
||||
' <li>abrir el submenú de un menú.</li>\n' +
|
||||
' <li>desplazarse entre los botones de un grupo de barra de herramientas.</li>\n' +
|
||||
' <li>desplazarse entre los elementos de la ruta de elemento del pie de página.</li>\n' +
|
||||
'</ul>\n' +
|
||||
'\n' +
|
||||
'<p>Las teclas de flecha <strong>abajo</strong> y <strong>arriba</strong> permiten</p>\n' +
|
||||
'\n' +
|
||||
'<ul>\n' +
|
||||
' <li>desplazarse entre los elementos de menú de un menú.</li>\n' +
|
||||
' <li>desplazarse entre los elementos de un menú emergente de una barra de herramientas.</li>\n' +
|
||||
'</ul>\n' +
|
||||
'\n' +
|
||||
'<p>Las teclas de <strong>flecha</strong> van cambiando dentro de la sección de la IU enfocada.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Para cerrar un menú, un submenú o un menú emergente que estén abiertos, pulse la tecla <strong>Esc</strong>.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Si el enfoque actual se encuentra en la parte superior de una sección de la IU determinada, al pulsar la tecla <strong>Esc</strong> saldrá\n' +
|
||||
' de la navegación con el teclado por completo.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>Ejecutar un elemento de menú o un botón de barra de herramientas</h1>\n' +
|
||||
'\n' +
|
||||
'<p>Si el elemento de menú o el botón de barra de herramientas deseado está resaltado, pulse la tecla <strong>Retorno</strong> o <strong>Entrar</strong>,\n' +
|
||||
' o la <strong>barra espaciadora</strong> para ejecutar el elemento.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>Navegar por cuadros de diálogo sin pestañas</h1>\n' +
|
||||
'\n' +
|
||||
'<p>En los cuadros de diálogo sin pestañas, el primer componente interactivo se enfoca al abrirse el cuadro de diálogo.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Para navegar entre los componentes interactivos del cuadro de diálogo, pulse las teclas <strong>Tab</strong> o <strong>Mayús+Tab</strong>.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>Navegar por cuadros de diálogo con pestañas</h1>\n' +
|
||||
'\n' +
|
||||
'<p>En los cuadros de diálogo con pestañas, el primer botón del menú de pestaña se enfoca al abrirse el cuadro de diálogo.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Para navegar entre componentes interactivos de esta pestaña del cuadro de diálogo, pulse las teclas <strong>Tab</strong> o\n' +
|
||||
' <strong>Mayús+Tab</strong>.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Si desea cambiar a otra pestaña del cuadro de diálogo, enfoque el menú de pestañas y, a continuación, pulse la tecla de <strong>flecha</strong>\n' +
|
||||
' correspondiente para moverse por las pestañas disponibles.</p>\n');
|
||||
@@ -0,0 +1,93 @@
|
||||
tinymce.Resource.add('tinymce.html-i18n.help-keynav.eu',
|
||||
'<h1>Hasi teklatuaren nabigazioa</h1>\n' +
|
||||
'\n' +
|
||||
'<dl>\n' +
|
||||
' <dt>Fokuratu menu-barra</dt>\n' +
|
||||
' <dd>Windows edo Linux: Alt+F9</dd>\n' +
|
||||
' <dd>macOS: ⌥F9</dd>\n' +
|
||||
' <dt>Fokuratu tresna-barra</dt>\n' +
|
||||
' <dd>Windows edo Linux: Alt+F10</dd>\n' +
|
||||
' <dd>macOS: ⌥F10</dd>\n' +
|
||||
' <dt>Fokuratu orri-oina</dt>\n' +
|
||||
' <dd>Windows edo Linux: Alt+F11</dd>\n' +
|
||||
' <dd>macOS: ⌥F11</dd>\n' +
|
||||
' <dt>Fokuratu jakinarazpena</dt>\n' +
|
||||
' <dd>Windows edo Linux: Alt+F12</dd>\n' +
|
||||
' <dd>macOS: ⌥F12</dd>\n' +
|
||||
' <dt>Fokuratu testuinguruaren tresna-barra</dt>\n' +
|
||||
' <dd>Windows, Linux edo macOS: Ktrl+F9</dd>\n' +
|
||||
'</dl>\n' +
|
||||
'\n' +
|
||||
'<p>Nabigazioa EIko lehen elementuan hasiko da: elementu hori nabarmendu egingo da, edo azpimarratu lehen elementua bada\n' +
|
||||
' orri-oineko elementuaren bidea.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>Nabigatu EIko atalen artean</h1>\n' +
|
||||
'\n' +
|
||||
'<p>EIko atal batetik hurrengora mugitzeko, sakatu <strong>Tabuladorea</strong>.</p>\n' +
|
||||
'\n' +
|
||||
'<p>EIko atal batetik aurrekora mugitzeko, sakatu <strong>Maius+Tabuladorea</strong>.</p>\n' +
|
||||
'\n' +
|
||||
'<p>EIko atal hauen <strong>Tabuladorea</strong> da:</p>\n' +
|
||||
'\n' +
|
||||
'<ol>\n' +
|
||||
' <li>Menu-barra</li>\n' +
|
||||
' <li>Tresna-barraren talde bakoitza</li>\n' +
|
||||
' <li>Alboko barra</li>\n' +
|
||||
' <li>Orri-oineko elementuaren bidea</li>\n' +
|
||||
' <li>Orri-oneko urrats-kontaketa txandakatzeko botoia</li>\n' +
|
||||
' <li>Orri-oineko marken esteka</li>\n' +
|
||||
' <li>Orri-oineko editorearen tamaina aldatzeko heldulekua</li>\n' +
|
||||
'</ol>\n' +
|
||||
'\n' +
|
||||
'<p>EIko atal bat ez badago, saltatu egin da.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Orri-oinak teklatuaren nabigazioa fokuratuta badago, eta alboko barra ikusgai ez badago, <strong>Maius+Tabuladorea</strong> sakatuz gero,\n' +
|
||||
' fokua tresna-barrako lehen taldera eramaten da, ez azkenera.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>Nabigatu EIko atalen barruan</h1>\n' +
|
||||
'\n' +
|
||||
'<p>EIko elementu batetik hurrengora mugitzeko, sakatu dagokion <strong>Gezia</strong> tekla.</p>\n' +
|
||||
'\n' +
|
||||
'<p><strong>Ezkerrera</strong> eta <strong>Eskuinera</strong> gezi-teklak</p>\n' +
|
||||
'\n' +
|
||||
'<ul>\n' +
|
||||
' <li>menu-barrako menuen artean mugitzen da.</li>\n' +
|
||||
' <li>ireki azpimenu bat menuan.</li>\n' +
|
||||
' <li>mugitu botoi batetik bestera tresna-barren talde batean.</li>\n' +
|
||||
' <li>mugitu orri-oineko elementuaren bideko elementu batetik bestera.</li>\n' +
|
||||
'</ul>\n' +
|
||||
'\n' +
|
||||
'<p><strong>Gora</strong> eta <strong>Behera</strong> gezi-teklak</p>\n' +
|
||||
'\n' +
|
||||
'<ul>\n' +
|
||||
' <li>mugitu menu bateko menu-elementuen artean.</li>\n' +
|
||||
' <li>mugitu tresna-barrako menu gainerakor bateko menu-elementuen artean.</li>\n' +
|
||||
'</ul>\n' +
|
||||
'\n' +
|
||||
'<p><strong>Gezia</strong> teklen zikloa nabarmendutako EI atalen barruan.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Irekitako menu bat ixteko, ireki azpimenua, edo ireki menu gainerakorra, sakatu <strong>Ihes</strong> tekla.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Une horretan fokuratzea EIko atal jakin baten "goialdean" badago, <strong>Ihes</strong> tekla sakatuz gero\n' +
|
||||
' teklatuaren nabigaziotik irtengo zara.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>Exekutatu menuko elementu bat edo tresna-barrako botoi bat</h1>\n' +
|
||||
'\n' +
|
||||
'<p>Nahi den menuaren elementua edo tresna-barraren botoia nabarmenduta dagoenean, sakatu <strong>Itzuli</strong>, <strong>Sartu</strong>\n' +
|
||||
' edo <strong>Zuriune-barra</strong> elementua exekutatzeko.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>Nabigatu fitxarik gabeko elkarrizketak</h1>\n' +
|
||||
'\n' +
|
||||
'<p>Fitxarik gabeko elkarrizketetan, lehen osagai interaktiboa fokuratzen da elkarrizketa irekitzen denean.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Nabigatu elkarrizketa interaktiboko osagai batetik bestera <strong>Tabuladorea</strong> edo <strong>Maius+Tabuladorea</strong> sakatuta.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>Nabigatu fitxadun elkarrizketak</h1>\n' +
|
||||
'\n' +
|
||||
'<p>Fitxadun elkarrizketetan, fitxa-menuko lehen botoia fokuratzen da elkarrizketa irekitzen denean.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Nabigatu elkarrizketa-fitxa honen interaktiboko osagai batetik bestera <strong>Tabuladorea</strong> edo\n' +
|
||||
' <strong>Maius+Tabuladorea</strong> sakatuta.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Aldatu beste elkarrizketa-fitxa batera fitxa-menua fokuratu eta dagokion <strong>Gezia</strong>\n' +
|
||||
' tekla sakatzeko, erabilgarri dauden fitxa batetik bestera txandakatzeko.</p>\n');
|
||||
@@ -0,0 +1,93 @@
|
||||
tinymce.Resource.add('tinymce.html-i18n.help-keynav.fa',
|
||||
'<h1>شروع پیمایش صفحهکلید</h1>\n' +
|
||||
'\n' +
|
||||
'<dl>\n' +
|
||||
' <dt>تمرکز بر نوار منو</dt>\n' +
|
||||
' <dd>Windows یا Linux:: Alt+F9</dd>\n' +
|
||||
' <dd>macOS: ⌥F9</dd>\n' +
|
||||
' <dt>تمرکز بر نوار ابزار</dt>\n' +
|
||||
' <dd>Windows یا Linux: Alt+F10</dd>\n' +
|
||||
' <dd>macOS: ⌥F10</dd>\n' +
|
||||
' <dt>تمرکز بر پانویس</dt>\n' +
|
||||
' <dd>Windows یا Linux: Alt+F11</dd>\n' +
|
||||
' <dd>macOS: ⌥F11</dd>\n' +
|
||||
' <dt>تمرکز اعلان</dt>\n' +
|
||||
' <dd>ویندوز یا لینوکس: Alt+F12</dd>\n' +
|
||||
' <dd>macOS: ⌥F12</dd>\n' +
|
||||
' <dt>تمرکز بر نوار ابزار بافتاری</dt>\n' +
|
||||
' <dd>Windows ،Linux یا macOS: Ctrl+F9</dd>\n' +
|
||||
'</dl>\n' +
|
||||
'\n' +
|
||||
'<p>پیمایش در اولین مورد رابط کاربری شروع میشود و درخصوص اولین مورد در\n' +
|
||||
' مسیر عنصر پانویس، برجسته یا زیرخطدار میشود.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>پیمایش بین بخشهای رابط کاربری</h1>\n' +
|
||||
'\n' +
|
||||
'<p>برای جابجایی از یک بخش رابط کاربری به بخش بعدی، <strong>Tab</strong> را فشار دهید.</p>\n' +
|
||||
'\n' +
|
||||
'<p>برای جابجایی از یک بخش رابط کاربری به بخش قبلی، <strong>Shift+Tab</strong> را فشار دهید.</p>\n' +
|
||||
'\n' +
|
||||
'<p>ترتیب <strong>Tab</strong> این بخشهای رابط کاربری عبارتند از:</p>\n' +
|
||||
'\n' +
|
||||
'<ol>\n' +
|
||||
' <li>نوار منو</li>\n' +
|
||||
' <li>هر گروه نوار ابزار</li>\n' +
|
||||
' <li>نوار کناری</li>\n' +
|
||||
' <li>مسیر عنصر در پانویس</li>\n' +
|
||||
' <li>دکمه تغییر وضعیت تعداد کلمات در پانویس</li>\n' +
|
||||
' <li>پیوند نمانامسازی در پانویس</li>\n' +
|
||||
' <li>دسته تغییر اندازه ویرایشگر در پانویس</li>\n' +
|
||||
'</ol>\n' +
|
||||
'\n' +
|
||||
'<p>اگر بخشی از رابط کاربری موجود نباشد، رد میشود.</p>\n' +
|
||||
'\n' +
|
||||
'<p>اگر پانویس دارای تمرکز بر پیمایش صفحهکلید باشد، و نوار کناری قابلمشاهده وجود ندارد، فشردن <strong>Shift+Tab</strong>\n' +
|
||||
' تمرکز را به گروه نوار ابزار اول میبرد، نه آخر.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>پیمایش در بخشهای رابط کاربری</h1>\n' +
|
||||
'\n' +
|
||||
'<p>برای جابجایی از یک عنصر رابط کاربری به بعدی، کلید <strong>جهتنمای</strong> مناسب را فشار دهید.</p>\n' +
|
||||
'\n' +
|
||||
'<p>کلیدهای جهتنمای <strong>چپ</strong> و <strong>راست</strong></p>\n' +
|
||||
'\n' +
|
||||
'<ul>\n' +
|
||||
' <li>جابجایی بین منوها در نوار منو.</li>\n' +
|
||||
' <li>باز کردن منوی فرعی در یک منو.</li>\n' +
|
||||
' <li>جابجایی بین دکمهها در یک گروه نوار ابزار.</li>\n' +
|
||||
' <li>جابجایی بین موارد در مسیر عنصر پانویس.</li>\n' +
|
||||
'</ul>\n' +
|
||||
'\n' +
|
||||
'<p>کلیدهای جهتنمای <strong>پایین</strong> و <strong>بالا</strong></p>\n' +
|
||||
'\n' +
|
||||
'<ul>\n' +
|
||||
' <li>جابجایی بین موارد منو در یک منو.</li>\n' +
|
||||
' <li>جابجایی بین موارد در یک منوی بازشوی نوار ابزار.</li>\n' +
|
||||
'</ul>\n' +
|
||||
'\n' +
|
||||
'<p>کلیدهای<strong>جهتنما</strong> در بخش رابط کاربری متمرکز میچرخند.</p>\n' +
|
||||
'\n' +
|
||||
'<p>برای بستن یک منوی باز، یک منوی فرعی باز، یا یک منوی بازشوی باز، کلید <strong>Esc</strong> را فشار دهید.</p>\n' +
|
||||
'\n' +
|
||||
'<p>اگر تمرکز فعلی در «بالای» یک بخش رابط کاربری خاص است، فشردن کلید <strong>Esc</strong> نیز موجب\n' +
|
||||
' خروج کامل از پیمایش صفحهکلید میشود.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>اجرای یک مورد منو یا دکمه نوار ابزار</h1>\n' +
|
||||
'\n' +
|
||||
'<p>وقتی مورد منو یا دکمه نوار ابزار مورد نظر هایلایت شد، دکمه <strong>بازگشت</strong>، <strong>Enter</strong>،\n' +
|
||||
' یا <strong>نوار Space</strong> را فشار دهید تا مورد را اجرا کنید.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>پیمایش در کادرهای گفتگوی بدون زبانه</h1>\n' +
|
||||
'\n' +
|
||||
'<p>در کادرهای گفتگوی بدون زبانه، وقتی کادر گفتگو باز میشود، اولین جزء تعاملی متمرکز میشود.</p>\n' +
|
||||
'\n' +
|
||||
'<p>با فشردن <strong>Tab</strong> یا <strong>Shift+Tab</strong>، بین اجزای کادر گفتگوی تعاملی پیمایش کنید.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>پیمایش کادرهای گفتگوی زبانهدار</h1>\n' +
|
||||
'\n' +
|
||||
'<p>در کادرهای گفتگوی زبانهدار، وقتی کادر گفتگو باز میشود، اولین دکمه در منوی زبانه متمرکز میشود.</p>\n' +
|
||||
'\n' +
|
||||
'<p>با فشردن <strong>Tab</strong> یا\n' +
|
||||
' <strong>Shift+Tab</strong>، بین اجزای تعاملی این زبانه کادر گفتگو پیمایش کنید.</p>\n' +
|
||||
'\n' +
|
||||
'<p>با دادن تمرکز به منوی زبانه و سپس فشار دادن کلید <strong>جهتنمای</strong>\n' +
|
||||
' مناسب برای چرخش میان زبانههای موجود، به زبانه کادر گفتگوی دیگری بروید.</p>\n');
|
||||
@@ -0,0 +1,93 @@
|
||||
tinymce.Resource.add('tinymce.html-i18n.help-keynav.fi',
|
||||
'<h1>Näppäimistönavigoinnin aloittaminen</h1>\n' +
|
||||
'\n' +
|
||||
'<dl>\n' +
|
||||
' <dt>Siirrä kohdistus valikkopalkkiin</dt>\n' +
|
||||
' <dd>Windows tai Linux: Alt+F9</dd>\n' +
|
||||
' <dd>macOS: ⌥F9</dd>\n' +
|
||||
' <dt>Siirrä kohdistus työkalupalkkiin</dt>\n' +
|
||||
' <dd>Windows tai Linux: Alt+F10</dd>\n' +
|
||||
' <dd>macOS: ⌥F10</dd>\n' +
|
||||
' <dt>Siirrä kohdistus alatunnisteeseen</dt>\n' +
|
||||
' <dd>Windows tai Linux: Alt+F11</dd>\n' +
|
||||
' <dd>macOS: ⌥F11</dd>\n' +
|
||||
' <dt>Keskitä ilmoitukseen</dt>\n' +
|
||||
' <dd>Windows ja Linux: Alt + F12</dd>\n' +
|
||||
' <dd>macOS: ⌥F12</dd>\n' +
|
||||
' <dt>Siirrä kohdistus kontekstuaaliseen työkalupalkkiin</dt>\n' +
|
||||
' <dd>Windows, Linux tai macOS: Ctrl+F9</dd>\n' +
|
||||
'</dl>\n' +
|
||||
'\n' +
|
||||
'<p>Navigointi aloitetaan ensimmäisestä käyttöliittymän kohteesta, joka joko korostetaan tai alleviivataan, jos\n' +
|
||||
' kyseessä on Alatunniste-elementin polun ensimmäinen kohde.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>Käyttöliittymän eri osien välillä navigointi</h1>\n' +
|
||||
'\n' +
|
||||
'<p>Paina <strong>sarkainnäppäintä</strong> siirtyäksesi käyttöliittymän osasta seuraavaan.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Jos haluat siirtyä edelliseen käyttöliittymän osaan, paina <strong>Shift+sarkainnäppäin</strong>.</p>\n' +
|
||||
'\n' +
|
||||
'<p><strong>Sarkainnäppäin</strong> siirtää sinua näissä käyttöliittymän osissa tässä järjestyksessä:</p>\n' +
|
||||
'\n' +
|
||||
'<ol>\n' +
|
||||
' <li>Valikkopalkki</li>\n' +
|
||||
' <li>Työkalupalkin ryhmät</li>\n' +
|
||||
' <li>Sivupalkki</li>\n' +
|
||||
' <li>Elementin polku alatunnisteessa</li>\n' +
|
||||
' <li>Sanalaskurin vaihtopainike alatunnisteessa</li>\n' +
|
||||
' <li>Brändäyslinkki alatunnisteessa</li>\n' +
|
||||
' <li>Editorin koon muuttamisen kahva alatunnisteessa</li>\n' +
|
||||
'</ol>\n' +
|
||||
'\n' +
|
||||
'<p>Jos jotakin käyttöliittymän osaa ei ole, se ohitetaan.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Jos kohdistus on siirretty alatunnisteeseen näppäimistönavigoinnilla eikä sivupalkkia ole näkyvissä, <strong>Shift+sarkainnäppäin</strong>\n' +
|
||||
' siirtää kohdistuksen työkalupalkin ensimmäiseen ryhmään, eikä viimeiseen.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>Käyttöliittymän eri osien sisällä navigointi</h1>\n' +
|
||||
'\n' +
|
||||
'<p>Paina <strong>nuolinäppäimiä</strong> siirtyäksesi käyttöliittymäelementistä seuraavaan.</p>\n' +
|
||||
'\n' +
|
||||
'<p><strong>Vasen</strong>- ja <strong>Oikea</strong>-nuolinäppäimet</p>\n' +
|
||||
'\n' +
|
||||
'<ul>\n' +
|
||||
' <li>siirtävät sinua valikkopalkin valikoiden välillä.</li>\n' +
|
||||
' <li>avaavat valikon alavalikon.</li>\n' +
|
||||
' <li>siirtävät sinua työkalupalkin ryhmän painikkeiden välillä.</li>\n' +
|
||||
' <li>siirtävät sinua kohteiden välillä alatunnisteen elementin polussa.</li>\n' +
|
||||
'</ul>\n' +
|
||||
'\n' +
|
||||
'<p><strong>Alas</strong>- ja <strong>Ylös</strong>-nuolinäppäimet</p>\n' +
|
||||
'\n' +
|
||||
'<ul>\n' +
|
||||
' <li>siirtävät sinua valikon valikkokohteiden välillä.</li>\n' +
|
||||
' <li>siirtävät sinua työkalupalkin ponnahdusvalikon kohteiden välillä.</li>\n' +
|
||||
'</ul>\n' +
|
||||
'\n' +
|
||||
'<p><strong>Nuolinäppäimet</strong> siirtävät sinua käyttöliittymän korostetun osan sisällä syklissä.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Paina <strong>Esc</strong>-näppäintä sulkeaksesi avoimen valikon, avataksesi alavalikon tai avataksesi ponnahdusvalikon.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Jos kohdistus on käyttöliittymän tietyn osion ylälaidassa, <strong>Esc</strong>-näppäimen painaminen\n' +
|
||||
' poistuu myös näppäimistönavigoinnista kokonaan.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>Suorita valikkokohde tai työkalupalkin painike</h1>\n' +
|
||||
'\n' +
|
||||
'<p>Kun haluamasi valikkokohde tai työkalupalkin painike on korostettuna, paina <strong>Return</strong>-, <strong>Enter</strong>-\n' +
|
||||
' tai <strong>välilyöntinäppäintä</strong> suorittaaksesi kohteen.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>Välilehdittömissä valintaikkunoissa navigointi</h1>\n' +
|
||||
'\n' +
|
||||
'<p>Kun välilehdetön valintaikkuna avautuu, kohdistus siirtyy sen ensimmäiseen interaktiiviseen komponenttiin.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Voit siirtyä valintaikkunan interaktiivisten komponenttien välillä painamalla <strong>sarkainnäppäintä</strong> tai <strong>Shift+sarkainnäppäin</strong>.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>Välilehdellisissä valintaikkunoissa navigointi</h1>\n' +
|
||||
'\n' +
|
||||
'<p>Kun välilehdellinen valintaikkuna avautuu, kohdistus siirtyy välilehtivalikon ensimmäiseen painikkeeseen.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Voit siirtyä valintaikkunan välilehden interaktiivisen komponenttien välillä painamalla <strong>sarkainnäppäintä</strong> tai\n' +
|
||||
' <strong>Shift+sarkainnäppäin</strong>.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Voit siirtyä valintaikkunan toiseen välilehteen siirtämällä kohdistuksen välilehtivalikkoon ja painamalla sopivaa <strong>nuolinäppäintä</strong>\n' +
|
||||
' siirtyäksesi käytettävissä olevien välilehtien välillä syklissä.</p>\n');
|
||||
@@ -0,0 +1,93 @@
|
||||
tinymce.Resource.add('tinymce.html-i18n.help-keynav.fr_FR',
|
||||
'<h1>Débuter la navigation au clavier</h1>\n' +
|
||||
'\n' +
|
||||
'<dl>\n' +
|
||||
' <dt>Cibler la barre du menu</dt>\n' +
|
||||
' <dd>Windows ou Linux : Alt+F9</dd>\n' +
|
||||
' <dd>macOS : ⌥F9</dd>\n' +
|
||||
" <dt>Cibler la barre d'outils</dt>\n" +
|
||||
' <dd>Windows ou Linux : Alt+F10</dd>\n' +
|
||||
' <dd>macOS : ⌥F10</dd>\n' +
|
||||
' <dt>Cibler le pied de page</dt>\n' +
|
||||
' <dd>Windows ou Linux : Alt+F11</dd>\n' +
|
||||
' <dd>macOS : ⌥F11</dd>\n' +
|
||||
' <dt>Cibler la notification</dt>\n' +
|
||||
' <dd>Windows ou Linux : Alt+F12</dd>\n' +
|
||||
' <dd>macOS : ⌥F12</dd>\n' +
|
||||
" <dt>Cibler une barre d'outils contextuelle</dt>\n" +
|
||||
' <dd>Windows, Linux ou macOS : Ctrl+F9</dd>\n' +
|
||||
'</dl>\n' +
|
||||
'\n' +
|
||||
"<p>La navigation débutera sur le premier élément de l'interface utilisateur, qui sera mis en surbrillance ou bien souligné dans le cas du premier élément du\n" +
|
||||
" chemin d'éléments du pied de page.</p>\n" +
|
||||
'\n' +
|
||||
"<h1>Naviguer entre les sections de l'interface utilisateur</h1>\n" +
|
||||
'\n' +
|
||||
"<p>Pour passer d'une section de l'interface utilisateur à la suivante, appuyez sur <strong>Tabulation</strong>.</p>\n" +
|
||||
'\n' +
|
||||
"<p>Pour passer d'une section de l'interface utilisateur à la précédente, appuyez sur <strong>Maj+Tabulation</strong>.</p>\n" +
|
||||
'\n' +
|
||||
"<p>L'ordre de <strong>Tabulation</strong> de ces sections de l'interface utilisateur est le suivant :</p>\n" +
|
||||
'\n' +
|
||||
'<ol>\n' +
|
||||
' <li>Barre du menu</li>\n' +
|
||||
" <li>Chaque groupe de barres d'outils</li>\n" +
|
||||
' <li>Barre latérale</li>\n' +
|
||||
" <li>Chemin d'éléments du pied de page</li>\n" +
|
||||
" <li>Bouton d'activation du compteur de mots dans le pied de page</li>\n" +
|
||||
' <li>Lien de marque dans le pied de page</li>\n' +
|
||||
" <li>Poignée de redimensionnement de l'éditeur dans le pied de page</li>\n" +
|
||||
'</ol>\n' +
|
||||
'\n' +
|
||||
"<p>Si une section de l'interface utilisateur n'est pas présente, elle sera ignorée.</p>\n" +
|
||||
'\n' +
|
||||
"<p>Si le pied de page comporte un ciblage par navigation au clavier et qu'il n'y a aucune barre latérale visible, appuyer sur <strong>Maj+Tabulation</strong>\n" +
|
||||
" déplace le ciblage vers le premier groupe de barres d'outils et non le dernier.</p>\n" +
|
||||
'\n' +
|
||||
"<h1>Naviguer au sein des sections de l'interface utilisateur</h1>\n" +
|
||||
'\n' +
|
||||
"<p>Pour passer d'un élément de l'interface utilisateur au suivant, appuyez sur la <strong>Flèche</strong> appropriée.</p>\n" +
|
||||
'\n' +
|
||||
'<p>Les touches fléchées <strong>Gauche</strong> et <strong>Droite</strong></p>\n' +
|
||||
'\n' +
|
||||
'<ul>\n' +
|
||||
' <li>se déplacent entre les menus de la barre des menus.</li>\n' +
|
||||
" <li>ouvrent un sous-menu au sein d'un menu.</li>\n" +
|
||||
" <li>se déplacent entre les boutons d'un groupe de barres d'outils.</li>\n" +
|
||||
" <li>se déplacent entre les éléments du chemin d'éléments du pied de page.</li>\n" +
|
||||
'</ul>\n' +
|
||||
'\n' +
|
||||
'<p>Les touches fléchées <strong>Bas</strong> et <strong>Haut</strong></p>\n' +
|
||||
'\n' +
|
||||
'<ul>\n' +
|
||||
" <li>se déplacent entre les éléments de menu au sein d'un menu.</li>\n" +
|
||||
" <li>se déplacent entre les éléments au sein d'un menu contextuel de barre d'outils.</li>\n" +
|
||||
'</ul>\n' +
|
||||
'\n' +
|
||||
"<p>Les <strong>Flèches</strong> parcourent la section de l'interface utilisateur ciblée.</p>\n" +
|
||||
'\n' +
|
||||
'<p>Pour fermer un menu ouvert, un sous-menu ouvert ou un menu contextuel ouvert, appuyez sur <strong>Echap</strong>.</p>\n' +
|
||||
'\n' +
|
||||
"<p>Si l'actuel ciblage se trouve en « haut » d'une section spécifique de l'interface utilisateur, appuyer sur <strong>Echap</strong> permet également de quitter\n" +
|
||||
' entièrement la navigation au clavier.</p>\n' +
|
||||
'\n' +
|
||||
"<h1>Exécuter un élément de menu ou un bouton de barre d'outils</h1>\n" +
|
||||
'\n' +
|
||||
"<p>Lorsque l'élément de menu ou le bouton de barre d'outils désiré est mis en surbrillance, appuyez sur la touche <strong>Retour arrière</strong>, <strong>Entrée</strong>\n" +
|
||||
" ou la <strong>Barre d'espace</strong> pour exécuter l'élément.</p>\n" +
|
||||
'\n' +
|
||||
'<h1>Naviguer au sein de dialogues sans onglets</h1>\n' +
|
||||
'\n' +
|
||||
"<p>Dans les dialogues sans onglets, le premier composant interactif est ciblé lorsque le dialogue s'ouvre.</p>\n" +
|
||||
'\n' +
|
||||
'<p>Naviguez entre les composants du dialogue interactif en appuyant sur <strong>Tabulation</strong> ou <strong>Maj+Tabulation</strong>.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>Naviguer au sein de dialogues avec onglets</h1>\n' +
|
||||
'\n' +
|
||||
"<p>Dans les dialogues avec onglets, le premier bouton du menu de l'onglet est ciblé lorsque le dialogue s'ouvre.</p>\n" +
|
||||
'\n' +
|
||||
'<p>Naviguez entre les composants interactifs de cet onglet de dialogue en appuyant sur <strong>Tabulation</strong> ou\n' +
|
||||
' <strong>Maj+Tabulation</strong>.</p>\n' +
|
||||
'\n' +
|
||||
"<p>Passez à un autre onglet de dialogue en ciblant le menu de l'onglet et en appuyant sur la <strong>Flèche</strong>\n" +
|
||||
' appropriée pour parcourir les onglets disponibles.</p>\n');
|
||||
@@ -0,0 +1,93 @@
|
||||
tinymce.Resource.add('tinymce.html-i18n.help-keynav.he_IL',
|
||||
'<h1>התחל ניווט במקלדת</h1>\n' +
|
||||
'\n' +
|
||||
'<dl>\n' +
|
||||
' <dt>התמקד בשורת התפריטים</dt>\n' +
|
||||
' <dd>Windows או Linux: Alt+F9</dd>\n' +
|
||||
' <dd>macOS: ⌥F9</dd>\n' +
|
||||
' <dt>העבר מיקוד לסרגל הכלים</dt>\n' +
|
||||
' <dd>Windows או Linux: Alt+F10</dd>\n' +
|
||||
' <dd>macOS: ⌥F10</dd>\n' +
|
||||
' <dt>העבר מיקוד לכותרת התחתונה</dt>\n' +
|
||||
' <dd>Windows או Linux: Alt+F11</dd>\n' +
|
||||
' <dd>macOS: ⌥F11</dd>\n' +
|
||||
' <dt>העבר מיקוד להודעה</dt>\n' +
|
||||
' <dd>Windows או Linux: Alt+F12</dd>\n' +
|
||||
' <dd>macOS: ⌥F12</dd>\n' +
|
||||
' <dt>העבר מיקוד לסרגל כלים הקשרי</dt>\n' +
|
||||
' <dd>Windows, Linux או macOS: Ctrl+F9</dd>\n' +
|
||||
'</dl>\n' +
|
||||
'\n' +
|
||||
'<p>הניווט יתחיל ברכיב הראשון במשך, שיודגש או שיהיה מתחתיו קו תחתון במקרה של הפריט הראשון\n' +
|
||||
' הנתיב של רכיב הכותרת התחתונה.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>עבור בין מקטעים במסך</h1>\n' +
|
||||
'\n' +
|
||||
'<p>כדי לעבור בין המקטעים במסך, הקש <strong>Tab</strong>.</p>\n' +
|
||||
'\n' +
|
||||
'<p>כדי לעבור למקטע הקודם במסך, הקש <strong>Shift+Tab</strong>.</p>\n' +
|
||||
'\n' +
|
||||
'<p>הסדר מבחינת מקש <strong>Tab</strong> של הרכיבים במסך:</p>\n' +
|
||||
'\n' +
|
||||
'<ol>\n' +
|
||||
' <li>שורת התפריטים</li>\n' +
|
||||
' <li>כל קבוצה בסרגל הכלים</li>\n' +
|
||||
' <li>הסרגל הצידי</li>\n' +
|
||||
' <li>נתיב של רכיב בכותרת התחתונה</li>\n' +
|
||||
' <li>לחצן לספירת מילים בכותרת התחתונה</li>\n' +
|
||||
' <li>קישור של המותג בכותרת התחתונה</li>\n' +
|
||||
' <li>ידית לשינוי גודל עבור העורך בכותרת התחתונה</li>\n' +
|
||||
'</ol>\n' +
|
||||
'\n' +
|
||||
'<p>אם רכיב כלשהו במסך לא מופיע, המערכת תדלג עליו.</p>\n' +
|
||||
'\n' +
|
||||
'<p>אם בכותרת התחתונה יש מיקוד של ניווט במקלדת, ולא מופיע סרגל בצד, יש להקיש <strong>Shift+Tab</strong>\n' +
|
||||
' מעביר את המיקוד לקבוצה הראשונה בסרגל הכלים, לא האחרונה.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>עבור בתוך מקטעים במסך</h1>\n' +
|
||||
'\n' +
|
||||
'<p>כדי לעבור מרכיב אחד לרכיב אחר במסך, הקש על מקש <strong>החץ</strong> המתאים.</p>\n' +
|
||||
'\n' +
|
||||
'<p>מקשי החיצים <strong>שמאלה</strong> ו<strong>ימינה</strong></p>\n' +
|
||||
'\n' +
|
||||
'<ul>\n' +
|
||||
' <li>עבור בין תפריטים בשורת התפריטים.</li>\n' +
|
||||
' <li>פתח תפריט משני בתפריט.</li>\n' +
|
||||
' <li>עבור בין לחצנים בקבוצה בסרגל הכלים.</li>\n' +
|
||||
' <li>עבור בין פריטים ברכיב בכותרת התחתונה.</li>\n' +
|
||||
'</ul>\n' +
|
||||
'\n' +
|
||||
'<p>מקשי החיצים <strong>למטה</strong> ו<strong>למעלה</strong></p>\n' +
|
||||
'\n' +
|
||||
'<ul>\n' +
|
||||
' <li>עבור בין פריטים בתפריט.</li>\n' +
|
||||
' <li>עבור בין פריטים בחלון הקובץ של סרגל הכלים.</li>\n' +
|
||||
'</ul>\n' +
|
||||
'\n' +
|
||||
'<p>מקשי <strong>החצים</strong> משתנים בתוך המקטע במסך שעליו נמצא המיקוד.</p>\n' +
|
||||
'\n' +
|
||||
'<p>כדי לסגור תפריט פתוח, תפריט משני פתוח או חלון קופץ, הקש על <strong>Esc</strong>.</p>\n' +
|
||||
'\n' +
|
||||
"<p>אם המיקוד הוא על החלק 'העליון' של מקטע מסוים במסך, הקשה על <strong>Esc</strong> מביאה גם ליציאה\n" +
|
||||
' מהניווט במקלדת לחלוטין.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>הפעל פריט בתפריט או לחצן בסרגל הכלים</h1>\n' +
|
||||
'\n' +
|
||||
'<p>כאשר הפריט הרצוי בתפריט או הלחצן בסרגל הכלים מודגשים, הקש על <strong>Return</strong>, <strong>Enter</strong>,\n' +
|
||||
' או על <strong>מקש הרווח</strong> כדי להפעיל את הפריט.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>ניווט בחלונות דו-שיח בלי כרטיסיות</h1>\n' +
|
||||
'\n' +
|
||||
'<p>בחלונות דו-שיח בלי כרטיסיות, הרכיב האינטראקטיבי הראשון מקבל את המיקוד כאשר החלון נפתח.</p>\n' +
|
||||
'\n' +
|
||||
'<p>עבור בין רכיבים אינטראקטיביים בחלון על ידי הקשה על <strong>Tab</strong> או <strong>Shift+Tab</strong>.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>ניווט בחלונות דו-שיח עם כרטיסיות</h1>\n' +
|
||||
'\n' +
|
||||
'<p>בחלונות דו-שיח עם כרטיסיות, הלחצן הראשון בתפריט מקבל את המיקוד כאשר החלון נפתח.</p>\n' +
|
||||
'\n' +
|
||||
'<p>עבור בין רכיבים אינטראקטיביים בחלון על ידי הקשה על <strong>Tab</strong> או\n' +
|
||||
' <strong>Shift+Tab</strong>.</p>\n' +
|
||||
'\n' +
|
||||
'<p>עבור לכרטיסיה אחרת בחלון על ידי העברת המיקוד לתפריט הכרטיסיות והקשה על <strong>החץ</strong>המתאים\n' +
|
||||
' כדי לעבור בין הכרטיסיות הזמינות.</p>\n');
|
||||
@@ -0,0 +1,93 @@
|
||||
tinymce.Resource.add('tinymce.html-i18n.help-keynav.hi',
|
||||
'<h1>कीबोर्ड नेविगेशन शुरू करें</h1>\n' +
|
||||
'\n' +
|
||||
'<dl>\n' +
|
||||
' <dt>मेन्यू बार पर फ़ोकस करें</dt>\n' +
|
||||
' <dd>Windows या Linux: Alt+F9</dd>\n' +
|
||||
' <dd>macOS: ⌥F9</dd>\n' +
|
||||
' <dt>टूलबार पर फ़ोकस करें</dt>\n' +
|
||||
' <dd>Windows या Linux: Alt+F10</dd>\n' +
|
||||
' <dd>macOS: ⌥F10</dd>\n' +
|
||||
' <dt>फ़ुटर पर फ़ोकस करें</dt>\n' +
|
||||
' <dd>Windows या Linux: Alt+F11</dd>\n' +
|
||||
' <dd>macOS: ⌥F11</dd>\n' +
|
||||
' <dt>नोटिफ़िकेशन फ़ोकस</dt>\n' +
|
||||
' <dd>Windows या Linux: Alt+F12</dd>\n' +
|
||||
' <dd>macOS: ⌥F12</dd>\n' +
|
||||
' <dt>प्रासंगिक टूलबार पर फ़ोकस करें</dt>\n' +
|
||||
' <dd>Windows, Linux या macOS: Ctrl+F9</dd>\n' +
|
||||
'</dl>\n' +
|
||||
'\n' +
|
||||
'<p>नेविगेशन पहले UI आइटम पर शुरू होगा, जिसे हाइलाइट किया जाएगा या पहले आइटम के मामले में फ़ुटर तत्व पथ में\n' +
|
||||
' रेखांकित किया जाएगा।</p>\n' +
|
||||
'\n' +
|
||||
'<h1>UI सेक्शन के बीच नेविगेट करें</h1>\n' +
|
||||
'\n' +
|
||||
'<p>एक UI सेक्शन से दूसरे सेक्शन में जाने के लिए, <strong>Tab</strong> दबाएं।</p>\n' +
|
||||
'\n' +
|
||||
'<p>एक UI सेक्शन से पिछले सेक्शन में जाने के लिए, <strong>Shift+Tab</strong> दबाएं।</p>\n' +
|
||||
'\n' +
|
||||
'<p>इन UI सेक्शन का <strong>Tab</strong> क्रम नीचे दिया गया है:</p>\n' +
|
||||
'\n' +
|
||||
'<ol>\n' +
|
||||
' <li>मेन्यू बार</li>\n' +
|
||||
' <li>प्रत्येक टूलबार समूह</li>\n' +
|
||||
' <li>साइडबार</li>\n' +
|
||||
' <li>फ़ुटर में तत्व पथ</li>\n' +
|
||||
' <li>फ़ुटर में शब्द गणना टॉगल बटन</li>\n' +
|
||||
' <li>फ़ुटर में ब्रांडिंग लिंक</li>\n' +
|
||||
' <li>फ़ुटर में संपादक का आकार बदलने का हैंडल</li>\n' +
|
||||
'</ol>\n' +
|
||||
'\n' +
|
||||
'<p>अगर कोई UI सेक्शन मौजूद नहीं है, तो उसे छोड़ दिया जाता है।</p>\n' +
|
||||
'\n' +
|
||||
'<p>अगर फ़ुटर में कीबोर्ड नेविगेशन फ़ोकस है, और कोई दिखा देने वाला साइडबार नहीं है, तो <strong>Shift+Tab</strong> दबाने से\n' +
|
||||
' फ़ोकस पहले टूलबार समूह पर चला जाता है, पिछले पर नहीं।</p>\n' +
|
||||
'\n' +
|
||||
'<h1>UI सेक्शन के भीतर नेविगेट करें</h1>\n' +
|
||||
'\n' +
|
||||
'<p>एक UI तत्व से दूसरे में जाने के लिए उपयुक्त <strong>ऐरो</strong> कुंजी दबाएं।</p>\n' +
|
||||
'\n' +
|
||||
'<p><strong>बाएं</strong> और <strong>दाएं</strong> ऐरो कुंजियां</p>\n' +
|
||||
'\n' +
|
||||
'<ul>\n' +
|
||||
' <li>मेन्यू बार में मेन्यू के बीच ले जाती हैं।</li>\n' +
|
||||
' <li>मेन्यू में एक सब-मेन्यू खोलें।</li>\n' +
|
||||
' <li>टूलबार समूह में बटनों के बीच ले जाएं।</li>\n' +
|
||||
' <li>फ़ुटर के तत्व पथ में आइटम के बीच ले जाएं।</li>\n' +
|
||||
'</ul>\n' +
|
||||
'\n' +
|
||||
'<p><strong>नीचे</strong> और <strong>ऊपर</strong> ऐरो कुंजियां</p>\n' +
|
||||
'\n' +
|
||||
'<ul>\n' +
|
||||
' <li>मेन्यू में मेन्यू आइटम के बीच ले जाती हैं।</li>\n' +
|
||||
' <li>टूलबार पॉप-अप मेन्यू में आइटम के बीच ले जाएं।</li>\n' +
|
||||
'</ul>\n' +
|
||||
'\n' +
|
||||
'<p>फ़ोकस वाले UI सेक्शन के भीतर <strong>ऐरो</strong> कुंजियां चलाती रहती हैं।</p>\n' +
|
||||
'\n' +
|
||||
'<p>कोई खुला मेन्यू, कोई खुला सब-मेन्यू या कोई खुला पॉप-अप मेन्यू बंद करने के लिए <strong>Esc</strong> कुंजी दबाएं।</p>\n' +
|
||||
'\n' +
|
||||
"<p>अगर मौजूदा फ़ोकस किसी विशेष UI सेक्शन के 'शीर्ष' पर है, तो <strong>Esc</strong> कुंजी दबाने से भी\n" +
|
||||
' कीबोर्ड नेविगेशन पूरी तरह से बाहर हो जाता है।</p>\n' +
|
||||
'\n' +
|
||||
'<h1>मेन्यू आइटम या टूलबार बटन निष्पादित करें</h1>\n' +
|
||||
'\n' +
|
||||
'<p>जब वांछित मेन्यू आइटम या टूलबार बटन हाइलाइट किया जाता है, तो आइटम को निष्पादित करने के लिए <strong>Return</strong>, <strong>Enter</strong>,\n' +
|
||||
' या <strong>Space bar</strong> दबाएं।</p>\n' +
|
||||
'\n' +
|
||||
'<h1>गैर-टैब वाले डायलॉग पर नेविगेट करें</h1>\n' +
|
||||
'\n' +
|
||||
'<p>गैर-टैब वाले डायलॉग में, डायलॉग खुलने पर पहला इंटरैक्टिव घटक फ़ोकस लेता है।</p>\n' +
|
||||
'\n' +
|
||||
'<p><strong>Tab</strong> or <strong>Shift+Tab</strong> दबाकर इंटरैक्टिव डायलॉग घटकों के बीच नेविगेट करें।</p>\n' +
|
||||
'\n' +
|
||||
'<h1>टैब किए गए डायलॉग पर नेविगेट करें</h1>\n' +
|
||||
'\n' +
|
||||
'<p>टैब किए गए डायलॉग में, डायलॉग खुलने पर टैब मेन्यू में पहला बटन फ़ोकस लेता है।</p>\n' +
|
||||
'\n' +
|
||||
'<p>इस डायलॉग टैब के इंटरैक्टिव घटकों के बीच नेविगेट करने के लिए <strong>Tab</strong> या\n' +
|
||||
' <strong>Shift+Tab</strong> दबाएं।</p>\n' +
|
||||
'\n' +
|
||||
'<p>टैब मेन्यू को फ़ोकस देकर और फिर उपलब्ध टैब में के बीच जाने के लिए उपयुक्त <strong>ऐरो</strong>\n' +
|
||||
' कुंजी दबाकर दूसरे डायलॉग टैब पर स्विच करें।</p>\n');
|
||||
@@ -0,0 +1,93 @@
|
||||
tinymce.Resource.add('tinymce.html-i18n.help-keynav.hr',
|
||||
'<h1>Početak navigacije na tipkovnici</h1>\n' +
|
||||
'\n' +
|
||||
'<dl>\n' +
|
||||
' <dt>Fokusiranje trake izbornika</dt>\n' +
|
||||
' <dd>Windows ili Linux: Alt+F9</dd>\n' +
|
||||
' <dd>macOS: ⌥F9</dd>\n' +
|
||||
' <dt>Fokusiranje alatne trake</dt>\n' +
|
||||
' <dd>Windows ili Linux: Alt+F10</dd>\n' +
|
||||
' <dd>macOS: ⌥F10</dd>\n' +
|
||||
' <dt>Fokusiranje podnožja</dt>\n' +
|
||||
' <dd>Windows ili Linux: Alt+F11</dd>\n' +
|
||||
' <dd>macOS: ⌥F11</dd>\n' +
|
||||
' <dt>Fokusiranje obavijesti</dt>\n' +
|
||||
' <dd>Windows ili Linux: Alt + F12</dd>\n' +
|
||||
' <dd>macOS: ⌥F12</dd>\n' +
|
||||
' <dt>Fokusiranje kontekstne alatne trake</dt>\n' +
|
||||
' <dd>Windows, Linux ili macOS: Ctrl+F9</dd>\n' +
|
||||
'</dl>\n' +
|
||||
'\n' +
|
||||
'<p>Navigacija će započeti kod prve stavke na korisničkom sučelju, koja će biti istaknuta ili podcrtana ako se radi o prvoj stavci u\n' +
|
||||
' putu elementa u podnožju.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>Navigacija između dijelova korisničkog sučelja</h1>\n' +
|
||||
'\n' +
|
||||
'<p>Za pomicanje s jednog dijela korisničkog sučelja na drugi pritisnite <strong>tabulator</strong>.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Za pomicanje s jednog dijela korisničkog sučelja na prethodni pritisnite <strong>Shift + tabulator</strong>.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Ovo je redoslijed pomicanja <strong>tabulatora</strong> po dijelovima korisničkog sučelja:</p>\n' +
|
||||
'\n' +
|
||||
'<ol>\n' +
|
||||
' <li>Traka izbornika</li>\n' +
|
||||
' <li>Pojedinačne grupe na alatnoj traci</li>\n' +
|
||||
' <li>Bočna traka</li>\n' +
|
||||
' <li>Put elemenata u podnožju</li>\n' +
|
||||
' <li>Gumb za pomicanje po broju riječi u podnožju</li>\n' +
|
||||
' <li>Veza na brand u podnožju</li>\n' +
|
||||
' <li>Značajka za promjenu veličine alata za uređivanje u podnožju</li>\n' +
|
||||
'</ol>\n' +
|
||||
'\n' +
|
||||
'<p>Ako neki dio korisničkog sučelja nije naveden, on se preskače.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Ako u podnožju postoji fokus za navigaciju na tipkovnici, a nema vidljive bočne trake, pritiskom na <strong>Shift + tabulator</strong>\n' +
|
||||
' fokus se prebacuje na prvu skupinu na alatnoj traci, ne na zadnju.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>Navigacija unutar dijelova korisničkog sučelja</h1>\n' +
|
||||
'\n' +
|
||||
'<p>Za pomicanje s jednog elementa korisničkog sučelja na drugi pritisnite tipku s odgovarajućom <strong>strelicom</strong>.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Tipke s <strong>lijevom</strong> i <strong>desnom</strong> strelicom</p>\n' +
|
||||
'\n' +
|
||||
'<ul>\n' +
|
||||
' <li>služe za pomicanje između izbornika na alatnoj traci.</li>\n' +
|
||||
' <li>otvaraju podizbornik unutar izbornika.</li>\n' +
|
||||
' <li>služe za pomicanje između gumba unutar skupina na alatnoj traci.</li>\n' +
|
||||
' <li>služe za pomicanje između stavki na elementu puta u podnožju.</li>\n' +
|
||||
'</ul>\n' +
|
||||
'\n' +
|
||||
'<p>Tipke s <strong>donjom</strong> i <strong>gornjom</strong> strelicom</p>\n' +
|
||||
'\n' +
|
||||
'<ul>\n' +
|
||||
' <li>služe za pomicanje između stavki unutar izbornika.</li>\n' +
|
||||
' <li>služe za pomicanje između stavki na alatnoj traci skočnog izbornika.</li>\n' +
|
||||
'</ul>\n' +
|
||||
'\n' +
|
||||
'<p>Tipkama <strong>strelica</strong> kružno se pomičete unutar dijela korisničkog sučelja koji je u fokusu.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Za zatvaranje otvorenog izbornika, otvorenog podizbornika ili otvorenog skočnog izbornika pritisnite tipku <strong>Esc</strong>.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Ako je fokus trenutačno postavljen na vrh pojedinačnog dijela korisničkog sučelja, pritiskom na tipku <strong>Esc</strong> također\n' +
|
||||
' u potpunosti zatvarate navigaciju na tipkovnici.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>Izvršavanje radnji putem stavki izbornika ili gumba na alatnoj traci</h1>\n' +
|
||||
'\n' +
|
||||
'<p>Nakon što se istakne stavka izbornika ili gumb na alatnoj traci s radnjom koju želite izvršiti, pritisnite tipku <strong>Return</strong>, <strong>Enter</strong>\n' +
|
||||
' ili <strong>razmak</strong> da biste pokrenuli željenu radnju.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>Navigacija dijaloškim okvirima izvan kartica</h1>\n' +
|
||||
'\n' +
|
||||
'<p>Prilikom otvaranja dijaloških okvira izvan kartica fokus se nalazi na prvoj interaktivnoj komponenti.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Navigaciju između interaktivnih dijaloških komponenata vršite pritiskom na <strong>tabulator</strong> ili <strong>Shift + tabulator</strong>.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>Navigacija dijaloškim okvirima u karticama</h1>\n' +
|
||||
'\n' +
|
||||
'<p>Prilikom otvaranja dijaloških okvira u karticama fokus se nalazi na prvom gumbu u izborniku unutar kartice.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Navigaciju između interaktivnih komponenata dijaloškog okvira u kartici vršite pritiskom na <strong>tabulator</strong> ili\n' +
|
||||
' <strong>Shift + tabulator</strong>.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Na karticu s drugim dijaloškim okvirom možete se prebaciti tako da stavite fokus na izbornik kartice pa pritisnete tipku s odgovarajućom <strong>strelicom</strong>\n' +
|
||||
' za kružno pomicanje između dostupnih kartica.</p>\n');
|
||||
@@ -0,0 +1,93 @@
|
||||
tinymce.Resource.add('tinymce.html-i18n.help-keynav.hu_HU',
|
||||
'<h1>Billentyűzetes navigáció indítása</h1>\n' +
|
||||
'\n' +
|
||||
'<dl>\n' +
|
||||
' <dt>Fókusz a menüsávra</dt>\n' +
|
||||
' <dd>Windows és Linux: Alt+F9</dd>\n' +
|
||||
' <dd>macOS: ⌥F9</dd>\n' +
|
||||
' <dt>Fókusz az eszköztárra</dt>\n' +
|
||||
' <dd>Windows és Linux: Alt+F10</dd>\n' +
|
||||
' <dd>macOS: ⌥F10</dd>\n' +
|
||||
' <dt>Fókusz a láblécre</dt>\n' +
|
||||
' <dd>Windows és Linux: Alt+F11</dd>\n' +
|
||||
' <dd>macOS: ⌥F11</dd>\n' +
|
||||
' <dt>Ráközelítés az értesítésre</dt>\n' +
|
||||
' <dd>Windows vagy Linux: Alt+F12</dd>\n' +
|
||||
' <dd>macOS: ⌥F12</dd>\n' +
|
||||
' <dt>Fókusz egy környezetfüggő eszköztárra</dt>\n' +
|
||||
' <dd>Windows, Linux és macOS: Ctrl+F9</dd>\n' +
|
||||
'</dl>\n' +
|
||||
'\n' +
|
||||
'<p>A navigáció az első felhasználói felületi elemnél kezdődik, amelyet a rendszer kiemel, illetve aláhúz, amennyiben az az első elem\n' +
|
||||
' a lábléc elemútvonalán.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>Navigálás a felhasználói felület szakaszai között</h1>\n' +
|
||||
'\n' +
|
||||
'<p>A felhasználói felület következő szakaszára váltáshoz nyomja meg a <strong>Tab</strong> billentyűt.</p>\n' +
|
||||
'\n' +
|
||||
'<p>A felhasználói felület előző szakaszára váltáshoz nyomja meg a <strong>Shift+Tab</strong> billentyűt.</p>\n' +
|
||||
'\n' +
|
||||
'<p>A <strong>Tab</strong> billentyűvel a felhasználói felület szakaszai között a következő sorrendben vált:</p>\n' +
|
||||
'\n' +
|
||||
'<ol>\n' +
|
||||
' <li>Menüsáv</li>\n' +
|
||||
' <li>Az egyes eszköztárcsoportok</li>\n' +
|
||||
' <li>Oldalsáv</li>\n' +
|
||||
' <li>Elemútvonal a láblécen</li>\n' +
|
||||
' <li>Szószámátkapcsoló gomb a láblécen</li>\n' +
|
||||
' <li>Márkalink a láblécen</li>\n' +
|
||||
' <li>Szerkesztő átméretezési fogópontja a láblécen</li>\n' +
|
||||
'</ol>\n' +
|
||||
'\n' +
|
||||
'<p>Ha a felhasználói felület valamelyik eleme nincs jelen, a rendszer kihagyja.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Ha a billentyűzetes navigáció fókusza a láblécen van, és nincs látható oldalsáv, a <strong>Shift+Tab</strong>\n' +
|
||||
' billentyűkombináció lenyomásakor az első eszköztárcsoportra ugrik a fókusz, nem az utolsóra.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>Navigálás a felhasználói felület szakaszain belül</h1>\n' +
|
||||
'\n' +
|
||||
'<p>A felhasználói felület következő elemére váltáshoz nyomja meg a megfelelő <strong>nyílbillentyűt</strong>.</p>\n' +
|
||||
'\n' +
|
||||
'<p>A <strong>bal</strong> és a <strong>jobb</strong> nyílgomb</p>\n' +
|
||||
'\n' +
|
||||
'<ul>\n' +
|
||||
' <li>a menüsávban a menük között vált.</li>\n' +
|
||||
' <li>a menükben megnyit egy almenüt.</li>\n' +
|
||||
' <li>az eszköztárcsoportban a gombok között vált.</li>\n' +
|
||||
' <li>a lábléc elemútvonalán az elemek között vált.</li>\n' +
|
||||
'</ul>\n' +
|
||||
'\n' +
|
||||
'<p>A <strong>le</strong> és a <strong>fel</strong> nyílgomb</p>\n' +
|
||||
'\n' +
|
||||
'<ul>\n' +
|
||||
' <li>a menükben a menüpontok között vált.</li>\n' +
|
||||
' <li>az eszköztár előugró menüjében az elemek között vált.</li>\n' +
|
||||
'</ul>\n' +
|
||||
'\n' +
|
||||
'<p>A <strong>nyílbillentyűk</strong> lenyomásával körkörösen lépkedhet a fókuszban lévő felhasználói felületi szakasz elemei között.</p>\n' +
|
||||
'\n' +
|
||||
'<p>A megnyitott menüket, almenüket és előugró menüket az <strong>Esc</strong> billentyűvel zárhatja be.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Ha a fókusz az aktuális felületi elem „felső” részén van, az <strong>Esc</strong> billentyűvel az egész\n' +
|
||||
' billentyűzetes navigációból kilép.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>Menüpont vagy eszköztárgomb aktiválása</h1>\n' +
|
||||
'\n' +
|
||||
'<p>Amikor a kívánt menüelem vagy eszköztárgomb van kijelölve, nyomja meg a <strong>Return</strong>, az <strong>Enter</strong>\n' +
|
||||
' vagy a <strong>Szóköz</strong> billentyűt az adott elem vagy gomb aktiválásához.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>Navigálás a lapokkal nem rendelkező párbeszédablakokban</h1>\n' +
|
||||
'\n' +
|
||||
'<p>A lapokkal nem rendelkező párbeszédablakokban az első interaktív összetevő kapja a fókuszt, amikor a párbeszédpanel megnyílik.</p>\n' +
|
||||
'\n' +
|
||||
'<p>A párbeszédpanelek interaktív összetevői között a <strong>Tab</strong> vagy a <strong>Shift+Tab</strong> billentyűvel navigálhat.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>Navigálás a lapokkal rendelkező párbeszédablakokban</h1>\n' +
|
||||
'\n' +
|
||||
'<p>A lapokkal rendelkező párbeszédablakokban a lapmenü első gombja kapja a fókuszt, amikor a párbeszédpanel megnyílik.</p>\n' +
|
||||
'\n' +
|
||||
'<p>A párbeszédpanel e lapjának interaktív összetevői között a <strong>Tab</strong> vagy\n' +
|
||||
' <strong>Shift+Tab</strong> billentyűvel navigálhat.</p>\n' +
|
||||
'\n' +
|
||||
'<p>A párbeszédablak másik lapjára úgy léphet, hogy a fókuszt a lapmenüre állítja, majd lenyomja a megfelelő <strong>nyílbillentyűt</strong>\n' +
|
||||
' a rendelkezésre álló lapok közötti lépkedéshez.</p>\n');
|
||||
@@ -0,0 +1,93 @@
|
||||
tinymce.Resource.add('tinymce.html-i18n.help-keynav.id',
|
||||
'<h1>Memulai navigasi keyboard</h1>\n' +
|
||||
'\n' +
|
||||
'<dl>\n' +
|
||||
' <dt>Fokus pada bilah Menu</dt>\n' +
|
||||
' <dd>Windows atau Linux: Alt+F9</dd>\n' +
|
||||
' <dd>macOS: ⌥F9</dd>\n' +
|
||||
' <dt>Fokus pada Bilah Alat</dt>\n' +
|
||||
' <dd>Windows atau Linux: Alt+F10</dd>\n' +
|
||||
' <dd>macOS: ⌥F10</dd>\n' +
|
||||
' <dt>Fokus pada footer</dt>\n' +
|
||||
' <dd>Windows atau Linux: Alt+F11</dd>\n' +
|
||||
' <dd>macOS: ⌥F11</dd>\n' +
|
||||
' <dt>Fokuskan pemberitahuan</dt>\n' +
|
||||
' <dd>Windows atau Linux: Alt+F12</dd>\n' +
|
||||
' <dd>macOS: ⌥F12</dd>\n' +
|
||||
' <dt>Fokus pada bilah alat kontekstual</dt>\n' +
|
||||
' <dd>Windows, Linux, atau macOS: Ctrl+F9</dd>\n' +
|
||||
'</dl>\n' +
|
||||
'\n' +
|
||||
'<p>Navigasi akan dimulai dari item pertama UI, yang akan disorot atau digarisbawahi di\n' +
|
||||
' alur elemen Footer.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>Berpindah antar-bagian UI</h1>\n' +
|
||||
'\n' +
|
||||
'<p>Untuk berpindah dari satu bagian UI ke bagian berikutnya, tekan <strong>Tab</strong>.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Untuk berpindah dari satu bagian UI ke bagian sebelumnya, tekan <strong>Shift+Tab</strong>.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Urutan <strong>Tab</strong> bagian-bagian UI ini adalah:</p>\n' +
|
||||
'\n' +
|
||||
'<ol>\n' +
|
||||
' <li>Bilah menu</li>\n' +
|
||||
' <li>Tiap grup bilah alat</li>\n' +
|
||||
' <li>Bilah sisi</li>\n' +
|
||||
' <li>Alur elemen di footer</li>\n' +
|
||||
' <li>Tombol aktifkan/nonaktifkan jumlah kata di footer</li>\n' +
|
||||
' <li>Tautan merek di footer</li>\n' +
|
||||
' <li>Pengatur pengubahan ukuran editor di footer</li>\n' +
|
||||
'</ol>\n' +
|
||||
'\n' +
|
||||
'<p>Jika suatu bagian UI tidak ada, bagian tersebut dilewati.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Jika fokus navigasi keyboard ada pada footer, tetapi tidak ada bilah sisi yang terlihat, menekan <strong>Shift+Tab</strong>\n' +
|
||||
' akan memindahkan fokus ke grup bilah alat pertama, bukan yang terakhir.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>Berpindah di dalam bagian-bagian UI</h1>\n' +
|
||||
'\n' +
|
||||
'<p>Untuk berpindah dari satu elemen UI ke elemen berikutnya, tekan tombol <strong>Panah</strong> yang sesuai.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Tombol panah <strong>Kiri</strong> dan <strong>Kanan</strong> untuk</p>\n' +
|
||||
'\n' +
|
||||
'<ul>\n' +
|
||||
' <li>berpindah-pindah antar-menu di dalam bilah menu.</li>\n' +
|
||||
' <li>membuka sub-menu di dalam menu.</li>\n' +
|
||||
' <li>berpindah-pindah antar-tombol di dalam grup bilah alat.</li>\n' +
|
||||
' <li>berpindah-pindah antar-item di dalam alur elemen footer.</li>\n' +
|
||||
'</ul>\n' +
|
||||
'\n' +
|
||||
'<p>Tombol panah <strong>Bawah</strong> dan <strong>Atas</strong> untuk</p>\n' +
|
||||
'\n' +
|
||||
'<ul>\n' +
|
||||
' <li>berpindah-pindah antar-item menu di dalam menu.</li>\n' +
|
||||
' <li>berpindah-pindah antar-item di dalam menu pop-up bilah alat.</li>\n' +
|
||||
'</ul>\n' +
|
||||
'\n' +
|
||||
'<p>Tombol <strong>Panah</strong> hanya bergerak di dalam bagian UI yang difokuskan.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Untuk menutup menu, sub-menu, atau menu pop-up yang terbuka, tekan tombol <strong>Esc</strong>.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Jika fokus sedang berada di ‘atas’ bagian UI tertentu, menekan tombol <strong>Esc</strong> juga dapat mengeluarkan fokus\n' +
|
||||
' dari seluruh navigasi keyboard.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>Menjalankan item menu atau tombol bilah alat</h1>\n' +
|
||||
'\n' +
|
||||
'<p>Jika item menu atau tombol bilah alat yang diinginkan tersorot, tekan <strong>Return</strong>, <strong>Enter</strong>,\n' +
|
||||
' atau <strong>Spasi</strong> untuk menjalankan item.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>Berpindah dalam dialog tanpa tab</h1>\n' +
|
||||
'\n' +
|
||||
'<p>Dalam dialog tanpa tab, fokus diarahkan pada komponen interaktif pertama saat dialog terbuka.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Berpindah di antara komponen dalam dialog interaktif dengan menekan <strong>Tab</strong> atau <strong>Shift+Tab</strong>.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>Berpindah dalam dialog dengan tab</h1>\n' +
|
||||
'\n' +
|
||||
'<p>Dalam dialog yang memiliki tab, fokus diarahkan pada tombol pertama di dalam menu saat dialog terbuka.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Berpindah di antara komponen-komponen interaktif pada tab dialog ini dengan menekan <strong>Tab</strong> atau\n' +
|
||||
' <strong>Shift+Tab</strong>.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Beralih ke tab dialog lain dengan mengarahkan fokus pada menu tab lalu tekan tombol <strong>Panah</strong>\n' +
|
||||
' yang sesuai untuk berpindah ke berbagai tab yang tersedia.</p>\n');
|
||||
@@ -0,0 +1,93 @@
|
||||
tinymce.Resource.add('tinymce.html-i18n.help-keynav.it',
|
||||
'<h1>Iniziare la navigazione tramite tastiera</h1>\n' +
|
||||
'\n' +
|
||||
'<dl>\n' +
|
||||
' <dt>Impostare lo stato attivo per la barra dei menu</dt>\n' +
|
||||
' <dd>Windows o Linux: ALT+F9</dd>\n' +
|
||||
' <dd>macOS: ⌥F9</dd>\n' +
|
||||
' <dt>Impostare lo stato attivo per la barra degli strumenti</dt>\n' +
|
||||
' <dd>Windows o Linux: ALT+F10</dd>\n' +
|
||||
' <dd>macOS: ⌥F10</dd>\n' +
|
||||
' <dt>Impostare lo stato attivo per il piè di pagina</dt>\n' +
|
||||
' <dd>Windows o Linux: ALT+F11</dd>\n' +
|
||||
' <dd>macOS: ⌥F11</dd>\n' +
|
||||
' <dt>Metti a fuoco la notifica</dt>\n' +
|
||||
' <dd>Windows o Linux: ALT+F12</dd>\n' +
|
||||
' <dd>macOS: ⌥F12</dd>\n' +
|
||||
' <dt>Impostare lo stato attivo per la barra degli strumenti contestuale</dt>\n' +
|
||||
' <dd>Windows, Linux o macOS: CTRL+F9</dd>\n' +
|
||||
'</dl>\n' +
|
||||
'\n' +
|
||||
"<p>La navigazione inizierà dalla prima voce dell'interfaccia utente, che sarà evidenziata o sottolineata nel caso della prima voce\n" +
|
||||
" nel percorso dell'elemento del piè di pagina.</p>\n" +
|
||||
'\n' +
|
||||
"<h1>Navigare tra le sezioni dell'interfaccia utente</h1>\n" +
|
||||
'\n' +
|
||||
"<p>Per passare da una sezione dell'interfaccia utente alla successiva, premere <strong>TAB</strong>.</p>\n" +
|
||||
'\n' +
|
||||
"<p>Per passare da una sezione dell'interfaccia utente alla precedente, premere <strong>MAIUSC+TAB</strong>.</p>\n" +
|
||||
'\n' +
|
||||
"<p>L'ordine di <strong>tabulazione</strong> di queste sezioni dell'interfaccia utente è:</p>\n" +
|
||||
'\n' +
|
||||
'<ol>\n' +
|
||||
' <li>Barra dei menu</li>\n' +
|
||||
' <li>Ogni gruppo di barre degli strumenti</li>\n' +
|
||||
' <li>Barra laterale</li>\n' +
|
||||
" <li>Percorso dell'elemento nel piè di pagina</li>\n" +
|
||||
' <li>Pulsante di attivazione/disattivazione del conteggio delle parole nel piè di pagina</li>\n' +
|
||||
' <li>Collegamento al marchio nel piè di pagina</li>\n' +
|
||||
" <li>Quadratino di ridimensionamento dell'editor nel piè di pagina</li>\n" +
|
||||
'</ol>\n' +
|
||||
'\n' +
|
||||
"<p>Se una sezione dell'interfaccia utente non è presente, viene saltata.</p>\n" +
|
||||
'\n' +
|
||||
'<p>Se il piè di pagina ha lo stato attivo per la navigazione tramite tastiera e non è presente alcuna barra laterale visibile, premendo <strong>MAIUSC+TAB</strong>\n' +
|
||||
" si sposta lo stato attivo sul primo gruppo di barre degli strumenti, non sull'ultimo.</p>\n" +
|
||||
'\n' +
|
||||
"<h1>Navigare all'interno delle sezioni dell'interfaccia utente</h1>\n" +
|
||||
'\n' +
|
||||
"<p>Per passare da un elemento dell'interfaccia utente al successivo, premere il tasto <strong>freccia</strong> appropriato.</p>\n" +
|
||||
'\n' +
|
||||
'<p>I tasti freccia <strong>Sinistra</strong> e <strong>Destra</strong></p>\n' +
|
||||
'\n' +
|
||||
'<ul>\n' +
|
||||
' <li>consentono di spostarsi tra i menu della barra dei menu.</li>\n' +
|
||||
' <li>aprono un sottomenu in un menu.</li>\n' +
|
||||
' <li>consentono di spostarsi tra i pulsanti di un gruppo di barre degli strumenti.</li>\n' +
|
||||
" <li>consentono di spostarsi tra le voci nel percorso dell'elemento del piè di pagina.</li>\n" +
|
||||
'</ul>\n' +
|
||||
'\n' +
|
||||
'<p>I tasti freccia <strong>Giù</strong> e <strong>Su</strong></p>\n' +
|
||||
'\n' +
|
||||
'<ul>\n' +
|
||||
' <li>consentono di spostarsi tra le voci di un menu.</li>\n' +
|
||||
' <li>consentono di spostarsi tra le voci di un menu a comparsa della barra degli strumenti.</li>\n' +
|
||||
'</ul>\n' +
|
||||
'\n' +
|
||||
"<p>I tasti <strong>freccia</strong> consentono di spostarsi all'interno della sezione dell'interfaccia utente con stato attivo.</p>\n" +
|
||||
'\n' +
|
||||
'<p>Per chiudere un menu aperto, un sottomenu aperto o un menu a comparsa aperto, premere il tasto <strong>ESC</strong>.</p>\n' +
|
||||
'\n' +
|
||||
"<p>Se lo stato attivo corrente si trova nella parte superiore di una particolare sezione dell'interfaccia utente, premendo il tasto <strong>ESC</strong> si esce\n" +
|
||||
' completamente dalla navigazione tramite tastiera.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>Eseguire una voce di menu o un pulsante della barra degli strumenti</h1>\n' +
|
||||
'\n' +
|
||||
'<p>Quando la voce di menu o il pulsante della barra degli strumenti desiderati sono evidenziati, premere il tasto di<strong>ritorno a capo</strong>, il tasto <strong>Invio</strong>\n' +
|
||||
' o la <strong>barra spaziatrice</strong> per eseguirli.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>Navigare nelle finestre di dialogo non a schede</h1>\n' +
|
||||
'\n' +
|
||||
"<p>Nelle finestre di dialogo non a schede, all'apertura della finestra di dialogo diventa attivo il primo componente interattivo.</p>\n" +
|
||||
'\n' +
|
||||
'<p>Per spostarsi tra i componenti interattivi della finestra di dialogo, premere <strong>TAB</strong> o <strong>MAIUSC+TAB</strong>.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>Navigare nelle finestre di dialogo a schede</h1>\n' +
|
||||
'\n' +
|
||||
"<p>Nelle finestre di dialogo a schede, all'apertura della finestra di dialogo diventa attivo il primo pulsante del menu della scheda.</p>\n" +
|
||||
'\n' +
|
||||
'<p>Per spostarsi tra i componenti interattivi di questa scheda della finestra di dialogo, premere <strong>TAB</strong> o\n' +
|
||||
' <strong>MAIUSC+TAB</strong>.</p>\n' +
|
||||
'\n' +
|
||||
"<p>Per passare a un'altra scheda della finestra di dialogo, attivare il menu della scheda e premere il tasto <strong>freccia</strong>\n" +
|
||||
' appropriato per scorrere le schede disponibili.</p>\n');
|
||||
@@ -0,0 +1,93 @@
|
||||
tinymce.Resource.add('tinymce.html-i18n.help-keynav.ja',
|
||||
'<h1>キーボード ナビゲーションの開始</h1>\n' +
|
||||
'\n' +
|
||||
'<dl>\n' +
|
||||
' <dt>メニュー バーをフォーカス</dt>\n' +
|
||||
' <dd>Windows または Linux: Alt+F9</dd>\n' +
|
||||
' <dd>macOS: ⌥F9</dd>\n' +
|
||||
' <dt>ツール バーをフォーカス</dt>\n' +
|
||||
' <dd>Windows または Linux: Alt+F10</dd>\n' +
|
||||
' <dd>macOS: ⌥F10</dd>\n' +
|
||||
' <dt>フッターをフォーカス</dt>\n' +
|
||||
' <dd>Windows または Linux: Alt+F11</dd>\n' +
|
||||
' <dd>macOS: ⌥F11</dd>\n' +
|
||||
' <dt>通知にフォーカス</dt>\n' +
|
||||
' <dd>Windows または Linux: Alt+F12</dd>\n' +
|
||||
' <dd>macOS: ⌥F12</dd>\n' +
|
||||
' <dt>コンテキスト ツール バーをフォーカス</dt>\n' +
|
||||
' <dd>Windows、Linux または macOS: Ctrl+F9</dd>\n' +
|
||||
'</dl>\n' +
|
||||
'\n' +
|
||||
'<p>ナビゲーションは最初の UI 項目から開始され、強調表示されるか、フッターの要素パスにある最初の項目の場合は\n' +
|
||||
' 下線が引かれます。</p>\n' +
|
||||
'\n' +
|
||||
'<h1>UI セクション間の移動</h1>\n' +
|
||||
'\n' +
|
||||
'<p>次の UI セクションに移動するには、<strong>Tab</strong> を押します。</p>\n' +
|
||||
'\n' +
|
||||
'<p>前の UI セクションに移動するには、<strong>Shift+Tab</strong> を押します。</p>\n' +
|
||||
'\n' +
|
||||
'<p>これらの UI セクションの <strong>Tab</strong> の順序:</p>\n' +
|
||||
'\n' +
|
||||
'<ol>\n' +
|
||||
' <li>メニュー バー</li>\n' +
|
||||
' <li>各ツール バー グループ</li>\n' +
|
||||
' <li>サイド バー</li>\n' +
|
||||
' <li>フッターの要素パス</li>\n' +
|
||||
' <li>フッターの単語数切り替えボタン</li>\n' +
|
||||
' <li>フッターのブランド リンク</li>\n' +
|
||||
' <li>フッターのエディター サイズ変更ハンドル</li>\n' +
|
||||
'</ol>\n' +
|
||||
'\n' +
|
||||
'<p>UI セクションが存在しない場合は、スキップされます。</p>\n' +
|
||||
'\n' +
|
||||
'<p>フッターにキーボード ナビゲーション フォーカスがあり、表示可能なサイド バーがない場合、<strong>Shift+Tab</strong> を押すと、\n' +
|
||||
' フォーカスが最後ではなく最初のツール バー グループに移動します。</p>\n' +
|
||||
'\n' +
|
||||
'<h1>UI セクション内の移動</h1>\n' +
|
||||
'\n' +
|
||||
'<p>次の UI 要素に移動するには、適切な<strong>矢印</strong>キーを押します。</p>\n' +
|
||||
'\n' +
|
||||
'<p><strong>左矢印</strong>と<strong>右矢印</strong>のキー</p>\n' +
|
||||
'\n' +
|
||||
'<ul>\n' +
|
||||
' <li>メニュー バーのメニュー間で移動します。</li>\n' +
|
||||
' <li>メニュー内のサブメニューを開きます。</li>\n' +
|
||||
' <li>ツール バー グループのボタン間で移動します。</li>\n' +
|
||||
' <li>フッターの要素パスの項目間で移動します。</li>\n' +
|
||||
'</ul>\n' +
|
||||
'\n' +
|
||||
'<p><strong>下矢印</strong>と<strong>上矢印</strong>のキー</p>\n' +
|
||||
'\n' +
|
||||
'<ul>\n' +
|
||||
' <li>メニュー内のメニュー項目間で移動します。</li>\n' +
|
||||
' <li>ツール バー ポップアップ メニュー内のメニュー項目間で移動します。</li>\n' +
|
||||
'</ul>\n' +
|
||||
'\n' +
|
||||
'<p><strong>矢印</strong>キーで、フォーカスされた UI セクション内で循環します。</p>\n' +
|
||||
'\n' +
|
||||
'<p>開いたメニュー、開いたサブメニュー、開いたポップアップ メニューを閉じるには、<strong>Esc</strong> キーを押します。</p>\n' +
|
||||
'\n' +
|
||||
'<p>現在のフォーカスが特定の UI セクションの「一番上」にある場合、<strong>Esc</strong> キーを押すと\n' +
|
||||
' キーボード ナビゲーションも完全に閉じられます。</p>\n' +
|
||||
'\n' +
|
||||
'<h1>メニュー項目またはツール バー ボタンの実行</h1>\n' +
|
||||
'\n' +
|
||||
'<p>目的のメニュー項目やツール バー ボタンが強調表示されている場合、<strong>リターン</strong>、<strong>Enter</strong>、\n' +
|
||||
' または<strong>スペース キー</strong>を押して項目を実行します。</p>\n' +
|
||||
'\n' +
|
||||
'<h1>タブのないダイアログの移動</h1>\n' +
|
||||
'\n' +
|
||||
'<p>タブのないダイアログでは、ダイアログが開くと最初の対話型コンポーネントがフォーカスされます。</p>\n' +
|
||||
'\n' +
|
||||
'<p><strong>Tab</strong> または <strong>Shift+Tab</strong> を押して、対話型ダイアログ コンポーネント間で移動します。</p>\n' +
|
||||
'\n' +
|
||||
'<h1>タブ付きダイアログの移動</h1>\n' +
|
||||
'\n' +
|
||||
'<p>タブ付きダイアログでは、ダイアログが開くとタブ メニューの最初のボタンがフォーカスされます。</p>\n' +
|
||||
'\n' +
|
||||
'<p><strong>Tab</strong> または\n' +
|
||||
' <strong>Shift+Tab</strong> を押して、このダイアログ タブの対話型コンポーネント間で移動します。</p>\n' +
|
||||
'\n' +
|
||||
'<p>タブ メニューをフォーカスしてから適切な<strong>矢印</strong>キーを押して表示可能なタブを循環して、\n' +
|
||||
' 別のダイアログに切り替えます。</p>\n');
|
||||
@@ -0,0 +1,93 @@
|
||||
tinymce.Resource.add('tinymce.html-i18n.help-keynav.kk',
|
||||
'<h1>Пернетақта навигациясын бастау</h1>\n' +
|
||||
'\n' +
|
||||
'<dl>\n' +
|
||||
' <dt>Мәзір жолағын фокустау</dt>\n' +
|
||||
' <dd>Windows немесе Linux: Alt+F9</dd>\n' +
|
||||
' <dd>macOS: ⌥F9</dd>\n' +
|
||||
' <dt>Құралдар тақтасын фокустау</dt>\n' +
|
||||
' <dd>Windows немесе Linux: Alt+F10</dd>\n' +
|
||||
' <dd>macOS: ⌥F10</dd>\n' +
|
||||
' <dt>Төменгі деректемені фокустау</dt>\n' +
|
||||
' <dd>Windows немесе Linux: Alt+F11</dd>\n' +
|
||||
' <dd>macOS: ⌥F11</dd>\n' +
|
||||
' <dt>Хабарландыруды белгілеу</dt>\n' +
|
||||
' <dd>Windows немесе Linux: Alt+F12</dd>\n' +
|
||||
' <dd>macOS: ⌥F12</dd>\n' +
|
||||
' <dt>Мәтінмәндік құралдар тақтасын фокустау</dt>\n' +
|
||||
' <dd>Windows, Linux немесе macOS: Ctrl+F9</dd>\n' +
|
||||
'</dl>\n' +
|
||||
'\n' +
|
||||
'<p>Навигация бөлектелетін немесе Төменгі деректеме элементінің жолындағы бірінші элемент жағдайында асты сызылатын\n' +
|
||||
' бірінші ПИ элементінен басталады.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>ПИ бөлімдері арасында навигациялау</h1>\n' +
|
||||
'\n' +
|
||||
'<p>Бір ПИ бөлімінен келесісіне өту үшін <strong>Tab</strong> пернесін басыңыз.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Бір ПИ бөлімінен алдыңғысына өту үшін <strong>Shift+Tab</strong> пернесін басыңыз.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Осы ПИ бөлімдерінің <strong>Tab</strong> реті:</p>\n' +
|
||||
'\n' +
|
||||
'<ol>\n' +
|
||||
' <li>Мәзір жолағы</li>\n' +
|
||||
' <li>Әрбір құралдар тақтасы тобы</li>\n' +
|
||||
' <li>Бүйірлік жолақ</li>\n' +
|
||||
' <li>Төменгі деректемедегі элемент жолы</li>\n' +
|
||||
' <li>Төменгі деректемедегі сөздер санын ауыстыру түймесі</li>\n' +
|
||||
' <li>Төменгі деректемедегі брендингтік сілтеме</li>\n' +
|
||||
' <li>Төменгі деректемедегі редактор өлшемін өзгерту тұтқасы</li>\n' +
|
||||
'</ol>\n' +
|
||||
'\n' +
|
||||
'<p>ПИ бөлімі көрсетілмесе, ол өткізіп жіберіледі.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Төменгі деректемеде пернетақта навигациясының фокусы болса және бүйірлік жолақ көрінбесе, <strong>Shift+Tab</strong> тіркесімін басу әрекеті\n' +
|
||||
' фокусты соңғысы емес, бірінші құралдар тақтасы тобына жылжытады.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>ПИ бөлімдерінде навигациялау</h1>\n' +
|
||||
'\n' +
|
||||
'<p>Бір ПИ элементінен келесісіне өту үшін <strong>Arrow</strong> (Көрсеткі) пернесін басыңыз.</p>\n' +
|
||||
'\n' +
|
||||
'<p><strong>Left</strong> (Сол жақ) және <strong>Right</strong> (Оң жақ) көрсеткі пернелері</p>\n' +
|
||||
'\n' +
|
||||
'<ul>\n' +
|
||||
' <li>мәзір жолағындағы мәзірлер арасында жылжыту.</li>\n' +
|
||||
' <li>мәзірде ішкі мәзірді ашу.</li>\n' +
|
||||
' <li>құралдар тақтасы тобындағы түймелер арасында жылжыту.</li>\n' +
|
||||
' <li>төменгі деректеме элементінің жолындағы элементтер арасында жылжыту.</li>\n' +
|
||||
'</ul>\n' +
|
||||
'\n' +
|
||||
'<p><strong>Down</strong> (Төмен) және <strong>Up</strong> (Жоғары) көрсеткі пернелері</p>\n' +
|
||||
'\n' +
|
||||
'<ul>\n' +
|
||||
' <li>мәзірдегі мәзір элементтері арасында жылжыту.</li>\n' +
|
||||
' <li>құралдар тақтасының ашылмалы мәзіріндегі мәзір элементтері арасында жылжыту.</li>\n' +
|
||||
'</ul>\n' +
|
||||
'\n' +
|
||||
'<p>Фокусталған ПИ бөліміндегі <strong>Arrow</strong> (Көрсеткі) пернелерінің циклі.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Ашық мәзірді жабу үшін ішкі мәзірді ашып немесе ашылмалы мәзірді ашып, <strong>Esc</strong> пернесін басыңыз.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Ағымдағы фокус белгілі бір ПИ бөлімінің «үстінде» болса, <strong>Esc</strong> пернесін басу әрекеті пернетақта\n' +
|
||||
' навигациясын толығымен жабады.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>Мәзір элементін немесе құралдар тақтасы түймесін орындау</h1>\n' +
|
||||
'\n' +
|
||||
'<p>Қажетті мәзір элементі немесе құралдар тақтасы түймесі бөлектелген кезде, элементті орындау үшін <strong>Return</strong> (Қайтару), <strong>Enter</strong> (Енгізу)\n' +
|
||||
' немесе <strong>Space bar</strong> (Бос орын) пернесін басыңыз.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>Белгіленбеген диалог терезелерін навигациялау</h1>\n' +
|
||||
'\n' +
|
||||
'<p>Белгіленбеген диалог терезелерінде диалог терезесі ашылған кезде бірінші интерактивті құрамдас фокусталады.</p>\n' +
|
||||
'\n' +
|
||||
'<p><strong>Tab</strong> немесе <strong>Shift+Tab</strong> пернесін басу арқылы интерактивті диалог терезесінің құрамдастары арасында навигациялаңыз.</p>\n' +
|
||||
'\n' +
|
||||
'<h1>Белгіленген диалог терезелерін навигациялау</h1>\n' +
|
||||
'\n' +
|
||||
'<p>Белгіленген диалог терезелерінде диалог терезесі ашылған кезде қойынды мәзіріндегі бірінші түйме фокусталады.</p>\n' +
|
||||
'\n' +
|
||||
'<p><strong>Tab</strong> немесе\n' +
|
||||
' <strong>Shift+Tab</strong> пернесін басу арқылы осы диалог терезесі қойындысының интерактивті құрамдастары арасында навигациялаңыз.</p>\n' +
|
||||
'\n' +
|
||||
'<p>Қойынды мәзірінің фокусын беру арқылы басқа диалог терезесінің қойындысына ауысып, тиісті <strong>Arrow</strong> (Көрсеткі)\n' +
|
||||
' пернесін басу арқылы қолжетімді қойындылар арасында айналдыруға болады.</p>\n');
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user